Compare commits

..

7 Commits
dev ... master

Author SHA1 Message Date
Alexei Sorokin
659bc4c3aa Enable ping keepalive 2017-06-26 19:17:42 +03:00
Alexei Sorokin
0ff3f9754b Fix a reconnecting regression 2017-06-19 19:47:42 +03:00
Alexei Sorokin
33a9780d97 Improve reconnecting ability
And increase timeout to 5 seconds.
2017-06-14 21:02:26 +03:00
Alexei Sorokin
2349db325d irc: Hide threading hacks inside the class 2017-06-06 19:44:03 +03:00
Alexei Sorokin
aa67de2acc Switch to asyncio and slixmpp 2017-06-04 15:32:26 +03:00
Alexei Sorokin
b702c1af45 xmpp: Fix issues with reconnecting 2017-06-03 22:12:30 +03:00
drymer
60dfc17c67 Modify authors 2017-02-22 17:20:17 +00:00
4 changed files with 102 additions and 74 deletions

View File

@ -1 +1 @@
The program was made by ninguno. drymer just polish it a bit. The program was made by ninguno. drymer just polished it a bit. XRevan86 rewrote it and made it better.

View File

@ -1,10 +1,10 @@
# hybridbot # hybridbot
Hybridbot is a bot that relays messages from a IRC channel to an XMPP MUC and vice versa. First of all, you need to install sleekxmpp and irc. Hybridbot is a bot that relays messages from a IRC channel to an XMPP MUC and vice versa. First of all, you need to install slixmpp and irc.
su -c "pip install sleekxmpp irc" su -c "pip3 install slixmpp irc"
# or # or
su -c "pip install -r requirements.txt" su -c "pip3 install -r requirements.txt"
It's configuration is simple, you have to create a config file per relay, as this example (remember to change the values): It's configuration is simple, you have to create a config file per relay, as this example (remember to change the values):
@ -32,10 +32,10 @@ The option "owner" is a string that will be printed when issuing the ".help" com
To execute it, just do: To execute it, just do:
python hybridbot.py myconfig.ini python3 hybridbot.py myconfig.ini
Or if the config file is named "config.ini", just do: Or if the config file is named "config.ini", just do:
python hybridbot.py python3 hybridbot.py
And on the MUC or the IRC channel, you can issue two commands, ".help" and ".users". And on the MUC or the IRC channel, you can issue two commands, ".help" and ".users".

View File

