Add Bot class
- Use rewrite-2 callbacks modified for async - Utility function for running singular controller - Update TestBot to work with new system
This commit is contained in:
parent
b8bb75a897
commit
053573e3cb
6 changed files with 227 additions and 73 deletions
|
|
@ -5,9 +5,10 @@ logging.basicConfig(level=logging.INFO)
|
|||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
from .bot import *
|
||||
from .connection import *
|
||||
from .room import *
|
||||
from .controller import *
|
||||
from .room import *
|
||||
from .utils import *
|
||||
|
||||
__all__ = connection.__all__ + room.__all__ + controller.__all__ + utils.__all__
|
||||
|
|
|
|||
138
yaboli/bot.py
Normal file
138
yaboli/bot.py
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from .callbacks import *
|
||||
from .controller import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
__all__ = ["Bot"]
|
||||
|
||||
|
||||
|
||||
class Bot(Controller):
|
||||
# ^ and $ not needed since we're doing a re.fullmatch
|
||||
SPECIFIC_RE = r"!(\S+)\s+@(\S+)([\S\s]*)"
|
||||
GENERIC_RE = r"!(\S+)([\S\s]*)"
|
||||
|
||||
def __init__(self, nick):
|
||||
super().__init__(nick)
|
||||
|
||||
self._callbacks = Callbacks()
|
||||
self.register_default_callbacks()
|
||||
|
||||
def register_callback(self, event, callback, specific=True):
|
||||
self._callbacks.add((event, specific), callback)
|
||||
|
||||
async def on_send(self, message):
|
||||
parsed = self.parse_message(message.content)
|
||||
if not parsed:
|
||||
return
|
||||
command, args = parsed
|
||||
|
||||
# general callback (specific set to False)
|
||||
general = asyncio.ensure_future(
|
||||
self._callbacks.call((command, False), message, args)
|
||||
)
|
||||
|
||||
if len(args) > 0:
|
||||
mention = args[0]
|
||||
args = args[1:]
|
||||
if mention[:1] == "@" and similar(mention[1:], self.nick):
|
||||
# specific callback (specific set to True)
|
||||
await self._callbacks.call((command, True), message, args)
|
||||
|
||||
await general
|
||||
|
||||
def parse_message(self, content):
|
||||
"""
|
||||
(command, args) = parse_message(content)
|
||||
|
||||
Returns None, not a (None, None) tuple, when message could not be parsed
|
||||
"""
|
||||
|
||||
match = re.fullmatch(self.GENERIC_RE, content)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
command = match.group(1)
|
||||
argstr = match.group(2)
|
||||
args = self.parse_args(argstr)
|
||||
|
||||
return command, args
|
||||
|
||||
def parse_args(self, text):
|
||||
"""
|
||||
Use single- and double-quotes bash-style to include whitespace in arguments.
|
||||
A backslash always escapes the next character.
|
||||
Any non-escaped whitespace separates arguments.
|
||||
|
||||
Returns a list of arguments.
|
||||
Deals with unclosed quotes and backslashes without crashing.
|
||||
"""
|
||||
|
||||
escape = False
|
||||
quote = None
|
||||
args = []
|
||||
arg = ""
|
||||
|
||||
for character in text:
|
||||
if escape:
|
||||
arg += character
|
||||
escape = False
|
||||
elif character == "\\":
|
||||
escape = True
|
||||
elif quote:
|
||||
if character == quote:
|
||||
quote = None
|
||||
else:
|
||||
arg += character
|
||||
elif character in "'\"":
|
||||
quote = character
|
||||
elif character.isspace() and len(arg) > 0:
|
||||
args.append(arg)
|
||||
arg = ""
|
||||
else:
|
||||
arg += character
|
||||
|
||||
#if escape or quote:
|
||||
#return None # syntax error
|
||||
|
||||
if len(arg) > 0:
|
||||
args.append(arg)
|
||||
|
||||
return args
|
||||
|
||||
def parse_flags(self, arglist):
|
||||
flags = ""
|
||||
args = []
|
||||
kwargs = {}
|
||||
|
||||
for arg in arglist:
|
||||
# kwargs (--abc, --foo=bar)
|
||||
if arg[:2] == "--":
|
||||
arg = arg[2:]
|
||||
if "=" in arg:
|
||||
s = arg.split("=", maxsplit=1)
|
||||
kwargs[s[0]] = s[1]
|
||||
else:
|
||||
kwargs[arg] = None
|
||||
# flags (-x, -rw)
|
||||
elif arg[:1] == "-":
|
||||
arg = arg[1:]
|
||||
flags += arg
|
||||
# args (normal arguments)
|
||||
else:
|
||||
args.append(arg)
|
||||
|
||||
return flags, args, kwargs
|
||||
|
||||
|
||||
|
||||
# BOTRULEZ COMMANDS
|
||||
|
||||
def register_default_callbacks(self):
|
||||
self.register_callback("ping", self.command_ping)
|
||||
self.register_callback("ping", self.command_ping, specific=False)
|
||||
|
||||
async def command_ping(self, message, args):
|
||||
await self.room.send("Pong!", message.message_id)
|
||||
|
|
@ -1,30 +1,27 @@
|
|||
import asyncio
|
||||
|
||||
__all__ = ["Callbacks"]
|
||||
|
||||
|
||||
|
||||
class Callbacks():
|
||||
"""
|
||||
Manage callbacks
|
||||
Manage callbacks asynchronously
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._callbacks = {}
|
||||
|
||||
def add(self, event, callback, *args, **kwargs):
|
||||
def add(self, event, callback):
|
||||
"""
|
||||
add(event, callback, *args, **kwargs) -> None
|
||||
add(event, callback) -> None
|
||||
|
||||
Add a function to be called on event.
|
||||
The function will be called with *args and **kwargs.
|
||||
Certain arguments might be added, depending on the event.
|
||||
"""
|
||||
|
||||
if not event in self._callbacks:
|
||||
self._callbacks[event] = []
|
||||
|
||||
callback_info = {
|
||||
"callback": callback,
|
||||
"args": args,
|
||||
"kwargs": kwargs
|
||||
}
|
||||
|
||||
self._callbacks[event].append(callback_info)
|
||||
self._callbacks[event].append(callback)
|
||||
|
||||
def remove(self, event):
|
||||
"""
|
||||
|
|
@ -36,21 +33,18 @@ class Callbacks():
|
|||
if event in self._callbacks:
|
||||
del self._callbacks[event]
|
||||
|
||||
def call(self, event, *args):
|
||||
async def call(self, event, *args, **kwargs):
|
||||
"""
|
||||
call(event) -> None
|
||||
await call(event) -> None
|
||||
|
||||
Call all callbacks subscribed to the event with *args and the arguments specified when the
|
||||
callback was added.
|
||||
Call all callbacks subscribed to the event with *args and **kwargs".
|
||||
"""
|
||||
|
||||
if event in self._callbacks:
|
||||
for c_info in self._callbacks[event]:
|
||||
c = c_info["callback"]
|
||||
args = c_info["args"] + args
|
||||
kwargs = c_info["kwargs"]
|
||||
|
||||
c(*args, **kwargs)
|
||||
tasks = [asyncio.ensure_future(callback(*args, **kwargs))
|
||||
for callback in self._callbacks.get(event, [])]
|
||||
|
||||
for task in tasks:
|
||||
await task
|
||||
|
||||
def exists(self, event):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import logging
|
||||
from .callbacks import *
|
||||
from .connection import *
|
||||
from .utils import *
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ class Room:
|
|||
self.pm_with_nick = None
|
||||
self.pm_with_user_id = None
|
||||
|
||||
self._callbacks = {}
|
||||
self._callbacks = Callbacks()
|
||||
self._add_callbacks()
|
||||
|
||||
self._stopping = False
|
||||
|
|
@ -266,20 +267,20 @@ class Room:
|
|||
# All the private functions for dealing with stuff
|
||||
|
||||
def _add_callbacks(self):
|
||||
self._callbacks["bounce-event"] = self._handle_bounce
|
||||
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
|
||||
self._callbacks.add("bounce-event", self._handle_bounce)
|
||||
self._callbacks.add("disconnect-event", self._handle_disconnect)
|
||||
self._callbacks.add("hello-event", self._handle_hello)
|
||||
self._callbacks.add("join-event", self._handle_join)
|
||||
self._callbacks.add("login-event", self._handle_login)
|
||||
self._callbacks.add("logout-event", self._handle_logout)
|
||||
self._callbacks.add("network-event", self._handle_network)
|
||||
self._callbacks.add("nick-event", self._handle_nick)
|
||||
self._callbacks.add("edit-message-event", self._handle_edit_message)
|
||||
self._callbacks.add("part-event", self._handle_part)
|
||||
self._callbacks.add("ping-event", self._handle_ping)
|
||||
self._callbacks.add("pm-initiate-event", self._handle_pm_initiate)
|
||||
self._callbacks.add("send-event", self._handle_send)
|
||||
self._callbacks.add("snapshot-event", self._handle_snapshot)
|
||||
|
||||
async def _send_packet(self, *args, **kwargs):
|
||||
response = await self._conn.send(*args, **kwargs)
|
||||
|
|
@ -291,12 +292,10 @@ class Room:
|
|||
self._check_for_errors(packet)
|
||||
|
||||
ptype = packet.get("type")
|
||||
callback = self._callbacks.get(ptype)
|
||||
if callback:
|
||||
try:
|
||||
await callback(packet)
|
||||
except asyncio.CancelledError as e:
|
||||
logger.info(f"&{self.roomname}: Callback of type {ptype!r} cancelled.")
|
||||
try:
|
||||
await self._callbacks.call(ptype, packet)
|
||||
except asyncio.CancelledError as e:
|
||||
logger.info(f"&{self.roomname}: Callback of type {ptype!r} cancelled.")
|
||||
|
||||
def _check_for_errors(self, packet):
|
||||
if packet.get("throttled", False):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import asyncio
|
||||
|
||||
__all__ = [
|
||||
"run_controller",
|
||||
"mention", "mention_reduced", "similar",
|
||||
"Session", "Listing",
|
||||
"Message", "Log",
|
||||
|
|
@ -7,6 +10,18 @@ __all__ = [
|
|||
|
||||
|
||||
|
||||
def run_controller(controller, room):
|
||||
"""
|
||||
Helper function to run a singular controller.
|
||||
"""
|
||||
|
||||
async def run():
|
||||
task, reason = await controller.connect(room)
|
||||
if task:
|
||||
await task
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(run())
|
||||
|
||||
def mention(nick):
|
||||
return "".join(c for c in nick if c not in ".!?;&<'\"" and not c.isspace())
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue