web api
This commit is contained in:
parent
38c86f22c0
commit
5d54f08c2b
199
v6d3music/api.py
Normal file
199
v6d3music/api.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from v6d3music.core.mainasrc import main_for_raw_vc, raw_vc_for_member
|
||||||
|
from v6d3music.core.mainaudio import MainAudio
|
||||||
|
|
||||||
|
from v6d2ctx.context import Explicit
|
||||||
|
|
||||||
|
ResponseType: TypeAlias = list | dict | float | str | None
|
||||||
|
|
||||||
|
|
||||||
|
class Api:
|
||||||
|
class MisusedApi(KeyError):
|
||||||
|
def json(self) -> ResponseType:
|
||||||
|
return {'error': list(map(str, self.args)), 'errormessage': str(self)}
|
||||||
|
|
||||||
|
class UnknownApi(MisusedApi):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ExplicitFailure(MisusedApi):
|
||||||
|
def __init__(self, explicit: Explicit) -> None:
|
||||||
|
super().__init__(*explicit.args)
|
||||||
|
|
||||||
|
def __init__(self, client: discord.Client) -> None:
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
async def api(self, request: dict, user_id: int) -> ResponseType:
|
||||||
|
return await UserApi(self.client, request, user_id).api()
|
||||||
|
|
||||||
|
|
||||||
|
class UserApi(Api):
|
||||||
|
class UnknownMember(Api.MisusedApi):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, client: discord.Client, request: dict, user_id: int) -> None:
|
||||||
|
super().__init__(client)
|
||||||
|
self.request = request
|
||||||
|
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:
|
||||||
|
match requests:
|
||||||
|
case list():
|
||||||
|
return list(
|
||||||
|
await asyncio.gather(
|
||||||
|
*(self.sub(request).api() for request in requests)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case dict():
|
||||||
|
items = list(requests.items())
|
||||||
|
responses = await asyncio.gather(
|
||||||
|
*(self.sub(request if 'type' in request else request | {'type': key}).api() for key, request in items)
|
||||||
|
)
|
||||||
|
return dict((key, response) for (key, _), response in zip(items, responses))
|
||||||
|
case _:
|
||||||
|
raise Api.MisusedApi('that should not happen')
|
||||||
|
|
||||||
|
async def _api(self) -> ResponseType:
|
||||||
|
match self.request:
|
||||||
|
case {'guild': str() as guild_id_str} if guild_id_str.isdecimal() and len(guild_id_str) < 100:
|
||||||
|
self.request.pop('guild')
|
||||||
|
return await (await self._guild_api(int(guild_id_str))).api()
|
||||||
|
case {'type': 'ping', 't': (float() | int()) as t}:
|
||||||
|
return time.time() - t
|
||||||
|
case {'type': 'guilds'}:
|
||||||
|
guilds = []
|
||||||
|
for guild in self.client.guilds:
|
||||||
|
if guild.get_member(self.user_id) is not None:
|
||||||
|
guilds.append(str(guild.id))
|
||||||
|
return guilds
|
||||||
|
case {'type': '?'}:
|
||||||
|
return 'this is user api'
|
||||||
|
case {'type': '*', 'requests': list() | dict() as requests}:
|
||||||
|
return await self.subs(requests)
|
||||||
|
case _:
|
||||||
|
raise Api.UnknownApi('unknown user api')
|
||||||
|
|
||||||
|
async def api(self):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
return await self._api()
|
||||||
|
except Explicit as e:
|
||||||
|
raise Api.ExplicitFailure(e)
|
||||||
|
except Api.MisusedApi as e:
|
||||||
|
catches = self.request.get('catches', {})
|
||||||
|
if len(e.args) and (key := e.args[0]) in catches:
|
||||||
|
return catches[key]
|
||||||
|
if '*' in catches:
|
||||||
|
return e.json()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class GuildApi(UserApi):
|
||||||
|
class VoiceNotConnected(Api.MisusedApi):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, client: discord.Client, request: dict, member: discord.Member) -> None:
|
||||||
|
super().__init__(client, request, member.id)
|
||||||
|
self.member = member
|
||||||
|
self.guild = member.guild
|
||||||
|
|
||||||
|
async def voice_api(self) -> 'VoiceApi':
|
||||||
|
voice = self.member.voice
|
||||||
|
if voice is None:
|
||||||
|
raise GuildApi.VoiceNotConnected('you are not connected to voice')
|
||||||
|
channel = voice.channel
|
||||||
|
if channel is None:
|
||||||
|
raise GuildApi.VoiceNotConnected('you are not connected to a voice channel')
|
||||||
|
if self.client.user is None:
|
||||||
|
raise GuildApi.VoiceNotConnected('bot client user not initialised')
|
||||||
|
if self.client.user.id not in channel.voice_states:
|
||||||
|
raise GuildApi.VoiceNotConnected('bot not connected')
|
||||||
|
return VoiceApi(self.client, self.request, self.member, channel)
|
||||||
|
|
||||||
|
def sub(self, request: dict) -> 'GuildApi':
|
||||||
|
return GuildApi(self.client, request, self.member)
|
||||||
|
|
||||||
|
async def _api(self) -> ResponseType:
|
||||||
|
match self.request:
|
||||||
|
case {'voice': _}:
|
||||||
|
self.request.pop('voice')
|
||||||
|
return await (await self.voice_api()).api()
|
||||||
|
case {'type': '?'}:
|
||||||
|
return 'this is guild api'
|
||||||
|
case {'type': '*', 'requests': list() | dict() as requests}:
|
||||||
|
return await self.subs(requests)
|
||||||
|
case _:
|
||||||
|
raise Api.UnknownApi('unknown guild api')
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceApi(GuildApi):
|
||||||
|
def __init__(
|
||||||
|
self, client: discord.Client, request: dict, member: discord.Member, channel: discord.VoiceChannel | discord.StageChannel
|
||||||
|
) -> None:
|
||||||
|
super().__init__(client, request, member)
|
||||||
|
self.channel = channel
|
||||||
|
|
||||||
|
async def _main_api(self) -> 'MainApi':
|
||||||
|
vc = await raw_vc_for_member(self.member)
|
||||||
|
main = await main_for_raw_vc(vc, create=False, force_play=False)
|
||||||
|
return MainApi(self.client, self.request, self.member, self.channel, vc, main)
|
||||||
|
|
||||||
|
def sub(self, request: dict) -> 'VoiceApi':
|
||||||
|
return VoiceApi(self.client, request, self.member, self.channel)
|
||||||
|
|
||||||
|
async def _api(self) -> ResponseType:
|
||||||
|
match self.request:
|
||||||
|
case {'main': _}:
|
||||||
|
self.request.pop('main')
|
||||||
|
return await (await self._main_api()).api()
|
||||||
|
case {'type': '?'}:
|
||||||
|
return 'this is voice api'
|
||||||
|
case {'type': '*', 'requests': list() | dict() as requests}:
|
||||||
|
return await self.subs(requests)
|
||||||
|
case _:
|
||||||
|
raise Api.UnknownApi('unknown voice api')
|
||||||
|
|
||||||
|
|
||||||
|
class MainApi(VoiceApi):
|
||||||
|
def __init__(
|
||||||
|
self, client: discord.Client, request: dict, member: discord.Member, channel: discord.VoiceChannel | discord.StageChannel,
|
||||||
|
vc: discord.VoiceClient, main: MainAudio
|
||||||
|
) -> None:
|
||||||
|
super().__init__(client, request, member, channel)
|
||||||
|
self.vc = vc
|
||||||
|
self.main = main
|
||||||
|
|
||||||
|
def sub(self, request: dict) -> 'MainApi':
|
||||||
|
return MainApi(self.client, request, self.member, self.channel, self.vc, self.main)
|
||||||
|
|
||||||
|
async def _api(self) -> ResponseType:
|
||||||
|
match self.request:
|
||||||
|
case {'type': 'volume'}:
|
||||||
|
return self.main.volume
|
||||||
|
case {'type': 'playing'}:
|
||||||
|
return self.vc.is_playing()
|
||||||
|
case {'type': 'queueformat'}:
|
||||||
|
return await self.main.queue.format()
|
||||||
|
case {'type': 'queuejson'}:
|
||||||
|
return await self.main.queue.pubjson(self.member)
|
||||||
|
case {'type': '?'}:
|
||||||
|
return 'this is main api'
|
||||||
|
case {'type': '*', 'requests': list() | dict() as requests}:
|
||||||
|
return await self.subs(requests)
|
||||||
|
case _:
|
||||||
|
raise Api.UnknownApi('unknown main api')
|
@ -13,6 +13,7 @@ from v6d1tokens.client import request_token
|
|||||||
|
|
||||||
from v6d3music.config import auth_redirect, myroot
|
from v6d3music.config import auth_redirect, myroot
|
||||||
from v6d3music.utils.bytes_hash import bytes_hash
|
from v6d3music.utils.bytes_hash import bytes_hash
|
||||||
|
from v6d3music.api import Api
|
||||||
|
|
||||||
session_db = Db(myroot / 'session.db', kvfactory=KVJson())
|
session_db = Db(myroot / 'session.db', kvfactory=KVJson())
|
||||||
|
|
||||||
@ -23,14 +24,16 @@ class MusicAppFactory(AppFactory):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
secret: str,
|
secret: str,
|
||||||
client: discord.Client
|
client: discord.Client,
|
||||||
|
api: Api
|
||||||
):
|
):
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.redirect = auth_redirect
|
self.redirect = auth_redirect
|
||||||
self.loop = asyncio.get_running_loop()
|
self.loop = asyncio.get_running_loop()
|
||||||
self.client = client
|
self.client = client
|
||||||
|
self._api = api
|
||||||
|
|
||||||
def auth_link(self):
|
def auth_link(self) -> str:
|
||||||
if self.client.user is None:
|
if self.client.user is None:
|
||||||
return ''
|
return ''
|
||||||
else:
|
else:
|
||||||
@ -156,10 +159,12 @@ class MusicAppFactory(AppFactory):
|
|||||||
return cid
|
return cid
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def session_data(cls, session: str) -> dict:
|
def session_data(cls, session: str | None) -> dict:
|
||||||
|
if session is None:
|
||||||
|
return {}
|
||||||
data = session_db.get(session, {})
|
data = session_db.get(session, {})
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
data = {}
|
return {}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def define_routes(self, routes: web.RouteTableDef) -> None:
|
def define_routes(self, routes: web.RouteTableDef) -> None:
|
||||||
@ -174,14 +179,11 @@ class MusicAppFactory(AppFactory):
|
|||||||
@routes.get('/auth/')
|
@routes.get('/auth/')
|
||||||
async def auth(request: web.Request) -> web.Response:
|
async def auth(request: web.Request) -> web.Response:
|
||||||
if 'session' in request.query:
|
if 'session' in request.query:
|
||||||
print(request.query.get('code'))
|
|
||||||
response = web.HTTPFound('/')
|
response = web.HTTPFound('/')
|
||||||
session = str(request.query.get('session'))
|
session = str(request.query.get('session'))
|
||||||
s_state = str(request.query.get('state'))
|
s_state = str(request.query.get('state'))
|
||||||
code = str(request.query.get('code'))
|
code = str(request.query.get('code'))
|
||||||
if bytes_hash(session.encode()) != s_state:
|
if bytes_hash(session.encode()) != s_state:
|
||||||
print(session)
|
|
||||||
print(bytes_hash(session.encode()), s_state)
|
|
||||||
raise web.HTTPBadRequest
|
raise web.HTTPBadRequest
|
||||||
data = self.session_data(session)
|
data = self.session_data(session)
|
||||||
data['code'] = code
|
data['code'] = code
|
||||||
@ -224,10 +226,31 @@ class MusicAppFactory(AppFactory):
|
|||||||
text=await self.file('main.css')
|
text=await self.file('main.css')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@routes.post('/api/')
|
||||||
|
async def api(request: web.Request) -> web.Response:
|
||||||
|
session = request.query.get('session')
|
||||||
|
data = self.session_data(session)
|
||||||
|
sclient = await self.session_client(data)
|
||||||
|
if sclient is None:
|
||||||
|
raise web.HTTPUnauthorized
|
||||||
|
user = self.client_user(sclient)
|
||||||
|
if user is None:
|
||||||
|
raise web.HTTPUnauthorized
|
||||||
|
user_id = self.user_id(user)
|
||||||
|
if user_id is None:
|
||||||
|
raise web.HTTPUnauthorized
|
||||||
|
user_id = int(user_id)
|
||||||
|
jsr = await request.json()
|
||||||
|
assert isinstance(jsr, dict)
|
||||||
|
try:
|
||||||
|
return web.json_response(await self._api.api(jsr, user_id))
|
||||||
|
except Api.MisusedApi as e:
|
||||||
|
return web.json_response(e.json(), status=404)
|
||||||
|
|
||||||
@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)
|
factory = cls(await request_token('music-client', 'token'), client, Api(client))
|
||||||
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:
|
||||||
|
@ -7,13 +7,10 @@ from v6d3music.core.queueaudio import QueueAudio
|
|||||||
mainasrcs: dict[discord.Guild, MainAudio] = {}
|
mainasrcs: dict[discord.Guild, MainAudio] = {}
|
||||||
|
|
||||||
|
|
||||||
async def raw_vc_for(ctx: Context) -> discord.VoiceClient:
|
async def raw_vc_for_member(member: discord.Member) -> discord.VoiceClient:
|
||||||
if ctx.guild is None:
|
vc: discord.VoiceProtocol | None = member.guild.voice_client
|
||||||
raise Explicit('not in a guild')
|
|
||||||
assert ctx.member is not None
|
|
||||||
vc: discord.VoiceProtocol | None = ctx.guild.voice_client
|
|
||||||
if vc is None or isinstance(vc, discord.VoiceClient) and not vc.is_connected():
|
if vc is None or isinstance(vc, discord.VoiceClient) and not vc.is_connected():
|
||||||
vs: discord.VoiceState | None = ctx.member.voice
|
vs: discord.VoiceState | None = member.voice
|
||||||
if vs is None:
|
if vs is None:
|
||||||
raise Explicit('not connected')
|
raise Explicit('not connected')
|
||||||
vch: discord.VoiceChannel | None = vs.channel # type: ignore
|
vch: discord.VoiceChannel | None = vs.channel # type: ignore
|
||||||
@ -22,15 +19,21 @@ async def raw_vc_for(ctx: Context) -> discord.VoiceClient:
|
|||||||
try:
|
try:
|
||||||
vc = await vch.connect()
|
vc = await vch.connect()
|
||||||
except discord.ClientException:
|
except discord.ClientException:
|
||||||
vc = ctx.guild.voice_client
|
vc = member.guild.voice_client
|
||||||
assert vc is not None
|
assert vc is not None
|
||||||
await ctx.guild.fetch_channels()
|
await member.guild.fetch_channels()
|
||||||
await vc.disconnect(force=True)
|
await vc.disconnect(force=True)
|
||||||
raise Explicit('try again later')
|
raise Explicit('try again later')
|
||||||
assert isinstance(vc, discord.VoiceClient)
|
assert isinstance(vc, discord.VoiceClient)
|
||||||
return vc
|
return vc
|
||||||
|
|
||||||
|
|
||||||
|
async def raw_vc_for(ctx: Context) -> discord.VoiceClient:
|
||||||
|
if ctx.member is None:
|
||||||
|
raise Explicit('not in a guild')
|
||||||
|
return await raw_vc_for_member(ctx.member)
|
||||||
|
|
||||||
|
|
||||||
async def main_for_raw_vc(vc: discord.VoiceClient, *, create: bool, force_play: bool) -> MainAudio:
|
async def main_for_raw_vc(vc: discord.VoiceClient, *, create: bool, force_play: bool) -> MainAudio:
|
||||||
if vc.guild in mainasrcs:
|
if vc.guild in mainasrcs:
|
||||||
source = mainasrcs[vc.guild]
|
source = mainasrcs[vc.guild]
|
||||||
|
@ -135,3 +135,7 @@ class QueueAudio(discord.AudioSource):
|
|||||||
audio.cleanup()
|
audio.cleanup()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def pubjson(self, member: discord.Member) -> list:
|
||||||
|
audios = list(self.queue)
|
||||||
|
return [await audio.pubjson(member) for audio in audios]
|
||||||
|
@ -180,3 +180,12 @@ class YTAudio(discord.AudioSource):
|
|||||||
print(f'regenerated {self.origin}')
|
print(f'regenerated {self.origin}')
|
||||||
finally:
|
finally:
|
||||||
self.regenerating = False
|
self.regenerating = False
|
||||||
|
|
||||||
|
async def pubjson(self, member: discord.Member) -> dict:
|
||||||
|
return {
|
||||||
|
'seconds': self.source_seconds(),
|
||||||
|
'timecode': self.source_timecode(),
|
||||||
|
'duration': self.duration(),
|
||||||
|
'description': self.description,
|
||||||
|
'canbeskipped': self.can_be_skipped_by(member),
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<link rel="stylesheet" href="/main.css">
|
<link rel="stylesheet" href="/main.css" />
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script src="/main.js"></script>
|
<script src="/main.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -1,69 +1,66 @@
|
|||||||
const genRanHex = size => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
|
const genRanHex = (size) =>
|
||||||
|
[...Array(size)]
|
||||||
|
.map(() => Math.floor(Math.random() * 16).toString(16))
|
||||||
|
.join("");
|
||||||
const sessionStr = () => {
|
const sessionStr = () => {
|
||||||
if (!localStorage.getItem('session'))
|
if (!localStorage.getItem("session"))
|
||||||
localStorage.setItem('session', genRanHex(64));
|
localStorage.setItem("session", genRanHex(64));
|
||||||
return localStorage.getItem('session');
|
return localStorage.getItem("session");
|
||||||
};
|
};
|
||||||
const sessionState = async () => {
|
const sessionState = async () => {
|
||||||
const response = await fetch(
|
const response = await fetch(`/state/?session=${sessionStr()}`);
|
||||||
`/state/?session=${sessionStr()}`
|
|
||||||
);
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
};
|
};
|
||||||
const sessionStatus = (
|
const sessionStatus = (() => {
|
||||||
() => {
|
|
||||||
let task;
|
let task;
|
||||||
return (async () => {
|
return async () => {
|
||||||
if (task === undefined) {
|
if (task === undefined) {
|
||||||
task = (async () => {
|
task = (async () => {
|
||||||
const response = await fetch(
|
const response = await fetch(`/status/?session=${sessionStr()}`);
|
||||||
`/status/?session=${sessionStr()}`
|
|
||||||
);
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
return await task;
|
return await task;
|
||||||
})
|
};
|
||||||
}
|
})();
|
||||||
)();
|
const root = document.querySelector("#root");
|
||||||
const root = document.querySelector('#root');
|
|
||||||
const logEl = (msg) => {
|
const logEl = (msg) => {
|
||||||
const el = document.createElement('pre');
|
const el = document.createElement("pre");
|
||||||
el.innerText = msg;
|
el.innerText = msg;
|
||||||
root.append(el);
|
root.append(el);
|
||||||
};
|
};
|
||||||
const sessionClient = async () => {
|
const sessionClient = async () => {
|
||||||
const session = await sessionStatus();
|
const session = await sessionStatus();
|
||||||
return session && session['client'];
|
return session && session["client"];
|
||||||
};
|
};
|
||||||
const sessionUser = async () => {
|
const sessionUser = async () => {
|
||||||
const client = await sessionClient();
|
const client = await sessionClient();
|
||||||
return client && client['user'];
|
return client && client["user"];
|
||||||
};
|
};
|
||||||
const userAvatarUrl = async () => {
|
const userAvatarUrl = async () => {
|
||||||
const user = await sessionUser();
|
const user = await sessionUser();
|
||||||
return user && user['avatar'];
|
return user && user["avatar"];
|
||||||
};
|
};
|
||||||
const userUsername = async () => {
|
const userUsername = async () => {
|
||||||
const user = await sessionUser();
|
const user = await sessionUser();
|
||||||
return user && user['username'];
|
return user && user["username"];
|
||||||
};
|
};
|
||||||
const userAvatarImg = async () => {
|
const userAvatarImg = async () => {
|
||||||
const avatar = await userAvatarUrl();
|
const avatar = await userAvatarUrl();
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement("img");
|
||||||
img.src = avatar;
|
img.src = avatar;
|
||||||
img.width = 64;
|
img.width = 64;
|
||||||
img.height = 64;
|
img.height = 64;
|
||||||
img.alt = await userUsername();
|
img.alt = await userUsername();
|
||||||
return img;
|
return img;
|
||||||
} else {
|
} else {
|
||||||
return baseEl('span');
|
return baseEl("span");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const userId = async () => {
|
const userId = async () => {
|
||||||
const user = await sessionUser();
|
const user = await sessionUser();
|
||||||
return user && user['id'];
|
return user && user["id"];
|
||||||
};
|
};
|
||||||
const baseEl = (tag, ...appended) => {
|
const baseEl = (tag, ...appended) => {
|
||||||
const element = document.createElement(tag);
|
const element = document.createElement(tag);
|
||||||
@ -71,24 +68,122 @@ const baseEl = (tag, ...appended) => {
|
|||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
const aLogin = () => {
|
const aLogin = () => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement("a");
|
||||||
a.href = '/login/';
|
a.href = "/login/";
|
||||||
a.innerText = 'login';
|
a.innerText = "login";
|
||||||
return a;
|
return a;
|
||||||
};
|
};
|
||||||
const pageHome = async () => {
|
const pageHome = async () => {
|
||||||
return baseEl(
|
return baseEl(
|
||||||
'div',
|
"div",
|
||||||
baseEl('div', aLogin()),
|
baseEl("div", aLogin()),
|
||||||
baseEl('div', await userAvatarImg()),
|
baseEl("div", await userAvatarImg()),
|
||||||
baseEl('div', await userId()),
|
baseEl("div", await userId()),
|
||||||
baseEl('div', await userUsername()),
|
baseEl("div", await userUsername()),
|
||||||
)
|
baseEl("div", await aQueueWidget())
|
||||||
|
);
|
||||||
};
|
};
|
||||||
let authbase;
|
let authbase;
|
||||||
const aAuth = async () => {
|
const aAuth = async () => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement("a");
|
||||||
a.href = authbase + '&state=' + await sessionState();
|
a.href = authbase + "&state=" + (await sessionState());
|
||||||
a.innerText = 'auth';
|
a.innerText = "auth";
|
||||||
return a;
|
return a;
|
||||||
};
|
};
|
||||||
|
const aApi = async (request) => {
|
||||||
|
const response = await fetch(`/api/?session=${sessionStr()}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
const aGuilds = async () => {
|
||||||
|
return await aApi({ type: "guilds" });
|
||||||
|
};
|
||||||
|
const aQueue = async () => {
|
||||||
|
const requests = {};
|
||||||
|
for (const guild of await aGuilds()) {
|
||||||
|
requests[guild] = {
|
||||||
|
type: "*",
|
||||||
|
guild,
|
||||||
|
voice: null,
|
||||||
|
main: null,
|
||||||
|
catches: { "you are not connected to voice": null, "*": null },
|
||||||
|
requests: { volume: {}, playing: {}, queueformat: {}, queuejson: {} },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const responses = await aApi({ type: "*", requests });
|
||||||
|
for (const [guild, response] of Object.entries(responses)) {
|
||||||
|
if (response !== null && response.error === undefined) {
|
||||||
|
response.guild = guild;
|
||||||
|
response.time = Date.now() / 1000;
|
||||||
|
response.delta = () => Date.now() / 1000 - response.time;
|
||||||
|
let index = 0;
|
||||||
|
for (const audio of response.queuejson) {
|
||||||
|
audio.playing = response.playing && index === 0;
|
||||||
|
audio.delta = () => (audio.playing ? response.delta() : 0);
|
||||||
|
audio.now = () => audio.seconds + audio.delta();
|
||||||
|
audio.ts = () => {
|
||||||
|
const seconds_total = Math.round(audio.now());
|
||||||
|
const seconds = seconds_total % 60;
|
||||||
|
const minutes_total = (seconds_total - seconds) / 60;
|
||||||
|
const minutes = minutes_total % 60;
|
||||||
|
const hours = (minutes_total - minutes) / 60;
|
||||||
|
return `${hours}:${("00" + minutes).slice(-2)}:${(
|
||||||
|
"00" + seconds
|
||||||
|
).slice(-2)}`;
|
||||||
|
};
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const sleep = (s) => {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, 1000 * s));
|
||||||
|
};
|
||||||
|
const aUpdateAudioSchedule = async (timecode, audio, i, current_i) => {
|
||||||
|
while (i == current_i()) {
|
||||||
|
timecode.innerText = audio.ts();
|
||||||
|
await sleep(0.5);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const audioWidget = (audio, i, current_i) => {
|
||||||
|
const description = baseEl("span", audio.description);
|
||||||
|
const timecode = baseEl("span", audio.timecode);
|
||||||
|
const duration = baseEl("span", audio.duration);
|
||||||
|
aUpdateAudioSchedule(timecode, audio, i, current_i);
|
||||||
|
return baseEl("div", "audio", " ", timecode, "/", duration, " ", description);
|
||||||
|
};
|
||||||
|
const aUpdateQueueOnce = async (queue, el, i, current_i) => {
|
||||||
|
console.log(queue);
|
||||||
|
console.log(JSON.stringify(queue));
|
||||||
|
el.innerHTML = "";
|
||||||
|
if (queue !== null) {
|
||||||
|
for (const audio of queue.queuejson) {
|
||||||
|
el.append(audioWidget(audio, i, current_i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const aUpdateQueueSetup = async (el) => {
|
||||||
|
let i = 0;
|
||||||
|
await aUpdateQueueOnce(await aQueue(), el, i, () => i);
|
||||||
|
(async () => {
|
||||||
|
while (true) {
|
||||||
|
const queue = await aQueue();
|
||||||
|
i += 1;
|
||||||
|
await aUpdateQueueOnce(queue, el, i, () => i);
|
||||||
|
await sleep(2);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
const aQueueWidget = async () => {
|
||||||
|
const el = baseEl("div");
|
||||||
|
if (await sessionUser()) await aUpdateQueueSetup(el);
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user