Skip to content

[pull] main from mhdzumair:main#267

Merged
pull[bot] merged 15 commits into
geek-cookbook:mainfrom
mhdzumair:main
May 31, 2026
Merged

[pull] main from mhdzumair:main#267
pull[bot] merged 15 commits into
geek-cookbook:mainfrom
mhdzumair:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 31, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

mhdzumair added 15 commits May 30, 2026 10:10
…nd advanced import

- Implement list_downloaded_torrents for all debrid providers (Premiumize,
  OffCloud, PikPak, Seedr) alongside existing RD/TorBox/AllDebrid/Debrid-Link
- Parse torrent titles with PTT; detect movie/series/sports and match against
  the media DB to populate matched_title + external_ids (imdb/tmdb/tvdb)
- Add dedicated Formula/MotoGP racing parser: series title "{league} {event}
  {year}" with the session (Qualifying/Race/Sprint) as the episode
- Surface detected sports_category in the missing-torrents response so the UI
  can preselect the category for quick import
- Rewrite advanced import to match the frontend payload (advanced_imports);
  create/resolve media (sports stubs as series for F1/MotoGP, movie otherwise),
  link streams, per-file episode metadata, languages, and catalogs
…for racing

Frontend:
- Add sports_category to MissingTorrentItem so the detected category preselects
  in the Edit Metadata panel and Advanced Import dialog
- Show the file/episode annotation button for racing-series sports
  (formula_racing / motogp_racing), not just movie/series
- Carry episode_title / release_date through the advanced-import file payload

Backend:
- Accept episode_title / release_date on advanced-import file entries
- Populate series_metadata / season / episode rows during advanced import so
  series (incl. F1/MotoGP) list their episodes on the detail page, using the
  annotated episode names
…ts response

- Derive the racing session name (e.g. "Qualifying") per video file from its
  filename and include it as files[].episode_title for F1/MotoGP torrents
- Frontend: thread episode_title through MissingTorrentFile → TorrentFile so the
  annotation dialog prefills the episode title for racing series
- Add test for dotted filename with extension
…tine

Fixes a NOT-NULL violation (is_remastered/is_upscaled/is_subbed) in the
debrid advanced import, which used an incomplete stream insert.

- Add import_helpers::persist_torrent_import — the single routine that writes a
  torrent stream + torrent_stream row (all columns), links media, trackers,
  languages, audio/HDR/channel extras, per-file metadata (with season/episode
  links), catalogs, and series season/episode metadata
- Add import_helpers::insert_torrent_stream_row and ensure_series_episode_metadata
- Route watchlist advanced import, content magnet import, and content torrent
  file import through the shared routine; remove the duplicated per-path inserts
- Content imports now also populate series season/episode metadata

As a result, series (incl. F1/MotoGP) consistently list episodes on the detail
page regardless of import route.
…mport analysis

Episode organization (applies to any non-IMDb/user-created series during import):
- Racing (F1/MotoGP) sessions map to canonical fixed slots: FP1, FP2/Sprint
  Qualifying, FP3/Sprint, Qualifying, Race — stable across re-imports
- Files with a detectable date are ordered chronologically; dates already present
  reuse their episode number to align with existing media
- Remaining files are numbered after the current max, by filename order
- Files with explicit episode numbers and provider-backed series are left untouched
- Runs inside persist_torrent_import, so every import route benefits

Quick import (POST /watchlist/{provider}/import) rewritten:
- Accept the real payload (info_hashes + overrides + anon fields) instead of the
  stale {items} shape
- Re-fetch torrent files from the provider, match movies/series against the
  metadata DB for an external id, create stubs for sports, then persist via the
  shared routine (with episode organization)

Add parser::racing_session_episode and import_helpers::{extract_iso_date,
organize_user_series_episodes}; remove the now-unused upsert_debrid_torrent.
Two silent failures meant imported sports/user-created series showed no episodes
and no catalog:

- series_metadata/season/episode IDs are `integer` (i32), but the inserts decoded
  RETURNING id as i64 — a runtime decode error that was swallowed, so series_id
  came back None and episode creation bailed out. Fixed the column types.
- catalog has NOT NULL `is_system`/`display_order` with no default; the catalog
  upsert only set `name`, so it errored (silently). Now sets is_system=false,
  display_order=0.

Also surface these failures via tracing::warn instead of swallowing them.
Implements the full Telegram contribution bot in src/bot/ (18 modules),
fixes the broken MTProto session loader, and wires per-user channels.

Bot (src/bot/):
- Webhook dispatcher routes messages and callbacks to typed handlers
- /start /help /login /status /cancel /scrape commands
- Multi-step wizard: detect content type → media type → analyze →
  match selection (inline keyboard) → metadata review (edit
  resolution/quality/codec) → confirm import