@ -1,17 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
import re import re
import signal import signal
import sys import sys
import time import time
from threading import Thread import slixmpp
from irc.bot import SingleServerIRCBot
import sleekxmpp
if sys.version_info.major >= 3:
from configparser import SafeConfigParser from configparser import SafeConfigParser
else: from irc.bot import SingleServerIRCBot
from ConfigParser import SafeConfigParser
class IRCBot: class IRCBot:
@ -19,11 +15,11 @@ class IRCBot:
self.client = SingleServerIRCBot([(opts['server'], opts['port'])], self.client = SingleServerIRCBot([(opts['server'], opts['port'])],
opts['nick'], opts['nick']) opts['nick'], opts['nick'])
self.conn = self.client.connection self.conn = self.client.connection
self.opts = opts
self.nick = opts['nick'] self.nick = opts['nick']
self.pure_nick = self.nick self.pure_nick = self.nick
self.chan = opts['chan'] self.chan = opts['chan']
self.inter = inter self.inter = inter
self.loop = None
def register_handlers(self): def register_handlers(self):
self.conn.add_global_handler('welcome', self.on_session_start) self.conn.add_global_handler('welcome', self.on_session_start)
@ -51,7 +47,7 @@ class IRCBot:
if typ == 'action': if typ == 'action':
body = '/me ' + body body = '/me ' + body
self.inter.relay_message('irc', nick, body) self.call_outside(self.inter.relay_message, 'irc', nick, body)
def on_presence(self, conn, event): def on_presence(self, conn, event):
try: try:
@ -61,19 +57,19 @@ class IRCBot:
if typ == 'part': if typ == 'part':
if nick != self.nick: if nick != self.nick:
if nick in self.inter.get_irc_users(): if nick in self.inter.get_irc_users():
self.inter.remove_irc_user(nick) self.call_outside(self.inter.remove_irc_user, nick)
else: else:
if nick != self.nick: if nick != self.nick:
if nick not in self.inter.get_irc_users(): if nick not in self.inter.get_irc_users():
self.inter.append_irc_user(nick) self.call_outside(self.inter.append_irc_user, nick)
except Exception as e: except Exception as e:
sys.stderr.write(str(e) + '\n') print(str(e), file=sys.stderr)
def on_namreply(self, conn, event): def on_namreply(self, conn, event):
for nick in event.arguments[2].split(): for nick in event.arguments[2].split():
if nick != conn.get_nickname(): if nick != conn.get_nickname():
self.inter.append_irc_user(nick) self.call_outside(self.inter.append_irc_user, nick)
def on_kick(self, conn, event): def on_kick(self, conn, event):
self.nick = self.pure_nick self.nick = self.pure_nick
@ -101,51 +97,95 @@ class IRCBot:
self.conn.privmsg(self.chan, prefix + r) self.conn.privmsg(self.chan, prefix + r)
except Exception as e: except Exception as e:
sys.stderr.write(str(e) + '\n') print(str(e), file=sys.stderr)
def start(self): 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() self.register_handlers()
self.client.start() yield from self.loop.run_in_executor(None, self.client.start)
class XMPPBot: class XMPPBot:
def __init__(self, opts, inter): def __init__(self, opts, inter, timeout=5.0):
if sys.version_info.major < 3: self.client = slixmpp.ClientXMPP(opts['jid'], opts['passwd'])
sleekxmpp.util.misc_ops.setdefaultencoding('utf-8')
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.client = sleekxmpp.ClientXMPP(opts['jid'], opts['passwd'])
self.opts = opts
self.nick = opts['nick'] self.nick = opts['nick']
self.pure_nick = self.nick self.pure_nick = self.nick
self.muc = opts['muc'] self.muc = opts['muc']
self.inter = inter self.inter = inter
def register_handlers(self): 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('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('groupchat_message', self.on_message)
self.client.add_event_handler('muc::%s::presence' % self.muc, self.client.add_event_handler('muc::%s::presence' % self.muc,
self.on_presence) self.on_presence)
def join_muc(self): def connect(self):
muc_plugin = self.client.plugin['xep_0045'] self.client.connect()
if self.muc in muc_plugin.getJoinedRooms(): def join_muc(self):
muc_plugin.leaveMUC(self.muc, self.nick, @asyncio.coroutine
msg='Replaced by new connection') def loop_cycle():
muc_plugin.joinMUC(self.muc, self.nick, wait=True) 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): def on_session_start(self, event):
# print('connected with %s' %con, file=sys.stderr)
print('Connected to XMPP') print('Connected to XMPP')
self.client.get_roster() self.client.get_roster()
self.client.send_presence() self.client.send_presence()
def on_got_online(self, event):
self.join_muc() 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): def on_message(self, event):
body = event['body'] body = event['body']
nick = event['mucnick'] nick = event['mucnick']
self.inter.relay_message('xmpp', nick, body) self.inter.relay_message('xmpp', nick, body)
@asyncio.coroutine
def on_presence(self, event): def on_presence(self, event):
try: try:
muc_plugin = self.client.plugin['xep_0045'] muc_plugin = self.client.plugin['xep_0045']
@ -155,11 +195,15 @@ class XMPPBot:
if not typ: if not typ:
typ = event['type'] typ = event['type']
if not nick: if not nick:
nick = muc_plugin.getNick(self.muc, event['from']) nick = muc_plugin.get_nick(self.muc, event['from'])
if typ == 'error': if typ == 'available':
self.muc_is_joined = True
elif typ == 'error':
self.muc_is_joined = False
if event['error']['code'] == '409': if event['error']['code'] == '409':
self.nick = self.nick + '_' self.nick += '_'
self.join_muc() self.join_muc()
elif typ == 'unavailable': elif typ == 'unavailable':
@ -167,8 +211,9 @@ class XMPPBot:
if nick in self.inter.get_xmpp_users(): if nick in self.inter.get_xmpp_users():
self.inter.remove_xmpp_user(nick) self.inter.remove_xmpp_user(nick)
else: else:
self.muc_is_joined = False
self.nick = self.pure_nick self.nick = self.pure_nick
time.sleep(0.5) yield from asyncio.sleep(0.5)
self.join_muc() self.join_muc()
else: else:
@ -177,7 +222,7 @@ class XMPPBot:
self.inter.append_xmpp_user(nick) self.inter.append_xmpp_user(nick)
except Exception as e: except Exception as e:
sys.stderr.write(str(e) + '\n') print(str(e), file=sys.stderr)
def send_message(self, msg, prefix=''): def send_message(self, msg, prefix=''):
try: try:
@ -187,20 +232,12 @@ class XMPPBot:
mtype='groupchat') mtype='groupchat')
except Exception as e: except Exception as e:
sys.stderr.write(str(e) + '\n') print(str(e), file=sys.stderr)
def start(self): @asyncio.coroutine
self.client.register_plugin('xep_0045') # XMPP MUC. def run(self):
self.client.register_plugin('xep_0199') # XMPP Ping.
if self.client.connect():
# sys.stderr.write('connected with %s\n'%con)
self.register_handlers() self.register_handlers()
self.client.process(block=True) self.connect()
else:
# sys.stderr.write('could not connect!\n')
sys.stderr.write('Could not connect to server, or password ' +
'mismatch!\n')
sys.exit(1)
class Intermedia: class Intermedia:
@ -288,7 +325,7 @@ class Intermedia:
prefix=nick_prefix) prefix=nick_prefix)
except Exception as e: except Exception as e:
sys.stderr.write(str(e) + '\n') print(str(e), file=sys.stderr)
def get_irc_users(self): def get_irc_users(self):
return self.irc_users return self.irc_users
@ -320,8 +357,8 @@ if __name__ == '__main__':
config.read('config.ini') config.read('config.ini')
if not config.sections(): if not config.sections():
sys.stderr.write('Error: Configuration file does not exist ' + print('Error: Configuration file does not exist or is empty.',
'or is empty.\n') file=sys.stderr)
sys.exit(1) sys.exit(1)
shared_opts['prefix'] = config.get('Shared', 'prefix') shared_opts['prefix'] = config.get('Shared', 'prefix')
@ -338,22 +375,13 @@ if __name__ == '__main__':
xmpp_opts['nick'] = config.get('XMPP', 'nick') xmpp_opts['nick'] = config.get('XMPP', 'nick')
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)
loop = asyncio.get_event_loop()
while True:
inter = Intermedia(shared_opts, irc_opts['chan'], xmpp_opts['muc']) inter = Intermedia(shared_opts, irc_opts['chan'], xmpp_opts['muc'])
ircbot = IRCBot(irc_opts, inter) ircbot = IRCBot(irc_opts, inter)
xmppbot = XMPPBot(xmpp_opts, inter) xmppbot = XMPPBot(xmpp_opts, inter)
inter.set_bots(ircbot, xmppbot) inter.set_bots(ircbot, xmppbot)
irc_thread = Thread(target=ircbot.start, args=()) asyncio.async(xmppbot.run())
xmpp_thread = Thread(target=xmppbot.start, args=()) asyncio.async(ircbot.run())
loop.run_forever()
irc_thread.daemon = True
xmpp_thread.daemon = True
irc_thread.start()
time.sleep(1)
xmpp_thread.start()
irc_thread.join()
xmpp_thread.join()

View File

@ -1,2 +1,2 @@
sleekxmpp>=1.2.0 slixmpp
irc>=5.0.1 irc>=5.0.1