Initial noscript markup for chat form & stream info

Use with-user decorator on routes (instead of with-token)
このコミットが含まれているのは:
n9k 2022-02-14 10:16:09 +00:00
コミット 885c10b5de
16個のファイルの変更376行の追加82行の削除

ファイルの表示

@ -5,7 +5,7 @@ from collections import OrderedDict
from quart import Quart from quart import Quart
from werkzeug.security import generate_password_hash 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 from anonstream.segments import DirectoryCache
async def create_app(): async def create_app():
@ -22,8 +22,11 @@ async def create_app():
app.config['AUTH_USERNAME'] = config['auth_username'] app.config['AUTH_USERNAME'] = config['auth_username']
app.config['AUTH_PWHASH'] = auth_pwhash app.config['AUTH_PWHASH'] = auth_pwhash
app.config['AUTH_TOKEN'] = generate_token() 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.chat = OrderedDict()
app.websockets = {} app.users = {}
app.websockets = set()
app.segments_directory_cache = DirectoryCache(config['segments_dir']) app.segments_directory_cache = DirectoryCache(config['segments_dir'])
async with app.app_context(): async with app.app_context():

ファイルの表示

@ -2,21 +2,26 @@ import asyncio
from quart import current_app, request, render_template, make_response, redirect, websocket from quart import current_app, request, render_template, make_response, redirect, websocket
from anonstream.segments import CatSegments from anonstream.stream import get_stream_title
from anonstream.wrappers import with_token_from, auth_required 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 from anonstream.websocket import websocket_outbound, websocket_inbound
@current_app.route('/') @current_app.route('/')
@with_token_from(request) @with_user_from(request)
async def home(token): async def home(user):
return await render_template('home.html', token=token) return await render_template('home.html', user=user)
@current_app.route('/stream.mp4') @current_app.route('/stream.mp4')
@with_token_from(request) @with_user_from(request)
async def stream(token): async def stream(user):
try: try:
cat_segments = CatSegments(current_app.segments_directory_cache, token) cat_segments = CatSegments(
except ValueError: directory_cache=current_app.segments_directory_cache,
token=user['token']
)
except Offline:
return 'offline', 404 return 'offline', 404
response = await make_response(cat_segments.stream()) response = await make_response(cat_segments.stream())
response.headers['Content-Type'] = 'video/mp4' response.headers['Content-Type'] = 'video/mp4'
@ -29,19 +34,43 @@ async def login():
return redirect('/') return redirect('/')
@current_app.websocket('/live') @current_app.websocket('/live')
@with_token_from(websocket) @with_user_from(websocket)
async def live(token): async def live(user):
queue = asyncio.Queue() queue = asyncio.Queue()
current_app.websockets[token] = queue current_app.websockets.add(queue)
producer = websocket_outbound(queue) producer = websocket_outbound(queue)
consumer = websocket_inbound( consumer = websocket_inbound(
queue=queue,
connected_websockets=current_app.websockets, connected_websockets=current_app.websockets,
token=token, token=user['token'],
secret=current_app.config['SECRET_KEY'], secret=current_app.config['SECRET_KEY'],
chat=current_app.chat, chat=current_app.chat,
) )
try: try:
await asyncio.gather(producer, consumer) await asyncio.gather(producer, consumer)
finally: 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),
)

ファイルの表示

@ -8,8 +8,11 @@ import aiofiles
RE_SEGMENT = re.compile(r'^(?P<index>\d+)\.ts$') RE_SEGMENT = re.compile(r'^(?P<index>\d+)\.ts$')
class Offline(Exception):
pass
class DirectoryCache: class DirectoryCache:
def __init__(self, directory, ttl=0.5): def __init__(self, directory, ttl=1.0):
self.directory = directory self.directory = directory
self.ttl = ttl self.ttl = ttl
self.expires = None self.expires = None
@ -41,7 +44,10 @@ class CatSegments:
def __init__(self, directory_cache, token): def __init__(self, directory_cache, token):
self.directory_cache = directory_cache self.directory_cache = directory_cache
self.token = token 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): async def stream(self):
while True: while True:

ファイルの表示

@ -1,27 +1,35 @@
/* token */
const token = document.querySelector("body").dataset.token;
/* insert js-only markup */ /* insert js-only markup */
const jsmarkup_info_title = '<header id="info__title" data-js="true"></header>'; const jsmarkup_info = '<div id="info_js"></div>';
const jsmarkup_chat_messages = '<ul id="chat-messages" data-js="true"></ul>'; const jsmarkup_info_title = '<header id="info_js__title" data-js="true"></header>';
const jsmarkup_chat_messages = '<ul id="chat-messages_js" data-js="true"></ul>';
const jsmarkup_chat_form = `\ const jsmarkup_chat_form = `\
<form id="chat-form" data-js="true" action="/chat" method="post"> <form id="chat-form_js" data-js="true" action="/chat" method="post">
<input id="chat-form__nonce" type="hidden" name="nonce" value=""> <input id="chat-form_js__nonce" type="hidden" name="nonce" value="">
<textarea id="chat-form__message" name="message" maxlength="512" required placeholder="Send a message..." rows="1"></textarea> <textarea id="chat-form_js__message" name="message" maxlength="512" required placeholder="Send a message..." rows="1"></textarea>
<div id="chat-live"> <div id="chat-live">
<span id="chat-live__ball"></span> <span id="chat-live__ball"></span>
<span id="chat-live__status">Not connected to chat</span> <span id="chat-live__status">Not connected to chat</span>
</div> </div>
<input id="chat-form__submit" type="submit" value="Chat" disabled> <input id="chat-form_js__submit" type="submit" value="Chat" disabled>
</form>`; </form>`;
const insert_jsmarkup = () => { const insert_jsmarkup = () => {
if (document.getElementById("info__title") === null) { if (document.getElementById("info_js") === null) {
const parent = document.getElementById("info"); 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); 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"); const parent = document.getElementById("chat__messages");
parent.insertAdjacentHTML("beforeend", jsmarkup_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"); const parent = document.getElementById("chat__form");
parent.insertAdjacentHTML("beforeend", jsmarkup_chat_form); parent.insertAdjacentHTML("beforeend", jsmarkup_chat_form);
} }
@ -30,9 +38,9 @@ const insert_jsmarkup = () => {
insert_jsmarkup(); insert_jsmarkup();
/* create websocket */ /* 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_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 on_websocket_message = (event) => {
const receipt = JSON.parse(event.data); const receipt = JSON.parse(event.data);
switch (receipt.type) { switch (receipt.type) {
@ -69,7 +77,8 @@ const on_websocket_message = (event) => {
const chat_message_name = document.createElement("span"); const chat_message_name = document.createElement("span");
chat_message_name.classList.add("chat-message__name"); chat_message_name.classList.add("chat-message__name");
chat_message_name.innerText = receipt.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"); const chat_message_text = document.createElement("span");
chat_message_text.classList.add("chat-message__text"); chat_message_text.classList.add("chat-message__text");
@ -104,7 +113,7 @@ const connect_websocket = () => {
} }
chat_live_ball.style.borderColor = "gold"; chat_live_ball.style.borderColor = "gold";
chat_live_status.innerText = "Connecting to chat..."; 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) => { ws.addEventListener("open", (event) => {
chat_form_submit.disabled = false; chat_form_submit.disabled = false;
chat_live_ball.style.borderColor = "green"; chat_live_ball.style.borderColor = "green";
@ -134,10 +143,10 @@ const connect_websocket = () => {
connect_websocket(); connect_websocket();
/* override js-only chat form */ /* override js-only chat form */
const chat_form = document.getElementById("chat-form"); const chat_form = document.getElementById("chat-form_js");
const chat_form_nonce = document.getElementById("chat-form__nonce"); const chat_form_nonce = document.getElementById("chat-form_js__nonce");
const chat_form_message = document.getElementById("chat-form__message"); const chat_form_message = document.getElementById("chat-form_js__message");
const chat_form_submit = document.getElementById("chat-form__submit"); const chat_form_submit = document.getElementById("chat-form_js__submit");
chat_form.addEventListener("submit", (event) => { chat_form.addEventListener("submit", (event) => {
event.preventDefault(); event.preventDefault();
const payload = {message: chat_form_message.value, nonce: chat_form_nonce.value}; const payload = {message: chat_form_message.value, nonce: chat_form_nonce.value};

ファイルの表示

@ -15,6 +15,9 @@
--pure-video-height: calc(100vw * var(--aspect-y) / var(--aspect-x)); --pure-video-height: calc(100vw * var(--aspect-y) / var(--aspect-x));
--video-height: max(144px, min(75vh, var(--pure-video-height))); --video-height: max(144px, min(75vh, var(--pure-video-height)));
--button-height: 2rem;
--nojs-info-height: 17ch;
} }
body { body {
@ -35,6 +38,15 @@ body {
a { a {
color: #42a5d7; color: #42a5d7;
} }
iframe {
width: 100%;
border: 1px solid red;
box-sizing: border-box;
}
noscript {
display: grid;
height: 100%;
}
#stream { #stream {
background: black; background: black;
@ -45,14 +57,18 @@ a {
#info { #info {
border-top: var(--main-border); border-top: var(--main-border);
box-sizing: border-box;
padding: 0.75ch 1ch;
overflow-y: scroll;
grid-area: info; grid-area: info;
} }
#info__title { #info_js {
overflow-y: auto;
margin: 1ch 1.5ch;
}
#info_js__title {
font-size: 18pt; font-size: 18pt;
} }
#info_nojs {
height: var(--nojs-info-height);
}
#chat { #chat {
display: grid; display: grid;
@ -70,51 +86,57 @@ a {
margin-bottom: 1ch; margin-bottom: 1ch;
border-bottom: var(--chat-border); border-bottom: var(--chat-border);
} }
#chat-form { #chat-form_js {
display: grid; display: grid;
grid-template: auto 2rem / auto 8ch; grid-template: auto var(--button-height) / auto 8ch;
grid-gap: 0.75ch; grid-gap: 0.75ch;
margin: 1ch; margin: 1ch;
} }
#chat-form__submit { #chat-form_js__submit {
grid-column: 2 / span 1; grid-column: 2 / span 1;
} }
#chat-form__message { #chat-form_js__message {
grid-column: 1 / span 2; grid-column: 1 / span 2;
background-color: #434347; background-color: #434347;
border-radius: 4px; border-radius: 4px;
border: 2px solid transparent; border: 2px solid transparent;
transition: 0.25s; transition: 0.25s;
max-height: 16ch; max-height: 16ch;
min-height: 2.25ch; min-height: 1.75ch;
padding: 1.5ch; padding: 1.5ch;
color: #c3c3c7; color: #c3c3c7;
resize: vertical; resize: vertical;
} }
#chat-form__message:not(:focus):hover { #chat-form_js__message:not(:focus):hover {
border-color: #737377; border-color: #737377;
} }
#chat-form__message:focus { #chat-form_js__message:focus {
background-color: black; background-color: black;
border-color: #3584e4; border-color: #3584e4;
} }
#chat-form_nojs {
height: 13ch;
}
#chat__messages { #chat__messages {
margin: 0 1ch;
overflow-y: auto; overflow-y: auto;
position: relative; position: relative;
} }
#chat-messages { #chat-messages_js {
list-style: none; list-style: none;
padding: 0;
margin: 0; margin: 0;
padding: 0 1ch;
width: 100%; width: 100%;
box-sizing: border-box;
max-height: 100%; max-height: 100%;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
font-size: 11pt; font-size: 11pt;
} }
#chat-messages_nojs {
height: 100%;
}
.chat-message { .chat-message {
padding: 0.5ch 0.75ch ; padding: 0.5ch 0.75ch;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border-radius: 4px; border-radius: 4px;
@ -129,10 +151,11 @@ a {
} }
.chat-message__text { .chat-message__text {
overflow-wrap: anywhere; overflow-wrap: anywhere;
line-height: 1.3125;
} }
#chat-live { #chat-live {
font-size: 9pt; font-size: 9pt;
line-height: 2rem; line-height: var(--button-height);
} }
#chat-live__ball { #chat-live__ball {
border: 4px solid maroon; border: 4px solid maroon;
@ -186,6 +209,9 @@ footer {
background-color: #3065a6; background-color: #3065a6;
border-style: inset; border-style: inset;
} }
#both:target #info_nojs {
height: 9ch;
}
@media (min-width: 720px) { @media (min-width: 720px) {
:root { :root {
@ -214,4 +240,7 @@ footer {
border-left: var(--chat-border); border-left: var(--chat-border);
min-height: 100vh; min-height: 100vh;
} }
#both:target #info_nojs {
height: var(--nojs-info-height);
}
} }

