import re, sys, os, io, sqlite3, hashlib, time, datetime import xml.etree.ElementTree as ET import logging, traceback, pprint from xmpp_tg.mtproto import TelegramGateClient from xmpp_tg.utils import var_dump, display_tg_name, get_contact_jid, localtime # modified by adnidor class WrongNumberOfArgsError(Exception): pass class CommandHandler(): _on_connect = lambda: None _helptext = "Available Commands:" def _unknown_command_handler(self, *args, **kwargs): return "Unknown command, for a list send !help" def _min_args(self, num_args): if len(self.arguments) < num_args: raise WrongNumberOfArgsError("!{} needs at least {} arguments".format(self._command, num_args)) def __init__(self, msg): self._command = msg["body"].split(" ")[0][1:] self._handler = getattr(self, self._command, self._unknown_command_handler) self.type = "groupchat" if msg["type"] == "groupchat" else "chat" self.sender = msg["from"] self.jid = msg["from"].bare self.replyto = self.sender.full if self.type == "chat" else self.sender.bare self.arguments = msg["body"].split(" ")[1:] self.msg = msg def _update(self, text): xmpp.send_message(mto=self.replyto, mtype=self.type, mbody=text) def debug(self, *args, **kwargs): """nolist Show debug info""" return pprint.pformat(self.__dict__) def help(self, *args, **kwargs): """List available commands""" #taken from https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation def trim(docstring): if not docstring: return '' # Convert tabs to spaces (following the normal Python rules) # and split into a list of lines: lines = docstring.expandtabs().splitlines() # Determine minimum indentation (first line doesn't count): indent = 500 for line in lines[1:]: stripped = line.lstrip() if stripped: indent = min(indent, len(line) - len(stripped)) # Remove indentation (first line is special): trimmed = [lines[0].strip()] if indent < 500: for line in lines[1:]: trimmed.append(line[indent:].rstrip()) # Strip off trailing and leading blank lines: while trimmed and not trimmed[-1]: trimmed.pop() while trimmed and not trimmed[0]: trimmed.pop(0) # Return a single string: return '\n'.join(trimmed) if len(self.arguments) == 0: methods = [func for func in dir(self) if not func.startswith("_") and callable(getattr(self, func)) and not isinstance(func, Exception)] reply = self._helptext for method in methods: docstring = getattr(self, method).__doc__ if docstring is None: docstring = "No description available" if docstring.startswith("nolist "): continue reply += "\n!{} - {}".format(method, docstring.split("\n")[0]) return reply else: method = getattr(self,self.arguments[0]) reply = trim(method.__doc__) return reply class GateCommandHandler(CommandHandler): _helptext = "Available Gateway commands:" def configure(hndl, self): """Get config/set config options""" config_exclude = ['jid', 'tg_phone'] option = hndl.arguments[0] if len(hndl.arguments) >= 1 else None value = hndl.arguments[1] if len(hndl.arguments) == 2 else None if value is not None and option not in config_exclude: self.db_connection.execute("update accounts set {} = ? where jid = ?".format(option), (value,hndl.jid,) ) self.accounts[hndl.jid] = self.db_connection.execute("SELECT * FROM accounts where jid = ?", (hndl.jid,) ).fetchone() message = "=== Your current configuration ===\n\n" for param, value in self.accounts[hndl.jid].items(): message = message + "<%s>: %s" % (param, value) + "\n" message = message + "\nTo modify some option, please, send !configure param value" return message def login(hndl, self): #1 arg """Initiates Telegram session Usage: !login """ hndl._min_args(1) phone_no = hndl.arguments[0] hndl._update("Please wait...") self.spawn_tg_client(hndl.jid, phone_no) if self.tg_connections[hndl.jid].is_user_authorized(): self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='online', pstatus='connected') return "You are already authenticated in Telegram" else: # remove old sessions for this JID # self.db_connection.execute("DELETE from accounts where jid = ?", (hndl.jid, ) ) self.tg_connections[hndl.jid].send_code_request(phone_no) self._update('Gate is connected. Telegram should send SMS message to you.') return 'Please enter one-time code via !code 12345.' def code(hndl, self): """nolist Enter confirmation code""" hndl._min_args(1) code = hndl.arguments[0] jid = hndl.jid if not self.tg_connections[jid].is_user_authorized(): try: hndl._update('Trying authenticate...') self.tg_connections[jid].sign_in(self.tg_phones[jid], code) except SessionPasswordNeededError: self.gate_reply_message(iq, 'Two-factor authentication detected.') self.gate_reply_message(iq, 'Please enter your password via !password abc123.') return 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) else: return 'Authentication failed.' else: return 'You are already authenticated. Please use !logout before new login.' def password(hndl, self): """nolist Enter password""" hndl._min_args(1) password = hndl.arguments[1] jid = self.jid if not self.tg_connections[jid].is_user_authorized(): self.gate_reply_message(iq, 'Checking password...') self.tg_connections[jid].sign_in(password=password) 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) else: self.gate_reply_message(iq, 'Authentication failed.') else: self.gate_reply_message(iq, 'You are already authenticated. Please use !logout before new login.') def reload_dialogs(hndl, self): """Reload Telegram dialogs""" if not self.tg_connections[hndl.jid].is_user_authorized(): return "Error" self.tg_process_dialogs(hndl.jid) return "Dialogs reloadad" def logout(hndl, self): """Deletes current Telegram session at Gateway""" self.tg_connections[hndl.jid].log_out() self.db_connection.execute("DELETE FROM accounts WHERE jid = ?", (hndl.jid,)) return 'Your Telegram session was deleted' def add(hndl, self): #1 arg """Add contact by nickname or t.me link""" hndl._min_args(1) argument = hndl.arguments[0] jid = hndl.jid result = self.tg_connections[jid].get_entity(argument) if type(result) == User: tg_peer = InputPeerUser( result.id, result.access_hash ) result = self.tg_connections[jid].invoke( SendMessageRequest(tg_peer, 'Hello! I just want to add you in my contact list.', generate_random_long() ) ) elif type(result) == Channel: tg_peer = InputPeerChannel( result.id, result.access_hash ) self.tg_connections[jid].invoke(JoinChannelRequest( InputPeerChannel(result.id, result.access_hash) ) ) else: self.gate_reply_message(iq, 'Sorry, nothing found.') return self.tg_process_dialogs(jid) def join(hndl, self): #1 arg """Join conference via invite link""" hndl._min_args(1) argument = hndl.arguments[0] jid = hndl.jid link = argument.split('/') # https://t.me/joinchat/HrCmckx_SkMbSGFLhXCvSg self.tg_connections[jid].invoke(ImportChatInviteRequest(link[4])) time.sleep(1) self.tg_process_dialogs(jid) def group(hndl, self): #2 args """Creat a Group""" hndl._min_args(2) # group name? # groupname = parsed[1] # group users? # groupuser = self.tg_connections[jid].get_entity(parsed[2]) # we re ready to make group self.tg_connections[jid].invoke(CreateChatRequest([groupuser], groupname)) self.tg_process_dialogs(jid) def supergroup(hndl, self): #1 arg """Create a channel""" hndl._min_args(1) groupname = hndl.arguments[0] self.tg_connections[hndl.jid].invoke(CreateChannelRequest(groupname, groupname, megagroup = True)) self.tg_process_dialogs(hndl.jid) channel = supergroup def username(hndl, self): #1 arg """Change your Telegram nickname""" hndl._min_args(1) username = hndl.arguments[0] self.tg_connections[hndl.jid].invoke(UpdateUsernameRequest(username)) def name(hndl, self): #1 or 2 args """Change your Telegram display name""" hndl._min_args(1) firstname = hndl.arguments[0] lastname = hndl.arguments[1] if len(hndl.arguments) > 1 else None self.tg_connections[hndl.jid].invoke(UpdateProfileRequest(first_name = firstname, last_name = lastname)) def about(hndl, self): #>0 args """Change your Telegram 'about' text""" hndl._min_args(1) about = hndl.msg['body'][7:] self.tg_connections[hndl.jid].invoke(UpdateProfileRequest(about = about)) def import_contact(hndl, self): #2 args """Add contact by phone number""" hndl._min_args(2) phone = hndl.arguments[0] firstname = hndl.arguments[1] lastname = hndl.arguments[2] if len(hndl.arguments) > 2 else None contact = InputPhoneContact(client_id=generate_random_long(), phone=phone, first_name=firstname, last_name=lastname) self.tg_connections[hndl.jid].invoke(ImportContactsRequest([contact])) self.tg_process_dialogs(jid) def roster(hndl, self): """Send Telegram contact list back, with JIDs""" response = "Telegram chats:\n" for jid,tid in self.contact_list[hndl.jid].items(): response += "{}: {}\n".format(tid, jid) return response def oldhelp(hndl, self): """nolist Old !help message""" return ('=== 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' '!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' '!join - Join Telegram conference via invite link \n\n' '!import phone firstname lastname - Add Telegram contact with phone number \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\n' '!roster - Lists yout TG roster\n') class ChatCommandHandler(CommandHandler): _helptext = "Available Userchat commands:" def __init__(self, msg): super(ChatCommandHandler, self).__init__(msg) if self._command.startswith("s/"): self._handler = self._replace self.tg_id = int(msg['to'].node[1:]) def block(hndl, self): """Block this user""" nickname = display_tg_name(self.tg_dialogs[hndl.jid]['users'][hndl.tg_id]) self.tg_connections[hndl.jid].invoke(BlockRequest( InputPeerUser(hndl.tg_id, self.tg_dialogs[jid]['users'][hndl.tg_id].access_hash) ) ) self.gate_reply_message(iq, 'User %s blacklisted!' % nickname) def unblock(hndl, self): """Unblock this user""" nickname = display_tg_name(self.tg_dialogs[hndl.jid]['users'][hndl.tg_id]) self.tg_connections[hndl.jid].invoke(UnblockRequest( InputPeerUser(hndl.tg_id, self.tg_dialogs[jid]['users'][hndl.tg_id].access_hash) ) ) self.gate_reply_message(iq, 'User %s unblacklisted!' % nickname) def remove(hndl, self): """Remove User from Telegram contact list""" peer = InputPeerUser(hndl.tg_id, self.tg_dialogs[hndl.jid]['users'][hndl.tg_id].access_hash) c_jid = get_contact_jid(self.tg_dialogs[hndl.jid]['users'][hndl.tg_id], self.boundjid.bare) self.tg_connections[hndl.jid].invoke( DeleteContactRequest(peer) ) self.tg_connections[hndl.jid].invoke( DeleteHistoryRequest( peer, max_id = 0, just_clear = None ) ) self.send_presence(pto = hndl.jid, pfrom = c_jid, ptype = 'unavailable') self.send_presence(pto = hndl.jid, pfrom = c_jid, ptype = 'unsubscribed') self.send_presence(pto = hndl.jid, pfrom = c_jid, ptype = 'unsubscribe') def _replace(hndl, self): peer = InputPeerUser(hdnl.tg_id, self.tg_dialogs[hndl.jid]['users'][hndl.tg_id].access_hash) msg_id, edited = self.edit_message(hdnl.jid, hdnl.tg_id, hndl.msg['body']) if not edited: return # and send it if edited != '' and edited != ' ': self.tg_dialogs[hndl.jid]['messages'][hndl.tg_id]["body"] = edited self.tg_connections[hndl.jid].invoke( EditMessageRequest(peer, msg_id, message = edited) ) else: del(self.tg_dialogs[hndl.jid]['messages'][hndl.tg_id]) self.tg_connections[hndl.jid].invoke( DeleteMessagesRequest([msg_id], revoke = True) ) class GroupchatCommandHandler(CommandHandler): _helptext = "Available Groupchat commands:" def __init__(self, msg): super(GroupchatCommandHandler, self).__init__(msg) if self._command.startswith("s/"): self._handler = self._replace def leave(hndl, self): """Leave group or channel""" tg_id = int(hndl.msg['to'].node[1:]) if tg_id in self.tg_dialogs[hndl.jid]['supergroups']: peer = InputPeerChannel(tg_id, self.tg_dialogs[hndl.jid]['supergroups'][tg_id].access_hash) self.tg_connections[hndl.jid].invoke(LeaveChannelRequest(peer)) self.tg_connections[hndl.jid].invoke(DeleteHistoryRequest(peer, max_id = 0, just_clear = None)) c_jid = get_contact_jid(self.tg_dialogs[hndl.jid]['supergroups'][tg_id], self.boundjid.bare) self.send_presence(pto =hndl.jid, pfrom = c_jid, ptype ='unavailable') self.send_presence(pto =hndl.jid, pfrom = c_jid, ptype ='unsubscribed') self.send_presence(pto =hndl.jid, pfrom = c_jid, ptype ='unsubscribe') if tg_id in self.tg_dialogs[hndl.jid]['groups']: self.tg_connections[hndl.jid].invoke(DeleteChatUserRequest(tg_id, self.tg_connections[hndl.jid].me)) self.tg_connections[hndl.jid].invoke(DeleteHistoryRequest(InputPeerChat(tg_id), max_id = 0, just_clear = None)) c_jid = get_contact_jid(self.tg_dialogs[hndl.jid]['groups'][tg_id], self.boundjid.bare) self.send_presence(pto =hndl.jid, pfrom = c_jid, ptype ='unavailable') self.send_presence(pto =hndl.jid, pfrom = c_jid, ptype ='unsubscribed') self.send_presence(pto =hndl.jid, pfrom = c_jid, ptype ='unsubscribe') def invite(hndl, self): """Invite user to group""" tg_id = int(hndl.msg['to'].node[1:]) if tg_id in self.tg_dialogs[hndl.jid]['supergroups']: invited_user = self.tg_connections[[hndl.jid]].get_entity(parsed[1]) if type(invited_user) == User: self.tg_connections[[hndl.jid]].invoke(EditBannedRequest(InputPeerChannel(tg_id, self.tg_dialogs[[hndl.jid]]['supergroups'][tg_id].access_hash), invited_user, ChannelBannedRights(until_date=None, view_messages=False))) self.tg_connections[[hndl.jid]].invoke(InviteToChannelRequest(InputPeerChannel(tg_id, self.tg_dialogs[[hndl.jid]]['supergroups'][tg_id].access_hash), [invited_user])) if tg_id in self.tg_dialogs[[hndl.jid]]['groups']: invited_user = self.tg_connections[[hndl.jid]].get_entity(parsed[1]) if type(invited_user) == User: self.tg_connections[[hndl.jid]].invoke(AddChatUserRequest(tg_id, invited_user, 0)) def kick(hndl, self): """Kick user""" tg_id = int(hndl.msg['to'].node[1:]) if tg_id in self.tg_dialogs[hndl.jid]['supergroups']: kicked_user = self.tg_connections[hndl.jid].get_entity(parsed[1]) if type(kicked_user) == User: self.tg_connections[hndl.jid].invoke(EditBannedRequest(InputPeerChannel(tg_id, self.tg_dialogs[hndl.jid]['supergroups'][tg_id].access_hash), kicked_user, ChannelBannedRights(until_date=None, view_messages=True))) if tg_id in self.tg_dialogs[hndl.jid]['groups']: kicked_user = self.tg_connections[hndl.jid].get_entity(parsed[1]) if type(kicked_user) == User: self.tg_connections[hndl.jid].invoke(DeleteChatUserRequest(tg_id, kicked_user)) def _replace(hndl, self): tg_id = int(hndl.msg['to'].node[1:]) peer = InputPeerChannel(tg_id, self.tg_dialogs[hndl.jid]['supergroups'][tg_id].access_hash) if tg_id in self.tg_dialogs[ hndl.jid]['supergroups'] else InputPeerChat(tg_id) msg_id, edited = self.edit_message(hndl.jid, tg_id, iq['body']) if not edited: return # and send it if edited != '' and edited != ' ': self.tg_dialogs[hndl.jid]['messages'][tg_id]["body"] = edited self.tg_connections[hndl.jid].invoke(EditMessageRequest(peer, msg_id, message = edited)) else: del(self.tg_dialogs[hndl.jid]['messages'][tg_id]) if isinstance(peer, InputPeerChannel): self.tg_connections[hndl.jid].invoke(DeleteMessagesChannel(peer, [msg_id])) else: self.tg_connections[hndl.jid].invoke(DeleteMessagesRequest([msg_id], revoke = True))