446 lines
20 KiB
Python
446 lines
20 KiB
Python
import time
|
|
import logging, pprint
|
|
|
|
from xmpp_tg.utils import var_dump, display_tg_name, get_contact_jid, localtime
|
|
from telethon.tl.types import User, Chat, Channel
|
|
|
|
# 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, xmpp):
|
|
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
|
|
self.xmpp = xmpp
|
|
|
|
def _update(self, text):
|
|
self.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 dupdate(self, *args, **kwargs):
|
|
"""nolist send message as update"""
|
|
self._update(str(args))
|
|
|
|
#TODO: handle non-existant commands
|
|
#TODO: add end text
|
|
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(self, gate):
|
|
"""Get config/set config options"""
|
|
config_exclude = ['jid', 'tg_phone']
|
|
|
|
option = self.arguments[0] if len(self.arguments) >= 1 else None
|
|
value = self.arguments[1] if len(self.arguments) == 2 else None
|
|
|
|
if value is not None and option not in config_exclude:
|
|
gate.db_connection.execute("update accounts set {} = ? where jid = ?".format(option), (value, self.jid,))
|
|
gate.accounts[self.jid] = gate.db_connection.execute("SELECT * FROM accounts where jid = ?", (self.jid,)).fetchone()
|
|
|
|
message = "=== Your current configuration ===\n\n"
|
|
for param, value in gate.accounts[self.jid].items():
|
|
message = message + "<%s>: %s" % (param, value) + "\n"
|
|
message = message + "\nTo modify some option, please, send !configure param value"
|
|
return message
|
|
|
|
def login(self, gate): #1 arg
|
|
"""Initiates Telegram session
|
|
|
|
Usage: !login <phone number in international format>"""
|
|
self._min_args(1)
|
|
|
|
phone_no = self.arguments[0]
|
|
|
|
self._update("Please wait...")
|
|
gate.spawn_tg_client(self.jid, phone_no)
|
|
|
|
if gate.tg_connections[self.jid].is_user_authorized():
|
|
# gate.send_presence(pto=jid, pfrom=gate.boundjid.bare, ptype='online', pstatus='connected') #already called in spawn_tg_client
|
|
return "You are already authenticated in Telegram"
|
|
else:
|
|
# remove old sessions for this JID #
|
|
gate.db_connection.execute("DELETE from accounts where jid = ?", (self.jid,))
|
|
gate.tg_connections[self.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(self, gate):
|
|
"""nolist Enter confirmation code"""
|
|
self._min_args(1)
|
|
|
|
code = self.arguments[0]
|
|
jid = self.jid
|
|
|
|
if not gate.tg_connections[jid].is_user_authorized():
|
|
try:
|
|
self._update('Trying authenticate...')
|
|
gate.tg_connections[jid].sign_in(gate.tg_phones[jid], code)
|
|
except SessionPasswordNeededError:
|
|
self._update('Two-factor authentication detected.')
|
|
self._update('Please enter your password via !password abc123.')
|
|
return
|
|
|
|
if gate.tg_connections[jid].is_user_authorized():
|
|
gate.send_presence(pto=jid, pfrom=gate.boundjid.bare, ptype='online', pstatus='connected')
|
|
self._update('Authentication successful. Initiating Telegram...')
|
|
gate.db_connection.execute("INSERT INTO accounts(jid, tg_phone) VALUES(?, ?)", (jid, gate.tg_phones[jid],))
|
|
gate.accounts[jid] = gate.db_connection.execute("SELECT * FROM accounts where jid = ?", (jid,)).fetchone()
|
|
gate.init_tg(jid)
|
|
|
|
else:
|
|
return 'Authentication failed.'
|
|
else:
|
|
return 'You are already authenticated. Please use !logout before new login.'
|
|
|
|
def password(self, gate):
|
|
"""nolist Enter password"""
|
|
self._min_args(1)
|
|
|
|
password = self.arguments[1]
|
|
jid = gate.jidhndl
|
|
|
|
if not gate.tg_connections[jid].is_user_authorized():
|
|
self._update('Checking password...')
|
|
gate.tg_connections[jid].sign_in(password=password)
|
|
|
|
if gate.tg_connections[jid].is_user_authorized():
|
|
gate.send_presence(pto=jid, pfrom=gate.boundjid.bare, ptype='online', pstatus='connected')
|
|
self._update('Authentication successful. Initiating Telegram...')
|
|
gate.db_connection.execute("INSERT INTO accounts(jid, tg_phone) VALUES(?, ?)", (jid, gate.tg_phones[jid],))
|
|
gate.accounts[jid] = gate.db_connection.execute("SELECT * FROM accounts where jid = ?", (jid,)).fetchone()
|
|
gate.init_tg(jid)
|
|
|
|
else:
|
|
return 'Authentication failed.'
|
|
else:
|
|
return 'You are already authenticated. Please use !logout before new login.'
|
|
|
|
def reload_dialogs(self, gate):
|
|
"""Reload Telegram dialogs"""
|
|
if not gate.tg_connections[self.jid].is_user_authorized():
|
|
return "Error"
|
|
|
|
gate.tg_process_dialogs(self.jid)
|
|
return "Dialogs reloadad"
|
|
|
|
def logout(self, gate):
|
|
"""Deletes current Telegram session at Gateway"""
|
|
gate.tg_connections[self.jid].log_out()
|
|
gate.db_connection.execute("DELETE FROM accounts WHERE jid = ?", (self.jid,))
|
|
return 'Your Telegram session was deleted'
|
|
|
|
def add(self, gate): #1 arg
|
|
"""Add contact by nickname or t.me link"""
|
|
self._min_args(1)
|
|
|
|
argument = self.arguments[0]
|
|
jid = self.jid
|
|
|
|
result = gate.tg_connections[jid].get_entity(argument)
|
|
if type(result) == User:
|
|
tg_peer = InputPeerUser( result.id, result.access_hash )
|
|
result = gate.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 )
|
|
gate.tg_connections[jid].invoke(JoinChannelRequest(InputPeerChannel(result.id, result.access_hash)))
|
|
else:
|
|
return 'Sorry, nothing found.'
|
|
|
|
gate.tg_process_dialogs(jid)
|
|
|
|
def join(self, gate): #1 arg
|
|
"""Join conference via invite link"""
|
|
self._min_args(1)
|
|
|
|
argument = self.arguments[0]
|
|
jid = self.jid
|
|
|
|
link = argument.split('/') # https://t.me/joinchat/HrCmckx_SkMbSGFLhXCvSg
|
|
gate.tg_connections[jid].invoke(ImportChatInviteRequest(link[4]))
|
|
time.sleep(1)
|
|
gate.tg_process_dialogs(jid)
|
|
|
|
def group(self, gate): #2 args
|
|
"""Creat a Group"""
|
|
self._min_args(2)
|
|
# group name? #
|
|
groupname = parsed[1]
|
|
|
|
# group users? #
|
|
groupuser = gate.tg_connections[jid].get_entity(parsed[2])
|
|
|
|
# we re ready to make group
|
|
gate.tg_connections[jid].invoke(CreateChatRequest([groupuser], groupname))
|
|
gate.tg_process_dialogs(jid)
|
|
|
|
|
|
def supergroup(self, gate): #1 arg
|
|
"""Create a channel"""
|
|
self._min_args(1)
|
|
|
|
groupname = self.arguments[0]
|
|
gate.tg_connections[self.jid].invoke(CreateChannelRequest(groupname, groupname, megagroup = True))
|
|
gate.tg_process_dialogs(self.jid)
|
|
|
|
channel = supergroup
|
|
|
|
def username(self, gate): #1 arg
|
|
"""Change your Telegram nickname"""
|
|
self._min_args(1)
|
|
|
|
username = self.arguments[0]
|
|
|
|
gate.tg_connections[self.jid].invoke(UpdateUsernameRequest(username))
|
|
|
|
def name(self, gate): #1 or 2 args
|
|
"""Change your Telegram display name"""
|
|
self._min_args(1)
|
|
|
|
firstname = self.arguments[0]
|
|
lastname = self.arguments[1] if len(self.arguments) > 1 else None
|
|
|
|
gate.tg_connections[self.jid].invoke(UpdateProfileRequest(first_name = firstname, last_name = lastname))
|
|
|
|
def about(self, gate): #>0 args
|
|
"""Change your Telegram 'about' text"""
|
|
self._min_args(1)
|
|
|
|
about = self.msg['body'][7:]
|
|
gate.tg_connections[self.jid].invoke(UpdateProfileRequest(about = about))
|
|
|
|
def import_contact(self, gate): #2 args
|
|
"""Add contact by phone number"""
|
|
self._min_args(2)
|
|
|
|
phone = self.arguments[0]
|
|
firstname = self.arguments[1]
|
|
lastname = self.arguments[2] if len(self.arguments) > 2 else None
|
|
|
|
contact = InputPhoneContact(client_id=generate_random_long(), phone=phone, first_name=firstname, last_name=lastname)
|
|
gate.tg_connections[self.jid].invoke(ImportContactsRequest([contact]))
|
|
gate.tg_process_dialogs(jid)
|
|
|
|
def roster(self, gate):
|
|
"""Send Telegram contact list back, with JIDs"""
|
|
response = "Telegram chats:\n"
|
|
for jid,tid in gate.contact_list[self.jid].items():
|
|
response += "{}: {}\n".format(tid, jid)
|
|
return response
|
|
|
|
def oldhelp(self, gate):
|
|
"""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(self, gate):
|
|
"""Block this user"""
|
|
nickname = display_tg_name(gate.tg_dialogs[self.jid]['users'][self.tg_id])
|
|
gate.tg_connections[self.jid].invoke(BlockRequest(InputPeerUser(self.tg_id, gate.tg_dialogs[self.jid]['users'][self.tg_id].access_hash)))
|
|
gate.gate_reply_message(iq, 'User %s blacklisted!' % nickname)
|
|
|
|
def unblock(self, gate):
|
|
"""Unblock this user"""
|
|
nickname = display_tg_name(gate.tg_dialogs[self.jid]['users'][self.tg_id])
|
|
gate.tg_connections[self.jid].invoke(UnblockRequest(InputPeerUser(self.tg_id, gate.tg_dialogs[self.jid]['users'][self.tg_id].access_hash)))
|
|
gate.gate_reply_message(iq, 'User %s unblacklisted!' % nickname)
|
|
|
|
def remove(self, gate):
|
|
"""Remove User from Telegram contact list"""
|
|
peer = InputPeerUser(self.tg_id, gate.tg_dialogs[self.jid]['users'][self.tg_id].access_hash)
|
|
c_jid = get_contact_jid(gate.tg_dialogs[self.jid]['users'][self.tg_id], gate.boundjid.bare)
|
|
|
|
gate.tg_connections[self.jid].invoke(DeleteContactRequest(peer))
|
|
gate.tg_connections[self.jid].invoke(DeleteHistoryRequest(peer, max_id = 0, just_clear = None))
|
|
|
|
gate.send_presence(pto = self.jid, pfrom = c_jid, ptype ='unavailable')
|
|
gate.send_presence(pto = self.jid, pfrom = c_jid, ptype ='unsubscribed')
|
|
gate.send_presence(pto = self.jid, pfrom = c_jid, ptype ='unsubscribe')
|
|
|
|
def _replace(self, gate):
|
|
peer = InputPeerUser(hdnl.tg_id, gate.tg_dialogs[self.jid]['users'][self.tg_id].access_hash)
|
|
|
|
msg_id, edited = gate.edit_message(hdnl.jid, hdnl.tg_id, self.msg['body'])
|
|
if not edited: return
|
|
|
|
# and send it
|
|
if edited != '' and edited != ' ':
|
|
gate.tg_dialogs[self.jid]['messages'][self.tg_id]["body"] = edited
|
|
gate.tg_connections[self.jid].invoke(EditMessageRequest(peer, msg_id, message = edited))
|
|
else:
|
|
del(gate.tg_dialogs[self.jid]['messages'][self.tg_id])
|
|
gate.tg_connections[self.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
|
|
self.tg_id = int(self.msg['to'].node[1:])
|
|
|
|
def leave(self, gate):
|
|
"""Leave group or channel"""
|
|
if self.tg_id in gate.tg_dialogs[self.jid]['supergroups']:
|
|
peer = InputPeerChannel(self.tg_id, gate.tg_dialogs[self.jid]['supergroups'][self.tg_id].access_hash)
|
|
gate.tg_connections[self.jid].invoke(LeaveChannelRequest(peer))
|
|
gate.tg_connections[self.jid].invoke(DeleteHistoryRequest(peer, max_id = 0, just_clear = None))
|
|
c_jid = get_contact_jid(gate.tg_dialogs[self.jid]['supergroups'][self.tg_id], gate.boundjid.bare)
|
|
|
|
gate.send_presence(pto =self.jid, pfrom = c_jid, ptype ='unavailable')
|
|
gate.send_presence(pto =self.jid, pfrom = c_jid, ptype ='unsubscribed')
|
|
gate.send_presence(pto =self.jid, pfrom = c_jid, ptype ='unsubscribe')
|
|
|
|
if self.tg_id in gate.tg_dialogs[self.jid]['groups']:
|
|
gate.tg_connections[self.jid].invoke(DeleteChatUserRequest(self.tg_id, gate.tg_connections[self.jid].me))
|
|
gate.tg_connections[self.jid].invoke(DeleteHistoryRequest(InputPeerChat(self.tg_id), max_id = 0, just_clear = None))
|
|
c_jid = get_contact_jid(gate.tg_dialogs[self.jid]['groups'][self.tg_id], gate.boundjid.bare)
|
|
|
|
gate.send_presence(pto =self.jid, pfrom = c_jid, ptype ='unavailable')
|
|
gate.send_presence(pto =self.jid, pfrom = c_jid, ptype ='unsubscribed')
|
|
gate.send_presence(pto =self.jid, pfrom = c_jid, ptype ='unsubscribe')
|
|
|
|
def invite(self, gate):
|
|
"""Invite user to group"""
|
|
if self.tg_id in gate.tg_dialogs[self.jid]['supergroups']:
|
|
invited_user = gate.tg_connections[[self.jid]].get_entity(parsed[1])
|
|
if type(invited_user) == User:
|
|
gate.tg_connections[[self.jid]].invoke(EditBannedRequest(InputPeerChannel(self.tg_id, gate.tg_dialogs[[self.jid]]['supergroups'][
|
|
self.tg_id].access_hash), invited_user, ChannelBannedRights(until_date=None, view_messages=False)))
|
|
gate.tg_connections[[self.jid]].invoke(InviteToChannelRequest(InputPeerChannel(self.tg_id, gate.tg_dialogs[[self.jid]]['supergroups'][
|
|
self.tg_id].access_hash), [invited_user]))
|
|
|
|
if self.tg_id in gate.tg_dialogs[[self.jid]]['groups']:
|
|
invited_user = gate.tg_connections[[self.jid]].get_entity(parsed[1])
|
|
if type(invited_user) == User:
|
|
gate.tg_connections[[self.jid]].invoke(AddChatUserRequest(self.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))
|