5
anonstream/stream.py ノーマルファイル
ファイルの表示

@ -0,0 +1,5 @@
def get_stream_title():
return 'Stream title'
def get_stream_uptime():
return None

ファイルの表示

@ -4,18 +4,18 @@
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" href="/static/style.css" type="text/css"> <link rel="stylesheet" href="/static/style.css" type="text/css">
</head> </head>
<body id="both" data-token="{{ token }}"> <body id="both" data-token="{{ user.token }}">
<video id="stream" src="/stream.mp4" controls></video> <video id="stream" src="{{ url_for('stream', token=user.token) }}" controls></video>
<article id="info"> <article id="info">
<noscript><iframe id="info_js" data-js="false"></iframe></noscript> <noscript><iframe id="info_nojs" src="{{ url_for('nojs_info', token=user.token) }}" data-js="false"></iframe></noscript>
</article> </article>
<aside id="chat"> <aside id="chat">
<header id="chat__header">Stream chat</header> <header id="chat__header">Stream chat</header>
<article id="chat__messages"> <article id="chat__messages">
<noscript><iframe id="chat-messages_nojs" data-js="false"></iframe></noscript> <noscript><iframe id="chat-messages_nojs" src="{{ url_for('nojs_chat', token=user.token) }}" data-js="false"></iframe></noscript>
</article> </article>
<section id="chat__form"> <section id="chat__form">
<noscript><iframe id="chat-form_nojs" data-js="false"></iframe></noscript> <noscript><iframe id="chat-form_nojs" src="{{ url_for('nojs_form', token=user.token, _anchor='notice') }}" data-js="false"></iframe></noscript>
</section> </section>
</aside> </aside>
<nav id="toggle"> <nav id="toggle">

ファイルの表示

@ -1,10 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>#title {font-size: 18pt;}</style>
</head>
<body>
<header id="title">{{ title }}</header>
</body>
</html>

172
anonstream/templates/nojs_form.html ノーマルファイル
ファイルの表示

@ -0,0 +1,172 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
:root {
--padding: 1ch;
}
body {
margin: 0;
height: 13ch;
color: white;
}
a {
color: #42a5d7;
}
label {
cursor: pointer;
}
.tripcode {
padding: 0 4px;
border-radius: 7px;
font-family: monospace;
align-self: center;
}
#notice {
padding: var(--margin);
text-align: center;
color: white;
text-decoration: none;
height: 100%;
box-sizing: border-box;
align-items: center;
}
#notice h1 {
font-size: 18pt;
margin: 0;
}
#chat-form, #appearance-form {
padding: var(--padding);
height: 100%;
box-sizing: border-box;
grid-gap: 0.75ch;
}
#chat-form {
display: grid;
grid: auto 2rem / auto 8ch;
grid-gap: 0.75ch;
}
#chat-form__message {
resize: none;
grid-column: 1 / span 2;
background-color: #434347;
border-radius: 4px;
border: 2px solid transparent;
transition: 0.25s;
padding: 1.5ch;
color: #c3c3c7;
}
#chat-form__message:not(:focus):hover {
border-color: #737377;
}
#chat-form__message:focus {
background-color: black;
border-color: #3584e4;
}
#appearance-form {
grid-auto-rows: 1fr 1fr 2rem;
grid-auto-columns: min-content 1fr min-content;
}
#appearance-form__label-password,
#appearance-form__password,
#password-column,
#hide-password {
grid-row: 2;
}
#appearance-form__buttons {
grid-column: 1 / span 3;
display: grid;
grid-template-columns: auto 8ch;
}
#password-toggle,
#appearance-form__password {
display: none;
}
#hide-password {
visibility: hidden;
}
#password-toggle:checked + #cleared-toggle:not(:checked) ~ #appearance-form__password {
display: inline;
}
#password-toggle:checked + #cleared-toggle:not(:checked) ~ #hide-password {
visibility: visible;
}
#password-toggle:checked + #cleared-toggle:not(:checked) + #password-column {
display: none;
}
#cleared-toggle,
#cleared,
#hide-cleared {
display: none;
}
#cleared-toggle:checked + #password-column > #cleared,
#cleared-toggle:checked + #password-column > #hide-cleared {
display: inline;
}
#cleared-toggle:checked + #password-column > #tripcode,
#cleared-toggle:checked + #password-column > #show-cleared {
display: none;
}
#notice, #appearance-form {
display: none;
}
#anchor:target > #chat-form {
display: none;
}
#anchor:target > #appearance-form {
display: grid;
}
#notice:target {
display: grid;
}
#notice:target + #chat-form,
#notice:target ~ #appearance-form {
display: none;
}
</style>
</head>
<body id="anchor">
<a href="#chat-form" id="notice">
<header><h1>No notice</h1></header>
<small>Click to dismiss</small>
</a>
</article>
<form id="chat-form" action="/chat/message">
<input type="hidden" name="token" value="{{ user.token }}">
<textarea id="chat-form__message" name="text" placeholder="Send a message..." required rows="1"></textarea>
<div id="chat-form__exit"><a href="#anchor">Settings</a></div>
<input id="chat-form__submit" type="submit" value="Chat">
</form>
<form id="appearance-form" action="/chat/appearance">
<input type="hidden" name="token" value="{{ user.token }}">
<label for="appearance-form__name">Name:</label>
<input id="appearance-form__name" name="name" value="{{ user.name or '' }}" placeholder="{{ user.name or default_name }}">
<label id="appearance-form__label-password" for="appearance-form__password">Tripcode:</label>
<input id="password-toggle" name="set-tripcode" type="checkbox">
<input id="cleared-toggle" name="clear-tripcode" type="checkbox">
<div id="password-column">
{% if user.tripcode == none %}
<label class="show-password tripcode" for="password-toggle">(no tripcode)</label>
{% else %}
<label id="tripcode" for="password-toggle" class="show-password tripcode" style="background-color:{{ user.tripcode.background }};color:{{ user.tripcode.foreground }};">{{ user.tripcode.digest }}</label>
<label id="show-cleared" for="cleared-toggle">&cross;</label>
<div id="cleared" class="tripcode">(cleared)</div>
<label id="hide-cleared" for="cleared-toggle">undo</label>
{% endif %}
</div>
<input id="appearance-form__password" name="password" type="password"></div>
<label id="hide-password" for="password-toggle">&cross;</label>
<div id="appearance-form__buttons">
<div><a href="#chat-form">Return to chat</a></div>
<input type="submit" value="Update">
</div>
</form>
</body>
</html>

