jabbergram/jabbergram.py

438 lines
16 KiB
Python
Raw Normal View History

2016-05-08 16:46:24 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import configparser
2017-07-08 19:45:07 +00:00
import logging
from sys import argv
2016-05-08 16:46:24 +00:00
from threading import Thread
from time import sleep
2016-09-26 11:19:46 +00:00
from xml.dom import minidom
2016-05-08 16:46:24 +00:00
2017-07-08 19:45:07 +00:00
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.")
2016-05-08 16:46:24 +00:00
2016-09-26 11:19:46 +00:00
class Request(ElementBase):
2017-07-08 19:46:10 +00:00
"""Special class to create http_upload requests."""
2016-09-26 11:19:46 +00:00
namespace = 'urn:xmpp:http:upload'
name = 'request'
plugin_attrib = 'request'
interfaces = set(('filename', 'size'))
sub_interfaces = interfaces
2016-09-26 11:19:46 +00:00
class Jabbergram(sleekxmpp.ClientXMPP):
2017-07-08 19:46:10 +00:00
"""Main object."""
def __init__(self, jid, password, rooms, nick, token, groups, verify_ssl):
2017-07-08 19:46:10 +00:00
"""
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.
"""
2016-05-08 16:46:24 +00:00
# XMPP
2016-09-26 11:19:46 +00:00
super(Jabbergram, self).__init__(jid, password)
2016-05-08 16:46:24 +00:00
self.add_event_handler('session_start', self.start)
self.add_event_handler('groupchat_message', self.muc_message)
2016-05-12 22:23:23 +00:00
self.muc_rooms = rooms.split()
2016-05-08 16:46:24 +00:00
self.nick = nick
self.token = token
self.xmpp_users = {}
2016-09-26 11:19:46 +00:00
self.jid = jid
self.verify_ssl = verify_ssl
for muc in self.muc_rooms:
self.add_event_handler("muc::%s::got_online" % muc,
2016-09-26 11:19:46 +00:00
self.muc_online)
self.add_event_handler("muc::%s::got_offline" % muc,
2016-09-26 11:19:46 +00:00
self.muc_offline)
2016-05-08 16:46:24 +00:00
# Telegram
self.groups = groups.split()
2016-05-08 16:46:24 +00:00
self.bot = telegram.Bot(self.token)
self.telegram_users = {}
2016-05-08 16:46:24 +00:00
2016-09-26 11:19:46 +00:00
# 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()
2016-09-23 19:36:46 +00:00
# put tg connector in a thread
2016-05-08 16:46:24 +00:00
t = Thread(target=self.read_tg)
t.daemon = True
t.start()
print('Please wait a couple of minutes until it\'s correctly '
2016-09-26 11:19:46 +00:00
'connected')
def init_http(self):
2017-07-08 19:46:10 +00:00
"""
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.
"""
2016-09-26 11:19:46 +00:00
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
2016-05-08 16:46:24 +00:00
def read_tg(self):
2017-07-08 19:46:10 +00:00
"""Main telegram function."""
2016-05-08 16:46:24 +00:00
update_id = 0
2016-09-26 11:19:46 +00:00
# wait until http_upload has been tested
sleep(5)
2016-05-08 16:46:24 +00:00
while True:
try:
for update in self.bot.getUpdates(offset=update_id,
2016-09-26 11:19:46 +00:00
timeout=10):
2017-01-29 02:47:46 +00:00
name = ''
size = 0
2017-07-09 01:04:25 +00:00
if update.edited_message:
update_id = update.update_id + 1
continue
if update.message.from_user:
user = str(update.message.from_user.username)
# sometimes there's no user. weird, but it happens
if not user:
user = str(update.message.from_user.first_name)
# even weirder is that username or first_name exists
# let's take last_name
if not user:
user = str(update.message.from_user.last_name)
2017-01-29 02:47:46 +00:00
2016-09-26 11:19:46 +00:00
if update.message.audio or update.message.document or \
update.message.photo or update.message.video \
or update.message.voice or update.message.sticker:
2016-09-26 11:19:46 +00:00
# proceed only if http upload is available
2016-09-26 11:19:46 +00:00
if self.max_size is not None:
if update.message.audio:
d_file = update.message.audio
ext = '.ogg'
2016-09-26 12:21:28 +00:00
size = d_file.file_size
2016-09-26 11:19:46 +00:00
elif update.message.document:
d_file = update.message.document
ext = ''
2016-09-26 12:21:28 +00:00
size = d_file.file_size
2016-09-26 11:19:46 +00:00
elif update.message.photo:
d_file = update.message.photo[-1]
ext = '.jpg'
2016-09-26 12:21:28 +00:00
size = d_file.file_size
2016-09-26 11:19:46 +00:00
elif update.message.video:
d_file = update.message.video[-1]
ext = '.mp4'
2016-09-26 12:21:28 +00:00
size = d_file.file_size
2016-09-26 11:19:46 +00:00
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):
2016-09-26 11:19:46 +00:00
t_file = self.bot.getFile(d_file.file_id)
f_name = '/tmp/' + d_file.file_id + ext
t_file.download(f_name)
2016-09-26 11:19:46 +00:00
url = self.http_upload.upload(
self.component,
self.verify_ssl,
f_name, size)
2016-09-26 11:19:46 +00:00
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.'
2016-09-26 11:19:46 +00:00
else:
message = 'A file has been uploaded to Telegram,'\
'but the XMPP server doesn\'t support H'\
'TTP Upload.'
2016-09-26 11:19:46 +00:00
2017-07-09 01:04:25 +00:00
elif update.message.new_chat_members:
2016-09-26 11:19:46 +00:00
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: ' + \
2016-09-26 11:19:46 +00:00
update.message.new_chat_title
elif update.message.new_chat_photo:
message = 'The group\'s photo has changed.'
2016-09-26 11:19:46 +00:00
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
2016-09-26 11:19:46 +00:00
if name:
msg = message + ' <- ' + user + ": " + \
update.message.text
else:
msg = user + ": " + message
2017-07-09 01:04:25 +00:00
if update.message.chat.type == 'supergroup':
chat = '@' + update.message.chat.username
else:
chat = str(update.message.chat.id)
if chat not in self.groups:
chat = str(update.message.chat_id)
2016-05-08 16:46:24 +00:00
if message and chat in self.groups:
index = self.groups.index(chat)
receiver = self.muc_rooms[index]
2016-05-12 22:23:23 +00:00
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
2016-05-12 22:23:23 +00:00
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)
2016-05-12 22:23:23 +00:00
else:
self.send_message(mto=receiver, mbody=msg,
2016-09-26 11:19:46 +00:00
mtype='groupchat')
2016-05-12 22:23:23 +00:00
update_id = update.update_id + 1
2016-05-08 21:12:04 +00:00
except NetworkError as e:
2016-09-26 11:19:46 +00:00
print(e)
2016-05-08 16:46:24 +00:00
sleep(1)
2017-07-09 01:04:15 +00:00
except Unauthorized as e:
2016-09-26 11:19:46 +00:00
print(e)
2016-05-08 21:12:04 +00:00
sleep(1)
2016-05-08 16:46:24 +00:00
except Exception as e:
2016-09-23 19:36:46 +00:00
update_id += 1
print(e)
2016-05-08 16:46:24 +00:00
def start(self, event):
2017-07-08 19:46:10 +00:00
"""Does some initial setup for XMPP and joins all mucs."""
2016-05-08 16:46:24 +00:00
self.get_roster()
self.send_presence()
for muc in self.muc_rooms:
self.plugin['xep_0045'].joinMUC(muc, self.nick, wait=True)
2016-05-08 16:46:24 +00:00
def muc_message(self, msg):
2017-07-08 19:46:10 +00:00
"""Muc message's handler."""
muc_room = str(msg['from']).split('/')[0]
index = self.muc_rooms.index(muc_room)
tg_group = self.groups[index]
2016-05-12 22:23:23 +00:00
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)
2016-05-12 22:23:23 +00:00
elif msg['mucnick'] != self.nick:
message = str(msg['from']).split('/')[1] + ': ' + str(msg['body'])
self.bot.sendMessage(tg_group, text=message)
2016-05-12 22:23:23 +00:00
def muc_online(self, presence):
2017-07-08 19:46:10 +00:00
"""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']]
2016-05-12 22:23:23 +00:00
def muc_offline(self, presence):
2017-07-08 19:46:10 +00:00
"""Muc presence's handler."""
user = presence['muc']['nick']
muc = presence['from'].bare
2016-05-12 22:23:23 +00:00
if user != self.nick:
self.xmpp_users[muc].remove(presence['muc']['nick'])
def say_users(self, service, muc, group):
2017-07-08 19:46:10 +00:00
"""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 = ""
2016-05-12 22:23:23 +00:00
msg = 'Telegram Users:' + tg_users
2016-05-12 22:23:23 +00:00
self.send_message(mto=muc, mbody=msg, mtype='groupchat')
2016-05-12 22:23:23 +00:00
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 = ""
2016-05-12 22:23:23 +00:00
msg = 'XMPP Users:' + xmpp_users
self.bot.sendMessage(group, text=msg)
2016-05-12 22:23:23 +00:00
def say_help(self, service, muc, group):
2017-07-08 19:46:10 +00:00
"""Help command."""
msg = 'Hi, I\'m ' + self.bot.username + '. I have two commands : ".us'\
'ers" and ".where".'
2016-05-12 22:23:23 +00:00
if service == 'xmpp':
self.send_message(mto=muc, mbody=msg, mtype='groupchat')
elif service == 'telegram':
self.bot.sendMessage(group, text=msg)
2016-09-26 11:19:46 +00:00
def say_where(self, service, muc, group):
2017-07-08 19:46:10 +00:00
"""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')
2016-05-12 22:23:23 +00:00
elif service == 'telegram':
msg = 'I\'m on ' + muc + '.'
self.bot.sendMessage(group, text=msg)
2016-09-26 11:19:46 +00:00
class HttpUpload():
2017-07-08 19:46:10 +00:00
"""HTTP upload main class."""
2016-09-26 11:19:46 +00:00
def __init__(self, parent_self):
2017-07-08 19:46:10 +00:00
"""Init... Yep."""
2016-09-26 11:19:46 +00:00
self.parent_self = parent_self
def discovery(self):
2017-07-08 19:46:10 +00:00
"""Discovers all server's components."""
2016-09-26 11:19:46 +00:00
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):
2017-07-08 19:46:10 +00:00
"""Discovers HTTP upload components attributes."""
2016-09-26 11:19:46 +00:00
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
2017-07-08 19:46:10 +00:00
response = str(info.send(timeout=30))
2016-09-26 11:19:46 +00:00
2017-07-08 19:46:10 +00:00
return response
2016-09-26 11:19:46 +00:00
def upload(self, component, verify_ssl, u_file, size):
2017-07-08 19:46:10 +00:00
"""Uploads to HTTP upload."""
2016-09-26 11:19:46 +00:00
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':
2016-09-26 11:19:46 +00:00
req = requests.put(put_url, data=open(u_file, 'rb'),
verify=False)
2017-07-08 19:46:10 +00:00
else:
2016-09-26 11:19:46 +00:00
req = requests.put(put_url, data=open(u_file, 'rb'))
return put_url
2016-05-08 16:46:24 +00:00
if __name__ == '__main__':
2016-09-26 11:19:46 +00:00
# parse config
2016-05-08 16:46:24 +00:00
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)
2016-09-26 11:19:46 +00:00
# assign values for the bot
2016-05-08 16:46:24 +00:00
jid = config[0]
password = config[1]
muc_rooms = config[2]
2016-05-08 16:46:24 +00:00
nick = config[3]
token = config[4]
groups = config[5]
verify_ssl = config[6]
2016-05-08 16:46:24 +00:00
xmpp = Jabbergram(jid, password, muc_rooms, nick, token, groups,
verify_ssl)
2016-05-08 16:46:24 +00:00
xmpp.register_plugin('xep_0045')
if xmpp.connect():
xmpp.process(block=True)
print("Done")
else:
print("Unable to connect.")