#!/usr/bin/env python3 import asyncio import re import signal import sys import time import slixmpp from configparser import SafeConfigParser from irc.bot import SingleServerIRCBot class IRCBot: def __init__(self, opts, inter): self.client = SingleServerIRCBot([(opts['server'], opts['port'])], opts['nick'], opts['nick']) self.conn = self.client.connection self.nick = opts['nick'] self.pure_nick = self.nick self.chan = opts['chan'] self.inter = inter self.loop = None def register_handlers(self): self.conn.add_global_handler('welcome', self.on_session_start) self.conn.add_global_handler('pubmsg', self.on_message) self.conn.add_global_handler('action', self.on_message) self.conn.add_global_handler('join', self.on_presence) self.conn.add_global_handler('part', self.on_presence) self.conn.add_global_handler('namreply', self.on_namreply) self.conn.add_global_handler('kick', self.on_kick) self.conn.add_global_handler('nicknameinuse', self.on_nicknameinuse) def join_chan(self): self.conn.part(self.chan, message='Replaced by new connection') self.conn.join(self.chan) def on_session_start(self, conn, event): print('Connected to IRC') self.join_chan() def on_message(self, conn, event): nick = event.source.split('!')[0] body = ''.join(event.arguments) typ = event.type if typ == 'action': body = '/me ' + body self.call_outside(self.inter.relay_message, 'irc', nick, body) def on_presence(self, conn, event): try: typ = event.type nick = event.source.nick if typ == 'part': if nick != self.nick: if nick in self.inter.get_irc_users(): self.call_outside(self.inter.remove_irc_user, nick) else: if nick != self.nick: if nick not in self.inter.get_irc_users(): self.call_outside(self.inter.append_irc_user, nick) except Exception as e: print(str(e), file=sys.stderr) def on_namreply(self, conn, event): for nick in event.arguments[2].split(): if nick != conn.get_nickname(): self.call_outside(self.inter.append_irc_user, nick) def on_kick(self, conn, event): self.nick = self.pure_nick conn.nick(self.nick) time.sleep(0.5) self.join_chan() def on_nicknameinuse(self, conn, event): self.nick = self.nick + '_' conn.nick(self.nick) def send_message(self, msg, lead_prefix='', prefix=''): buf = 460 result = [] try: for line in msg: for i in range(0, len(line), buf): result.append(line[i:i + buf]) self.conn.privmsg(self.chan, lead_prefix + result[0]) for r in result[1:]: time.sleep(0.5) self.conn.privmsg(self.chan, prefix + r) except Exception as e: print(str(e), file=sys.stderr) def call_outside(self, func, *args): assert self.loop self.loop.call_soon_threadsafe(func, *args) @asyncio.coroutine def run(self): self.loop = asyncio.get_event_loop() self.register_handlers() yield from self.loop.run_in_executor(None, self.client.start) class XMPPBot: def __init__(self, opts, inter, timeout=5.0): self.client = slixmpp.ClientXMPP(opts['jid'], opts['passwd']) self.client.register_plugin('xep_0199') # XMPP Ping. self.client_ping = self.client.plugin['xep_0199'] self.client.register_plugin('xep_0045') # XMPP MUC. self.client_muc = self.client.plugin['xep_0045'] self.muc_is_joined = False self.client_ping.timeout = self.timeout = timeout self.client_ping.keepalive = True self.nick = opts['nick'] self.pure_nick = self.nick self.muc = opts['muc'] self.inter = inter def register_handlers(self): self.client.add_event_handler('failed_all_auth', self.on_failed_all_auth) self.client.add_event_handler('session_start', self.on_session_start) self.client.add_event_handler('got_online', self.on_got_online) self.client.add_event_handler('disconnected', self.on_disconnected) self.client.add_event_handler('groupchat_message', self.on_message) self.client.add_event_handler('muc::%s::presence' % self.muc, self.on_presence) def connect(self): self.client.connect() def join_muc(self): @asyncio.coroutine def loop_cycle(): if self._join_muc_block > 0: return self._join_muc_block += 1 while not self.muc_is_joined: self.client_muc.join_muc(self.muc, self.nick) yield from asyncio.sleep(self.timeout) self._join_muc_block -= 1 asyncio.async(loop_cycle()) _join_muc_block = 0 def on_failed_all_auth(event): # print('could not connect!', file=sys.stderr) print('Could not connect to the server, or password mismatch!', file=sys.stderr) sys.exit(1) def on_session_start(self, event): # print('connected with %s' %con, file=sys.stderr) print('Connected to XMPP') self.client.get_roster() self.client.send_presence() def on_got_online(self, event): self.join_muc() @asyncio.coroutine def on_disconnected(self, event): self.muc_is_joined = False print('Connection lost, reattempting in %d seconds' % self.timeout) yield from asyncio.sleep(self.timeout) self.connect() def on_message(self, event): body = event['body'] nick = event['mucnick'] self.inter.relay_message('xmpp', nick, body) @asyncio.coroutine def on_presence(self, event): try: muc_plugin = self.client.plugin['xep_0045'] typ = event['muc']['type'] nick = event['muc']['nick'] if not typ: typ = event['type'] if not nick: nick = muc_plugin.get_nick(self.muc, event['from']) if typ == 'available': self.muc_is_joined = True elif typ == 'error': self.muc_is_joined = False if event['error']['code'] == '409': self.nick += '_' self.join_muc() elif typ == 'unavailable': if nick != self.nick: if nick in self.inter.get_xmpp_users(): self.inter.remove_xmpp_user(nick) else: self.muc_is_joined = False self.nick = self.pure_nick yield from asyncio.sleep(0.5) self.join_muc() else: if nick != self.nick: if nick not in self.inter.get_xmpp_users(): self.inter.append_xmpp_user(nick) except Exception as e: print(str(e), file=sys.stderr) def send_message(self, msg, prefix=''): try: msg[0] = prefix + msg[0] result = '\n'.join(msg) self.client.send_message(mto=self.muc, mbody=result, mtype='groupchat') except Exception as e: print(str(e), file=sys.stderr) @asyncio.coroutine def run(self): self.register_handlers() self.connect() class Intermedia: def __init__(self, shared_opts, irc_chan, xmpp_muc): self.irc_chan = irc_chan self.xmpp_muc = xmpp_muc self.ircbot = None self.xmppbot = None self.irc_users = [] self.xmpp_users = [] self.prefix = shared_opts['prefix'] self.owner = shared_opts['owner'] def set_bots(self, ircbot, xmppbot): self.ircbot = ircbot self.xmppbot = xmppbot def to_irc(self, msg, lead_prefix='', prefix=''): if self.ircbot: self.ircbot.send_message(msg, lead_prefix, prefix) def to_xmpp(self, msg, prefix=''): if self.xmppbot: self.xmppbot.send_message(msg, prefix) def relay_message(self, from_net, nick, body): if not self.ircbot or not self.xmppbot: return if from_net != 'irc' and from_net != 'xmpp': return if from_net == 'irc' and nick == self.ircbot.nick or \ from_net == 'xmpp' and nick == self.xmppbot.nick: return if not body or len(body) <= 0: return try: msg = body.replace('\r\n', '\n').replace('\r', '\n').split('\n') if msg and len(msg) > 0: if len(msg) == 1 and msg[0] == self.prefix + 'users': irc_users = ', '.join(self.get_irc_users()) xmpp_users = ', '.join(self.get_xmpp_users()) if irc_users: irc_users = '[ IRC Users ] ' + irc_users if xmpp_users: xmpp_users = '[ XMPP Users ] ' + xmpp_users if from_net == 'irc': for answer in [xmpp_users]: self.to_irc([answer]) elif from_net == 'xmpp': for answer in [irc_users]: self.to_xmpp([answer]) elif len(msg) == 1 and msg[0] == self.prefix + 'help': answer = 'The only command I have is \'' + self.prefix + \ 'users\'. Also, my owner is ' + self.owner + '.' if from_net == 'irc': self.to_irc([answer]) elif from_net == 'xmpp': self.to_xmpp([answer]) else: nick_prefix = '[' + nick + '] ' nick_prefix_me = '***' + nick + ' ' if (not re.match('^/me .+$', msg[0])): nick_prefix_lead = nick_prefix else: msg[0] = re.split('^/me ', msg[0])[1] nick_prefix_lead = nick_prefix_me if from_net == 'irc': self.to_xmpp(msg, prefix=nick_prefix_lead) elif from_net == 'xmpp': self.to_irc(msg, lead_prefix=nick_prefix_lead, prefix=nick_prefix) except Exception as e: print(str(e), file=sys.stderr) def get_irc_users(self): return self.irc_users def append_irc_user(self, user): self.irc_users.append(user) def remove_irc_user(self, user): self.irc_users.remove(user) def get_xmpp_users(self): return self.xmpp_users def append_xmpp_user(self, user): self.xmpp_users.append(user) def remove_xmpp_user(self, user): self.xmpp_users.remove(user) if __name__ == '__main__': config = SafeConfigParser() shared_opts = {} xmpp_opts = {} irc_opts = {} if len(sys.argv) > 1: config.read(sys.argv[1]) else: config.read('config.ini') if not config.sections(): print('Error: Configuration file does not exist or is empty.', file=sys.stderr) sys.exit(1) shared_opts['prefix'] = config.get('Shared', 'prefix') shared_opts['owner'] = config.get('Shared', 'owner') irc_opts['chan'] = config.get('IRC', 'channel') irc_opts['nick'] = config.get('IRC', 'nick') irc_opts['server'] = config.get('IRC', 'server') irc_opts['port'] = int(config.get('IRC', 'port')) xmpp_opts['jid'] = config.get('XMPP', 'jid') xmpp_opts['passwd'] = config.get('XMPP', 'password') xmpp_opts['muc'] = config.get('XMPP', 'muc') xmpp_opts['nick'] = config.get('XMPP', 'nick') signal.signal(signal.SIGINT, signal.SIG_DFL) loop = asyncio.get_event_loop() inter = Intermedia(shared_opts, irc_opts['chan'], xmpp_opts['muc']) ircbot = IRCBot(irc_opts, inter) xmppbot = XMPPBot(xmpp_opts, inter) inter.set_bots(ircbot, xmppbot) asyncio.async(xmppbot.run()) asyncio.async(ircbot.run()) loop.run_forever()