TeLLMgramBot 3.13.2__tar.gz → 3.13.4__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.
Files changed (26) hide show
  1. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/PKG-INFO +14 -15
  2. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/README.md +13 -14
  3. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/TeLLMgramBot.py +11 -11
  4. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/conversation.py +2 -2
  5. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/initialize.py +82 -119
  6. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/message_handlers.py +21 -7
  7. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/tools.py +4 -4
  8. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/PKG-INFO +14 -15
  9. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/setup.py +1 -1
  10. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/LICENSE +0 -0
  11. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/__init__.py +0 -0
  12. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/archive.py +0 -0
  13. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/database.py +0 -0
  14. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/models.py +0 -0
  15. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/__init__.py +0 -0
  16. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/anthropic_provider.py +0 -0
  17. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/base.py +0 -0
  18. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/factory.py +0 -0
  19. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/openai_provider.py +0 -0
  20. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/utils.py +0 -0
  21. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/web_utils.py +0 -0
  22. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/SOURCES.txt +0 -0
  23. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/dependency_links.txt +0 -0
  24. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/requires.txt +0 -0
  25. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/top_level.txt +0 -0
  26. {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TeLLMgramBot
3
- Version: 3.13.2
3
+ Version: 3.13.4
4
4
  Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
5
5
  Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
6
6
  Author: Digital Heresy
@@ -75,18 +75,17 @@ Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to an
75
75
  ## Directories
76
76
  TeLLMgramBot creates the following directories:
77
77
 
78
- - **`configs`** - Bot configuration and model parameters
79
- - `config.yaml` - Bot settings: owner, model names, token limits, search limits, nickname/initials, database name
78
+ - **`configs`** - Bot configuration and model parameters (path configurable via `TELLMGRAMBOT_CONFIGS_PATH`)
79
+ - `config.yaml` - Default bot configuration file (filename used throughout this README); can be changed by passing `config_file` to `TelegramBot.set()`
80
80
  - `models.yaml` - Token limits for each LLM model (pre-populated on first run)
81
- - **`prompts`** - Bot personas and URL analysis templates
82
- - `test_personality.prmpt` - Sample bot personality (multi-provider, can be renamed)
83
- - `url_analysis.prmpt` - URL summarization prompt template
81
+ - **`prompts`** - Bot personas (path configurable via `TELLMGRAMBOT_PROMPTS_PATH`)
82
+ - `test_personality.prmpt` - Default bot persona file (filename used throughout this README); can be changed by passing `prompt_file` to `TelegramBot.set()`
84
83
  - 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.
85
- - **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `log_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
86
- - Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `log_name` when configured).
84
+ - **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `instance_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
85
+ - Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `instance_name` when configured).
87
86
  - Bot keeps the 10 most recent logs per bot instance, automatically pruning older ones.
88
87
  - Pass `-v` or `--verbose` on startup for DEBUG-level logging.
89
- - **`data`** - SQLite database (default `conversations.db`, customizable via `db_name` config) storing all messages, users, and chats
88
+ - **`data`** - SQLite database (default `conversations.db`, customizable via `instance_name` config) storing all messages, users, and chats
90
89
  - Users manage their data via `/forget` and `/private` commands.
91
90
 
92
91
  ### Environment Variables for Paths
@@ -102,13 +101,13 @@ Override default directory locations by setting these environment variables (use
102
101
  If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
103
102
 
104
103
  ## API Keys
105
- TeLLMgramBot supports four API keys, each loadable from environment variables or `.key` files:
104
+ TeLLMgramBot supports four API keys. OpenAI, Anthropic, and VirusTotal keys load from environment variables or `.key` files. The Telegram key loads from its env var or the bot config field `telegram_api_key` (no `.key` file):
106
105
 
107
- | Key | Env Var | File | When required |
108
- |-----|---------|------|---------------|
106
+ | Key | Env Var | File/Config | When required |
107
+ |-----|---------|-------------|---------------|
109
108
  | [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
110
109
  | [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
111
- | [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `telegram.key` | Always required |
110
+ | [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | bot config `telegram_api_key` in `config.yaml` | Always required |
112
111
  | [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
113
112
 
114
113
  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.
@@ -151,9 +150,9 @@ When the bot is triggered in a group and about to respond (not deferring to anot
151
150
  - `bot_owner`: Telegram username(s) with admin access (required, no `@`). Accepts a single string or a YAML list of usernames.
152
151
  - `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
153
152
  - `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
153
+ - `telegram_api_key`: Telegram bot API key (required). Lookup order: (1) `TELLMGRAMBOT_TELEGRAM_API_KEY` env var, (2) `telegram_api_key` in `config.yaml`. Exits on placeholder, missing, or malformed key.
154
154
  - `bot_nickname` / `bot_initials`: Names the bot responds to in groups
155
- - `db_name`: Optional custom database filename without extension (e.g. `MyBot` creates `MyBot.db`); omit for default `conversations.db`. Use distinct names when running multiple bot instances in the same directory.
156
- - `log_name`: Optional label used for the console prefix and log filename (e.g. `MyBot` produces `[MyBot] INFO: ...` on the console and `MyBot_{timestamp}.log`); omit to use the bot's Telegram username. Useful for multi-platform deployments sharing the same config.
155
+ - `instance_name`: Optional label for console prefix, log filename, and database name (e.g. `MyBot` produces `[MyBot] INFO: ...` on console, `MyBot_{timestamp}.log` logs, and `MyBot.db` database); omit to use bot's Telegram username for logging and `conversations.db` for database. Use distinct names when running multiple bot instances in the same directory.
157
156
  - `token_limit`: Max tokens (optional; defaults to model's maximum)
158
157
  - `search_limit`: Max search results (optional; defaults to 30)
159
158
  - `archive_days`: Days before messages are eligible for archival (optional; default 60, minimum 1). Older messages are distilled into daily summaries, then progressively compressed into monthly digests. Once archived their respective raw messages do not return to the LLM context any more, only when searching messages.
@@ -43,18 +43,17 @@ Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to an
43
43
  ## Directories
44
44
  TeLLMgramBot creates the following directories:
45
45
 
46
- - **`configs`** - Bot configuration and model parameters
47
- - `config.yaml` - Bot settings: owner, model names, token limits, search limits, nickname/initials, database name
46
+ - **`configs`** - Bot configuration and model parameters (path configurable via `TELLMGRAMBOT_CONFIGS_PATH`)
47
+ - `config.yaml` - Default bot configuration file (filename used throughout this README); can be changed by passing `config_file` to `TelegramBot.set()`
48
48
  - `models.yaml` - Token limits for each LLM model (pre-populated on first run)
49
- - **`prompts`** - Bot personas and URL analysis templates
50
- - `test_personality.prmpt` - Sample bot personality (multi-provider, can be renamed)
51
- - `url_analysis.prmpt` - URL summarization prompt template
49
+ - **`prompts`** - Bot personas (path configurable via `TELLMGRAMBOT_PROMPTS_PATH`)
50
+ - `test_personality.prmpt` - Default bot persona file (filename used throughout this README); can be changed by passing `prompt_file` to `TelegramBot.set()`
52
51
  - 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.
53
- - **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `log_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
54
- - Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `log_name` when configured).
52
+ - **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `instance_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
53
+ - Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `instance_name` when configured).
55
54
  - Bot keeps the 10 most recent logs per bot instance, automatically pruning older ones.
56
55
  - Pass `-v` or `--verbose` on startup for DEBUG-level logging.
57
- - **`data`** - SQLite database (default `conversations.db`, customizable via `db_name` config) storing all messages, users, and chats
56
+ - **`data`** - SQLite database (default `conversations.db`, customizable via `instance_name` config) storing all messages, users, and chats
58
57
  - Users manage their data via `/forget` and `/private` commands.
59
58
 
60
59
  ### Environment Variables for Paths
@@ -70,13 +69,13 @@ Override default directory locations by setting these environment variables (use
70
69
  If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
71
70
 
72
71
  ## API Keys
73
- TeLLMgramBot supports four API keys, each loadable from environment variables or `.key` files:
72
+ TeLLMgramBot supports four API keys. OpenAI, Anthropic, and VirusTotal keys load from environment variables or `.key` files. The Telegram key loads from its env var or the bot config field `telegram_api_key` (no `.key` file):
74
73
 
75
- | Key | Env Var | File | When required |
76
- |-----|---------|------|---------------|
74
+ | Key | Env Var | File/Config | When required |
75
+ |-----|---------|-------------|---------------|
77
76
  | [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
78
77
  | [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
79
- | [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `telegram.key` | Always required |
78
+ | [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | bot config `telegram_api_key` in `config.yaml` | Always required |
80
79
  | [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
81
80
 
82
81
  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.
@@ -119,9 +118,9 @@ When the bot is triggered in a group and about to respond (not deferring to anot
119
118
  - `bot_owner`: Telegram username(s) with admin access (required, no `@`). Accepts a single string or a YAML list of usernames.
120
119
  - `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
121
120
  - `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
121
+ - `telegram_api_key`: Telegram bot API key (required). Lookup order: (1) `TELLMGRAMBOT_TELEGRAM_API_KEY` env var, (2) `telegram_api_key` in `config.yaml`. Exits on placeholder, missing, or malformed key.
122
122
  - `bot_nickname` / `bot_initials`: Names the bot responds to in groups
123
- - `db_name`: Optional custom database filename without extension (e.g. `MyBot` creates `MyBot.db`); omit for default `conversations.db`. Use distinct names when running multiple bot instances in the same directory.
124
- - `log_name`: Optional label used for the console prefix and log filename (e.g. `MyBot` produces `[MyBot] INFO: ...` on the console and `MyBot_{timestamp}.log`); omit to use the bot's Telegram username. Useful for multi-platform deployments sharing the same config.
123
+ - `instance_name`: Optional label for console prefix, log filename, and database name (e.g. `MyBot` produces `[MyBot] INFO: ...` on console, `MyBot_{timestamp}.log` logs, and `MyBot.db` database); omit to use bot's Telegram username for logging and `conversations.db` for database. Use distinct names when running multiple bot instances in the same directory.
125
124
  - `token_limit`: Max tokens (optional; defaults to model's maximum)
126
125
  - `search_limit`: Max search results (optional; defaults to 30)
127
126
  - `archive_days`: Days before messages are eligible for archival (optional; default 60, minimum 1). Older messages are distilled into daily summaries, then progressively compressed into monthly digests. Once archived their respective raw messages do not return to the LLM context any more, only when searching messages.
@@ -103,7 +103,7 @@ class TelegramBot:
103
103
  self.telegram['username'] = bot.username
104
104
  self.telegram['first_name'] = bot.first_name
105
105
  self.telegram['last_name'] = bot.last_name
106
- bind_log_identity(bot.username, self._log_name)
106
+ bind_log_identity(bot.username, self._instance_name)
107
107
  except Exception as e:
108
108
  # Fail fast if the bot's identity cannot be fetched to avoid
109
109
  # continuing with an uninitialized self.telegram['username'].
@@ -183,7 +183,7 @@ class TelegramBot:
183
183
  List all registered tools available to this bot instance (admin-only, private chat only).
184
184
 
185
185
  Shows name and description for every tool the bot exposes, including the built-in
186
- search_messages tool and any webhook tools defined in config.yaml. Restricted to
186
+ search_messages tool and any webhook tools defined in bot config. Restricted to
187
187
  bot_owner in a private chat; any other caller receives a generic denial.
188
188
 
189
189
  Args:
@@ -506,7 +506,7 @@ class TelegramBot:
506
506
  reply = _MSG_PROCESS_ERROR
507
507
  if url_match and self.key_status.url_analysis_enabled:
508
508
  await msg.reply_text("Sure, give me a moment to look at that URL...")
509
- reply = await handle_url_ask(text, self.llm['url_model'])
509
+ reply = await handle_url_ask(text, self.llm['url_model'], conv.system_content)
510
510
  elif self._online and self.key_status.chat_enabled:
511
511
  # This is the transition point between quick Telegram replies and the LLM.
512
512
  tools = self._build_tool_list(chat_type, username)
@@ -964,7 +964,7 @@ class TelegramBot:
964
964
  archive_days = INIT_BOT_CONFIG['archive_days'],
965
965
  persona_prompt = INIT_BOT_CONFIG['persona_prompt'],
966
966
  key_status: ApiKeyStatus | None = None,
967
- log_name: str | None = None,
967
+ instance_name: str | None = None,
968
968
  webhook_schemas: list | None = None,
969
969
  webhook_defs: dict | None = None,
970
970
  ):
@@ -983,8 +983,8 @@ class TelegramBot:
983
983
  persona_temp: LLM temperature (0.0-2.0). If None, defaults to 1.0.
984
984
  persona_prompt: System prompt defining the bot's behavior and personality.
985
985
  key_status: ApiKeyStatus object indicating available features. If None, calls init_structure().
986
- log_name: Optional label for console prefix and log file name (from config.yaml `log_name`).
987
- Defaults to the bot's Telegram username when not set.
986
+ instance_name: Optional label for console prefix and log file name (from bot config `instance_name`).
987
+ Defaults to the bot's Telegram username when not set.
988
988
  archive_days: Days before messages are eligible for Tier 1 archival (default: 60).
989
989
  Must be an integer >= 1; invalid values log a warning and fall back to 60.
990
990
  Tier 2 compression triggers at archive_days * 2.
@@ -1001,7 +1001,7 @@ class TelegramBot:
1001
1001
  """
1002
1002
  # Starting to initialize, not online yet
1003
1003
  self._online = False
1004
- self._log_name = log_name
1004
+ self._instance_name = instance_name
1005
1005
 
1006
1006
  # Bootstrap structure and logging; init_logging() is a no-op if init_structure already ran it.
1007
1007
  self.key_status = key_status if key_status is not None else init_structure()[0]
@@ -1104,13 +1104,13 @@ class TelegramBot:
1104
1104
  Side Effects:
1105
1105
  - Calls init_structure() which creates directories, config/prompt files, and checks API keys.
1106
1106
  - Calls discover_mcp_tools() if any 'mcp_server:' entries are in config (gracefully degrades if called from async context).
1107
- - Prints API key status summary and optional warnings if config values are missing or prompt is empty.
1108
- - Log identity/file label is taken from config.yaml `log_name` when set; otherwise defaults to the bot's Telegram username once _tele_info() resolves.
1107
+ - May log warnings (for missing config values, empty prompt, or skipped MCP discovery), but does not print a startup API key status summary.
1108
+ - Log identity/file label is taken from bot config `instance_name` when set; otherwise defaults to the bot's Telegram username once _tele_info() resolves.
1109
1109
  """
1110
1110
  # Bootstrap directories, logging, config, prompt (with appendix), and API keys in one call.
1111
1111
  key_status, config, prompt = init_structure(config_file, prompt_file)
1112
1112
 
1113
- # Build the webhook tool registry from the optional 'tools:' block in config.yaml.
1113
+ # Build the webhook tool registry from the optional 'tools:' block in bot config.
1114
1114
  allow_local = config['allow_local_webhooks'] or False
1115
1115
  webhook_schemas, webhook_defs = build_tool_registry(config.get('tools') or [], allow_local)
1116
1116
 
@@ -1145,7 +1145,7 @@ class TelegramBot:
1145
1145
  archive_days = config['archive_days'],
1146
1146
  persona_prompt = prompt,
1147
1147
  key_status = key_status,
1148
- log_name = config['log_name'],
1148
+ instance_name = config['instance_name'],
1149
1149
  webhook_schemas = webhook_schemas,
1150
1150
  webhook_defs = webhook_defs,
1151
1151
  )
@@ -111,9 +111,9 @@ class Conversation:
111
111
  init and on every incoming message so long sessions always carry an accurate
112
112
  UTC timestamp in format 'YYYY-MM-DD HH:MM UTC'.
113
113
  """
114
- dt_line = f'\nCurrent date and time: {format_now(None)}'
114
+ dt_line = f'\n- Current date and time: {format_now(None)}'
115
115
  new_content, n = re.subn(
116
- r'\nCurrent date and time:.*',
116
+ r'\n-?\s*Current date and time:.*',
117
117
  dt_line,
118
118
  self.system_content,
119
119
  )
@@ -96,14 +96,16 @@ class ApiKeyStatus:
96
96
  disabled_reasons: dict = field(default_factory=dict)
97
97
 
98
98
 
99
+ _TELEGRAM_KEY_RE = re.compile(r'^\d+:[A-Za-z0-9_-]+$')
100
+
99
101
  INIT_BOT_CONFIG = {
100
102
  'bot_owner': '<YOUR USERNAME>',
101
103
  'bot_nickname': 'Testy',
102
104
  'bot_initials': 'TB',
103
105
  'chat_model': 'gpt-5-mini',
104
106
  'url_model': 'gpt-5',
105
- 'db_name': None,
106
- 'log_name': None,
107
+ 'telegram_api_key': '<YOUR TELEGRAM API KEY HERE>',
108
+ 'instance_name': None,
107
109
  'token_limit': None,
108
110
  'search_limit': None,
109
111
  'persona_temp': None,
@@ -113,8 +115,7 @@ INIT_BOT_CONFIG = {
113
115
  }
114
116
 
115
117
  INIT_BOT_CONFIG_COMMENTS = {
116
- 'db_name': '# Optional, custom DB filename (e.g. MyBot -> MyBot.db). Defaults to conversations.db',
117
- 'log_name': '# Optional, label for console prefix and log file name. Defaults to the bot\'s Telegram username',
118
+ 'instance_name': '# Optional, sets log label and DB filename (e.g. MyBot -> MyBot.db). If omitted, log label defaults to the bot\'s Telegram username and DB filename remains conversations.db',
118
119
  'token_limit': '# Optional, overrides the chat_model\'s default max token limit',
119
120
  'search_limit': '# Optional, max results returned by message search (default: 30)',
120
121
  'persona_temp': '# Optional, LLM temperature 0.0-2.0 (default: model\'s default)',
@@ -125,12 +126,13 @@ INIT_BOT_CONFIG_COMMENTS = {
125
126
  # Append the framework-owned system appendix to the persona prompt.
126
127
  # Teaches an LLM how cross-chat memory works without requiring persona authors to include it.
127
128
  _SYSTEM_APPENDIX = (
128
- "You have access to past messages across chats and a search tool - use them to inform your responses, but never quote or volunteer content from other chats unprompted, especially in group settings.\n"
129
- "Always invoke the search tool before concluding a message does not exist.\n"
130
- "Messages marked private are never shared between chats.\n"
131
- "Never reveal or repeat Telegram user IDs, chat IDs, or any other internal numeric identifiers in your responses.\n"
132
- "When your context includes a '[Replying to Name, ...]' prefix indicating you were triggered via a reply, acknowledge the original author by name in your response.\n"
133
- "Current date and time: (not yet known)\n"
129
+ "## System\n"
130
+ "- You have access to past messages across chats and a search tool - use them to inform your responses, but never quote or volunteer content from other chats unprompted, especially in group settings.\n"
131
+ "- Always invoke the search tool before concluding a message does not exist.\n"
132
+ "- Messages marked private are never shared between chats.\n"
133
+ "- Never reveal or repeat Telegram user IDs, chat IDs, or any other internal numeric identifiers in your responses.\n"
134
+ "- When your context includes a '[Replying to Name, ...]' prefix indicating you were triggered via a reply, acknowledge the original author by name in your response.\n"
135
+ "- Current date and time: (not yet known)\n"
134
136
  )
135
137
 
136
138
 
@@ -165,24 +167,24 @@ def init_logging():
165
167
  logging.getLogger('httpcore').setLevel(logging.WARNING)
166
168
 
167
169
 
168
- def bind_log_identity(username: str, log_name: Optional[str] = None):
170
+ def bind_log_identity(username: str, instance_name: Optional[str] = None):
169
171
  """
170
172
  Bind the bot's identity to the logging system.
171
173
 
172
- Updates the console handler formatter to prefix every line with [log_name] and opens
173
- the per-instance log file named {log_name}_{timestamp}.log.
174
+ Updates the console handler formatter to prefix every line with [instance_name] and opens
175
+ the per-instance log file named {instance_name}_{timestamp}.log.
174
176
 
175
177
  Must be called after the bot's Telegram username is resolved by _tele_info().
176
178
 
177
179
  Args:
178
180
  username: The bot's Telegram username (without @); used as the identity label when
179
- log_name is not set.
180
- log_name: Optional override from config.yaml. When set, used for both the console
181
- prefix and log file name instead of the Telegram username -- useful for
182
- multi-platform deployments where the same bot runs on Telegram and Discord.
181
+ instance_name is not set.
182
+ instance_name: Optional override from bot config. When set, used for both the console
183
+ prefix and log file name instead of the Telegram username -- useful for
184
+ multi-platform deployments where the same bot runs on Telegram and Discord.
183
185
  """
184
- raw_label = log_name or username
185
- # Sanitize: strip path separators so a misconfigured log_name cannot escape TELLMGRAMBOT_LOGS_PATH
186
+ raw_label = instance_name or username
187
+ # Sanitize: strip path separators so a misconfigured instance_name cannot escape TELLMGRAMBOT_LOGS_PATH
186
188
  label = os.path.basename(raw_label).strip()
187
189
  if not label:
188
190
  label = username
@@ -244,7 +246,7 @@ def _requires_provider(config: dict) -> dict:
244
246
  all other non-empty models require OpenAI.
245
247
 
246
248
  Args:
247
- config: Parsed bot configuration dict (from init_bot_config or read_yaml on config.yaml).
249
+ config: Parsed bot configuration dict (from init_bot_config or read_yaml on bot config).
248
250
 
249
251
  Returns:
250
252
  Dict with keys 'openai' and 'anthropic', each a bool indicating if that provider key is needed.
@@ -266,66 +268,76 @@ def _model_provider(model: str) -> str | None:
266
268
  return 'anthropic' if model.startswith('claude-') else 'openai'
267
269
 
268
270
 
269
- def init_keys(config: Optional[dict] = None) -> ApiKeyStatus:
271
+ def _load_telegram_key(config: dict):
270
272
  """
271
- Load API keys and return an ApiKeyStatus indicating which features are available.
273
+ Load and validate the Telegram API key into TELLMGRAMBOT_TELEGRAM_API_KEY.
274
+ Lookup order: (1) TELLMGRAMBOT_TELEGRAM_API_KEY env var, (2) bot config
275
+ telegram_api_key. Exits on placeholder, missing, or malformed key.
272
276
 
273
- Telegram key is always required - bot exits if missing or invalid.
274
- All other keys (OpenAI, Anthropic, VirusTotal) are optional: missing keys
275
- disable the associated features rather than exiting.
277
+ Args:
278
+ config: Parsed bot configuration dict from init_bot_config().
279
+ """
280
+ env_var = 'TELLMGRAMBOT_TELEGRAM_API_KEY'
281
+ if not os.environ.get(env_var):
282
+ config_val = (config.get('telegram_api_key') or '').strip()
283
+ is_placeholder = config_val.startswith('<') and config_val.endswith('>')
284
+ if config_val and not is_placeholder:
285
+ os.environ[env_var] = config_val
286
+ elif is_placeholder:
287
+ sys.exit("telegram_api_key in bot config is still a placeholder. Replace it with your Telegram bot API key. Exiting...")
288
+ else:
289
+ sys.exit(
290
+ "Telegram API key is required. Set telegram_api_key in bot config "
291
+ "or TELLMGRAMBOT_TELEGRAM_API_KEY env var. Exiting..."
292
+ )
293
+ if not _TELEGRAM_KEY_RE.match(os.environ.get(env_var, '')):
294
+ sys.exit(
295
+ "Telegram API key is not in valid format '<digits>:<letters/digits/_/->' "
296
+ "(e.g. 123456789:ABCdef...) - exiting..."
297
+ )
276
298
 
277
- Provider keys (OpenAI/Anthropic) are loaded conditionally based on configured
278
- models. Key files are read from TELLMGRAMBOT_KEYS_PATH or the base execution path.
279
- Placeholder files are created for any missing keys. Prints API key status summary
280
- before returning.
299
+
300
+ def init_keys(config: dict) -> ApiKeyStatus:
301
+ """
302
+ Load API keys and return an ApiKeyStatus indicating which features are available.
303
+
304
+ Telegram key is loaded and validated first via _load_telegram_key(). All other
305
+ keys (OpenAI, Anthropic, VirusTotal) are optional: missing keys disable the
306
+ associated features rather than exiting. These keys are loaded from env var or
307
+ key file (TELLMGRAMBOT_KEYS_PATH or base execution path). Placeholder files are
308
+ created for any missing keys.
281
309
 
282
310
  Args:
283
- config: Optional parsed bot configuration dict. If None, reads from
284
- TELLMGRAMBOT_CONFIGS_PATH/config.yaml (or execution_dir/config.yaml).
311
+ config: Parsed bot configuration dict from init_bot_config().
285
312
 
286
313
  Returns:
287
314
  ApiKeyStatus with chat_enabled and url_analysis_enabled flags set based on which keys are available.
288
315
  """
289
- # If no config provided, fall back to reading the default config file.
290
- # Failures here are non-fatal: config = {} means no models configured.
291
- if config is None:
292
- try:
293
- config_path = os.path.join(os.environ.get('TELLMGRAMBOT_CONFIGS_PATH', execution_dir()), 'config.yaml')
294
- config = read_yaml(config_path) or {}
295
- except Exception:
296
- config = {}
316
+ _load_telegram_key(config)
297
317
 
298
318
  # Determine which provider keys are needed and which feature each model uses.
299
319
  provider_needs = _requires_provider(config)
300
320
  chat_provider = _model_provider(config.get('chat_model', ''))
301
321
  url_provider = _model_provider(config.get('url_model', ''))
302
322
 
303
- # Build the full set of keys to check: only the required provider key(s) + always-required keys.
323
+ # Only load the required provider key(s) + VirusTotal.
304
324
  provider_keys = {
305
325
  'openai': 'https://platform.openai.com/api-keys',
306
326
  'anthropic': 'https://docs.anthropic.com/en/api/getting-started',
307
327
  }
308
- always_keys = {
309
- 'telegram': 'https://core.telegram.org/api',
310
- 'virustotal': 'https://developers.virustotal.com/reference/overview'
311
- }
312
- keys = {k: v for k, v in provider_keys.items() if provider_needs.get(k)} | always_keys
328
+ optional_keys = {k: v for k, v in provider_keys.items() if provider_needs.get(k)}
329
+ optional_keys['virustotal'] = 'https://developers.virustotal.com/reference/overview'
313
330
 
314
- available = set() # tracks which keys loaded successfully
315
- for key, url in keys.items():
331
+ available = set(['telegram']) # telegram already validated above
332
+ for key, url in optional_keys.items():
316
333
  env_var = f"TELLMGRAMBOT_{key.upper()}_API_KEY"
317
334
 
318
- # If the key isn't already in the environment, try loading it from a key file.
319
335
  if not os.environ.get(env_var):
320
336
  path = os.path.join(os.environ.get('TELLMGRAMBOT_KEYS_PATH', execution_dir()), f"{key}.key")
321
337
  if not os.path.exists(path):
322
- # Key file missing: create a placeholder and warn (or exit for Telegram).
323
338
  generate_file_path(os.path.dirname(path), os.path.basename(path), "API key",
324
339
  f"YOUR {key.upper()} API KEY HERE - {url}\n"
325
340
  )
326
- if key == 'telegram':
327
- sys.exit("Telegram API key is required to run TeLLMgramBot! Exiting...")
328
- # Build a human-readable list of which features this key covers.
329
341
  which = [f"chat_model ({config.get('chat_model', '?')})" if chat_provider == key else None,
330
342
  f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
331
343
  "VirusTotal integration" if key == 'virustotal' else None]
@@ -333,14 +345,10 @@ def init_keys(config: Optional[dict] = None) -> ApiKeyStatus:
333
345
  logger.warning(f"{key}.key not found - {reason} will be disabled.")
334
346
  continue
335
347
  else:
336
- # Key file exists: load it into the environment for this process.
337
348
  os.environ[env_var] = read_text(path)
338
349
  logger.info(f"Loaded {env_var} secret from: {path}")
339
350
 
340
- # Reject blank or whitespace-only values (common copy-paste mistake).
341
351
  if not os.environ.get(env_var) or any(c.isspace() for c in os.environ.get(env_var)):
342
- if key == 'telegram':
343
- sys.exit("Telegram API key is blank or invalid! Exiting...")
344
352
  which = [f"chat_model ({config.get('chat_model', '?')})" if chat_provider == key else None,
345
353
  f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
346
354
  "VirusTotal integration" if key == 'virustotal' else None]
@@ -374,26 +382,9 @@ def init_keys(config: Optional[dict] = None) -> ApiKeyStatus:
374
382
  reasons.append("missing VirusTotal API key")
375
383
  key_status.disabled_reasons['url_analysis'] = ", ".join(reasons)
376
384
 
377
- print_api_key_status(key_status)
378
385
  return key_status
379
386
 
380
387
 
381
- def print_api_key_status(key_status: ApiKeyStatus):
382
- """Print a startup summary of which API keys are available and which features they enable."""
383
- print("--- TeLLMgramBot API Key Status ---")
384
- features = [
385
- ('chat', 'Chat (LLM)', key_status.chat_enabled),
386
- ('url_analysis', 'URL Analysis', key_status.url_analysis_enabled),
387
- ]
388
- for key, label, enabled in features:
389
- if enabled:
390
- print(f" {label:<12} : ENABLED")
391
- else:
392
- reason = key_status.disabled_reasons.get(key, 'unavailable')
393
- print(f" {label:<12} : DISABLED ({reason})")
394
- print("-----------------------------------")
395
-
396
-
397
388
  def init_bot_config(file: str = 'config.yaml') -> dict:
398
389
  """
399
390
  Returns contents of a bot configuration file (default 'config.yaml' if created).
@@ -412,9 +403,9 @@ def init_bot_config(file: str = 'config.yaml') -> dict:
412
403
  env_var = "TELLMGRAMBOT_CONFIGS_PATH"
413
404
  try:
414
405
  text = ''.join(
415
- '%s : %s\n' % (
416
- parameter.ljust(12),
417
- value if value else INIT_BOT_CONFIG_COMMENTS.get(parameter, '# Optional')
406
+ '%s: %s\n' % (
407
+ parameter,
408
+ (f"'{value}'" if isinstance(value, str) else value) if value is not None else INIT_BOT_CONFIG_COMMENTS.get(parameter, '# Optional')
418
409
  )
419
410
  for parameter, value in INIT_BOT_CONFIG.items()
420
411
  if parameter != 'persona_prompt'
@@ -497,33 +488,6 @@ def init_bot_prompt(file: str = 'test_personality.prmpt') -> str:
497
488
  sys.exit(f"{type(e).__name__} on {env_var} file '{file}': {e}\nExiting...")
498
489
 
499
490
 
500
- def init_url_prompt(file: str = 'url_analysis.prmpt') -> str:
501
- """
502
- Ensure prompt file is created that defines how the bot will analyze URLs via square brackets [].
503
- Must specify the environment variable 'TELLMGRAMBOT_PROMPTS_PATH'.
504
- """
505
- env_var = "TELLMGRAMBOT_PROMPTS_PATH"
506
- try:
507
- return read_text(
508
- generate_file_path(os.environ[env_var], file, "URL analysis prompt",
509
- "The user has provided a URL to perform some level of analysis. You will infer "
510
- "the nature of the analysis from the user's query.\n\n"
511
- "The contents of the URL mentioned have already been harvested and cleansed. "
512
- "Note the URL contents will likely have sections of text that are less relevant "
513
- "to the user's question (headers, footers, menus, ads, etc.). You will need to "
514
- "ignore those sections of text and focus on the main content of the page.\n\n"
515
- "The contents of the URL are shown below:\n"
516
- "BEGIN URL CONTENTS\n"
517
- "{url_content}\n"
518
- "END URL CONTENTS\n"
519
- )
520
- )
521
- except KeyError:
522
- sys.exit(f"{env_var} must be defined to create file '{file}'! Exiting...")
523
- except Exception as e:
524
- sys.exit(f"{type(e).__name__} on {env_var} file '{file}': {e}\nExiting...")
525
-
526
-
527
491
  def init_structure(
528
492
  config_file: str = 'config.yaml',
529
493
  prompt_file: str = 'test_personality.prmpt',
@@ -532,15 +496,15 @@ def init_structure(
532
496
  Performs the whole TeLLMgramBot first-run setup including:
533
497
  - Directories for configurations, prompts, and conversation/error logs.
534
498
  - Logging configuration (console handler; file handler bound later by bind_log_identity()).
535
- - Database naming and schema initialization from config (conversations.db or custom db_name).
536
- - Provider-conditional API keys (OpenAI/Anthropic determined by config.yaml model prefixes).
537
- - Configuration files (config.yaml, models.yaml) with inline parameter descriptions.
538
- - System prompt files (test_personality.prmpt, url_analysis.prmpt).
499
+ - Database naming and schema initialization from config (conversations.db or custom instance_name).
500
+ - Provider-conditional API keys (OpenAI/Anthropic determined by bot config model prefixes).
501
+ - Configuration files (bot config, models.yaml) with inline parameter descriptions.
502
+ - System prompt file (test_personality.prmpt).
539
503
 
540
- Provider key requirements are inferred from config.yaml model prefixes (claude-* -> Anthropic,
504
+ Provider key requirements are inferred from bot config model prefixes (claude-* -> Anthropic,
541
505
  gpt-* -> OpenAI, etc.) to streamline setup for single-provider deployments.
542
506
 
543
- Database naming: After init_bot_config() runs, if db_name is configured in config.yaml,
507
+ Database naming: After init_bot_config() runs, if instance_name is configured in bot config,
544
508
  calls set_db_filename() and performs a one-time rename migration of conversations.db to
545
509
  the custom filename (only if the target does not yet exist). This ordering ensures the DB
546
510
  filename is settled before schema init.
@@ -563,19 +527,19 @@ def init_structure(
563
527
  init_models_config()
564
528
 
565
529
  # Configure DB filename and run schema init now that config is available.
566
- # Must happen after init_bot_config() so db_name is known before init_db() runs.
567
- db_name = config.get('db_name')
568
- if db_name:
530
+ # Must happen after init_bot_config() so instance_name is known before init_db() runs.
531
+ instance_name = config.get('instance_name')
532
+ if instance_name:
569
533
  # Sanitize to a plain filename - reject path separators and absolute paths so a
570
- # misconfigured db_name cannot escape TELLMGRAMBOT_DATA_PATH.
571
- db_name_safe = os.path.basename(db_name).strip()
572
- if db_name_safe != db_name.strip() or not db_name_safe:
534
+ # misconfigured instance_name cannot escape TELLMGRAMBOT_DATA_PATH.
535
+ instance_name_safe = os.path.basename(instance_name).strip()
536
+ if instance_name_safe != instance_name.strip() or not instance_name_safe:
573
537
  logger.warning(
574
- f"db_name '{db_name}' contains path components; ignoring and using conversations.db"
538
+ f"instance_name '{instance_name}' contains path components; ignoring and using conversations.db"
575
539
  )
576
- db_name = None
540
+ instance_name = None
577
541
  else:
578
- set_db_filename(db_name_safe)
542
+ set_db_filename(instance_name_safe)
579
543
  # One-time migration: rename conversations.db to the new name if the target
580
544
  # doesn't exist yet. Assumes a single-bot deployment owns conversations.db.
581
545
  data_dir = os.environ.get('TELLMGRAMBOT_DATA_PATH', os.path.join(execution_dir(), 'data'))
@@ -608,7 +572,6 @@ def init_structure(
608
572
  prompt = INIT_BOT_CONFIG.get('persona_prompt', '')
609
573
  logger.warning(f"File '{prompt_file}' is empty, using default persona prompt.")
610
574
  prompt = prompt.rstrip() + "\n\n" + _SYSTEM_APPENDIX
611
- init_url_prompt()
612
575
 
613
576
  # Load API keys before running archival so the provider can authenticate.
614
577
  key_status = init_keys(config)
@@ -3,7 +3,6 @@ import re
3
3
  from typing import Optional
4
4
  import validators
5
5
 
6
- from .initialize import init_url_prompt
7
6
  from .utils import log_error
8
7
  from .models import TokenLimits
9
8
  from .web_utils import (
@@ -15,6 +14,20 @@ from .web_utils import (
15
14
  )
16
15
  from .providers.factory import get_provider
17
16
 
17
+ _URL_ANALYSIS_TEMPLATE = (
18
+ "## URL Analysis\n"
19
+ "The user has provided a URL to perform some level of analysis. You will infer "
20
+ "the nature of the analysis from the user's query.\n\n"
21
+ "The contents of the URL mentioned have already been harvested and cleansed. "
22
+ "Note the URL contents will likely have sections of text that are less relevant "
23
+ "to the user's question (headers, footers, menus, ads, etc.). You will need to "
24
+ "ignore those sections of text and focus on the main content of the page.\n\n"
25
+ "The contents of the URL are shown below:\n"
26
+ "BEGIN URL CONTENTS\n"
27
+ "{url_content}\n"
28
+ "END URL CONTENTS\n"
29
+ )
30
+
18
31
 
19
32
  def handle_greetings(text: str) -> Optional[str]:
20
33
  """
@@ -44,18 +57,19 @@ def handle_common_queries(text: str) -> Optional[str]:
44
57
  return None
45
58
 
46
59
 
47
- async def handle_url_ask(text: str, model: str = 'gpt-4o') -> Optional[str]:
60
+ async def handle_url_ask(text: str, model: str = 'gpt-4o', prompt: str = '') -> Optional[str]:
48
61
  """
49
62
  Process URL content in an LLM to provide a summary.
50
63
 
51
64
  Extracts URLs wrapped in square brackets [], validates them, checks for
52
65
  safety via VirusTotal, fetches content, and summarizes via an LLM specified
53
- by model name. Errors are logged via the logging system categorized by model
54
- and error type.
66
+ by model name. The bot's persona prompt is prepended to the URL analysis
67
+ system message so responses match the bot's personality.
55
68
 
56
69
  Args:
57
70
  text: The message text potentially containing a URL in [square brackets].
58
71
  model: The LLM model to use for URL summarization (default: 'gpt-4o').
72
+ prompt: Bot persona prompt prepended to the URL analysis system message.
59
73
 
60
74
  Returns:
61
75
  A summary string if a URL was found and processed successfully, an error
@@ -104,9 +118,9 @@ async def handle_url_ask(text: str, model: str = 'gpt-4o') -> Optional[str]:
104
118
  # Show the last 50 characters of the pruned URL content
105
119
  pruned_tail = messages[0]["content"][-50:]
106
120
 
107
- # Load system prompt template to insert URL content
108
- template = init_url_prompt('url_analysis.prmpt')
109
- messages[0]["content"] = template.format(url_content=messages[0]["content"])
121
+ # Build system message: bot persona (if any) + URL analysis template with content
122
+ url_system = _URL_ANALYSIS_TEMPLATE.replace('{url_content}', messages[0]["content"])
123
+ messages[0]["content"] = f"{prompt}\n\n{url_system}" if prompt else url_system
110
124
 
111
125
  # Call the LLM for the response that summarizes URL content
112
126
  try:
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Webhook and MCP tool registry and executors for TeLLMgramBot.
3
3
 
4
- Provides build_tool_registry() for startup parsing of config.yaml 'tools:' webhook
4
+ Provides build_tool_registry() for startup parsing of bot config 'tools:' webhook
5
5
  entries, discover_mcp_tools() for async MCP server discovery, execute_webhook() for
6
6
  dispatching webhook tool calls, and execute_mcp() for MCP tool calls at runtime.
7
7
  """
@@ -96,7 +96,7 @@ def build_tool_registry(
96
96
  are skipped with warnings. Duplicate tool names are deduplicated (first wins).
97
97
 
98
98
  Args:
99
- tools_config: List of raw tool definition dicts from config.yaml 'tools:' key.
99
+ tools_config: List of raw tool definition dicts from bot config 'tools:' key.
100
100
  Pass an empty list or None to produce an empty registry.
101
101
  allow_local: If True, the executor permits webhook URLs targeting loopback or
102
102
  link-local addresses. Mirrors the 'allow_local_webhooks' config key.
@@ -387,7 +387,7 @@ async def execute_mcp(tool_def: dict, arguments: dict) -> str:
387
387
  logger.warning(f"MCP '{name}': SSRF block - '{parsed.hostname}' is loopback or link-local")
388
388
  return (
389
389
  f"[Tool error: '{name}' targets a loopback or link-local address. "
390
- f"Set allow_local_webhooks: true in config.yaml to permit local addresses.]"
390
+ f"Set allow_local_webhooks: true in bot config to permit local addresses.]"
391
391
  )
392
392
 
393
393
  call_url = f"{server_url}/tools/call"
@@ -489,7 +489,7 @@ async def execute_webhook(tool_def: dict, arguments: dict) -> str:
489
489
  logger.warning(f"Webhook '{name}': SSRF block - '{hostname}' is loopback or link-local")
490
490
  return (
491
491
  f"[Tool error: '{name}' targets a loopback or link-local address. "
492
- f"Set allow_local_webhooks: true in config.yaml to permit local addresses.]"
492
+ f"Set allow_local_webhooks: true in bot config to permit local addresses.]"
493
493
  )
494
494
 
495
495
  host_part = f"{parsed.hostname}:{parsed.port}" if parsed.port else parsed.hostname
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TeLLMgramBot
3
- Version: 3.13.2
3
+ Version: 3.13.4
4
4
  Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
5
5
  Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
6
6
  Author: Digital Heresy
@@ -75,18 +75,17 @@ Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to an
75
75
  ## Directories
76
76
  TeLLMgramBot creates the following directories:
77
77
 
78
- - **`configs`** - Bot configuration and model parameters
79
- - `config.yaml` - Bot settings: owner, model names, token limits, search limits, nickname/initials, database name
78
+ - **`configs`** - Bot configuration and model parameters (path configurable via `TELLMGRAMBOT_CONFIGS_PATH`)
79
+ - `config.yaml` - Default bot configuration file (filename used throughout this README); can be changed by passing `config_file` to `TelegramBot.set()`
80
80
  - `models.yaml` - Token limits for each LLM model (pre-populated on first run)
81
- - **`prompts`** - Bot personas and URL analysis templates
82
- - `test_personality.prmpt` - Sample bot personality (multi-provider, can be renamed)
83
- - `url_analysis.prmpt` - URL summarization prompt template
81
+ - **`prompts`** - Bot personas (path configurable via `TELLMGRAMBOT_PROMPTS_PATH`)
82
+ - `test_personality.prmpt` - Default bot persona file (filename used throughout this README); can be changed by passing `prompt_file` to `TelegramBot.set()`
84
83
  - 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.
85
- - **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `log_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
86
- - Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `log_name` when configured).
84
+ - **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `instance_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
85
+ - Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `instance_name` when configured).
87
86
  - Bot keeps the 10 most recent logs per bot instance, automatically pruning older ones.
88
87
  - Pass `-v` or `--verbose` on startup for DEBUG-level logging.
89
- - **`data`** - SQLite database (default `conversations.db`, customizable via `db_name` config) storing all messages, users, and chats
88
+ - **`data`** - SQLite database (default `conversations.db`, customizable via `instance_name` config) storing all messages, users, and chats
90
89
  - Users manage their data via `/forget` and `/private` commands.
91
90
 
92
91
  ### Environment Variables for Paths
@@ -102,13 +101,13 @@ Override default directory locations by setting these environment variables (use
102
101
  If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
103
102
 
104
103
  ## API Keys
105
- TeLLMgramBot supports four API keys, each loadable from environment variables or `.key` files:
104
+ TeLLMgramBot supports four API keys. OpenAI, Anthropic, and VirusTotal keys load from environment variables or `.key` files. The Telegram key loads from its env var or the bot config field `telegram_api_key` (no `.key` file):
106
105
 
107
- | Key | Env Var | File | When required |
108
- |-----|---------|------|---------------|
106
+ | Key | Env Var | File/Config | When required |
107
+ |-----|---------|-------------|---------------|
109
108
  | [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
110
109
  | [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
111
- | [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `telegram.key` | Always required |
110
+ | [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | bot config `telegram_api_key` in `config.yaml` | Always required |
112
111
  | [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
113
112
 
114
113
  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.
@@ -151,9 +150,9 @@ When the bot is triggered in a group and about to respond (not deferring to anot
151
150
  - `bot_owner`: Telegram username(s) with admin access (required, no `@`). Accepts a single string or a YAML list of usernames.
152
151
  - `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
153
152
  - `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
153
+ - `telegram_api_key`: Telegram bot API key (required). Lookup order: (1) `TELLMGRAMBOT_TELEGRAM_API_KEY` env var, (2) `telegram_api_key` in `config.yaml`. Exits on placeholder, missing, or malformed key.
154
154
  - `bot_nickname` / `bot_initials`: Names the bot responds to in groups
155
- - `db_name`: Optional custom database filename without extension (e.g. `MyBot` creates `MyBot.db`); omit for default `conversations.db`. Use distinct names when running multiple bot instances in the same directory.
156
- - `log_name`: Optional label used for the console prefix and log filename (e.g. `MyBot` produces `[MyBot] INFO: ...` on the console and `MyBot_{timestamp}.log`); omit to use the bot's Telegram username. Useful for multi-platform deployments sharing the same config.
155
+ - `instance_name`: Optional label for console prefix, log filename, and database name (e.g. `MyBot` produces `[MyBot] INFO: ...` on console, `MyBot_{timestamp}.log` logs, and `MyBot.db` database); omit to use bot's Telegram username for logging and `conversations.db` for database. Use distinct names when running multiple bot instances in the same directory.
157
156
  - `token_limit`: Max tokens (optional; defaults to model's maximum)
158
157
  - `search_limit`: Max search results (optional; defaults to 30)
159
158
  - `archive_days`: Days before messages are eligible for archival (optional; default 60, minimum 1). Older messages are distilled into daily summaries, then progressively compressed into monthly digests. Once archived their respective raw messages do not return to the LLM context any more, only when searching messages.
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setup(
7
7
  name='TeLLMgramBot',
8
- version='3.13.2',
8
+ version='3.13.4',
9
9
  packages=find_packages(),
10
10
  license='MIT',
11
11
  author='Digital Heresy',
File without changes
File without changes