basic proxy implementation
This commit is contained in:
		
							parent
							
								
									28a5c527b6
								
							
						
					
					
						commit
						9e688b76b4
					
				
							
								
								
									
										1
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | .git* | ||||||
							
								
								
									
										212
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,212 @@ | |||||||
|  | # Byte-compiled / optimized / DLL files | ||||||
|  | __pycache__/ | ||||||
|  | *.py[cod] | ||||||
|  | *$py.class | ||||||
|  | 
 | ||||||
|  | # C extensions | ||||||
|  | *.so | ||||||
|  | 
 | ||||||
|  | # Distribution / packaging | ||||||
|  | .Python | ||||||
|  | build/ | ||||||
|  | develop-eggs/ | ||||||
|  | dist/ | ||||||
|  | downloads/ | ||||||
|  | eggs/ | ||||||
|  | .eggs/ | ||||||
|  | lib/ | ||||||
|  | lib64/ | ||||||
|  | parts/ | ||||||
|  | sdist/ | ||||||
|  | var/ | ||||||
|  | wheels/ | ||||||
|  | share/python-wheels/ | ||||||
|  | *.egg-info/ | ||||||
|  | .installed.cfg | ||||||
|  | *.egg | ||||||
|  | MANIFEST | ||||||
|  | 
 | ||||||
|  | # PyInstaller | ||||||
|  | #  Usually these files are written by a python script from a template | ||||||
|  | #  before PyInstaller builds the exe, so as to inject date/other infos into it. | ||||||
|  | *.manifest | ||||||
|  | *.spec | ||||||
|  | 
 | ||||||
|  | # Installer logs | ||||||
|  | pip-log.txt | ||||||
|  | pip-delete-this-directory.txt | ||||||
|  | 
 | ||||||
|  | # Unit test / coverage reports | ||||||
|  | htmlcov/ | ||||||
|  | .tox/ | ||||||
|  | .nox/ | ||||||
|  | .coverage | ||||||
|  | .coverage.* | ||||||
|  | .cache | ||||||
|  | nosetests.xml | ||||||
|  | coverage.xml | ||||||
|  | *.cover | ||||||
|  | *.py,cover | ||||||
|  | .hypothesis/ | ||||||
|  | .pytest_cache/ | ||||||
|  | cover/ | ||||||
|  | 
 | ||||||
|  | # Translations | ||||||
|  | *.mo | ||||||
|  | *.pot | ||||||
|  | 
 | ||||||
|  | # Django stuff: | ||||||
|  | *.log | ||||||
|  | local_settings.py | ||||||
|  | db.sqlite3 | ||||||
|  | db.sqlite3-journal | ||||||
|  | 
 | ||||||
|  | # Flask stuff: | ||||||
|  | instance/ | ||||||
|  | .webassets-cache | ||||||
|  | 
 | ||||||
|  | # Scrapy stuff: | ||||||
|  | .scrapy | ||||||
|  | 
 | ||||||
|  | # Sphinx documentation | ||||||
|  | docs/_build/ | ||||||
|  | 
 | ||||||
|  | # PyBuilder | ||||||
|  | .pybuilder/ | ||||||
|  | target/ | ||||||
|  | 
 | ||||||
|  | # Jupyter Notebook | ||||||
|  | .ipynb_checkpoints | ||||||
|  | 
 | ||||||
|  | # IPython | ||||||
|  | profile_default/ | ||||||
|  | ipython_config.py | ||||||
|  | 
 | ||||||
|  | # pyenv | ||||||
|  | #   For a library or package, you might want to ignore these files since the code is | ||||||
|  | #   intended to run in multiple environments; otherwise, check them in: | ||||||
|  | # .python-version | ||||||
|  | 
 | ||||||
|  | # pipenv | ||||||
|  | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | ||||||
|  | #   However, in case of collaboration, if having platform-specific dependencies or dependencies | ||||||
|  | #   having no cross-platform support, pipenv may install dependencies that don't work, or not | ||||||
|  | #   install all needed dependencies. | ||||||
|  | #Pipfile.lock | ||||||
|  | 
 | ||||||
