From 04364c6b3feb15bf1ac0202f26fd332dbc128ea2 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 2 Sep 2017 10:37:58 +0000 Subject: [PATCH] Add basic functionality Bots can now - stay connected - set their nick --- yaboli/TestBot.py | 5 +-- yaboli/connection.py | 55 ++++++++++++++++----------------- yaboli/controller.py | 10 +++++- yaboli/room.py | 72 +++++++++++++++++++++++++++++++++++++++++--- yaboli/utils.py | 29 ++++++++++++++++++ 5 files changed, 137 insertions(+), 34 deletions(-) diff --git a/yaboli/TestBot.py b/yaboli/TestBot.py index a98f922..6eba07c 100644 --- a/yaboli/TestBot.py +++ b/yaboli/TestBot.py @@ -9,8 +9,9 @@ class TestBot(Controller): def __init__(self, roomname): super().__init__(roomname) - #async def on_connected(self): - #await self.room.set_nick("TestBot") + async def on_snapshot(self, user_id, session_id, version, listing, log, nick=None, + pm_with_nick=None, pm_with_user_id=None): + await self.room.nick("TestBot") async def on_hello(self, user_id, session, room_is_private, version, account=None, account_has_access=None, account_email_verified=None): diff --git a/yaboli/connection.py b/yaboli/connection.py index f5d4b79..2e0335b 100644 --- a/yaboli/connection.py +++ b/yaboli/connection.py @@ -15,14 +15,14 @@ class Connection: self.cookie = cookie self.packet_hook = packet_hook - stopped = False + self.stopped = False self._ws = None self._pid = 0 self._pending_responses = {} async def run(self): - self._ws = await websockets.connect(self.url) + self._ws = await websockets.connect(self.url, max_size=None) try: while True: @@ -35,31 +35,33 @@ class Connection: self._ws = None stopped = True - for futures in self._pending_responses: - for future in futures: - future.set_error(ConnectionClosed) - future.cancel() + for future in self._pending_responses: + future.set_error(ConnectionClosed) + future.cancel() # TODO: Is this needed? async def stop(self): - if not stopped and self._ws: + if not self.stopped and self._ws: await self._ws.close() async def send(self, ptype, data=None, await_response=True): - if stopped: + if self.stopped: raise ConnectionClosed pid = self._new_pid() - packet["type"] = ptype - packet["data"] = data - packet["id"] = pid + packet = { + "type": ptype, + "data": data, + "id": str(pid) + } if await_response: wait_for = self._wait_for_response(pid) - await self._ws.send(json.dumps(packet)) + + await self._ws.send(json.dumps(packet, separators=(',', ':'))) + + if await_response: await wait_for return wait_for.result() - else: - await self._ws.send(json.dumps(packet)) def _new_pid(self): self._pid += 1 @@ -70,7 +72,8 @@ class Connection: # Deal with pending responses pid = packet.get("id") - for future in self._pending_responses.pop(pid, []): + future = self._pending_responses.pop(pid, None) + if future: future.set_result(packet) # Pass packet onto room @@ -78,19 +81,17 @@ class Connection: def _wait_for_response(self, pid): future = asyncio.Future() - - if pid not in self._pending_responses: - self._pending_responses[pid] = [] - self._pending_responses[pid].append(future) + self._pending_responses[pid] = future return future -def do_nothing(*args, **kwargs): - pass +#async def handle_packet(packet): + #if packet.get("type") == "ping-event": + #await c._ws.send('{"type":"ping-reply","data":{"time":' + str(packet.get("data").get("time")) + '}}') + ##await c.send("ping-reply", {"time": packet.get("data").get("time")}, False) -def run(): - conn = Connection("wss://echo.websocket.org", do_nothing) - loop = asyncio.get_event_loop() - #loop.call_later(3, conn.stop) - - loop.run_until_complete(asyncio.ensure_future(conn.run())) +#c = Connection("wss://euphoria.io/room/test/ws", handle_packet) + +#def run(): + #loop = asyncio.get_event_loop() + #loop.run_until_complete(asyncio.ensure_future(c.run())) diff --git a/yaboli/controller.py b/yaboli/controller.py index d778a41..031416d 100644 --- a/yaboli/controller.py +++ b/yaboli/controller.py @@ -54,9 +54,17 @@ class Controller: await self.room.stop() async def on_start(self): + """ + The first callback called when the controller is run. + """ + pass async def on_stop(self): + """ + The last callback called when the controller is run. + """ + pass async def on_connected(self): @@ -124,6 +132,6 @@ class Controller: async def on_send(self, message): pass - async def on_snapshot(self, user_id, snapshot_id, version, listing, log, nick=None, + async def on_snapshot(self, user_id, session_id, version, listing, log, nick=None, pm_with_nick=None, pm_with_user_id=None): pass diff --git a/yaboli/room.py b/yaboli/room.py index ee33641..63b2aa8 100644 --- a/yaboli/room.py +++ b/yaboli/room.py @@ -24,6 +24,8 @@ class Room: self.account_email_verified = None self.room_is_private = None self.version = None # the version of the code being run and served by the server + self.pm_with_nick = None + self.pm_with_user_id = None self._callbacks = {} self._add_callbacks() @@ -46,7 +48,15 @@ class Room: pass # TODO async def ping_reply(self, time): - pass # TODO + """ + The ping command initiates a client-to-server ping. The server will + send back a ping-reply with the same timestamp as soon as possible. + + ping-reply is a response to a ping command or ping-event. + """ + + data = {"time": time} + await self._conn.send("ping-reply", data, await_response=False) # CATEGORY: CHAT ROOM COMMANDS @@ -57,7 +67,28 @@ class Room: pass # TODO async def nick(self, name): - pass # TODO + """ + session_id, user_id, from_nick, to_nick = await nick(name) + + The nick command sets the name you present to the room. This name + applies to all messages sent during this session, until the nick + command is called again. + + nick-reply confirms the nick command. It returns the session’s former + and new names (the server may modify the requested nick). + """ + + data = {"name": name} + response = await self._conn.send("nick", data) + + session_id = response.get("session_id") + user_id = response.get("id") + from_nick = response.get("from") + to_nick = response.get("to") + + self.session.nick = to_nick + + return session_id, user_id, from_nick, to_nick async def pm_initiate(self, user_id): pass # TODO @@ -164,7 +195,11 @@ class Room: """ data = packet.get("data") - await self.controller.on_ping(data.get("time"), data.get("next")) + + await self.controller.on_ping( + data.get("time"), + data.get("next") + ) async def _handle_pm_initiate(self, packet): pass # TODO @@ -173,4 +208,33 @@ class Room: pass # TODO async def _handle_snapshot(self, packet): - pass # TODO + """ + A snapshot-event indicates that a session has successfully joined a + room. It also offers a snapshot of the room’s state and recent history. + """ + + data = packet.get("data") + + for session_data in data.get("listing"): + session = utils.Session.from_dict(session_data) + self.listing.add(session) + + log = [utils.Message.from_dict(d) for d in data.get("log")] + + self.session.nick = data.get("nick") + + self.pm_with_nick = data.get("pm_with_nick"), + self.pm_with_user_id = data.get("pm_with_user_id") + + await self.controller.on_connected() + + await self.controller.on_snapshot( + data.get("identity"), + data.get("session_id"), + self.version, + self.listing, + log, + nick=self.session.nick, + pm_with_nick=self.pm_with_nick, + pm_with_user_id=self.pm_with_user_id + ) diff --git a/yaboli/utils.py b/yaboli/utils.py index 1d7f27f..a9f0e6f 100644 --- a/yaboli/utils.py +++ b/yaboli/utils.py @@ -64,3 +64,32 @@ class Listing: def get_bots(self): return {uid: ses for uid, ses in self._sessions.items() if ses.client_type is "bot"} + +class Message(): + def __init__(self, message_id, time, sender, content, parent=None, previous_edit_id=None, + encryption_key=None, edited=None, deleted=None, truncated=None): + self.message_id = message_id + self.time = time + self.sender = sender + self.content = content + self.parent = parent + self.previous_edit_id = previous_edit_id + self.encryption_key = encryption_key + self.edited = edited + self.deleted = deleted + self.truncated = truncated + + @classmethod + def from_dict(cls, d): + return cls( + d.get("id"), + d.get("time"), + Session.from_dict(d.get("sender")), + d.get("content"), + d.get("parent"), + d.get("previous_edit_id"), + d.get("encryption_key"), + d.get("edited"), + d.get("deleted"), + d.get("truncated") + )