388 lines
12 KiB
Python
Executable File
388 lines
12 KiB
Python
Executable File
#!/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()
|