|  | # PEP 582; used by e.g. github.com/David-OConnor/pyflow | ||||||
|  | __pypackages__/ | ||||||
|  | 
 | ||||||
|  | # Celery stuff | ||||||
|  | celerybeat-schedule | ||||||
|  | celerybeat.pid | ||||||
|  | 
 | ||||||
|  | # SageMath parsed files | ||||||
|  | *.sage.py | ||||||
|  | 
 | ||||||
|  | # Environments | ||||||
|  | .env | ||||||
|  | .venv | ||||||
|  | env/ | ||||||
|  | venv/ | ||||||
|  | ENV/ | ||||||
|  | env.bak/ | ||||||
|  | venv.bak/ | ||||||
|  | 
 | ||||||
|  | # Spyder project settings | ||||||
|  | .spyderproject | ||||||
|  | .spyproject | ||||||
|  | 
 | ||||||
|  | # Rope project settings | ||||||
|  | .ropeproject | ||||||
|  | 
 | ||||||
|  | # mkdocs documentation | ||||||
|  | /site | ||||||
|  | 
 | ||||||
|  | # mypy | ||||||
|  | .mypy_cache/ | ||||||
|  | .dmypy.json | ||||||
|  | dmypy.json | ||||||
|  | 
 | ||||||
|  | # Pyre type checker | ||||||
|  | .pyre/ | ||||||
|  | 
 | ||||||
|  | # pytype static type analyzer | ||||||
|  | .pytype/ | ||||||
|  | 
 | ||||||
|  | # Cython debug symbols | ||||||
|  | cython_debug/ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider | ||||||
|  | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 | ||||||
|  | 
 | ||||||
|  | # User-specific stuff | ||||||
|  | .idea/**/workspace.xml | ||||||
|  | .idea/**/tasks.xml | ||||||
|  | .idea/**/usage.statistics.xml | ||||||
|  | .idea/**/dictionaries | ||||||
|  | .idea/**/shelf | ||||||
|  | 
 | ||||||
|  | # Generated files | ||||||
|  | .idea/**/contentModel.xml | ||||||
|  | 
 | ||||||
|  | # Sensitive or high-churn files | ||||||
|  | .idea/**/dataSources/ | ||||||
|  | .idea/**/dataSources.ids | ||||||
|  | .idea/**/dataSources.local.xml | ||||||
|  | .idea/**/sqlDataSources.xml | ||||||
|  | .idea/**/dynamic.xml | ||||||
|  | .idea/**/uiDesigner.xml | ||||||
|  | .idea/**/dbnavigator.xml | ||||||
|  | 
 | ||||||
|  | # Gradle | ||||||
|  | .idea/**/gradle.xml | ||||||
|  | .idea/**/libraries | ||||||
|  | 
 | ||||||
|  | # Gradle and Maven with auto-import | ||||||
|  | # When using Gradle or Maven with auto-import, you should exclude module files, | ||||||
|  | # since they will be recreated, and may cause churn.  Uncomment if using | ||||||
|  | # auto-import. | ||||||
|  | # .idea/artifacts | ||||||
|  | # .idea/compiler.xml | ||||||
|  | # .idea/jarRepositories.xml | ||||||
|  | # .idea/modules.xml | ||||||
|  | # .idea/*.iml | ||||||
|  | # .idea/modules | ||||||
|  | # *.iml | ||||||
|  | # *.ipr | ||||||
|  | 
 | ||||||
|  | # CMake | ||||||
|  | cmake-build-*/ | ||||||
|  | 
 | ||||||
|  | # Mongo Explorer plugin | ||||||
|  | .idea/**/mongoSettings.xml | ||||||
|  | 
 | ||||||
|  | # File-based project format | ||||||
|  | *.iws | ||||||
|  | 
 | ||||||
|  | # IntelliJ | ||||||
|  | out/ | ||||||
|  | 
 | ||||||
|  | # mpeltonen/sbt-idea plugin | ||||||
|  | .idea_modules/ | ||||||
|  | 
 | ||||||
