TeLLMgramBot 3.2.0__tar.gz → 3.2.2__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.2.0 → tellmgrambot-3.2.2}/PKG-INFO +1 -1
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/TeLLMgramBot.py +26 -19
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/conversation.py +43 -13
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/database.py +33 -10
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/initialize.py +5 -5
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/factory.py +2 -2
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/PKG-INFO +1 -1
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/setup.py +1 -1
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/LICENSE +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/README.md +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/__init__.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/message_handlers.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/models.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/__init__.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/anthropic_provider.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/base.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/openai_provider.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/utils.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/web_utils.py +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/SOURCES.txt +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/dependency_links.txt +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/requires.txt +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/top_level.txt +0 -0
- {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/setup.cfg +0 -0
|
@@ -12,7 +12,7 @@ from .providers.factory import get_provider
|
|
|
12
12
|
from .providers.base import ContextLengthExceededError, ProviderAuthError, ProviderConnectionError
|
|
13
13
|
from .initialize import INIT_BOT_CONFIG, ApiKeyStatus, init_structure, init_bot_config, init_bot_prompt
|
|
14
14
|
from .conversation import Conversation
|
|
15
|
-
from .database import get_private_mode, set_private_mode, delete_messages_for_user, delete_messages_for_chat
|
|
15
|
+
from .database import get_private_mode, set_private_mode, delete_messages_for_user, delete_messages_for_chat, delete_private_messages_for_user
|
|
16
16
|
from .models import TokenLimits
|
|
17
17
|
from .message_handlers import handle_greetings, handle_common_queries, handle_url_ask
|
|
18
18
|
from .utils import read_yaml, read_text, exact_word_match, generate_error_path, log_error
|
|
@@ -96,7 +96,7 @@ class TelegramBot:
|
|
|
96
96
|
In group chats: deletes the user's rows across all chats (private included).
|
|
97
97
|
|
|
98
98
|
In both cases, evicts only the in-memory Conversation objects that contain this
|
|
99
|
-
user's data
|
|
99
|
+
user's data - scoped to the current chat, the user's private chat, and any other
|
|
100
100
|
chats where their context was previously merged. Other users' active sessions are
|
|
101
101
|
not affected. All private-mode notices for this user are also cleared.
|
|
102
102
|
"""
|
|
@@ -128,15 +128,16 @@ class TelegramBot:
|
|
|
128
128
|
|
|
129
129
|
async def tele_private_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
130
130
|
"""
|
|
131
|
-
|
|
131
|
+
Enable or disable private mode for the requesting user (private chats only).
|
|
132
132
|
|
|
133
133
|
When private mode is ON, messages sent in private chats are flagged as is_private=1
|
|
134
|
-
in the database
|
|
135
|
-
|
|
134
|
+
in the database. These rows are excluded from all context loads - both group context
|
|
135
|
+
(cross-pollination) and private chat reloads. On /private off, they are permanently
|
|
136
|
+
deleted from the DB and purged from in-session memory (neuralyzer semantics).
|
|
136
137
|
|
|
137
138
|
Usage:
|
|
138
|
-
/private
|
|
139
|
-
/private off
|
|
139
|
+
/private - enables private mode (idempotent if already on)
|
|
140
|
+
/private off - disables private mode and purges in-session private messages
|
|
140
141
|
|
|
141
142
|
Only available in private chats; sending the command in a group returns an error.
|
|
142
143
|
"""
|
|
@@ -150,20 +151,26 @@ class TelegramBot:
|
|
|
150
151
|
return
|
|
151
152
|
|
|
152
153
|
user_id = user.id
|
|
154
|
+
chat_id = chat.id
|
|
153
155
|
args = (msg.text or "").strip().split()
|
|
154
156
|
force_off = len(args) > 1 and args[1].lower() == 'off'
|
|
155
|
-
current = await get_private_mode(user_id)
|
|
156
|
-
new_state = False if (force_off or current) else True
|
|
157
|
-
await set_private_mode(user_id, new_state)
|
|
158
157
|
|
|
159
|
-
if
|
|
158
|
+
if force_off:
|
|
159
|
+
# Neuralyzer: delete before flipping the mode flag so that if the delete
|
|
160
|
+
# fails, private mode stays ON and the user can safely retry /private off.
|
|
161
|
+
await delete_private_messages_for_user(user_id)
|
|
162
|
+
await set_private_mode(user_id, False)
|
|
163
|
+
# Also purge from in-memory conversation so the bot cannot reference them now.
|
|
164
|
+
if chat_id in self.conversations:
|
|
165
|
+
self.conversations[chat_id].purge_private_messages()
|
|
160
166
|
await msg.reply_text(
|
|
161
|
-
"Private mode is
|
|
162
|
-
"from group conversation contexts. Use /private off to disable."
|
|
167
|
+
"Private mode is OFF. Future messages in this private chat will be included in group conversation contexts."
|
|
163
168
|
)
|
|
164
169
|
else:
|
|
170
|
+
await set_private_mode(user_id, True)
|
|
165
171
|
await msg.reply_text(
|
|
166
|
-
"Private mode is
|
|
172
|
+
"Private mode is ON. Future messages in this private chat will be excluded "
|
|
173
|
+
"from group conversation contexts. Use /private off to disable."
|
|
167
174
|
)
|
|
168
175
|
|
|
169
176
|
async def tele_handle_response(self, text: str, msg: Message) -> str:
|
|
@@ -223,16 +230,16 @@ class TelegramBot:
|
|
|
223
230
|
token_budget = floor(self.chatgpt['prune_threshold'] / 2)
|
|
224
231
|
|
|
225
232
|
if user_id not in conv._context_cursor:
|
|
226
|
-
# First appearance of this user in this session
|
|
233
|
+
# First appearance of this user in this session - load their cross-chat history.
|
|
227
234
|
# Pass bot_id so private chats can also load shared group context.
|
|
228
235
|
# If no history exists yet, the cursor stays unset so the next message retries.
|
|
229
236
|
await conv.get_past_interaction(token_budget, user_id, self.telegram['bot_id'])
|
|
230
237
|
else:
|
|
231
|
-
# Already loaded
|
|
238
|
+
# Already loaded - check for new cross-chat messages since last load.
|
|
232
239
|
await conv.refresh_user_context(user_id, token_budget)
|
|
233
240
|
|
|
234
241
|
# Add the user's message to our conversation, respecting private mode
|
|
235
|
-
# Private mode only applies in private chats
|
|
242
|
+
# Private mode only applies in private chats - group messages are never flagged private
|
|
236
243
|
user_private_mode = await get_private_mode(user_id)
|
|
237
244
|
is_private = (chat_type == 'private') and user_private_mode
|
|
238
245
|
await conv.add_user_message(text, user_id, username, is_private)
|
|
@@ -246,7 +253,7 @@ class TelegramBot:
|
|
|
246
253
|
if warn_key not in self._private_mode_warned:
|
|
247
254
|
self._private_mode_warned.add(warn_key)
|
|
248
255
|
private_mode_notice = (
|
|
249
|
-
"\n\n(Note: your private mode is ON
|
|
256
|
+
"\n\n(Note: your private mode is ON - messages from our private chat "
|
|
250
257
|
"are not included in this group's context. To disable, send /private off in our DMs.)"
|
|
251
258
|
)
|
|
252
259
|
|
|
@@ -281,7 +288,7 @@ class TelegramBot:
|
|
|
281
288
|
|
|
282
289
|
# Add the full reply (including any warning) to the conversation.
|
|
283
290
|
# Propagate is_private so bot replies in private-mode exchanges are also excluded
|
|
284
|
-
# from group context loading
|
|
291
|
+
# from group context loading - otherwise the assistant's half of the conversation leaks.
|
|
285
292
|
await conv.add_assistant_message(
|
|
286
293
|
reply, self.telegram['bot_id'], self.telegram['username'], is_private
|
|
287
294
|
)
|
|
@@ -52,17 +52,22 @@ class Conversation:
|
|
|
52
52
|
self.system_model = TokenLimits(system_model)
|
|
53
53
|
self.messages = [{"role": "system", "content": system_content}]
|
|
54
54
|
|
|
55
|
-
# Maps user_id
|
|
55
|
+
# Maps user_id -> max message id at the time their cross-chat context was loaded.
|
|
56
56
|
# Used for both per-user idempotency (is the user loaded?) and mid-session refresh
|
|
57
57
|
# detection (have new cross-chat messages appeared since the last load?).
|
|
58
58
|
self._context_cursor: dict[int, int] = {}
|
|
59
59
|
|
|
60
|
-
# Maps user_id
|
|
60
|
+
# Maps user_id -> len(self.messages) after their history was loaded.
|
|
61
61
|
# Marks the boundary between historical context and live session messages so that
|
|
62
62
|
# refresh_user_context() inserts delta rows at the right position (after history,
|
|
63
63
|
# before live messages) rather than at index 1 (before all existing history).
|
|
64
64
|
self._history_end: dict[int, int] = {}
|
|
65
65
|
|
|
66
|
+
# Set of id()s for message dicts added while private mode was ON.
|
|
67
|
+
# Used by purge_private_messages() to remove them from self.messages without
|
|
68
|
+
# touching the database. Cleared on purge or clear_interaction().
|
|
69
|
+
self._private_message_ids: set[int] = set()
|
|
70
|
+
|
|
66
71
|
def set_system_content(self, new_content: str):
|
|
67
72
|
"""
|
|
68
73
|
Replace the system content (bot's personality prompt) in the conversation.
|
|
@@ -83,9 +88,13 @@ class Conversation:
|
|
|
83
88
|
content: The message text.
|
|
84
89
|
user_id: Telegram user ID of the sender.
|
|
85
90
|
username: Telegram username (display only, may be None).
|
|
86
|
-
is_private: If True,
|
|
91
|
+
is_private: If True, flags this message as private-mode excluded from
|
|
92
|
+
all context loads (user has /private ON in a private chat).
|
|
87
93
|
"""
|
|
88
|
-
|
|
94
|
+
msg_dict = {"role": "user", "content": content}
|
|
95
|
+
self.messages.append(msg_dict)
|
|
96
|
+
if is_private:
|
|
97
|
+
self._private_message_ids.add(id(msg_dict))
|
|
89
98
|
await insert_message(self.chat_id, user_id, username, "user", content, is_private)
|
|
90
99
|
|
|
91
100
|
async def add_assistant_message(self, content: str, bot_user_id: int, bot_username: str | None, is_private: bool = False):
|
|
@@ -100,7 +109,10 @@ class Conversation:
|
|
|
100
109
|
the is_private flag of the user message it responds to, so that both
|
|
101
110
|
sides of a private-mode exchange are excluded from shared contexts.
|
|
102
111
|
"""
|
|
103
|
-
|
|
112
|
+
msg_dict = {"role": "assistant", "content": content}
|
|
113
|
+
self.messages.append(msg_dict)
|
|
114
|
+
if is_private:
|
|
115
|
+
self._private_message_ids.add(id(msg_dict))
|
|
104
116
|
await insert_message(self.chat_id, bot_user_id, bot_username, "assistant", content, is_private)
|
|
105
117
|
|
|
106
118
|
async def get_message_token_count(self) -> int:
|
|
@@ -124,7 +136,7 @@ class Conversation:
|
|
|
124
136
|
Remove oldest user-assistant messages until token count is below threshold.
|
|
125
137
|
|
|
126
138
|
Recursively removes messages starting from index 1 (preserving the system message at index 0)
|
|
127
|
-
until the conversation fits within the token limit. The database is not modified
|
|
139
|
+
until the conversation fits within the token limit. The database is not modified - pruning
|
|
128
140
|
only affects the in-memory context window for the current session.
|
|
129
141
|
|
|
130
142
|
Args:
|
|
@@ -138,6 +150,23 @@ class Conversation:
|
|
|
138
150
|
except Exception as e:
|
|
139
151
|
log_error(e, f"{type(e).__name__} pruning under {token_limit} tokens for {self.chat_print}", self.error_log)
|
|
140
152
|
|
|
153
|
+
def purge_private_messages(self):
|
|
154
|
+
"""
|
|
155
|
+
Remove all in-session private-mode messages from the in-memory conversation.
|
|
156
|
+
|
|
157
|
+
Strips any messages added while private mode was ON from self.messages without
|
|
158
|
+
modifying the database. Called when a user disables private mode (/private off)
|
|
159
|
+
so the bot can no longer reference messages sent during the private session.
|
|
160
|
+
|
|
161
|
+
Side Effects:
|
|
162
|
+
- Modifies self.messages in-place, removing tracked private message dicts.
|
|
163
|
+
- Clears self._private_message_ids.
|
|
164
|
+
"""
|
|
165
|
+
if not self._private_message_ids:
|
|
166
|
+
return
|
|
167
|
+
self.messages = [m for m in self.messages if id(m) not in self._private_message_ids]
|
|
168
|
+
self._private_message_ids.clear()
|
|
169
|
+
|
|
141
170
|
async def get_past_interaction(self, token_limit: int, user_id: int, bot_id: int | None = None) -> bool:
|
|
142
171
|
"""
|
|
143
172
|
Load past conversation messages from the database up to a token limit.
|
|
@@ -145,10 +174,9 @@ class Conversation:
|
|
|
145
174
|
Implements bidirectional cross-pollination: loads all messages from the current chat
|
|
146
175
|
(all participants) plus all messages from the user's private chat (both user and
|
|
147
176
|
assistant turns), merged chronologically. This eliminates amnesia when a user switches
|
|
148
|
-
between private and group contexts.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
so private-mode messages don't surface in shared contexts.
|
|
177
|
+
between private and group contexts. Messages marked as private (is_private=1) are
|
|
178
|
+
always excluded, regardless of chat type, ensuring private-mode exchanges never leak
|
|
179
|
+
into any session context.
|
|
152
180
|
|
|
153
181
|
For private chats, if bot_id is provided and remaining token budget allows, also loads
|
|
154
182
|
full message history from any groups shared between the user and the bot. This lets the
|
|
@@ -181,7 +209,7 @@ class Conversation:
|
|
|
181
209
|
print(f"Context already loaded for User {user_id} in {self.chat_print}")
|
|
182
210
|
return True
|
|
183
211
|
|
|
184
|
-
exclude_private =
|
|
212
|
+
exclude_private = True
|
|
185
213
|
rows = await load_full_user_context(user_id, self.chat_id, exclude_private=exclude_private)
|
|
186
214
|
|
|
187
215
|
token_limit_reached = False
|
|
@@ -231,7 +259,7 @@ class Conversation:
|
|
|
231
259
|
label = "Full" if not token_limit_reached and not group_limit_reached else "Partial"
|
|
232
260
|
group_note = f", plus {group_rows_inserted} group message(s)" if group_rows_inserted > 0 else ""
|
|
233
261
|
print(
|
|
234
|
-
f"{label} context loaded for User {user_id} in {self.chat_print}
|
|
262
|
+
f"{label} context loaded for User {user_id} in {self.chat_print} - "
|
|
235
263
|
f"storing {token_count} token(s){group_note}"
|
|
236
264
|
)
|
|
237
265
|
else:
|
|
@@ -267,7 +295,7 @@ class Conversation:
|
|
|
267
295
|
if user_id not in self._context_cursor:
|
|
268
296
|
return False
|
|
269
297
|
|
|
270
|
-
exclude_private =
|
|
298
|
+
exclude_private = True
|
|
271
299
|
current_max = await get_max_cross_chat_message_id(user_id, self.chat_id, exclude_private)
|
|
272
300
|
|
|
273
301
|
if current_max <= self._context_cursor[user_id]:
|
|
@@ -314,11 +342,13 @@ class Conversation:
|
|
|
314
342
|
- Clears self.messages to contain only the system message (index 0).
|
|
315
343
|
- Resets self._context_cursor to empty dict.
|
|
316
344
|
- Resets self._history_end to empty dict.
|
|
345
|
+
- Resets self._private_message_ids to empty set.
|
|
317
346
|
- Deletes all messages for this chat (private) or this user (group) from database.
|
|
318
347
|
"""
|
|
319
348
|
self.messages = [self.messages[0]]
|
|
320
349
|
self._context_cursor = {}
|
|
321
350
|
self._history_end = {}
|
|
351
|
+
self._private_message_ids = set()
|
|
322
352
|
if self.chat_type == 'private':
|
|
323
353
|
await delete_messages_for_chat(self.chat_id)
|
|
324
354
|
else:
|
|
@@ -48,7 +48,7 @@ def get_db_path() -> str:
|
|
|
48
48
|
|
|
49
49
|
Reads TELLMGRAMBOT_DATA_PATH env var (directory path) and appends conversations.db.
|
|
50
50
|
Falls back to data/conversations.db relative to the execution directory if unset.
|
|
51
|
-
Module-level _DB_PATH overrides everything
|
|
51
|
+
Module-level _DB_PATH overrides everything - used by tests.
|
|
52
52
|
"""
|
|
53
53
|
if _DB_PATH:
|
|
54
54
|
return _DB_PATH
|
|
@@ -59,7 +59,7 @@ async def init_db() -> None:
|
|
|
59
59
|
"""
|
|
60
60
|
Create the messages and private_mode tables if they do not exist.
|
|
61
61
|
Enables WAL journal mode for safe concurrent reads and writes.
|
|
62
|
-
Safe to call repeatedly
|
|
62
|
+
Safe to call repeatedly - all statements use IF NOT EXISTS.
|
|
63
63
|
"""
|
|
64
64
|
async with aiosqlite.connect(get_db_path()) as db:
|
|
65
65
|
await db.executescript(_DDL)
|
|
@@ -160,26 +160,28 @@ async def load_full_user_context(
|
|
|
160
160
|
In Telegram, a user's private chat_id always equals their user_id. Three arms cover
|
|
161
161
|
all cross-context cases:
|
|
162
162
|
|
|
163
|
-
Arm 1
|
|
163
|
+
Arm 1 - chat_id = current_chat_id:
|
|
164
164
|
All messages (user and bot) in the current chat. Always included.
|
|
165
165
|
|
|
166
|
-
Arm 2
|
|
166
|
+
Arm 2 - chat_id = user_id AND chat_id != current_chat_id:
|
|
167
167
|
All messages (both sides) from the user's private chat, included when the
|
|
168
168
|
current chat is a group. Pulling both sides restores proper role alternation
|
|
169
169
|
that was broken when only the user's own rows were fetched.
|
|
170
170
|
|
|
171
|
-
Arm 3
|
|
171
|
+
Arm 3 - user_id = user_id AND chat_id != current_chat_id AND chat_id != user_id:
|
|
172
172
|
The requesting user's own messages from any other group chats (not the private
|
|
173
173
|
chat, not the current chat). Covers group-to-private and group-to-group context.
|
|
174
174
|
Only the user's own rows are fetched from other groups (not bot replies).
|
|
175
175
|
|
|
176
|
-
The exclude_private
|
|
176
|
+
The exclude_private flag is controlled by the caller and applies regardless of chat type.
|
|
177
|
+
Conversation.get_past_interaction always passes exclude_private=True so that is_private=1
|
|
178
|
+
rows are never rehydrated into any session context.
|
|
177
179
|
|
|
178
180
|
Args:
|
|
179
181
|
user_id: Telegram user ID of the requesting user.
|
|
180
182
|
current_chat_id: The chat_id of the current conversation (negative for groups, positive for private).
|
|
181
|
-
exclude_private: If True, rows with is_private=1 are excluded from all
|
|
182
|
-
|
|
183
|
+
exclude_private: If True, rows with is_private=1 are excluded from all arms of the query.
|
|
184
|
+
Always passed as True by Conversation.get_past_interaction so that private-mode messages
|
|
183
185
|
don't surface in shared contexts.
|
|
184
186
|
|
|
185
187
|
Returns:
|
|
@@ -243,7 +245,7 @@ async def load_new_cross_chat_context(
|
|
|
243
245
|
Args:
|
|
244
246
|
user_id: Telegram user ID of the requesting user.
|
|
245
247
|
current_chat_id: The chat_id of the current conversation.
|
|
246
|
-
since_id: Exclusive lower bound
|
|
248
|
+
since_id: Exclusive lower bound - only rows with id > since_id are returned.
|
|
247
249
|
exclude_private: If True, rows with is_private=1 are excluded.
|
|
248
250
|
|
|
249
251
|
Returns:
|
|
@@ -342,7 +344,7 @@ async def load_shared_group_context(
|
|
|
342
344
|
async def delete_messages_for_user(user_id: int) -> None:
|
|
343
345
|
"""
|
|
344
346
|
Delete all message rows for a given user_id across all chats.
|
|
345
|
-
Used by /forget in group chats
|
|
347
|
+
Used by /forget in group chats - only the issuing user's rows are removed.
|
|
346
348
|
"""
|
|
347
349
|
async with aiosqlite.connect(get_db_path()) as db:
|
|
348
350
|
await db.execute("DELETE FROM messages WHERE user_id = ?", (user_id,))
|
|
@@ -357,6 +359,27 @@ async def delete_messages_for_chat(chat_id: int) -> None:
|
|
|
357
359
|
await db.execute("DELETE FROM messages WHERE chat_id = ?", (chat_id,))
|
|
358
360
|
await db.commit()
|
|
359
361
|
|
|
362
|
+
async def delete_private_messages_for_user(user_id: int) -> None:
|
|
363
|
+
"""
|
|
364
|
+
Delete all private-mode message rows for a user from their private chat.
|
|
365
|
+
|
|
366
|
+
Removes rows where chat_id = user_id (the user's Telegram private chat) AND
|
|
367
|
+
is_private = 1. This covers both the user's own messages and the bot's replies
|
|
368
|
+
stored in the same private chat, since assistant turns share the same chat_id.
|
|
369
|
+
|
|
370
|
+
Called when the user turns private mode off (/private off) to ensure that
|
|
371
|
+
private-mode messages do not resurface on the next session reload.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
user_id: Telegram user ID; also serves as the private chat_id.
|
|
375
|
+
"""
|
|
376
|
+
async with aiosqlite.connect(get_db_path()) as db:
|
|
377
|
+
await db.execute(
|
|
378
|
+
"DELETE FROM messages WHERE chat_id = ? AND is_private = 1",
|
|
379
|
+
(user_id,)
|
|
380
|
+
)
|
|
381
|
+
await db.commit()
|
|
382
|
+
|
|
360
383
|
async def get_private_mode(user_id: int) -> bool:
|
|
361
384
|
"""
|
|
362
385
|
Return whether private mode is currently enabled for this user.
|
|
@@ -112,7 +112,7 @@ def init_keys() -> ApiKeyStatus:
|
|
|
112
112
|
"""
|
|
113
113
|
Load API keys and return a ApiKeyStatus indicating which features are available.
|
|
114
114
|
|
|
115
|
-
Telegram key is always required
|
|
115
|
+
Telegram key is always required - bot exits if missing or invalid.
|
|
116
116
|
All other keys (OpenAI, Anthropic, VirusTotal) are optional: missing keys
|
|
117
117
|
disable the associated features rather than exiting.
|
|
118
118
|
|
|
@@ -159,7 +159,7 @@ def init_keys() -> ApiKeyStatus:
|
|
|
159
159
|
f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
|
|
160
160
|
"VirusTotal integration" if key == 'virustotal' else None]
|
|
161
161
|
reason = ", ".join(w for w in which if w) or "related features"
|
|
162
|
-
print(f"Warning: {key}.key not found
|
|
162
|
+
print(f"Warning: {key}.key not found - {reason} will be disabled.")
|
|
163
163
|
continue
|
|
164
164
|
else:
|
|
165
165
|
os.environ[env_var] = read_text(path)
|
|
@@ -173,7 +173,7 @@ def init_keys() -> ApiKeyStatus:
|
|
|
173
173
|
f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
|
|
174
174
|
"VirusTotal integration" if key == 'virustotal' else None]
|
|
175
175
|
reason = ", ".join(w for w in which if w) or "related features"
|
|
176
|
-
print(f"Warning: {env_var} is blank or invalid
|
|
176
|
+
print(f"Warning: {env_var} is blank or invalid - {reason} will be disabled.")
|
|
177
177
|
continue
|
|
178
178
|
|
|
179
179
|
available.add(key)
|
|
@@ -348,8 +348,8 @@ def init_structure() -> ApiKeyStatus:
|
|
|
348
348
|
- Configuration files (config.yaml, models.yaml) with inline parameter descriptions.
|
|
349
349
|
- System prompt files (test_personality.prmpt, url_analysis.prmpt).
|
|
350
350
|
|
|
351
|
-
Provider key requirements are inferred from config.yaml model prefixes (claude-*
|
|
352
|
-
gpt-*
|
|
351
|
+
Provider key requirements are inferred from config.yaml model prefixes (claude-* -> Anthropic,
|
|
352
|
+
gpt-* -> OpenAI, etc.) to streamline setup for single-provider deployments.
|
|
353
353
|
|
|
354
354
|
Returns:
|
|
355
355
|
ApiKeyStatus indicating which features are available based on loaded API keys.
|
|
@@ -9,8 +9,8 @@ def get_provider(model: str) -> LLMProvider:
|
|
|
9
9
|
Return the appropriate LLM provider for the given model name.
|
|
10
10
|
|
|
11
11
|
Provider is inferred from the model name prefix:
|
|
12
|
-
- 'claude-'
|
|
13
|
-
- anything else
|
|
12
|
+
- 'claude-' -> AnthropicProvider
|
|
13
|
+
- anything else -> OpenAIProvider
|
|
14
14
|
"""
|
|
15
15
|
if model.startswith('claude-'):
|
|
16
16
|
key = 'anthropic'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|