From 885c10b5de7a5ef2dfe8d97159511585e6e74803 Mon Sep 17 00:00:00 2001 From: n9k Date: Mon, 14 Feb 2022 10:16:09 +0000 Subject: [PATCH] Initial noscript markup for chat form & stream info Use with-user decorator on routes (instead of with-token) --- anonstream/__init__.py | 7 +- anonstream/routes.py | 57 ++++++--- anonstream/segments.py | 10 +- anonstream/static/anonstream.js | 43 ++++--- anonstream/static/style.css | 61 +++++++--- anonstream/stream.py | 5 + anonstream/templates/home.html | 10 +- anonstream/templates/info.html | 10 -- anonstream/templates/nojs_form.html | 172 ++++++++++++++++++++++++++++ anonstream/templates/nojs_info.html | 19 +++ anonstream/users.py | 9 ++ anonstream/utils/token.py | 4 - anonstream/utils/users.py | 16 +++ anonstream/websocket.py | 12 +- anonstream/wrappers.py | 21 +++- config.toml | 2 + 16 files changed, 376 insertions(+), 82 deletions(-) create mode 100644 anonstream/stream.py delete mode 100644 anonstream/templates/info.html create mode 100644 anonstream/templates/nojs_form.html create mode 100644 anonstream/templates/nojs_info.html create mode 100644 anonstream/users.py delete mode 100644 anonstream/utils/token.py create mode 100644 anonstream/utils/users.py diff --git a/anonstream/__init__.py b/anonstream/__init__.py index 77e4026..6a45237 100644 --- a/anonstream/__init__.py +++ b/anonstream/__init__.py @@ -5,7 +5,7 @@ from collections import OrderedDict from quart import Quart from werkzeug.security import generate_password_hash -from anonstream.utils.token import generate_token +from anonstream.utils.users import generate_token from anonstream.segments import DirectoryCache async def create_app(): @@ -22,8 +22,11 @@ async def create_app(): app.config['AUTH_USERNAME'] = config['auth_username'] app.config['AUTH_PWHASH'] = auth_pwhash app.config['AUTH_TOKEN'] = generate_token() + app.config['DEFAULT_HOST_NAME'] = config['default_host_name'] + app.config['DEFAULT_ANON_NAME'] = config['default_anon_name'] app.chat = OrderedDict() - app.websockets = {} + app.users = {} + app.websockets = set() app.segments_directory_cache = DirectoryCache(config['segments_dir']) async with app.app_context(): diff --git a/anonstream/routes.py b/anonstream/routes.py index 630ea4d..1dfdb06 100644 --- a/anonstream/routes.py +++ b/anonstream/routes.py @@ -2,21 +2,26 @@ import asyncio from quart import current_app, request, render_template, make_response, redirect, websocket -from anonstream.segments import CatSegments -from anonstream.wrappers import with_token_from, auth_required +from anonstream.stream import get_stream_title +from anonstream.segments import CatSegments, Offline +from anonstream.users import get_default_name +from anonstream.wrappers import with_user_from, auth_required from anonstream.websocket import websocket_outbound, websocket_inbound @current_app.route('/') -@with_token_from(request) -async def home(token): - return await render_template('home.html', token=token) +@with_user_from(request) +async def home(user): + return await render_template('home.html', user=user) @current_app.route('/stream.mp4') -@with_token_from(request) -async def stream(token): +@with_user_from(request) +async def stream(user): try: - cat_segments = CatSegments(current_app.segments_directory_cache, token) - except ValueError: + cat_segments = CatSegments( + directory_cache=current_app.segments_directory_cache, + token=user['token'] + ) + except Offline: return 'offline', 404 response = await make_response(cat_segments.stream()) response.headers['Content-Type'] = 'video/mp4' @@ -29,19 +34,43 @@ async def login(): return redirect('/') @current_app.websocket('/live') -@with_token_from(websocket) -async def live(token): +@with_user_from(websocket) +async def live(user): queue = asyncio.Queue() - current_app.websockets[token] = queue + current_app.websockets.add(queue) producer = websocket_outbound(queue) consumer = websocket_inbound( + queue=queue, connected_websockets=current_app.websockets, - token=token, + token=user['token'], secret=current_app.config['SECRET_KEY'], chat=current_app.chat, ) try: await asyncio.gather(producer, consumer) finally: - current_app.websockets.pop(token) + current_app.websockets.remove(queue) + +@current_app.route('/info.html') +@with_user_from(request) +async def nojs_info(user): + return await render_template( + 'nojs_info.html', + user=user, + title=get_stream_title(), + ) + +@current_app.route('/chat/messages.html') +@with_user_from(request) +async def nojs_chat(user): + return await render_template('nojs_chat.html', user=user) + +@current_app.route('/chat/form.html') +@with_user_from(request) +async def nojs_form(user): + return await render_template( + 'nojs_form.html', + user=user, + default_name=get_default_name(user), + ) diff --git a/anonstream/segments.py b/anonstream/segments.py index 7f178b1..09cc85b 100644 --- a/anonstream/segments.py +++ b/anonstream/segments.py @@ -8,8 +8,11 @@ import aiofiles RE_SEGMENT = re.compile(r'^(?P\d+)\.ts$') +class Offline(Exception): + pass + class DirectoryCache: - def __init__(self, directory, ttl=0.5): + def __init__(self, directory, ttl=1.0): self.directory = directory self.ttl = ttl self.expires = None @@ -41,7 +44,10 @@ class CatSegments: def __init__(self, directory_cache, token): self.directory_cache = directory_cache self.token = token - self.index = max(self.directory_cache.segments()) + try: + self.index = max(self.directory_cache.segments()) + except ValueError: # max of empty sequence, i.e. there are no segments + raise Offline async def stream(self): while True: diff --git a/anonstream/static/anonstream.js b/anonstream/static/anonstream.js index 46cf110..f45427c 100644 --- a/anonstream/static/anonstream.js +++ b/anonstream/static/anonstream.js @@ -1,27 +1,35 @@ +/* token */ +const token = document.querySelector("body").dataset.token; + /* insert js-only markup */ -const jsmarkup_info_title = '
'; -const jsmarkup_chat_messages = ''; +const jsmarkup_info = '
'; +const jsmarkup_info_title = '
'; +const jsmarkup_chat_messages = ''; const jsmarkup_chat_form = `\ -
- - + + +
Not connected to chat
- +
`; const insert_jsmarkup = () => { - if (document.getElementById("info__title") === null) { + if (document.getElementById("info_js") === null) { const parent = document.getElementById("info"); + parent.insertAdjacentHTML("beforeend", jsmarkup_info); + } + if (document.getElementById("info_js__title") === null) { + const parent = document.getElementById("info_js"); parent.insertAdjacentHTML("beforeend", jsmarkup_info_title); } - if (document.getElementById("chat-messages") === null) { + if (document.getElementById("chat-messages_js") === null) { const parent = document.getElementById("chat__messages"); parent.insertAdjacentHTML("beforeend", jsmarkup_chat_messages); } - if (document.getElementById("chat-form") === null) { + if (document.getElementById("chat-form_js") === null) { const parent = document.getElementById("chat__form"); parent.insertAdjacentHTML("beforeend", jsmarkup_chat_form); } @@ -30,9 +38,9 @@ const insert_jsmarkup = () => { insert_jsmarkup(); /* create websocket */ -const info_title = document.getElementById("info__title"); +const info_title = document.getElementById("info_js__title"); const chat_messages_parent = document.getElementById("chat__messages"); -const chat_messages = document.getElementById("chat-messages"); +const chat_messages = document.getElementById("chat-messages_js"); const on_websocket_message = (event) => { const receipt = JSON.parse(event.data); switch (receipt.type) { @@ -69,7 +77,8 @@ const on_websocket_message = (event) => { const chat_message_name = document.createElement("span"); chat_message_name.classList.add("chat-message__name"); chat_message_name.innerText = receipt.name; - chat_message_name.style.color = receipt.color + //chat_message_name.dataset.color = receipt.color; // not working in any browser + chat_message_name.style.color = receipt.color; const chat_message_text = document.createElement("span"); chat_message_text.classList.add("chat-message__text"); @@ -104,7 +113,7 @@ const connect_websocket = () => { } chat_live_ball.style.borderColor = "gold"; chat_live_status.innerText = "Connecting to chat..."; - ws = new WebSocket(`ws://${document.domain}:${location.port}/live`); + ws = new WebSocket(`ws://${document.domain}:${location.port}/live?token=${encodeURIComponent(token)}`); ws.addEventListener("open", (event) => { chat_form_submit.disabled = false; chat_live_ball.style.borderColor = "green"; @@ -134,10 +143,10 @@ const connect_websocket = () => { connect_websocket(); /* override js-only chat form */ -const chat_form = document.getElementById("chat-form"); -const chat_form_nonce = document.getElementById("chat-form__nonce"); -const chat_form_message = document.getElementById("chat-form__message"); -const chat_form_submit = document.getElementById("chat-form__submit"); +const chat_form = document.getElementById("chat-form_js"); +const chat_form_nonce = document.getElementById("chat-form_js__nonce"); +const chat_form_message = document.getElementById("chat-form_js__message"); +const chat_form_submit = document.getElementById("chat-form_js__submit"); chat_form.addEventListener("submit", (event) => { event.preventDefault(); const payload = {message: chat_form_message.value, nonce: chat_form_nonce.value}; diff --git a/anonstream/static/style.css b/anonstream/static/style.css index d19886b..108581d 100644 --- a/anonstream/static/style.css +++ b/anonstream/static/style.css @@ -15,6 +15,9 @@ --pure-video-height: calc(100vw * var(--aspect-y) / var(--aspect-x)); --video-height: max(144px, min(75vh, var(--pure-video-height))); + + --button-height: 2rem; + --nojs-info-height: 17ch; } body { @@ -35,6 +38,15 @@ body { a { color: #42a5d7; } +iframe { + width: 100%; + border: 1px solid red; + box-sizing: border-box; +} +noscript { + display: grid; + height: 100%; +} #stream { background: black; @@ -45,14 +57,18 @@ a { #info { border-top: var(--main-border); - box-sizing: border-box; - padding: 0.75ch 1ch; - overflow-y: scroll; grid-area: info; } -#info__title { +#info_js { + overflow-y: auto; + margin: 1ch 1.5ch; +} +#info_js__title { font-size: 18pt; } +#info_nojs { + height: var(--nojs-info-height); +} #chat { display: grid; @@ -70,51 +86,57 @@ a { margin-bottom: 1ch; border-bottom: var(--chat-border); } -#chat-form { +#chat-form_js { display: grid; - grid-template: auto 2rem / auto 8ch; + grid-template: auto var(--button-height) / auto 8ch; grid-gap: 0.75ch; margin: 1ch; } -#chat-form__submit { +#chat-form_js__submit { grid-column: 2 / span 1; } -#chat-form__message { +#chat-form_js__message { grid-column: 1 / span 2; background-color: #434347; border-radius: 4px; border: 2px solid transparent; transition: 0.25s; max-height: 16ch; - min-height: 2.25ch; + min-height: 1.75ch; padding: 1.5ch; color: #c3c3c7; resize: vertical; } -#chat-form__message:not(:focus):hover { +#chat-form_js__message:not(:focus):hover { border-color: #737377; } -#chat-form__message:focus { +#chat-form_js__message:focus { background-color: black; border-color: #3584e4; } +#chat-form_nojs { + height: 13ch; +} #chat__messages { - margin: 0 1ch; overflow-y: auto; position: relative; } -#chat-messages { +#chat-messages_js { list-style: none; - padding: 0; margin: 0; + padding: 0 1ch; width: 100%; + box-sizing: border-box; max-height: 100%; position: absolute; bottom: 0; font-size: 11pt; } +#chat-messages_nojs { + height: 100%; +} .chat-message { - padding: 0.5ch 0.75ch ; + padding: 0.5ch 0.75ch; width: 100%; box-sizing: border-box; border-radius: 4px; @@ -129,10 +151,11 @@ a { } .chat-message__text { overflow-wrap: anywhere; + line-height: 1.3125; } #chat-live { font-size: 9pt; - line-height: 2rem; + line-height: var(--button-height); } #chat-live__ball { border: 4px solid maroon; @@ -186,6 +209,9 @@ footer { background-color: #3065a6; border-style: inset; } +#both:target #info_nojs { + height: 9ch; +} @media (min-width: 720px) { :root { @@ -214,4 +240,7 @@ footer { border-left: var(--chat-border); min-height: 100vh; } + #both:target #info_nojs { + height: var(--nojs-info-height); + } } diff --git a/anonstream/stream.py b/anonstream/stream.py new file mode 100644 index 0000000..d650eb1 --- /dev/null +++ b/anonstream/stream.py @@ -0,0 +1,5 @@ +def get_stream_title(): + return 'Stream title' + +def get_stream_uptime(): + return None diff --git a/anonstream/templates/home.html b/anonstream/templates/home.html index 6f16d50..05bf27c 100644 --- a/anonstream/templates/home.html +++ b/anonstream/templates/home.html @@ -4,18 +4,18 @@ - - + +
- +