DownVid
1000개 이상의 사이트를 지원하고, 인터페이스가 간결하며 macOS·Windows·Linux에서 모두 사용 가능
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Daily movie, TV, and book recommendations — movies and TV from your Plex library, books from your Goodreads want-to-read shelf — chosen by Gemini (on Vertex AI). This is an experiment in building an app with generative AI.
Stack: Go, Chi (routing), GORM (ORM), Postgres, Gemini on Vertex AI (google.golang.org/genai), zap + gutil/logging (JSON logs), OpenTelemetry (HTTP metrics on /metrics).
The home page shows one set of recommendations per calendar day:
ViewCount == 0).Each card shows poster, title, year, rating, genre, and runtime (movies), season count (TV), or author and page count (books). A tier's heading is omitted when it has no picks, so books simply don't appear unless Goodreads is configured.
Past days are listed at /dates (one row per distinct day, paginated).
rating and fall back to audienceRating, which is the only score Plex sets on TV shows/review/list_rss/{userID}), since the official API was retired in December 2020 and issues no new keysmetacritic.com/tv/<show>/season-N/| Method | Path | Description |
|---|---|---|
| GET | / | Today’s recommendations (UTC date) |
| GET | /date/YYYY-MM-DD | Recommendations for that day |
| GET | /dates | Paginated list of days (?page, ?size) |
| GET | /cron/recommend | Start recommendation generation (async; file lock) |
| GET | /cron/cache | Refresh Plex → Postgres cache (async; file lock) |
| GET | /stats | DB statistics |
| GET | /health | JSON health including DB ping |
| GET | /metrics | Prometheus exposition (otelhttp HTTP server metrics) |
| GET | /static/* | Embedded static files (e.g. favicon) |
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | yes | Postgres connection string, e.g. postgres://user:pass@host:5432/recommender?sslmode=disable |
PLEX_URL | yes | Plex server base URL |
PLEX_TOKEN | yes | Plex token |
GOOGLE_CLOUD_PROJECT | yes | GCP project ID (Vertex AI API enabled) |
GOOGLE_CLOUD_LOCATION | yes | Vertex AI region, e.g. us-central1 |
GOOGLE_GENAI_USE_VERTEXAI | no | true to use Vertex AI (recommended); the SDK also supports the Gemini Developer API |
GEMINI_MODEL | no | Model ID (default gemini-2.5-flash) |
GEMINI_API_KEY | no | When set, call the Gemini API with this key instead of Vertex AI, e.g. through a LiteLLM proxy set by GOOGLE_GEMINI_BASE_URL |
GOOGLE_APPLICATION_CREDENTIALS | no | Path to a service-account key for local dev; production uses ambient ADC (workload identity) |
TRAKT_CLIENT_ID | no | Trakt API app client id; enables Trakt signals |
TRAKT_CLIENT_SECRET | no | Trakt API app client secret |
TRAKT_CONNECT_TOKEN | no | Shared secret required to call GET /trakt/connect?token=…; the endpoint is disabled when unset |
ANILIST_USERNAME | no | AniList username (public list); enables AniList signals |
OMDB_API_KEY | no | OMDb key; enables Metacritic Metascores |
OMDB_BATCH_SIZE | no | OMDb lookups per cache run (default 40, sized for OMDb's free 1000/day quota) |
GOODREADS_USER_ID | no |
Authentication to Vertex AI uses Application Default Credentials — no API key. Locally, run gcloud auth application-default login or set GOOGLE_APPLICATION_CREDENTIALS.
External sources only re-rank titles you already own in Plex — they never add new titles. Each is off unless configured, and all are synced during /cron/cache.
TRAKT_CLIENT_ID/TRAKT_CLIENT_SECRET and a TRAKT_CONNECT_TOKEN, then authorize once — curl "http://localhost:8080/trakt/connect?token=$TRAKT_CONNECT_TOKEN" and enter the returned code at the Trakt URL. Tokens persist in the DB and auto-refresh.ANILIST_USERNAME (public list; no auth). Matched to owned anime by title + year.GOODREADS_USER_ID to the number in your profile URL — goodreads.com/user/show/12680-nat → 12680. This is a different namespace from the author id on /author/show/<id>, which will silently return someone else's shelves. The profile must be public; no auth or key is needed. Unlike the other sources, Goodreads doesn't just re-rank — the want-to-read shelf is the book candidate pool.OMDB_API_KEY. Metacritic has no public API, so scores come from OMDb, joined on the IMDb ID Plex already provides. Enrichment is incremental: each cache run looks up OMDB_BATCH_SIZE titles that have never been checked, so the library backfills over a few days inside the free tier. A title that comes back with a score is never re-fetched — critic scores are final once a title is released. Titles Metacritic doesn't cover are stamped too and retried only after 90 days, to catch a late score without re-spending the quota every run. A run is also time-boxed, so a slow OMDb defers titles to the next run rather than holding the shared cron lock past the point where /cron/recommend can acquire it.Signals feed genre affinity, a watchlist score boost, watched-elsewhere handling, and a short "recently loved" line in the prompt.
The Metascore is used three ways: it adds a modest term to candidate scoring (centered on 55, so an unscored title ranks the same as an average one rather than being penalized), it appears on the shortlist lines sent to Gemini, and it renders on each card as a colored badge linking to metacritic.com. That link is built from the title slug — Metacritic exposes no ID to join on — so it is only published for titles that actually have a Metascore.
recommender/
├── handlers/ # HTTP handlers and HTML templates (embedded)
├── lib/
│ ├── db/ # Migrations and GORM logger
│ ├── goodreads/ # Goodreads shelf client (RSS)
│ ├── health/ # Health check
│ ├── lock/ # File locks for cron endpoints
│ ├── metacritic/ # metacritic.com link building
│ ├── omdb/ # OMDb client (Metacritic Metascores)
│ ├── plex/ # Plex client and cache update
│ ├── recommend/ # Gemini generation, candidate scoring, and queries
│ └── validation/ # Request and response validation helpers
├── models/ # GORM models
├── static/ # Assets embedded into the binary (e.g. favicon)
└── data/ # Docker volume mount target for the DB (optional locally)
Package docs: pkg.go.dev/go.icco.me/recommender.
gcloud auth application-default login # or set GOOGLE_APPLICATION_CREDENTIALS
export DATABASE_URL=postgres://recommender:recommender@localhost:5432/recommender?sslmode=disable
export PLEX_URL=... PLEX_TOKEN=...
export GOOGLE_GENAI_USE_VERTEXAI=true GOOGLE_CLOUD_PROJECT=... GOOGLE_CLOUD_LOCATION=us-central1
go run .
Optional: POSTER_DIR=/path/to/posters. Need a local Postgres? docker compose up -d postgres.
docker compose up -d
Open http://localhost:8080. Trigger cache then recommendations:
curl -sS "http://localhost:8080/cron/cache"
curl -sS "http://localhost:8080/cron/recommend"
Logs: docker compose logs -f. Stop: docker compose down.
The compose file runs a bundled postgres:17 service (data in the pgdata volume) and mounts ./data at /data for cached posters (POSTER_DIR=/data/posters).
/cron/cache — Reads Plex libraries and stores all movies and TV shows in Postgres, including view_count, GUIDs (imdb/tmdb/tvdb), and the full genre list. Poster thumbs are stored as absolute URLs when Plex returns relative paths. External signals sync afterward, including a capped batch of OMDb Metascore lookups./cron/recommend — Skips if a successful run already exists for the UTC day. Otherwise: loads cached titles (minus anything recommended in the last 30 days), scores them (rating + novelty + Plex-derived taste affinity + Metacritic reception), takes a date-seeded diverse shortlist, asks Gemini to pick the best fits by ID with a one-line reason, slots them deterministically (comedy / action-drama / rewatch / wildcard movies + unwatched TV), caches the finalists' Plex posters locally, and replaces that day's rows in one transaction. Every attempt records a GenerationRun.A day is "done" when a GenerationRun with status ok exists for it — tracked explicitly rather than inferred from row counts, so cron never re-runs a completed day.
/cron/cache, /cron/recommend, and /metrics are unauthenticated. The cron endpoints trigger paid Gemini calls and full Plex/Trakt/AniList syncs, so anyone who can reach them can drive cost and load. Restrict /cron/* and /metrics to trusted callers at the ingress/reverse proxy (source-IP allow-list or an internal-only route); the app does not gate them itself.X-Plex-Token to the configured Plex host, so an absolute thumb URL pointing off-host cannot exfiltrate the token or be used for SSRF with credentials.gorm.ParamsFilter), so SQL is logged with placeholders and secrets like Trakt tokens never land in logs.| Goodreads numeric user id; enables the books tier |
PORT | no | HTTP port (default 8080) |
POSTER_DIR | no | Directory for locally cached Plex posters (default posters; Docker Compose uses /data/posters) |