Add basic functionality

Bots can now
 - stay connected
 - set their nick
This commit is contained in:
Joscha 2017-09-02 10:37:58 +00:00
parent d0ad542b72
commit 04364c6b3f
5 changed files with 137 additions and 34 deletions

View file

@ -9,8 +9,9 @@ class TestBot(Controller):
def __init__(self, roomname): def __init__(self, roomname):
super().__init__(roomname) super().__init__(roomname)
#async def on_connected(self): async def on_snapshot(self, user_id, session_id, version, listing, log, nick=None,
#await self.room.set_nick("TestBot") 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, async def on_hello(self, user_id, session, room_is_private, version, account=None,
account_has_access=None, account_email_verified=None): account_has_access=None, account_email_verified=None):

View file

@ -15,14 +15,14 @@ class Connection:
self.cookie = cookie self.cookie = cookie
self.packet_hook = packet_hook self.packet_hook = packet_hook
stopped = False self.stopped = False
self._ws = None self._ws = None
self._pid = 0 self._pid = 0
self._pending_responses = {} self._pending_responses = {}
async def run(self): async def run(self):
self._ws = await websockets.connect(self.url) self._ws = await websockets.connect(self.url, max_size=None)
try: try:
while True: while True:
@ -35,31 +35,33 @@ class Connection:
self._ws = None self._ws = None
stopped = True stopped = True
for futures in self._pending_responses: for future in self._pending_responses:
for future in futures: future.set_error(ConnectionClosed)
future.set_error(ConnectionClosed) future.cancel() # TODO: Is this needed?
future.cancel()
async def stop(self): async def stop(self):
if not stopped and self._ws: if not self.stopped and self._ws:
await self._ws.close() await self._ws.close()
async def send(self, ptype, data=None, await_response=True): async def send(self, ptype, data=None, await_response=True):
if stopped: if self.stopped:
raise ConnectionClosed raise ConnectionClosed
pid = self._new_pid() pid = self._new_pid()
packet["type"] = ptype packet = {
packet["data"] = data "type": ptype,
packet["id"] = pid "data": data,
"id": str(pid)
}
if await_response: if await_response:
wait_for = self._wait_for_response(pid) 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 await wait_for
return wait_for.result() return wait_for.result()
else:
await self._ws.send(json.dumps(packet))
def _new_pid(self): def _new_pid(self):
self._pid += 1 self._pid += 1
@ -70,7 +72,8 @@ class Connection:
# Deal with pending responses # Deal with pending responses
pid = packet.get("id") 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) future.set_result(packet)
# Pass packet onto room # Pass packet onto room
@ -78,19 +81,17 @@ class Connection:
def _wait_for_response(self, pid): def _wait_for_response(self, pid):
future = asyncio.Future() future = asyncio.Future()
self._pending_responses[pid] = future
if pid not in self._pending_responses:
self._pending_responses[pid] = []
self._pending_responses[pid].append(future)
return future return future
def do_nothing(*args, **kwargs): #async def handle_packet(packet):
pass #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(): #c = Connection("wss://euphoria.io/room/test/ws", handle_packet)
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())) #def run():
#loop = asyncio.get_event_loop()
#loop.run_until_complete(asyncio.ensure_future(c.run()))

View file

@ -54,9 +54,17 @@ class Controller:
await self.room.stop() await self.room.stop()
async def on_start(self): async def on_start(self):
"""
The first callback called when the controller is run.
"""
pass pass
async def on_stop(self): async def on_stop(self):
"""
The last callback called when the controller is run.
"""
pass pass
async def on_connected(self): async def on_connected(self):
@ -124,6 +132,6 @@ class Controller:
async def on_send(self, message): async def on_send(self, message):
pass 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): pm_with_nick=None, pm_with_user_id=None):
pass pass

View file

@ -24,6 +24,8 @@ class Room:
self.account_email_verified = None self.account_email_verified = None
self.room_is_private = None self.room_is_private = None
self.version = None # the version of the code being run and served by the server 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._callbacks = {}
self._add_callbacks() self._add_callbacks()
@ -46,7 +48,15 @@ class Room:
pass # TODO pass # TODO
async def ping_reply(self, time): 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 # CATEGORY: CHAT ROOM COMMANDS
@ -57,7 +67,28 @@ class Room:
pass # TODO pass # TODO
async def nick(self, name): 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 sessions 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): async def pm_initiate(self, user_id):
pass # TODO pass # TODO
@ -164,7 +195,11 @@ class Room:
""" """
data = packet.get("data") 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): async def _handle_pm_initiate(self, packet):
pass # TODO pass # TODO
@ -173,4 +208,33 @@ class Room:
pass # TODO pass # TODO
async def _handle_snapshot(self, packet): 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 rooms 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
)

View file

@ -64,3 +64,32 @@ class Listing:
def get_bots(self): def get_bots(self):
return {uid: ses for uid, ses in self._sessions.items() return {uid: ses for uid, ses in self._sessions.items()
if ses.client_type is "bot"} 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")
)