Start the rewrite (again)
This commit is contained in:
parent
9b0195aa72
commit
97b98c29f7
5 changed files with 351 additions and 801 deletions
15
yaboli/TestBot.py
Normal file
15
yaboli/TestBot.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import asyncio
|
||||||
|
from controller import Bot
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestBot(Bot):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_connected(self):
|
||||||
|
await self.room.set_nick("TestBot")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
bot = TestBot()
|
||||||
|
asyncio.get_event_loop().run_until_complete(bot.run())
|
||||||
25
yaboli/asynciotest.py
Normal file
25
yaboli/asynciotest.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def create():
|
||||||
|
await asyncio.sleep(3.0)
|
||||||
|
print("(1) create file")
|
||||||
|
|
||||||
|
async def write():
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
print("(2) write into file")
|
||||||
|
|
||||||
|
async def close():
|
||||||
|
print("(3) close file")
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
await create()
|
||||||
|
await write()
|
||||||
|
await close()
|
||||||
|
await asyncio.sleep(2.0)
|
||||||
|
loop.stop()
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
asyncio.ensure_future(test())
|
||||||
|
loop.run_forever()
|
||||||
|
print("Pending tasks at exit: %s" % asyncio.Task.all_tasks(loop))
|
||||||
|
loop.close()
|
||||||
|
|
@ -1,229 +1,96 @@
|
||||||
|
import logging
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
import asyncio
|
||||||
|
asyncio.get_event_loop().set_debug(True)
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import time
|
import websockets
|
||||||
import threading
|
from websockets import ConnectionClosed
|
||||||
import websocket
|
|
||||||
from websocket import WebSocketException as WSException
|
|
||||||
|
|
||||||
from . import callbacks
|
|
||||||
|
|
||||||
class Connection():
|
|
||||||
"""
|
|
||||||
Stays connected to a room in its own thread.
|
|
||||||
Callback functions are called when a packet is received.
|
|
||||||
|
|
||||||
Callbacks:
|
class Connection:
|
||||||
- all the message types from api.euphoria.io
|
def __init__(self, url, packet_hook, cookie=None):
|
||||||
These pass the packet data as argument to the called functions.
|
self.url = url
|
||||||
The other callbacks don't pass any special arguments.
|
self.cookie = cookie
|
||||||
- "connect"
|
self.packet_hook = packet_hook
|
||||||
- "disconnect"
|
|
||||||
- "stop"
|
|
||||||
"""
|
|
||||||
|
|
||||||
ROOM_FORMAT = "wss://euphoria.io/room/{}/ws"
|
stopped = False
|
||||||
|
|
||||||
def __init__(self, room, url_format=None):
|
|
||||||
"""
|
|
||||||
room - name of the room to connect to
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.room = room
|
|
||||||
|
|
||||||
if not url_format:
|
|
||||||
url_format = self.ROOM_FORMAT
|
|
||||||
self._url = url_format.format(self.room)
|
|
||||||
|
|
||||||
self._stopping = False
|
|
||||||
|
|
||||||
self._ws = None
|
self._ws = None
|
||||||
self._thread = None
|
self._pid = 0
|
||||||
self._send_id = 0
|
self._pending_responses = {}
|
||||||
self._callbacks = callbacks.Callbacks()
|
|
||||||
self._id_callbacks = callbacks.Callbacks()
|
|
||||||
|
|
||||||
def _connect(self, tries=-1, delay=10):
|
async def run(self):
|
||||||
"""
|
self._ws = await websockets.connect(self.url)
|
||||||
_connect(tries, delay) -> bool
|
|
||||||
|
|
||||||
tries - maximum number of retries
|
try:
|
||||||
-1 -> retry indefinitely
|
while True:
|
||||||
|
response = await self._ws.recv()
|
||||||
Returns True on success, False on failure.
|
asyncio.ensure_future(self._handle_json(response))
|
||||||
|
except websockets.ConnectionClosed:
|
||||||
Connect to the room.
|
pass
|
||||||
"""
|
finally:
|
||||||
|
await self._ws.close() # just to make sure it's closed
|
||||||
while tries != 0:
|
|
||||||
try:
|
|
||||||
self._ws = websocket.create_connection(
|
|
||||||
self._url,
|
|
||||||
enable_multithread=True
|
|
||||||
)
|
|
||||||
|
|
||||||
self._callbacks.call("connect")
|
|
||||||
|
|
||||||
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
|
self._ws = None
|
||||||
|
stopped = True
|
||||||
|
|
||||||
self._callbacks.call("disconnect")
|
for futures in self._pending_responses:
|
||||||
|
for future in futures:
|
||||||
|
future.set_error(ConnectionClosed)
|
||||||
|
future.cancel()
|
||||||
|
|
||||||
def launch(self):
|
async def stop(self):
|
||||||
"""
|
if not stopped and self._ws:
|
||||||
launch() -> Thread
|
await self._ws.close()
|
||||||
|
|
||||||
Connect to the room and spawn a new thread running run.
|
async def send(ptype, data=None, await_response=True):
|
||||||
"""
|
if stopped:
|
||||||
|
raise ConnectionClosed
|
||||||
|
|
||||||
if self._connect(tries=1):
|
pid = self._new_pid()
|
||||||
self._thread = threading.Thread(target=self._run,
|
packet["type"] = ptype
|
||||||
name="{}-{}".format(self.room, int(time.time())))
|
packet["data"] = data
|
||||||
self._thread.start()
|
packet["id"] = pid
|
||||||
return self._thread
|
|
||||||
|
if await_response:
|
||||||
|
wait_for = self._wait_for_response(pid)
|
||||||
|
await self._ws.send(json.dumps(packet))
|
||||||
|
await wait_for
|
||||||
|
return wait_for.result()
|
||||||
else:
|
else:
|
||||||
self.stop()
|
await self._ws.send(json.dumps(packet))
|
||||||
|
|
||||||
def _run(self):
|
def _new_pid(self):
|
||||||
"""
|
self._pid += 1
|
||||||
_run() -> None
|
return self._pid
|
||||||
|
|
||||||
Receive messages.
|
async def _handle_json(text):
|
||||||
"""
|
packet = json.loads(text)
|
||||||
|
|
||||||
while not self._stopping:
|
# Deal with pending responses
|
||||||
try:
|
pid = packet.get("id")
|
||||||
self._handle_json(self._ws.recv())
|
for future in self._pending_responses.pop(pid, []):
|
||||||
except (WSException, ConnectionResetError):
|
future.set_result(packet)
|
||||||
if not self._stopping:
|
|
||||||
self.disconnect()
|
|
||||||
self._connect()
|
|
||||||
|
|
||||||
def stop(self):
|
# Pass packet onto room
|
||||||
"""
|
await self.packet_hook(packet)
|
||||||
stop() -> None
|
|
||||||
|
|
||||||
Close the connection to the room.
|
def _wait_for_response(pid):
|
||||||
Joins the thread launched by self.launch().
|
future = asyncio.Future()
|
||||||
"""
|
|
||||||
|
|
||||||
self._stopping = True
|
if pid not in self._pending_responses:
|
||||||
self.disconnect()
|
self._pending_responses[pid] = []
|
||||||
|
self._pending_responses[pid].append(future)
|
||||||
|
|
||||||
self._callbacks.call("stop")
|
return future
|
||||||
|
|
||||||
if self._thread and self._thread != threading.current_thread():
|
def do_nothing(*args, **kwargs):
|
||||||
self._thread.join()
|
pass
|
||||||
|
|
||||||
def next_id(self):
|
def run():
|
||||||
"""
|
conn = Connection("wss://echo.websocket.org", do_nothing)
|
||||||
next_id() -> id
|
loop = asyncio.get_event_loop()
|
||||||
|
#loop.call_later(3, conn.stop)
|
||||||
|
|
||||||
Returns the id that will be used for the next package.
|
loop.run_until_complete(asyncio.ensure_future(conn.run()))
|
||||||
"""
|
|
||||||
|
|
||||||
return str(self._send_id)
|
|
||||||
|
|
||||||
def add_callback(self, ptype, callback, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
add_callback(ptype, callback, *args, **kwargs) -> None
|
|
||||||
|
|
||||||
Add a function to be called when a packet of type ptype is received.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._callbacks.add(ptype, callback, *args, **kwargs)
|
|
||||||
|
|
||||||
def add_id_callback(self, pid, callback, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
add_id_callback(pid, callback, *args, **kwargs) -> None
|
|
||||||
|
|
||||||
Add a function to be called when a packet with id pid is received.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._id_callbacks.add(pid, callback, *args, **kwargs)
|
|
||||||
|
|
||||||
def add_next_callback(self, callback, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
add_next_callback(callback, *args, **kwargs) -> None
|
|
||||||
|
|
||||||
Add a function to be called when the answer to the next message sent is received.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._id_callbacks.add(self.next_id(), callback, *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
|
|
||||||
|
|
||||||
if "error" in packet:
|
|
||||||
error = packet["error"]
|
|
||||||
else:
|
|
||||||
error = None
|
|
||||||
|
|
||||||
self._callbacks.call(packet["type"], data, error)
|
|
||||||
|
|
||||||
if "id" in packet:
|
|
||||||
self._id_callbacks.call(packet["id"], data, error)
|
|
||||||
self._id_callbacks.remove(packet["id"])
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
|
||||||
122
yaboli/controller.py
Normal file
122
yaboli/controller.py
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
from .room import Room
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Controller:
|
||||||
|
"""
|
||||||
|
Callback order:
|
||||||
|
|
||||||
|
on_start - self.room not available
|
||||||
|
while running:
|
||||||
|
<connects to room>
|
||||||
|
on_ping - always possible (until on_disconnected)
|
||||||
|
on_bounce - self.room only session
|
||||||
|
on_hello - self.room only session
|
||||||
|
<authenticated, access to room>
|
||||||
|
on_connected - self.room session and chat room (fully connected)
|
||||||
|
on_snapshot - self.room session and chat room
|
||||||
|
<other callbacks> - self.room session and chat room
|
||||||
|
<leaving room>
|
||||||
|
on_disconnected - self.room not connected to room any more
|
||||||
|
on_stop - self.room not available
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, roomname, human=False, cookie=None):
|
||||||
|
"""
|
||||||
|
roomname - name of room to connect to
|
||||||
|
human - whether the human flag should be set on connections
|
||||||
|
cookie - cookie to use in HTTP request, if any
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.roomname = roomname
|
||||||
|
self.human = human
|
||||||
|
self.cookie = cookie
|
||||||
|
|
||||||
|
self.room = None
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
await self.on_start()
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
self.room = Room(self.roomname, self, self.human, self.cookie)
|
||||||
|
await self.room.run()
|
||||||
|
self.room = None
|
||||||
|
|
||||||
|
await self.on_end()
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
if self.room:
|
||||||
|
await self.room.stop()
|
||||||
|
|
||||||
|
# room successfully connected
|
||||||
|
async def on_connected(self):
|
||||||
|
"""
|
||||||
|
Client has successfully (re-)joined the room.
|
||||||
|
|
||||||
|
Use: Actions that are meant to happen upon (re-)connecting to a room,
|
||||||
|
such as resetting the message history.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_disconnected(self):
|
||||||
|
"""
|
||||||
|
Client has disconnected from the room.
|
||||||
|
|
||||||
|
This is the last time the old self.room can be accessed.
|
||||||
|
Use: Reconfigure self before next connection.
|
||||||
|
Need to store information from old room?
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_bounce(self, reason=None, auth_options=None, agent_id=None, ip=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_disconnect(self, reason):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_hello(self, user_id, session, room_is_private, version, account=None, account_has_access=None, account_email_verified=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_join(self, session):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_login(self, account_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_logout(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_network(self, ntype, server_id, server_era):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_nick(self, session_id, user_id, from, to):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_edit_message(self, edit_id, message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_part(self, session):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_ping(self, ptime, pnext):
|
||||||
|
"""
|
||||||
|
Default implementation, refer to api.euphoria.io
|
||||||
|
"""
|
||||||
|
|
||||||
|
await self.room.ping_reply(ptime)
|
||||||
|
|
||||||
|
async def on_pm_initiate(self, from, from_nick, from_room, pm_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_send(self, message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_snapshot(self, user_id, snapshot_id, version, listing, log, nick=None, pm_with_nick=None, pm_with_user_id=None):
|
||||||
|
pass
|
||||||
673
yaboli/room.py
673
yaboli/room.py
|
|
@ -1,617 +1,138 @@
|
||||||
import time
|
import asyncio
|
||||||
|
from .connection import Connection
|
||||||
|
|
||||||
from . import connection
|
class Room:
|
||||||
from . import message
|
ROOM_FORMAT = "wss://euphoria.io/room/{}/ws"
|
||||||
from . import messages
|
HUMAN_FORMAT = f"{ROOM_FORMAT}?h=1"
|
||||||
from . import session
|
|
||||||
from . import sessions
|
|
||||||
from . import callbacks
|
|
||||||
|
|
||||||
class Room():
|
def __init__(self, roomname, controller, human=False, cookie=None):
|
||||||
"""
|
self.roomname = roomname
|
||||||
Connects to and provides more abstract access to a room on euphoria.
|
self.controller = controller
|
||||||
|
self.human = human
|
||||||
|
self.cookie = cookie
|
||||||
|
|
||||||
callback (values passed) - description
|
# Keeps track of sessions, but not messages, since they might be dealt
|
||||||
----------------------------------------------------------------------------------
|
# with differently by different controllers.
|
||||||
delete (message) - message has been deleted
|
# If you need to keep track of messages, use utils.Log.
|
||||||
edit (message) - message has been edited
|
|
||||||
identity - own session or nick has changed
|
|
||||||
join (session) - user has joined the room
|
|
||||||
message (message) - message has been sent
|
|
||||||
messages - message data has changed
|
|
||||||
nick (session, old, new) - user has changed their nick
|
|
||||||
part (session) - user has left the room
|
|
||||||
ping - ping event has happened
|
|
||||||
room - room info has changed
|
|
||||||
sessions - session data has changed
|
|
||||||
change - room has been changed
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, room=None, nick=None, password=None, message_limit=500):
|
|
||||||
"""
|
|
||||||
room - name of the room to connect to
|
|
||||||
nick - nick to assume, None -> no nick
|
|
||||||
password - room password (in case the room is private)
|
|
||||||
message_limit - maximum amount of messages that will be stored at a time
|
|
||||||
None - no limit
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.room = room
|
|
||||||
self.password = password
|
|
||||||
self.room_is_private = None
|
|
||||||
self.pm_with_nick = None
|
|
||||||
self.pm_with_user = None
|
|
||||||
|
|
||||||
self.nick = nick
|
|
||||||
self.session = None
|
self.session = None
|
||||||
self.message_limit = message_limit
|
self.sessions = None
|
||||||
|
|
||||||
self.ping_last = 0
|
self._callbacks = {}
|
||||||
self.ping_next = 0
|
self._add_callbacks()
|
||||||
self.ping_offset = 0 # difference between server and local time
|
|
||||||
|
|
||||||
self._messages = None
|
if human:
|
||||||
self._sessions = None
|
url = HUMAN_FORMAT.format(self.roomname)
|
||||||
|
|
||||||
self._callbacks = callbacks.Callbacks()
|
|
||||||
|
|
||||||
self._con = None
|
|
||||||
|
|
||||||
if self.room:
|
|
||||||
self.change(self.room, password=self.password)
|
|
||||||
|
|
||||||
def launch(self):
|
|
||||||
"""
|
|
||||||
launch() -> Thread
|
|
||||||
|
|
||||||
Open connection in a new thread (see connection.Connection.launch).
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._con.launch()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""
|
|
||||||
stop() -> None
|
|
||||||
|
|
||||||
Close connection to room.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._con.stop()
|
|
||||||
|
|
||||||
def change(self, room, password=None):
|
|
||||||
"""
|
|
||||||
change(room) -> None
|
|
||||||
|
|
||||||
Leave current room (if already connected) and join new room.
|
|
||||||
Clears all messages and sessions.
|
|
||||||
A call to launch() is necessary to start a new thread again.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self._con:
|
|
||||||
self._con.stop()
|
|
||||||
|
|
||||||
self.room = room
|
|
||||||
self.password = password
|
|
||||||
self.room_is_private = None
|
|
||||||
self.pm_with_nick = None
|
|
||||||
self.pm_with_user = None
|
|
||||||
|
|
||||||
self.session = None
|
|
||||||
|
|
||||||
self.ping_last = 0
|
|
||||||
self.ping_next = 0
|
|
||||||
self.ping_offset = 0 # difference between server and local time
|
|
||||||
|
|
||||||
self._messages = messages.Messages(message_limit=self.message_limit)
|
|
||||||
self._sessions = sessions.Sessions()
|
|
||||||
|
|
||||||
self._con = connection.Connection(self.room)
|
|
||||||
|
|
||||||
self._con.add_callback("bounce-event", self._handle_bounce_event)
|
|
||||||
self._con.add_callback("disconnect-event", self._handle_disconnect_event)
|
|
||||||
self._con.add_callback("hello-event", self._handle_hello_event)
|
|
||||||
self._con.add_callback("join-event", self._handle_join_event)
|
|
||||||
self._con.add_callback("network-event", self._handle_network_event)
|
|
||||||
self._con.add_callback("nick-event", self._handle_nick_event)
|
|
||||||
self._con.add_callback("edit-message-event", self._handle_edit_message_event)
|
|
||||||
self._con.add_callback("part-event", self._handle_part_event)
|
|
||||||
self._con.add_callback("ping-event", self._handle_ping_event)
|
|
||||||
self._con.add_callback("send-event", self._handle_send_event)
|
|
||||||
self._con.add_callback("snapshot-event", self._handle_snapshot_event)
|
|
||||||
|
|
||||||
self._callbacks.call("change")
|
|
||||||
|
|
||||||
def add_callback(self, event, callback, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
add_callback(ptype, callback, *args, **kwargs) -> None
|
|
||||||
|
|
||||||
Add a function to be called when a certain event happens.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._callbacks.add(event, callback, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_msg(self, mid):
|
|
||||||
"""
|
|
||||||
get_msg(message_id) -> Message
|
|
||||||
|
|
||||||
Returns the message with the given id, if found.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._messages.get(mid)
|
|
||||||
|
|
||||||
def get_msg_parent(self, mid):
|
|
||||||
"""
|
|
||||||
get_msg_parent(message_id) -> Message
|
|
||||||
|
|
||||||
Returns the message's parent, if found.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._messages.get_parent(mid)
|
|
||||||
|
|
||||||
def get_msg_children(self, mid):
|
|
||||||
"""
|
|
||||||
get_msg_children(message_id) -> list
|
|
||||||
|
|
||||||
Returns a sorted list of children of the given message, if found.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._messages.get_children(mid)
|
|
||||||
|
|
||||||
def get_msg_top_level(self):
|
|
||||||
"""
|
|
||||||
get_msg_top_level() -> list
|
|
||||||
|
|
||||||
Returns a sorted list of top-level messages.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._messages.get_top_level()
|
|
||||||
|
|
||||||
def get_msg_oldest(self):
|
|
||||||
"""
|
|
||||||
get_msg_oldest() -> Message
|
|
||||||
|
|
||||||
Returns the oldest message, if found.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._messages.get_oldest()
|
|
||||||
|
|
||||||
def get_msg_youngest(self):
|
|
||||||
"""
|
|
||||||
get_msg_youngest() -> Message
|
|
||||||
|
|
||||||
Returns the youngest message, if found.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._messages.get_youngest()
|
|
||||||
|
|
||||||
def get_session(self, sid):
|
|
||||||
"""
|
|
||||||
get_session(session_id) -> Session
|
|
||||||
|
|
||||||
Returns the session with that id.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._sessions.get(sid)
|
|
||||||
|
|
||||||
def get_sessions(self):
|
|
||||||
"""
|
|
||||||
get_sessions() -> list
|
|
||||||
|
|
||||||
Returns the full list of sessions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._sessions.get_all()
|
|
||||||
|
|
||||||
def get_people(self):
|
|
||||||
"""
|
|
||||||
get_people() -> list
|
|
||||||
|
|
||||||
Returns a list of all non-bot and non-lurker sessions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._sessions.get_people()
|
|
||||||
|
|
||||||
def get_accounts(self):
|
|
||||||
"""
|
|
||||||
get_accounts() -> list
|
|
||||||
|
|
||||||
Returns a list of all logged-in sessions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._sessions.get_accounts()
|
|
||||||
|
|
||||||
def get_agents(self):
|
|
||||||
"""
|
|
||||||
get_agents() -> list
|
|
||||||
|
|
||||||
Returns a list of all sessions who are not signed into an account and not bots or lurkers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._sessions.get_agents()
|
|
||||||
|
|
||||||
def get_bots(self):
|
|
||||||
"""
|
|
||||||
get_bots() -> list
|
|
||||||
|
|
||||||
Returns a list of all bot sessions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._sessions.get_bots()
|
|
||||||
|
|
||||||
def get_lurkers(self):
|
|
||||||
"""
|
|
||||||
get_lurkers() -> list
|
|
||||||
|
|
||||||
Returns a list of all lurker sessions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._sessions.get_lurkers()
|
|
||||||
|
|
||||||
def set_nick(self, nick):
|
|
||||||
"""
|
|
||||||
set_nick(nick) -> None
|
|
||||||
|
|
||||||
Change your nick.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.nick = nick
|
|
||||||
|
|
||||||
if not self.session or self.session.name != self.nick:
|
|
||||||
self._con.add_next_callback(self._handle_nick_reply)
|
|
||||||
self._con.send_packet("nick", name=nick)
|
|
||||||
|
|
||||||
def mentionable(self, nick=None):
|
|
||||||
"""
|
|
||||||
mentionable()
|
|
||||||
|
|
||||||
A mentionable version of the nick.
|
|
||||||
The nick defaults to the bot's nick.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if nick is None:
|
|
||||||
nick = self.nick
|
|
||||||
|
|
||||||
return "".join(c for c in nick if not c in ".!?;&<'\"" and not c.isspace())
|
|
||||||
|
|
||||||
def send_message(self, content, parent=None):
|
|
||||||
"""
|
|
||||||
send_message(content, parent) -> None
|
|
||||||
|
|
||||||
Send a message.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._con.add_next_callback(self._handle_send_reply)
|
|
||||||
self._con.send_packet("send", content=content, parent=parent)
|
|
||||||
|
|
||||||
def authenticate(self, password=None):
|
|
||||||
"""
|
|
||||||
authenticate(passsword) -> None
|
|
||||||
|
|
||||||
Try to authenticate so you can enter the room.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.password = password
|
|
||||||
|
|
||||||
self._con.add_next_callback(self._handle_auth_reply)
|
|
||||||
self._con.send_packet("auth", type="passcode", passcode=self.password)
|
|
||||||
|
|
||||||
def update_sessions(self):
|
|
||||||
"""
|
|
||||||
update_sessions() -> None
|
|
||||||
|
|
||||||
Resets and then updates the list of sessions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._con.add_next_callback(self._handle_who_reply)
|
|
||||||
self._con.send_packet("who")
|
|
||||||
|
|
||||||
def load_msgs(self, number=50):
|
|
||||||
"""
|
|
||||||
load_msgs(number) -> None
|
|
||||||
|
|
||||||
Request a certain number of older messages from the server.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._con.add_next_callback(self._handle_log_reply)
|
|
||||||
self._con.send_packet("log", n=number, before=self.get_msg_oldest().id)
|
|
||||||
|
|
||||||
def load_msg(self, mid):
|
|
||||||
"""
|
|
||||||
load_msg(message_id) -> None
|
|
||||||
|
|
||||||
Request an untruncated version of the message with that id.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._con.add_next_callback(self._handle_get_message_reply)
|
|
||||||
self._con.send_packet("get-message", id=mid)
|
|
||||||
|
|
||||||
# ----- HANDLING OF EVENTS -----
|
|
||||||
|
|
||||||
def _handle_connect(self):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._callbacks.call("connect")
|
|
||||||
|
|
||||||
def _handle_disconnect(self):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._callbacks.call("disconnect")
|
|
||||||
|
|
||||||
def _handle_stop(self):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._callbacks.call("stop")
|
|
||||||
|
|
||||||
def _handle_bounce_event(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "bounce-event", error)
|
|
||||||
self.stop()
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.password is not None:
|
|
||||||
self.authenticate(self.password)
|
|
||||||
else:
|
else:
|
||||||
self.stop()
|
url = ROOM_FORMAT.format(self.roomname)
|
||||||
|
self._conn = Connection(url, self._handle_packet, self.cookie)
|
||||||
|
|
||||||
def _handle_disconnect_event(self, data, error):
|
async def run(self):
|
||||||
"""
|
await self._conn.run()
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
async def stop(self):
|
||||||
self._callbacks.call("error", "disconnect-event", error)
|
await self._conn.stop()
|
||||||
self.stop()
|
|
||||||
return
|
|
||||||
|
|
||||||
self._con.disconnect()
|
# CATEGORY: SESSION COMMANDS
|
||||||
|
|
||||||
def _handle_hello_event(self, data, error):
|
async def auth(atype, passcode):
|
||||||
"""
|
pass # TODO
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
async def ping_reply(time):
|
||||||
self._callbacks.call("error", "hello-event", error)
|
pass # TODO
|
||||||
self.stop()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.session = session.Session.from_data(data["session"])
|
# CATEGORY: CHAT ROOM COMMANDS
|
||||||
self._sessions.add(self.session)
|
|
||||||
self._callbacks.call("identity")
|
|
||||||
self._callbacks.call("sessions")
|
|
||||||
|
|
||||||
self.room_is_private = data["room_is_private"]
|
async def get_message(message_id):
|
||||||
self._callbacks.call("room")
|
pass # TODO
|
||||||
|
|
||||||
def _handle_join_event(self, data, error):
|
async def log(n, before=None):
|
||||||
"""
|
pass # TODO
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
async def nick(name):
|
||||||
self._callbacks.call("error", "join-event", error)
|
pass # TODO
|
||||||
self.update_sessions()
|
|
||||||
return
|
|
||||||
|
|
||||||
ses = session.Session.from_data(data)
|
async def pm_initiate(user_id):
|
||||||
self._sessions.add(ses)
|
pass # TODO
|
||||||
self._callbacks.call("join", ses)
|
|
||||||
self._callbacks.call("sessions")
|
|
||||||
|
|
||||||
def _handle_network_event(self, data, error):
|
async def send(content, parent=None):
|
||||||
"""
|
pass # TODO
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
async def who()
|
||||||
self._callbacks.call("error", "network-event", error)
|
pass # TODO
|
||||||
return
|
|
||||||
|
|
||||||
if data["type"] == "partition":
|
# CATEGORY: ACCOUNT COMMANDS
|
||||||
self._sessions.remove_on_network_partition(data["server_id"], data["server_era"])
|
# NYI, and probably never will
|
||||||
self._callbacks.call("sessions")
|
|
||||||
|
|
||||||
def _handle_nick_event(self, data, error):
|
# CATEGORY: ROOM HOST COMMANDS
|
||||||
"""
|
# NYI, and probably never will
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
# CATEGORY: STAFF COMMANDS
|
||||||
self._callbacks.call("error", "nick-event", error)
|
# NYI, and probably never will
|
||||||
self.update_sessions()
|
|
||||||
return
|
|
||||||
|
|
||||||
ses = self.get_session(data["session_id"])
|
|
||||||
if ses:
|
|
||||||
ses.name = data["to"]
|
|
||||||
self._callbacks.call("nick", ses, data["from"], data["to"])
|
|
||||||
self._callbacks.call("sessions")
|
|
||||||
|
|
||||||
def _handle_edit_message_event(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
# All the private functions for dealing with stuff
|
||||||
self._callbacks.call("error", "edit-message-event", error)
|
|
||||||
return
|
|
||||||
|
|
||||||
msg = message.Message.from_data(data)
|
def _add_callbacks(self):
|
||||||
if msg:
|
self._callbacks["bounce-event"] = self._handle_bounce
|
||||||
self._messages.add(msg)
|
self._callbacks["disconnect-event"] = self._handle_disconnect
|
||||||
|
self._callbacks["hello-event"] = self._handle_hello
|
||||||
|
self._callbacks["join-event"] = self._handle_join
|
||||||
|
self._callbacks["login-event"] = self._handle_login
|
||||||
|
self._callbacks["logout-event"] = self._handle_logout
|
||||||
|
self._callbacks["network-event"] = self._handle_network
|
||||||
|
self._callbacks["nick-event"] = self._handle_nick
|
||||||
|
self._callbacks["edit-message-event"] = self._handle_edit_message
|
||||||
|
self._callbacks["part-event"] = self._handle_part
|
||||||
|
self._callbacks["ping-event"] = self._handle_ping
|
||||||
|
self._callbacks["pm-initiate-event"] = self._handle_pm_initiate
|
||||||
|
self._callbacks["send-event"] = self._handle_send
|
||||||
|
self._callbacks["snapshot-event"] = self._handle_snapshot
|
||||||
|
|
||||||
if msg.deleted:
|
async def _handle_packet(self, packet):
|
||||||
self._callbacks.call("delete", msg)
|
ptype = packet.get("type")
|
||||||
elif msg.edited:
|
callback = self._callbacks.get(ptype)
|
||||||
self._callbacks.call("edit", msg)
|
if callback:
|
||||||
|
await callback(packet)
|
||||||
|
|
||||||
self._callbacks.call("messages")
|
async def _handle_bounce(self, packet):
|
||||||
|
pass # TODO
|
||||||
|
|
||||||
def _handle_part_event(self, data, error):
|
async def _handle_disconnect(self, packet):
|
||||||
"""
|
pass # TODO
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
async def _handle_hello(self, packet):
|
||||||
self._callbacks.call("error", "part-event", error)
|
pass # TODO
|
||||||
self.update_sessions()
|
|
||||||
return
|
|
||||||
|
|
||||||
ses = session.Session.from_data(data)
|
async def _handle_join(self, packet):
|
||||||
if ses:
|
pass # TODO
|
||||||
self._sessions.remove(ses.session_id)
|
|
||||||
|
|
||||||
self._callbacks.call("part", ses)
|
async def _handle_login(self, packet):
|
||||||
self._callbacks.call("sessions")
|
pass # TODO
|
||||||
|
|
||||||
def _handle_ping_event(self, data, error):
|
async def _handle_logout(self, packet):
|
||||||
"""
|
pass # TODO
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
async def _handle_network(self, packet):
|
||||||
self._callbacks.call("error", "ping-event", error)
|
pass # TODO
|
||||||
return
|
|
||||||
|
|
||||||
self.ping_last = data["time"]
|
async def _handle_nick(self, packet):
|
||||||
self.ping_next = data["next"]
|
pass # TODO
|
||||||
self.ping_offset = self.ping_last - time.time()
|
|
||||||
|
|
||||||
self._con.send_packet("ping-reply", time=self.ping_last)
|
async def _handle_edit_message(self, packet):
|
||||||
self._callbacks.call("ping")
|
pass # TODO
|
||||||
|
|
||||||
def _handle_send_event(self, data, error):
|
async def _handle_part(self, packet):
|
||||||
"""
|
pass # TODO
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
async def _handle_ping(self, packet):
|
||||||
self._callbacks.call("error", "send-event", error)
|
pass # TODO
|
||||||
return
|
|
||||||
|
|
||||||
msg = message.Message.from_data(data)
|
async def _handle_pm_initiate(self, packet):
|
||||||
self._callbacks.call("message", msg)
|
pass # TODO
|
||||||
|
|
||||||
self._messages.add(msg)
|
async def _handle_send(self, packet):
|
||||||
self._callbacks.call("messages")
|
pass # TODO
|
||||||
|
|
||||||
def _handle_snapshot_event(self, data, error):
|
async def _handle_snapshot(self, packet):
|
||||||
"""
|
pass # TODO
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "snapshot-event", error)
|
|
||||||
self.stop()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.set_nick(self.nick)
|
|
||||||
|
|
||||||
if "pm_with_nick" in data or "pm_with_user_id" in data:
|
|
||||||
if "pm_with_nick" in data:
|
|
||||||
self.pm_with_nick = data["pm_with_nick"]
|
|
||||||
if "pm_with_user_id" in data:
|
|
||||||
self.pm_with_user_id = data["pm_with_user_id"]
|
|
||||||
self._callbacks.call("room")
|
|
||||||
|
|
||||||
self._sessions.remove_all()
|
|
||||||
for sesdata in data["listing"]:
|
|
||||||
self._sessions.add_from_data(sesdata)
|
|
||||||
self._callbacks.call("sessions")
|
|
||||||
|
|
||||||
self._messages.remove_all()
|
|
||||||
for msgdata in data["log"]:
|
|
||||||
self._messages.add_from_data(msgdata)
|
|
||||||
self._callbacks.call("messages")
|
|
||||||
|
|
||||||
# ----- HANDLING OF REPLIES -----
|
|
||||||
|
|
||||||
def _handle_auth_reply(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "auth-reply", error)
|
|
||||||
self.stop()
|
|
||||||
return
|
|
||||||
|
|
||||||
if not data["success"]:
|
|
||||||
self._con.stop()
|
|
||||||
|
|
||||||
def _handle_get_message_reply(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "get-message-reply", error)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._messages.add_from_data(data)
|
|
||||||
self._callbacks.call("messages")
|
|
||||||
|
|
||||||
def _handle_log_reply(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "log-reply", error)
|
|
||||||
return
|
|
||||||
|
|
||||||
for msgdata in data["log"]:
|
|
||||||
self._messages.add_from_data(msgdata)
|
|
||||||
self._callbacks.call("messages")
|
|
||||||
|
|
||||||
def _handle_nick_reply(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "nick-reply", error)
|
|
||||||
return
|
|
||||||
|
|
||||||
if "to" in data:
|
|
||||||
self.session.name = self.nick
|
|
||||||
self._callbacks.call("identity")
|
|
||||||
|
|
||||||
if data["to"] != self.nick:
|
|
||||||
self.set_nick(self.nick)
|
|
||||||
|
|
||||||
def _handle_send_reply(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "send-reply", error)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._messages.add_from_data(data)
|
|
||||||
self._callbacks.call("messages")
|
|
||||||
|
|
||||||
def _handle_who_reply(self, data, error):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
|
|
||||||
if error:
|
|
||||||
self._callbacks.call("error", "who-reply", error)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._sessions.remove_all()
|
|
||||||
for sesdata in data["listing"]:
|
|
||||||
self._sessions.add_from_data(sesdata)
|
|
||||||
self._callbacks.call("sessions")
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue