commit ac877be2aecc9d352ec205263db02420e2637ca8 Author: dsc Date: Mon Nov 15 17:36:09 2021 +0100 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..0209e6e --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +```text +virtualenv -p /usr/bin/python3 venv +source venv/bin/activate +pip install -r requirements.txt + +# edit settings.py + +ufw allow 7000 +python3 run.py +``` diff --git a/ircserver/__init__.py b/ircserver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ircserver/factory.py b/ircserver/factory.py new file mode 100644 index 0000000..1ecb734 --- /dev/null +++ b/ircserver/factory.py @@ -0,0 +1,40 @@ +import os +import re +import asyncio +from typing import Set +from datetime import datetime +import json + +from quart import Quart, render_template, request, redirect +import bottom + +import settings + +irc_bot = None +connected_websockets: Set[asyncio.Queue] = set() + + +async def _setup_irc(app: Quart): + global irc_bot + loop = asyncio.get_event_loop() + irc_bot = bottom.Client(host=settings.IRC_HOST, port=settings.IRC_PORT, ssl=settings.IRC_SSL, loop=loop) + from ircserver.irc import start + start() + #asyncio.create_task(message_worker()) + + +async def process_loop(): + while True: + await asyncio.sleep(1) + + +def create_app(): + global app + app = Quart(__name__) + + @app.before_serving + async def startup(): + await _setup_irc(app) + import ircserver.routes + + return app diff --git a/ircserver/irc.py b/ircserver/irc.py new file mode 100644 index 0000000..602344b --- /dev/null +++ b/ircserver/irc.py @@ -0,0 +1,70 @@ +from typing import List, Optional +from datetime import datetime +import os +import time +import asyncio +import random + +from ircserver.factory import connected_websockets +from ircserver.factory import irc_bot as bot +import settings + + +msg_queue = asyncio.Queue() +messages = [] + + +@bot.on('CLIENT_CONNECT') +async def connect(**kwargs): + bot.send('NICK', nick=settings.IRC_NICK) + bot.send('USER', user=settings.IRC_NICK, realname=settings.IRC_REALNAME) + + # Don't try to join channels until server sent MOTD + done, pending = await asyncio.wait( + [bot.wait("RPL_ENDOFMOTD"), bot.wait("ERR_NOMOTD")], + loop=bot.loop, + return_when=asyncio.FIRST_COMPLETED + ) + + # Cancel whichever waiter's event didn't come in. + for future in pending: + future.cancel() + + bot.send('JOIN', channel=settings.IRC_CHANNEL) + + +@bot.on('PING') +def keepalive(message, **kwargs): + bot.send('PONG', message=message) + + +@bot.on('client_disconnect') +def reconnect(**kwargs): + from quart import current_app as app + app.logger.warning("Lost IRC server connection") + time.sleep(3) + bot.loop.create_task(bot.connect()) + app.logger.warning("Reconnecting to IRC server") + + +@bot.on('PRIVMSG') +async def message(nick, target, message, **kwargs): + if nick == settings.IRC_NICK: + return + if target == settings.IRC_NICK: + target = nick + + msg = message + now = datetime.now() + now = time.mktime(now.timetuple()) + data = {"nick": nick, "message": msg, "date": now} + for ws in connected_websockets: + await ws.put({"data": [data]}) + messages.append(data) + + if len(messages) > 20: + messages.pop(0) + + +def start(): + bot.loop.create_task(bot.connect()) diff --git a/ircserver/routes.py b/ircserver/routes.py new file mode 100644 index 0000000..b5bcac7 --- /dev/null +++ b/ircserver/routes.py @@ -0,0 +1,48 @@ +import asyncio +import json +import time +from datetime import datetime + +from quart import websocket +from quart import current_app as app + +import settings +from ircserver.utils import collect_websocket + + +@app.websocket('/ws') +@collect_websocket +async def ws(queue): + from ircserver.irc import messages + from ircserver.factory import irc_bot as bot + if messages: + await websocket.send(json.dumps({"data": messages}).encode()) + + async def rx(): + while True: + buffer = await websocket.receive() + try: + if buffer: + bot.send("PRIVMSG", target=settings.IRC_CHANNEL, message=buffer.decode()) + + now = datetime.now() + now = time.mktime(now.timetuple()) + messages.append({"nick": "d4irc", "message": buffer.decode(), "now": now}) + if len(messages) > 20: + messages.pop(0) + except Exception as ex: + continue + + async def tx(): + while True: + data = await queue.get() + payload = json.dumps(data).encode() + await websocket.send(payload) + + consumer_task = asyncio.ensure_future(rx()) + producer_task = asyncio.ensure_future(tx()) + try: + await asyncio.gather(consumer_task, producer_task) + finally: + consumer_task.cancel() + producer_task.cancel() diff --git a/ircserver/utils.py b/ircserver/utils.py new file mode 100644 index 0000000..478ae0e --- /dev/null +++ b/ircserver/utils.py @@ -0,0 +1,25 @@ +import re +import json +import asyncio +import os +import random +from datetime import datetime +from collections import Counter +from functools import wraps +from typing import List, Union +from io import BytesIO + +import settings + + +def collect_websocket(func): + @wraps(func) + async def wrapper(*args, **kwargs): + from ircserver.factory import connected_websockets + queue = asyncio.Queue() + connected_websockets.add(queue) + try: + return await func(queue, *args, **kwargs) + finally: + connected_websockets.remove(queue) + return wrapper diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d93092e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +quart +bottom diff --git a/run.py b/run.py new file mode 100644 index 0000000..20bc685 --- /dev/null +++ b/run.py @@ -0,0 +1,5 @@ +from ircserver.factory import create_app +import settings + +app = create_app() +app.run(settings.BIND_HOST, 7000, debug=settings.DEBUG) diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..6767b03 --- /dev/null +++ b/settings.py @@ -0,0 +1,9 @@ +IRC_HOST = "94.125.182.252" +IRC_PORT = 6667 +IRC_SSL = False +IRC_NICK = "d4irc" +IRC_CHANNEL = "#maemo-leste" +IRC_REALNAME = "d4irc" +DEBUG = True +URL = '/' +BIND_HOST = "135.125.235.26"