def __init__(self, channel: 'WeChatChannel'): self.channel: 'WeChatChannel' = channel self.logger: logging.Logger = logging.getLogger(__name__) # noinspection PyProtectedMember self._ = self.channel._ self.MISSING_GROUP: GroupChat = GroupChat( channel=self.channel, uid=ChatID("__error_group__"), name=self._("Group Missing") ) self.MISSING_CHAT: PrivateChat = PrivateChat( channel=self.channel, uid=ChatID("__error_chat__"), name=self._("Chat Missing") ) self.efb_chat_objs: Dict[str, Chat] = {} # Cached Chat objects. Key: tuple(chat PUID, group PUID or None) # Load system chats self.system_chats: List[Chat] = [] for i in channel.flag('system_chats_to_include'): self.system_chats.append( self.wxpy_chat_to_efb_chat( wxpy.Chat( wxpy.utils.wrap_user_name(i), self.bot ) ) )
def chat_migration(self, update: Update, context: CallbackContext): message = update.effective_message from_id = ChatID(message.migrate_from_chat_id) to_id = ChatID(message.migrate_to_chat_id) from_str = utils.chat_id_to_str(self.channel.channel_id, from_id) to_str = utils.chat_id_to_str(self.channel.channel_id, to_id) for i in self.db.get_chat_assoc(master_uid=from_str): self.db.add_chat_assoc(master_uid=from_str, slave_uid=to_str) self.db.remove_chat_assoc(master_uid=from_str)
def __init__(self, channel: 'QQMessengerChannel'): self.channel: 'QQMessengerChannel' = channel self.logger: logging.Logger = logging.getLogger(__name__) self.MISSING_GROUP: GroupChat = GroupChat( channel=self.channel, uid=ChatID("__error_group__"), name="Group Missing") self.MISSING_CHAT: PrivateChat = PrivateChat( channel=self.channel, uid=ChatID("__error_chat__"), name="Chat Missing")
def chat_id_str_to_id(s: EFBChannelChatIDStr) -> Tuple[ModuleID, ChatID, Optional[ChatID]]: """ Reverse of chat_id_to_str. Returns: channel_id, chat_uid, group_id """ ids = s.split(" ", 2) channel_id = ModuleID(ids[0]) chat_uid = ChatID(ids[1]) if len(ids) < 3: group_id = None else: group_id = ChatID(ids[2]) return channel_id, chat_uid, group_id
def chat_migration(self, update: Update, context: CallbackContext): """Triggered by any message update with either ``migrate_from_chat_id`` or ``migrate_to_chat_id`` or both (which shouldn’t happen). """ message = update.effective_message if message.migrate_from_chat_id is not None: from_id = ChatID(message.migrate_from_chat_id) to_id = ChatID(message.chat.id) elif message.migrate_to_chat_id is not None: from_id = ChatID(message.chat.id) to_id = ChatID(message.migrate_to_chat_id) else: # Per ptb filter specs, this part of code should not be reached. return self.chat_migration_by_id(from_id, to_id)
def make_system_member(self, name: str = "", alias: Optional[str] = None, id: ChatID = ChatID(""), uid: ChatID = ChatID(""), vendor_specific: Dict[str, Any] = None, description: str = "", middleware: Optional[Middleware] = None) -> ETMSystemChatMember: # TODO: remove deprecated ID assert not id, f"id is {id!r}" return ETMSystemChatMember(self.db, self, name=name, alias=alias, uid=uid, vendor_specific=vendor_specific, description=description, middleware=middleware)
def __init__( self, db: 'DatabaseManager', *, channel: Optional[SlaveChannel] = None, middleware: Optional[Middleware] = None, module_name: str = "", channel_emoji: str = "", module_id: ModuleID = ModuleID(""), name: str = "", alias: Optional[str] = None, uid: ChatID = ChatID(""), vendor_specific: Dict[str, Any] = None, description: str = "", notification: ChatNotificationState = ChatNotificationState.ALL, with_self: bool = True): super().__init__(db, channel=channel, middleware=middleware, module_name=module_name, channel_emoji=channel_emoji, module_id=module_id, name=name, alias=alias, uid=uid, vendor_specific=vendor_specific, description=description, notification=notification, with_self=with_self)
def chat_migration(self, update: Update, context: CallbackContext): message = update.effective_message if message.migrate_from_chat_id is not None: from_id = ChatID(message.migrate_from_chat_id) to_id = ChatID(message.chat.id) elif message.migrate_to_chat_id is not None: from_id = ChatID(message.chat.id) to_id = ChatID(message.migrate_to_chat_id) else: # Per ptb filter specs, this part of code should not be reached. return from_str = utils.chat_id_to_str(self.channel.channel_id, from_id) to_str = utils.chat_id_to_str(self.channel.channel_id, to_id) for i in self.db.get_chat_assoc(master_uid=from_str): self.db.add_chat_assoc(master_uid=from_str, slave_uid=to_str) self.db.remove_chat_assoc(master_uid=from_str)
def get_chat(self, chat_uid: ChatID) -> 'Chat': chat_info = chat_uid.split('_') chat_type = chat_info[0] chat_attr = chat_info[1] chat = None if chat_type == 'friend': chat_uin = int(chat_attr) remark_name = self.get_friend_remark(chat_uin) chat = ChatMgr.build_efb_chat_as_private( EFBPrivateChat( uid=chat_attr, name=remark_name if remark_name else "", )) elif chat_type == 'group': chat_uin = int(chat_attr) group_info = self.get_group_info(chat_uin, no_cache=False) group_members = self.get_group_member_list(chat_uin, no_cache=False) chat = ChatMgr.build_efb_chat_as_group( EFBGroupChat(uid=f"group_{chat_uin}", name=group_info.get('GroupName', "")), group_members) elif chat_type == 'private': pass # fixme elif chat_type == 'phone': pass # fixme return chat
def __init__(self, instance_id: InstanceID = None): """ Initialize the channel Args: coordinator (:obj:`ehforwarderbot.coordinator.EFBCoordinator`): The EFB framework coordinator """ super().__init__(instance_id) self.load_config() PuidMap.SYSTEM_ACCOUNTS = self.SYSTEM_ACCOUNTS self.flag: ExperimentalFlagsManager = ExperimentalFlagsManager(self) self.qr_uuid: Tuple[str, int] = ('', 0) self.master_qr_picture_id: Optional[str] = None self.authenticate('console_qr_code', first_start=True) # Managers self.slave_message: SlaveMessageManager = SlaveMessageManager(self) self.chats: ChatManager = ChatManager(self) self.user_auth_chat = SystemChat(channel=self, name=self._("EWS User Auth"), uid=ChatID("__ews_user_auth__"))
def get_chat(self, chat_uid: ChatID) -> 'Chat': chat_info = chat_uid.split('_') chat_type = chat_info[0] chat_attr = chat_info[1] chat = None if chat_type == 'friend': chat_uin = int(chat_attr) if not self.info_list.get('friend', None) and chat_uin in self.info_list['friend']: chat = ChatMgr.build_efb_chat_as_private(EFBPrivateChat( uid=f"friend_{chat_attr}", name=self.info_list['friend'][chat_uin].remark, alias=self.info_list['friend'][chat_uin].nickname )) else: remark_name = self.get_friend_remark(chat_uin) chat = ChatMgr.build_efb_chat_as_private(EFBPrivateChat( uid=f"friend_{chat_attr}", name=remark_name if remark_name else "", )) elif chat_type == 'group': chat_uin = int(chat_attr) group_info = self.get_group_info(chat_uin, no_cache=False) group_members = self.get_group_member_list(chat_uin, no_cache=False) chat = ChatMgr.build_efb_chat_as_group(EFBGroupChat( uid=f"group_{chat_uin}", name=group_info.get('name', "") ), group_members) elif chat_type == 'private': pass # fixme elif chat_type == 'phone': pass # fixme return chat
def chat_id_str_to_id(s: EFBChannelChatIDStr) -> Tuple[ModuleID, ChatID]: """ Reverse of chat_id_to_str. Returns: channel_id, chat_uid """ chat_ids = s.split(" ", 1) return ModuleID(chat_ids[0]), ChatID(chat_ids[1])
def make_status_message(self, msg_base: Message = None, mid: MessageID = None) -> Message: if mid is not None: msg = self.message_cache[mid] elif msg_base is not None: msg = msg_base else: raise ValueError reply = Message( type=MsgType.Text, chat=msg.chat, author=msg.chat.make_system_member(uid=ChatID("filter_info"), name="Filter middleware", middleware=self), deliver_to=coordinator.master, ) if mid: reply.uid = mid else: reply.uid = str(uuid.uuid4()) status = self.filter_reason(msg) if not status: # Blue circle emoji status = "\U0001F535 This chat is not filtered." else: # Red circle emoji status = "\U0001F534 " + status reply.text = "Filter status for chat {chat_id} from {module_id}:\n" \ "\n" \ "{status}\n".format( module_id=msg.chat.module_id, chat_id=msg.chat.id, status=status ) command = MessageCommand(name="%COMMAND_NAME%", callable_name="toggle_filter_by_chat_id", kwargs={ "mid": reply.uid, "module_id": msg.chat.module_id, "chat_id": msg.chat.id }) if self.is_chat_filtered_by_id(msg.chat): command.name = "Unfilter by chat ID" command.kwargs['value'] = False else: command.name = "Filter by chat ID" command.kwargs['value'] = True reply.commands = MessageCommands([command]) return reply
def fill_group(self, group: Chat): """Populate members into a group per membership template.""" for name, notification, avatar, alias in self.__group_member_templates: group.add_member( name=name, alias=f"{alias} @ {group.name[::-1]}" if alias is not None else None, uid=ChatID(self.CHAT_ID_FORMAT.format(hash=hash(name))), )
def get_singly_linked_chat_id_str( self, chat: Chat) -> Optional[EFBChannelChatIDStr]: """Return the singly-linked remote chat if available. Otherwise return None. """ master_chat_uid = utils.chat_id_to_str(self.channel_id, ChatID(str(chat.id))) chats = self.db.get_chat_assoc(master_uid=master_chat_uid) if len(chats) == 1: return chats[0] return None
def __init__(self, db: 'DatabaseManager', chat: 'Chat', *, name: str = "", alias: Optional[str] = None, uid: ChatID = ChatID(""), vendor_specific: Dict[str, Any] = None, description: str = "", middleware: Optional[Middleware] = None): super().__init__(db, chat, name=name, alias=alias, uid=uid, vendor_specific=vendor_specific, description=description, middleware=middleware)
def add_system_member( self, name: str = "", alias: Optional[str] = None, uid: ChatID = ChatID(""), # type: ignore vendor_specific: Dict[str, Any] = None, description: str = "", id='', middleware: Optional[Middleware] = None) -> ETMSystemChatMember: # TODO: remove deprecated ID assert not id, f"id is {id!r}" member = self.make_system_member(name=name, alias=alias, uid=uid, vendor_specific=vendor_specific, description=description, middleware=middleware) self.members.append(member) return member
def build_efb_msg(self, mid: str, thread_id: str, author_id: str, message_object: Message, nested: bool = False) -> EFBMessage: efb_msg = EFBMessage( uid=MessageID(mid), text=message_object.text, type=MsgType.Text, deliver_to=coordinator.master, ) # Authors efb_msg.chat = self.chat_manager.get_thread(thread_id) efb_msg.author = efb_msg.chat.get_member(ChatID(author_id)) if not nested and message_object.replied_to: efb_msg.target = self.build_efb_msg(message_object.reply_to_id, thread_id=thread_id, author_id=message_object.author, message_object=message_object.replied_to, nested=True) if message_object.mentions: mentions: Dict[Tuple[int, int], ChatMember] = dict() for i in message_object.mentions: mentions[(i.offset, i.offset + i.length)] = efb_msg.chat.get_member(i.thread_id) efb_msg.substitutions = Substitutions(mentions) if message_object.emoji_size: efb_msg.text += " (%s)" % message_object.emoji_size.name[0] # " (S)", " (M)", " (L)" if message_object.reactions: reactions: DefaultDict[ReactionName, List[ChatMember]] = defaultdict(list) for user_id, reaction in message_object.reactions.items(): reactions[ReactionName(reaction.value)].append( efb_msg.chat.get_member(user_id)) efb_msg.reactions = reactions return efb_msg
def generate_chats(self): """Generate a list of chats per the chat templates, and categorise them accordingly. """ self.chats: List[Chat] = [] self.chats_by_chat_type: Dict[ChatTypeName, List[Chat]] = { 'PrivateChat': [], 'GroupChat': [], 'SystemChat': [], } self.chats_by_notification_state: Dict[ ChatNotificationState, List[Chat]] = { ChatNotificationState.ALL: [], ChatNotificationState.MENTIONS: [], ChatNotificationState.NONE: [], } self.chats_by_profile_picture: Dict[bool, List[Chat]] = { True: [], False: [] } self.chats_by_alias: Dict[bool, List[Chat]] = {True: [], False: []} for name, chat_type, notification, avatar, alias in self.__chat_templates: chat = chat_type(channel=self, name=name, alias=alias, uid=ChatID( self.CHAT_ID_FORMAT.format(hash=hash(name))), notification=notification) self.__picture_dict[chat.uid] = avatar if chat_type == GroupChat: self.fill_group(chat) self.chats_by_chat_type[chat_type.__name__].append(chat) self.chats_by_notification_state[notification].append(chat) self.chats_by_profile_picture[avatar is not None].append(chat) self.chats_by_alias[alias is not None].append(chat) self.chats.append(chat) name = "Unknown Chat" self.unknown_chat: PrivateChat = PrivateChat( channel=self, name=name, alias="不知道", uid=ChatID(self.CHAT_ID_FORMAT.format(hash=hash(name))), notification=ChatNotificationState.ALL) name = "Unknown Chat @ unknown channel" self.unknown_channel: PrivateChat = PrivateChat( module_id="__this_is_not_a_channel__", module_name="Unknown Channel", channel_emoji="‼️", name=name, alias="知らんでぇ", uid=ChatID(self.CHAT_ID_FORMAT.format(hash=hash(name))), notification=ChatNotificationState.ALL) name = "backup_chat" self.backup_chat: PrivateChat = PrivateChat( channel=self, name=name, uid=ChatID(self.CHAT_ID_FORMAT.format(hash=hash(name))), notification=ChatNotificationState.ALL) name = "backup_member" self.backup_member: ChatMember = ChatMember( self.chats_by_chat_type['GroupChat'][0], name=name, uid=ChatID(self.CHAT_ID_FORMAT.format(hash=hash(name))))
def build_efb_chat_as_system_user(self, context): # System user only! return SystemChat( channel=self.channel, name=str(context['event_description']), uid=ChatID("__{context[uid_prefix]}__".format(context=context)))
def build_chat_by_thread_obj(self, thread: Thread) -> Chat: vendor_specific = { "chat_type": thread.type.name.capitalize(), "profile_picture_url": thread.photo, } chat: Chat if thread.uid == self.client.uid: chat = PrivateChat(channel=self.channel, uid=thread.uid, other_is_self=True) chat.name = chat.self.name # type: ignore elif isinstance(thread, User): chat = PrivateChat(channel=self.channel, name=thread.name, uid=ChatID(thread.uid), alias=thread.own_nickname or thread.nickname, vendor_specific=vendor_specific) elif isinstance(thread, Page): desc = self._("{subtitle}\n{category} in {city}\n" "Likes: {likes}\n{url}").format( subtitle=thread.sub_title, category=thread.category, city=thread.city, likes=thread.likes, url=thread.url) chat = PrivateChat(channel=self.channel, name=thread.name, uid=ChatID(thread.uid), description=desc, vendor_specific=vendor_specific) elif isinstance(thread, Group): name = thread.name or "" group = GroupChat(channel=self.channel, name=name, uid=ChatID(thread.uid), vendor_specific=vendor_specific) participant_ids = thread.participants - {self.client.uid} try: participants: Dict[ThreadID, User] = self.client.fetchThreadInfo( *participant_ids) for i in participant_ids: member = participants[i] alias = member.own_nickname or member.nickname or None if thread.nicknames and i in thread.nicknames: alias = thread.nicknames[str(i)] or None group.add_member(name=member.name, alias=alias, uid=ChatID(i)) except FBchatException: self.logger.exception( "Error occurred while building chat members.") for i in participant_ids: group.add_member(name=str(i), uid=ChatID(i)) if thread.name is None: names = sorted(i.name for i in group.members) # TRANSLATORS: separation symbol between member names when group name is not provided. name = self._(", ").join(names[:3]) if len(names) > 3: extras = len(names) - 3 name += self.ngettext(", and {number} more", ", and {number} more", extras).format(number=extras) group.name = name chat = group else: chat = SystemChat(channel=self.channel, name=thread.name, uid=ChatID(thread.uid), vendor_specific=vendor_specific) if chat.self: chat.self.uid = self.client.uid return chat
def error(self, update: object, context: CallbackContext): """ Print error to console, and send error message to first admin. Triggered by python-telegram-bot error callback. """ assert context.error error: Exception = context.error if "make sure that only one bot instance is running" in str(error): now = time.time() # Warn the user only from the second time within ``CONFLICTION_TIMEOUT`` # seconds to suppress isolated warnings. # https://github.com/ehForwarderBot/efb-telegram-master/issues/103 if now - self.last_poll_confliction_time < self.CONFLICTION_TIMEOUT: msg = self._( 'Conflicted polling detected. If this error persists, ' 'please ensure you are running only one instance of this Telegram bot.' ) self.logger.critical(msg) self.bot_manager.send_message(self.config['admins'][0], msg) self.last_poll_confliction_time = now return if "Invalid server response" in str(error) and not update: self.logger.error( "Boom! Telegram API is no good. (Invalid server response.)") return # noinspection PyBroadException try: raise error except telegram.error.Unauthorized: self.logger.error( "The bot is not authorised to send update:\n%s\n%s", str(update), str(error)) except telegram.error.BadRequest as e: assert isinstance(update, Update) if e.message == "Message is not modified" and update.callback_query: self.logger.error("Chill bro, don't click that fast.") else: self.logger.exception("Message request is invalid.\n%s\n%s", str(update), str(error)) self.bot_manager.send_message( self.config['admins'][0], self._("Message request is invalid.\n{error}\n" "<code>{update}</code>").format( error=html.escape(str(error)), update=html.escape(str(update))), parse_mode="HTML") except (telegram.error.TimedOut, telegram.error.NetworkError): self.timeout_count += 1 self.logger.error( "Poor internet connection detected.\n" "Number of network error occurred since last startup: %s\n%s\nUpdate: %s", self.timeout_count, str(error), str(update)) if isinstance(update, Update) and isinstance( update.message, Message): update.message.reply_text(self._( "This message is not processed due to poor internet environment " "of the server.\n" "<code>{code}</code>").format( code=html.escape(str(error))), quote=True, parse_mode="HTML") timeout_interval = self.flag('network_error_prompt_interval') if timeout_interval > 0 and self.timeout_count % timeout_interval == 0: self.bot_manager.send_message( self.config['admins'][0], self.ngettext( "<b>EFB Telegram Master channel</b>\n" "You may have a poor internet connection on your server. " "Currently {count} network error is detected.\n" "For more details, please refer to the log.", "<b>EFB Telegram Master channel</b>\n" "You may have a poor internet connection on your server. " "Currently {count} network errors are detected.\n" "For more details, please refer to the log.", self.timeout_count).format(count=self.timeout_count), parse_mode="HTML") except telegram.error.ChatMigrated as e: assert isinstance(update, Update) new_id = e.new_chat_id assert isinstance(update.message, Message) old_id = ChatID(str(update.message.chat_id)) count = 0 for i in self.db.get_chat_assoc( master_uid=etm_utils.chat_id_to_str( self.channel_id, old_id)): self.logger.debug( 'Migrating slave chat %s from Telegram chat %s to %s.', i, old_id, new_id) self.db.remove_chat_assoc(slave_uid=i) self.db.add_chat_assoc(master_uid=etm_utils.chat_id_to_str( self.channel_id, ChatID(str(new_id))), slave_uid=i) count += 1 self.bot_manager.send_message( new_id, self.ngettext( "Chat migration detected.\n" "All {count} remote chat are now linked to this new group.", "Chat migration detected.\n" "All {count} remote chats are now linked to this new group.", count).format(count=count)) except Exception: try: self.bot_manager.send_message( self.config['admins'][0], self. _("EFB Telegram Master channel encountered error <code>{error}</code> " "caused by update <code>{update}</code>. See log for details." ).format(error=html.escape(str(error)), update=html.escape(str(update))), parse_mode="HTML") except Exception as ex: self.logger.exception( "Failed to send error message through Telegram: %s", ex) finally: self.logger.exception( 'Unhandled telegram bot error!\n' 'Update %s caused error %s. Exception', update, error)
def wxpy_chat_to_efb_chat(self, chat: wxpy.Chat) -> Chat: # self.logger.debug("Converting WXPY chat %r, %sin recursive mode", chat, '' if recursive else 'not ') # self.logger.debug("WXPY chat with ID: %s, name: %s, alias: %s;", chat.puid, chat.nick_name, chat.alias) if chat is None: return self.MISSING_USER cache_key = chat.puid chat_name, chat_alias = self.get_name_alias(chat) cached_obj: Optional[Chat] = None if cache_key in self.efb_chat_objs: cached_obj = self.efb_chat_objs[cache_key] if chat_name == cached_obj.name and chat_alias == cached_obj.alias: return cached_obj # if chat name or alias changes, update cache efb_chat: Chat chat_id = ChatID(chat.puid or f"__invalid_{uuid4()}__") if cached_obj: efb_chat = cached_obj efb_chat.uid = chat_id efb_chat.name = chat_name efb_chat.alias = chat_alias efb_chat.vendor_specific = {'is_mp': isinstance(chat, wxpy.MP)} if isinstance(chat, wxpy.Group): # Update members if necessary remote_puids = {i.puid for i in chat.members} local_ids = {i.uid for i in efb_chat.members if not isinstance(i, SelfChatMember)} # Add missing members missing_puids = remote_puids - local_ids for member in chat.members: if member.puid in missing_puids: member_name, member_alias = self.get_name_alias(member) efb_chat.add_member(name=member_name, alias=member_alias, uid=member.puid, vendor_specific={'is_mp': False}) elif chat == chat.bot.self: efb_chat = PrivateChat(channel=self.channel, uid=chat_id, name=chat_name, alias=chat_alias, vendor_specific={'is_mp': True}, other_is_self=True) elif isinstance(chat, wxpy.Group): efb_chat = GroupChat(channel=self.channel, uid=chat_id, name=chat_name, alias=chat_alias, vendor_specific={'is_mp': False}) for i in chat.members: if i.user_name == self.bot.self.user_name: continue member_name, member_alias = self.get_name_alias(i) efb_chat.add_member(name=member_name, alias=member_alias, uid=i.puid, vendor_specific={'is_mp': False}) elif isinstance(chat, wxpy.MP): efb_chat = PrivateChat(channel=self.channel, uid=chat_id, name=chat_name, alias=chat_alias, vendor_specific={'is_mp': True}) elif isinstance(chat, wxpy.User): efb_chat = PrivateChat(channel=self.channel, uid=chat_id, name=chat_name, alias=chat_alias, vendor_specific={'is_mp': False}) else: efb_chat = SystemChat(channel=self.channel, uid=chat_id, name=chat_name, alias=chat_alias, vendor_specific={'is_mp': False}) efb_chat.vendor_specific.update(self.generate_vendor_specific(chat)) if efb_chat.vendor_specific.get('is_muted', False): efb_chat.notification = ChatNotificationState.MENTIONS self.efb_chat_objs[cache_key] = efb_chat return efb_chat
def __init__(self, instance_id: InstanceID = None): super().__init__(instance_id) # For test self.chat: PrivateChat = PrivateChat( channel=self, name='Echo Message', uid=ChatID('echo_chat')) self.condition: Optional[Condition] = None
def msg(self, update: Update, context: CallbackContext): """ Process, wrap and dispatch messages from user. """ assert isinstance(update, Update) assert update.effective_message assert update.effective_chat message: Message = update.effective_message mid = utils.message_id_to_str(update=update) self.logger.debug("[%s] Received message from Telegram: %s", mid, message.to_dict()) destination = None edited = None quote = False if update.edited_message or update.edited_channel_post: self.logger.debug('[%s] Message is edited: %s', mid, message.edit_date) msg_log = self.db.get_msg_log( master_msg_id=utils.message_id_to_str(update=update)) if not msg_log or msg_log.slave_message_id == self.db.FAIL_FLAG: message.reply_text(self._( "Error: This message cannot be edited, and thus is not sent. (ME01)" ), quote=True) return destination = msg_log.slave_origin_uid edited = msg_log quote = msg_log.build_etm_msg(self.chat_manager).target is not None if destination is None: destination = self.get_singly_linked_chat_id_str( update.effective_chat) if destination: # if the chat is singly-linked quote = message.reply_to_message is not None self.logger.debug("[%s] Chat %s is singly-linked to %s", mid, message.chat, destination) if destination is None: # not singly linked quote = False self.logger.debug("[%s] Chat %s is not singly-linked", mid, update.effective_chat) reply_to = message.reply_to_message cached_dest = self.chat_dest_cache.get(str(message.chat.id)) if reply_to: self.logger.debug("[%s] Message is quote-replying to %s", mid, reply_to) dest_msg = self.db.get_msg_log( master_msg_id=utils.message_id_to_str( TelegramChatID(reply_to.chat.id), TelegramMessageID(reply_to.message_id))) if dest_msg: destination = dest_msg.slave_origin_uid self.chat_dest_cache.set(str(message.chat.id), destination) self.logger.debug( "[%s] Quoted message is found in database with destination: %s", mid, destination) elif cached_dest: self.logger.debug("[%s] Cached destination found: %s", mid, cached_dest) destination = cached_dest self._send_cached_chat_warning(update, TelegramChatID(message.chat.id), cached_dest) self.logger.debug("[%s] Destination chat = %s", mid, destination) if destination is None: self.logger.debug("[%s] Destination is not found for this message", mid) candidates = ( self.db.get_recent_slave_chats(TelegramChatID(message.chat.id), limit=5) or self.db.get_chat_assoc(master_uid=utils.chat_id_to_str( self.channel_id, ChatID(str(message.chat.id))))[:5]) if candidates: self.logger.debug( "[%s] Candidate suggestions are found for this message: %s", mid, candidates) tg_err_msg = message.reply_text(self._( "Error: No recipient specified.\n" "Please reply to a previous message. (MS01)"), quote=True) self.channel.chat_binding.register_suggestions( update, candidates, TelegramChatID(update.effective_chat.id), TelegramMessageID(tg_err_msg.message_id)) else: self.logger.debug( "[%s] Candidate suggestions not found, give up.", mid) message.reply_text(self._( "Error: No recipient specified.\n" "Please reply to a previous message. (MS02)"), quote=True) else: return self.process_telegram_message(update, context, destination, quote=quote, edited=edited)