#!/usr/bin/env python3 # -*- coding: utf-8 -*- import configparser import logging from sys import argv from threading import Thread from time import sleep from xml.dom import minidom import sleekxmpp import telegram from sleekxmpp.xmlstream.stanzabase import ElementBase from telegram.error import NetworkError, Unauthorized try: import requests except: logging.error("HTTP Upload support disabled.") class Request(ElementBase): """Special class to create http_upload requests.""" namespace = 'urn:xmpp:http:upload' name = 'request' plugin_attrib = 'request' interfaces = set(('filename', 'size')) sub_interfaces = interfaces class Jabbergram(sleekxmpp.ClientXMPP): """Main object.""" def __init__(self, jid, password, rooms, nick, token, groups, verify_ssl): """ Executed when the class is initialized. It adds session handlers, muc handlers and send the telegram reading function and the HTTP upload initializing functions to their respective threads. """ # XMPP super(Jabbergram, self).__init__(jid, password) self.add_event_handler('session_start', self.start) self.add_event_handler('groupchat_message', self.muc_message) self.muc_rooms = rooms.split() self.nick = nick self.token = token self.xmpp_users = {} self.jid = jid self.verify_ssl = verify_ssl for muc in self.muc_rooms: self.add_event_handler("muc::%s::got_online" % muc, self.muc_online) self.add_event_handler("muc::%s::got_offline" % muc, self.muc_offline) # Telegram self.groups = groups.split() self.bot = telegram.Bot(self.token) self.telegram_users = {} # initialize http upload on a thread since its needed to be connected # to xmpp t = Thread(target=self.init_http) t.daemon = True t.start() # put tg connector in a thread t = Thread(target=self.read_tg) t.daemon = True t.start() print('Please wait a couple of minutes until it\'s correctly ' 'connected') def init_http(self): """ Initializes HTTP upload support. Sends a discovery stanza to the server to find the HTTP upload component and asks for the max size a file can be. """ self.http_upload = self.HttpUpload(self) self.component = self.http_upload.discovery() if self.component: xml = self.http_upload.disco_info(self.component) xml = minidom.parseString(str(xml)) self.max_size = int(xml.getElementsByTagName('value') [1].firstChild.data) else: try: self.component = self.jid.split('@')[1] xml = self.http_upload.disco_info(self.component) xml = minidom.parseString(str(xml)) self.max_size = int(xml.getElementsByTagName('value') [1].firstChild.data) except: self.max_size = None def read_tg(self): """Main telegram function.""" update_id = 0 # wait until http_upload has been tested sleep(5) while True: try: for update in self.bot.getUpdates(offset=update_id, timeout=10): name = '' size = 0 if update.edited_message: update_id = update.update_id + 1 continue if update.message.from_user: user = update.message.from_user.username # sometimes there's no user. weird, but it happens if not user: user = update.message.from_user.first_name # sometimes there's no user. weird, but it happens elif update.message['from']: user = str(update.message['from'].first_name) if update.message.audio or update.message.document or \ update.message.photo or update.message.video \ or update.message.voice or update.message.sticker: # proceed only if http upload is available if self.max_size is not None: if update.message.audio: d_file = update.message.audio ext = '.ogg' size = d_file.file_size elif update.message.document: d_file = update.message.document ext = '' size = d_file.file_size elif update.message.photo: d_file = update.message.photo[-1] ext = '.jpg' size = d_file.file_size elif update.message.video: d_file = update.message.video[-1] ext = '.mp4' size = d_file.file_size elif update.message.voice: d_file = update.message.voice ext = '.ogg' size = d_file.file_size elif update.message.sticker: d_file = update.message.sticker ext = '.png' size = d_file.file_size if self.max_size >= int(size): t_file = self.bot.getFile(d_file.file_id) f_name = '/tmp/' + d_file.file_id + ext t_file.download(f_name) url = self.http_upload.upload( self.component, self.verify_ssl, f_name, size) if update.message.caption: message = update.message.caption + ' ' else: message = 'File uploaded: ' message += url else: message = 'A file has been uploaded to Telegr'\ 'am, but is too big.' else: message = 'A file has been uploaded to Telegram,'\ 'but the XMPP server doesn\'t support H'\ 'TTP Upload.' elif update.message.new_chat_member: message = 'This user has joined the group.' elif update.message.left_chat_member: message = 'This user has left the group.' elif update.message.new_chat_title: message = 'The group\'s title has changed: ' + \ update.message.new_chat_title elif update.message.new_chat_photo: message = 'The group\'s photo has changed.' else: if update.message.reply_to_message: name = update.message.reply_to_message.from_user\ .username if name != self.bot.username: message = name + ': ' + \ update.message.reply_to_message.text else: message = update.message.reply_to_message.text else: message = update.message.text if name: msg = message + ' <- ' + user + ": " + \ update.message.text else: msg = user + ": " + message if update.message.chat.type == 'supergroup' and \ update.message.chat.username: chat = '@' + update.message.chat.username else: chat = str(update.message.chat.id) if chat not in self.groups: chat = str(update.message.chat_id) if message and chat in self.groups: index = self.groups.index(chat) receiver = self.muc_rooms[index] if chat in self.telegram_users: if user not in self.telegram_users[chat]: self.telegram_users[chat] += ' ' + user else: self.telegram_users[chat] = ' ' + user if message == '.users': self.say_users('telegram', receiver, chat) elif message == '.help': self.say_help('telegram', receiver, chat) elif message == '.where': self.say_where('telegram', receiver, chat) else: self.send_message(mto=receiver, mbody=msg, mtype='groupchat') update_id = update.update_id + 1 except NetworkError as e: print(e) sleep(1) except Unauthorized as e: print(e) sleep(1) except Exception as e: update_id += 1 print(e) def start(self, event): """Does some initial setup for XMPP and joins all mucs.""" self.get_roster() self.send_presence() for muc in self.muc_rooms: self.plugin['xep_0045'].joinMUC(muc, self.nick, wait=True) def muc_message(self, msg): """Muc message's handler.""" muc_room = str(msg['from']).split('/')[0] index = self.muc_rooms.index(muc_room) tg_group = self.groups[index] if msg['body'] == '.users': self.say_users('xmpp', muc_room, tg_group) elif msg['body'] == '.help': self.say_help('xmpp', muc_room, tg_group) elif msg['body'] == '.where': self.say_where('xmpp', muc_room, tg_group) elif msg['mucnick'] != self.nick: message = str(msg['from']).split('/')[1] + ': ' + str(msg['body']) self.bot.sendMessage(tg_group, text=message) def muc_online(self, presence): """Muc presence's handler.""" user = presence['muc']['nick'] muc = presence['from'].bare if user != self.nick: if muc in self.xmpp_users: self.xmpp_users[muc].append(presence['muc']['nick']) else: self.xmpp_users[muc] = [presence['muc']['nick']] def muc_offline(self, presence): """Muc presence's handler.""" user = presence['muc']['nick'] muc = presence['from'].bare if user != self.nick: self.xmpp_users[muc].remove(presence['muc']['nick']) def say_users(self, service, muc, group): """It returns the users on XMPP or Telegram.""" if service == 'xmpp': if group in self.telegram_users: tg_users = self.telegram_users[group] else: tg_users = "" msg = 'Telegram Users:' + tg_users self.send_message(mto=muc, mbody=msg, mtype='groupchat') elif service == 'telegram': xmpp_users = "" if muc in self.xmpp_users: for i in self.xmpp_users[muc]: xmpp_users = xmpp_users + ' ' + i else: xmpp_users = "" msg = 'XMPP Users:' + xmpp_users self.bot.sendMessage(group, text=msg) def say_help(self, service, muc, group): """Help command.""" msg = 'Hi, I\'m ' + self.bot.username + '. I have two commands : ".us'\ 'ers" and ".where".' if service == 'xmpp': self.send_message(mto=muc, mbody=msg, mtype='groupchat') elif service == 'telegram': self.bot.sendMessage(group, text=msg) def say_where(self, service, muc, group): """Returns Telegram's group location if it's public.""" if service == 'xmpp': if '@' in group: msg = 'I\'m on http://telegram.me/' + group.split('@')[1] + '.' else: msg = 'Sorry, I\'m on a private group, you\'ll have to ask fo'\ 'r an invitation.' self.send_message(mto=muc, mbody=msg, mtype='groupchat') elif service == 'telegram': msg = 'I\'m on ' + muc + '.' self.bot.sendMessage(group, text=msg) class HttpUpload(): """HTTP upload main class.""" def __init__(self, parent_self): """Init... Yep.""" self.parent_self = parent_self def discovery(self): """Discovers all server's components.""" disco = sleekxmpp.basexmpp.BaseXMPP.Iq(self.parent_self) disco['query'] = "http://jabber.org/protocol/disco#items" disco['type'] = 'get' disco['from'] = self.parent_self.jid disco['to'] = self.parent_self.jid.split('@')[1] d = disco.send(timeout=30) xml = minidom.parseString(str(d)) item = xml.getElementsByTagName('item') for component in item: component = component.getAttribute('jid') info = self.disco_info(component) if "urn:xmpp:http:upload" in info: http_upload_component = component break else: http_upload_component = "" return http_upload_component def disco_info(self, component): """Discovers HTTP upload components attributes.""" info = sleekxmpp.basexmpp.BaseXMPP.Iq(self.parent_self) info['query'] = "http://jabber.org/protocol/disco#info" info['type'] = 'get' info['from'] = self.parent_self.jid info['to'] = component response = str(info.send(timeout=30)) return response def upload(self, component, verify_ssl, u_file, size): """Uploads to HTTP upload.""" peticion = Request() peticion['filename'] = u_file.split('/')[-1] peticion['size'] = str(size) iq = sleekxmpp.basexmpp.BaseXMPP.Iq(self.parent_self) iq.set_payload(peticion) iq['type'] = 'get' iq['to'] = component iq['from'] = self.parent_self.jid send = iq.send(timeout=30) xml = minidom.parseString(str(send)) put_url = xml.getElementsByTagName('put')[0].firstChild.data if verify_ssl == 'False': req = requests.put(put_url, data=open(u_file, 'rb'), verify=False) else: req = requests.put(put_url, data=open(u_file, 'rb')) return put_url if __name__ == '__main__': # parse config config = [] parser = configparser.SafeConfigParser() if len(argv) == 2: parser.read(argv[1]) else: parser.read('config.ini') for name, value in parser.items('config'): config.append(value) # assign values for the bot jid = config[0] password = config[1] muc_rooms = config[2] nick = config[3] token = config[4] groups = config[5] verify_ssl = config[6] xmpp = Jabbergram(jid, password, muc_rooms, nick, token, groups, verify_ssl) xmpp.register_plugin('xep_0045') if xmpp.connect(): xmpp.process(block=True) print("Done") else: print("Unable to connect.")