|  | # JIRA plugin | ||||||
|  | atlassian-ide-plugin.xml | ||||||
|  | 
 | ||||||
|  | # Cursive Clojure plugin | ||||||
|  | .idea/replstate.xml | ||||||
|  | 
 | ||||||
|  | # Crashlytics plugin (for Android Studio and IntelliJ) | ||||||
|  | com_crashlytics_export_strings.xml | ||||||
|  | crashlytics.properties | ||||||
|  | crashlytics-build.properties | ||||||
|  | fabric.properties | ||||||
|  | 
 | ||||||
|  | # Editor-based Rest Client | ||||||
|  | .idea/httpRequests | ||||||
|  | 
 | ||||||
|  | # Android studio 3.1+ serialized cache file | ||||||
|  | .idea/caches/build_file_checksums.ser | ||||||
							
								
								
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | FROM python:3.11 | ||||||
|  | 
 | ||||||
|  | WORKDIR /code/ | ||||||
|  | 
 | ||||||
|  | COPY requirements.txt requirements.txt | ||||||
|  | 
 | ||||||
|  | RUN pip install --no-cache-dir --upgrade -r requirements.txt | ||||||
|  | 
 | ||||||
|  | COPY app app | ||||||
|  | 
 | ||||||
|  | RUN python3 -m app.main | ||||||
|  | 
 | ||||||
|  | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] | ||||||
							
								
								
									
										126
									
								
								app/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								app/main.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | |||||||
|  | import asyncio | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import Any, Callable, Coroutine, TypeVar | ||||||
|  | 
 | ||||||
