blob: 9853c36cf808fe707785a59946945e0b63e4f225 [file] [log] [blame]
import logging
import sleekxmpp
import glob
import urllib
# local imports
import hangupsthread
import rosterdb
from sleekxmpp import ComponentXMPP
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
from hangups.auth import GoogleAuthError
class XMPPThread(ComponentXMPP):
def __init__(self, name, host, secret='secret', port=5347):
ComponentXMPP.__init__(self, name, secret, host, port,
##
# Note Here: this seems to be required for
# jabberd2. If the message event isn't
# firing change this to False
##
use_jc_ns=True)
self.name = name
#self.roster.set_backend(db=rosterdb.RosterDB('database.sqlite', 'roster'))
self.registerPlugin('xep_0030') # service discovery
self.registerPlugin('xep_0004') # Data Forms
self.registerPlugin('xep_0054') # vcard
self.registerPlugin('xep_0060') # PubSub
self.registerPlugin('xep_0077') # registration
self.registerPlugin('xep_0085') # chat state notification
self.registerPlugin('xep_0172') # user nick
self.registerPlugin('xep_0199') # XMPP Ping
self.registerPlugin('xep_0280') # carbons
self['xep_0030'].add_identity(category='gateway', itype='hangouts', name='Google Hangouts Transport')
# god knows why the xep-0077 plugin doesn't do component registration
self['xep_0030'].add_feature('jabber:iq:register')
self.register_handler(Callback('Handle Registration',
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.default_ns),
self._disco_register_handler))
# all handlers should be threaded
self.add_event_handler('message', self._message, threaded=True)
self.add_event_handler('chatstate', self._chatstate, threaded=True)
self.add_event_handler('presence_available', self._presence_available,
threaded=True)
self.add_event_handler('presence_unavailable',
self._presence_unavailable,
threaded=True)
self.add_event_handler('presence_probe', self._presence_probe,
threaded=True)
# session handler is unthreaded so we set up all the useful stuff first
self.add_event_handler("session_start", self._session_start, threaded=False)
self.add_event_handler("session_end", self._session_end, threaded=False)
self.hangups_threads = {}
def _session_start(self, event):
# Anyone can subscribe to the transport bot identity
self.roster[self.name].auto_authorize = True
self.roster[self.name].auto_subscribe = True
# However you see it offline until you log in successfully
# to the google service
self.roster[self.name].send_presence(ptype='unavailable')
for token in glob.glob('*-refresh_token.cache'):
name = token.replace('-refresh_token.cache', '')
logging.info('starting Hangups thread for %s', name)
try:
thread = hangupsthread.HangupsThread(self, name)
self.hangups_threads[name] = thread
thread.start()
self.add_subscription(self.name, name)
self.send_presence(pto=name, ptype='available')
except GoogleAuthError as e:
logging.error("FAILED TO RUN THREAD FOR %s", name, e)
def _session_end(self, event):
for name in self.hangups_threads:
logging.info('Stopping Hangups thread for %s', name)
self.hangups_threads[name].stop()
def _disco_register_handler(self, iq):
if iq['type'] == 'set':
ifrom = iq['from'].bare
reg = iq['register']
iq.reply()
try:
thread = hangupsthread.HangupsThread(self, ifrom,
gmail=reg['username'],
password=reg['password'])
self.hangups_threads[ifrom] = thread
thread.start()
to=iq['to']
# successfully registered
iq.send()
# now force the client to add the bot as a buddy
self.send_presence(pto=to, ptype='subscribe')
except GoogleAuthError as e:
iq.error()
iq['error']['condition'] = 'not-allowed'
iq['error']['text'] = 'Login to Google Failed: {}'.format(e)
iq.send()
elif iq['type'] == 'get':
iq.reply(clear=True)
iq.enable('register')
register = iq['register']
register['instructions'] = 'Please Enter your gmail login'
register.add_field('password')
register.add_field('username')
iq.send()
def _message(self, msg):
mto = msg.get_to().bare
mfrom = msg.get_from().bare
if mto == self.name:
self.bot_message(msg)
elif mto in self.roster:
self.hangups_threads[mfrom].send_message(msg)
else:
msg.reply('got it').send()
def bot_message(self, msg):
"""handle messages directly to the service
A chatbot is far easier than a service implementation because
xmpp clients are very uneven in their service implementations
once you have the service subscribed, any client can send
messages to it
current commands are:
online - set the bot online
offline - set the bot offline"""
cmd = msg['body'].split(' ')
# strip resource so actions go to all clients
mfrom = msg.get_from().bare
cmd[0] = cmd[0].lower()
if cmd[0] == 'offline':
self.send_presence(ptype='unavailable', pto=mfrom)
elif cmd[0] == 'online':
self.send_presence(pto=mfrom)
elif cmd[0] == 'query':
if not mfrom in self.hangups_threads:
msg.reply("no jid found").send()
return
body=[]
for user in self.hangups_threads[mfrom].user_list.get_all():
if user.is_self:
continue
body.append(user.full_name + '(' + user.id_.chat_id +')')
jid=sleekxmpp.JID(local=user.id_.gaia_id, domain=self.name)
self.roster.add(jid)
self.roster[jid][mfrom]['whitelisted'] = True
self.roster[jid][jid]['name'] = user.full_name
self.roster[jid].send_presence(ptype='subscribe',pto=mfrom,pnick=user.full_name)
body = "\n".join(body)
msg.reply(body).send()
else:
msg.reply('Command ' + msg['body'] + ' not understood').send()
def add_subscription(self, jid, to):
self.roster[jid][to]['to'] = True
self.roster[jid][to]['from'] = True
self.roster[jid][to]['subscription'] = 'both'
def incoming_typing(self, to, user, state):
jid = sleekxmpp.JID(local=user.id_.gaia_id, domain=self.name)
msg = self.Message(sfrom=jid, sto=to, stype='chat')
msg['chat_state'] = state
msg.send()
def incoming_user(self, to, user):
jid=sleekxmpp.JID(local=user.id_.gaia_id, domain=self.name)
self.roster[jid][to]['whitelisted'] = True
self.add_subscription(jid, to)
vcard = self['xep_0054'].make_vcard()
vcard['FN'] = user.full_name
vcard['NICKNAME'] = user.full_name
if user.photo_url:
vcard['PHOTO']['TYPE'] = 'image/jpeg'
try:
vcard['PHOTO']['BINVAL'] = urllib.request.urlopen('https:' + user.photo_url).read()
except (urllib.error.HTTPError):
logging.info("Failed to load photo from %s", user.photo_url)
self['xep_0054'].publish_vcard(jid=jid, vcard=vcard)
def incoming_presence(self, to, user, reachable, available, mood_message):
jid=sleekxmpp.JID(local=user.id_.gaia_id, domain=self.name)
if reachable:
if not available:
ptype = 'away'
else:
ptype = 'available'
else:
ptype = 'unavailable'
self.roster[jid].send_presence(ptype=ptype, pto = to,
pnick = user.full_name,
pstatus = mood_message)
def incoming_message(self, to, user, text):
mfrom = sleekxmpp.JID(local=user.id_.gaia_id, domain=self.name)
msg = self.send_message(to, text, mfrom=mfrom)
def incoming_disconnect(self, to):
for jid in self.roster:
if jid == self.name:
continue
self.roster[jid][to].send_presence(ptype = 'unavailable')
def _chatstate(self, msg):
mfrom = msg.get_from().bare
mto = msg.get_to().bare
if mto != self.name:
self.hangups_threads[mfrom].send_typing_notification(msg)
def _presence_available(self, msg):
pass
def _presence_probe(self, msg):
self._presence_available(msg)
def _presence_unavailable(self, msg):
pass