From 31ce80b2bf96d8475ab559afeb9800929683b784 Mon Sep 17 00:00:00 2001 From: n9k Date: Tue, 14 Jun 2022 01:11:29 +0000 Subject: [PATCH] Control socket: view and change users' attributes Changing things without thinking about it is probably going to cause weird undefined behaviour. --- anonstream/control/server.py | 124 ++++++++++++++++++++++++++++++++++- anonstream/utils/user.py | 5 +- 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/anonstream/control/server.py b/anonstream/control/server.py index 4a64abd..506c7f0 100644 --- a/anonstream/control/server.py +++ b/anonstream/control/server.py @@ -1,8 +1,15 @@ import asyncio import json +from quart import current_app + from anonstream.chat import delete_chat_messages from anonstream.stream import get_stream_title, set_stream_title +from anonstream.utils.user import USER_WEBSOCKET_ATTRS + +USERS_BY_TOKEN = current_app.users_by_token +USERS = current_app.users +USERS_UPDATE_BUFFER = current_app.users_update_buffer class UnknownMethod(Exception): pass @@ -218,6 +225,112 @@ async def command_title_set(args): raise Garbage(garbage) return normal_options, response +async def command_user_help(args): + match args: + case []: + normal_options = ['help'] + response = ( + 'Usage: user [show | attr USER | get USER ATTR | set USER ATTR VALUE]\n' + 'Commands:\n' + ' user [show]...........show all users\' tokens\n' + ' user attr USER........show names of a user\'s attributes\n' + ' user get USER ATTR....show an attribute of a user\n' + ' user set USER ATTR....set an attribute of a user\n' + 'Definitions:\n' + ' USER..................={token TOKEN | hash HASH}\n' + ' TOKEN.................a token\n' + ' HASH..................a token hash\n' + ' ATTR..................a user attribute, re:[a-z0-9_]+\n' + ) + case [*garbage]: + raise Garbage(garbage) + return normal_options, response + +async def command_user_show(args): + match args: + case []: + normal_options = ['show'] + response = json.dumps(tuple(USERS_BY_TOKEN)) + '\n' + case [*garbage]: + raise Garbage(garbage) + return normal_options, response + +async def command_user_attr(args): + match args: + case []: + raise Incomplete + case ['token', token_json]: + try: + token = json.loads(token_json) + except json.JSONDecodeError: + raise BadArgument('could not decode token as json') + try: + user = USERS_BY_TOKEN[token] + except KeyError: + raise Failed(f"no user exists with token {token!r}, try 'user show'") + normal_options = ['attr', 'token', json.dumps(token).replace(' ', r'\u0020')] + response = json.dumps(tuple(user.keys())) + '\n' + case [*garbage]: + raise Garbage(garbage) + return normal_options, response + +async def command_user_get(args): + match args: + case ['token', token_json, attr]: + try: + token = json.loads(token_json) + except json.JSONDecodeError: + raise BadArgument('could not decode token as json') + try: + user = USERS_BY_TOKEN[token] + except KeyError: + raise Failed(f"no user exists with token {token!r}, try 'user show'") + try: + value = user[attr] + except KeyError: + raise Failed(f"user has no attribute {attr!r}, try 'user attr token {token}'") + try: + value_json = json.dumps(value) + except TypeError: + raise Failed(f'attribute {attr!r} is not JSON serializable') + normal_options = ['get', 'token', json.dumps(token).replace(' ', r'\u0020'), attr] + response = value_json + '\n' + case []: + raise Incomplete + case [*garbage]: + raise Garbage(garbage) + return normal_options, response + +async def command_user_set(args): + match args: + case ['token', token_json, attr, value_json]: + try: + token = json.loads(token_json) + except json.JSONDecodeError: + raise BadArgument('could not decode token as json') + try: + user = USERS_BY_TOKEN[token] + except KeyError: + raise Failed(f"no user exists with token {token!r}, try 'user show'") + try: + value = user[attr] + except KeyError: + raise Failed(f"user has no attribute {attr!r}, try 'user attr token {TOKEN}") + try: + value = json.loads(value_json) + except JSON.JSONDecodeError: + raise Failed('could not decode json') + user[attr] = value + if attr in USER_WEBSOCKET_ATTRS: + USERS_UPDATE_BUFFER.add(token) + normal_options = ['set', 'token', json.dumps(token).replace(' ', r'\u0020'), attr, json.dumps(value)] + response = '' + case []: + raise Incomplete + case [*garbage]: + raise Garbage(garbage) + return normal_options, response + async def command_chat_help(args): match args: case []: @@ -254,6 +367,7 @@ METHOD_HELP = 'help' METHOD_EXIT = 'exit' METHOD_TITLE = 'title' METHOD_CHAT = 'chat' +METHOD_USER = 'user' METHOD_COMMAND_FUNCTIONS = { METHOD_HELP: { @@ -274,5 +388,13 @@ METHOD_COMMAND_FUNCTIONS = { None: command_chat_help, 'help': command_chat_help, 'delete': command_chat_delete, - } + }, + METHOD_USER: { + None: command_user_show, + 'help': command_user_help, + 'show': command_user_show, + 'attr': command_user_attr, + 'get': command_user_get, + 'set': command_user_set, + }, } diff --git a/anonstream/utils/user.py b/anonstream/utils/user.py index 5fef85a..8a01228 100644 --- a/anonstream/utils/user.py +++ b/anonstream/utils/user.py @@ -10,6 +10,8 @@ from math import inf from quart import escape, Markup +USER_WEBSOCKET_ATTRS = {'broadcaster', 'name', 'color', 'tripcode', 'tag'} + Presence = Enum( 'Presence', names=( @@ -44,8 +46,7 @@ def trilean(presence): return None def get_user_for_websocket(user): - keys = ('broadcaster', 'name', 'color', 'tripcode', 'tag') return { - **{key: user[key] for key in keys}, + **{key: user[key] for key in USER_WEBSOCKET_ATTRS}, 'watching': trilean(user['presence']), }