diff --git a/app/api/routes/content.py b/app/api/routes/content.py index a0ed089..f765cf7 100644 --- a/app/api/routes/content.py +++ b/app/api/routes/content.py @@ -14,7 +14,9 @@ from app.core._config import CLIENT_TELEGRAM_API_KEY, CLIENT_TELEGRAM_BOT_USERNA from app.core.models.content_v3 import EncryptedContent as ECv3, ContentDerivative as CDv3, UploadSession from app.core.content.content_id import ContentId from app.core.network.dht import MetricsAggregator +import os import json +import time import uuid @@ -303,9 +305,22 @@ async def s_api_v1_content_view(request, content_address: str): from app.core._crypto.signer import Signer from app.core._secrets import hot_seed, hot_pubkey from app.core._utils.b58 import b58encode as _b58e - import time, json signer = Signer(hot_seed) - exp = int(time.time()) + 600 + # Media URLs are polled very frequently by the web client (e.g. every 5s). + # If we generate a new exp for every request, the signed URL changes every poll, + # forcing the player to reload and breaking continuous streaming. + # + # To keep URLs stable while still expiring tokens, we "bucket" exp time. + # Default behavior keeps tokens stable for ~10 minutes; can be tuned via env. + ttl_sec = int(os.getenv("STORAGE_PROXY_TOKEN_TTL_SEC", "600")) + bucket_sec = int(os.getenv("STORAGE_PROXY_TOKEN_BUCKET_SEC", str(ttl_sec))) + ttl_sec = max(1, ttl_sec) + bucket_sec = max(1, bucket_sec) + now = int(time.time()) + exp_base = now + ttl_sec + # Always move to the next bucket boundary so the token doesn't flip immediately + # after a boundary due to rounding edge cases. + exp = ((exp_base // bucket_sec) + 1) * bucket_sec uid = int(user_id or 0) payload = {'hash': hash_value, 'scope': scope, 'exp': exp, 'uid': uid} blob = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() diff --git a/app/core/background/indexer_service.py b/app/core/background/indexer_service.py index d9370b7..921758c 100644 --- a/app/core/background/indexer_service.py +++ b/app/core/background/indexer_service.py @@ -270,7 +270,20 @@ async def indexer_loop(memory, platform_found: bool, seqno: int) -> [bool, int]: user = await session.get(User, user_wallet_connection.user_id) if user: - user_uploader_wrapper = Wrapped_CBotChat(memory._telegram_bot, chat_id=user.telegram_id, user=user, db_session=session) + # Notify user about indexed content via client bot (main UX bot), + # but keep ability to clean up uploader-bot hint messages. + user_client_wrapper = Wrapped_CBotChat( + memory._client_telegram_bot, + chat_id=user.telegram_id, + user=user, + db_session=session, + ) + user_uploader_wrapper = Wrapped_CBotChat( + memory._telegram_bot, + chat_id=user.telegram_id, + user=user, + db_session=session, + ) ref_id = (user.meta or {}).get('ref_id') if not ref_id: ref_id = user.ensure_ref_id() @@ -281,12 +294,12 @@ async def indexer_loop(memory, platform_found: bool, seqno: int) -> [bool, int]: item_index=item_index, ) - await user_uploader_wrapper.send_message( + await user_client_wrapper.send_message( message_text, message_type='notification' ) - await user_uploader_wrapper.send_content( + await user_client_wrapper.send_content( session, encrypted_stored_content ) @@ -301,7 +314,11 @@ async def indexer_loop(memory, platform_found: bool, seqno: int) -> [bool, int]: ) )) for hint_message in result.scalars().all(): - await user_uploader_wrapper.delete_message(hint_message.message_id) + # Delete the hint with the bot that originally sent it. + if hint_message.bot_id == user_client_wrapper.bot_id: + await user_client_wrapper.delete_message(hint_message.message_id) + elif hint_message.bot_id == user_uploader_wrapper.bot_id: + await user_uploader_wrapper.delete_message(hint_message.message_id) except BaseException as e: make_log("Indexer", f"Error while deleting hint messages: {e}" + '\n' + traceback.format_exc(), level="error") elif encrypted_stored_content.type.startswith('onchain') and encrypted_stored_content.onchain_index == item_index: