Compare commits
2 Commits
main
...
feat/mkeve
Author | SHA1 | Date |
---|---|---|
Derek | a0487033af | |
Derek | 56f57f22b6 |
|
@ -14,7 +14,7 @@ class GracefulShutdownException(Exception):
|
|||
|
||||
|
||||
class ShutdownRequest(Event):
|
||||
pass
|
||||
_hidden = True
|
||||
|
||||
|
||||
class ChatProcess(Process, ABC):
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
from .group import cli
|
||||
from .start import start
|
||||
from .mkevent import mkevent
|
||||
from .websocketutils import websocketutils
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import json
|
||||
|
||||
import click
|
||||
import websockets
|
||||
|
||||
from .group import cli
|
||||
from events import Message, Subscription
|
||||
from events.Message import USER_TYPE
|
||||
from chats.Twitch.Events import Raid
|
||||
from utils import make_sync
|
||||
|
||||
@make_sync
|
||||
async def send(data, ws):
|
||||
async with websockets.connect(ws) as websocket:
|
||||
await websocket.send(data)
|
||||
|
||||
|
||||
@cli.group()
|
||||
@click.option('--ws', default='ws://localhost:8080')
|
||||
@click.option('--via', default="console")
|
||||
@click.pass_context
|
||||
def mkevent(ctx, ws, via):
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['ws'] = ws
|
||||
ctx.obj['via'] = via
|
||||
|
||||
@mkevent.command(help="Make event via JSON")
|
||||
@click.argument('data_json')
|
||||
@click.pass_context
|
||||
def raw(ctx, data_json):
|
||||
send(data_json, ctx.obj['ws'])
|
||||
|
||||
@mkevent.command(help="Make generic chat message")
|
||||
@click.argument('username')
|
||||
@click.argument('text')
|
||||
@click.option('-t', '--type', 'user_type', default=USER_TYPE.SYSTEM.name,
|
||||
type=click.Choice([enum.name for enum in USER_TYPE]))
|
||||
@click.pass_context
|
||||
def msg(ctx, username, text, user_type=None):
|
||||
user_type = USER_TYPE[user_type]
|
||||
mockevent = Message(ctx.obj['via'], text, username, username, user_type)
|
||||
send(mockevent.serialize(), ctx.obj['ws'])
|
||||
|
||||
@mkevent.command(help="Make Twitch Raid")
|
||||
@click.argument('channel')
|
||||
@click.option('-c', '--count', type=click.IntRange(1, None), default=1)
|
||||
@click.pass_context
|
||||
def raid(ctx, channel, count=None):
|
||||
mockevent = Raid(ctx.obj['via'], channel, channel, count)
|
||||
send(mockevent.serialize(), ctx.obj['ws'])
|
||||
|
||||
@mkevent.command(help="Make subscription")
|
||||
@click.argument('user')
|
||||
@click.option('-s', '--streak', type=click.IntRange(0, None), default=0)
|
||||
@click.option('-g', '--gifted', multiple=True)
|
||||
@click.pass_context
|
||||
def sub(ctx, user, streak=None, gifted=None):
|
||||
mockevent = Subscription(ctx.obj['via'], user, user, streak=streak, gifted_to=gifted)
|
||||
send(mockevent.serialize(), ctx.obj['ws'])
|
|
@ -58,6 +58,7 @@ class ConfigChangeHandler(FileSystemEventHandler):
|
|||
@click.option('--show-time/--no-time', default=False, help="Show time in logs")
|
||||
@click.option('--watch/--no-watch', default=True, help="Automatically reload on config changes")
|
||||
def start(config_file, loglevel, show_time=False, watch=True, port=None, bind=None):
|
||||
"""Start audiencekit server"""
|
||||
# Set root logger details
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(loglevel)
|
||||
|
@ -70,7 +71,7 @@ def start(config_file, loglevel, show_time=False, watch=True, port=None, bind=No
|
|||
logging.getLogger('websockets.client').setLevel(logging.INFO)
|
||||
logging.getLogger('asyncio').setLevel(logging.INFO)
|
||||
logging.getLogger('urllib3.connectionpool').setLevel(logging.INFO)
|
||||
|
||||
|
||||
reload_event = Event()
|
||||
if watch:
|
||||
handler = ConfigChangeHandler(reload_event)
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import json
|
||||
import importlib
|
||||
import dataclasses
|
||||
|
||||
import click
|
||||
import websockets
|
||||
|
||||
from .group import cli
|
||||
from events import Event
|
||||
from utils import make_sync, get_subclasses
|
||||
|
||||
@make_sync
|
||||
async def send(data, ws):
|
||||
async with websockets.connect(ws) as websocket:
|
||||
await websocket.send(data)
|
||||
|
||||
@click.pass_context
|
||||
def mkevent_generic(ctx, *args, **kwargs):
|
||||
mockevent = ctx.obj['event'](ctx.obj['via'], *args, **kwargs)
|
||||
send(mockevent.serialize(), ctx.obj['ws'])
|
||||
|
||||
|
||||
class EventCommandFactory(click.MultiCommand):
|
||||
def list_commands(self, ctx):
|
||||
event_classes = get_subclasses(Event)
|
||||
return [cls.__name__ for cls in event_classes if cls._hidden != True]
|
||||
|
||||
def get_command(self, ctx, name):
|
||||
target_event = next((cls for cls in get_subclasses(Event) if cls.__name__ == name), None)
|
||||
|
||||
ctx.obj['event'] = target_event
|
||||
|
||||
params = []
|
||||
for field in dataclasses.fields(target_event):
|
||||
# Skip over Event base fields
|
||||
if field.name in ['id', 'via', 'raw', 'timestamp']:
|
||||
continue
|
||||
# Make argument from args, and options from kwargs
|
||||
required = all(isinstance(default, dataclasses._MISSING_TYPE) for default in [field.default, field.default_factory])
|
||||
if required:
|
||||
param = click.Argument([field.name])
|
||||
else:
|
||||
param = click.Option(param_decls=[f'--{field.name}'])
|
||||
params.append(param)
|
||||
|
||||
command = click.Command(name, callback=mkevent_generic, params=params, help=target_event.__doc__)
|
||||
return command
|
||||
|
||||
|
||||
@cli.group(name='ws')
|
||||
@click.option('--target', '-t', default='ws://localhost:8080', help="Websocket bus of the running server")
|
||||
@click.option('--chatmod', '-c', multiple=True, help="Load a chat module (makes its specific events accessible)")
|
||||
@click.option('--pluginmod', '-p', multiple=True, help="Load a plugin module (makes its specific events accessible)")
|
||||
@click.pass_context
|
||||
def websocketutils(ctx, target, chatmod=[], pluginmod=[]):
|
||||
"""Send events to a running server"""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['ws'] = target
|
||||
|
||||
try:
|
||||
for module_name in chatmod:
|
||||
importlib.import_module(f'.{module_name}', package='chats')
|
||||
|
||||
for module_name in pluginmod:
|
||||
importlib.import_module(f'.{module_name}', package='plugins')
|
||||
except ModuleNotFoundError as e:
|
||||
option = 'chatmod' if e.name.startswith('chat') else 'pluginmod'
|
||||
raise click.BadOptionUsage(option, f'Could not import requested module: {e.name}', ctx=ctx)
|
||||
|
||||
@websocketutils.command(cls=EventCommandFactory)
|
||||
@click.option('--via', default="console")
|
||||
@click.option('--id')
|
||||
@click.option('--raw')
|
||||
@click.option('--timestamp')
|
||||
@click.pass_context
|
||||
def mkevent(ctx, via, id, raw, timestamp):
|
||||
"""Create event via CLI"""
|
||||
ctx.obj['via'] = via
|
||||
ctx.obj['id'] = id
|
||||
ctx.obj['raw'] = raw
|
||||
ctx.obj['timestamp'] = timestamp
|
||||
|
||||
@websocketutils.command()
|
||||
@click.argument('data_json')
|
||||
@click.pass_context
|
||||
def raw(ctx, data_json):
|
||||
"""Create event via JSON"""
|
||||
send(data_json, ctx.obj['ws'])
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import websockets
|
||||
|
||||
from events import Event
|
||||
from utils import get_subclasses
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -20,13 +21,7 @@ class WebsocketServerProcess(Process):
|
|||
self._pipe, self._caller_pipe = Pipe()
|
||||
self.clients = set()
|
||||
|
||||
self.update_classes()
|
||||
|
||||
def update_classes(self):
|
||||
def all_subclasses(cls):
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
self._event_classes = all_subclasses(Event)
|
||||
self._event_classes = get_subclasses(Event)
|
||||
|
||||
@property
|
||||
def message_pipe(self):
|
||||
|
|
|
@ -6,6 +6,7 @@ from events import Event
|
|||
|
||||
@dataclass
|
||||
class Control(Event):
|
||||
"""Generic inter-bus communication"""
|
||||
target: str
|
||||
data: dict = field(default_factory=dict)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from events import Event
|
|||
|
||||
@dataclass
|
||||
class Delete(Event):
|
||||
"""Inform clients to remove a specific event"""
|
||||
target_id: int
|
||||
show_masked: bool = False
|
||||
reason: str = None
|
||||
|
|
|
@ -6,6 +6,8 @@ from dataclasses import dataclass, field, asdict
|
|||
|
||||
@dataclass
|
||||
class Event:
|
||||
# Set to true in your subclass to disable CLI creation
|
||||
_hidden = False
|
||||
via: str
|
||||
id: int = field(default_factory=lambda: random.getrandbits(32), kw_only=True)
|
||||
timestamp: float = field(default_factory=time.time, kw_only=True)
|
||||
|
|
|
@ -5,6 +5,7 @@ from events import Event
|
|||
|
||||
@dataclass
|
||||
class Follow(Event):
|
||||
"""User has signed up for go-live notifications"""
|
||||
user_name: str
|
||||
user_id: str
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ class USER_TYPE(str, Enum):
|
|||
|
||||
@dataclass
|
||||
class Message(Event):
|
||||
"""Chat message"""
|
||||
text: str
|
||||
user_name: str
|
||||
user_id: str
|
||||
|
@ -42,8 +43,8 @@ class Message(Event):
|
|||
|
||||
@dataclass
|
||||
class SysMessage(Message):
|
||||
"""A Message with user_type, user_name, and user_id set automatically (SYSTEM and Event.via respectfuly)"""
|
||||
|
||||
"""Message with user_type, user_name, and user_id set automatically (SYSTEM and Event.via respectfuly)"""
|
||||
_hidden = True
|
||||
user_name: str = field(init=False)
|
||||
user_id: str = field(init=False)
|
||||
user_type: USER_TYPE = USER_TYPE.SYSTEM
|
||||
|
|
|
@ -5,6 +5,7 @@ from events import Event
|
|||
|
||||
@dataclass
|
||||
class Subscription(Event):
|
||||
"""User has signed up for a re-ocurring donation"""
|
||||
user_name: str
|
||||
user_id: str
|
||||
gifted_to: list = None
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
from .NonBlockingWebsocket import NonBlockingWebsocket
|
||||
from .make_sync import make_sync
|
||||
from .get_subclasses import get_subclasses
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
def get_subclasses(cls):
|
||||
"""Return a set of all subclasses (recursively) of a given class"""
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in get_subclasses(c)])
|
Loading…
Reference in New Issue