19
anonstream/templates/nojs_info.html ノーマルファイル
ファイルの表示

@ -0,0 +1,19 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
overflow-y: scroll;
margin: 1ch 1.5ch;
}
#title {
color: white;
font-size: 18pt;
}
</style>
</head>
<body>
<header id="title">{{ title }}</header>
</body>
</html>

9
anonstream/users.py ノーマルファイル
ファイルの表示

@ -0,0 +1,9 @@
from quart import current_app
def get_default_name(user):
return (
current_app.config['DEFAULT_HOST_NAME']
if user['broadcaster'] else
current_app.config['DEFAULT_ANON_NAME']
)

ファイルの表示

@ -1,4 +0,0 @@
import secrets
def generate_token():
return secrets.token_hex(16)

16
anonstream/utils/users.py ノーマルファイル
ファイルの表示

@ -0,0 +1,16 @@
import secrets
def generate_token():
return secrets.token_hex(16)
def generate_user(token, broadcaster, timestamp):
return {
'token': token,
'broadcaster': broadcaster,
'name': None,
'tripcode': None,
'seen': {
'first': timestamp,
'last': timestamp,
},
}

ファイルの表示

@ -2,6 +2,7 @@ import asyncio
from quart import websocket from quart import websocket
from anonstream.stream import get_stream_title, get_stream_uptime
from anonstream.chat import add_chat_message from anonstream.chat import add_chat_message
from anonstream.utils.chat import generate_nonce from anonstream.utils.chat import generate_nonce
from anonstream.utils.websocket import parse from anonstream.utils.websocket import parse
@ -10,8 +11,8 @@ async def websocket_outbound(queue):
payload = { payload = {
'type': 'init', 'type': 'init',
'nonce': generate_nonce(), 'nonce': generate_nonce(),
'title': 'Stream title', 'title': get_stream_title(),
'uptime': None, 'uptime': get_stream_uptime(),
'chat': [], 'chat': [],
} }
await websocket.send_json(payload) await websocket.send_json(payload)
@ -19,7 +20,7 @@ async def websocket_outbound(queue):
payload = await queue.get() payload = await queue.get()
await websocket.send_json(payload) await websocket.send_json(payload)
async def websocket_inbound(connected_websockets, token, secret, chat): async def websocket_inbound(queue, connected_websockets, token, secret, chat):
while True: while True:
receipt = await websocket.receive_json() receipt = await websocket.receive_json()
receipt, error = parse(chat.keys(), secret, receipt) receipt, error = parse(chat.keys(), secret, receipt)
@ -36,7 +37,6 @@ async def websocket_inbound(connected_websockets, token, secret, chat):
'nonce': nonce, 'nonce': nonce,
'next': generate_nonce(), 'next': generate_nonce(),
} }
queue = connected_websockets[token]
await queue.put(payload) await queue.put(payload)
if error is None: if error is None:
@ -46,5 +46,5 @@ async def websocket_inbound(connected_websockets, token, secret, chat):
'name': 'Anonymous', 'name': 'Anonymous',
'text': text, 'text': text,
} }
for queue in connected_websockets.values(): for other_queue in connected_websockets:
await queue.put(payload) await other_queue.put(payload)

