begin message handling rewrite

This commit is contained in:
2019-02-26 22:38:13 +01:00
parent eb66822e2d
commit 9588e61aa6

View File

@@ -1,5 +1,6 @@
import re, sys, os, io, sqlite3, hashlib, time, datetime import re, sys, os, io, sqlite3, hashlib, time, datetime
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import logging, traceback
from sleekxmpp.componentxmpp import ComponentXMPP from sleekxmpp.componentxmpp import ComponentXMPP
from sleekxmpp import Presence, Message from sleekxmpp import Presence, Message
@@ -261,17 +262,277 @@ class XMPPTelegram(ComponentXMPP):
self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='unavailable') self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='unavailable')
sys.exit(0) sys.exit(0)
def process_command(self, iq): class MessageHandler():
_unknown_command_handler = lambda self: "Unknown command, for a list send '!help'"
_on_connect = lambda: None
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):
"""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))]
reply = "Available commands:"
for method in methods:
docstring = getattr(self, method).__doc__
if docstring is None:
docstring = "No description available"
reply += "\n"+method+" ("+docstring.split("\n")[0]+")"
return reply
else:
method = getattr(self,self.arguments[0])
reply = trim(method.__doc__)
return reply
class GateMessageHandler(MessageHandler):
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 <phone number in international format>"""
if len(hndl.arguments) != 1:
return "Wrong number of arguments"
phone_no = 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):
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):
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 list_sessions(hndl, self):
if not self.tg_connections[hndl.jid].is_user_authorized():
return "Error"
sessions = self.tg_connections[hndl.jid].invoke(GetAuthorizationsRequest())
return str(sessions)
def reload_dialogs(hndl, self):
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):
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(self): #1 arg
result = self.tg_connections[jid].get_entity(parsed[1])
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(self): #1 arg
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)
def group(self): #2 args
# 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 channel(self): #1 arg
groupname = parsed[1]
self.tg_connections[jid].invoke(CreateChannelRequest(groupname, groupname, broadcast = True))
self.tg_process_dialogs(jid)
def supergroup(self): #1 arg
groupname = parsed[1]
self.tg_connections[jid].invoke(CreateChannelRequest(groupname, groupname, megagroup = True))
self.tg_process_dialogs(jid)
def username(self): #1 arg
username = parsed[1]
self.tg_connections[jid].invoke(UpdateUsernameRequest(username))
def name(self): #1 or 2 args
firstname = parsed[1]
lastname = parsed[2] if len(parsed) > 2 else None
self.tg_connections[jid].invoke(UpdateProfileRequest(first_name = firstname, last_name = lastname))
def about(self): #>0 args
about = iq['body'][7:]
self.tg_connections[jid].invoke(UpdateProfileRequest(about = about))
def import_contact(self): #2 args
phone = parsed[1]
firstname = parsed[2]
lastname = parsed[3] if len(parsed) > 3 else None
contact = InputPhoneContact(client_id=generate_random_long(), phone=phone, first_name=firstname, last_name=lastname)
self.tg_connections[jid].invoke(ImportContactsRequest([contact]))
self.tg_process_dialogs(jid)
def roster(hndl, self):
response = "Telegram chats:\n"
for jid,tid in self.contact_list[hndl.jid].items():
response += "{}: {}\n".format(tid, jid)
return response
def process_command(self, msg):
""" """
Commands to gateway, users or chats (starts with !) Commands to gateway, users or chats (starts with !)
:param iq: :param iq:
:return: :return:
""" """
parsed = iq['body'].split(' ') logging.info("received message "+str(msg["body"])+" from "+str(msg["from"]))
jid = iq['from'].bare is_command = msg["body"].startswith("!") and msg["body"][1] != "_"
if is_command:
command = msg["body"].split(" ")[0][1:]
handler = self.GateMessageHandler(msg)._handler
try:
reply = str(handler(self))
except Exception as e:
if self.config["debug"]:
reply = traceback.format_exc()
else:
if isinstance(e, NotAuthorizedError):
reply = str(e)
else:
logging.error("Exception in command from {}, command was '{}'".format(msg["from"],msg["body"]))
traceback.print_exc()
reply = "Internal error, please contact Sysadmin"
if reply is not None:
self.gate_reply_message(msg, reply)
#msg.reply(reply).send()
parsed = msg['body'].split(' ')
jid = msg['from'].bare
if parsed[0] == '!help': if parsed[0] == '!help':
self.gate_reply_message(iq, '=== Available gateway commands ===:\n\n' self.gate_reply_message(msg, '=== Available gateway commands ===:\n\n'
'!help - Displays this text\n' '!help - Displays this text\n'
'!login +123456789 - Initiates Telegram session\n' '!login +123456789 - Initiates Telegram session\n'
@@ -299,149 +560,6 @@ class XMPPTelegram(ComponentXMPP):
'!roster - Lists yout TG roster\n' '!roster - Lists yout TG roster\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])
if self.tg_connections[jid].is_user_authorized():
self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='online', pstatus='connected')
self.gate_reply_message(iq, 'You are already authenticated in Telegram.')
else:
# remove old sessions for this JID #
self.db_connection.execute("DELETE from accounts where jid = ?", (jid, ) )
self.tg_connections[jid].send_code_request(parsed[1])
self.gate_reply_message(iq, 'Gate is connected. Telegram should send SMS message to you.')
self.gate_reply_message(iq, 'Please enter one-time code via !code 12345.')
elif parsed[0] in ['!code', '!password']: # --------------------------------------------------
if not self.tg_connections[jid].is_user_authorized():
if parsed[0] == '!code':
try:
self.gate_reply_message(iq, 'Trying authenticate...')
self.tg_connections[jid].sign_in(self.tg_phones[jid], parsed[1])
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 parsed[0] == '!password':
self.gate_reply_message(iq, 'Checking password...')
self.tg_connections[jid].sign_in(password=parsed[1])
if self.tg_connections[jid].is_user_authorized():
self.send_presence(pto=jid, pfrom=self.boundjid.bare, ptype='online', pstatus='connected')
self.gate_reply_message(iq, '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.')
elif parsed[0] == '!list_sessions': # --------------------------------------------------
if not self.tg_connections[jid].is_user_authorized():
self.gate_reply_message(iq, 'Error.')
return
sessions = self.tg_connections[jid].invoke(GetAuthorizationsRequest())
elif parsed[0] == '!reload_dialogs':
if not self.tg_connections[jid].is_user_authorized():
self.gate_reply_message(iq, 'Error.')
return
self.tg_process_dialogs(jid)
self.gate_reply_message(iq, 'Dialogs reloaded.')
elif parsed[0] == '!logout': # --------------------------------------------------
self.tg_connections[jid].log_out()
self.db_connection.execute("DELETE FROM accounts WHERE jid = ?", (jid,))
self.gate_reply_message(iq, 'Your Telegram session was deleted')
elif parsed[0] == '!add': # add user
result = self.tg_connections[jid].get_entity(parsed[1])
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)
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])
# we re ready to make group
self.tg_connections[jid].invoke(CreateChatRequest([groupuser], groupname))
self.tg_process_dialogs(jid)
elif parsed[0] == '!channel' and len(parsed) >= 2:
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:
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:
username = parsed[1]
self.tg_connections[jid].invoke(UpdateUsernameRequest(username))
elif parsed[0] == '!name' and len(parsed) >= 2:
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:
about = iq['body'][7:]
self.tg_connections[jid].invoke(UpdateProfileRequest(about = about))
elif parsed[0] == '!import' and len(parsed) >= 3:
phone = parsed[1]
firstname = parsed[2]
lastname = parsed[3] if len(parsed) > 3 else None
contact = InputPhoneContact(client_id=generate_random_long(), phone=phone, first_name=firstname, last_name=lastname)
self.tg_connections[jid].invoke(ImportContactsRequest([contact]))
self.tg_process_dialogs(jid)
elif parsed[0] == '!roster': # create new channel
response = "Telegram chats:\n"
for jid,tid in self.contact_list[jid].items():
response += "{}: {}\n".format(tid, jid)
self.gate_reply_message(iq, response)
else: # --------------------------------------------------
self.gate_reply_message(iq, 'Unknown command. Try !help for list all commands.')
def process_chat_user_command(self, iq): def process_chat_user_command(self, iq):
parsed = iq['body'].split(' ') parsed = iq['body'].split(' ')