operator api

This commit is contained in:
AF 2022-12-09 10:27:56 +00:00
parent 0fb4af62ec
commit aadf14c4df
5 changed files with 102 additions and 39 deletions

View File

@ -23,34 +23,27 @@ class Api:
def __init__(self, explicit: Explicit) -> None: def __init__(self, explicit: Explicit) -> None:
super().__init__(*explicit.args) super().__init__(*explicit.args)
def __init__(self, client: discord.Client) -> None: def __init__(self, client: discord.Client, roles: dict[str, str]) -> None:
self.client = client self.client = client
self.roles = roles
def is_operator(self, user_id: int) -> bool:
return '(operator)' in self.roles.get(f'roles{user_id}', '')
async def api(self, request: dict, user_id: int) -> ResponseType: async def api(self, request: dict, user_id: int) -> ResponseType:
return await UserApi(self.client, request, user_id).api() return await UserApi(self, request, user_id).api()
class UserApi(Api): class UserApi:
class UnknownMember(Api.MisusedApi): class UnknownMember(Api.MisusedApi):
pass pass
def __init__(self, client: discord.Client, request: dict, user_id: int) -> None: def __init__(self, api: Api, request: dict, user_id: int) -> None:
super().__init__(client) self.pi = api
self.client = api.client
self.request = request self.request = request
self.user_id = user_id self.user_id = user_id
async def _guild_api(self, guild_id: int) -> 'GuildApi':
guild = self.client.get_guild(guild_id) or await self.client.fetch_guild(guild_id)
if guild is None:
raise UserApi.UnknownMember('unknown guild')
member = guild.get_member(self.user_id) or await guild.fetch_member(self.user_id)
if member is None:
raise UserApi.UnknownMember('unknown member of a guild')
return GuildApi(self.client, self.request, member)
def sub(self, request: dict) -> 'UserApi':
return UserApi(self.client, request, self.user_id)
async def subs(self, requests: list[dict] | dict[str, dict]) -> ResponseType: async def subs(self, requests: list[dict] | dict[str, dict]) -> ResponseType:
match requests: match requests:
case list(): case list():
@ -68,11 +61,31 @@ class UserApi(Api):
case _: case _:
raise Api.MisusedApi('that should not happen') raise Api.MisusedApi('that should not happen')
def sub(self, request: dict) -> 'UserApi':
return UserApi(self.pi, request, self.user_id)
async def _guild_api(self, guild_id: int) -> 'GuildApi':
guild = self.client.get_guild(guild_id) or await self.client.fetch_guild(guild_id)
if guild is None:
raise UserApi.UnknownMember('unknown guild')
member = guild.get_member(self.user_id) or await guild.fetch_member(self.user_id)
if member is None:
raise UserApi.UnknownMember('unknown member of a guild')
return GuildApi(self.pi, self.request, member)
async def _operator_api(self) -> 'OperatorApi':
if not self.pi.is_operator(self.user_id):
raise UserApi.UnknownMember('not an operator')
return OperatorApi(self.pi, self.request, self.user_id)
async def _api(self) -> ResponseType: async def _api(self) -> ResponseType:
match self.request: match self.request:
case {'guild': str() as guild_id_str} if guild_id_str.isdecimal() and len(guild_id_str) < 100: case {'guild': str() as guild_id_str} if guild_id_str.isdecimal() and len(guild_id_str) < 100:
self.request.pop('guild') self.request.pop('guild')
return await (await self._guild_api(int(guild_id_str))).api() return await (await self._guild_api(int(guild_id_str))).api()
case {'operator': _}:
self.request.pop('operator')
return await (await self._operator_api()).api()
case {'type': 'ping', 't': (float() | int()) as t}: case {'type': 'ping', 't': (float() | int()) as t}:
return time.time() - t return time.time() - t
case {'type': 'guilds'}: case {'type': 'guilds'}:
@ -107,8 +120,8 @@ class GuildApi(UserApi):
class VoiceNotConnected(Api.MisusedApi): class VoiceNotConnected(Api.MisusedApi):
pass pass
def __init__(self, client: discord.Client, request: dict, member: discord.Member) -> None: def __init__(self, api: Api, request: dict, member: discord.Member) -> None:
super().__init__(client, request, member.id) super().__init__(api, request, member.id)
self.member = member self.member = member
self.guild = member.guild self.guild = member.guild
@ -123,10 +136,10 @@ class GuildApi(UserApi):
raise GuildApi.VoiceNotConnected('bot client user not initialised') raise GuildApi.VoiceNotConnected('bot client user not initialised')
if self.client.user.id not in channel.voice_states: if self.client.user.id not in channel.voice_states:
raise GuildApi.VoiceNotConnected('bot not connected') raise GuildApi.VoiceNotConnected('bot not connected')
return VoiceApi(self.client, self.request, self.member, channel) return VoiceApi(self.pi, self.request, self.member, channel)
def sub(self, request: dict) -> 'GuildApi': def sub(self, request: dict) -> 'GuildApi':
return GuildApi(self.client, request, self.member) return GuildApi(self.pi, request, self.member)
async def _api(self) -> ResponseType: async def _api(self) -> ResponseType:
match self.request: match self.request:
@ -143,18 +156,18 @@ class GuildApi(UserApi):
class VoiceApi(GuildApi): class VoiceApi(GuildApi):
def __init__( def __init__(
self, client: discord.Client, request: dict, member: discord.Member, channel: discord.VoiceChannel | discord.StageChannel self, api: Api, request: dict, member: discord.Member, channel: discord.VoiceChannel | discord.StageChannel
) -> None: ) -> None:
super().__init__(client, request, member) super().__init__(api, request, member)
self.channel = channel self.channel = channel
async def _main_api(self) -> 'MainApi': async def _main_api(self) -> 'MainApi':
vc = await raw_vc_for_member(self.member) vc = await raw_vc_for_member(self.member)
main = await main_for_raw_vc(vc, create=False, force_play=False) main = await main_for_raw_vc(vc, create=False, force_play=False)
return MainApi(self.client, self.request, self.member, self.channel, vc, main) return MainApi(self.pi, self.request, self.member, self.channel, vc, main)
def sub(self, request: dict) -> 'VoiceApi': def sub(self, request: dict) -> 'VoiceApi':
return VoiceApi(self.client, request, self.member, self.channel) return VoiceApi(self.pi, request, self.member, self.channel)
async def _api(self) -> ResponseType: async def _api(self) -> ResponseType:
match self.request: match self.request:
@ -171,15 +184,15 @@ class VoiceApi(GuildApi):
class MainApi(VoiceApi): class MainApi(VoiceApi):
def __init__( def __init__(
self, client: discord.Client, request: dict, member: discord.Member, channel: discord.VoiceChannel | discord.StageChannel, self, api: Api, request: dict, member: discord.Member, channel: discord.VoiceChannel | discord.StageChannel,
vc: discord.VoiceClient, main: MainAudio vc: discord.VoiceClient, main: MainAudio
) -> None: ) -> None:
super().__init__(client, request, member, channel) super().__init__(api, request, member, channel)
self.vc = vc self.vc = vc
self.main = main self.main = main
def sub(self, request: dict) -> 'MainApi': def sub(self, request: dict) -> 'MainApi':
return MainApi(self.client, request, self.member, self.channel, self.vc, self.main) return MainApi(self.pi, request, self.member, self.channel, self.vc, self.main)
async def _api(self) -> ResponseType: async def _api(self) -> ResponseType:
match self.request: match self.request:
@ -197,3 +210,32 @@ class MainApi(VoiceApi):
return await self.subs(requests) return await self.subs(requests)
case _: case _:
raise Api.UnknownApi('unknown main api') raise Api.UnknownApi('unknown main api')
class OperatorApi(UserApi):
def sub(self, request: dict) -> 'OperatorApi':
return OperatorApi(self.pi, request, self.user_id)
async def _guild_visible(self, guild: discord.Guild) -> bool:
return True
async def _api(self) -> ResponseType:
match self.request:
case {'type': 'guilds'}:
guilds = []
for guild in self.client.guilds:
if self._guild_visible(guild):
guilds.append(
{
'id': str(guild.id),
'member_count': guild.member_count,
'name': guild.name,
}
)
return guilds
case {'type': '?'}:
return 'this is operator api'
case {'type': '*', 'requests': list() | dict() as requests}:
return await self.subs(requests)
case _:
raise Api.UnknownApi('unknown operator api')

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import functools import functools
import os
import urllib.parse import urllib.parse
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Coroutine, Generic, Hashable, TypeVar from typing import Any, Callable, Coroutine, Generic, Hashable, TypeVar
@ -281,7 +282,11 @@ class MusicAppFactory(AppFactory):
@classmethod @classmethod
async def start(cls, client: discord.Client): async def start(cls, client: discord.Client):
try: try:
factory = cls(await request_token('music-client', 'token'), client, Api(client)) factory = cls(
await request_token('music-client', 'token'),
client,
Api(client, {key: value for key, value in os.environ.items() if key.startswith('roles')})
)
except aiohttp.ClientConnectorError: except aiohttp.ClientConnectorError:
print('no web app (likely due to no token)') print('no web app (likely due to no token)')
else: else:

View File

@ -196,3 +196,8 @@ const pageHome = async () => {
baseEl("div", await aQueueWidget()) baseEl("div", await aQueueWidget())
); );
}; };
aApi({
type: "guilds",
operator: null,
catches: { "not an operator": null, "*": null },
}).then(console.log);

View File

@ -102,8 +102,20 @@ async def on_ready():
await restore_vcs() await restore_vcs()
banned_guilds = set(map(int, map(str.strip, os.getenv('banned_guilds', '').split(':'))))
def guild_allowed(guild: discord.Guild | None) -> bool:
return guild is not None and guild.id not in banned_guilds
def message_allowed(message: discord.Message) -> bool:
return guild_allowed(message.guild)
@client.event @client.event
async def on_message(message: discord.Message) -> None: async def on_message(message: discord.Message) -> None:
if message_allowed(message):
await handle_content(message, message.content, prefix, client) await handle_content(message, message.content, prefix, client)

View File

@ -7,7 +7,6 @@ from v6d3music.utils.extract import extract
async def aextract(params: dict, url: str, **kwargs): async def aextract(params: dict, url: str, **kwargs):
with Benchmark('AEX'):
with ProcessPoolExecutor() as pool: with ProcessPoolExecutor() as pool:
return await asyncio.get_running_loop().run_in_executor( return await asyncio.get_running_loop().run_in_executor(
pool, pool,