Continue rewrite
This commit is contained in:
parent
6b65bef5e0
commit
1f5fc58e06
5 changed files with 238 additions and 69 deletions
|
|
@ -13,9 +13,13 @@ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||||
from .cookiejar import *
|
from .cookiejar import *
|
||||||
from .connection import *
|
from .connection import *
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
|
from .room import *
|
||||||
|
from utils import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
connection.__all__ +
|
connection.__all__ +
|
||||||
cookiejar.__all__ +
|
cookiejar.__all__ +
|
||||||
exceptions.__all__
|
exceptions.__all__ +
|
||||||
|
room.__all__ +
|
||||||
|
utils.__all__
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
import socket
|
import socket
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
from .exceptions import ConnectionClosed
|
from .exceptions import *
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -33,7 +33,7 @@ class Connection:
|
||||||
|
|
||||||
async def send(self, ptype, data=None, await_response=True):
|
async def send(self, ptype, data=None, await_response=True):
|
||||||
if not self._ws:
|
if not self._ws:
|
||||||
raise exceptions.ConnectionClosed
|
raise ConnectionClosed
|
||||||
#raise asyncio.CancelledError
|
#raise asyncio.CancelledError
|
||||||
|
|
||||||
pid = str(self._new_pid())
|
pid = str(self._new_pid())
|
||||||
|
|
@ -63,9 +63,16 @@ class Connection:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._stopped = True
|
self._stopped = True
|
||||||
if self._ws:
|
await self.reconnect() # _run() does the cleaning up now.
|
||||||
await self._ws.close() # _run() does the cleaning up now.
|
|
||||||
await self._runtask
|
await self._runtask
|
||||||
|
|
||||||
|
async def reconnect(self):
|
||||||
|
"""
|
||||||
|
Reconnect to the url.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._ws:
|
||||||
|
await self._ws.close()
|
||||||
|
|
||||||
async def _connect(self, tries):
|
async def _connect(self, tries):
|
||||||
"""
|
"""
|
||||||
|
|
@ -116,6 +123,8 @@ class Connection:
|
||||||
- make sure the ping task has finished
|
- make sure the ping task has finished
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
asyncio.create_task(self.disconnect_callback())
|
||||||
|
|
||||||
# stop ping task
|
# stop ping task
|
||||||
if self._pingtask:
|
if self._pingtask:
|
||||||
self._pingtask.cancel()
|
self._pingtask.cancel()
|
||||||
|
|
@ -131,7 +140,7 @@ class Connection:
|
||||||
# clean up pending response futures
|
# clean up pending response futures
|
||||||
for _, future in self._pending_responses.items():
|
for _, future in self._pending_responses.items():
|
||||||
logger.debug(f"Cancelling future with ConnectionClosed: {future}")
|
logger.debug(f"Cancelling future with ConnectionClosed: {future}")
|
||||||
future.set_exception(exceptions.ConnectionClosed("No server response"))
|
future.set_exception(ConnectionClosed("No server response"))
|
||||||
self._pending_responses = {}
|
self._pending_responses = {}
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
|
|
@ -164,7 +173,7 @@ class Connection:
|
||||||
await asyncio.sleep(self.ping_delay)
|
await asyncio.sleep(self.ping_delay)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.warning("Ping timed out.")
|
logger.warning("Ping timed out.")
|
||||||
await self._ws.close() # trigger a reconnect attempt
|
await self.reconnect()
|
||||||
except (websockets.ConnectionClosed, ConnectionResetError, asyncio.CancelledError):
|
except (websockets.ConnectionClosed, ConnectionResetError, asyncio.CancelledError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,12 @@ __all__ = ["ConnectionClosed"]
|
||||||
|
|
||||||
class ConnectionClosed(Exception):
|
class ConnectionClosed(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class RoomException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AuthenticationRequired(RoomException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RoomClosed(RoomException):
|
||||||
|
pass
|
||||||
|
|
|
||||||
181
yaboli/room.py
181
yaboli/room.py
|
|
@ -1,3 +1,8 @@
|
||||||
|
from .connection import *
|
||||||
|
from .exceptions import *
|
||||||
|
from .utils import *
|
||||||
|
|
||||||
|
|
||||||
__all__ == ["Room", "Inhabitant"]
|
__all__ == ["Room", "Inhabitant"]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -6,18 +11,24 @@ class Room:
|
||||||
TODO
|
TODO
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
CONNECTED = 1
|
||||||
|
DISCONNECTED = 2
|
||||||
|
EXITED = 3
|
||||||
|
|
||||||
def __init__(self, roomname, inhabitant, password=None, human=False, cookiejar=None):
|
def __init__(self, roomname, inhabitant, password=None, human=False, cookiejar=None):
|
||||||
# TODO: Connect to room etc.
|
# TODO: Connect to room etc.
|
||||||
# TODO: Deal with room/connection states of:
|
# TODO: Deal with room/connection states of:
|
||||||
# disconnected connecting, fast-forwarding, connected
|
# disconnected connecting, fast-forwarding, connected
|
||||||
|
|
||||||
self._inhabitant = inhabitant
|
|
||||||
|
|
||||||
# Room info (all fields readonly!)
|
# Room info (all fields readonly!)
|
||||||
self.roomname = roomname
|
self.roomname = roomname
|
||||||
|
self.password = password
|
||||||
|
self.human = human
|
||||||
|
|
||||||
self.session = None
|
self.session = None
|
||||||
self.account = None
|
self.account = None
|
||||||
self.listing = None # TODO
|
self.listing = Listing()
|
||||||
|
|
||||||
self.account_has_access = None
|
self.account_has_access = None
|
||||||
self.account_email_verified = None
|
self.account_email_verified = None
|
||||||
self.room_is_private = None
|
self.room_is_private = None
|
||||||
|
|
@ -25,10 +36,20 @@ class Room:
|
||||||
self.pm_with_nick = None
|
self.pm_with_nick = None
|
||||||
self.pm_with_user_id = None
|
self.pm_with_user_id = None
|
||||||
|
|
||||||
#asyncio.create_task(self._run())
|
self._inhabitant = inhabitant
|
||||||
|
self._status = Room.DISCONNECTED
|
||||||
|
|
||||||
|
# TODO: Allow for all parameters of Connection() to be specified in Room().
|
||||||
|
self._connection = Connection(
|
||||||
|
self.format_room_url(self.roomname, human=self.human),
|
||||||
|
self._receive_packet,
|
||||||
|
self._disconnected,
|
||||||
|
cookiejar
|
||||||
|
)
|
||||||
|
|
||||||
async def exit(self):
|
async def exit(self):
|
||||||
pass
|
self._status = Room.EXITED
|
||||||
|
await self._connection.stop()
|
||||||
|
|
||||||
# ROOM COMMANDS
|
# ROOM COMMANDS
|
||||||
# These always return a response from the server.
|
# These always return a response from the server.
|
||||||
|
|
@ -72,11 +93,137 @@ class Room:
|
||||||
|
|
||||||
# COMMUNICATION WITH CONNECTION
|
# COMMUNICATION WITH CONNECTION
|
||||||
|
|
||||||
async def _receive_packet(self, ptype, data, error, throttled):
|
|
||||||
pass # TODO
|
|
||||||
|
|
||||||
async def _disconnected(self):
|
async def _disconnected(self):
|
||||||
pass # TODO
|
# While disconnected, keep the last known session info, listing etc.
|
||||||
|
# All of this is instead reset when the hello/snapshot events are received.
|
||||||
|
self.status = Room.DISCONNECTED
|
||||||
|
self._connected_future = asyncio.Future()
|
||||||
|
|
||||||
|
await self._inhabitant.disconnected(self)
|
||||||
|
|
||||||
|
async def _receive_packet(self, ptype, data, error, throttled):
|
||||||
|
# Ignoring errors and throttling for now
|
||||||
|
functions = {
|
||||||
|
"bounce-event": self._event_bounce,
|
||||||
|
#"disconnect-event": self._event_disconnect, # Not important, can ignore
|
||||||
|
"hello-event": self._event_hello,
|
||||||
|
"join-event": self._event_join,
|
||||||
|
#"login-event": self._event_login,
|
||||||
|
#"logout-event": self._event_logout,
|
||||||
|
"network-event": self._event_network,
|
||||||
|
"nick-event": self._event_nick,
|
||||||
|
#"edit-message-event": self._event_edit_message,
|
||||||
|
"part-event": self._event_part,
|
||||||
|
"ping-event": self._event_ping,
|
||||||
|
"pm-initiate-event": self._event_pm_initiate,
|
||||||
|
"send-event": self._event_send,
|
||||||
|
"snapshot-event": self._event_snapshot,
|
||||||
|
}
|
||||||
|
|
||||||
|
function = functions.get(ptype)
|
||||||
|
if function:
|
||||||
|
await function(data)
|
||||||
|
|
||||||
|
async def _event_bounce(self, data):
|
||||||
|
if self.password is not None:
|
||||||
|
try:
|
||||||
|
response = await self._connection.send("auth", type=passcode, passcode=self.password)
|
||||||
|
rdata = response.get("data")
|
||||||
|
success = rdata.get("success")
|
||||||
|
if not success:
|
||||||
|
reason = rdata.get("reason")
|
||||||
|
raise AuthenticationRequired(f"Could not join &{self.roomname}: {reason}")
|
||||||
|
except ConnectionClosed:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise AuthenticationRequired(f"&{self.roomname} is password locked but no password was given")
|
||||||
|
|
||||||
|
async def _event_hello(self, data):
|
||||||
|
self.session = Session.from_dict(data.get("session"))
|
||||||
|
self.room_is_private = = data.get("room_is_private")
|
||||||
|
self.version = data.get("version")
|
||||||
|
self.account = data.get("account", None)
|
||||||
|
self.account_has_access = data.get("account_has_access", None)
|
||||||
|
self.account_email_verified = data.get("account_email_verified", None)
|
||||||
|
|
||||||
|
async def _event_join(self, data):
|
||||||
|
session = Session.from_dict(data)
|
||||||
|
self.listing.add(session)
|
||||||
|
await self._inhabitant.join(self, session)
|
||||||
|
|
||||||
|
async def _event_network(self, data):
|
||||||
|
server_id = data.get("server_id")
|
||||||
|
server_era = data.get("server_era")
|
||||||
|
|
||||||
|
sessions = self.listing.remove_combo(server_id, server_era)
|
||||||
|
for session in sessions:
|
||||||
|
await self._inhabitant.part(self, session)
|
||||||
|
|
||||||
|
async def _event_nick(self, data):
|
||||||
|
sid = data.get("session_id")
|
||||||
|
uid = data.get("user_id")
|
||||||
|
from_nick = data.get("from")
|
||||||
|
to_nick = data.get("to")
|
||||||
|
|
||||||
|
session = self.listing.by_sid(sid)
|
||||||
|
if session:
|
||||||
|
session.nick = to_nick
|
||||||
|
|
||||||
|
await self._inhabitant.nick(self, sid, uid, from_nick, to_nick)
|
||||||
|
|
||||||
|
async def _event_part(self, data):
|
||||||
|
session = Session.from_dict(data)
|
||||||
|
self.listing.remove(session.sid)
|
||||||
|
await self._inhabitant.part(self, session)
|
||||||
|
|
||||||
|
async def _event_ping(self, data):
|
||||||
|
try:
|
||||||
|
self._connection.send()
|
||||||
|
except exceptions.ConnectionClosed:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _event_pm_initiate(self, data):
|
||||||
|
from_uid = data.get("from")
|
||||||
|
from_nick = data.get("from_nick")
|
||||||
|
from_room = data.get("from_room")
|
||||||
|
pm_id = data.get("pm_id")
|
||||||
|
|
||||||
|
await self._inhabitant.pm(self, from_uid, from_nick, from_room, pm_id)
|
||||||
|
|
||||||
|
async def _event_send(self, data):
|
||||||
|
pass # TODO X
|
||||||
|
# TODO: Figure out a way to bring fast-forwarding into this
|
||||||
|
|
||||||
|
async def _event_snapshot(self, data):
|
||||||
|
# update listing
|
||||||
|
self.listing = Listing()
|
||||||
|
sessions = [Session.from_dict(d) for d in data.get("listing")]
|
||||||
|
for session in sessions:
|
||||||
|
self.listing.add(session)
|
||||||
|
|
||||||
|
# update (and possibly set) nick
|
||||||
|
new_nick = data.get("nick", None)
|
||||||
|
if self.session:
|
||||||
|
prev_nick = self.session.nick
|
||||||
|
if new_nick != prev_nick:
|
||||||
|
self.nick(prev_nick)
|
||||||
|
self.session.nick = new_nick
|
||||||
|
|
||||||
|
# update more room info
|
||||||
|
self.pm_with_nick = data.get("pm_with_nick", None),
|
||||||
|
self.pm_with_user_id = data.get("pm_with_user_id", None)
|
||||||
|
|
||||||
|
# Now, we're finally connected again!
|
||||||
|
self.status = Room.CONNECTED
|
||||||
|
if not self._connected_future.done(): # should always be True, I think
|
||||||
|
self._connected_future.set_result(None)
|
||||||
|
|
||||||
|
# Let's let the inhabitant know.
|
||||||
|
log = [Message.from_dict(m) for m in data.get("log")]
|
||||||
|
await self._inhabitant.connected(self, log)
|
||||||
|
|
||||||
|
# TODO: Figure out a way to bring fast-forwarding into this
|
||||||
|
# Should probably happen where this comment is
|
||||||
|
|
||||||
# SOME USEFUL PUBLIC METHODS
|
# SOME USEFUL PUBLIC METHODS
|
||||||
|
|
||||||
|
|
@ -93,21 +240,20 @@ class Room:
|
||||||
return url
|
return url
|
||||||
|
|
||||||
async def connected(self):
|
async def connected(self):
|
||||||
pass
|
await self._connected_future
|
||||||
|
|
||||||
# REST OF THE IMPLEMENTATION
|
# REST OF THE IMPLEMENTATION
|
||||||
|
|
||||||
async def _run(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _send_while_connected(*args, **kwargs):
|
async def _send_while_connected(*args, **kwargs):
|
||||||
while True:
|
while True:
|
||||||
|
if self._status == Room.CLOSED:
|
||||||
|
raise RoomClosed()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.connected()
|
await self.connected()
|
||||||
if not self._status != Room._CONNECTED: continue # TODO: Figure out a good solution
|
|
||||||
return await self._connection.send(*args, **kwargs)
|
return await self._connection.send(*args, **kwargs)
|
||||||
except RoomDisconnected:
|
except ConnectionClosed:
|
||||||
pass # Just try again
|
pass # just try again
|
||||||
|
|
||||||
|
|
||||||
class Inhabitant:
|
class Inhabitant:
|
||||||
|
|
@ -138,5 +284,8 @@ class Inhabitant:
|
||||||
async def send(self, room, message):
|
async def send(self, room, message):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def fast_forward(self, room, message):
|
||||||
|
pass
|
||||||
|
|
||||||
async def pm(self, room, from_uid, from_nick, from_room, pm_id):
|
async def pm(self, room, from_uid, from_nick, from_room, pm_id):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,54 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
#import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
#logger = logging.getLogger(__name__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"run_controller", "run_bot",
|
#"run_controller", "run_bot",
|
||||||
"mention", "mention_reduced", "similar",
|
"mention", "mention_reduced", "similar",
|
||||||
"format_time", "format_time_delta",
|
"format_time", "format_time_delta",
|
||||||
"Session", "Listing",
|
"Session", "Listing",
|
||||||
"Message", "Log",
|
"Message", "Log",
|
||||||
"ResponseError"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_controller(controller, room):
|
#def run_controller(controller, room):
|
||||||
"""
|
# """
|
||||||
Helper function to run a singular controller.
|
# Helper function to run a singular controller.
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
async def run():
|
# async def run():
|
||||||
task, reason = await controller.connect(room)
|
# task, reason = await controller.connect(room)
|
||||||
if task:
|
# if task:
|
||||||
await task
|
# await task
|
||||||
else:
|
# else:
|
||||||
logger.warn(f"Could not connect to &{room}: {reason!r}")
|
# logger.warn(f"Could not connect to &{room}: {reason!r}")
|
||||||
|
#
|
||||||
asyncio.get_event_loop().run_until_complete(run())
|
# asyncio.get_event_loop().run_until_complete(run())
|
||||||
|
#
|
||||||
def run_bot(bot_class, room, *args, **kwargs):
|
#def run_bot(bot_class, room, *args, **kwargs):
|
||||||
"""
|
# """
|
||||||
Helper function to run a bot. To run Multibots, use the MultibotKeeper.
|
# Helper function to run a bot. To run Multibots, use the MultibotKeeper.
|
||||||
This restarts the bot when it is explicitly restarted through Bot.restart().
|
# This restarts the bot when it is explicitly restarted through Bot.restart().
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
async def run():
|
# async def run():
|
||||||
while True:
|
# while True:
|
||||||
logger.info(f"Creating new instance and connecting to &{room}")
|
# logger.info(f"Creating new instance and connecting to &{room}")
|
||||||
bot = bot_class(*args, **kwargs)
|
# bot = bot_class(*args, **kwargs)
|
||||||
task, reason = await bot.connect(room)
|
# task, reason = await bot.connect(room)
|
||||||
if task:
|
# if task:
|
||||||
await task
|
# await task
|
||||||
else:
|
# else:
|
||||||
logger.warn(f"Could not connect to &{room}: {reason!r}")
|
# logger.warn(f"Could not connect to &{room}: {reason!r}")
|
||||||
|
#
|
||||||
if bot.restarting:
|
# if bot.restarting:
|
||||||
logger.info(f"Restarting in &{room}")
|
# logger.info(f"Restarting in &{room}")
|
||||||
else:
|
# else:
|
||||||
break
|
# break
|
||||||
|
#
|
||||||
asyncio.get_event_loop().run_until_complete(run())
|
# asyncio.get_event_loop().run_until_complete(run())
|
||||||
|
|
||||||
def mention(nick):
|
def mention(nick):
|
||||||
return "".join(c for c in nick if c not in ".!?;&<'\"" and not c.isspace())
|
return "".join(c for c in nick if c not in ".!?;&<'\"" and not c.isspace())
|
||||||
|
|
@ -157,8 +156,13 @@ class Listing:
|
||||||
self._sessions.pop(session_id)
|
self._sessions.pop(session_id)
|
||||||
|
|
||||||
def remove_combo(self, server_id, server_era):
|
def remove_combo(self, server_id, server_era):
|
||||||
|
removed = [ses for ses in self._sessions.items()
|
||||||
|
if ses.server_id == server_id and ses.server_era == server_era]
|
||||||
|
|
||||||
self._sessions = {i: ses for i, ses in self._sessions.items()
|
self._sessions = {i: ses for i, ses in self._sessions.items()
|
||||||
if ses.server_id != server_id and ses.server_era != server_era}
|
if ses.server_id != server_id and ses.server_era != server_era}
|
||||||
|
|
||||||
|
return removed
|
||||||
|
|
||||||
def by_sid(self, session_id):
|
def by_sid(self, session_id):
|
||||||
return self._sessions.get(session_id);
|
return self._sessions.get(session_id);
|
||||||
|
|
@ -226,9 +230,3 @@ class Message():
|
||||||
d.get("deleted", None),
|
d.get("deleted", None),
|
||||||
d.get("truncated", None)
|
d.get("truncated", None)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Log:
|
|
||||||
pass # TODO
|
|
||||||
|
|
||||||
class ResponseError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue