Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@
from .telegram import start_telegram_client, stop_telegram_client
from .routers import files_router, folders_router, streaming_router, auth_router, tv_router

# Import bot to register handlers
from . import bot # noqa

settings = get_settings()

# Rate limiter - uses IP address by default
Expand Down
4 changes: 2 additions & 2 deletions backend/app/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
verify_token_payload,
get_current_user,
)
from ..telegram import tg_client
from .. import telegram

# Get limiter from main app
limiter = Limiter(key_func=get_remote_address)
Expand All @@ -40,7 +40,7 @@
async def get_bot_info_endpoint():
"""Get bot username and name for the login screen."""
try:
me = await tg_client.get_me()
me = await telegram.tg_client.get_me()
return BotInfoResponse(
username=me.username,
name=f"{me.first_name} {me.last_name or ''}".strip(),
Expand Down
11 changes: 6 additions & 5 deletions backend/app/routers/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from ..database import get_db
from ..models import File, User
from ..auth import get_current_user
from ..telegram import get_message_from_channel, tg_client
from .. import telegram
from ..telegram import get_message_from_channel
from ..streaming import stream_file as stream_file_generator

# Logger for internal debugging (not exposed to users)
Expand Down Expand Up @@ -82,7 +83,7 @@ async def stream_file(
async def file_streamer():
"""Generator that streams file chunks from Telegram MTProto."""
async for chunk in stream_file_generator(
tg_client,
telegram.tg_client,
message,
from_bytes,
until_bytes
Expand Down Expand Up @@ -149,14 +150,14 @@ async def get_thumbnail(
# Try using the file_id directly if stored (fallback)
if file.thumbnail_file_id:
try:
thumb_bytes = await tg_client.download_media(file.thumbnail_file_id, in_memory=True)
thumb_bytes = await telegram.tg_client.download_media(file.thumbnail_file_id, in_memory=True)
return Response(content=thumb_bytes.getvalue(), media_type="image/jpeg")
except Exception:
pass
Comment on lines +153 to 156

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Don’t turn fallback download failures into a fake 404.

Lines 153-156 swallow every download_media(...) failure, so client/session/API problems are silently converted into the later “Thumbnail not found” path. Please narrow this to the expected not-found case and log unexpected failures instead of pass.

🧰 Tools
🪛 Ruff (0.15.18)

[error] 155-156: try-except-pass detected, consider logging the exception

(S110)


[warning] 155-155: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routers/streaming.py` around lines 153 - 156, The thumbnail
fallback in the streaming route is swallowing all `download_media(...)`
exceptions, which turns real client/session/API errors into a misleading later
“Thumbnail not found” response. Update the exception handling in the thumbnail
retrieval logic in `streaming.py` to only suppress the expected not-found case,
and add logging for unexpected failures instead of using a bare `pass`. Keep the
fix localized around the `telegram.tg_client.download_media(...)` fallback path
and the surrounding response handling.

Source: Linters/SAST tools

raise HTTPException(status_code=404, detail="Thumbnail not found in message")

# Download thumbnail to memory
thumb_bytes = await tg_client.download_media(thumbnail.file_id, in_memory=True)
thumb_bytes = await telegram.tg_client.download_media(thumbnail.file_id, in_memory=True)

return Response(
content=thumb_bytes.getvalue(),
Expand Down Expand Up @@ -208,7 +209,7 @@ async def stream_public_file(
async def file_streamer():
"""Generator that streams file chunks from Telegram MTProto."""
async for chunk in stream_file_generator(
tg_client,
telegram.tg_client,
message,
from_bytes,
until_bytes
Expand Down
45 changes: 26 additions & 19 deletions backend/app/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,30 @@ def get_session_name(index: int) -> str:
return str(SESSION_DIR / f"bot_{index}")


# Build a pool: main token first, then any helper tokens
tokens = settings.all_bot_tokens
clients = []

for i, token in enumerate(tokens):
client = Client(
name=get_session_name(i),
api_id=settings.telegram_api_id,
api_hash=settings.telegram_api_hash,
bot_token=token,
ipv6=False,
max_concurrent_transmissions=settings.telegram_client_concurrency,
no_updates=(i > 0), # only main client receives updates
)
client.pool_index = i # custom attr for logging
clients.append(client)

# Main client — used for bot commands, message fetching, forwarding, etc.
tg_client = clients[0]
# Created in build_clients() inside the running event loop (see start_telegram_client).
clients: list[Client] = []
tg_client: Client | None = None


def build_clients() -> None:
"""Build client pool lazily so Pyrogram binds to uvicorn's event loop."""
global tg_client
if clients:
return
SESSION_DIR.mkdir(parents=True, exist_ok=True)
for i, token in enumerate(settings.all_bot_tokens):
client = Client(
name=get_session_name(i),
api_id=settings.telegram_api_id,
api_hash=settings.telegram_api_hash,
bot_token=token,
ipv6=False,
max_concurrent_transmissions=settings.telegram_client_concurrency,
no_updates=(i > 0), # only main client receives updates
)
client.pool_index = i # custom attr for logging
clients.append(client)
tg_client = clients[0]


# ── lifecycle helpers ────────────────────────────────────────────────
Expand Down Expand Up @@ -75,6 +80,8 @@ async def stop_all_clients():

async def start_telegram_client():
"""Called from app lifespan — starts the full pool."""
build_clients()
from . import bot # noqa: F401 — register handlers after client exists
await start_all_clients()


Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ services:
ports:
- "${BACKEND_PORT:-8000}:8000"
volumes:
# Persist Telegram session
- telegram_session:/app
# Persist Telegram session files only (do not mount over /app — that hides rebuilt code)
- telegram_session:/app/session
networks:
- telegram-tv-network

Expand Down