ファイルの表示

@ -1,9 +1,10 @@
import time
from functools import wraps from functools import wraps
from quart import current_app, request, abort, make_response from quart import current_app, request, abort, make_response
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from anonstream.utils.token import generate_token from anonstream.utils.users import generate_token, generate_user
def check_auth(context): def check_auth(context):
auth = context.authorization auth = context.authorization
@ -31,15 +32,23 @@ def auth_required(f):
return wrapper return wrapper
def with_token_from(context): def with_user_from(context):
def with_token_from_context(f): def with_user_from_context(f):
@wraps(f) @wraps(f)
async def wrapper(*args, **kwargs): async def wrapper(*args, **kwargs):
if check_auth(context): broadcaster = check_auth(context)
if broadcaster:
token = current_app.config['AUTH_TOKEN'] token = current_app.config['AUTH_TOKEN']
else: else:
token = context.args.get('token') or generate_token() token = context.args.get('token') or generate_token()
return await f(token, *args, **kwargs) timestamp = int(time.time())
user = current_app.users.get(token)
if user is not None:
user['seen']['last'] = timestamp
else:
user = generate_user(token, broadcaster, timestamp)
return await f(user, *args, **kwargs)
return wrapper return wrapper
return with_token_from_context
return with_user_from_context

ファイルの表示

@ -1,3 +1,5 @@
secret_key = "test" secret_key = "test"
auth_username = "broadcaster" auth_username = "broadcaster"
segments_dir = "stream/" segments_dir = "stream/"
default_host_name = "Broadcaster"
default_anon_name = "Anonymous"