TeLLMgramBot 3.4.0__tar.gz → 3.4.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.
Files changed (24) hide show
  1. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/PKG-INFO +2 -9
  2. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/README.md +1 -8
  3. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/TeLLMgramBot.py +28 -24
  4. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/conversation.py +8 -4
  5. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/database.py +37 -46
  6. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/message_handlers.py +0 -21
  7. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot.egg-info/PKG-INFO +2 -9
  8. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/setup.py +1 -1
  9. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/LICENSE +0 -0
  10. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/__init__.py +0 -0
  11. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/initialize.py +0 -0
  12. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/models.py +0 -0
  13. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/providers/__init__.py +0 -0
  14. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/providers/anthropic_provider.py +0 -0
  15. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/providers/base.py +0 -0
  16. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/providers/factory.py +0 -0
  17. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/providers/openai_provider.py +0 -0
  18. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/utils.py +0 -0
  19. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot/web_utils.py +0 -0
  20. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot.egg-info/SOURCES.txt +0 -0
  21. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot.egg-info/dependency_links.txt +0 -0
  22. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot.egg-info/requires.txt +0 -0
  23. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/TeLLMgramBot.egg-info/top_level.txt +0 -0
  24. {tellmgrambot-3.4.0 → tellmgrambot-3.4.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TeLLMgramBot
3
- Version: 3.4.0
3
+ Version: 3.4.2
4
4
  Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
5
5
  Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
6
6
  Author: Digital Heresy
@@ -144,14 +144,7 @@ Each file with the associated API key will update its respective environment var
144
144
  ## Commands and Interactions
145
145
 
146
146
  ### Read Receipt Acknowledgement (Group Chats Only)
147
- In group and supergroup chats, you can request the bot to acknowledge reading your message with a lightweight 👀 reaction instead of waiting for a full response. Simply mention the bot's username, nickname, or initials followed by an explicit request:
148
-
149
- - `@botname acknowledge`
150
- - `botname mark as read`
151
- - `BN did you read that?`
152
- - `botname confirm you received`
153
-
154
- The bot will respond with a 👀 reaction emoji on your message (if the Telegram client supports message reactions), or with a short "Got it!" reply as fallback. Read receipt requests are processed immediately without touching the conversation context, making them ideal for confirming the bot is online without disrupting group flow.
147
+ In group and supergroup chats, whenever you mention the bot by username (`@botname`), nickname, or initials, the bot immediately responds with a 👀 reaction emoji on your message to confirm it is online, followed by a full LLM response. The reaction uses the Telegram message reaction API when available (👀 emoji), or falls back to a short "Got it!" text reply for older clients that don't support reactions.
155
148
 
156
149
  ## Bot Setup
157
150
  This library includes an example script `test_local.py`, which uses files from the folders `configs` and `prompts` for TeLLMgramBot to process.
@@ -113,14 +113,7 @@ Each file with the associated API key will update its respective environment var
113
113
  ## Commands and Interactions
114
114
 
115
115
  ### Read Receipt Acknowledgement (Group Chats Only)
116
- In group and supergroup chats, you can request the bot to acknowledge reading your message with a lightweight 👀 reaction instead of waiting for a full response. Simply mention the bot's username, nickname, or initials followed by an explicit request:
117
-
118
- - `@botname acknowledge`
119
- - `botname mark as read`
120
- - `BN did you read that?`
121
- - `botname confirm you received`
122
-
123
- The bot will respond with a 👀 reaction emoji on your message (if the Telegram client supports message reactions), or with a short "Got it!" reply as fallback. Read receipt requests are processed immediately without touching the conversation context, making them ideal for confirming the bot is online without disrupting group flow.
116
+ In group and supergroup chats, whenever you mention the bot by username (`@botname`), nickname, or initials, the bot immediately responds with a 👀 reaction emoji on your message to confirm it is online, followed by a full LLM response. The reaction uses the Telegram message reaction API when available (👀 emoji), or falls back to a short "Got it!" text reply for older clients that don't support reactions.
124
117
 
125
118
  ## Bot Setup
126
119
  This library includes an example script `test_local.py`, which uses files from the folders `configs` and `prompts` for TeLLMgramBot to process.
@@ -27,10 +27,10 @@ from .database import (
27
27
  delete_messages_for_user,
28
28
  delete_messages_for_chat,
29
29
  delete_private_messages_for_user,
30
- delete_paired_bot_replies,
30
+ delete_bot_replies_for_user,
31
31
  )
32
32
  from .models import TokenLimits
33
- from .message_handlers import handle_greetings, handle_common_queries, handle_url_ask, handle_read_receipt
33
+ from .message_handlers import handle_greetings, handle_common_queries, handle_url_ask
34
34
  from .utils import read_yaml, read_text, exact_word_match, generate_error_path, log_error
35
35
 
36
36
  class TelegramBot:
@@ -108,19 +108,24 @@ class TelegramBot:
108
108
  """
109
109
  Remove conversation history from the database (behavior depends on chat type).
110
110
 
111
- In private chats: deletes all messages in the private chat (including bot replies).
112
- In group chats: deletes the user's rows across all chats (private included), then
113
- deletes bot replies in the current chat that were paired with the user's messages
114
- or were orphaned by a previous /forget call, leaving other users' conversations intact.
111
+ In private chats: deletes all bot replies whose reply_to_id points to this user's messages,
112
+ then deletes the user's rows across all chats (full wipe including group messages),
113
+ then deletes any remaining messages in the private chat (pre-migration rows with NULL reply_to_id).
114
+ In group chats: deletes bot replies linked to this user's messages, then deletes the user's
115
+ rows across all chats, leaving other users' messages intact.
115
116
 
116
117
  In both cases, evicts only the in-memory Conversation objects that contain this
117
118
  user's data - scoped to the current chat, the user's private chat, and any other
118
119
  chats where their context was previously merged. Other users' active sessions are
119
120
  not affected.
120
121
  """
121
- user_id = update.message.from_user.id
122
- chat_id = update.message.chat.id
123
- chat_type = update.message.chat.type
122
+ validated = await self.tele_validate(update)
123
+ if not validated:
124
+ return
125
+ (msg, chat, user) = validated
126
+ user_id = user.id
127
+ chat_id = chat.id
128
+ chat_type = chat.type
124
129
 
125
130
  # Find all chat_ids where this user's context has been merged in memory,
126
131
  # identified by user_id being present in the Conversation's _context_cursor.
@@ -131,19 +136,21 @@ class TelegramBot:
131
136
  user_chat_ids.update({chat_id, user_id})
132
137
 
133
138
  if chat_type == 'private':
139
+ # Wipe bot replies linked to this user, then user's own rows across all chats,
140
+ # then any remaining bot replies in the private chat (pre-migration rows).
141
+ await delete_bot_replies_for_user(user_id)
142
+ await delete_messages_for_user(user_id)
134
143
  await delete_messages_for_chat(chat_id)
135
144
  else:
145
+ await delete_bot_replies_for_user(user_id)
136
146
  await delete_messages_for_user(user_id)
137
- # Delete bot replies paired to this user's messages, plus any pre-existing
138
- # orphaned assistant rows left by earlier /forget calls in this chat.
139
- await delete_paired_bot_replies(chat_id, self.telegram['bot_id'])
140
147
 
141
148
  # Evict only the Conversations that contain this user's data, not all sessions.
142
149
  for evict_id in user_chat_ids:
143
150
  self.conversations.pop(evict_id, None)
144
151
  self.token_warning.pop(evict_id, None)
145
152
 
146
- await update.message.reply_text("My memories of our conversations are wiped!")
153
+ await msg.reply_text("My memories of our conversations are wiped!")
147
154
 
148
155
  async def tele_private_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
149
156
  """
@@ -261,7 +268,7 @@ class TelegramBot:
261
268
  # Private mode only applies in private chats - group messages are never flagged private
262
269
  user_private_mode = await get_private_mode(user_id)
263
270
  is_private = (chat_type == 'private') and user_private_mode
264
- await conv.add_user_message(text, user_id, username, is_private)
271
+ user_msg_id = await conv.add_user_message(text, user_id, username, is_private)
265
272
 
266
273
  # Check if the user is asking about a [URL]
267
274
  url_match = re.search(r'\[http(s)?://\S+]', text)
@@ -293,7 +300,7 @@ class TelegramBot:
293
300
  # Propagate is_private so bot replies in private-mode exchanges are also excluded
294
301
  # from group context loading - otherwise the assistant's half of the conversation leaks.
295
302
  await conv.add_assistant_message(
296
- reply, self.telegram['bot_id'], self.telegram['username'], is_private
303
+ reply, self.telegram['bot_id'], self.telegram['username'], is_private, user_msg_id
297
304
  )
298
305
 
299
306
  # Recompute token count after full exchange is stored, then prune if needed
@@ -307,9 +314,10 @@ class TelegramBot:
307
314
  """
308
315
  Send a read receipt acknowledgement via message reaction.
309
316
 
310
- Attempts to add a eyes-emoji reaction to the user's message to confirm the bot
311
- read it, avoiding a full LLM response. If the message reaction API is unavailable
312
- (e.g., older Telegram clients), falls back to a short "Got it!" text reply.
317
+ Attempts to add a 👀 emoji reaction to the user's message to confirm the bot
318
+ read it. If the message reaction API is unavailable (e.g., older Telegram clients),
319
+ falls back to a short "Got it!" text reply. Called unconditionally on every group
320
+ mention before the normal LLM response.
313
321
 
314
322
  Args:
315
323
  msg: The Telegram Message object to react to.
@@ -338,17 +346,13 @@ class TelegramBot:
338
346
  if exact_word_match(self.telegram['username'], msg.text):
339
347
  pattern = r'@?\b' + re.escape(self.telegram['username']) + r'\b'
340
348
  new_text = re.sub(pattern, '', msg.text).strip()
341
- if handle_read_receipt(new_text):
342
- await self._send_read_receipt(msg, context)
343
- return
349
+ await self._send_read_receipt(msg, context)
344
350
  response = await self.tele_handle_response(new_text, msg)
345
351
  elif (
346
352
  exact_word_match(self.telegram['nickname'], msg.text) or
347
353
  exact_word_match(self.telegram['initials'], msg.text)
348
354
  ):
349
- if handle_read_receipt(msg.text):
350
- await self._send_read_receipt(msg, context)
351
- return
355
+ await self._send_read_receipt(msg, context)
352
356
  response = await self.tele_handle_response(msg.text, msg)
353
357
  else:
354
358
  return
@@ -80,7 +80,7 @@ class Conversation:
80
80
  self.system_content = new_content
81
81
  self.messages[0] = {"role": "system", "content": new_content}
82
82
 
83
- async def add_user_message(self, content: str, user_id: int, username: str | None, is_private: bool = False):
83
+ async def add_user_message(self, content: str, user_id: int, username: str | None, is_private: bool = False) -> int:
84
84
  """
85
85
  Add a user message to conversation memory and persist it to the database.
86
86
 
@@ -90,14 +90,17 @@ class Conversation:
90
90
  username: Telegram username (display only, may be None).
91
91
  is_private: If True, flags this message as private-mode excluded from
92
92
  all context loads (user has /private ON in a private chat).
93
+
94
+ Returns:
95
+ The database id of the newly inserted row, for use as reply_to_id.
93
96
  """
94
97
  msg_dict = {"role": "user", "content": content}
95
98
  self.messages.append(msg_dict)
96
99
  if is_private:
97
100
  self._private_message_ids.add(id(msg_dict))
98
- await insert_message(self.chat_id, user_id, username, "user", content, is_private)
101
+ return await insert_message(self.chat_id, user_id, username, "user", content, is_private)
99
102
 
100
- async def add_assistant_message(self, content: str, bot_user_id: int, bot_username: str | None, is_private: bool = False):
103
+ async def add_assistant_message(self, content: str, bot_user_id: int, bot_username: str | None, is_private: bool = False, reply_to_id: int | None = None):
101
104
  """
102
105
  Add an assistant message to conversation memory and persist it to the database.
103
106
 
@@ -108,12 +111,13 @@ class Conversation:
108
111
  is_private: If True, excludes this reply from group context loading. Should match
109
112
  the is_private flag of the user message it responds to, so that both
110
113
  sides of a private-mode exchange are excluded from shared contexts.
114
+ reply_to_id: Database id of the user message this reply responds to.
111
115
  """
112
116
  msg_dict = {"role": "assistant", "content": content}
113
117
  self.messages.append(msg_dict)
114
118
  if is_private:
115
119
  self._private_message_ids.add(id(msg_dict))
116
- await insert_message(self.chat_id, bot_user_id, bot_username, "assistant", content, is_private)
120
+ await insert_message(self.chat_id, bot_user_id, bot_username, "assistant", content, is_private, reply_to_id)
117
121
 
118
122
  async def get_message_token_count(self) -> int:
119
123
  """
@@ -22,14 +22,15 @@ _DDL = """
22
22
  PRAGMA journal_mode=WAL;
23
23
 
24
24
  CREATE TABLE IF NOT EXISTS messages (
25
- id INTEGER PRIMARY KEY AUTOINCREMENT,
26
- chat_id INTEGER NOT NULL,
27
- user_id INTEGER NOT NULL,
28
- username TEXT,
29
- role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
30
- content TEXT NOT NULL,
31
- is_private INTEGER NOT NULL DEFAULT 0,
32
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
25
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26
+ chat_id INTEGER NOT NULL,
27
+ user_id INTEGER NOT NULL,
28
+ username TEXT,
29
+ role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
30
+ content TEXT NOT NULL,
31
+ is_private INTEGER NOT NULL DEFAULT 0,
32
+ reply_to_id INTEGER,
33
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
33
34
  );
34
35
 
35
36
  CREATE INDEX IF NOT EXISTS idx_messages_chat_id ON messages(chat_id, created_at);
@@ -60,9 +61,14 @@ async def init_db() -> None:
60
61
  Create the messages and private_mode tables if they do not exist.
61
62
  Enables WAL journal mode for safe concurrent reads and writes.
62
63
  Safe to call repeatedly - all statements use IF NOT EXISTS.
64
+ Migrates existing databases by adding reply_to_id if not present.
63
65
  """
64
66
  async with aiosqlite.connect(get_db_path()) as db:
65
67
  await db.executescript(_DDL)
68
+ # Migration: add reply_to_id to existing databases that predate this column.
69
+ existing = {row[1] async for row in await db.execute("PRAGMA table_info(messages)")}
70
+ if 'reply_to_id' not in existing:
71
+ await db.execute("ALTER TABLE messages ADD COLUMN reply_to_id INTEGER")
66
72
  await db.commit()
67
73
 
68
74
  async def insert_message(
@@ -72,9 +78,10 @@ async def insert_message(
72
78
  role: str,
73
79
  content: str,
74
80
  is_private: bool = False,
75
- ) -> None:
81
+ reply_to_id: Optional[int] = None,
82
+ ) -> int:
76
83
  """
77
- Persist a single message row.
84
+ Persist a single message row and return its database id.
78
85
 
79
86
  Args:
80
87
  chat_id: Telegram chat ID (negative for groups, positive/==user_id for private).
@@ -83,14 +90,19 @@ async def insert_message(
83
90
  role: 'user', 'assistant', or 'system'.
84
91
  content: Message text.
85
92
  is_private: If True, this message is excluded from group context loading.
93
+ reply_to_id: Database id of the message this is a reply to (None if not a reply).
94
+
95
+ Returns:
96
+ The integer id of the newly inserted row.
86
97
  """
87
98
  async with aiosqlite.connect(get_db_path()) as db:
88
- await db.execute(
89
- "INSERT INTO messages (chat_id, user_id, username, role, content, is_private) "
90
- "VALUES (?, ?, ?, ?, ?, ?)",
91
- (chat_id, user_id, username, role, content, int(is_private)),
99
+ cursor = await db.execute(
100
+ "INSERT INTO messages (chat_id, user_id, username, role, content, is_private, reply_to_id) "
101
+ "VALUES (?, ?, ?, ?, ?, ?, ?)",
102
+ (chat_id, user_id, username, role, content, int(is_private), reply_to_id),
92
103
  )
93
104
  await db.commit()
105
+ return cursor.lastrowid
94
106
 
95
107
  async def load_messages_for_chat(
96
108
  chat_id: int,
@@ -338,51 +350,30 @@ async def load_shared_group_context(
338
350
  all_rows.sort(key=lambda r: (r[2], r[3]))
339
351
  return [{"role": row[0], "content": row[1]} for row in all_rows]
340
352
 
341
- async def delete_paired_bot_replies(chat_id: int, bot_id: int) -> None:
342
- """
343
- Delete bot reply rows in a group chat that are paired with deleted user messages or orphaned.
344
353
 
345
- Handles two cases in one pass:
346
- 1. Paired bot replies: after delete_messages_for_user() removes a user's rows, the bot
347
- replies that immediately followed those messages become orphaned (no user row before
348
- them). These are caught by the orphan query below.
349
- 2. Pre-existing orphans: assistant rows left behind by earlier /forget calls that deleted
350
- user rows but not their paired replies. Cleaned up opportunistically on every group
351
- /forget to heal historical debris.
354
+ async def delete_bot_replies_for_user(user_id: int) -> None:
355
+ """
356
+ Delete assistant rows whose reply_to_id references any message from this user.
352
357
 
353
- Both cases are unified by a single query: delete assistant rows whose immediately preceding
354
- message in the same chat is not a user message. Reliable because the bot processes messages
355
- sequentially - each reply is stored immediately after its user message, so the immediately
356
- preceding row unambiguously identifies the pairing. Other users' conversations are unaffected
357
- as long as their user rows remain (their user rows serve as the preceding message for their
358
- own bot replies).
358
+ Must be called before delete_messages_for_user so that the user's message ids
359
+ still exist for the subquery to match against. Rows with reply_to_id = NULL
360
+ (pre-migration messages) are unaffected.
359
361
 
360
362
  Args:
361
- chat_id: Telegram chat ID of the group chat to clean up.
362
- bot_id: Telegram user ID of the bot account (identifies assistant rows by user_id).
363
+ user_id: Telegram user ID whose bot replies should be removed.
363
364
  """
364
365
  async with aiosqlite.connect(get_db_path()) as db:
365
366
  await db.execute(
366
- "DELETE FROM messages "
367
- "WHERE chat_id = ? AND role = 'assistant' AND user_id = ? "
368
- "AND NOT EXISTS ("
369
- " SELECT 1 FROM messages prev "
370
- " WHERE prev.chat_id = messages.chat_id "
371
- " AND prev.id = ("
372
- " SELECT MAX(m2.id) FROM messages m2 "
373
- " WHERE m2.chat_id = messages.chat_id "
374
- " AND m2.id < messages.id"
375
- " ) "
376
- " AND prev.role = 'user'"
377
- ")",
378
- (chat_id, bot_id),
367
+ "DELETE FROM messages WHERE role = 'assistant' "
368
+ "AND reply_to_id IN (SELECT id FROM messages WHERE user_id = ?)",
369
+ (user_id,),
379
370
  )
380
371
  await db.commit()
381
372
 
382
373
  async def delete_messages_for_user(user_id: int) -> None:
383
374
  """
384
375
  Delete all message rows for a given user_id across all chats.
385
- Used by /forget in group chats - only the issuing user's rows are removed.
376
+ Used by /forget - call delete_bot_replies_for_user first to clean up linked bot replies.
386
377
  """
387
378
  async with aiosqlite.connect(get_db_path()) as db:
388
379
  await db.execute("DELETE FROM messages WHERE user_id = ?", (user_id,))
@@ -27,27 +27,6 @@ def handle_greetings(text: str) -> Optional[str]:
27
27
  return f'{word}!'
28
28
  return None
29
29
 
30
- def handle_read_receipt(text: str) -> bool:
31
- """
32
- Detect explicit user requests for read receipt acknowledgement.
33
-
34
- Matches explicit acknowledgement trigger phrases: "acknowledge", "acknowledged", "mark as read",
35
- "read receipt", "did you read", "did you get that", and "confirm you received". Uses word-boundary
36
- matching to avoid false positives from casual conversation (e.g., "unacknowledged" does not match).
37
-
38
- Args:
39
- text: The message text to analyze (typically after bot mention/nickname stripped).
40
-
41
- Returns:
42
- True if the message matches a read receipt trigger phrase; False otherwise.
43
- """
44
- text_clean = re.sub(r'[^\w\s]', '', text.lower().strip())
45
- triggers = {
46
- 'acknowledge', 'acknowledged', 'mark as read',
47
- 'read receipt', 'did you read', 'did you get that',
48
- 'confirm you received',
49
- }
50
- return any(re.search(rf'\b{re.escape(t)}\b', text_clean) is not None for t in triggers)
51
30
 
52
31
  def handle_common_queries(text: str) -> Optional[str]:
53
32
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TeLLMgramBot
3
- Version: 3.4.0
3
+ Version: 3.4.2
4
4
  Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
5
5
  Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
6
6
  Author: Digital Heresy
@@ -144,14 +144,7 @@ Each file with the associated API key will update its respective environment var
144
144
  ## Commands and Interactions
145
145
 
146
146
  ### Read Receipt Acknowledgement (Group Chats Only)
147
- In group and supergroup chats, you can request the bot to acknowledge reading your message with a lightweight 👀 reaction instead of waiting for a full response. Simply mention the bot's username, nickname, or initials followed by an explicit request:
148
-
149
- - `@botname acknowledge`
150
- - `botname mark as read`
151
- - `BN did you read that?`
152
- - `botname confirm you received`
153
-
154
- The bot will respond with a 👀 reaction emoji on your message (if the Telegram client supports message reactions), or with a short "Got it!" reply as fallback. Read receipt requests are processed immediately without touching the conversation context, making them ideal for confirming the bot is online without disrupting group flow.
147
+ In group and supergroup chats, whenever you mention the bot by username (`@botname`), nickname, or initials, the bot immediately responds with a 👀 reaction emoji on your message to confirm it is online, followed by a full LLM response. The reaction uses the Telegram message reaction API when available (👀 emoji), or falls back to a short "Got it!" text reply for older clients that don't support reactions.
155
148
 
156
149
  ## Bot Setup
157
150
  This library includes an example script `test_local.py`, which uses files from the folders `configs` and `prompts` for TeLLMgramBot to process.
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setup(
7
7
  name='TeLLMgramBot',
8
- version='3.4.0',
8
+ version='3.4.2',
9
9
  packages=find_packages(),
10
10
  license='MIT',
11
11
  author='Digital Heresy',
File without changes
File without changes