Skip to content

Commit 9de5140

Browse files
authored
Merge pull request #2 from Ovler-Young/More-Topics
More topics
2 parents a7e295b + f83d491 commit 9de5140

File tree

6 files changed

+187
-125
lines changed

6 files changed

+187
-125
lines changed

efb_telegram_master/bot_manager.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,11 @@ def answer_callback_query(self, *args, prefix="", suffix="", text=None,
546546
*args, text=prefix + text + suffix, **kwargs
547547
)
548548

549+
@Decorators.retry_on_timeout
550+
@Decorators.retry_on_chat_migration
551+
def get_chat_info(self, *args, **kwargs):
552+
return self.updater.bot.get_chat(*args, **kwargs)
553+
549554
@Decorators.retry_on_timeout
550555
@Decorators.retry_on_chat_migration
551556
def create_forum_topic(self, *args, **kwargs):

efb_telegram_master/chat_binding.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,22 @@ def link_chat_show_list(self, update: Update, context: CallbackContext):
223223
self.link_handler.conversations[storage_id] = Flags.LINK_EXEC
224224
self.msg_storage[storage_id] = ChatListStorage([chat])
225225
return self.build_link_action_message(chat, tg_chat_id, tg_msg_id)
226+
if message.message_thread_id:
227+
topic = message.message_thread_id
228+
if topic:
229+
slave_origin_uid = self.db.get_topic_slave(
230+
topic_chat_id=TelegramChatID(message.chat_id),
231+
message_thread_id=topic
232+
)
233+
if slave_origin_uid:
234+
channel_id, chat_id, _ = utils.chat_id_str_to_id(slave_origin_uid)
235+
chat: ETMChatType = self.chat_manager.get_chat(channel_id, chat_id, build_dummy=True)
236+
tg_chat_id = TelegramChatID(message.chat_id)
237+
tg_msg_id = TelegramMessageID(message.reply_text(self._("Processing...")).message_id)
238+
storage_id: Tuple[TelegramChatID, TelegramMessageID] = (tg_chat_id, tg_msg_id)
239+
self.link_handler.conversations[storage_id] = Flags.LINK_EXEC
240+
self.msg_storage[storage_id] = ChatListStorage([chat])
241+
return self.build_link_action_message(chat, tg_chat_id, tg_msg_id)
226242

227243
if message.chat.type != telegram.Chat.PRIVATE:
228244
links = self.db.get_chat_assoc(
@@ -567,15 +583,28 @@ def link_chat(self, update: Update, args: Optional[List[str]]):
567583
msg = self.bot.send_message(tg_chat_to_link, text=txt)
568584

569585
chat.link(self.channel.channel_id, ChatID(str(tg_chat_to_link)), self.channel.flag("multiple_slave_chats"))
570-
try:
571-
self.db.remove_topic_assoc(
572-
topic_chat_id=self.channel.topic_group,
573-
slave_uid=chat_uid,
574-
)
575-
#TODO delete or close the topic after link?
576-
except Exception as e:
577-
self.logger.warn("Error occurred while remove topic assoc.\nError: %s\n%s",
578-
repr(e), traceback.format_exc())
586+
self.db.remove_topic_assoc(
587+
slave_uid=chat_uid,
588+
)
589+
590+
# chat_id = utils.chat_id_to_str(self.channel.channel_id, ChatID(str(tg_chat_to_link)))
591+
# links = self.db.get_chat_assoc(master_uid=chat_id)
592+
# if len(links) > 1 and self.channel.topic_group:
593+
# try:
594+
# topic: ForumTopic = self.bot.create_forum_topic(
595+
# chat_id=chat_id,
596+
# name=chat.chat_title
597+
# )
598+
# thread_id = topic.message_thread_id
599+
# self.db.add_topic_assoc(
600+
# topic_chat_id=chat_id,
601+
# message_thread_id=thread_id,
602+
# slave_uid=chat_uid,
603+
# )
604+
# return thread_id
605+
# except telegram.error.BadRequest as e:
606+
# self.logger.error('Failed to create topic, Reason: %s', e)
607+
# return None
579608

580609
txt = self._("Chat {0} is now linked.").format(chat_display_name)
581610
self.bot.edit_message_text(text=txt, chat_id=msg.chat.id, message_id=msg.message_id)

efb_telegram_master/db.py

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .message import ETMMsg
2323
from .msg_type import TGMsgType
2424
from .utils import TelegramChatID, EFBChannelChatIDStr, TgChatMsgIDStr, message_id_to_str, \
25-
chat_id_to_str, OldMsgID, chat_id_str_to_id, TelegramMessageID
25+
chat_id_to_str, OldMsgID, chat_id_str_to_id, TelegramMessageID, TelegramTopicID
2626

2727
if TYPE_CHECKING:
2828
from . import TelegramChannel
@@ -72,8 +72,6 @@ class MsgLog(BaseModel):
7272
"""Editable message ID from Telegram if ``master_msg_id`` is not editable
7373
and a separate one is sent.
7474
"""
75-
master_message_thread_id = TextField(null=True)
76-
"""Message thread ID from Telegram"""
7775
slave_message_id = TextField()
7876
"""Message from slave channel."""
7977
text = TextField()
@@ -204,8 +202,6 @@ def __init__(self, channel: 'TelegramChannel'):
204202
self._migrate(2)
205203
elif "file_unique_id" not in msg_log_columns:
206204
self._migrate(3)
207-
elif "master_message_thread_id" not in msg_log_columns:
208-
self._migrate(4)
209205
self.logger.debug("Database migration finished...")
210206

211207
def stop_worker(self):
@@ -256,12 +252,6 @@ def _migrate(i: int):
256252
migrate(
257253
migrator.add_column("msglog", "file_unique_id", MsgLog.file_unique_id)
258254
)
259-
if i <= 4:
260-
# Migration 4: Add column for message thread ID to message log table
261-
# 2025APR12
262-
migrate(
263-
migrator.add_column("msglog", "master_message_thread_id", MsgLog.master_message_thread_id)
264-
)
265255

266256
def add_chat_assoc(self, master_uid: EFBChannelChatIDStr,
267257
slave_uid: EFBChannelChatIDStr,
@@ -389,80 +379,111 @@ def get_chat_assoc(master_uid: Optional[EFBChannelChatIDStr] = None,
389379
except DoesNotExist:
390380
return []
391381

392-
def add_topic_assoc(self, message_thread_id: EFBChannelChatIDStr,
393-
slave_uid: EFBChannelChatIDStr,
394-
topic_chat_id: int):
382+
def add_topic_assoc(self, topic_chat_id: TelegramChatID,
383+
message_thread_id: EFBChannelChatIDStr,
384+
slave_uid: EFBChannelChatIDStr, ):
395385
"""
396386
Add topic associations (topic links).
397387
One Master channel with many Slave channel.
398388
399389
Args:
400-
message_thread_id (str): thread UID in topic
401-
slave_uid (str): Slave channel UID ("%(channel_id)s.%(chat_id)s")
390+
topic_chat_id (TelegramChatID): The topic group chat ID
391+
message_thread_id (EFBChannelChatIDStr): The topic thread ID
392+
slave_uid (EFBChannelChatIDStr): Slave channel UID ("%(channel_id)s.%(chat_id)s")
402393
"""
403394
return TopicAssoc.create(topic_chat_id=topic_chat_id, message_thread_id=message_thread_id, slave_uid=slave_uid)
404395

405396
@staticmethod
406-
def get_topic_thread_id(topic_chat_id: int,
407-
slave_uid: Optional[EFBChannelChatIDStr]
408-
) -> int:
397+
def get_topic_thread_id(slave_uid: EFBChannelChatIDStr) -> TelegramTopicID:
409398
"""
410399
Get topic association (topic link) information.
411400
Only one parameter is to be provided.
412401
413402
Args:
414-
topic_chat_id (int): The topic UID
415-
slave_uid (str): Slave channel UID ("%(channel_id)s.%(chat_id)s")
403+
slave_uid (EFBChannelChatIDStr): Slave channel UID ("%(channel_id)s.%(chat_id)s")
416404
417405
Returns:
418406
The message thread_id
419407
"""
420408
try:
421409
assoc = TopicAssoc.select(TopicAssoc.message_thread_id)\
422-
.where(TopicAssoc.slave_uid == slave_uid, TopicAssoc.topic_chat_id == topic_chat_id)\
410+
.where(TopicAssoc.slave_uid == slave_uid)\
423411
.order_by(TopicAssoc.id.desc()).first()
424412
if assoc:
425413
return int(assoc.message_thread_id)
426414
except DoesNotExist:
427415
return None
428416

429417
@staticmethod
430-
def get_topic_slave(topic_chat_id: int,
431-
message_thread_id: int
418+
def get_topic_slave(topic_chat_id: TelegramTopicID,
419+
message_thread_id: Optional[EFBChannelChatIDStr] = None,
432420
) -> Optional[EFBChannelChatIDStr]:
433421
"""
434422
Get topic association (topic link) information.
435423
Only one parameter is to be provided.
436424
437425
Args:
438-
topic_chat_id (int): The topic UID
439-
message_thread_id (int): The message thread ID
426+
topic_chat_id (TelegramTopicID): The topic UID
427+
message_thread_id (TelegramTopicID): The message thread ID
440428
441429
Returns:
442430
Slave channel UID ("%(channel_id)s.%(chat_id)s")
443431
"""
444432
try:
445-
return TopicAssoc.select(TopicAssoc.slave_uid)\
446-
.where(TopicAssoc.message_thread_id == message_thread_id, TopicAssoc.topic_chat_id == topic_chat_id).first().slave_uid
433+
if message_thread_id:
434+
return TopicAssoc.select(TopicAssoc.slave_uid)\
435+
.where(TopicAssoc.message_thread_id == message_thread_id, TopicAssoc.topic_chat_id == topic_chat_id).first().slave_uid
436+
else:
437+
return TopicAssoc.select(TopicAssoc.slave_uid)\
438+
.where(TopicAssoc.topic_chat_id == topic_chat_id).first().slave_uid
447439
except DoesNotExist:
448440
return None
449-
except AttributeError: # Handle case where .slave_uid doesn't exist on the result
441+
except AttributeError:
450442
return None
451443

452444
@staticmethod
453-
def remove_topic_assoc(topic_chat_id: int, slave_uid: Optional[EFBChannelChatIDStr] = None):
445+
def get_topic_slaves(topic_chat_id: TelegramChatID) -> Optional[List[Tuple[EFBChannelChatIDStr, TelegramTopicID]]]:
446+
"""
447+
Get topic association (topic link) information.
448+
Only one parameter is to be provided.
449+
450+
Args:
451+
topic_chat_id (TelegramChatID): The topic UID
452+
453+
Returns:
454+
List[Tuple[EFBChannelChatIDStr, TelegramTopicID]]: A list of tuples containing slave channel UID and message thread ID
455+
"""
456+
try:
457+
query = TopicAssoc.select(TopicAssoc.slave_uid, TopicAssoc.message_thread_id)\
458+
.where(TopicAssoc.topic_chat_id == topic_chat_id).order_by(TopicAssoc.id.desc())
459+
return [(row.slave_uid, int(row.message_thread_id)) for row in query]
460+
except DoesNotExist:
461+
return None
462+
except AttributeError:
463+
return None
464+
465+
@staticmethod
466+
def remove_topic_assoc(topic_chat_id: Optional[TelegramChatID] = None,
467+
message_thread_id: Optional[EFBChannelChatIDStr] = None,
468+
slave_uid: Optional[EFBChannelChatIDStr] = None):
454469
"""
455470
Remove topic association (topic link).
456471
457472
Args:
458-
topic_chat_id (int): The topic group chat ID
459-
slave_uid (str): Slave channel UID ("%(channel_id)s.%(chat_id)s")
473+
topic_chat_id (TelegramChatID): The topic group chat ID
474+
message_thread_id (EFBChannelChatIDStr): The topic thread ID
475+
slave_uid (EFBChannelChatIDStr): Slave channel UID ("%(channel_id)s.%(chat_id)s")
460476
"""
461477
try:
462-
return TopicAssoc.delete().where(
463-
(TopicAssoc.topic_chat_id == str(topic_chat_id)) &
464-
(TopicAssoc.slave_uid == str(slave_uid))
465-
).execute()
478+
if bool(topic_chat_id and message_thread_id) == bool(slave_uid):
479+
raise ValueError("Please provide either topic_chat_id and message_thread_id or slave_uid.")
480+
elif topic_chat_id and message_thread_id:
481+
return TopicAssoc.delete().where(
482+
(TopicAssoc.topic_chat_id == str(topic_chat_id)) &
483+
(TopicAssoc.message_thread_id == str(message_thread_id))
484+
).execute()
485+
elif slave_uid:
486+
return TopicAssoc.delete().where(TopicAssoc.slave_uid == slave_uid).execute()
466487
except DoesNotExist:
467488
return 0
468489

@@ -494,7 +515,6 @@ def add_or_update_message_log(self,
494515

495516
row.master_msg_id = master_msg_id
496517
row.master_msg_id_alt = master_msg_id_alt
497-
row.master_message_thread_id = str(master_message.message_thread_id) if master_message.message_thread_id else None
498518
row.text = msg.text
499519
row.slave_origin_uid = chat_id_to_str(chat=msg.chat)
500520
row.slave_member_uid = chat_id_to_str(chat=msg.author)

efb_telegram_master/master_message.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,22 +160,36 @@ def msg(self, update: Update, context: CallbackContext):
160160
if destination:
161161
quote = message.reply_to_message is not None
162162
self.logger.debug("[%s] Chat %s is singly-linked to %s", mid, message.chat, destination)
163+
if message.chat.is_forum:
164+
ideal_thread_id = self.db.get_topic_thread_id(slave_uid=destination)
165+
if ideal_thread_id and ideal_thread_id != message.message_thread_id:
166+
self.logger.debug("[%s] Chat %s is singly-linked to %s, but the thread ID is not matching.", mid, message.chat, destination)
167+
destination = None
168+
quote = False
169+
return
163170

164171
if destination is None:
165-
thread_id = message.message_thread_id
166-
if thread_id:
167-
if TelegramChatID(update.effective_chat.id) == self.channel.topic_group:
168-
destination = self.db.get_topic_slave(message_thread_id=thread_id, topic_chat_id=self.channel.topic_group)
169-
if destination:
170-
quote = message.reply_to_message.message_id != message.reply_to_message.message_thread_id
171-
if not quote:
172-
message.reply_to_message = None
173-
else:
172+
if message.chat.is_forum:
173+
topic_destinations = self.db.get_topic_slaves(topic_chat_id=message.chat.id)
174+
thread_id = message.message_thread_id
175+
if thread_id:
176+
for (destination, topic_id) in topic_destinations:
177+
if topic_id == thread_id:
178+
self.logger.debug("[%s] Chat %s is singly-linked to %s in topic %s", mid, message.chat, destination, topic_id)
179+
destination = destination
180+
quote = message.reply_to_message.message_id != message.reply_to_message.message_thread_id
181+
if quote:
182+
message.reply_to_message = None
183+
break
184+
if destination is None:
174185
self.logger.debug("[%s] Ignored message as it's a topic which wasn't created by this bot", mid)
175186
return
176187
else:
177-
self.logger.debug("[%s] Ignored message as it's a invalid topic group.", mid)
178-
return
188+
self.logger.debug("[%s] Chat %s is a forum, but no thread ID is found.", mid, message.chat)
189+
destinations = self.db.get_chat_assoc(master_uid=utils.chat_id_to_str(self.channel_id, ChatID(str(message.chat.id))))
190+
if len(destinations) == len(topic_destinations):
191+
self.logger.debug("[%s] Chat %s is a forum, and all destinations are in topics. The new message is not in any topic, so ignore it.", mid, message.chat)
192+
return
179193

180194
if destination is None: # not singly linked
181195
quote = False

0 commit comments

Comments
 (0)