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
61
TestBot.py
61
TestBot.py
|
|
@ -1,41 +1,48 @@
|
||||||
import asyncio
|
|
||||||
import yaboli
|
import yaboli
|
||||||
from yaboli.utils import *
|
from yaboli.utils import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#class TestBot(Bot):
|
#class TestBot(Bot):
|
||||||
class TestBot(yaboli.Controller):
|
class TestBot(yaboli.Bot):
|
||||||
def __init__(self, nick):
|
def __init__(self, nick):
|
||||||
super().__init__(nick=nick)
|
super().__init__(nick=nick)
|
||||||
|
|
||||||
|
self.register_callback("tree", self.command_tree, specific=False)
|
||||||
|
|
||||||
async def on_send(self, message):
|
#async def on_send(self, message):
|
||||||
if message.content == "!spawnevil":
|
#if message.content == "!spawnevil":
|
||||||
bot = TestBot("TestSpawn")
|
#bot = TestBot("TestSpawn")
|
||||||
task, reason = await bot.connect("test")
|
#task, reason = await bot.connect("test")
|
||||||
second = await self.room.send("We have " + ("a" if task else "no") + " task. Reason: " + reason, message.message_id)
|
#second = await self.room.send("We have " + ("a" if task else "no") + " task. Reason: " + reason, message.message_id)
|
||||||
if task:
|
#if task:
|
||||||
await bot.stop()
|
#await bot.stop()
|
||||||
await self.room.send("Stopped." if task.done() else "Still running (!)", second.message_id)
|
#await self.room.send("Stopped." if task.done() else "Still running (!)", second.message_id)
|
||||||
|
|
||||||
await self.room.send("All's over now.", message.message_id)
|
#await self.room.send("All's over now.", message.message_id)
|
||||||
|
|
||||||
elif message.content == "!tree":
|
#elif message.content == "!tree":
|
||||||
messages = [message]
|
#messages = [message]
|
||||||
|
#newmessages = []
|
||||||
|
#for i in range(2):
|
||||||
|
#for m in messages:
|
||||||
|
#for j in range(2):
|
||||||
|
#newm = await self.room.send(f"{m.content}.{j}", m.message_id)
|
||||||
|
#newmessages.append(newm)
|
||||||
|
#messages = newmessages
|
||||||
|
#newmessages = []
|
||||||
|
|
||||||
|
async def command_tree(self, message, args):
|
||||||
|
messages = [message]
|
||||||
|
newmessages = []
|
||||||
|
for i in range(2):
|
||||||
|
for m in messages:
|
||||||
|
for j in range(2):
|
||||||
|
newm = await self.room.send(f"{message.content}.{j}", m.message_id)
|
||||||
|
newmessages.append(newm)
|
||||||
|
messages = newmessages
|
||||||
newmessages = []
|
newmessages = []
|
||||||
for i in range(2):
|
|
||||||
for m in messages:
|
|
||||||
for j in range(2):
|
|
||||||
newm = await self.room.send(f"{m.content}.{j}", m.message_id)
|
|
||||||
newmessages.append(newm)
|
|
||||||
messages = newmessages
|
|
||||||
newmessages = []
|
|
||||||
|
|
||||||
async def run_bot():
|
|
||||||
bot = TestBot("TestSummoner")
|
|
||||||
task, reason = await bot.connect("test")
|
|
||||||
if task:
|
|
||||||
await task
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.get_event_loop().run_until_complete(run_bot())
|
bot = TestBot("TestSummoner")
|
||||||
|
run_controller(bot, "test")
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
from .bot import *
|
||||||
from .connection import *
|
from .connection import *
|
||||||
from .room import *
|
|
||||||
from .controller import *
|
from .controller import *
|
||||||
|
from .room import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
__all__ = connection.__all__ + room.__all__ + controller.__all__ + utils.__all__
|
__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():
|
class Callbacks():
|
||||||
"""
|
"""
|
||||||
Manage callbacks
|
Manage callbacks asynchronously
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._callbacks = {}
|
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.
|
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:
|
if not event in self._callbacks:
|
||||||
self._callbacks[event] = []
|
self._callbacks[event] = []
|
||||||
|
self._callbacks[event].append(callback)
|
||||||
callback_info = {
|
|
||||||
"callback": callback,
|
|
||||||
"args": args,
|
|
||||||
"kwargs": kwargs
|
|
||||||
}
|
|
||||||
|
|
||||||
self._callbacks[event].append(callback_info)
|
|
||||||
|
|
||||||
def remove(self, event):
|
def remove(self, event):
|
||||||
"""
|
"""
|
||||||
|
|
@ -36,21 +33,18 @@ class Callbacks():
|
||||||
if event in self._callbacks:
|
if event in self._callbacks:
|
||||||
del self._callbacks[event]
|
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
|
Call all callbacks subscribed to the event with *args and **kwargs".
|
||||||
callback was added.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if event in self._callbacks:
|
tasks = [asyncio.ensure_future(callback(*args, **kwargs))
|
||||||
for c_info in self._callbacks[event]:
|
for callback in self._callbacks.get(event, [])]
|
||||||
c = c_info["callback"]
|
|
||||||
args = c_info["args"] + args
|
for task in tasks:
|
||||||
kwargs = c_info["kwargs"]
|
await task
|
||||||
|
|
||||||
c(*args, **kwargs)
|
|
||||||
|
|
||||||
def exists(self, event):
|
def exists(self, event):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from .callbacks import *
|
||||||
from .connection import *
|
from .connection import *
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
|
||||||
|
|
@ -33,7 +34,7 @@ class Room:
|
||||||
self.pm_with_nick = None
|
self.pm_with_nick = None
|
||||||
self.pm_with_user_id = None
|
self.pm_with_user_id = None
|
||||||
|
|
||||||
self._callbacks = {}
|
self._callbacks = Callbacks()
|
||||||
self._add_callbacks()
|
self._add_callbacks()
|
||||||
|
|
||||||
self._stopping = False
|
self._stopping = False
|
||||||
|
|
@ -266,20 +267,20 @@ class Room:
|
||||||
# All the private functions for dealing with stuff
|
# All the private functions for dealing with stuff
|
||||||
|
|
||||||
def _add_callbacks(self):
|
def _add_callbacks(self):
|
||||||
self._callbacks["bounce-event"] = self._handle_bounce
|
self._callbacks.add("bounce-event", self._handle_bounce)
|
||||||
self._callbacks["disconnect-event"] = self._handle_disconnect
|
self._callbacks.add("disconnect-event", self._handle_disconnect)
|
||||||
self._callbacks["hello-event"] = self._handle_hello
|
self._callbacks.add("hello-event", self._handle_hello)
|
||||||
self._callbacks["join-event"] = self._handle_join
|
self._callbacks.add("join-event", self._handle_join)
|
||||||
self._callbacks["login-event"] = self._handle_login
|
self._callbacks.add("login-event", self._handle_login)
|
||||||
self._callbacks["logout-event"] = self._handle_logout
|
self._callbacks.add("logout-event", self._handle_logout)
|
||||||
self._callbacks["network-event"] = self._handle_network
|
self._callbacks.add("network-event", self._handle_network)
|
||||||
self._callbacks["nick-event"] = self._handle_nick
|
self._callbacks.add("nick-event", self._handle_nick)
|
||||||
self._callbacks["edit-message-event"] = self._handle_edit_message
|
self._callbacks.add("edit-message-event", self._handle_edit_message)
|
||||||
self._callbacks["part-event"] = self._handle_part
|
self._callbacks.add("part-event", self._handle_part)
|
||||||
self._callbacks["ping-event"] = self._handle_ping
|
self._callbacks.add("ping-event", self._handle_ping)
|
||||||
self._callbacks["pm-initiate-event"] = self._handle_pm_initiate
|
self._callbacks.add("pm-initiate-event", self._handle_pm_initiate)
|
||||||
self._callbacks["send-event"] = self._handle_send
|
self._callbacks.add("send-event", self._handle_send)
|
||||||
self._callbacks["snapshot-event"] = self._handle_snapshot
|
self._callbacks.add("snapshot-event", self._handle_snapshot)
|
||||||
|
|
||||||
async def _send_packet(self, *args, **kwargs):
|
async def _send_packet(self, *args, **kwargs):
|
||||||
response = await self._conn.send(*args, **kwargs)
|
response = await self._conn.send(*args, **kwargs)
|
||||||
|
|
@ -291,12 +292,10 @@ class Room:
|
||||||
self._check_for_errors(packet)
|
self._check_for_errors(packet)
|
||||||
|
|
||||||
ptype = packet.get("type")
|
ptype = packet.get("type")
|
||||||
callback = self._callbacks.get(ptype)
|
try:
|
||||||
if callback:
|
await self._callbacks.call(ptype, packet)
|
||||||
try:
|
except asyncio.CancelledError as e:
|
||||||
await callback(packet)
|
logger.info(f"&{self.roomname}: Callback of type {ptype!r} cancelled.")
|
||||||
except asyncio.CancelledError as e:
|
|
||||||
logger.info(f"&{self.roomname}: Callback of type {ptype!r} cancelled.")
|
|
||||||
|
|
||||||
def _check_for_errors(self, packet):
|
def _check_for_errors(self, packet):
|
||||||
if packet.get("throttled", False):
|
if packet.get("throttled", False):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"run_controller",
|
||||||
"mention", "mention_reduced", "similar",
|
"mention", "mention_reduced", "similar",
|
||||||
"Session", "Listing",
|
"Session", "Listing",
|
||||||
"Message", "Log",
|
"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):
|
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())
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue