diff --git a/start.py b/start.py index 969b561..5986875 100644 --- a/start.py +++ b/start.py @@ -13,7 +13,7 @@ xmpp_logger = logging.getLogger('sleekxmpp') class StreamToLogger: """ - Прикидывается файловым объектом. Нужен для перехвата стандартных потоков ввода-вывода. + Stream logger. """ def __init__(self, logger, level=logging.INFO, old_out=None): self.logger = logger @@ -24,7 +24,7 @@ class StreamToLogger: self._prev = None def write(self, buf): - if self._prev == buf == '\n': # Надо на буфер переделывать + if self._prev == buf == '\n': self._prev = buf buf = '' else: @@ -39,7 +39,7 @@ class StreamToLogger: pass -# Настраиваем логгирование +# Logger config logging.basicConfig( level=logging.DEBUG if CONFIG['debug'] else logging.INFO, format='%(asctime)s :: %(levelname)s:%(name)s :: %(message)s', @@ -47,7 +47,7 @@ logging.basicConfig( handlers=[logging.handlers.RotatingFileHandler(filename=CONFIG['logfile']), logging.StreamHandler(sys.stdout)] ) -# Создаем логгеры и перехватчики для STDOUT/STDERR +# Stdout/stderr logger_stdout = logging.getLogger('__stdout') sys.stdout = StreamToLogger(logger_stdout, logging.INFO) @@ -58,7 +58,7 @@ logging.getLogger().log(logging.INFO, '~'*81) logging.getLogger().log(logging.INFO, ' RESTART '*9) logging.getLogger().log(logging.INFO, '~'*81) print('----------------------------------------------------------------------') -print('--- Telegram (MTProto) <---> XMPP Gateway ---') +print('--- Telegram (MTProto) <-> XMPP Gateway ---') print('----------------------------------------------------------------------') print() print('Starting...') diff --git a/xmpp_tg/__init__.py b/xmpp_tg/__init__.py index fbcf852..b77c09c 100644 --- a/xmpp_tg/__init__.py +++ b/xmpp_tg/__init__.py @@ -1,3 +1,3 @@ from xmpp_tg.xmpp import XMPPTelegram -__version__ = '0.3.5' +__version__ = '0.4.0' diff --git a/xmpp_tg/monkey.py b/xmpp_tg/monkey.py index 4e053bd..b611971 100644 --- a/xmpp_tg/monkey.py +++ b/xmpp_tg/monkey.py @@ -2,6 +2,7 @@ from sleekxmpp.plugins.xep_0054 import XEP_0054 from sleekxmpp import Iq from sleekxmpp.exceptions import XMPPError +from telethon.update_state import UpdateState def patched_handle_get_vcard(self, iq): if iq['type'] == 'result': @@ -18,5 +19,25 @@ def patched_handle_get_vcard(self, iq): elif iq['type'] == 'set': raise XMPPError('service-unavailable') -# Грязно патчим баг в библиотеке +def patched_stop_workers(self): + """ + Waits for all the worker threads to stop. + """ + # Put dummy ``None`` objects so that they don't need to timeout. + n = self._workers + self._workers = None + if n: + with self._updates_lock: + for _ in range(n): + self._updates.put(None) + + for t in self._worker_threads: + t.join() + + self._worker_threads.clear() + self._workers = n + + +# hey i'm baboon XEP_0054._handle_get_vcard = patched_handle_get_vcard +UpdateState.stop_workers = patched_stop_workers diff --git a/xmpp_tg/mtproto.py b/xmpp_tg/mtproto.py index a692289..ed4247f 100644 --- a/xmpp_tg/mtproto.py +++ b/xmpp_tg/mtproto.py @@ -30,6 +30,7 @@ import time from xmpp_tg.utils import localtime, display_tg_name from .utils import var_dump +import xmpp_tg.monkey import traceback @@ -59,6 +60,11 @@ class TelegramGateClient(TelegramClient): def xmpp_update_handler(self, obj): + """ + Main function: Telegram update handler. + :param media: + :return: + """ # print("We have received update for <%s>" % self.jid) # print(obj) @@ -69,19 +75,6 @@ class TelegramGateClient(TelegramClient): self._media_thread.start() self._status_update_thread.start() - ''' - Боты - Сделать запоминание ростера в бд - Сделать лучше хендлинг ошибок - Доделать все типы информационных сообщений - Сделать джойны по линкам в чаты/каналы - Сделать поиск и добавление пользователей - Сделать листание истории - Сделать отправку всех непрочтенных сообщений при входе - ''' - - # Здесь будет очень длинный пиздец ^__^ - nl = '\n' try: @@ -123,8 +116,10 @@ class TelegramGateClient(TelegramClient): # detect from id if is_user: cid = obj.message.from_id - peer = InputPeerUser(cid, self.xmpp_gate.tg_dialogs[self.jid]['users'][cid].access_hash) if cid in self.xmpp_gate.tg_dialogs[self.jid]['users'] else None - prefix = 'u' + user = self._get_user_information(cid) + peer = InputPeerUser(user.id, user.access_hash) + prefix = 'u' + prefix = 'b' if user.bot else prefix elif is_group: cid = obj.message.to_id.chat_id peer = InputPeerChat(cid) @@ -181,9 +176,9 @@ class TelegramGateClient(TelegramClient): if type(obj.status) is UserStatusOnline: self._status_updates[str(obj.user_id)] = { 'status': None, 'message': 'Online' } elif type(obj.status) is UserStatusOffline: - self._status_updates[str(obj.user_id)] = { 'status': 'xa', 'message': localtime(obj.status.was_online).strftime('Last seen at %H:%M %d/%m/%Y') } + self._status_updates[str(obj.user_id)] = { 'status': 'away', 'message': localtime(obj.status.was_online).strftime('Last seen at %H:%M %d/%m/%Y') } elif type(obj.status) is UserStatusRecently: - self._status_updates[str(obj.user_id)] = { 'status': 'away', 'message': 'Last seen recently' } + self._status_updates[str(obj.user_id)] = { 'status': 'dnd', 'message': 'Last seen recently' } else: pass @@ -201,7 +196,7 @@ class TelegramGateClient(TelegramClient): def generate_media_link(self, media): """ - Генерирует будующее имя и ссылку на скачиваемое медиа-вложения из сообщения + Generates download link from media object :param media: :return: """ @@ -228,7 +223,7 @@ class TelegramGateClient(TelegramClient): @staticmethod def get_document_attribute(attributes, match): """ - Находит заданных аттрибут в списке. Используется при разборе медиа-вложений типа Документ. + Get document attribute. :param attributes: :param match: :return: @@ -248,24 +243,23 @@ class TelegramGateClient(TelegramClient): self.xmpp_gate.tg_dialogs[self.jid]['users'][uid] = entity return entity else: - return {'first_name': 'Unknown', 'last_name': 'user', 'access_hash': -1, 'id': 0} + return {'first_name': 'Unknown', 'last_name': 'user', 'access_hash': -1, 'id': 0, 'bot': False} def _process_forward_msg(self, message): """ - Обрабатывает информацию в пересланном сообщении (от кого оно и/или из какого канала). Требует дополнительно - предоставление информации об пользователях/каналах. + Process forward message to find out from what user message is forwarded. :param message: :param users: :param channels: :return: """ - if message.fwd_from.from_id: # От пользователя + if message.fwd_from.from_id: # from user usr = self._get_user_information(message.fwd_from.from_id) fwd_from = display_tg_name(usr) - if message.fwd_from.channel_id: # От канала + if message.fwd_from.channel_id: # from channel fwd_from = 'Channel {}'.format(message.fwd_from.channel_id) # let's construct @@ -274,74 +268,72 @@ class TelegramGateClient(TelegramClient): def _process_media_msg(self, media): """ - Обрабатывает медиа-вложения в сообщениях. Добавляет их в очередь на загрузку. Производит разбор с генерацию - готового для вывода сообщения с информацией о медиа и сгенерированной ссылкой на него. + Process message with media. :param media: :return: """ msg = '' - if type(media) is MessageMediaDocument: # Документ или замаскированная сущность + if type(media) is MessageMediaDocument: # document attributes = media.document.attributes - attributes_types = [type(a) for a in attributes] # Документами могут быть разные вещи и иметь аттрибуты + attributes_types = [type(a) for a in attributes] size_text = '|Size: {:.2f} Mb'.format(media.document.size / 1024 / 1024) - if media.document.size > self.xmpp_gate.config['media_max_download_size']: # Не загружаем большие файлы - g_link = {'link': 'File is too big to be downloaded via Telegram <---> XMPP Gateway. Sorry.'} - else: - g_link = self.generate_media_link(media) # Добавляем файл в очередь на загрузку в отдельном потоке + if media.document.size > self.xmpp_gate.config['media_max_download_size']: # too big file + g_link = {'link': 'File is too big to be downloaded via this gateway. Sorry.'} + else: # add it to download queue if everything is ok + g_link = self.generate_media_link(media) self._media_queue.put({'media': media, 'file': g_link['name']}) attr_fn = self.get_document_attribute(attributes, DocumentAttributeFilename) - if attr_fn: # Если есть оригинальное имя файла, то выводим + if attr_fn: # file has filename attrib msg = '[FileName:{}{}] {}'.format(attr_fn.file_name, size_text, g_link['link']) else: msg = g_link['link'] - if DocumentAttributeSticker in attributes_types: # Стикер + if DocumentAttributeSticker in attributes_types: # sticker smile = self.get_document_attribute(attributes, DocumentAttributeSticker).alt - msg = '[Sticker {}] {}'.format(smile, g_link['link']) # У стикеров свой формат вывода - elif DocumentAttributeAudio in attributes_types: # Аудио файл / Голосовое сообщение + msg = '[Sticker {}] {}'.format(smile, g_link['link']) + elif DocumentAttributeAudio in attributes_types: # audio file attr_a = self.get_document_attribute(attributes, DocumentAttributeAudio) - if attr_a.voice: # Голосовое сообщение - msg = '[VoiceMessage|{} sec] {}'.format(attr_a.duration, g_link['link']) # Тоже свой формат - else: # Приложенный аудиофайл, добавляем возможную информацию из его тегов + if attr_a.voice: # voicemessage + msg = '[VoiceMessage|{} sec] {}'.format(attr_a.duration, g_link['link']) + else: # other audio attr_f = self.get_document_attribute(attributes, DocumentAttributeFilename) msg = '[Audio|File:{}{}|Performer:{}|Title:{}|Duration:{} sec] {}' \ .format(attr_f.file_name, size_text, attr_a.performer, attr_a.title, attr_a.duration, g_link['link']) - elif DocumentAttributeVideo in attributes_types: # Видео + elif DocumentAttributeVideo in attributes_types: # video video_type = 'Video' video_file = '' caption = '' - if DocumentAttributeAnimated in attributes_types: # Проверка на "gif" + if DocumentAttributeAnimated in attributes_types: # it is "gif" video_type = 'AnimatedVideo' - if DocumentAttributeFilename in attributes_types: # Если есть оригинальное имя файла - указываем + if DocumentAttributeFilename in attributes_types: # file has filename attrib attr_v = self.get_document_attribute(attributes, DocumentAttributeFilename) video_file = '|File:{}'.format(attr_v.file_name) if hasattr(media, 'caption'): caption = media.caption + ' ' - # Тоже свой формат msg = '[{}{}{}] {}{}'.format(video_type, video_file, size_text, caption, g_link['link']) - elif type(media) is MessageMediaPhoto: # Фотография (сжатая, jpeg) + elif type(media) is MessageMediaPhoto: # photo (jpg) g_link = self.generate_media_link(media) msg = g_link['link'] self._media_queue.put({'media': media, 'file': g_link['name']}) - if hasattr(media, 'caption'): # Если есть описание - указываем + if hasattr(media, 'caption'): # caption msg = '{} {}'.format(media.caption, msg) - elif type(media) is MessageMediaContact: # Контакт (с номером) + elif type(media) is MessageMediaContact: # contact msg = 'First name: {} / Last name: {} / Phone: {}'\ .format(media.first_name, media.last_name, media.phone_number) - elif type(media) in [MessageMediaGeo, MessageMediaVenue]: # Адрес на карте + elif type(media) in [MessageMediaGeo, MessageMediaVenue]: # address map_link_template = 'https://maps.google.com/maps?q={0:.4f},{1:.4f}&ll={0:.4f},{1:.4f}&z=16' map_link = map_link_template.format(media.geo.lat, media.geo.long) msg = map_link @@ -353,7 +345,7 @@ class TelegramGateClient(TelegramClient): def _process_info_msg(self, message, peer): """ - Обрабатывает информационные сообщения в групповых чатах. Возвращает готовое для вывода сообщение. + Information messages. :param message: :param users: :return: @@ -411,14 +403,14 @@ class TelegramGateClient(TelegramClient): def media_thread_downloader(self): """ - Этот метод запускается в отдельном потоке и скачивает по очереди все медиа вложения из сообщений + Media downloader thread :return: """ while True: try: - if self._media_queue.empty(): # Нет медиа в очереди - спим + if self._media_queue.empty(): # queue is empty time.sleep(0.1) - else: # Иначе скачиваем медиа + else: # queue is not empty print('MTD ::: Queue is not empty. Downloading...') media = self._media_queue.get() file_path = self.xmpp_gate.config['media_store_path'] + media['file'] diff --git a/xmpp_tg/utils.py b/xmpp_tg/utils.py index d1d18db..109cc67 100644 --- a/xmpp_tg/utils.py +++ b/xmpp_tg/utils.py @@ -1,7 +1,3 @@ -""" -Различные полезные функции -""" - import types import time import pytz @@ -36,7 +32,9 @@ def get_contact_jid(peer, gatejid): return 'g' + str(peer.id) + '@' + gatejid elif peer.id and not peer.bot: # it is... user? return 'u' + str(peer.id) + '@' + gatejid - else: + elif peer.id and peer.bot: + return 'b' + str(peer.id) + '@' + gatejid + else: # what a fuck is this? return None def localtime(utc_dt): diff --git a/xmpp_tg/xmpp.py b/xmpp_tg/xmpp.py index 73213cb..95e54d4 100644 --- a/xmpp_tg/xmpp.py +++ b/xmpp_tg/xmpp.py @@ -29,16 +29,16 @@ from telethon.errors import SessionPasswordNeededError from xmpp_tg.mtproto import TelegramGateClient from xmpp_tg.utils import var_dump, display_tg_name, get_contact_jid, localtime -import xmpp_tg.monkey # Патчим баги в библиотеках +import xmpp_tg.monkey # monkeypatch class XMPPTelegram(ComponentXMPP): """ - Класс XMPP компонента транспорта между Telegram и Jabber + Main XMPPTelegram class. """ def __init__(self, config_dict): """ - Инициализация, подключение плагинов и регистрация событий + Transport initialization :param config_dict: """ @@ -86,14 +86,14 @@ class XMPPTelegram(ComponentXMPP): def __del__(self): """ - Деструктор. Теоретически. + Destructor :return: """ self.db_connection.close() def handle_start(self, arg): """ - Обработчик события успешного подключения компонента к Jabber серверу + Successful connection to Jabber server :param arg: :return: """ @@ -104,47 +104,49 @@ class XMPPTelegram(ComponentXMPP): def message(self, iq): """ - Обработчик входящих сообщений из XMPP + Message from XMPP :param iq: :return: """ jid = iq['from'].bare - if iq['to'] == self.config['jid'] and iq['type'] == 'chat': # Пишут транспорту + if iq['to'] == self.config['jid'] and iq['type'] == 'chat': # message to gateway if iq['body'].startswith('!'): self.process_command(iq) else: self.gate_reply_message(iq, 'Only commands accepted. Try !help for more info.') - else: # Пишут в Telegram + else: # --- outgoing message --- if jid in self.tg_connections and self.tg_connections[jid].is_user_authorized(): - if iq['body'].startswith('!'): # Команда из чата - if iq['to'].bare.startswith('u'): + if iq['body'].startswith('!'): # it is command! + if iq['to'].bare.startswith( ('u', 'b') ): self.process_chat_user_command(iq) elif iq['to'].bare.startswith('g') or iq['to'].bare.startswith('s') or iq['to'].bare.startswith('c'): self.process_chat_group_command(iq) else: self.gate_reply_message(iq, 'Error.') - else: # Обычное сообщение + else: # -- normal message -- tg_id = int(iq['to'].node[1:]) tg_peer = None msg = iq['body'] reply_mid = None - if msg.startswith('>'): # Проверка на цитирование + if msg.startswith('>'): # quoting check msg_lines = msg.split('\n') - matched = re.match(r'>[ ]*(?P[\d]+)[ ]*', msg_lines[0]).groupdict() + matched = re.match(r'>[ ]*(?P[\d]+)[ ]*', msg_lines[0]) + matched = matched.groupdict() if matched else {} - if 'mid' in matched: # Если нашли ID сообщения, то указываем ответ + if 'mid' in matched: # citation reply_mid = int(matched['mid']) msg = '\n'.join(msg_lines[1:]) - if iq['to'].bare.startswith('u'): # Обычный пользователь + if iq['to'].bare.startswith( ('u', 'b') ): # normal user tg_peer = InputPeerUser(tg_id, self.tg_dialogs[jid]['users'][tg_id].access_hash) - elif iq['to'].bare.startswith('g'): # Обычная группа + elif iq['to'].bare.startswith('g'): # generic group tg_peer = InputPeerChat(tg_id) - elif iq['to'].bare.startswith('s') or iq['to'].bare.startswith('c'): # Супергруппа + elif iq['to'].bare.startswith( ('s', 'c') ): # supergroup tg_peer = InputPeerChannel(tg_id, self.tg_dialogs[jid]['supergroups'][tg_id].access_hash) + # peer OK. if tg_peer: result = None @@ -162,30 +164,19 @@ class XMPPTelegram(ComponentXMPP): if not result: result = self.tg_connections[jid].invoke(SendMessageRequest(tg_peer, msg, generate_random_long(), reply_to_msg_id=reply_mid)) - msg_id = None - - # Ищем ID отправленного сообщения - if type(result) is Updates: # Супегруппа / канал - for upd in result.updates: - if type(upd) is UpdateMessageID: - msg_id = upd.id - elif type(result) is UpdateShortSentMessage: # ЛС / Группа + # find sent message id and save it + if result and hasattr(result, 'id'): # update id msg_id = result.id - - # last message id for current peer - self.tg_dialogs[jid]['messages'][tg_id] = {'id': msg_id, 'body': msg} - - # if msg_id: - # # Отправляем ответ с ID отправленного сообщения - # self.send_message(mto=iq['from'], mfrom=iq['to'], mtype='chat', - # mbody='[Your MID:{}]'.format(msg_id)) + self.tg_dialogs[jid]['messages'][tg_id] = {'id': msg_id, 'body': msg} + #self.send_message(mto=iq['from'], mfrom=iq['to'], mtype='chat', mbody='[Your MID:{}]'.format(msg_id)) + def event_presence_unsub(self, presence): return def event_presence(self, presence): """ - Обработчик события subscribe + Presence handler :param presence: :return: """ @@ -213,7 +204,7 @@ class XMPPTelegram(ComponentXMPP): def handle_online(self, event, sync_roster = True): """ - Обработчик события online. Подключается к Telegram при наличии авторизации. + Gateway's subscriber comes online :param event: :return: """ @@ -240,7 +231,7 @@ class XMPPTelegram(ComponentXMPP): def handle_offline(self, event): """ - Обработчик события offline. Отключается от Telegram, если было создано подключение. + Gateway's subscriber comes offline. :param event: :return: """ @@ -255,6 +246,11 @@ class XMPPTelegram(ComponentXMPP): self.tg_connections[jid].disconnect() def handle_interrupt(self, signal, frame): + """ + Interrupted (Ctrl+C). + :param event: + :return: + """ for jid in self.tg_connections: print('Disconnecting: %s' % jid) @@ -267,7 +263,7 @@ class XMPPTelegram(ComponentXMPP): def process_command(self, iq): """ - Обработчик общих команд транспорта + Commands to gateway, users or chats (starts with !) :param iq: :return: """ @@ -311,11 +307,13 @@ class XMPPTelegram(ComponentXMPP): elif parsed[0] == '!login': # -------------------------------------------------- self.gate_reply_message(iq, 'Please wait...') self.spawn_tg_client(jid, parsed[1]) - + if self.tg_connections[jid].is_user_authorized(): self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='online', pstatus='connected') self.gate_reply_message(iq, 'You are already authenticated in Telegram.') else: + # remove old sessions for this JID # + self.db_connection.execute("DELETE from accounts where jid = ?", (jid, ) ) self.tg_connections[jid].send_code_request(parsed[1]) self.gate_reply_message(iq, 'Gate is connected. Telegram should send SMS message to you.') self.gate_reply_message(iq, 'Please enter one-time code via !code 12345.') @@ -389,7 +387,7 @@ class XMPPTelegram(ComponentXMPP): groupuser = self.tg_connections[jid].get_entity(parsed[2]) # we re ready to make group - self.tg_connections[jid].invoke(CreateChatRequest([groupusers], groupname)) + self.tg_connections[jid].invoke(CreateChatRequest([groupuser], groupname)) self.tg_process_dialogs(jid) elif parsed[0] == '!channel' and len(parsed) >= 2: # create new channel @@ -523,11 +521,11 @@ class XMPPTelegram(ComponentXMPP): tg_id = int(iq['to'].node[1:]) if tg_id in self.tg_dialogs[jid]['supergroups']: kicked_user = self.tg_connections[jid].get_entity(parsed[1]) - if type(invited_user) == User: + if type(kicked_user) == User: self.tg_connections[jid].invoke(EditBannedRequest( InputPeerChannel(tg_id, self.tg_dialogs[jid]['supergroups'][tg_id].access_hash), kicked_user, ChannelBannedRights(until_date=None,view_messages=True) ) ) if tg_id in self.tg_dialogs[jid]['groups']: kicked_user = self.tg_connections[jid].get_entity(parsed[1]) - if type(invited_user) == User: + if type(kicked_user) == User: self.tg_connections[jid].invoke( DeleteChatUserRequest(tg_id, kicked_user) ) elif iq['body'].startswith('!s/'): @@ -553,7 +551,7 @@ class XMPPTelegram(ComponentXMPP): def spawn_tg_client(self, jid, phone): """ - Создает и инициализирует подключение к Telegram + Spawns Telegram client :param jid: :param phone: :return: @@ -572,17 +570,17 @@ class XMPPTelegram(ComponentXMPP): def init_tg(self, jid): """ - Инициализация транспорта для конкретного пользователя после подключения к Telegram + Initialize :param jid: :return: """ - # Устанавливаем, что пользователь онлайн + # Set status = Online self.tg_connections[jid].invoke(UpdateStatusRequest(offline=False)) - # Получаем и обрабатываем список диалогов + # Process Telegram contact list self.tg_process_dialogs(jid, sync_roster = False) - # Регистрируем обработчик обновлений в Telegram + # Register Telegrap updates handler self.tg_connections[jid].add_update_handler(self.tg_connections[jid].xmpp_update_handler) def roster_exchange(self, tojid, contacts): @@ -615,7 +613,7 @@ class XMPPTelegram(ComponentXMPP): print('Processing dialogs...') - # Инициализируем словари для диалогов + # dialogs dictonaries self.tg_dialogs[jid] = dict() self.tg_dialogs[jid]['raw'] = list() self.tg_dialogs[jid]['users'] = dict() @@ -623,7 +621,7 @@ class XMPPTelegram(ComponentXMPP): self.tg_dialogs[jid]['supergroups'] = dict() self.tg_dialogs[jid]['messages'] = dict() - # Оффсеты для получения диалогов + # offsets last_peer = InputPeerEmpty() last_msg_id = 0 last_date = None @@ -631,7 +629,7 @@ class XMPPTelegram(ComponentXMPP): # roster exchange # self.contact_list[jid] = dict() - while True: # В цикле по кускам получаем все диалоги + while True: dlgs = self.tg_connections[jid].invoke(GetDialogsRequest(offset_date=last_date, offset_id=last_msg_id, offset_peer=last_peer, limit=100)) @@ -640,9 +638,9 @@ class XMPPTelegram(ComponentXMPP): for usr in dlgs.users: self.tg_dialogs[jid]['users'][usr.id] = usr for cht in dlgs.chats: - if type(cht) in [Chat, ChatForbidden]: # Старая группа + if type(cht) in [Chat, ChatForbidden]: # normal group self.tg_dialogs[jid]['groups'][cht.id] = cht - elif type(cht) in [Channel, ChannelForbidden]: # Супергруппа + elif type(cht) in [Channel, ChannelForbidden]: # supergroup self.tg_dialogs[jid]['supergroups'][cht.id] = cht for dlg in dlgs.dialogs: @@ -660,18 +658,20 @@ class XMPPTelegram(ComponentXMPP): vcard['DESC'] = 'This user no longer exists in Telegram' else: rostername = display_tg_name(usr) + rostername = '[B] ' + rostername if usr.bot else rostername + vcard['FN'] = display_tg_name(usr) + vcard['DESC'] = '' if usr.first_name: vcard['N']['GIVEN'] = usr.first_name if usr.last_name: vcard['N']['FAMILY'] = usr.last_name if usr.username: vcard['DESC'] = 'Telegram Username: @' + usr.username + if usr.phone: + vcard['DESC'] += "\n" + 'Phone number: ' + usr.phone - if usr.bot: - vcard['DESC'] += ' [Bot]' - - vcard['NICKNAME'] = vcard['FN'] + vcard['NICKNAME'] = vcard['FN'] # add photo to VCard # photo, photosha1hash = self.get_peer_photo(jid, usr) if sync_roster else (None, None) @@ -692,21 +692,21 @@ class XMPPTelegram(ComponentXMPP): if type(usr.status) is UserStatusOnline: self.send_presence(pto=jid, pfrom=u_jid, pstatus = 'Online' ) elif type(usr.status) is UserStatusRecently: - self.send_presence(pto=jid, pfrom=u_jid, pshow='away', pstatus='Last seen recently') + self.send_presence(pto=jid, pfrom=u_jid, pshow='dnd', pstatus='Last seen recently') elif type(usr.status) is UserStatusOffline: - self.send_presence(pto=jid, pfrom=u_jid, pshow='xa', pstatus=localtime(usr.status.was_online).strftime('Last seen at %H:%M %d/%m/%Y') ) + self.send_presence(pto=jid, pfrom=u_jid, pshow='away', pstatus=localtime(usr.status.was_online).strftime('Last seen at %H:%M %d/%m/%Y') ) else: - self.send_presence(pto=jid, pfrom=u_jid, pshow='dnd', pstatus='Last seen a long time ago') + self.send_presence(pto=jid, pfrom=u_jid, pshow='xa', pstatus='Last seen a long time ago') if type(dlg.peer) in [PeerChat, PeerChannel]: cht = None - if type(dlg.peer) is PeerChat: # Старая группа + if type(dlg.peer) is PeerChat: # old group cht = self.tg_connections[jid].invoke(GetFullChatRequest(dlg.peer.chat_id)) cht = cht.chats[0] if cht.deactivated or cht.left: cht = None - elif type(dlg.peer) is PeerChannel: # Супергруппа + elif type(dlg.peer) is PeerChannel: # supergroup cht = self.tg_dialogs[jid]['supergroups'][dlg.peer.channel_id] @@ -732,25 +732,25 @@ class XMPPTelegram(ComponentXMPP): self.send_presence(pto=jid, pfrom=u_jid, pshow = 'chat', pstatus = cht.title) - if len(dlgs.dialogs) == 0: # Если все диалоги получены - прерываем цикл + if len(dlgs.dialogs) == 0: # all dialogs was received. if sync_roster and 'use_roster_exchange' in self.accounts[jid] and self.accounts[jid]['use_roster_exchange'] == 'true': self.roster_exchange(jid, self.contact_list[jid]) elif sync_roster: self.roster_fill(jid, self.contact_list[jid]) break - else: # Иначе строим оффсеты - last_msg_id = dlgs.dialogs[-1].top_message # Нужен последний id сообщения. Наркоманы. + else: # get next part of dialogs. + last_msg_id = dlgs.dialogs[-1].top_message # we fucking need last msg id! last_peer = dlgs.dialogs[-1].peer - last_date = next(msg for msg in dlgs.messages # Ищем дату среди сообщений + last_date = next(msg for msg in dlgs.messages # find date if type(msg.to_id) is type(last_peer) and msg.id == last_msg_id).date - if type(last_peer) is PeerUser: # Пользователь + if type(last_peer) is PeerUser: # user/bot access_hash = self.tg_dialogs[jid]['users'][last_peer.user_id].access_hash last_peer = InputPeerUser(last_peer.user_id, access_hash) - elif type(last_peer) in [Chat, ChatForbidden]: # Группа + elif type(last_peer) in [Chat, ChatForbidden]: # normal group last_peer = InputPeerChat(last_peer.chat_id) - elif type(last_peer) in [Channel, ChannelForbidden]: # Супергруппа + elif type(last_peer) in [Channel, ChannelForbidden]: # supergroup/channel access_hash = self.tg_dialogs[jid]['supergroups'][last_peer.channel_id].access_hash last_peer = InputPeerChannel(last_peer.channel_id, access_hash) @@ -759,7 +759,7 @@ class XMPPTelegram(ComponentXMPP): def gate_reply_message(self, iq, msg): """ - Отправляет ответное сообщение от имени транспорта + Reply to message to gate. :param iq: :param msg: :return: @@ -808,7 +808,7 @@ class XMPPTelegram(ComponentXMPP): def init_database(self): """ - Инициализация БД + Database initialization :return: """ def dict_factory(cursor, row): @@ -820,6 +820,6 @@ class XMPPTelegram(ComponentXMPP): conn = sqlite3.connect(self.config['db_connect'], isolation_level=None, check_same_thread=False) conn.row_factory = dict_factory - conn.execute("CREATE TABLE IF NOT EXISTS accounts(jid VARCHAR(255), tg_phone VARCHAR(25), use_roster_exchange BOOLEAN default false, keep_online BOOLEAN default false, status_update_interval INTEGER default 60, enable_avatars BOOLEAN default false)") + conn.execute("CREATE TABLE IF NOT EXISTS accounts(jid VARCHAR(255), tg_phone VARCHAR(25), use_roster_exchange BOOLEAN default false, keep_online BOOLEAN default false, status_update_interval INTEGER default 30, enable_avatars BOOLEAN default false)") return conn