diff --git a/nodeping2irc.py b/nodeping2irc.py index 34aa312..5d8860d 100644 --- a/nodeping2irc.py +++ b/nodeping2irc.py @@ -1,5 +1,6 @@ import aiohttp import aiohttp.web +import argparse import asyncio import collections import concurrent.futures @@ -65,11 +66,15 @@ class MessageQueue: class IRCClientProtocol(asyncio.Protocol): - def __init__(self, messageQueue, stopEvent, loop): + def __init__(self, messageQueue, stopEvent, loop, nick, real, channel): logging.debug(f'Protocol init {id(self)}: {messageQueue} {id(messageQueue)}, {stopEvent}, {loop}') self.messageQueue = messageQueue self.stopEvent = stopEvent self.loop = loop + self.nick = nick + self.real = real + self.channel = channel + self.channelb = channel.encode('utf-8') self.buffer = b'' self.connected = False @@ -81,9 +86,10 @@ class IRCClientProtocol(asyncio.Protocol): logging.info('Connected') self.transport = transport self.connected = True - self.send(b'NICK npbot') - self.send(b'USER npbot npbot npbot :I am a bot.') - self.send(b'JOIN #nodeping') + nickb = self.nick.encode('utf-8') + self.send(b'NICK ' + nickb) + self.send(b'USER ' + nickb + b' ' + nickb + b' ' + nickb + b' :' + self.real.encode('utf-8')) + self.send(b'JOIN ' + self.channelb) asyncio.create_task(self.send_messages()) async def _get_message(self): @@ -113,7 +119,7 @@ class IRCClientProtocol(asyncio.Protocol): logging.debug(f'{id(self)}: got message: {message!r}') if message is None: break - self.send(b'PRIVMSG #nodeping :' + message.encode('utf-8')) + self.send(b'PRIVMSG ' + self.channelb + b' :' + message.encode('utf-8')) #TODO self.messageQueue.putleft_nowait if delivery fails await asyncio.sleep(1) # Rate limit @@ -142,15 +148,20 @@ class IRCClientProtocol(asyncio.Protocol): class WebServer: - def __init__(self, messageQueue): + def __init__(self, messageQueue, host, port, auth): self.messageQueue = messageQueue + self.host = host + self.port = port + self.auth = auth + if auth: + self.authHeader = f'Basic {base64.b64encode(auth.encode("utf-8")).decode("utf-8")}' self._app = aiohttp.web.Application() self._app.add_routes([aiohttp.web.post('/nodeping', self.nodeping_post)]) async def run(self, stopEvent): runner = aiohttp.web.AppRunner(self._app) await runner.setup() - site = aiohttp.web.TCPSite(runner, '127.0.0.1', 8080) + site = aiohttp.web.TCPSite(runner, self.host, self.port) await site.start() await stopEvent.wait() await runner.cleanup() @@ -158,7 +169,7 @@ class WebServer: async def nodeping_post(self, request): logging.info(f'Received request with data: {await request.read()!r}') authHeader = request.headers.get('Authorization') - if not authHeader or authHeader != 'Basic YXJjaGl2ZXRlYW06aXNvbmZpcmU=': #TODO move out of source code + if self.auth and (not authHeader or authHeader != self.authHeader): return aiohttp.web.HTTPForbidden() try: data = await request.json() @@ -176,13 +187,12 @@ class WebServer: return aiohttp.web.HTTPOk() -async def run_irc(loop, messageQueue, sigintEvent): +async def run_irc(loop, messageQueue, sigintEvent, host, port, ssl, nick, real, channel): stopEvent = asyncio.Event() while True: stopEvent.clear() try: -# transport, protocol = await loop.create_connection(lambda: IRCClientProtocol(messageQueue, stopEvent, loop), 'irc.hackint.org', 6697, ssl = True) - transport, protocol = await loop.create_connection(lambda: IRCClientProtocol(messageQueue, stopEvent, loop), '127.0.0.1', 8888) + transport, protocol = await loop.create_connection(lambda: IRCClientProtocol(messageQueue, stopEvent, loop, nick = nick, real = real, channel = channel), host, port, ssl = ssl) try: await asyncio.wait((stopEvent.wait(), sigintEvent.wait()), return_when = concurrent.futures.FIRST_COMPLETED) finally: @@ -194,12 +204,29 @@ async def run_irc(loop, messageQueue, sigintEvent): break -async def run_webserver(loop, messageQueue, sigintEvent): - server = WebServer(messageQueue) +async def run_webserver(loop, messageQueue, sigintEvent, host, port, auth): + server = WebServer(messageQueue, host, port, auth) await server.run(sigintEvent) +def parse_args(): + parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--irchost', type = str, help = 'IRC server hostname', default = 'irc.hackint.org') + parser.add_argument('--ircport', type = int, help = 'IRC server port', default = 6697) + parser.add_argument('--ircssl', choices = ['yes', 'no', 'insecure'], help = 'enable, disable, or use insecure SSL/TLS', default = 'yes') + parser.add_argument('--ircnick', help = 'IRC nickname', default = 'npbot') + parser.add_argument('--ircreal', help = 'IRC realname', default = 'I am a bot.') + parser.add_argument('--ircchannel', help = 'IRC channel to join and post messages', default = '#nodeping') + parser.add_argument('--webhost', type = str, help = 'web server host to bind to', default = '127.0.0.1') + parser.add_argument('--webport', type = int, help = 'web server port to bind to', default = 8080) + parser.add_argument('--webauth', type = str, help = 'basic auth data (user:pass, or None to disable the check)', default = None) + return parser.parse_args() + + async def main(): + args = parse_args() + ssl = {'yes': True, 'no': False, 'insecure': ssl.SSLContext()}[args.ircssl] + loop = asyncio.get_running_loop() messageQueue = MessageQueue() @@ -211,7 +238,9 @@ async def main(): sigintEvent.set() loop.add_signal_handler(signal.SIGINT, sigint_callback) - await asyncio.gather(run_irc(loop, messageQueue, sigintEvent), run_webserver(loop, messageQueue, sigintEvent)) + irc = run_irc(loop, messageQueue, sigintEvent, host = args.irchost, port = args.ircport, ssl = ssl, nick = args.ircnick, real = args.ircreal, channel = args.ircchannel) + webserver = run_webserver(loop, messageQueue, sigintEvent, host = args.webhost, port = args.webport, auth = args.webauth) + await asyncio.gather(irc, webserver) asyncio.run(main())