null-bot
A small Telegram bot for extracting and saving opportunities and events from web pages or pasted text. Uses an LLM agent to parse content into structured JSON and stores entries in a local PocketBase instance. This bot uses all open-source tools. The LLM of choice is granite4.1:8b by IBM under their Apache 2.0 License.
Features
- Parse Opportunity (
/op) and Event (/ev) entries from a URL or pasted text - Two entry types with separate system prompts and JSON schemas (externalized to
prompts.py) - Follow-up prompt when users paste text: ask for a source URL only when saving
- Converts date/time to PocketBase-friendly format (
YYYY-MM-DD HH:MM:SS) - Retry decorator for robust LLM / network calls
Requirements
- Python 3.11+ recommended
- See
requirements.txtfor full dependency list
Setup
- Clone the repo or copy files to your machine.
- Create and activate a Python virtual environment:
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
- Install dependencies:
pip install -r requirements.txt
- Environment variables
- Create a
.envfile in the project root with at minimum:
TG_TOKEN=your_telegram_bot_token_here
OLLAMA_BASE_URL=http://localhost:11434/v1
ALLOWED_USERS=1234,5678
POCKETBASE_URL=http://127.0.0.1:8090
POCKETBASE_ADMIN_EMAIL=admin@example.com
POCKETBASE_ADMIN_PASSWORD=secret
- Notes:
ALLOWED_USERSshould be a comma-separated list of Telegram user IDs (no brackets).- The bot reads
TG_TOKENandALLOWED_USERSfrom the environment.
- Ollama (local LLM) setup
-
This project uses a local Ollama instance (or any compatible local LLM HTTP API) as the LLM provider. The bot expects an HTTP endpoint available at
OLLAMA_BASE_URL(defaulthttp://localhost:11434/v1). -
Quick steps to get Ollama running locally:
-
Install Ollama for your platform — follow the official instructions: https://ollama.com/docs (or use the native installer for Windows/macOS/Linux).
-
Pull or install a model you want to use. Example (CLI):
ollama pull granite4.1:8b- Start the Ollama daemon / HTTP API so the bot can reach it. Depending on your Ollama installation this may be:
# example commands — consult your Ollama docs if these differ ollama serve # or ollama daemon- Set
OLLAMA_BASE_URLin your.envto point to the running API, for example:
OLLAMA_BASE_URL=http://localhost:11434/v1- Verify the API is reachable (example curl):
curl -s -X POST "${OLLAMA_BASE_URL}/completions" \ -H "Content-Type: application/json" \ -d '{"model":"<model-name>","prompt":"hello","max_tokens":16}'A successful response indicates your Ollama HTTP API is reachable and can serve model requests.
-
-
Notes and troubleshooting
- If your Ollama installation exposes a different port or path, update
OLLAMA_BASE_URLaccordingly. - If you prefer hosted LLMs (OpenAI, Anthropic, Cohere, etc.),
agent.pycan be adapted to use other providers; ensure the provider client is configured and the prompts inprompts.pyare compatible.
- If your Ollama installation exposes a different port or path, update
Running the bot
Start the bot with the project's entrypoint (example):
python main.py
The bot listens for commands:
/op <url or paste>— parse an opportunity/ev <url or paste>— parse an event- If you send a URL directly in chat, the bot will ask whether to process it as an event or an opportunity using buttons.
If you paste text (instead of sending a URL), the bot will parse it and when you click Save it will prompt you for a source URL (or you can /skip).
How it works (high-level)
agent.pyusespydantic-ai+ a local LLM provider (e.g. Ollama) and system prompts fromprompts.pyto parse pages/text into structured JSON.database.pyconverts datetime fields and uploads the entry to the appropriate PocketBase collection (eventsoropportunities).bot.pyhandles Telegram interactions, queues parse tasks, and preserves per-user state incontext.user_data.
Troubleshooting
- If dates show as
Noneafter save: verify PocketBase field names (datetimefor events,deadlinefor opportunities) and ensure.envis configured. - If the bot doesn't start: check
TG_TOKENis present and valid. - If parsing fails or you see unexpected behavior, check logs printed to the console for
convert_datetime_to_pocketbase()andupload_entry()debug messages.