- All content types: magnet, torrent file/URL, NZB, YouTube, HTTP,
  AceStream, forwarded video, sports
- Anonymous display name prompt for contribute_anonymously users
- Batch mode: forward multiple files, per-item review buttons,
  "Set as TV Series" auto-assigns series/season/episode from filenames,
  Import All for bulk ingestion
- Login token generation (consumed by existing telegram_login endpoint)
- Redis-backed conversation/batch state with 30-min / 24-h TTLs
- Callback payload caching for 64-byte Telegram limit
- Backup channel copy via TELEGRAM_BACKUP_CHANNEL_ID

Wiring:
- telegram_webhook.rs: parses Update, spawns dispatch, fast-acks
- register_commands (setMyCommands) on API startup
- All imports go through process_contribution_import pipeline
  (contribution record, auto-approve, points, moderator notify)

Session fix (src/scrapers/telegram.rs + src/util/telegram_session.rs):
- Replaces non-functional bincode_deserialize stub with real
  Telethon StringSession parser (dc_id, ip, port, auth_key extraction)
- init_client fails loudly when session is unauthenticated instead of
  silently scraping nothing
- New binary src/bin/telegram_session.rs for session conversion

Per-user channels (src/db/telegram_channels.rs + orchestrator.rs):
- Reads enabled channels from profile tgc config
- Request-time and background scrapers both pass per-user channels

Import handler refactors:
- torrent_import: pub analyze_magnet_for_bot, analyze_torrent_bytes,
  extract_info_hash_from_magnet
- youtube_import: pub analyze_youtube_for_bot
- nzb_import: pub analyze_nzb_url_for_bot
- http_import: pub analyze_http_for_bot
- acestream_import: pub analyze_acestream_for_bot

Config: adds telegram_backup_channel_id (TELEGRAM_BACKUP_CHANNEL_ID)
…acker

Tier 1 — SQL type/enum hard errors (broken endpoints):
- watch_history.rs: cast action param as ::watchaction in dynamic WHERE
- user_library.rs: decode RETURNING id/media_id as i32 (INT4 not INT8)
- rss.rs: decode rss_feed.id as i32 in user_update_rss_feed check
- content/streams.rs: SELECT stream_type::text to decode streamtype enum
- content/user_metadata.rs: fix ::media_type_enum → ::mediatype; fix
  series_id/season_id/genre_id/media_id/creator_id to i32; add missing
  mediatype cast in list query too
- admin_scrapers.rs: remove invalid ON CONFLICT on UPDATE; guard with
  NOT EXISTS subquery; bind ids as i32

Tier 2 — Data integrity bugs & panic:
- db/streams.rs: supply required NOT-NULL cols in file_media_link INSERT
  (is_primary, confidence, link_source, created_at)
- ptt/engine.rs: use floor_char_boundary for all byte-index string slices
  to prevent scraper task panics on multibyte UTF-8 titles
- scrapers/persist.rs: strip NUL bytes (0x00) from usenet stream text
  fields before Postgres insert; add strip_nul() helper

Tier 3 — HTTP 414 / oversized GET requests:
- premiumize.rs: batch check_cached into chunks of 80 hashes per request
- stremthru.rs: batch check_cached into chunks of 80 hashes per request

Tier 4 — Log noise reduction:
- providers/mod.rs: treat reqwest decode errors as transient (→ debug),
  collapsing 138k playback.rs warnings
- torrents/torbox.rs: check HTTP status before JSON decode; downgrade
  checkcached decode failure to debug, collapsing 45k warnings
- routes/poster.rs: downgrade upstream 404 and annotate-failed to debug
- jobs/runner.rs: use PgListener::connect() (dedicated standalone conn)
  instead of connect_with(&pool) to prevent NotificationResponse on
  shared pool query connections
Instead of silently propagating reqwest's opaque "error decoding response
body" error, add a shared response_json() helper in providers/mod.rs that
reads the response as text first, then parses JSON, and warns with the
HTTP status + truncated body preview (up to 500 chars) on failure.

This replaces the previous approach of treating decode errors as transient
which hid the problem. Now every decode failure produces a WARN with the
exact response from the debrid service (rate-limit page, HTML 502,
plain-text error, etc.) so the root cause is visible in the exception
tracker. Also reverts the providers/mod.rs is_decode() change.

