somewhat new app

This commit is contained in:
AF 2022-12-09 02:28:12 +00:00
parent 03d2c18b29
commit 0fb4af62ec
5 changed files with 116 additions and 55 deletions

View File

@ -1,23 +1,61 @@
import asyncio import asyncio
import functools
import urllib.parse import urllib.parse
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Any, Callable, Coroutine, Generic, Hashable, TypeVar
import aiohttp import aiohttp
import discord import discord
from aiohttp import web from aiohttp import web
from v6d3music.api import Api
from v6d3music.config import auth_redirect, myroot
from v6d3music.utils.bytes_hash import bytes_hash
from ptvp35 import Db, KVJson from ptvp35 import Db, KVJson
from v6d0auth.appfactory import AppFactory from v6d0auth.appfactory import AppFactory
from v6d0auth.run_app import start_app from v6d0auth.run_app import start_app
from v6d1tokens.client import request_token from v6d1tokens.client import request_token
from v6d3music.config import auth_redirect, myroot
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())
T = TypeVar('T')
TKey = TypeVar('TKey', bound=Hashable)
class CachedEntry(Generic[T]):
def __init__(self, value: T, getter: Callable[[], Coroutine[Any, Any, T]]) -> None:
self.__value: T = value
self.__getter = getter
self.__task: asyncio.Future[T] = asyncio.Future()
self.__task.set_result(value)
async def _set(self) -> T:
self.__value = await self.__getter()
return self.__value
def get_nowait(self) -> T:
if self.__task.done():
self.__task = asyncio.create_task(self._set())
return self.__value
async def get(self) -> T:
if self.__task.done():
self.__task = asyncio.create_task(self._set())
return await self.__task
class CachedDictionary(Generic[TKey, T]):
def __init__(self, factory: Callable[[TKey], Coroutine[Any, Any, T]]) -> None:
self.__factory = factory
self.__entries: dict[TKey, CachedEntry[T]] = {}
def entry(self, key: TKey, default: T) -> CachedEntry[T]:
if key not in self.__entries:
self.__entries[key] = CachedEntry(default, functools.partial(self.__factory, key))
return self.__entries[key]
class MusicAppFactory(AppFactory): class MusicAppFactory(AppFactory):
htmlroot = Path(__file__).parent / 'html' htmlroot = Path(__file__).parent / 'html'
@ -32,6 +70,9 @@ class MusicAppFactory(AppFactory):
self.loop = asyncio.get_running_loop() self.loop = asyncio.get_running_loop()
self.client = client self.client = client
self._api = api self._api = api
self._token_clients: CachedDictionary[str, dict | None] = CachedDictionary(
self._token_client
)
def auth_link(self) -> str: def auth_link(self) -> str:
if self.client.user is None: if self.client.user is None:
@ -40,28 +81,13 @@ class MusicAppFactory(AppFactory):
return f'https://discord.com/api/oauth2/authorize?client_id={self.client.user.id}' \ return f'https://discord.com/api/oauth2/authorize?client_id={self.client.user.id}' \
f'&redirect_uri={urllib.parse.quote(self.redirect)}&response_type=code&scope=identify' f'&redirect_uri={urllib.parse.quote(self.redirect)}&response_type=code&scope=identify'
def _path(self, file: str):
return self.htmlroot / file
def _file(self, file: str): def _file(self, file: str):
with open(self.htmlroot / file) as f: with open(self.htmlroot / file) as f:
return f.read() return f.read()
async def file(self, file: str):
return await self.loop.run_in_executor(
None,
self._file,
file
)
async def html_resp(self, file: str):
text = await self.file(f'{file}.html')
text = text.replace(
'$$DISCORD_AUTH$$',
self.auth_link()
)
return web.Response(
text=text,
content_type='text/html'
)
async def code_token(self, code: str) -> dict: async def code_token(self, code: str) -> dict:
assert self.client.user is not None assert self.client.user is not None
data = { data = {
@ -78,13 +104,7 @@ class MusicAppFactory(AppFactory):
async with session.post('https://discord.com/api/oauth2/token', data=data, headers=headers) as response: async with session.post('https://discord.com/api/oauth2/token', data=data, headers=headers) as response:
return await response.json() return await response.json()
@classmethod async def _token_client(self, access_token: str) -> dict | None:
async def session_client(cls, data: dict) -> Optional[dict]:
match data:
case {'token': {'access_token': str() as access_token}}:
pass
case _:
return None
headers = { headers = {
'Authorization': f'Bearer {access_token}' 'Authorization': f'Bearer {access_token}'
} }
@ -92,6 +112,17 @@ class MusicAppFactory(AppFactory):
async with session.get('https://discord.com/api/oauth2/@me', headers=headers) as response: async with session.get('https://discord.com/api/oauth2/@me', headers=headers) as response:
return await response.json() return await response.json()
async def token_client(self, access_token: str) -> dict | None:
return self._token_clients.entry(access_token, None).get_nowait()
async def session_client(self, data: dict) -> dict | None:
match data:
case {'token': {'access_token': str() as access_token}}:
pass
case _:
return None
return await self.token_client(access_token)
@classmethod @classmethod
def client_status(cls, sclient: dict) -> dict: def client_status(cls, sclient: dict) -> dict:
user = cls.client_user(sclient) user = cls.client_user(sclient)
@ -109,7 +140,7 @@ class MusicAppFactory(AppFactory):
} }
@classmethod @classmethod
def user_username_full(cls, user: dict) -> Optional[str]: def user_username_full(cls, user: dict) -> str | None:
match user: match user:
case {'username': str() as username, 'discriminator': str() as discriminator}: case {'username': str() as username, 'discriminator': str() as discriminator}:
return f'{username}#{discriminator}' return f'{username}#{discriminator}'
@ -117,19 +148,19 @@ class MusicAppFactory(AppFactory):
return None return None
@classmethod @classmethod
def client_user(cls, sclient: dict) -> Optional[dict]: def client_user(cls, sclient: dict) -> dict | None:
return sclient.get('user') return sclient.get('user')
@classmethod @classmethod
def user_id(cls, user: dict) -> Optional[str | int]: def user_id(cls, user: dict) -> str | int | None:
return user.get('id') return user.get('id')
@classmethod @classmethod
def user_avatar(cls, user: dict) -> Optional[str]: def user_avatar(cls, user: dict) -> str | None:
return user.get('avatar') return user.get('avatar')
@classmethod @classmethod
def user_avatar_url(cls, user: dict) -> Optional[str]: def user_avatar_url(cls, user: dict) -> str | None:
cid = cls.user_id(user) cid = cls.user_id(user)
if cid is None: if cid is None:
return None return None
@ -169,15 +200,19 @@ class MusicAppFactory(AppFactory):
def define_routes(self, routes: web.RouteTableDef) -> None: def define_routes(self, routes: web.RouteTableDef) -> None:
@routes.get('/') @routes.get('/')
async def home(_request: web.Request) -> web.Response: async def home(_request: web.Request) -> web.StreamResponse:
return await self.html_resp('home') return web.FileResponse(self._path('home.html'))
@routes.get('/login/') @routes.get('/login/')
async def login(_request: web.Request) -> web.Response: async def login(_request: web.Request) -> web.StreamResponse:
return await self.html_resp('login') return web.FileResponse(self._path('login.html'))
@routes.get('/authlink/')
async def authlink(_request: web.Request) -> web.StreamResponse:
return web.Response(text=self.auth_link())
@routes.get('/auth/') @routes.get('/auth/')
async def auth(request: web.Request) -> web.Response: async def auth(request: web.Request) -> web.StreamResponse:
if 'session' in request.query: if 'session' in request.query:
response = web.HTTPFound('/') response = web.HTTPFound('/')
session = str(request.query.get('session')) session = str(request.query.get('session'))
@ -191,7 +226,7 @@ class MusicAppFactory(AppFactory):
await session_db.set(session, data) await session_db.set(session, data)
return response return response
else: else:
return await self.html_resp('auth') return web.FileResponse(self._path('auth.html'))
@routes.get('/state/') @routes.get('/state/')
async def get_state(request: web.Request) -> web.Response: async def get_state(request: web.Request) -> web.Response:
@ -215,17 +250,12 @@ class MusicAppFactory(AppFactory):
) )
@routes.get('/main.js') @routes.get('/main.js')
async def mainjs(_request: web.Request) -> web.Response: async def mainjs(_request: web.Request) -> web.StreamResponse:
return web.Response( return web.FileResponse(self._path('main.js'))
text=await self.file('main.js')
)
@routes.get('/main.css') @routes.get('/main.css')
async def maincss(_request: web.Request) -> web.Response: async def maincss(_request: web.Request) -> web.StreamResponse:
return web.Response( return web.FileResponse(self._path('main.css'))
text=await self.file('main.css'),
content_type='text/css'
)
@routes.post('/api/') @routes.post('/api/')
async def api(request: web.Request) -> web.Response: async def api(request: web.Request) -> web.Response:

