TeLLMgramBot 3.9.2__tar.gz → 3.10.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tellmgrambot-3.10.1/PKG-INFO +174 -0
- tellmgrambot-3.10.1/README.md +142 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/TeLLMgramBot.py +102 -13
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/conversation.py +46 -30
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/database.py +197 -39
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/initialize.py +4 -10
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/providers/anthropic_provider.py +31 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/utils.py +16 -9
- tellmgrambot-3.10.1/TeLLMgramBot.egg-info/PKG-INFO +174 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/setup.py +1 -1
- tellmgrambot-3.9.2/PKG-INFO +0 -205
- tellmgrambot-3.9.2/README.md +0 -173
- tellmgrambot-3.9.2/TeLLMgramBot.egg-info/PKG-INFO +0 -205
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/LICENSE +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/__init__.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/message_handlers.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/models.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/providers/__init__.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/providers/base.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/providers/factory.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/providers/openai_provider.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot/web_utils.py +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot.egg-info/SOURCES.txt +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot.egg-info/dependency_links.txt +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot.egg-info/requires.txt +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/TeLLMgramBot.egg-info/top_level.txt +0 -0
- {tellmgrambot-3.9.2 → tellmgrambot-3.10.1}/setup.cfg +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TeLLMgramBot
|
|
3
|
+
Version: 3.10.1
|
|
4
|
+
Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
|
|
5
|
+
Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
|
|
6
|
+
Author: Digital Heresy
|
|
7
|
+
Author-email: ronin.atx@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: openai>2.0
|
|
13
|
+
Requires-Dist: anthropic>=0.40
|
|
14
|
+
Requires-Dist: PyYAML
|
|
15
|
+
Requires-Dist: httpx
|
|
16
|
+
Requires-Dist: beautifulsoup4
|
|
17
|
+
Requires-Dist: validators
|
|
18
|
+
Requires-Dist: tiktoken>=0.12
|
|
19
|
+
Requires-Dist: python-telegram-bot>=20.8
|
|
20
|
+
Requires-Dist: aiosqlite>=0.19
|
|
21
|
+
Requires-Dist: tzdata>=2025.2
|
|
22
|
+
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
32
|
+
|
|
33
|
+
# TeLLMgramBot
|
|
34
|
+
The basic goal of this project is to create a bridge between a Telegram Bot and a Large Language Model (LLM), supporting both OpenAI's GPT models and Anthropic's Claude models.
|
|
35
|
+
* To use this library, you must have a Telegram account **with a user name**, not just a phone number. If you don't have one, [create one online](https://telegram.org/).
|
|
36
|
+
* If added to a Telegram group, the bot must be [administrator](https://www.alphr.com/add-admin-telegram/) in order to respond to a user calling out its name, initials, or nickname.
|
|
37
|
+
<img src="assets/TeLLMgramBot_Logo.png" width=200 align=center />
|
|
38
|
+
|
|
39
|
+
## Telegram Bot + LLM Encapsulation
|
|
40
|
+
* The Telegram interface handles special commands and basic "chatty" responses that don't require an LLM, like "Hello". Dynamic conversations are handed off to the LLM while Telegram acts as the interaction broker.
|
|
41
|
+
* Pass URLs in [square brackets] and mention how the bot should interpret them.
|
|
42
|
+
* Example: "What do you think of this article? [https://some_site/article]"
|
|
43
|
+
* Uses a separate model (configurable via `url_model`) to handle larger URL content.
|
|
44
|
+
* Ask questions about message history across all your chats using natural language; the bot will search, attribute messages to speakers, and include messages from other bots.
|
|
45
|
+
* Example: "Who said thanks for the breakdown?" or "What did George say about the project?" or "Show me the last few messages."
|
|
46
|
+
* All search filters (speaker, chat, date) are optional. Results are ordered most-recent-first. Configure `search_limit` to control how many results to return (default: 30).
|
|
47
|
+
* Token limits measure conversation length and determine when to prune oldest messages to stay within model limits.
|
|
48
|
+
* The bot loads the user's full history across all chats up to 50% of the token budget. In private chats, shared group context fills the remaining budget, enabling the bot to reference group conversations from a private context.
|
|
49
|
+
* This eliminates amnesia when switching between private and group chats.
|
|
50
|
+
* Users can manage privacy via two commands:
|
|
51
|
+
* `/forget` - In private chats, clears your full conversation and resets all active sessions. In group chats, removes only your messages and cleans up paired bot replies.
|
|
52
|
+
* `/private` - Toggle private mode (private chats only). When ON, your messages in private chats are excluded from group conversation contexts, enabling selective privacy even in shared groups.
|
|
53
|
+
|
|
54
|
+
## Why Telegram?
|
|
55
|
+
Using Telegram as the interface not only solves "exposing" the interface, but gives you boatloads of interactivity over a standard Command Line interface, or trying to create a website with input boxes and submit buttons to try to handle everything:
|
|
56
|
+
1. Telegram already lets you paste in verbose, multiline messages.
|
|
57
|
+
2. Telegram already lets you paste in pictures, videos, links, etc.
|
|
58
|
+
3. Telegram already lets you react with emojis, stickers, etc.
|
|
59
|
+
4. Telegram message reactions (👀) provide a lightweight read receipt without breaking conversation flow.
|
|
60
|
+
|
|
61
|
+
## Supported LLM Providers
|
|
62
|
+
TeLLMgramBot selects the LLM provider automatically based on the model name:
|
|
63
|
+
|
|
64
|
+
| Model prefix | Provider | Example models |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `gpt-` | OpenAI | `gpt-4o`, `gpt-4o-mini`, `gpt-5-mini` |
|
|
67
|
+
| `claude-` | Anthropic | `claude-sonnet-4-6`, `claude-haiku-4-5` |
|
|
68
|
+
|
|
69
|
+
Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to any supported model and supply the corresponding API key - no other changes needed.
|
|
70
|
+
|
|
71
|
+
## Directories
|
|
72
|
+
TeLLMgramBot creates the following directories:
|
|
73
|
+
|
|
74
|
+
- **`configs`** - Bot configuration and model parameters
|
|
75
|
+
- `config.yaml` - Bot settings: owner, model names, token limits, search limits, nickname/initials
|
|
76
|
+
- `models.yaml` - Token limits for each LLM model (pre-populated on first run)
|
|
77
|
+
- **`prompts`** - Bot personas and URL analysis templates
|
|
78
|
+
- `test_personality.prmpt` - Sample bot personality (multi-provider, can be renamed)
|
|
79
|
+
- `url_analysis.prmpt` - URL summarization prompt template
|
|
80
|
+
- A system appendix is automatically appended to every persona at runtime, teaching the LLM about cross-chat memory and search behavior. User messages include speaker annotations with chat context and timestamps so the LLM always knows who is speaking, in which chat, and when.
|
|
81
|
+
- **`logs`** - Bot instance logs (one per startup, e.g. `tellmgrambot_2026-03-29_10-30-45.log`)
|
|
82
|
+
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only.
|
|
83
|
+
- Bot keeps the 10 most recent logs, automatically pruning older ones.
|
|
84
|
+
- Pass `-v` or `--verbose` on startup for DEBUG-level logging.
|
|
85
|
+
- **`data`** - SQLite database (`conversations.db`) storing all messages, users, and chats
|
|
86
|
+
- Users manage their data via `/forget` and `/private` commands.
|
|
87
|
+
|
|
88
|
+
### Environment Variables for Paths
|
|
89
|
+
Override default directory locations by setting these environment variables (useful for containerized deployments):
|
|
90
|
+
|
|
91
|
+
| Variable | Purpose | Default |
|
|
92
|
+
|----------|---------|---------|
|
|
93
|
+
| `TELLMGRAMBOT_CONFIGS_PATH` | Directory containing `config.yaml` and `models.yaml` | `{exec_dir}/configs` |
|
|
94
|
+
| `TELLMGRAMBOT_PROMPTS_PATH` | Directory containing prompt files | `{exec_dir}/prompts` |
|
|
95
|
+
| `TELLMGRAMBOT_LOGS_PATH` | Directory for log files | `{exec_dir}/logs` |
|
|
96
|
+
| `TELLMGRAMBOT_DATA_PATH` | Directory containing `conversations.db` | `{exec_dir}/data` |
|
|
97
|
+
|
|
98
|
+
If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
|
|
99
|
+
|
|
100
|
+
## API Keys
|
|
101
|
+
TeLLMgramBot supports four API keys, each loadable from environment variables or `.key` files:
|
|
102
|
+
|
|
103
|
+
| Key | Env Var | File | When required |
|
|
104
|
+
|-----|---------|------|---------------|
|
|
105
|
+
| [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
|
|
106
|
+
| [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
|
|
107
|
+
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `telegram.key` | Always required |
|
|
108
|
+
| [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
|
|
109
|
+
|
|
110
|
+
Missing provider keys (OpenAI or Anthropic) disable chat and URL analysis but allow the bot to start. Missing VirusTotal disables URL analysis. Telegram key is required - the bot will not start without it.
|
|
111
|
+
|
|
112
|
+
Key files are created in the execution directory (or `TELLMGRAMBOT_KEYS_PATH` for legacy deployments). Alternatively, set environment variables before launching, e.g.:
|
|
113
|
+
```python
|
|
114
|
+
os.environ['TELLMGRAMBOT_OPENAI_API_KEY'] = my_vault.get('openai_key')
|
|
115
|
+
os.environ['TELLMGRAMBOT_ANTHROPIC_API_KEY'] = my_vault.get('anthropic_key')
|
|
116
|
+
os.environ['TELLMGRAMBOT_TELEGRAM_API_KEY'] = my_vault.get('telegram_key')
|
|
117
|
+
os.environ['TELLMGRAMBOT_VIRUSTOTAL_API_KEY'] = my_vault.get('virustotal_key')
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Commands and Interactions
|
|
121
|
+
|
|
122
|
+
### Available Commands
|
|
123
|
+
- `/nick <name>` - Set your nickname (for bot use in group chats).
|
|
124
|
+
- `/forget` - Clear your conversation history. In private chats, clears everything and resets all active sessions. In group chats, removes only your messages.
|
|
125
|
+
- `/private` - Toggle private mode (private chats only). When ON, your messages are excluded from group context loading.
|
|
126
|
+
- `/help` - Display all available commands and usage information (includes administrator-only commands).
|
|
127
|
+
|
|
128
|
+
### Group Chat Triggers
|
|
129
|
+
The bot responds in groups when you:
|
|
130
|
+
- Mention the bot by username (e.g., `@botname`)
|
|
131
|
+
- Mention the bot by nickname or initials (configured via `config.yaml`)
|
|
132
|
+
- Reply directly to one of the bot's messages
|
|
133
|
+
|
|
134
|
+
If a message explicitly @mentions another account, the bot defers with "Looks like that's for @OtherBot!" instead.
|
|
135
|
+
|
|
136
|
+
The bot also automatically captures and indexes messages from other bots in the group, making them searchable and available as context in private chats. For example, you can ask "What did Bot B say about the project?" and the bot will search across all shared groups.
|
|
137
|
+
|
|
138
|
+
### Read Receipt (Group Chats Only)
|
|
139
|
+
When triggered and not deferring, the bot immediately sends a 👀 reaction on your message (falls back to "Got it!" on older clients) before generating the full LLM response.
|
|
140
|
+
|
|
141
|
+
## Bot Setup
|
|
142
|
+
1. Ensure API keys are set up and your Telegram bot is created via BotFather.
|
|
143
|
+
2. Install TeLLMgramBot: `pip install TeLLMgramBot`
|
|
144
|
+
3. Configure the bot via `config.yaml` (created on first run):
|
|
145
|
+
- `bot_owner`: Your Telegram username (required, no `@`)
|
|
146
|
+
- `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
|
|
147
|
+
- `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
|
|
148
|
+
- `bot_nickname` / `bot_initials`: Names the bot responds to in groups
|
|
149
|
+
- `token_limit`: Max tokens (optional; defaults to model's maximum)
|
|
150
|
+
- `search_limit`: Max search results (optional; defaults to 30)
|
|
151
|
+
4. **Disable group privacy mode in BotFather:**
|
|
152
|
+
```
|
|
153
|
+
/setprivacy -> select your bot -> Disable
|
|
154
|
+
```
|
|
155
|
+
With privacy mode enabled (default), the bot won't receive group messages that don't mention it, so it can't index other bots or load cross-chat context.
|
|
156
|
+
5. Run the bot:
|
|
157
|
+
```python
|
|
158
|
+
from TeLLMgramBot import TelegramBot
|
|
159
|
+
telegram_bot = TelegramBot.set()
|
|
160
|
+
telegram_bot.start_polling()
|
|
161
|
+
```
|
|
162
|
+
Once you see `TeLLMgramBot polling...`, the bot is online.
|
|
163
|
+
6. Type `/help` in Telegram to see all available commands.
|
|
164
|
+
|
|
165
|
+
## Resources
|
|
166
|
+
* GitHub repository [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) has guides to create a Telegram bot.
|
|
167
|
+
* For more information on OpenAI models and token limits:
|
|
168
|
+
* [OpenAI model overview and maximum tokens](https://platform.openai.com/docs/models)
|
|
169
|
+
* [OpenAI message conversion to tokens](https://github.com/openai/openai-python)
|
|
170
|
+
* [OpenAI custom fine-tuning](https://platform.openai.com/docs/guides/model-optimization)
|
|
171
|
+
* [OpenAI's tiktoken library](https://github.com/openai/tiktoken/tree/main)
|
|
172
|
+
* For more information on Anthropic Claude models:
|
|
173
|
+
* [Anthropic model overview and context windows](https://docs.anthropic.com/en/docs/about-claude/models)
|
|
174
|
+
* [Anthropic Python SDK](https://github.com/anthropic/anthropic-sdk-python)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# TeLLMgramBot
|
|
2
|
+
The basic goal of this project is to create a bridge between a Telegram Bot and a Large Language Model (LLM), supporting both OpenAI's GPT models and Anthropic's Claude models.
|
|
3
|
+
* To use this library, you must have a Telegram account **with a user name**, not just a phone number. If you don't have one, [create one online](https://telegram.org/).
|
|
4
|
+
* If added to a Telegram group, the bot must be [administrator](https://www.alphr.com/add-admin-telegram/) in order to respond to a user calling out its name, initials, or nickname.
|
|
5
|
+
<img src="assets/TeLLMgramBot_Logo.png" width=200 align=center />
|
|
6
|
+
|
|
7
|
+
## Telegram Bot + LLM Encapsulation
|
|
8
|
+
* The Telegram interface handles special commands and basic "chatty" responses that don't require an LLM, like "Hello". Dynamic conversations are handed off to the LLM while Telegram acts as the interaction broker.
|
|
9
|
+
* Pass URLs in [square brackets] and mention how the bot should interpret them.
|
|
10
|
+
* Example: "What do you think of this article? [https://some_site/article]"
|
|
11
|
+
* Uses a separate model (configurable via `url_model`) to handle larger URL content.
|
|
12
|
+
* Ask questions about message history across all your chats using natural language; the bot will search, attribute messages to speakers, and include messages from other bots.
|
|
13
|
+
* Example: "Who said thanks for the breakdown?" or "What did George say about the project?" or "Show me the last few messages."
|
|
14
|
+
* All search filters (speaker, chat, date) are optional. Results are ordered most-recent-first. Configure `search_limit` to control how many results to return (default: 30).
|
|
15
|
+
* Token limits measure conversation length and determine when to prune oldest messages to stay within model limits.
|
|
16
|
+
* The bot loads the user's full history across all chats up to 50% of the token budget. In private chats, shared group context fills the remaining budget, enabling the bot to reference group conversations from a private context.
|
|
17
|
+
* This eliminates amnesia when switching between private and group chats.
|
|
18
|
+
* Users can manage privacy via two commands:
|
|
19
|
+
* `/forget` - In private chats, clears your full conversation and resets all active sessions. In group chats, removes only your messages and cleans up paired bot replies.
|
|
20
|
+
* `/private` - Toggle private mode (private chats only). When ON, your messages in private chats are excluded from group conversation contexts, enabling selective privacy even in shared groups.
|
|
21
|
+
|
|
22
|
+
## Why Telegram?
|
|
23
|
+
Using Telegram as the interface not only solves "exposing" the interface, but gives you boatloads of interactivity over a standard Command Line interface, or trying to create a website with input boxes and submit buttons to try to handle everything:
|
|
24
|
+
1. Telegram already lets you paste in verbose, multiline messages.
|
|
25
|
+
2. Telegram already lets you paste in pictures, videos, links, etc.
|
|
26
|
+
3. Telegram already lets you react with emojis, stickers, etc.
|
|
27
|
+
4. Telegram message reactions (👀) provide a lightweight read receipt without breaking conversation flow.
|
|
28
|
+
|
|
29
|
+
## Supported LLM Providers
|
|
30
|
+
TeLLMgramBot selects the LLM provider automatically based on the model name:
|
|
31
|
+
|
|
32
|
+
| Model prefix | Provider | Example models |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `gpt-` | OpenAI | `gpt-4o`, `gpt-4o-mini`, `gpt-5-mini` |
|
|
35
|
+
| `claude-` | Anthropic | `claude-sonnet-4-6`, `claude-haiku-4-5` |
|
|
36
|
+
|
|
37
|
+
Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to any supported model and supply the corresponding API key - no other changes needed.
|
|
38
|
+
|
|
39
|
+
## Directories
|
|
40
|
+
TeLLMgramBot creates the following directories:
|
|
41
|
+
|
|
42
|
+
- **`configs`** - Bot configuration and model parameters
|
|
43
|
+
- `config.yaml` - Bot settings: owner, model names, token limits, search limits, nickname/initials
|
|
44
|
+
- `models.yaml` - Token limits for each LLM model (pre-populated on first run)
|
|
45
|
+
- **`prompts`** - Bot personas and URL analysis templates
|
|
46
|
+
- `test_personality.prmpt` - Sample bot personality (multi-provider, can be renamed)
|
|
47
|
+
- `url_analysis.prmpt` - URL summarization prompt template
|
|
48
|
+
- A system appendix is automatically appended to every persona at runtime, teaching the LLM about cross-chat memory and search behavior. User messages include speaker annotations with chat context and timestamps so the LLM always knows who is speaking, in which chat, and when.
|
|
49
|
+
- **`logs`** - Bot instance logs (one per startup, e.g. `tellmgrambot_2026-03-29_10-30-45.log`)
|
|
50
|
+
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only.
|
|
51
|
+
- Bot keeps the 10 most recent logs, automatically pruning older ones.
|
|
52
|
+
- Pass `-v` or `--verbose` on startup for DEBUG-level logging.
|
|
53
|
+
- **`data`** - SQLite database (`conversations.db`) storing all messages, users, and chats
|
|
54
|
+
- Users manage their data via `/forget` and `/private` commands.
|
|
55
|
+
|
|
56
|
+
### Environment Variables for Paths
|
|
57
|
+
Override default directory locations by setting these environment variables (useful for containerized deployments):
|
|
58
|
+
|
|
59
|
+
| Variable | Purpose | Default |
|
|
60
|
+
|----------|---------|---------|
|
|
61
|
+
| `TELLMGRAMBOT_CONFIGS_PATH` | Directory containing `config.yaml` and `models.yaml` | `{exec_dir}/configs` |
|
|
62
|
+
| `TELLMGRAMBOT_PROMPTS_PATH` | Directory containing prompt files | `{exec_dir}/prompts` |
|
|
63
|
+
| `TELLMGRAMBOT_LOGS_PATH` | Directory for log files | `{exec_dir}/logs` |
|
|
64
|
+
| `TELLMGRAMBOT_DATA_PATH` | Directory containing `conversations.db` | `{exec_dir}/data` |
|
|
65
|
+
|
|
66
|
+
If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
|
|
67
|
+
|
|
68
|
+
## API Keys
|
|
69
|
+
TeLLMgramBot supports four API keys, each loadable from environment variables or `.key` files:
|
|
70
|
+
|
|
71
|
+
| Key | Env Var | File | When required |
|
|
72
|
+
|-----|---------|------|---------------|
|
|
73
|
+
| [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
|
|
74
|
+
| [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
|
|
75
|
+
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `telegram.key` | Always required |
|
|
76
|
+
| [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
|
|
77
|
+
|
|
78
|
+
Missing provider keys (OpenAI or Anthropic) disable chat and URL analysis but allow the bot to start. Missing VirusTotal disables URL analysis. Telegram key is required - the bot will not start without it.
|
|
79
|
+
|
|
80
|
+
Key files are created in the execution directory (or `TELLMGRAMBOT_KEYS_PATH` for legacy deployments). Alternatively, set environment variables before launching, e.g.:
|
|
81
|
+
```python
|
|
82
|
+
os.environ['TELLMGRAMBOT_OPENAI_API_KEY'] = my_vault.get('openai_key')
|
|
83
|
+
os.environ['TELLMGRAMBOT_ANTHROPIC_API_KEY'] = my_vault.get('anthropic_key')
|
|
84
|
+
os.environ['TELLMGRAMBOT_TELEGRAM_API_KEY'] = my_vault.get('telegram_key')
|
|
85
|
+
os.environ['TELLMGRAMBOT_VIRUSTOTAL_API_KEY'] = my_vault.get('virustotal_key')
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Commands and Interactions
|
|
89
|
+
|
|
90
|
+
### Available Commands
|
|
91
|
+
- `/nick <name>` - Set your nickname (for bot use in group chats).
|
|
92
|
+
- `/forget` - Clear your conversation history. In private chats, clears everything and resets all active sessions. In group chats, removes only your messages.
|
|
93
|
+
- `/private` - Toggle private mode (private chats only). When ON, your messages are excluded from group context loading.
|
|
94
|
+
- `/help` - Display all available commands and usage information (includes administrator-only commands).
|
|
95
|
+
|
|
96
|
+
### Group Chat Triggers
|
|
97
|
+
The bot responds in groups when you:
|
|
98
|
+
- Mention the bot by username (e.g., `@botname`)
|
|
99
|
+
- Mention the bot by nickname or initials (configured via `config.yaml`)
|
|
100
|
+
- Reply directly to one of the bot's messages
|
|
101
|
+
|
|
102
|
+
If a message explicitly @mentions another account, the bot defers with "Looks like that's for @OtherBot!" instead.
|
|
103
|
+
|
|
104
|
+
The bot also automatically captures and indexes messages from other bots in the group, making them searchable and available as context in private chats. For example, you can ask "What did Bot B say about the project?" and the bot will search across all shared groups.
|
|
105
|
+
|
|
106
|
+
### Read Receipt (Group Chats Only)
|
|
107
|
+
When triggered and not deferring, the bot immediately sends a 👀 reaction on your message (falls back to "Got it!" on older clients) before generating the full LLM response.
|
|
108
|
+
|
|
109
|
+
## Bot Setup
|
|
110
|
+
1. Ensure API keys are set up and your Telegram bot is created via BotFather.
|
|
111
|
+
2. Install TeLLMgramBot: `pip install TeLLMgramBot`
|
|
112
|
+
3. Configure the bot via `config.yaml` (created on first run):
|
|
113
|
+
- `bot_owner`: Your Telegram username (required, no `@`)
|
|
114
|
+
- `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
|
|
115
|
+
- `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
|
|
116
|
+
- `bot_nickname` / `bot_initials`: Names the bot responds to in groups
|
|
117
|
+
- `token_limit`: Max tokens (optional; defaults to model's maximum)
|
|
118
|
+
- `search_limit`: Max search results (optional; defaults to 30)
|
|
119
|
+
4. **Disable group privacy mode in BotFather:**
|
|
120
|
+
```
|
|
121
|
+
/setprivacy -> select your bot -> Disable
|
|
122
|
+
```
|
|
123
|
+
With privacy mode enabled (default), the bot won't receive group messages that don't mention it, so it can't index other bots or load cross-chat context.
|
|
124
|
+
5. Run the bot:
|
|
125
|
+
```python
|
|
126
|
+
from TeLLMgramBot import TelegramBot
|
|
127
|
+
telegram_bot = TelegramBot.set()
|
|
128
|
+
telegram_bot.start_polling()
|
|
129
|
+
```
|
|
130
|
+
Once you see `TeLLMgramBot polling...`, the bot is online.
|
|
131
|
+
6. Type `/help` in Telegram to see all available commands.
|
|
132
|
+
|
|
133
|
+
## Resources
|
|
134
|
+
* GitHub repository [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) has guides to create a Telegram bot.
|
|
135
|
+
* For more information on OpenAI models and token limits:
|
|
136
|
+
* [OpenAI model overview and maximum tokens](https://platform.openai.com/docs/models)
|
|
137
|
+
* [OpenAI message conversion to tokens](https://github.com/openai/openai-python)
|
|
138
|
+
* [OpenAI custom fine-tuning](https://platform.openai.com/docs/guides/model-optimization)
|
|
139
|
+
* [OpenAI's tiktoken library](https://github.com/openai/tiktoken/tree/main)
|
|
140
|
+
* For more information on Anthropic Claude models:
|
|
141
|
+
* [Anthropic model overview and context windows](https://docs.anthropic.com/en/docs/about-claude/models)
|
|
142
|
+
* [Anthropic Python SDK](https://github.com/anthropic/anthropic-sdk-python)
|
|
@@ -17,12 +17,17 @@ from .utils import format_dt
|
|
|
17
17
|
from .database import (
|
|
18
18
|
get_private_mode,
|
|
19
19
|
set_private_mode,
|
|
20
|
+
insert_message,
|
|
20
21
|
delete_messages_for_user,
|
|
21
22
|
delete_messages_for_chat,
|
|
22
23
|
delete_private_messages_for_user,
|
|
23
24
|
delete_bot_replies_for_user,
|
|
24
25
|
get_shared_group_chat_ids,
|
|
26
|
+
prune_foreign_bot_messages,
|
|
25
27
|
search_messages,
|
|
28
|
+
upsert_chat,
|
|
29
|
+
upsert_user,
|
|
30
|
+
wipe_all_data,
|
|
26
31
|
)
|
|
27
32
|
from .initialize import (
|
|
28
33
|
INIT_BOT_CONFIG,
|
|
@@ -38,11 +43,15 @@ from .utils import exact_word_match, log_error
|
|
|
38
43
|
|
|
39
44
|
logger = logging.getLogger(__name__)
|
|
40
45
|
|
|
46
|
+
_FOREIGN_BOT_MESSAGE_CAP = 50
|
|
47
|
+
|
|
41
48
|
_SEARCH_TOOL = {
|
|
42
49
|
"name": "search_messages",
|
|
43
50
|
"description": (
|
|
44
51
|
"Search the full message history across the user's private chat and shared group chats. "
|
|
45
52
|
"Use whenever the user asks who said something, what someone said, or what was discussed. "
|
|
53
|
+
"Always search before claiming a person has no message history -- do not assume from context alone. "
|
|
54
|
+
"Run the search immediately when it would help answer the question -- do not ask the user for permission to search. "
|
|
46
55
|
"All filters are optional -- omit them to retrieve recent messages broadly. "
|
|
47
56
|
"Results are ordered most-recent-first; to find the earliest message, look at the last result."
|
|
48
57
|
),
|
|
@@ -52,8 +61,8 @@ _SEARCH_TOOL = {
|
|
|
52
61
|
"content_query": {"type": "string", "description": "Text to search for in message content"},
|
|
53
62
|
"speaker_query": {"type": "string", "description": "Name or @username of the person who sent the message. When the user refers to their own messages ('I said', 'my messages', 'what did I say'), use the current user's name or @username from the system prompt. If multiple users match, the search will return an ambiguity error asking you to clarify."},
|
|
54
63
|
"chat_query": {"type": "string", "description": "Name of the group chat to search within. Use the exact chat title if known. If multiple chats match, the search will return an ambiguity error asking you to clarify."},
|
|
55
|
-
"date_from": {"type": "string", "description": "ISO
|
|
56
|
-
"date_to": {"type": "string", "description": "ISO
|
|
64
|
+
"date_from": {"type": "string", "description": "Start of time range as ISO datetime (YYYY-MM-DDTHH:MM). For a full day, use T00:00."},
|
|
65
|
+
"date_to": {"type": "string", "description": "End of time range as ISO datetime (YYYY-MM-DDTHH:MM). For a full day, use T23:59."},
|
|
57
66
|
},
|
|
58
67
|
"required": [],
|
|
59
68
|
},
|
|
@@ -91,6 +100,7 @@ class TelegramBot:
|
|
|
91
100
|
"Administrator-only commands:\n"
|
|
92
101
|
"/start - Go online to receive new responses (default).\n"
|
|
93
102
|
"/stop - Go offline to prevent new responses.\n"
|
|
103
|
+
"/wipe - Permanently delete all conversation data (irreversible).\n"
|
|
94
104
|
)
|
|
95
105
|
|
|
96
106
|
async def tele_start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
@@ -112,6 +122,18 @@ class TelegramBot:
|
|
|
112
122
|
else:
|
|
113
123
|
await update.message.reply_text("Sorry, I can't do that for you.")
|
|
114
124
|
|
|
125
|
+
async def tele_wipe_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
126
|
+
"""A bot_owner-only command to permanently delete all messages, users, and chats from the database."""
|
|
127
|
+
uname = update.message.from_user.username
|
|
128
|
+
if uname != self.telegram['owner']:
|
|
129
|
+
await update.message.reply_text("Sorry, I can't do that for you.")
|
|
130
|
+
return
|
|
131
|
+
await wipe_all_data()
|
|
132
|
+
self.conversations.clear()
|
|
133
|
+
self.token_warning.clear()
|
|
134
|
+
context.application.bot_data.pop('_foreign_bot_reply_seen', None)
|
|
135
|
+
await update.message.reply_text("All conversation data wiped.")
|
|
136
|
+
|
|
115
137
|
async def tele_nick_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
116
138
|
"""
|
|
117
139
|
Let bot assistant know to call the user by a different name other than the Telegram username.
|
|
@@ -286,9 +308,8 @@ class TelegramBot:
|
|
|
286
308
|
|
|
287
309
|
conv = self.conversations[chat_id]
|
|
288
310
|
|
|
289
|
-
# Refresh datetime
|
|
311
|
+
# Refresh datetime on every message
|
|
290
312
|
conv.update_datetime()
|
|
291
|
-
conv.update_current_user(first_name, last_name, username)
|
|
292
313
|
|
|
293
314
|
token_budget = floor(self.llm['prune_threshold'] / 2)
|
|
294
315
|
|
|
@@ -360,6 +381,30 @@ class TelegramBot:
|
|
|
360
381
|
|
|
361
382
|
return reply
|
|
362
383
|
|
|
384
|
+
async def _store_foreign_bot_message(self, msg: Message, chat: Chat) -> None:
|
|
385
|
+
"""
|
|
386
|
+
Persist a group message from another bot to the database.
|
|
387
|
+
|
|
388
|
+
Called when a bot message is received in a group chat. Stores the message with
|
|
389
|
+
is_foreign_bot=True so it is searchable via the LLM search tool. Enforces the
|
|
390
|
+
per-chat cap (_FOREIGN_BOT_MESSAGE_CAP) by pruning the oldest rows after insert.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
msg: The incoming Telegram Message from the foreign bot.
|
|
394
|
+
chat: The group chat the message was sent in.
|
|
395
|
+
"""
|
|
396
|
+
bot = msg.from_user
|
|
397
|
+
await upsert_user(bot.id, bot.username, bot.first_name, bot.last_name)
|
|
398
|
+
await upsert_chat(chat.id, chat.type, chat.title)
|
|
399
|
+
await insert_message(
|
|
400
|
+
chat_id=chat.id,
|
|
401
|
+
user_id=bot.id,
|
|
402
|
+
role='user',
|
|
403
|
+
content=msg.text,
|
|
404
|
+
is_foreign_bot=True,
|
|
405
|
+
)
|
|
406
|
+
await prune_foreign_bot_messages(chat.id, _FOREIGN_BOT_MESSAGE_CAP)
|
|
407
|
+
|
|
363
408
|
def _foreign_bot_mention(self, msg: Message) -> str | None:
|
|
364
409
|
"""
|
|
365
410
|
Return the @username of the first explicitly @mentioned account that is not us, or None.
|
|
@@ -418,7 +463,12 @@ class TelegramBot:
|
|
|
418
463
|
"""
|
|
419
464
|
Route incoming Telegram messages to appropriate handlers based on chat type and trigger conditions.
|
|
420
465
|
|
|
421
|
-
In group/supergroup chats,
|
|
466
|
+
In group/supergroup chats, messages from other bots (user.is_bot=True, user.id != our bot_id)
|
|
467
|
+
are immediately persisted to the database via _store_foreign_bot_message() and the handler returns
|
|
468
|
+
without generating an LLM response. This makes foreign bot messages searchable via the search tool.
|
|
469
|
+
|
|
470
|
+
For non-bot messages in group/supergroup chats, the bot responds when any of the following
|
|
471
|
+
conditions are met:
|
|
422
472
|
- User mentions the bot by @username
|
|
423
473
|
- User mentions the bot by nickname (set via config)
|
|
424
474
|
- User mentions the bot by initials (set via config)
|
|
@@ -427,7 +477,7 @@ class TelegramBot:
|
|
|
427
477
|
In all matching group scenarios, a read receipt acknowledgement (👀 reaction or "Got it!" fallback)
|
|
428
478
|
is sent immediately via _send_read_receipt() before generating the full LLM response.
|
|
429
479
|
|
|
430
|
-
In private chats, the bot responds to all messages.
|
|
480
|
+
In private chats, the bot responds to all messages (Telegram does not deliver bot-to-bot DMs).
|
|
431
481
|
|
|
432
482
|
Args:
|
|
433
483
|
update: The Telegram Update object containing the incoming message.
|
|
@@ -438,6 +488,41 @@ class TelegramBot:
|
|
|
438
488
|
return
|
|
439
489
|
(msg, chat, user) = validated
|
|
440
490
|
|
|
491
|
+
# Store foreign bot messages in group chats and return - no LLM response needed.
|
|
492
|
+
if (
|
|
493
|
+
user.is_bot and
|
|
494
|
+
user.id != self.telegram['bot_id'] and
|
|
495
|
+
msg.text and
|
|
496
|
+
chat.type in ('group', 'supergroup')
|
|
497
|
+
):
|
|
498
|
+
await self._store_foreign_bot_message(msg, chat)
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
# Opportunistically capture foreign bot messages via reply_to_message.
|
|
502
|
+
# Telegram does not deliver bot messages as standalone updates; this extracts
|
|
503
|
+
# them when a user replies to another bot so they become searchable.
|
|
504
|
+
# Deduplicate: multiple users replying to the same bot message would each
|
|
505
|
+
# trigger capture; use a short-lived in-memory set keyed by (chat, bot, msg_id).
|
|
506
|
+
reply = msg.reply_to_message
|
|
507
|
+
if (
|
|
508
|
+
reply and
|
|
509
|
+
reply.from_user and
|
|
510
|
+
reply.from_user.is_bot and
|
|
511
|
+
reply.from_user.id != self.telegram['bot_id'] and
|
|
512
|
+
reply.text and
|
|
513
|
+
chat.type in ('group', 'supergroup')
|
|
514
|
+
):
|
|
515
|
+
recently_seen = context.application.bot_data.setdefault('_foreign_bot_reply_seen', {})
|
|
516
|
+
dedupe_key = (chat.id, reply.from_user.id, reply.message_id)
|
|
517
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
518
|
+
cutoff = now - datetime.timedelta(hours=1)
|
|
519
|
+
stale = [k for k, seen_at in recently_seen.items() if seen_at < cutoff]
|
|
520
|
+
for k in stale:
|
|
521
|
+
recently_seen.pop(k, None)
|
|
522
|
+
if dedupe_key not in recently_seen:
|
|
523
|
+
recently_seen[dedupe_key] = now
|
|
524
|
+
await self._store_foreign_bot_message(reply, chat)
|
|
525
|
+
|
|
441
526
|
# If it's a group text, only reply if the bot is named
|
|
442
527
|
# The real magic of how the bot behaves is in tele_handle_response()
|
|
443
528
|
response = "Sorry, I couldn't process your message! Please contact my creator."
|
|
@@ -448,21 +533,21 @@ class TelegramBot:
|
|
|
448
533
|
msg.reply_to_message.from_user.id == self.telegram['bot_id']
|
|
449
534
|
)
|
|
450
535
|
if exact_word_match(self.telegram['username'], msg.text):
|
|
536
|
+
# Explicit @username mention: strongest signal - respond even if another
|
|
537
|
+
# account is also @mentioned (both bots may be intentionally addressed).
|
|
451
538
|
pattern = r'@?\b' + re.escape(self.telegram['username']) + r'\b'
|
|
452
539
|
new_text = re.sub(pattern, '', msg.text).strip()
|
|
453
540
|
await self._send_read_receipt(msg, context)
|
|
454
541
|
response = await self.tele_handle_response(new_text, msg)
|
|
455
542
|
elif (
|
|
456
543
|
exact_word_match(self.telegram['nickname'], msg.text) or
|
|
457
|
-
exact_word_match(self.telegram['initials'], msg.text)
|
|
544
|
+
exact_word_match(self.telegram['initials'], msg.text) or
|
|
545
|
+
is_reply_to_bot
|
|
458
546
|
):
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
elif is_reply_to_bot:
|
|
547
|
+
# Weaker trigger (nickname, initials, or reply): check for a foreign
|
|
548
|
+
# @mention first. If present, the message is likely for another account.
|
|
462
549
|
foreign = self._foreign_bot_mention(msg)
|
|
463
550
|
if foreign:
|
|
464
|
-
# The reply is directed at a different account - deflect without
|
|
465
|
-
# sending a read receipt, since we are not handling this message.
|
|
466
551
|
await msg.reply_text(f"Looks like that message is for {foreign}!")
|
|
467
552
|
return
|
|
468
553
|
await self._send_read_receipt(msg, context)
|
|
@@ -630,7 +715,10 @@ class TelegramBot:
|
|
|
630
715
|
def start_polling(self):
|
|
631
716
|
"""The main polling "loop" the user interacts with via Telegram."""
|
|
632
717
|
logger.info(f"TeLLMgramBot {self.telegram['username']} polling...")
|
|
633
|
-
self.telegram['app'].run_polling(
|
|
718
|
+
self.telegram['app'].run_polling(
|
|
719
|
+
poll_interval=self.telegram['pollinterval'],
|
|
720
|
+
allowed_updates=Update.ALL_TYPES,
|
|
721
|
+
)
|
|
634
722
|
logger.info(f"TeLLMgramBot {self.telegram['username']} polling ended.")
|
|
635
723
|
|
|
636
724
|
# Initialization
|
|
@@ -702,6 +790,7 @@ class TelegramBot:
|
|
|
702
790
|
self.telegram['app'].add_handler(CommandHandler('help', self.tele_commands))
|
|
703
791
|
self.telegram['app'].add_handler(CommandHandler('start', self.tele_start_command))
|
|
704
792
|
self.telegram['app'].add_handler(CommandHandler('stop', self.tele_stop_command))
|
|
793
|
+
self.telegram['app'].add_handler(CommandHandler('wipe', self.tele_wipe_command))
|
|
705
794
|
self.telegram['app'].add_handler(CommandHandler('nick', self.tele_nick_command))
|
|
706
795
|
self.telegram['app'].add_handler(CommandHandler('forget', self.tele_forget_command))
|
|
707
796
|
self.telegram['app'].add_handler(CommandHandler('private', self.tele_private_command))
|