[SVN] release 0.4.0
[SVN] all comments in code now in English; Sofia ♥ Fuck you with this barbarian language in code comments! (no, no fuck please) [FIX] monkey-patched telethon library to fix updates receiving in some sessions (fix github issue #686; commit b20aa0ccc91b3d767c26702f3611c44772d87f5a) [FIX] fixed "normal" quotation, such as `> text`, in previous releases it was intended to be message id to reply [FIX] now processing bots in contact list, in roster with `b` prefix [FIX] fixed message editing and deleting in supergroups [FIX] now removing old telegram session from database when initiating a new one for current JID [FIX] fixed group creating; group, supergroup, channels creating is now OK and tested [FIX] fixed processing bot in roster; now it does not crashes gateway [UPD] default status update interval is now 30 [UPD] slighly (and finally) changed behaviour of presences: `Last seen recently` = `dnd (do not disturb)`, because user that enabled this privacy settings does not want you to disturb `Last seen a long time ago` = `xa (extended away)`, because it is 'long' away 'Last seen at %date%` = `away`, because user just got away [ADD] basic interaction with bots (now only with text commands)
This commit is contained in:
10
start.py
10
start.py
@@ -13,7 +13,7 @@ xmpp_logger = logging.getLogger('sleekxmpp')
|
|||||||
|
|
||||||
class StreamToLogger:
|
class StreamToLogger:
|
||||||
"""
|
"""
|
||||||
Прикидывается файловым объектом. Нужен для перехвата стандартных потоков ввода-вывода.
|
Stream logger.
|
||||||
"""
|
"""
|
||||||
def __init__(self, logger, level=logging.INFO, old_out=None):
|
def __init__(self, logger, level=logging.INFO, old_out=None):
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
@@ -24,7 +24,7 @@ class StreamToLogger:
|
|||||||
self._prev = None
|
self._prev = None
|
||||||
|
|
||||||
def write(self, buf):
|
def write(self, buf):
|
||||||
if self._prev == buf == '\n': # Надо на буфер переделывать
|
if self._prev == buf == '\n':
|
||||||
self._prev = buf
|
self._prev = buf
|
||||||
buf = ''
|
buf = ''
|
||||||
else:
|
else:
|
||||||
@@ -39,7 +39,7 @@ class StreamToLogger:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Настраиваем логгирование
|
# Logger config
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG if CONFIG['debug'] else logging.INFO,
|
level=logging.DEBUG if CONFIG['debug'] else logging.INFO,
|
||||||
format='%(asctime)s :: %(levelname)s:%(name)s :: %(message)s',
|
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)]
|
handlers=[logging.handlers.RotatingFileHandler(filename=CONFIG['logfile']), logging.StreamHandler(sys.stdout)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Создаем логгеры и перехватчики для STDOUT/STDERR
|
# Stdout/stderr
|
||||||
logger_stdout = logging.getLogger('__stdout')
|
logger_stdout = logging.getLogger('__stdout')
|
||||||
sys.stdout = StreamToLogger(logger_stdout, logging.INFO)
|
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, ' RESTART '*9)
|
||||||
logging.getLogger().log(logging.INFO, '~'*81)
|
logging.getLogger().log(logging.INFO, '~'*81)
|
||||||
print('----------------------------------------------------------------------')
|
print('----------------------------------------------------------------------')
|
||||||
print('--- Telegram (MTProto) <---> XMPP Gateway ---')
|
print('--- Telegram (MTProto) <-> XMPP Gateway ---')
|
||||||
print('----------------------------------------------------------------------')
|
print('----------------------------------------------------------------------')
|
||||||
print()
|
print()
|
||||||
print('Starting...')
|
print('Starting...')
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from xmpp_tg.xmpp import XMPPTelegram
|
from xmpp_tg.xmpp import XMPPTelegram
|
||||||
|
|
||||||
__version__ = '0.3.5'
|
__version__ = '0.4.0'
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from sleekxmpp.plugins.xep_0054 import XEP_0054
|
|||||||
from sleekxmpp import Iq
|
from sleekxmpp import Iq
|
||||||
from sleekxmpp.exceptions import XMPPError
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
|
||||||
|
from telethon.update_state import UpdateState
|
||||||
|
|
||||||
def patched_handle_get_vcard(self, iq):
|
def patched_handle_get_vcard(self, iq):
|
||||||
if iq['type'] == 'result':
|
if iq['type'] == 'result':
|
||||||
@@ -18,5 +19,25 @@ def patched_handle_get_vcard(self, iq):
|
|||||||
elif iq['type'] == 'set':
|
elif iq['type'] == 'set':
|
||||||
raise XMPPError('service-unavailable')
|
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
|
XEP_0054._handle_get_vcard = patched_handle_get_vcard
|
||||||
|
UpdateState.stop_workers = patched_stop_workers
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import time
|
|||||||
from xmpp_tg.utils import localtime, display_tg_name
|
from xmpp_tg.utils import localtime, display_tg_name
|
||||||
|
|
||||||
from .utils import var_dump
|
from .utils import var_dump
|
||||||
|
import xmpp_tg.monkey
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
@@ -59,6 +60,11 @@ class TelegramGateClient(TelegramClient):
|
|||||||
|
|
||||||
def xmpp_update_handler(self, obj):
|
def xmpp_update_handler(self, obj):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Main function: Telegram update handler.
|
||||||
|
:param media:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
# print("We have received update for <%s>" % self.jid)
|
# print("We have received update for <%s>" % self.jid)
|
||||||
# print(obj)
|
# print(obj)
|
||||||
|
|
||||||
@@ -69,19 +75,6 @@ class TelegramGateClient(TelegramClient):
|
|||||||
self._media_thread.start()
|
self._media_thread.start()
|
||||||
self._status_update_thread.start()
|
self._status_update_thread.start()
|
||||||
|
|
||||||
'''
|
|
||||||
Боты
|
|
||||||
Сделать запоминание ростера в бд
|
|
||||||
Сделать лучше хендлинг ошибок
|
|
||||||
Доделать все типы информационных сообщений
|
|
||||||
Сделать джойны по линкам в чаты/каналы
|
|
||||||
Сделать поиск и добавление пользователей
|
|
||||||
Сделать листание истории
|
|
||||||
Сделать отправку всех непрочтенных сообщений при входе
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Здесь будет очень длинный пиздец ^__^
|
|
||||||
|
|
||||||
nl = '\n'
|
nl = '\n'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -123,8 +116,10 @@ class TelegramGateClient(TelegramClient):
|
|||||||
# detect from id
|
# detect from id
|
||||||
if is_user:
|
if is_user:
|
||||||
cid = obj.message.from_id
|
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
|
user = self._get_user_information(cid)
|
||||||
|
peer = InputPeerUser(user.id, user.access_hash)
|
||||||
prefix = 'u'
|
prefix = 'u'
|
||||||
|
prefix = 'b' if user.bot else prefix
|
||||||
elif is_group:
|
elif is_group:
|
||||||
cid = obj.message.to_id.chat_id
|
cid = obj.message.to_id.chat_id
|
||||||
peer = InputPeerChat(cid)
|
peer = InputPeerChat(cid)
|
||||||
@@ -181,9 +176,9 @@ class TelegramGateClient(TelegramClient):
|
|||||||
if type(obj.status) is UserStatusOnline:
|
if type(obj.status) is UserStatusOnline:
|
||||||
self._status_updates[str(obj.user_id)] = { 'status': None, 'message': 'Online' }
|
self._status_updates[str(obj.user_id)] = { 'status': None, 'message': 'Online' }
|
||||||
elif type(obj.status) is UserStatusOffline:
|
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:
|
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:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -201,7 +196,7 @@ class TelegramGateClient(TelegramClient):
|
|||||||
|
|
||||||
def generate_media_link(self, media):
|
def generate_media_link(self, media):
|
||||||
"""
|
"""
|
||||||
Генерирует будующее имя и ссылку на скачиваемое медиа-вложения из сообщения
|
Generates download link from media object
|
||||||
:param media:
|
:param media:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -228,7 +223,7 @@ class TelegramGateClient(TelegramClient):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_document_attribute(attributes, match):
|
def get_document_attribute(attributes, match):
|
||||||
"""
|
"""
|
||||||
Находит заданных аттрибут в списке. Используется при разборе медиа-вложений типа Документ.
|
Get document attribute.
|
||||||
:param attributes:
|
:param attributes:
|
||||||
:param match:
|
:param match:
|
||||||
:return:
|
:return:
|
||||||
@@ -248,24 +243,23 @@ class TelegramGateClient(TelegramClient):
|
|||||||
self.xmpp_gate.tg_dialogs[self.jid]['users'][uid] = entity
|
self.xmpp_gate.tg_dialogs[self.jid]['users'][uid] = entity
|
||||||
return entity
|
return entity
|
||||||
else:
|
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):
|
def _process_forward_msg(self, message):
|
||||||
"""
|
"""
|
||||||
Обрабатывает информацию в пересланном сообщении (от кого оно и/или из какого канала). Требует дополнительно
|
Process forward message to find out from what user message is forwarded.
|
||||||
предоставление информации об пользователях/каналах.
|
|
||||||
:param message:
|
:param message:
|
||||||
:param users:
|
:param users:
|
||||||
:param channels:
|
:param channels:
|
||||||
:return:
|
: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)
|
usr = self._get_user_information(message.fwd_from.from_id)
|
||||||
fwd_from = display_tg_name(usr)
|
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)
|
fwd_from = 'Channel {}'.format(message.fwd_from.channel_id)
|
||||||
|
|
||||||
# let's construct
|
# let's construct
|
||||||
@@ -274,74 +268,72 @@ class TelegramGateClient(TelegramClient):
|
|||||||
|
|
||||||
def _process_media_msg(self, media):
|
def _process_media_msg(self, media):
|
||||||
"""
|
"""
|
||||||
Обрабатывает медиа-вложения в сообщениях. Добавляет их в очередь на загрузку. Производит разбор с генерацию
|
Process message with media.
|
||||||
готового для вывода сообщения с информацией о медиа и сгенерированной ссылкой на него.
|
|
||||||
:param media:
|
:param media:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
msg = ''
|
msg = ''
|
||||||
|
|
||||||
if type(media) is MessageMediaDocument: # Документ или замаскированная сущность
|
if type(media) is MessageMediaDocument: # document
|
||||||
attributes = media.document.attributes
|
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)
|
size_text = '|Size: {:.2f} Mb'.format(media.document.size / 1024 / 1024)
|
||||||
|
|
||||||
if media.document.size > self.xmpp_gate.config['media_max_download_size']: # Не загружаем большие файлы
|
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 Telegram <---> XMPP Gateway. Sorry.'}
|
g_link = {'link': 'File is too big to be downloaded via this gateway. Sorry.'}
|
||||||
else:
|
else: # add it to download queue if everything is ok
|
||||||
g_link = self.generate_media_link(media) # Добавляем файл в очередь на загрузку в отдельном потоке
|
g_link = self.generate_media_link(media)
|
||||||
self._media_queue.put({'media': media, 'file': g_link['name']})
|
self._media_queue.put({'media': media, 'file': g_link['name']})
|
||||||
|
|
||||||
attr_fn = self.get_document_attribute(attributes, DocumentAttributeFilename)
|
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'])
|
msg = '[FileName:{}{}] {}'.format(attr_fn.file_name, size_text, g_link['link'])
|
||||||
else:
|
else:
|
||||||
msg = g_link['link']
|
msg = g_link['link']
|
||||||
|
|
||||||
if DocumentAttributeSticker in attributes_types: # Стикер
|
if DocumentAttributeSticker in attributes_types: # sticker
|
||||||
smile = self.get_document_attribute(attributes, DocumentAttributeSticker).alt
|
smile = self.get_document_attribute(attributes, DocumentAttributeSticker).alt
|
||||||
msg = '[Sticker {}] {}'.format(smile, g_link['link']) # У стикеров свой формат вывода
|
msg = '[Sticker {}] {}'.format(smile, g_link['link'])
|
||||||
elif DocumentAttributeAudio in attributes_types: # Аудио файл / Голосовое сообщение
|
elif DocumentAttributeAudio in attributes_types: # audio file
|
||||||
attr_a = self.get_document_attribute(attributes, DocumentAttributeAudio)
|
attr_a = self.get_document_attribute(attributes, DocumentAttributeAudio)
|
||||||
|
|
||||||
if attr_a.voice: # Голосовое сообщение
|
if attr_a.voice: # voicemessage
|
||||||
msg = '[VoiceMessage|{} sec] {}'.format(attr_a.duration, g_link['link']) # Тоже свой формат
|
msg = '[VoiceMessage|{} sec] {}'.format(attr_a.duration, g_link['link'])
|
||||||
else: # Приложенный аудиофайл, добавляем возможную информацию из его тегов
|
else: # other audio
|
||||||
attr_f = self.get_document_attribute(attributes, DocumentAttributeFilename)
|
attr_f = self.get_document_attribute(attributes, DocumentAttributeFilename)
|
||||||
msg = '[Audio|File:{}{}|Performer:{}|Title:{}|Duration:{} sec] {}' \
|
msg = '[Audio|File:{}{}|Performer:{}|Title:{}|Duration:{} sec] {}' \
|
||||||
.format(attr_f.file_name, size_text, attr_a.performer, attr_a.title,
|
.format(attr_f.file_name, size_text, attr_a.performer, attr_a.title,
|
||||||
attr_a.duration, g_link['link'])
|
attr_a.duration, g_link['link'])
|
||||||
elif DocumentAttributeVideo in attributes_types: # Видео
|
elif DocumentAttributeVideo in attributes_types: # video
|
||||||
video_type = 'Video'
|
video_type = 'Video'
|
||||||
video_file = ''
|
video_file = ''
|
||||||
caption = ''
|
caption = ''
|
||||||
|
|
||||||
if DocumentAttributeAnimated in attributes_types: # Проверка на "gif"
|
if DocumentAttributeAnimated in attributes_types: # it is "gif"
|
||||||
video_type = 'AnimatedVideo'
|
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)
|
attr_v = self.get_document_attribute(attributes, DocumentAttributeFilename)
|
||||||
video_file = '|File:{}'.format(attr_v.file_name)
|
video_file = '|File:{}'.format(attr_v.file_name)
|
||||||
|
|
||||||
if hasattr(media, 'caption'):
|
if hasattr(media, 'caption'):
|
||||||
caption = media.caption + ' '
|
caption = media.caption + ' '
|
||||||
|
|
||||||
# Тоже свой формат
|
|
||||||
msg = '[{}{}{}] {}{}'.format(video_type, video_file, size_text, caption, g_link['link'])
|
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)
|
g_link = self.generate_media_link(media)
|
||||||
msg = g_link['link']
|
msg = g_link['link']
|
||||||
|
|
||||||
self._media_queue.put({'media': media, 'file': g_link['name']})
|
self._media_queue.put({'media': media, 'file': g_link['name']})
|
||||||
|
|
||||||
if hasattr(media, 'caption'): # Если есть описание - указываем
|
if hasattr(media, 'caption'): # caption
|
||||||
msg = '{} {}'.format(media.caption, msg)
|
msg = '{} {}'.format(media.caption, msg)
|
||||||
|
|
||||||
elif type(media) is MessageMediaContact: # Контакт (с номером)
|
elif type(media) is MessageMediaContact: # contact
|
||||||
msg = 'First name: {} / Last name: {} / Phone: {}'\
|
msg = 'First name: {} / Last name: {} / Phone: {}'\
|
||||||
.format(media.first_name, media.last_name, media.phone_number)
|
.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_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)
|
map_link = map_link_template.format(media.geo.lat, media.geo.long)
|
||||||
msg = map_link
|
msg = map_link
|
||||||
@@ -353,7 +345,7 @@ class TelegramGateClient(TelegramClient):
|
|||||||
|
|
||||||
def _process_info_msg(self, message, peer):
|
def _process_info_msg(self, message, peer):
|
||||||
"""
|
"""
|
||||||
Обрабатывает информационные сообщения в групповых чатах. Возвращает готовое для вывода сообщение.
|
Information messages.
|
||||||
:param message:
|
:param message:
|
||||||
:param users:
|
:param users:
|
||||||
:return:
|
:return:
|
||||||
@@ -411,14 +403,14 @@ class TelegramGateClient(TelegramClient):
|
|||||||
|
|
||||||
def media_thread_downloader(self):
|
def media_thread_downloader(self):
|
||||||
"""
|
"""
|
||||||
Этот метод запускается в отдельном потоке и скачивает по очереди все медиа вложения из сообщений
|
Media downloader thread
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if self._media_queue.empty(): # Нет медиа в очереди - спим
|
if self._media_queue.empty(): # queue is empty
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
else: # Иначе скачиваем медиа
|
else: # queue is not empty
|
||||||
print('MTD ::: Queue is not empty. Downloading...')
|
print('MTD ::: Queue is not empty. Downloading...')
|
||||||
media = self._media_queue.get()
|
media = self._media_queue.get()
|
||||||
file_path = self.xmpp_gate.config['media_store_path'] + media['file']
|
file_path = self.xmpp_gate.config['media_store_path'] + media['file']
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
"""
|
|
||||||
Различные полезные функции
|
|
||||||
"""
|
|
||||||
|
|
||||||
import types
|
import types
|
||||||
import time
|
import time
|
||||||
import pytz
|
import pytz
|
||||||
@@ -36,7 +32,9 @@ def get_contact_jid(peer, gatejid):
|
|||||||
return 'g' + str(peer.id) + '@' + gatejid
|
return 'g' + str(peer.id) + '@' + gatejid
|
||||||
elif peer.id and not peer.bot: # it is... user?
|
elif peer.id and not peer.bot: # it is... user?
|
||||||
return 'u' + str(peer.id) + '@' + gatejid
|
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
|
return None
|
||||||
|
|
||||||
def localtime(utc_dt):
|
def localtime(utc_dt):
|
||||||
|
|||||||
132
xmpp_tg/xmpp.py
132
xmpp_tg/xmpp.py
@@ -29,16 +29,16 @@ from telethon.errors import SessionPasswordNeededError
|
|||||||
|
|
||||||
from xmpp_tg.mtproto import TelegramGateClient
|
from xmpp_tg.mtproto import TelegramGateClient
|
||||||
from xmpp_tg.utils import var_dump, display_tg_name, get_contact_jid, localtime
|
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):
|
class XMPPTelegram(ComponentXMPP):
|
||||||
"""
|
"""
|
||||||
Класс XMPP компонента транспорта между Telegram и Jabber
|
Main XMPPTelegram class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config_dict):
|
def __init__(self, config_dict):
|
||||||
"""
|
"""
|
||||||
Инициализация, подключение плагинов и регистрация событий
|
Transport initialization
|
||||||
:param config_dict:
|
:param config_dict:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -86,14 +86,14 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""
|
"""
|
||||||
Деструктор. Теоретически.
|
Destructor
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.db_connection.close()
|
self.db_connection.close()
|
||||||
|
|
||||||
def handle_start(self, arg):
|
def handle_start(self, arg):
|
||||||
"""
|
"""
|
||||||
Обработчик события успешного подключения компонента к Jabber серверу
|
Successful connection to Jabber server
|
||||||
:param arg:
|
:param arg:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -104,47 +104,49 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def message(self, iq):
|
def message(self, iq):
|
||||||
"""
|
"""
|
||||||
Обработчик входящих сообщений из XMPP
|
Message from XMPP
|
||||||
:param iq:
|
:param iq:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
jid = iq['from'].bare
|
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('!'):
|
if iq['body'].startswith('!'):
|
||||||
self.process_command(iq)
|
self.process_command(iq)
|
||||||
else:
|
else:
|
||||||
self.gate_reply_message(iq, 'Only commands accepted. Try !help for more info.')
|
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 jid in self.tg_connections and self.tg_connections[jid].is_user_authorized():
|
||||||
if iq['body'].startswith('!'): # Команда из чата
|
if iq['body'].startswith('!'): # it is command!
|
||||||
if iq['to'].bare.startswith('u'):
|
if iq['to'].bare.startswith( ('u', 'b') ):
|
||||||
self.process_chat_user_command(iq)
|
self.process_chat_user_command(iq)
|
||||||
elif iq['to'].bare.startswith('g') or iq['to'].bare.startswith('s') or iq['to'].bare.startswith('c'):
|
elif iq['to'].bare.startswith('g') or iq['to'].bare.startswith('s') or iq['to'].bare.startswith('c'):
|
||||||
self.process_chat_group_command(iq)
|
self.process_chat_group_command(iq)
|
||||||
else:
|
else:
|
||||||
self.gate_reply_message(iq, 'Error.')
|
self.gate_reply_message(iq, 'Error.')
|
||||||
else: # Обычное сообщение
|
else: # -- normal message --
|
||||||
tg_id = int(iq['to'].node[1:])
|
tg_id = int(iq['to'].node[1:])
|
||||||
tg_peer = None
|
tg_peer = None
|
||||||
msg = iq['body']
|
msg = iq['body']
|
||||||
reply_mid = None
|
reply_mid = None
|
||||||
|
|
||||||
if msg.startswith('>'): # Проверка на цитирование
|
if msg.startswith('>'): # quoting check
|
||||||
msg_lines = msg.split('\n')
|
msg_lines = msg.split('\n')
|
||||||
matched = re.match(r'>[ ]*(?P<mid>[\d]+)[ ]*', msg_lines[0]).groupdict()
|
matched = re.match(r'>[ ]*(?P<mid>[\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'])
|
reply_mid = int(matched['mid'])
|
||||||
msg = '\n'.join(msg_lines[1:])
|
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)
|
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)
|
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)
|
tg_peer = InputPeerChannel(tg_id, self.tg_dialogs[jid]['supergroups'][tg_id].access_hash)
|
||||||
|
|
||||||
|
# peer OK.
|
||||||
if tg_peer:
|
if tg_peer:
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
@@ -162,30 +164,19 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
if not result:
|
if not result:
|
||||||
result = self.tg_connections[jid].invoke(SendMessageRequest(tg_peer, msg, generate_random_long(), reply_to_msg_id=reply_mid))
|
result = self.tg_connections[jid].invoke(SendMessageRequest(tg_peer, msg, generate_random_long(), reply_to_msg_id=reply_mid))
|
||||||
|
|
||||||
msg_id = None
|
# find sent message id and save it
|
||||||
|
if result and hasattr(result, 'id'): # update id
|
||||||
# Ищем 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: # ЛС / Группа
|
|
||||||
msg_id = result.id
|
msg_id = result.id
|
||||||
|
|
||||||
# last message id for current peer
|
|
||||||
self.tg_dialogs[jid]['messages'][tg_id] = {'id': msg_id, 'body': msg}
|
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))
|
||||||
|
|
||||||
# if msg_id:
|
|
||||||
# # Отправляем ответ с ID отправленного сообщения
|
|
||||||
# self.send_message(mto=iq['from'], mfrom=iq['to'], mtype='chat',
|
|
||||||
# mbody='[Your MID:{}]'.format(msg_id))
|
|
||||||
|
|
||||||
def event_presence_unsub(self, presence):
|
def event_presence_unsub(self, presence):
|
||||||
return
|
return
|
||||||
|
|
||||||
def event_presence(self, presence):
|
def event_presence(self, presence):
|
||||||
"""
|
"""
|
||||||
Обработчик события subscribe
|
Presence handler
|
||||||
:param presence:
|
:param presence:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -213,7 +204,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def handle_online(self, event, sync_roster = True):
|
def handle_online(self, event, sync_roster = True):
|
||||||
"""
|
"""
|
||||||
Обработчик события online. Подключается к Telegram при наличии авторизации.
|
Gateway's subscriber comes online
|
||||||
:param event:
|
:param event:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -240,7 +231,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def handle_offline(self, event):
|
def handle_offline(self, event):
|
||||||
"""
|
"""
|
||||||
Обработчик события offline. Отключается от Telegram, если было создано подключение.
|
Gateway's subscriber comes offline.
|
||||||
:param event:
|
:param event:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -255,6 +246,11 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
self.tg_connections[jid].disconnect()
|
self.tg_connections[jid].disconnect()
|
||||||
|
|
||||||
def handle_interrupt(self, signal, frame):
|
def handle_interrupt(self, signal, frame):
|
||||||
|
"""
|
||||||
|
Interrupted (Ctrl+C).
|
||||||
|
:param event:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
for jid in self.tg_connections:
|
for jid in self.tg_connections:
|
||||||
print('Disconnecting: %s' % jid)
|
print('Disconnecting: %s' % jid)
|
||||||
@@ -267,7 +263,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def process_command(self, iq):
|
def process_command(self, iq):
|
||||||
"""
|
"""
|
||||||
Обработчик общих команд транспорта
|
Commands to gateway, users or chats (starts with !)
|
||||||
:param iq:
|
:param iq:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -316,6 +312,8 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='online', pstatus='connected')
|
self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='online', pstatus='connected')
|
||||||
self.gate_reply_message(iq, 'You are already authenticated in Telegram.')
|
self.gate_reply_message(iq, 'You are already authenticated in Telegram.')
|
||||||
else:
|
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.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, 'Gate is connected. Telegram should send SMS message to you.')
|
||||||
self.gate_reply_message(iq, 'Please enter one-time code via !code 12345.')
|
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])
|
groupuser = self.tg_connections[jid].get_entity(parsed[2])
|
||||||
|
|
||||||
# we re ready to make group
|
# 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)
|
self.tg_process_dialogs(jid)
|
||||||
|
|
||||||
elif parsed[0] == '!channel' and len(parsed) >= 2: # create new channel
|
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:])
|
tg_id = int(iq['to'].node[1:])
|
||||||
if tg_id in self.tg_dialogs[jid]['supergroups']:
|
if tg_id in self.tg_dialogs[jid]['supergroups']:
|
||||||
kicked_user = self.tg_connections[jid].get_entity(parsed[1])
|
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) ) )
|
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']:
|
if tg_id in self.tg_dialogs[jid]['groups']:
|
||||||
kicked_user = self.tg_connections[jid].get_entity(parsed[1])
|
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) )
|
self.tg_connections[jid].invoke( DeleteChatUserRequest(tg_id, kicked_user) )
|
||||||
|
|
||||||
elif iq['body'].startswith('!s/'):
|
elif iq['body'].startswith('!s/'):
|
||||||
@@ -553,7 +551,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def spawn_tg_client(self, jid, phone):
|
def spawn_tg_client(self, jid, phone):
|
||||||
"""
|
"""
|
||||||
Создает и инициализирует подключение к Telegram
|
Spawns Telegram client
|
||||||
:param jid:
|
:param jid:
|
||||||
:param phone:
|
:param phone:
|
||||||
:return:
|
:return:
|
||||||
@@ -572,17 +570,17 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def init_tg(self, jid):
|
def init_tg(self, jid):
|
||||||
"""
|
"""
|
||||||
Инициализация транспорта для конкретного пользователя после подключения к Telegram
|
Initialize
|
||||||
:param jid:
|
:param jid:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# Устанавливаем, что пользователь онлайн
|
# Set status = Online
|
||||||
self.tg_connections[jid].invoke(UpdateStatusRequest(offline=False))
|
self.tg_connections[jid].invoke(UpdateStatusRequest(offline=False))
|
||||||
|
|
||||||
# Получаем и обрабатываем список диалогов
|
# Process Telegram contact list
|
||||||
self.tg_process_dialogs(jid, sync_roster = False)
|
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)
|
self.tg_connections[jid].add_update_handler(self.tg_connections[jid].xmpp_update_handler)
|
||||||
|
|
||||||
def roster_exchange(self, tojid, contacts):
|
def roster_exchange(self, tojid, contacts):
|
||||||
@@ -615,7 +613,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
print('Processing dialogs...')
|
print('Processing dialogs...')
|
||||||
|
|
||||||
# Инициализируем словари для диалогов
|
# dialogs dictonaries
|
||||||
self.tg_dialogs[jid] = dict()
|
self.tg_dialogs[jid] = dict()
|
||||||
self.tg_dialogs[jid]['raw'] = list()
|
self.tg_dialogs[jid]['raw'] = list()
|
||||||
self.tg_dialogs[jid]['users'] = dict()
|
self.tg_dialogs[jid]['users'] = dict()
|
||||||
@@ -623,7 +621,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
self.tg_dialogs[jid]['supergroups'] = dict()
|
self.tg_dialogs[jid]['supergroups'] = dict()
|
||||||
self.tg_dialogs[jid]['messages'] = dict()
|
self.tg_dialogs[jid]['messages'] = dict()
|
||||||
|
|
||||||
# Оффсеты для получения диалогов
|
# offsets
|
||||||
last_peer = InputPeerEmpty()
|
last_peer = InputPeerEmpty()
|
||||||
last_msg_id = 0
|
last_msg_id = 0
|
||||||
last_date = None
|
last_date = None
|
||||||
@@ -631,7 +629,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
# roster exchange #
|
# roster exchange #
|
||||||
self.contact_list[jid] = dict()
|
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,
|
dlgs = self.tg_connections[jid].invoke(GetDialogsRequest(offset_date=last_date, offset_id=last_msg_id,
|
||||||
offset_peer=last_peer, limit=100))
|
offset_peer=last_peer, limit=100))
|
||||||
|
|
||||||
@@ -640,9 +638,9 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
for usr in dlgs.users:
|
for usr in dlgs.users:
|
||||||
self.tg_dialogs[jid]['users'][usr.id] = usr
|
self.tg_dialogs[jid]['users'][usr.id] = usr
|
||||||
for cht in dlgs.chats:
|
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
|
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
|
self.tg_dialogs[jid]['supergroups'][cht.id] = cht
|
||||||
|
|
||||||
for dlg in dlgs.dialogs:
|
for dlg in dlgs.dialogs:
|
||||||
@@ -660,16 +658,18 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
vcard['DESC'] = 'This user no longer exists in Telegram'
|
vcard['DESC'] = 'This user no longer exists in Telegram'
|
||||||
else:
|
else:
|
||||||
rostername = display_tg_name(usr)
|
rostername = display_tg_name(usr)
|
||||||
|
rostername = '[B] ' + rostername if usr.bot else rostername
|
||||||
|
|
||||||
vcard['FN'] = display_tg_name(usr)
|
vcard['FN'] = display_tg_name(usr)
|
||||||
|
vcard['DESC'] = ''
|
||||||
if usr.first_name:
|
if usr.first_name:
|
||||||
vcard['N']['GIVEN'] = usr.first_name
|
vcard['N']['GIVEN'] = usr.first_name
|
||||||
if usr.last_name:
|
if usr.last_name:
|
||||||
vcard['N']['FAMILY'] = usr.last_name
|
vcard['N']['FAMILY'] = usr.last_name
|
||||||
if usr.username:
|
if usr.username:
|
||||||
vcard['DESC'] = 'Telegram Username: @' + usr.username
|
vcard['DESC'] = 'Telegram Username: @' + usr.username
|
||||||
|
if usr.phone:
|
||||||
if usr.bot:
|
vcard['DESC'] += "\n" + 'Phone number: ' + usr.phone
|
||||||
vcard['DESC'] += ' [Bot]'
|
|
||||||
|
|
||||||
vcard['NICKNAME'] = vcard['FN']
|
vcard['NICKNAME'] = vcard['FN']
|
||||||
|
|
||||||
@@ -692,21 +692,21 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
if type(usr.status) is UserStatusOnline:
|
if type(usr.status) is UserStatusOnline:
|
||||||
self.send_presence(pto=jid, pfrom=u_jid, pstatus = 'Online' )
|
self.send_presence(pto=jid, pfrom=u_jid, pstatus = 'Online' )
|
||||||
elif type(usr.status) is UserStatusRecently:
|
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:
|
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:
|
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]:
|
if type(dlg.peer) in [PeerChat, PeerChannel]:
|
||||||
cht = None
|
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 = self.tg_connections[jid].invoke(GetFullChatRequest(dlg.peer.chat_id))
|
||||||
cht = cht.chats[0]
|
cht = cht.chats[0]
|
||||||
if cht.deactivated or cht.left:
|
if cht.deactivated or cht.left:
|
||||||
cht = None
|
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]
|
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)
|
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':
|
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])
|
self.roster_exchange(jid, self.contact_list[jid])
|
||||||
elif sync_roster:
|
elif sync_roster:
|
||||||
self.roster_fill(jid, self.contact_list[jid])
|
self.roster_fill(jid, self.contact_list[jid])
|
||||||
break
|
break
|
||||||
else: # Иначе строим оффсеты
|
else: # get next part of dialogs.
|
||||||
last_msg_id = dlgs.dialogs[-1].top_message # Нужен последний id сообщения. Наркоманы.
|
last_msg_id = dlgs.dialogs[-1].top_message # we fucking need last msg id!
|
||||||
last_peer = dlgs.dialogs[-1].peer
|
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(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
|
access_hash = self.tg_dialogs[jid]['users'][last_peer.user_id].access_hash
|
||||||
last_peer = InputPeerUser(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)
|
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
|
access_hash = self.tg_dialogs[jid]['supergroups'][last_peer.channel_id].access_hash
|
||||||
last_peer = InputPeerChannel(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):
|
def gate_reply_message(self, iq, msg):
|
||||||
"""
|
"""
|
||||||
Отправляет ответное сообщение от имени транспорта
|
Reply to message to gate.
|
||||||
:param iq:
|
:param iq:
|
||||||
:param msg:
|
:param msg:
|
||||||
:return:
|
:return:
|
||||||
@@ -808,7 +808,7 @@ class XMPPTelegram(ComponentXMPP):
|
|||||||
|
|
||||||
def init_database(self):
|
def init_database(self):
|
||||||
"""
|
"""
|
||||||
Инициализация БД
|
Database initialization
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
def dict_factory(cursor, row):
|
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 = sqlite3.connect(self.config['db_connect'], isolation_level=None, check_same_thread=False)
|
||||||
conn.row_factory = dict_factory
|
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
|
return conn
|
||||||
|
|||||||
Reference in New Issue
Block a user