diff --git a/config_example.py b/config_example.py index d7ec5aa..1df42e2 100644 --- a/config_example.py +++ b/config_example.py @@ -4,6 +4,7 @@ CONFIG = { 'title': 'XMPP <-> Telegram Gate', 'debug': True, + 'logfile': '/dev/null', 'jid': 'tlgrm.localhost', 'secret': 'secret', @@ -15,6 +16,10 @@ CONFIG = { 'tg_api_id': '17349', # Telegram Desktop (GitHub) 'tg_api_hash': '344583e45741c457fe1862106095a5eb', + #'tg_server_ip': '149.154.167.50', + #'tg_server_port': 443, + #'tg_server_dc': 2, + 'db_connect': 'db.sqlite', 'media_web_link_prefix': 'http://tlgrm.localhost/media/', diff --git a/start.py b/start.py index 1d5a041..969b561 100644 --- a/start.py +++ b/start.py @@ -44,7 +44,7 @@ logging.basicConfig( level=logging.DEBUG if CONFIG['debug'] else logging.INFO, format='%(asctime)s :: %(levelname)s:%(name)s :: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', - handlers=[logging.handlers.RotatingFileHandler(filename='gate.log'), logging.StreamHandler(sys.stdout)] + handlers=[logging.handlers.RotatingFileHandler(filename=CONFIG['logfile']), logging.StreamHandler(sys.stdout)] ) # Создаем логгеры и перехватчики для STDOUT/STDERR @@ -62,7 +62,7 @@ print('--- Telegram (MTProto) <---> XMPP Gateway ---') print('----------------------------------------------------------------------') print() print('Starting...') -print('Gate build: rev{}'.format(xmpp_tg.__version__)) +print('Gate version: {}'.format(xmpp_tg.__version__)) print('Process pid: {}'.format(os.getpid())) print('Using Telethon v{} and SleekXMPP v{}'.format(telethon.TelegramClient.__version__, sleekxmpp.__version__)) print() diff --git a/xmpp_tg/__init__.py b/xmpp_tg/__init__.py index 54bdddc..a5f638a 100644 --- a/xmpp_tg/__init__.py +++ b/xmpp_tg/__init__.py @@ -1,3 +1,3 @@ from xmpp_tg.xmpp import XMPPTelegram -__version__ = 15 +__version__ = '0.3.0' diff --git a/xmpp_tg/mtproto.py b/xmpp_tg/mtproto.py index 2e5b534..c84601f 100644 --- a/xmpp_tg/mtproto.py +++ b/xmpp_tg/mtproto.py @@ -42,30 +42,32 @@ class TelegramGateClient(TelegramClient): self.xmpp_gate = xmpp_gate self.jid = jid self.phone = phone - self.user_options = {'nl_after_info': True, 'status_update_interval': 60} self._media_queue = queue.Queue() self._media_thread = threading.Thread(name='MediaDownloaderThread', target=self.media_thread_downloader) - self._media_thread.start() + + self._status_updates = dict() + self._status_update_thread = threading.Thread(name = 'StatusUpdateThread', target = self.status_updater_thread) self._groups_users = dict() self._message_cache_users = dict() self._message_cache_groups = dict() self._message_cache_supergroups = dict() - self._status_last = dict() - self._del_pts = 0 def xmpp_update_handler(self, obj): - print('new update for ' + self.jid) - print(type(obj), obj.__dict__) + + print("We have received update for <%s>" % self.jid) + print(obj) - # link to self-user # + # we have received some updates, so we're logined and can get object and start mtd / upd threads # if not self.me: me = self.get_me() self.me = InputPeerUser(me.id, me.access_hash) + self._media_thread.start() + self._status_update_thread.start() ''' Боты @@ -80,7 +82,7 @@ class TelegramGateClient(TelegramClient): # Здесь будет очень длинный пиздец ^__^ - nl = '\n' if self.user_options['nl_after_info'] else '' + nl = '\n' try: @@ -95,16 +97,8 @@ class TelegramGateClient(TelegramClient): # message from normal group # if type(obj) in [UpdateShortChatMessage] and not obj.out: fwd_from = self._process_forward_msg(obj) if obj.fwd_from else '' # process forward messages - nickname = '' - - # get sender information from chat info - if obj.from_id not in self._groups_users: - chat_info = self.invoke(GetFullChatRequest(obj.chat_id)) - - for usr in chat_info.users: - self._groups_users[usr.id] = usr - - nickname = display_tg_name(self._groups_users[obj.from_id].first_name, self._groups_users[obj.from_id].last_name) + usr = self._get_user_information(obj.from_id) + nickname = display_tg_name(usr) # send message self.gate_send_message(mfrom='g' + str(obj.chat_id), mbody ='[MSG {}] [User: {}] {}{}'.format(obj.id, nickname, fwd_from, obj.message) ) @@ -159,7 +153,7 @@ class TelegramGateClient(TelegramClient): # get sender information from chat info # if not is_user and not obj.message.post: usr = self._get_user_information(obj.message.from_id) - nickname = display_tg_name(usr.first_name, usr.last_name) + nickname = display_tg_name(usr) msg = '[User: {}] {}'.format(nickname, msg) @@ -183,34 +177,24 @@ class TelegramGateClient(TelegramClient): # Status Updates # if type(obj) is UpdateUserStatus: - # save last update time # - if (obj.user_id in self._status_last) and ( (time.time() - self._status_last[obj.user_id]['time'] < self.user_options['status_update_interval']) or self._status_last[obj.user_id]['status'] == obj.status ): - return - - self._status_last[obj.user_id] = {'status': obj.status, 'time': time.time()} - # process status update # if type(obj.status) is UserStatusOnline: - self.xmpp_gate.send_presence( pto=self.jid, pfrom='u'+str(obj.user_id)+'@'+self.xmpp_gate.config['jid']) + self._status_updates[str(obj.user_id)] = { 'status': None, 'message': 'Online' } elif type(obj.status) is UserStatusOffline: - self.xmpp_gate.send_presence( pto=self.jid, pfrom='u'+str(obj.user_id)+'@'+self.xmpp_gate.config['jid'], ptype='xa', pstatus=obj.status.was_online.strftime('Last seen at %H:%M %d/%m/%Y') ) + self._status_updates[str(obj.user_id)] = { 'status': 'xa', 'message': obj.status.was_online.strftime('Last seen at %H:%M %d/%m/%Y') } elif type(obj.status) is UserStatusRecently: - self.xmpp_gate.send_presence( pto=self.jid, pfrom='u' + str(obj.user_id) + '@' + self.xmpp_gate.config['jid'], pstatus='Last seen recently' ) + self._status_updates[str(obj.user_id)] = { 'status': 'away', 'message': 'Last seen recently' } else: - print(type(obj.status)) - print(obj.update.status.__dict__) + pass except Exception: print('Exception occurs!') print(traceback.format_exc()) - print(' ') - def gate_send_message(self, mfrom, mbody): - tg_from = int(mfrom[1:]) - if not tg_from in self.xmpp_gate.tg_dialogs[self.jid]['users'] and not tg_from in self.xmpp_gate.tg_dialogs[self.jid]['groups'] and not tg_from in self.xmpp_gate.tg_dialogs[self.jid]['supergroups']: - print('Re-init dialog list...') + tg_from = int(mfrom[1:]) + if not tg_from in self.xmpp_gate.tg_dialogs[self.jid]['users'] and not tg_from in self.xmpp_gate.tg_dialogs[self.jid]['groups'] and not tg_from in self.xmpp_gate.tg_dialogs[self.jid]['supergroups']: # new contact appeared self.xmpp_gate.tg_process_dialogs( self.jid ) self.xmpp_gate.send_message( mto=self.jid, mfrom=mfrom + '@' + self.xmpp_gate.config['jid'], mtype='chat', mbody=mbody) @@ -279,7 +263,7 @@ class TelegramGateClient(TelegramClient): if message.fwd_from.from_id: # От пользователя usr = self._get_user_information(message.fwd_from.from_id) - fwd_from = display_tg_name(usr.first_name, usr.last_name) + fwd_from = display_tg_name(usr) if message.fwd_from.channel_id: # От канала fwd_from = 'Channel {}'.format(message.fwd_from.channel_id) @@ -296,7 +280,6 @@ class TelegramGateClient(TelegramClient): :return: """ msg = '' - # print(var_dump(media)) if type(media) is MessageMediaDocument: # Документ или замаскированная сущность attributes = media.document.attributes @@ -378,7 +361,7 @@ class TelegramGateClient(TelegramClient): msg = '' usr = self._get_user_information(message.from_id) - nickname = display_tg_name(usr.first_name, usr.last_name) + nickname = display_tg_name(usr) # supergroup created # if type(message.action) is MessageActionChannelCreate: @@ -393,19 +376,19 @@ class TelegramGateClient(TelegramClient): added_users = [] for user_id in message.action.users: usr = self._get_user_information(user_id) - added_users.append(display_tg_name(usr.first_name, usr.last_name)) + added_users.append(display_tg_name(usr)) msg = 'User [{}] has just invited [{}]'.format(nickname, ','.join(added_users)) # user exit # elif type(message.action) is MessageActionChatDeleteUser: usr = self._get_user_information(message.action.user_id) - msg = 'User [{}] has just left the room'.format(display_tg_name(usr.first_name, usr.last_name)) + msg = 'User [{}] has just left the room'.format(display_tg_name(usr)) # user joined # elif type(message.action) is MessageActionChatJoinedByLink: usr = self._get_user_information(message.action.user_id) - msg = 'User [{}] joined the room'.format(display_tg_name(usr.first_name, usr.last_name)) + msg = 'User [{}] joined the room'.format(display_tg_name(usr)) # chat name modified # elif type(message.action) is MessageActionChatEditTitle: @@ -418,7 +401,7 @@ class TelegramGateClient(TelegramClient): if len(message_req.messages) > 0: pinned_message = message_req.messages[0].message pinned_from = self._get_user_information(message_req.messages[0].from_id) - msg = 'User [{}] pinned message: [{}]: {}'.format(nickname, display_tg_name(pinned_from.first_name, pinned_from.last_name), pinned_message) + msg = 'User [{}] pinned message: [{}]: {}'.format(nickname, display_tg_name(pinned_from), pinned_message) # group converted to supergroup elif type(message.action) in [MessageActionChatMigrateTo, MessageActionChannelMigrateFrom]: @@ -446,3 +429,16 @@ class TelegramGateClient(TelegramClient): print('MTD ::: Media downloaded') except Exception: print(traceback.format_exc()) + + def status_updater_thread(self): + + while True: + try: + if len(self._status_updates) > 0: + for uid, status in self._status_updates.items(): + self.xmpp_gate.send_presence( pto=self.jid, pfrom='u'+str(uid)+'@'+self.xmpp_gate.config['jid'], pshow = status['status'], pstatus = status['message'] ) + except Exception: + print(traceback.format_exc()) + + self._status_updates = dict() + time.sleep( self.xmpp_gate.accounts[self.jid]['status_update_interval']) diff --git a/xmpp_tg/utils.py b/xmpp_tg/utils.py index 04f41f4..ba640b4 100644 --- a/xmpp_tg/utils.py +++ b/xmpp_tg/utils.py @@ -6,23 +6,35 @@ import types from datetime import datetime -def display_tg_name(first_name, last_name): - if first_name and last_name: - return '{} {}'.format(first_name, last_name) - elif first_name: - return first_name - elif last_name: - return last_name +def display_tg_name(peer): + if hasattr(peer,'title') and hasattr(peer,'broadcast') and peer.broadcast: # channel + return '[C] ' + peer.title + elif hasattr(peer,'title') and hasattr(peer,'broadcast') and not peer.broadcast: # supergroup + return '[SG] ' + peer.title + elif hasattr(peer,'title'): # normal group + return '[G] ' + peer.title + elif peer.first_name and peer.last_name: # user with first and last name + return '{} {}'.format(peer.first_name, peer.last_name) + elif peer.first_name: # user with firstname only + return peer.first_name + elif peer.last_name: # user with lastname only + return peer.last_name + elif peer.username: # user with username only + return peer.username + else: # no match, unknown contact + return '[Unknown]' + +def get_contact_jid(peer, gatejid): + if peer.id and hasattr(peer,'title') and hasattr(peer, 'broadcast') and peer.broadcast: # channel + return 'c' + str(peer.id) + '@' + gatejid + elif peer.id and hasattr(peer,'title') and hasattr(peer,'broadcast') and not peer.broadcast: # supergroup + return 's' + str(peer.id) + '@' + gatejid + elif peer.id and hasattr(peer,'title'): # normal group + return 'g' + str(peer.id) + '@' + gatejid + elif peer.id and not peer.bot: # it is... user? + return 'u' + str(peer.id) + '@' + gatejid else: - return '[No name]' - - -def make_gate_jid(): - pass - - -def parse_gate_jid(): - pass + return None def var_dump(obj, depth=7, l=""): @@ -60,4 +72,4 @@ def var_dump(obj, depth=7, l=""): objdict[a] = str(e) return name + "{\n" + "\n".join(l + repr(k) + ": " + var_dump(v, depth=depth - 1, l=l + " ") + "," for k, v in - objdict.items()) + "\n" + l + "}" \ No newline at end of file + objdict.items()) + "\n" + l + "}" diff --git a/xmpp_tg/xmpp.py b/xmpp_tg/xmpp.py index 2669745..8b3fa74 100644 --- a/xmpp_tg/xmpp.py +++ b/xmpp_tg/xmpp.py @@ -1,28 +1,30 @@ import sqlite3 import re import sys +import time import sleekxmpp from sleekxmpp.componentxmpp import ComponentXMPP import xml.etree.ElementTree as ET -from telethon.tl.functions.messages import GetDialogsRequest, SendMessageRequest -from telethon.tl.functions.account import UpdateStatusRequest, GetAuthorizationsRequest -from telethon.tl.functions.contacts import DeleteContactRequest -from telethon.tl.functions.channels import JoinChannelRequest, LeaveChannelRequest +from telethon.tl.functions.messages import GetDialogsRequest, SendMessageRequest, ImportChatInviteRequest, GetFullChatRequest, AddChatUserRequest, DeleteChatUserRequest, CreateChatRequest +from telethon.tl.functions.account import UpdateStatusRequest, GetAuthorizationsRequest, UpdateProfileRequest, UpdateUsernameRequest +from telethon.tl.functions.contacts import DeleteContactRequest, BlockRequest, UnblockRequest +from telethon.tl.functions.channels import JoinChannelRequest, LeaveChannelRequest, InviteToChannelRequest, EditBannedRequest, CreateChannelRequest from telethon.tl.types import InputPeerEmpty, InputPeerUser, InputPeerChat, InputPeerChannel from telethon.tl.types import User, Chat, Channel from telethon.tl.types import PeerChannel, PeerChat, PeerUser, Chat, ChatForbidden, Channel, ChannelForbidden from telethon.tl.types import UserStatusOnline, UserStatusRecently, UserStatusOffline from telethon.tl.types import Updates, UpdateShortSentMessage, UpdateMessageID +from telethon.tl.types import ChannelBannedRights from telethon.tl.types.messages import Dialogs, DialogsSlice from telethon.helpers import generate_random_long from telethon.errors import SessionPasswordNeededError from xmpp_tg.mtproto import TelegramGateClient -from xmpp_tg.utils import var_dump, display_tg_name +from xmpp_tg.utils import var_dump, display_tg_name, get_contact_jid import xmpp_tg.monkey # Патчим баги в библиотеках class XMPPTelegram(ComponentXMPP): @@ -40,9 +42,10 @@ class XMPPTelegram(ComponentXMPP): config_dict['port']) self.auto_authorize = True - self.auto_subscribe = True + # self.auto_subscribe = True self.config = config_dict + self.accounts = dict() # personal configuration per JID self.tg_connections = dict() self.tg_phones = dict() self.tg_dialogs = dict() @@ -92,7 +95,7 @@ class XMPPTelegram(ComponentXMPP): """ users = self.db_connection.execute("SELECT * FROM accounts").fetchall() for usr in users: - print('Sending presence...') + self.accounts[usr['jid']] = usr self.send_presence(pto=usr['jid'], pfrom=self.boundjid.bare, ptype='probe') def message(self, iq): @@ -111,15 +114,13 @@ class XMPPTelegram(ComponentXMPP): else: # Пишут в Telegram if jid in self.tg_connections and self.tg_connections[jid].is_user_authorized(): if iq['body'].startswith('!'): # Команда из чата - print('command received') if iq['to'].bare.startswith('u'): self.process_chat_user_command(iq) - elif iq['to'].bare.startswith('g') or iq['to'].bare.startswith('s'): + 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: # Обычное сообщение - print('sent message') tg_id = int(iq['to'].node[1:]) tg_peer = None msg = iq['body'] @@ -140,8 +141,6 @@ class XMPPTelegram(ComponentXMPP): elif iq['to'].bare.startswith('s') or iq['to'].bare.startswith('c'): # Супергруппа tg_peer = InputPeerChannel(tg_id, self.tg_dialogs[jid]['supergroups'][tg_id].access_hash) - print(tg_peer) - if tg_peer: # Отправляем сообщение и получаем новый апдейт result = self.tg_connections[jid].invoke( @@ -163,7 +162,6 @@ class XMPPTelegram(ComponentXMPP): # mbody='[Your MID:{}]'.format(msg_id)) def event_presence_unsub(self, presence): - print('defense') return def event_presence(self, presence): @@ -177,11 +175,11 @@ class XMPPTelegram(ComponentXMPP): # handle "online" to transport: if ptype == 'available' and presence['to'].bare == self.boundjid.bare: - self.handle_online(presence) + self.handle_online(presence, False) # handle online elif ptype == 'subscribe': self.send_presence(pto=presence['from'].bare, pfrom=presence['to'].bare, ptype='subscribed') elif ptype == 'subscribed': - self.send_presence(pto=presence['from'].bare, pfrom=presence['to'].bare, ptype='subscribee') + pass elif ptype == 'unsubscribe': pass elif ptype == 'unsubscribed': @@ -194,7 +192,7 @@ class XMPPTelegram(ComponentXMPP): # self.send_presence(pto=presence['from'], pfrom=presence['to']) pass - def handle_online(self, event): + def handle_online(self, event, sync_roster = True): """ Обработчик события online. Подключается к Telegram при наличии авторизации. :param event: @@ -202,7 +200,7 @@ class XMPPTelegram(ComponentXMPP): """ jid = event['from'].bare to = event['to'].bare - + # maybe if i'll ignore it — it will go ahead if to != self.boundjid.bare: return @@ -218,7 +216,7 @@ class XMPPTelegram(ComponentXMPP): self.tg_connections[jid].invoke(UpdateStatusRequest(offline=False)) self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='online', pstatus='connected') - self.tg_process_dialogs(jid) + self.tg_process_dialogs(jid, sync_roster) # do not sync roster if we already have connection! def handle_offline(self, event): @@ -230,7 +228,7 @@ class XMPPTelegram(ComponentXMPP): jid = event['from'].bare # keep telegram online ? - if self.config['xmpp_keep_online']: + if self.accounts[jid]['keep_online']: return if jid in self.tg_connections: @@ -258,39 +256,38 @@ class XMPPTelegram(ComponentXMPP): jid = iq['from'].bare if parsed[0] == '!help': - self.gate_reply_message(iq, 'Available command:\n\n' + self.gate_reply_message(iq, '=== Available gateway commands ===:\n\n' '!help - Displays this text\n' '!login +123456789 - Initiates Telegram session\n' '!code 12345 - Entering one-time code during auth\n' - #'!password abc123 - Entering password during two-factor auth\n' - '!list_sessions - List all created sessions at Telegram servers\n' + '!password abc123 - Entering password during two-factor auth\n' + '!configure - Configure transport settings\n' + #'!list_sessions - List all created sessions at Telegram servers\n' #'!delete_session 123 - Delete session\n' '!logout - Deletes current Telegram session at gate\n' '!reload_dialogs - Reloads dialogs list from Telegram\n\n' '!add - Find and add Telegram contact. Any formats accepted (nickname or t.me link)\n\n' - '!del - Removes Telegram contact or leaves a chat. Any formats accepted (nickname or t.me link) \n\n' - #'!create_group - Initiates group creation\n' - #'!create_channel - Initiates channel creation\n\n' - #'!change_name first last - Changes your name in Telegram\n' - #'!change_username username - Changes your @username in Telegram\n' - # '!blocked_users_list\n' - # '!blocked_users_add\n' - # '!blocked_users_remove\n' - # '!last_seen_privacy_status\n' - # '!last_seen_privacy_set\n' - # '!last_seen_privacy_never_add\n' - # '!last_seen_privacy_never_remove\n' - # '!last_seen_privacy_always_add\n' - # '!last_seen_privacy_always_remove\n' - # '!group_invite_settings_status\n' - # '!group_invite_settings_set\n' - # '!group_invite_settings_add\n' - # '!group_invite_settings_remove\n' - # '!group_invite_settings_add\n' - # '!group_invite_settings_remove\n' - # '!account_selfdestruct_setting_status\n' - # '!account_selfdestruct_setting_set\n' + '!join - Join Telegram conference via invite link \n\n' + '!group GroupName @InviteContact - Create a normal group\n' + '!supergroup SupergroupName - Create a supergroup\n' + '!channel ChannelName - Create a channel\n\n' + '!name first last - Change your name in Telegram\n' + '!about text - Change about text in Telegram\n' + '!username - Changes your @username in Telegram\n' ) + elif parsed[0] == '!configure': + config_exclude = ['jid', 'tg_phone'] + if len(parsed) > 2 and parsed[1] not in config_exclude: + self.db_connection.execute("update accounts set {} = ? where jid = ?".format(parsed[1]), (parsed[2],jid,) ) + self.accounts[jid] = self.db_connection.execute("SELECT * FROM accounts where jid = ?", (jid,) ).fetchone() + + message = "=== Your current configuration ===\n\n" + for param, value in self.accounts[jid].items(): + message = message + "<%s>: %s" % (param, value) + "\n" + message = message + "\nTo modify some option, please, send !configure param value" + self.gate_reply_message(iq, message) + + elif parsed[0] == '!login': # -------------------------------------------------- self.gate_reply_message(iq, 'Please wait...') self.spawn_tg_client(jid, parsed[1]) @@ -320,8 +317,10 @@ class XMPPTelegram(ComponentXMPP): 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, 'Authentication successful. Initiating Telegram...') + self.db_connection.execute("INSERT INTO accounts(jid, tg_phone) VALUES(?, ?)", (jid, self.tg_phones[jid],)) + self.accounts[jid] = self.db_connection.execute("SELECT * FROM accounts where jid = ?", (jid,) ).fetchone() self.init_tg(jid) - self.db_connection.execute("INSERT INTO accounts VALUES(?, ?)", (jid, self.tg_phones[jid],)) + else: self.gate_reply_message(iq, 'Authentication failed.') else: @@ -332,7 +331,6 @@ class XMPPTelegram(ComponentXMPP): return sessions = self.tg_connections[jid].invoke(GetAuthorizationsRequest()) - print(sessions.__dict__) elif parsed[0] == '!reload_dialogs': if not self.tg_connections[jid].is_user_authorized(): self.gate_reply_message(iq, 'Error.') @@ -356,88 +354,124 @@ class XMPPTelegram(ComponentXMPP): return self.tg_process_dialogs(jid) + + elif parsed[0] == '!join': # join chat by link + link = parsed[1].split('/') # https://t.me/joinchat/HrCmckx_SkMbSGFLhXCvSg + self.tg_connections[jid].invoke(ImportChatInviteRequest(link[4])) + time.sleep(1) + self.tg_process_dialogs(jid) + + elif parsed[0] == '!group' and len(parsed) >= 3: # create new group + # group name? # + groupname = parsed[1] + + # group users? # + groupuser = self.tg_connections[jid].get_entity(parsed[2]) - elif parsed[0] == '!del': # add user - result = self.tg_connections[jid].get_entity(parsed[1]) - if type(result) == User: - tg_peer = InputPeerUser( result.id, result.access_hash ) - req = self.tg_connections[jid].invoke( DeleteContactRequest(tg_peer) ) - print(req) - prefix = 'u' - elif type(result) == Channel: - tg_peer = InputPeerChannel( result.id, result.access_hash ) - self.tg_connections[jid].invoke(LeaveChannelRequest( InputPeerChannel(result.id, result.access_hash) ) ) - prefix = 'c' if result.broadcast else 's' - else: - self.gate_reply_message(iq, 'Sorry, nothing found.') - return - - presence_from = prefix + str(result.id) + '@' + self.boundjid.bare - self.send_presence(pto=jid, pfrom=presence_from, ptype='unavailable') - self.send_presence(pto=jid, pfrom=presence_from, ptype='unsubscribed') - self.send_presence(pto=jid, pfrom=presence_from, ptype='unsubscribe') - - + # we re ready to make group + self.tg_connections[jid].invoke(CreateChatRequest([groupusers], groupname)) + self.tg_process_dialogs(jid) + + elif parsed[0] == '!channel' and len(parsed) >= 2: # create new channel + groupname = parsed[1] + self.tg_connections[jid].invoke(CreateChannelRequest(groupname, groupname, broadcast = True)) + self.tg_process_dialogs(jid) + + elif parsed[0] == '!supergroup' and len(parsed) >= 2: # create new channel + groupname = parsed[1] + self.tg_connections[jid].invoke(CreateChannelRequest(groupname, groupname, megagroup = True)) + self.tg_process_dialogs(jid) + + elif parsed[0] == '!username' and len(parsed) >= 2: # create new channel + username = parsed[1] + self.tg_connections[jid].invoke(UpdateUsernameRequest(username)) + + elif parsed[0] == '!name' and len(parsed) >= 2: # create new channel + firstname = parsed[1] + lastname = parsed[2] if len(parsed) > 2 else None + self.tg_connections[jid].invoke(UpdateProfileRequest(first_name = firstname, last_name = lastname)) + + elif parsed[0] == '!about' and len(parsed) >= 2: # create new channel + about = iq['body'][7:] + self.tg_connections[jid].invoke(UpdateProfileRequest(about = about)) + else: # -------------------------------------------------- self.gate_reply_message(iq, 'Unknown command. Try !help for list all commands.') def process_chat_user_command(self, iq): - parsed = [] + parsed = iq['body'].split(' ') + jid = iq['from'].bare - if parsed[0] == '!search': - pass - elif parsed[0] == '!get_history': - pass - elif parsed[0] == '!forward_messages': - pass - elif parsed[0] == '!delete_messages': - pass - elif parsed[0] == '!block_status': - pass - elif parsed[0] == '!block_set': - pass - elif parsed[0] == '!block_unser': - pass - elif parsed[0] == '!clear_history': - pass - elif parsed[0] == '!delete_conversation': - pass - elif parsed[0] == '!help': - pass + if parsed[0] == '!help': + self.gate_reply_message(iq, '=== Available dialog commands ===:\n\n' + '!help - Displays this text\n' + '!block - Blacklists current user\n' + '!unblock - Unblacklists current user\n' + ) + elif parsed[0] == '!block': + tg_id = int(iq['to'].node[1:]) + nickname = display_tg_name(self.tg_dialogs[jid]['users'][tg_id]) + self.tg_connections[jid].invoke(BlockRequest( InputPeerUser(tg_id, self.tg_dialogs[jid]['users'][tg_id].access_hash) ) ) + self.gate_reply_message(iq, 'User %s blacklisted!' % nickname) + + elif parsed[0] == '!unblock': + tg_id = int(iq['to'].node[1:]) + nickname = display_tg_name(self.tg_dialogs[jid]['users'][tg_id]) + self.tg_connections[jid].invoke(UnblockRequest( InputPeerUser(tg_id, self.tg_dialogs[jid]['users'][tg_id].access_hash) ) ) + self.gate_reply_message(iq, 'User %s unblacklisted!' % nickname) + + def process_chat_group_command(self, iq): - parsed = [] + parsed = iq['body'].split(' ') + jid = iq['from'].bare - if parsed[0] == '!search': - pass - elif parsed[0] == '!get_history': - pass - elif parsed[0] == '!forward_messages': - pass - elif parsed[0] == '!delete_messages': - pass - elif parsed[0] == '!pin_message': - pass - elif parsed[0] == '!unpin_message': - pass - elif parsed[0] == '!leave_group': - pass - elif parsed[0] == '!add_members': - pass - elif parsed[0] == '!bans_list': - pass - elif parsed[0] == '!ban_user': - pass - elif parsed[0] == '!unban_user': - pass - elif parsed[0] == '!restrict_user': - pass - elif parsed[0] == '!unrestrict_user': - pass - elif parsed[0] == '!get_recent_actions': - pass - elif parsed[0] == '!get_recent_actions': - pass + if parsed[0] == '!help': + self.gate_reply_message(iq, '=== Available chat commands ===:\n\n' + '!help - Displays this text\n' + '!leave - Leaves current group or supergroup\n' + '!invite - Invites user to group\n' + '!kick - Kicks user to group\n' + ) + elif parsed[0] == '!leave': + tg_id = int(iq['to'].node[1:]) + if tg_id in self.tg_dialogs[jid]['supergroups']: + self.tg_connections[jid].invoke(LeaveChannelRequest( InputPeerChannel(tg_id, self.tg_dialogs[jid]['supergroups'][tg_id].access_hash) ) ) + c_jid = get_contact_jid(self.tg_dialogs[jid]['supergroups'][tg_id], self.boundjid.bare) + self.send_presence(pto = jid, pfrom = c_jid, ptype = 'unavailable') + self.send_presence(pto = jid, pfrom = c_jid, ptype = 'unsubscribed') + self.send_presence(pto = jid, pfrom = c_jid, ptype = 'unsubscribe') + if tg_id in self.tg_dialogs[jid]['groups']: + self.tg_connections[jid].invoke( DeleteChatUserRequest(tg_id, self.tg_connections[jid].me) ) + c_jid = get_contact_jid(self.tg_dialogs[jid]['groups'][tg_id], self.boundjid.bare) + self.send_presence(pto = jid, pfrom = c_jid, ptype = 'unavailable') + self.send_presence(pto = jid, pfrom = c_jid, ptype = 'unsubscribed') + self.send_presence(pto = jid, pfrom = c_jid, ptype = 'unsubscribe') + + elif parsed[0] == '!invite': + tg_id = int(iq['to'].node[1:]) + if tg_id in self.tg_dialogs[jid]['supergroups']: + invited_user = self.tg_connections[jid].get_entity(parsed[1]) + if type(invited_user) == User: + self.tg_connections[jid].invoke(EditBannedRequest( InputPeerChannel(tg_id, self.tg_dialogs[jid]['supergroups'][tg_id].access_hash), invited_user, ChannelBannedRights(until_date=None,view_messages=False) ) ) + self.tg_connections[jid].invoke(InviteToChannelRequest( InputPeerChannel(tg_id, self.tg_dialogs[jid]['supergroups'][tg_id].access_hash), [invited_user] ) ) + if tg_id in self.tg_dialogs[jid]['groups']: + invited_user = self.tg_connections[jid].get_entity(parsed[1]) + if type(invited_user) == User: + self.tg_connections[jid].invoke( AddChatUserRequest(tg_id, invited_user, 0) ) + + elif parsed[0] == '!kick': + 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: + 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: + self.tg_connections[jid].invoke( DeleteChatUserRequest(tg_id, kicked_user) ) + + def spawn_tg_client(self, jid, phone): """ @@ -446,8 +480,9 @@ class XMPPTelegram(ComponentXMPP): :param phone: :return: """ - client = TelegramGateClient('a_'+phone, int(self.config['tg_api_id']), self.config['tg_api_hash'], - self, jid, phone) + client = TelegramGateClient('a_'+phone, int(self.config['tg_api_id']), self.config['tg_api_hash'], self, jid, phone) + if 'tg_server_ip' in self.config and 'tg_server_dc' in self.config and 'tg_server_port' in self.config: + client.session.set_dc(self.config['tg_server_dc'], self.config['tg_server_ip'], self.config['tg_server_port']) client.connect() self.tg_connections[jid] = client @@ -498,9 +533,8 @@ class XMPPTelegram(ComponentXMPP): self.send(presence) - def tg_process_dialogs(self, jid): + def tg_process_dialogs(self, jid, sync_roster = True): - print('! -- Process Dialogs -- !') # Инициализируем словари для диалогов self.tg_dialogs[jid] = dict() self.tg_dialogs[jid]['raw'] = list() @@ -534,15 +568,15 @@ class XMPPTelegram(ComponentXMPP): if type(dlg.peer) is PeerUser: usr = self.tg_dialogs[jid]['users'][dlg.peer.user_id] vcard = self.plugin['xep_0054'].make_vcard() - u_jid = 'u' + str(usr.id) + '@' + self.boundjid.bare + u_jid = get_contact_jid(usr, self.boundjid.bare) if usr.deleted: rostername = "Deleted Account" vcard['FN'] = 'Deleted account' vcard['DESC'] = 'This user no longer exists in Telegram' else: - rostername = display_tg_name(usr.first_name, usr.last_name) - vcard['FN'] = display_tg_name(usr.first_name, usr.last_name) + rostername = display_tg_name(usr) + vcard['FN'] = display_tg_name(usr) if usr.first_name: vcard['N']['GIVEN'] = usr.first_name if usr.last_name: @@ -559,62 +593,50 @@ class XMPPTelegram(ComponentXMPP): self.plugin['xep_0054'].publish_vcard(jid=u_jid, vcard=vcard) self.plugin['xep_0172'].publish_nick(nick=vcard['FN'], ifrom=u_jid) - # self.send_presence(pto=jid, pfrom=u_jid, ptype='subscribe') self.contact_list[jid][u_jid] = rostername if usr.bot: - self.send_presence(pto=jid, pfrom=u_jid, pstatus='Bot') + self.send_presence(pto=jid, pfrom=u_jid, pshow = 'chat', pstatus='Bot') else: if type(usr.status) is UserStatusOnline: - self.send_presence(pto=jid, pfrom=u_jid) + 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') elif type(usr.status) is UserStatusOffline: - self.send_presence( - pto=jid, - pfrom=u_jid, - ptype='xa', - pstatus=usr.status.was_online.strftime('Last seen at %H:%M %d/%m/%Y') - ) + self.send_presence(pto=jid, pfrom=u_jid, pshow='xa', pstatus=usr.status.was_online.strftime('Last seen at %H:%M %d/%m/%Y') ) else: - self.send_presence(pto=jid, pfrom=u_jid, ptype='unavailable', - pstatus='Last seen a long time ago') + self.send_presence(pto=jid, pfrom=u_jid, pshow='dnd', pstatus='Last seen a long time ago') if type(dlg.peer) in [PeerChat, PeerChannel]: - g_type = '' cht = None if type(dlg.peer) is PeerChat: # Старая группа - cht = self.tg_dialogs[jid]['groups'][dlg.peer.chat_id] - c_jid = 'g' + str(cht.id) + '@' + self.boundjid.bare - g_type = 'G' + 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: # Супергруппа cht = self.tg_dialogs[jid]['supergroups'][dlg.peer.channel_id] - if cht.broadcast: - g_type = 'C' - c_jid = 'c' + str(cht.id) + '@' + self.boundjid.bare - else: - g_type = 'SG' - c_jid = 's' + str(cht.id) + '@' + self.boundjid.bare - rostername = '[{}] {}'.format(g_type, cht.title) + if cht and cht.id: + rostername = display_tg_name(cht) + c_jid = get_contact_jid(cht, self.boundjid.bare) - vcard = self.plugin['xep_0054'].make_vcard() - vcard['FN'] = '[{}] {}'.format(g_type, cht.title) - vcard['NICKNAME'] = vcard['FN'] - vcard['JABBERID'] = c_jid - self.plugin['xep_0054'].publish_vcard(jid=c_jid, vcard=vcard) - self.plugin['xep_0172'].publish_nick(nick=vcard['FN'], ifrom=c_jid) + vcard = self.plugin['xep_0054'].make_vcard() + vcard['FN'] = rostername + vcard['NICKNAME'] = rostername + vcard['JABBERID'] = c_jid + self.plugin['xep_0054'].publish_vcard(jid=c_jid, vcard=vcard) + self.plugin['xep_0172'].publish_nick(nick=vcard['FN'], ifrom=c_jid) - self.contact_list[jid][c_jid] = rostername - #self.send_presence(pto=jid, pfrom=c_jid, ptype='subscribe') - self.send_presence(pto=jid, pfrom=c_jid) + self.contact_list[jid][c_jid] = rostername + self.send_presence(pto=jid, pfrom=c_jid, pshow = 'chat', pstatus = cht.title) if len(dlgs.dialogs) == 0: # Если все диалоги получены - прерываем цикл - if self.config['xmpp_use_roster_exchange']: + 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]) - else: + elif sync_roster: self.roster_fill(jid, self.contact_list[jid]) break else: # Иначе строим оффсеты @@ -632,7 +654,7 @@ class XMPPTelegram(ComponentXMPP): elif type(last_peer) in [Channel, ChannelForbidden]: # Супергруппа access_hash = self.tg_dialogs[jid]['supergroups'][last_peer.channel_id].access_hash last_peer = InputPeerChannel(last_peer.channel_id, access_hash) - + def tg_process_unread_messages(self): pass @@ -659,12 +681,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)" - ")") - - # conn.execute("CREATE TABLE IF NOT EXISTS roster(" - # "") + 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)") return conn