|  | import aiohttp | ||||||
|  | from fastapi import FastAPI, HTTPException | ||||||
|  | from fastapi.responses import ( | ||||||
|  |     FileResponse, | ||||||
|  |     JSONResponse, | ||||||
|  |     PlainTextResponse, | ||||||
|  |     RedirectResponse, | ||||||
|  |     Response, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | app = FastAPI() | ||||||
|  | 
 | ||||||
|  | root = Path(__file__).parent / "static" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/") | ||||||
|  | async def home(): | ||||||
|  |     return FileResponse(root / "home.html") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/operator/") | ||||||
|  | async def operatorhome(): | ||||||
|  |     return FileResponse(root / "operator.html") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/login/") | ||||||
|  | async def login(): | ||||||
|  |     return FileResponse(root / "login.html") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | base = "http://v6d3music/" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/authlink/") | ||||||
|  | async def authlink(): | ||||||
|  |     async with aiohttp.ClientSession() as s, s.get(f"{base}authlink/") as response: | ||||||
|  |         return PlainTextResponse(await response.text()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | T = TypeVar("T") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def url_get( | ||||||
|  |     url: str, params: dict[str, str], handle: Callable[[aiohttp.ClientResponse], Coroutine[Any, Any, T]] | ||||||
|  | ) -> T: | ||||||
|  |     async with aiohttp.ClientSession() as s, s.get(url, params=params) as response: | ||||||
|  |         return await handle(response) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def url_post( | ||||||
|  |     url: str, params: dict[str, str], json: dict, handle: Callable[[aiohttp.ClientResponse], Coroutine[Any, Any, T]] | ||||||
|  | ) -> T: | ||||||
|  |     async with aiohttp.ClientSession() as s, s.post(url, params=params, json=json) as response: | ||||||
|  |         print(await response.read()) | ||||||
|  |         return await handle(response) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def repeat(repeated: Callable[[], Coroutine[Any, Any, T]]) -> T: | ||||||
|  |     for _ in range(60): | ||||||
|  |         try: | ||||||
|  |             return await repeated() | ||||||
|  |         except aiohttp.ClientConnectorError: | ||||||
|  |             await asyncio.sleep(1) | ||||||
|  |     raise HTTPException(504) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def handle_auth(response: aiohttp.ClientResponse): | ||||||
|  |     if 300 <= response.status <= 399: | ||||||
|  |         return RedirectResponse("/") | ||||||
|  |     else: | ||||||
|  |         return Response(content=await response.read(), media_type=response.content_type, status_code=response.status) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/auth/") | ||||||
|  | async def auth(session: str | None = None, state: str | None = None, code: str | None = None): | ||||||
|  |     match session, state, code: | ||||||
|  |         case str() as session, str() as state, str() as code: | ||||||
|  |             params: dict[str, str] = {"session": session, "state": state, "code": code} | ||||||
|  |             return await repeat(lambda: url_get(f"{base}auth/", params, handle_auth)) | ||||||
|  |         case None, None, None: | ||||||
|  |             return FileResponse(root / "auth.html") | ||||||
|  |         case _: | ||||||
|  |             raise HTTPException(400) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async def handle_json(response: aiohttp.ClientResponse): | ||||||
|  |     return JSONResponse(await response.json()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/state/") | ||||||
|  | async def state(session: str): | ||||||
|  |     return await repeat(lambda: url_get(f"{base}state/", {"session": session}, handle_json)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/status/") | ||||||
|  | async def status(session: str): | ||||||
|  |     return await repeat(lambda: url_get(f"{base}status/", {"session": session}, handle_json)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/main.js") | ||||||
|  | async def mainjs(): | ||||||
|  |     return FileResponse(root / "main.js") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/operator.js") | ||||||
|  | async def operatorjs(): | ||||||
|  |     return FileResponse(root / "operator.js") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/main.css") | ||||||
|  | async def maincss(): | ||||||
|  |     return FileResponse(root / "main.css") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.get("/operator.css") | ||||||
|  | async def operatorcss(): | ||||||
|  |     return FileResponse(root / "operator.css") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @app.post("/api/") | ||||||
|  | async def api(json: dict, session: str): | ||||||
|  |     return await repeat(lambda: url_post(f"{base}api/", {"session": session}, json, handle_json)) | ||||||
							
								
								
									
										8
									
								
								app/static/auth.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/static/auth.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | <link rel="stylesheet" href="/main.css"> | ||||||
|  | <div id="root"></div> | ||||||
|  | <script src="/main.js"></script> | ||||||
|  | <script> | ||||||
|  |     (async () => { | ||||||
|  |         window.location = window.location + `&session=${sessionStr()}`; | ||||||
|  |     })(); | ||||||
|  | </script> | ||||||
							
								
								
									
										17
									
								
								app/static/home.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/static/home.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <head> | ||||||
|  |   <link rel="stylesheet" href="/main.css" /> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |   <div id="root-container"> | ||||||
|  |     <div class="sidebars"></div> | ||||||
|  |     <div id="root"></div> | ||||||
|  |     <div class="sidebars"></div> | ||||||
|  |   </div> | ||||||
|  |   <script src="/main.js"></script> | ||||||
|  |   <script> | ||||||
|  |     (async () => { | ||||||
|  |       root.append(await pageHome()); | ||||||
|  |     })(); | ||||||
|  |   </script> | ||||||
|  | </body> | ||||||
							
								
								
									
										12
									
								
								app/static/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/static/login.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | <link rel="stylesheet" href="/main.css"> | ||||||
|  | <div id="root"></div> | ||||||
|  | <script src="/main.js"></script> | ||||||
|  | <script> | ||||||
|  |     (async () => { | ||||||
|  |         const a = await aAuth(); | ||||||
|  |         root.append(a); | ||||||
|  |         logEl(sessionStr()); | ||||||
|  |         logEl(await sessionState()); | ||||||
|  |         a.click(); | ||||||
|  |     })(); | ||||||
|  | </script> | ||||||
							
								
								
									
										50
									
								
								app/static/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/static/main.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | html, | ||||||
|  | body, | ||||||
|  | input { | ||||||
|  |   color: white; | ||||||
|  |   background: black; | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ::-webkit-scrollbar { | ||||||
|  |   width: 1em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ::-webkit-scrollbar-track { | ||||||
|  |   background: #111; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ::-webkit-scrollbar-thumb { | ||||||
|  |   background: #444; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ::-webkit-scrollbar-thumb:hover { | ||||||
|  |   background: #555; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | html, | ||||||
|  | body, | ||||||
|  | #root-container { | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #root-container { | ||||||
|  |   display: flex; | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #root { | ||||||
|  |   width: 0%; | ||||||
|  |   min-width: min(40em, 100%); | ||||||
|  |   flex: auto; | ||||||
|  |   overflow-y: scroll; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .sidebars { | ||||||
|  |   width: 100%; | ||||||
|  |   background: #050505; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #homeroot { | ||||||
|  |   padding: 1em; | ||||||
|  | } | ||||||
							
								
								
									
										204
									
								
								app/static/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								app/static/main.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,204 @@ | |||||||
|  | const genRanHex = (size) => | ||||||
|  |   [...Array(size)] | ||||||
|  |     .map(() => Math.floor(Math.random() * 16).toString(16)) | ||||||
|  |     .join(""); | ||||||
|  | const sessionStr = () => { | ||||||
|  |   if (!localStorage.getItem("session")) | ||||||
|  |     localStorage.setItem("session", genRanHex(64)); | ||||||
|  |   return localStorage.getItem("session"); | ||||||
|  | }; | ||||||
|  | const sessionState = async () => { | ||||||
|  |   const response = await fetch(`/state/?session=${sessionStr()}`); | ||||||
|  |   return await response.json(); | ||||||
|  | }; | ||||||
|  | const sessionStatus = (() => { | ||||||
|  |   let task; | ||||||
|  |   return async () => { | ||||||
|  |     if (task === undefined) { | ||||||
|  |       task = (async () => { | ||||||
|  |         const response = await fetch(`/status/?session=${sessionStr()}`); | ||||||
|  |         return await response.json(); | ||||||
|  |       })(); | ||||||
|  |     } | ||||||
|  |     return await task; | ||||||
|  |   }; | ||||||
|  | })(); | ||||||
|  | const root = document.querySelector("#root"); | ||||||
|  | const logEl = (msg) => { | ||||||
|  |   const el = document.createElement("pre"); | ||||||
|  |   el.innerText = msg; | ||||||
|  |   root.append(el); | ||||||
|  | }; | ||||||
|  | const sessionClient = async () => { | ||||||
|  |   const session = await sessionStatus(); | ||||||
|  |   return session && session["client"]; | ||||||
|  | }; | ||||||
|  | const sessionUser = async () => { | ||||||
|  |   const client = await sessionClient(); | ||||||
|  |   return client && client["user"]; | ||||||
|  | }; | ||||||
|  | const userAvatarUrl = async () => { | ||||||
|  |   const user = await sessionUser(); | ||||||
|  |   return user && user["avatar"]; | ||||||
|  | }; | ||||||
|  | const userUsername = async () => { | ||||||
|  |   const user = await sessionUser(); | ||||||
|  |   return user && user["username"]; | ||||||
|  | }; | ||||||
|  | const userAvatarImg = async () => { | ||||||
|  |   const avatar = await userAvatarUrl(); | ||||||
|  |   if (avatar) { | ||||||
|  |     const img = document.createElement("img"); | ||||||
|  |     img.src = avatar; | ||||||
|  |     img.width = 64; | ||||||
|  |     img.height = 64; | ||||||
|  |     img.alt = await userUsername(); | ||||||
|  |     return img; | ||||||
|  |   } else { | ||||||
|  |     return baseEl("span"); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | const userId = async () => { | ||||||
|  |   const user = await sessionUser(); | ||||||
|  |   return user && user["id"]; | ||||||
|  | }; | ||||||
|  | const baseEl = (tag, ...appended) => { | ||||||
|  |   const element = document.createElement(tag); | ||||||
|  |   element.append(...appended); | ||||||
|  |   return element; | ||||||
|  | }; | ||||||
|  | const aLogin = () => { | ||||||
|  |   const a = document.createElement("a"); | ||||||
|  |   a.href = "/login/"; | ||||||
|  |   a.innerText = "login"; | ||||||
|  |   return a; | ||||||
|  | }; | ||||||
|  | const aAuthLink = async () => { | ||||||
|  |   const response = await fetch("/authlink/"); | ||||||
|  |   return await response.text(); | ||||||
|  | }; | ||||||
|  | const aAuth = async () => { | ||||||
|  |   const a = document.createElement("a"); | ||||||
|  |   const [authlink, sessionstate] = await Promise.all([ | ||||||
|  |     aAuthLink(), | ||||||
|  |     sessionState(), | ||||||
|  |   ]); | ||||||
|  |   a.href = authlink + "&state=" + sessionstate; | ||||||
|  |   a.innerText = "auth"; | ||||||
|  |   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 audioWidget = (audio) => { | ||||||
|  |   const description = baseEl("span", audio.description); | ||||||
|  |   const timecode = baseEl("span", audio.timecode); | ||||||
|  |   const duration = baseEl("span", audio.duration); | ||||||
|  |   audio.tce = timecode; | ||||||
|  |   return baseEl("div", "audio", " ", timecode, "/", duration, " ", description); | ||||||
|  | }; | ||||||
|  | const aUpdateQueueOnce = async (queue, el) => { | ||||||
|  |   el.innerHTML = ""; | ||||||
|  |   if (queue !== null) { | ||||||
|  |     for (const audio of queue.queuejson) { | ||||||
|  |       el.append(audioWidget(audio)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | const aUpdateQueueSetup = async (el) => { | ||||||
|  |   let queue = await aQueue(); | ||||||
|  |   await aUpdateQueueOnce(queue, el); | ||||||
|  |   (async () => { | ||||||
|  |     while (true) { | ||||||
|  |       await sleep(2); | ||||||
|  |       if (queue !== null && queue.queuejson.length > 100) { | ||||||
|  |         await sleep((queue.queuejson.length - 100) / 200); | ||||||
|  |       } | ||||||
|  |       const newQueue = await aQueue(); | ||||||
|  |       await aUpdateQueueOnce(newQueue, el); | ||||||
|  |       queue = newQueue; | ||||||
|  |     } | ||||||
|  |   })(); | ||||||
|  |   (async () => { | ||||||
|  |     while (true) { | ||||||
|  |       await sleep(0.25); | ||||||
|  |       if (queue !== null) { | ||||||
|  |         for (const audio of queue.queuejson) { | ||||||
|  |           audio.tce.innerText = audio.ts(); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   })(); | ||||||
|  | }; | ||||||
|  | const aQueueWidget = async () => { | ||||||
|  |   const el = baseEl("div"); | ||||||
|  |   if (await sessionUser()) await aUpdateQueueSetup(el); | ||||||
|  |   return el; | ||||||
|  | }; | ||||||
|  | const pageHome = async () => { | ||||||
|  |   const el = document.createElement("div"); | ||||||
|  |   el.append( | ||||||
|  |     baseEl("div", aLogin()), | ||||||
|  |     baseEl("div", await userAvatarImg()), | ||||||
|  |     baseEl("div", await userId()), | ||||||
|  |     baseEl("div", await userUsername()), | ||||||
|  |     baseEl("div", await aQueueWidget()) | ||||||
|  |   ); | ||||||
|  |   el.id = "homeroot"; | ||||||
|  |   return el; | ||||||
|  | }; | ||||||
							
								
								
									
										20
									
								
								app/static/operator.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/static/operator.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | #operatorroot { | ||||||
|  |   height: 10em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* #operation { | ||||||
|  | } */ | ||||||
|  | 
 | ||||||
|  | #workerpool { | ||||||
|  |   display: grid; | ||||||
|  |   grid-template-columns: repeat(5, 1fr); | ||||||
|  |   gap: 1em; | ||||||
|  |   padding: 1em; | ||||||
|  |   height: 5em; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .workerview { | ||||||
|  |   background: #0f0f0f; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								app/static/operator.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/static/operator.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <head> | ||||||
|  |   <link rel="stylesheet" href="/main.css" /> | ||||||
|  |   <link rel="stylesheet" href="/operator.css" /> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |   <div id="root-container"> | ||||||
|  |     <div class="sidebars"></div> | ||||||
|  |     <div id="root"><div id="operatorroot"></div></div> | ||||||
|  |     <div class="sidebars"></div> | ||||||
|  |   </div> | ||||||
|  |   <script src="/main.js"></script> | ||||||
|  |   <script> | ||||||
|  |     (async () => { | ||||||
|  |       root.append(await pageHome()); | ||||||
|  |     })(); | ||||||
|  |   </script> | ||||||
|  |   <script src="/operator.js"></script> | ||||||
|  |   <script> | ||||||
|  |     (async () => { | ||||||
|  |       operatorroot.append(await pageOperator()); | ||||||
|  |     })(); | ||||||
|  |   </script> | ||||||
|  | </body> | ||||||
							
								
								
									
										69
									
								
								app/static/operator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/static/operator.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | |||||||
|  | aApi({ | ||||||
|  |   type: "guilds", | ||||||
|  |   operator: null, | ||||||
|  |   catches: { "not an operator": null, "*": null }, | ||||||
|  | }).then(console.log); | ||||||
|  | aApi({ | ||||||
|  |   type: "sleep", | ||||||
|  |   operator: null, | ||||||
|  |   duration: 1, | ||||||
|  |   echo: {}, | ||||||
|  |   time: null, | ||||||
|  |   catches: { "not an operator": null, "*": null }, | ||||||
|  | }).then(console.log); | ||||||
|  | aApi({ | ||||||
|  |   type: "*", | ||||||
|  |   idkey: "target", | ||||||
|  |   idbase: { | ||||||
|  |     type: "*", | ||||||
|  |     requests: { | ||||||
|  |       Count: {}, | ||||||
|  |       Concurrency: {}, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   operator: null, | ||||||
|  |   requests: { | ||||||
|  |     "v6d3music.api.Api().api": {}, | ||||||
|  |     "v6d3music.processing.pool.UnitJob.run": {}, | ||||||
|  |   }, | ||||||
|  |   catches: { "not an operator": null, "*": null }, | ||||||
|  |   time: null, | ||||||
|  | }).then((value) => console.log(JSON.stringify(value, undefined, 2))); | ||||||
|  | aApi({ | ||||||
|  |   type: "pool", | ||||||
|  |   operator: null, | ||||||
|  |   catches: { "not an operator": null, "*": null }, | ||||||
|  | }).then((value) => console.log(JSON.stringify(value, undefined, 2))); | ||||||
|  | const elJob = (job) => { | ||||||
|  |   const jobview = document.createElement("div"); | ||||||
|  |   jobview.classList.add("jobview"); | ||||||
|  |   jobview.innerText = JSON.stringify(job); | ||||||
|  |   return jobview; | ||||||
|  | }; | ||||||
|  | const elWorker = (worker) => { | ||||||
|  |   const workerview = document.createElement("div"); | ||||||
|  |   workerview.classList.add("workerview"); | ||||||
|  |   workerview.append(`qsize: ${worker.qsize}`); | ||||||
|  |   workerview.append(elJob(worker.job)); | ||||||
|  |   return workerview; | ||||||
|  | }; | ||||||
|  | const elPool = async () => { | ||||||
|  |   const pool = document.createElement("div"); | ||||||
|  |   pool.id = "workerpool"; | ||||||
|  |   const workers = await aApi({ | ||||||
|  |     type: "pool", | ||||||
|  |     operator: null, | ||||||
|  |     catches: { "not an operator": null, "*": null }, | ||||||
|  |   }); | ||||||
|  |   if (workers === null || workers.error !== undefined) return null; | ||||||
|  |   for (const worker of workers) { | ||||||
|  |     pool.append(elWorker(worker)); | ||||||
|  |   } | ||||||
|  |   return pool; | ||||||
|  | }; | ||||||
|  | const pageOperator = async () => { | ||||||
|  |   const operation = document.createElement("div"); | ||||||
|  |   operation.id = "operation"; | ||||||
|  |   operation.append(await elPool()); | ||||||
|  |   return operation; | ||||||
|  | }; | ||||||
							
								
								
									
										3
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | aiohttp==3.8.4 | ||||||
|  | fastapi==0.92.0 | ||||||
|  | uvicorn[standard]==0.20.0 | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user