Applied to all provider HTTP helpers: rd_get/rd_post/get_access_token
(realdebrid), tb_get/tb_post_form/tb_post_json/checkcached (torbox),
ad_get/ad_post_form/magnet-status (alldebrid), pm_post_form (premiumize),
st_get/st_post/magnets-check (stremthru), oc_get/oc_post_form/cache
(offcloud), dl_get/dl_post/refresh-token/seedbox-list (debridlink),
link-generate/link-lookup (easydebrid).
Root cause of recurring i64/INT4 and enum-cast runtime errors:
- ~1,124 runtime (unchecked) sqlx queries vs 13 compile-time macros
- 240 i64 fields where schema has zero bigint id/FK columns (all INT4)
- 14 Postgres enums handled as String + 112/216 hand-written SQL casts
- CI had no Postgres, so type drift was never caught at build time

This commit establishes the foundation that makes recurrence structurally
harder and detectable at compile/CI time:

src/db/types.rs (new)
  - All 14 Postgres enum types as native Rust enums with #[derive(sqlx::Type)]
    and #[sqlx(type_name=...)] — bind/decode without any ::enumname or ::text
    cast; wrong variant is a compile error
  - 10 core ID newtypes (MediaId, StreamId, UserId, ProfileId, StreamFileId,
    SeriesId, SeasonId, EpisodeId, GenreId, IntegrationId) with #[sqlx(transparent)]
    forcing i32; documents which 8 columns are legitimately i64
  - Re-exported from src/db/mod.rs for codebase-wide use
  - Cargo.toml: add explicit "macros" feature to sqlx

.github/workflows/ci.yml
  - Add postgres:17-alpine service to rust-build job
  - Install sqlx-cli; run migrations; run `cargo sqlx prepare --check`
    → CI now fails if any query! macro drifts from the real schema
  - Build and test with SQLX_OFFLINE=true + TEST_DATABASE_URL wired in

.githooks/pre-commit (new, version-controlled)
  - Adds cargo check (SQLX_OFFLINE=true) for staged Rust files, alongside
    existing ruff/prettier/eslint checks
  - Session already updated via `make install-hooks`

Makefile: add `install-hooks` target
AGENTS.md: fix false "all queries compile-time validated" claim;
  document INT4/i64 rules, enum type rules, and developer workflow

Next step: migrate static queries in src/db/ layer to query_as! macros
using the new types (requires `cargo sqlx prepare` against a live DB).
issues

Main changes (Cursor migration + manual fixes):
- Replace all string-cast WHERE comparisons (type::text = 'MOVIE',
  status::text = $N,
  format!("m.type::text = '{...}'")) with native enum binds — no cast,
  no injection risk
- Convert db/meta.rs Internal branch to query_as! macro (compile-time
  SQL verification,
  mirrors External branch; collapses duplicated base_sql)
- Port contribution bot handlers (bot/) to Rust with full parity to
  Python
- Update all DB query result types to use MediaId/UserId/MediaType
  newtypes so wrong
  types cause compile errors rather than silent runtime failures
- Add 13 new .sqlx/ entries for offline SQLX compile-time query
  verification
- Fix configure.rs: preserve secret_str through redirect to
  /app/configure?secret_str=
- Fix metrics_middleware.rs: RAII InFlightGuard prevents stuck in-flight
  counter on
  client disconnect / future cancellation
- Fix easynews.rs: 401 short-circuits all remaining queries
  (user-specific cred failure)
- Fix torbox_search.rs: 429 skips title query and returns early
  (per-token rate limit)
- Fix public_indexer_registry.rs: sort non-CF indexers first so byparr
  sites run last
- Disable CF bypass (byparr) in live API fan_out — Chromium sessions
  only in bg workers
- Add source health gate (NZBIndex + Binsearch) — 429s record failures;
  gate blocks
  further calls once success rate falls below threshold
- Fix /metrics column bug (media_type → type); replace COUNT(*) full
  table scans on
  large stream tables with pg_stat_user_tables n_live_tup estimates
- Remove unauthenticated /metrics root route; keep /api/v1/metrics under
  api_key_middleware
- Log Zilean response body on JSON parse failure (was silently dropping
  error context)
- Add Grafana + Prometheus dev stack (docker-compose-grafana-dev.yml,
  prometheus-dev.yml)
Switch checkcached to the POST batch endpoint for larger hash lists and return requestdl URLs with redirect=true so playback skips a server-side JSON round trip.
Load stored torrent bytes from the DB and submit them to TorBox when available, falling back to magnet links for streams without a stored file.
…arity

Store torrent_type and torrent_file from Prowlarr/Jackett/RSS scrapes, link
announce trackers, gate catalog streams by provider, and prefer file upload
on debrid playback. Fix feed-job privacy bypass, download URL fallback,
flag parsing, upload corruption, and bounded torrent fetches.
@pull pull Bot locked and limited conversation to collaborators May 31, 2026
@pull pull Bot added the ⤵️ pull label May 31, 2026
@pull pull Bot merged commit b28750f into geek-cookbook:main May 31, 2026
1 check passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant