From a96663a5d406808a608738d414a864fb2733c51b Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 24 Apr 2016 14:40:45 +0200 Subject: [PATCH] Initial commit --- .gitignore | 2 + bugbot/connection.py | 235 +++++++++++++++++++++++++++++++++++++++++++ bugbot/download.py | 100 ++++++++++++++++++ bugbot/format.py | 0 bugbot/log.py | 0 config.json | 0 main.py | 0 7 files changed, 337 insertions(+) create mode 100644 .gitignore create mode 100644 bugbot/connection.py create mode 100644 bugbot/download.py create mode 100644 bugbot/format.py create mode 100644 bugbot/log.py create mode 100644 config.json create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6eac956 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +logs.db +logsreadable/* diff --git a/bugbot/connection.py b/bugbot/connection.py new file mode 100644 index 0000000..0913c59 --- /dev/null +++ b/bugbot/connection.py @@ -0,0 +1,235 @@ +import json +import time +import threading +import websocket +from websocket import WebSocketException as WSException + + +ROOM_FORMAT = "wss://euphoria.io/room/{}/ws" + + +class Connection(): + """ + Stays connected to a room in its own thread. + Callback functions are called when a packet is received. + """ + + def __init__(self, room): + """ + room - name of the room to connect to + """ + + self.room = room + + self.stopping = False + + self.ws = None + self.send_id = 0 + self.callbacks = {} + self.id_callbacks = {} + + def connect(self, tries=-1, delay=10): + """ + _connect(tries, delay) -> bool + + tries - maximum number of retries + -1 -> retry indefinitely + + Returns True on success, False on failure. + + Connect to the room. + """ + + while tries != 0: + try: + self.ws = websocket.create_connection( + ROOM_FORMAT.format(self.room), + enable_multithread=True + ) + return True + except WSException: + if tries > 0: + tries -= 1 + if tries != 0: + time.sleep(delay) + return False + + def disconnect(self): + """ + disconnect() -> None + + Reconnect to the room. + WARNING: To completely disconnect, use stop(). + """ + + if self.ws: + self.ws.close() + self.ws = None + + def launch(self): + """ + launch() -> Thread + + Connect to the room and spawn a new thread running run. + """ + + if self.connect(tries=1): + self.thread = threading.Thread(target=self.run, name=self.room) + self.thread.start() + return self.thread + else: + self.stop() + + def run(self): + """ + run() -> None + + Receive messages. + """ + + while not self.stopping: + try: + self.handle_json(self.ws.recv()) + except (WSException, OSError, ValueError): + if not self.stopping: + self.disconnect() + self.connect() + + def stop(self): + """ + stop() -> None + + Close the connection to the room. + """ + + self.stopping = True + self.disconnect() + + def join(self): + """ + join() -> None + + Join the thread spawned by launch. + """ + + if self.thread: + self.thread.join() + + def add_callback(self, ptype, callback, *args, **kwargs): + """ + add_callback(ptype, callback) -> None + + Add a function to be called when a packet of type ptype is received. + """ + + if not ptype in self.callbacks: + self.callbacks[ptype] = [] + + callback_info = { + "callback": callback, + "args": args, + "kwargs": kwargs + } + + self.callbacks[ptype].append(callback_info) + + def add_id_callback(self, pid, callback, *args, **kwargs): + """ + add_id_callback(pid, callback) -> None + + Add a function to be called when a packet with id pid is received. + """ + + if not pid in self.id_callbacks: + self.id_callbacks[pid] = [] + + callback_info = { + "callback": callback, + "args": args, + "kwargs": kwargs + } + + self.id_callbacks[pid].append(callback_info) + + def call_callback(self, event, *args): + """ + call_callback(event) -> None + + Call all callbacks subscribed to the event with *args. + """ + + if event in self.callbacks: + for c_info in self.callbacks[event]: + c = c_info["callback"] + args = c_info["args"] + args + kwargs = c_info["kwargs"] + c(*args, **kwargs) + + def call_id_callback(self, pid, *args): + """ + call_callback(pid) -> None + + Call all callbacks subscribed to the pid with *args. + """ + + if pid in self.id_callbacks: + for c_info in self.id_callbacks.pop(pid): + c = c_info["callback"] + args = c_info["args"] + args + kwargs = c_info["kwargs"] + c(*args, **kwargs) + + def handle_json(self, data): + """ + handle_json(data) -> None + + Handle incoming 'raw' data. + """ + + packet = json.loads(data) + self.handle_packet(packet) + + def handle_packet(self, packet): + """ + handle_packet(ptype, data) -> None + + Handle incoming packets + """ + + if "data" in packet: + data = packet["data"] + else: + data = None + + self.call_callback(packet["type"], data) + + if "id" in packet: + self.call_id_callback(packet["id"], data) + + def send_json(self, data): + """ + send_json(data) -> None + + Send 'raw' json. + """ + + if self.ws: + try: + self.ws.send(json.dumps(data)) + except WSException: + self.disconnect() + + def send_packet(self, ptype, **kwargs): + """ + send_packet(ptype, **kwargs) -> None + + Send a formatted packet. + """ + + packet = { + "type": ptype, + "data": kwargs or None, + "id": str(self.send_id) + } + self.send_id += 1 + self.send_json(packet) diff --git a/bugbot/download.py b/bugbot/download.py new file mode 100644 index 0000000..1bbffc0 --- /dev/null +++ b/bugbot/download.py @@ -0,0 +1,100 @@ +import tempfile + +import connection + +class Downloader(): + """ + Update or redownload a room's log. + """ + + def __init__(self, room, logfile, password=None): + """ + room - name of the room to download the logs of + logfile - path to the file to save the log in + password - password of said room, optional + """ + + pass + + def _handle_ping_event(self, data): + """ + _handle_ping_event(data) -> None + + Pong! + """ + + pass + + def _handle_bounce_event(self, data): + """ + _handle_bounce_event(data) -> None + + Authenticate if possible, otherwise give up and stop. + """ + + pass + + def _handle_auth_reply(self, data): + """ + _handle_auth_reply(data) -> None + + Disconnect if authentication unsucessful. + """ + + pass + + def _handle_disconnect_event(self, data): + """ + _handle_disconnect_event(data) -> None + + Immediately disconnect. + """ + + pass + + def _handle_snapshot_event(self, data): + """ + _handle_snapshot_event(data) -> None + + Save messages and request further messages + """ + + pass + + def _handle_get_message_reply(self, data): + """ + _handle_get_message_reply(data) -> None + + Append untruncated message to log file and then continue + transferring the messages from the temp file to the + log file. + """ + + pass + + def _handle_log_reply(self, data): + """ + _handle_log_reply(data) -> None + + Save messages received to temp file. + """ + + pass + + def launch(self): + """ + launch() -> None + + Start the download in a separate thread. + """ + + pass + + def transfer(self): + """ + transfer() -> None + + Transfer the messages from the temporary file to the log file. + """ + + pass diff --git a/bugbot/format.py b/bugbot/format.py new file mode 100644 index 0000000..e69de29 diff --git a/bugbot/log.py b/bugbot/log.py new file mode 100644 index 0000000..e69de29 diff --git a/config.json b/config.json new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..e69de29