From 1d5b4462910e1d7f8846cf6e9e406471520d8fd3 Mon Sep 17 00:00:00 2001 From: n9k Date: Sun, 19 Jun 2022 08:10:23 +0000 Subject: [PATCH] Track the last time users were sent chat messages --- anonstream/helpers/user.py | 1 + anonstream/routes/core.py | 4 ++-- anonstream/routes/nojs.py | 3 ++- anonstream/routes/websocket.py | 5 +++-- anonstream/routes/wrappers.py | 4 ++-- anonstream/user.py | 13 ++++++++++--- anonstream/websocket.py | 3 ++- anonstream/wrappers.py | 13 +++++++++---- 8 files changed, 31 insertions(+), 15 deletions(-) diff --git a/anonstream/helpers/user.py b/anonstream/helpers/user.py index 072230c..41b1023 100644 --- a/anonstream/helpers/user.py +++ b/anonstream/helpers/user.py @@ -44,6 +44,7 @@ def generate_user(timestamp, token, broadcaster, presence): 'seen': timestamp, 'watching': -inf, 'eyes': -inf, + 'reading': -inf, }, 'presence': presence, 'linespan': deque(), diff --git a/anonstream/routes/core.py b/anonstream/routes/core.py index 92425be..ad15dbe 100644 --- a/anonstream/routes/core.py +++ b/anonstream/routes/core.py @@ -9,7 +9,7 @@ from werkzeug.exceptions import TooManyRequests from anonstream.captcha import get_captcha_image from anonstream.segments import segments, StopSendingSegments from anonstream.stream import is_online, get_stream_uptime -from anonstream.user import watched, create_eyes, renew_eyes, EyesException, RatelimitedEyes +from anonstream.user import watching, create_eyes, renew_eyes, EyesException, RatelimitedEyes from anonstream.routes.wrappers import with_user_from, auth_required from anonstream.utils.security import generate_csp @@ -42,7 +42,7 @@ async def stream(user): except EyesException as e: raise StopSendingSegments(f'eyes {eyes_id} not allowed: {e!r}') from e print(f'{uri}: {eyes_id}~{user["token"]}') - watched(user) + watching(user) generator = segments(segment_read_hook, token=user['token']) response = await make_response(generator) diff --git a/anonstream/routes/nojs.py b/anonstream/routes/nojs.py index 0a6c836..1635e69 100644 --- a/anonstream/routes/nojs.py +++ b/anonstream/routes/nojs.py @@ -6,7 +6,7 @@ from quart import current_app, request, render_template, redirect, url_for, esca from anonstream.captcha import get_random_captcha_digest_for from anonstream.chat import add_chat_message, Rejected from anonstream.stream import is_online, get_stream_title, get_stream_uptime_and_viewership -from anonstream.user import add_state, pop_state, try_change_appearance, update_presence, get_users_by_presence, Presence, verify, deverify, BadCaptcha +from anonstream.user import add_state, pop_state, try_change_appearance, update_presence, get_users_by_presence, Presence, verify, deverify, BadCaptcha, reading from anonstream.routes.wrappers import with_user_from, render_template_with_etag from anonstream.helpers.chat import get_scrollback from anonstream.helpers.user import get_default_name @@ -46,6 +46,7 @@ async def nojs_info(user): @current_app.route('/chat/messages.html') @with_user_from(request) async def nojs_chat_messages(user): + reading(user) return await render_template_with_etag( 'nojs_chat_messages.html', {'csp': generate_csp()}, diff --git a/anonstream/routes/websocket.py b/anonstream/routes/websocket.py index 12eafd4..ab4927a 100644 --- a/anonstream/routes/websocket.py +++ b/anonstream/routes/websocket.py @@ -7,15 +7,16 @@ from math import inf from quart import current_app, websocket -from anonstream.user import see +from anonstream.user import see, reading from anonstream.websocket import websocket_outbound, websocket_inbound from anonstream.routes.wrappers import with_user_from @current_app.websocket('/live') @with_user_from(websocket) async def live(user): - queue = asyncio.Queue(maxsize=0) + queue = asyncio.Queue() user['websockets'][queue] = -inf + reading(user) producer = websocket_outbound(queue, user) consumer = websocket_inbound(queue, user) diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index 59b006f..9bc0da9 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -5,7 +5,6 @@ import hashlib import hmac import re import string -import time from functools import wraps from quart import current_app, request, abort, make_response, render_template, request @@ -15,6 +14,7 @@ from anonstream.broadcast import broadcast from anonstream.user import see from anonstream.helpers.user import generate_user from anonstream.utils.user import generate_token, Presence +from anonstream.wrappers import get_timestamp CONFIG = current_app.config MESSAGES = current_app.messages @@ -68,7 +68,7 @@ def with_user_from(context): def with_user_from_context(f): @wraps(f) async def wrapper(*args, **kwargs): - timestamp = int(time.time()) + timestamp = get_timestamp() # Check if broadcaster broadcaster = check_auth(context) diff --git a/anonstream/user.py b/anonstream/user.py index 914f48f..8530810 100644 --- a/anonstream/user.py +++ b/anonstream/user.py @@ -7,7 +7,7 @@ from math import inf from quart import current_app -from anonstream.wrappers import try_except_log, with_timestamp +from anonstream.wrappers import try_except_log, with_timestamp, get_timestamp from anonstream.helpers.user import get_default_name, get_presence, Presence from anonstream.helpers.captcha import check_captcha_digest, Answer from anonstream.helpers.tripcode import generate_tripcode @@ -136,11 +136,18 @@ def delete_tripcode(user): def see(timestamp, user): user['last']['seen'] = timestamp -@with_timestamp() -def watched(timestamp, user): +def watching(user, timestamp=None): + if timestamp is None: + timestamp = get_timestamp() user['last']['seen'] = timestamp user['last']['watching'] = timestamp +def reading(user, timestamp=None): + if timestamp is None: + timestamp = get_timestamp() + user['last']['seen'] = timestamp + user['last']['reading'] = timestamp + @with_timestamp() def get_all_users_for_websocket(timestamp): return { diff --git a/anonstream/websocket.py b/anonstream/websocket.py index 4a48132..0c21382 100644 --- a/anonstream/websocket.py +++ b/anonstream/websocket.py @@ -9,7 +9,7 @@ from quart import current_app, websocket from anonstream.stream import get_stream_title, get_stream_uptime_and_viewership from anonstream.captcha import get_random_captcha_digest_for from anonstream.chat import get_all_messages_for_websocket, add_chat_message, Rejected -from anonstream.user import get_all_users_for_websocket, see, verify, deverify, BadCaptcha, try_change_appearance +from anonstream.user import get_all_users_for_websocket, see, reading, verify, deverify, BadCaptcha, try_change_appearance from anonstream.wrappers import with_timestamp from anonstream.utils.chat import generate_nonce from anonstream.utils.websocket import parse_websocket_data, Malformed, WS @@ -75,6 +75,7 @@ async def websocket_inbound(queue, user): @with_timestamp() def handle_inbound_pong(timestamp, queue, user): print(f'[pong] {user["token"]}') + reading(user, timestamp=timestamp) user['websockets'][queue] = timestamp return None diff --git a/anonstream/wrappers.py b/anonstream/wrappers.py index 50535fb..e0552c3 100644 --- a/anonstream/wrappers.py +++ b/anonstream/wrappers.py @@ -16,13 +16,18 @@ def with_function_call(fn, *fn_args, **fn_kwargs): def with_constant(x): return with_function_call(lambda: x) -def with_timestamp(monotonic=False, precise=False): +def get_timestamp(monotonic=False, precise=False): n = 1_000_000_000 if monotonic: - fn = precise and time.monotonic or (lambda: time.monotonic_ns() // n) + timestamp = precise and time.monotonic() or time.monotonic_ns() // n else: - fn = precise and time.time or (lambda: time.time_ns() // n) - return with_function_call(fn) + timestamp = precise and time.time() or time.time_ns() // n + return timestamp + +def with_timestamp(monotonic=False, precise=False): + def get_timestamp_specific(): + return get_timestamp(monotonic=monotonic, precise=precise) + return with_function_call(get_timestamp_specific) def try_except_log(errors, exception_class): def try_except_log_specific(f):