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):
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):

View file

@ -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()))

View file

@ -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

View file

@ -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 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):
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 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):
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")
)