View File

@ -3,7 +3,11 @@
<link rel="stylesheet" href="/main.css" /> <link rel="stylesheet" href="/main.css" />
</head> </head>
<body> <body>
<div id="root"></div> <div id="root-container">
<div class="sidebars"></div>
<div id="root"></div>
<div class="sidebars"></div>
</div>
<script src="/main.js"></script> <script src="/main.js"></script>
<script> <script>
(async () => { (async () => {

View File

@ -2,7 +2,6 @@
<div id="root"></div> <div id="root"></div>
<script src="/main.js"></script> <script src="/main.js"></script>
<script> <script>
authbase = '$$DISCORD_AUTH$$';
(async () => { (async () => {
const a = await aAuth(); const a = await aAuth();
root.append(a); root.append(a);

View File

@ -21,3 +21,24 @@ input {
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: #555; background: #555;
} }
html,
body,
#root-container {
height: 100%;
}
#root-container {
display: flex;
}
#root {
width: auto;
min-width: min(40em, 100%);
flex: auto;
}
.sidebars {
width: 100%;
background: #111;
}

View File

@ -73,10 +73,17 @@ const aLogin = () => {
a.innerText = "login"; a.innerText = "login";
return a; return a;
}; };
let authbase; const aAuthLink = async () => {
const response = await fetch("/authlink/");
return await response.text();
};
const aAuth = async () => { const aAuth = async () => {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = authbase + "&state=" + (await sessionState()); const [authlink, sessionstate] = await Promise.all([
aAuthLink(),
sessionState(),
]);
a.href = authlink + "&state=" + sessionstate;
a.innerText = "auth"; a.innerText = "auth";
return a; return a;
}; };
@ -165,7 +172,7 @@ const aUpdateQueueSetup = async (el) => {
})(); })();
(async () => { (async () => {
while (true) { while (true) {
await sleep(.25); await sleep(0.25);
if (queue !== null) { if (queue !== null) {
for (const audio of queue.queuejson) { for (const audio of queue.queuejson) {
audio.tce.innerText = audio.ts(); audio.tce.innerText = audio.ts();