{"id":50925233,"url":"https://github.com/gizmo385/youtube-rss-manager","last_synced_at":"2026-06-16T22:02:15.853Z","repository":{"id":359918261,"uuid":"1231636528","full_name":"gizmo385/youtube-rss-manager","owner":"gizmo385","description":"Dynamic OPML generation for your YouTube subscriptions","archived":false,"fork":false,"pushed_at":"2026-05-24T04:18:26.000Z","size":147,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T06:29:02.508Z","etag":null,"topics":["home-server","homelab","rss","youtube"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gizmo385.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-07T06:31:50.000Z","updated_at":"2026-05-24T04:18:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gizmo385/youtube-rss-manager","commit_stats":null,"previous_names":["gizmo385/youtube-rss-manager"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/gizmo385/youtube-rss-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gizmo385%2Fyoutube-rss-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gizmo385%2Fyoutube-rss-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gizmo385%2Fyoutube-rss-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gizmo385%2Fyoutube-rss-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gizmo385","download_url":"https://codeload.github.com/gizmo385/youtube-rss-manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gizmo385%2Fyoutube-rss-manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34425024,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["home-server","homelab","rss","youtube"],"created_at":"2026-06-16T22:02:14.780Z","updated_at":"2026-06-16T22:02:15.847Z","avatar_url":"https://github.com/gizmo385.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# YouTube Subscriptions OPML Manager\n\nA multi-user web app that syncs YouTube subscriptions via the YouTube Data API and exposes them as categorized OPML feeds. Designed for self-hosted setups where an RSS reader like FreshRSS pulls subscription feeds on a schedule.\n\nA standalone CLI tool is also included for single-user, one-shot OPML export.\n\n## Features\n\n- OIDC login (works with any provider: Keycloak, Authentik, Authelia, etc.)\n- Per-user YouTube OAuth with support for brand/managed channels\n- Automatic subscription sync (every 6 hours) with manual trigger\n- Organize channels into user-defined categories\n- Ignore channels to exclude them from feeds\n- Per-channel, per-category, and per-user shorts filtering (cascading preference: subscription \u003e category \u003e user)\n- Token-authenticated OPML endpoints for RSS readers (`/opml/\u003ctoken\u003e/all.opml`, `/opml/\u003ctoken\u003e/\u003ccategory-slug\u003e.opml`)\n- Rotatable OPML tokens\n- Dark mode toggle\n- Channel search and YouTube topic metadata display\n\n## Prerequisites\n\n- Docker and Docker Compose\n- An OIDC provider (Keycloak, Authentik, Authelia, or any OpenID Connect-compatible identity provider)\n- A Google Cloud project with the YouTube Data API v3 enabled and a Web application OAuth client\n\n## Setup\n\n### 1. External services\n\n**OIDC provider:** Create an OIDC client (Authorization Code flow) in your identity provider. Set the valid redirect URI to `\u003cBASE_URL\u003e/auth/callback`. The provider must support OpenID Connect Discovery (a `/.well-known/openid-configuration` endpoint).\n\n**Google Cloud:**\n\n1. Go to Cloud Console \u003e Credentials \u003e Create OAuth client ID. Select **Web application** (not Desktop).\n2. Set the authorized redirect URI to `\u003cBASE_URL\u003e/auth/youtube/callback`.\n3. Enable the **YouTube Data API v3** on the same project.\n4. The `youtube.readonly` scope is classified as \"sensitive\" by Google. In Testing mode, you must add users to the OAuth consent screen's test user list (max ~100). This is fine for household use.\n\n### 2. Configure environment\n\n```bash\ncp .env.example .env\n```\n\nFill in the values:\n\n| Variable | Description |\n|---|---|\n| `POSTGRES_PASSWORD` | Database password |\n| `SESSION_SECRET` | Random string for cookie signing. Generate: `python -c \"import secrets; print(secrets.token_urlsafe(32))\"` |\n| `FERNET_KEY` | Encryption key for stored refresh tokens. Generate: `python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"` |\n| `BASE_URL` | Public URL of the app (e.g. `https://youtube-rss.example.com`) |\n| `OIDC_ISSUER` | OIDC issuer URL (e.g. `https://sso.example.com/realms/myrealm`) |\n| `OIDC_CLIENT_ID` | OIDC client ID |\n| `OIDC_CLIENT_SECRET` | OIDC client secret |\n| `YOUTUBE_CLIENT_ID` | Google OAuth client ID |\n| `YOUTUBE_CLIENT_SECRET` | Google OAuth client secret |\n\n### 3. Run\n\n```bash\ndocker compose up -d\n```\n\nThe app runs database migrations on startup automatically. It will be available on port 8000.\n\n## Usage\n\n1. Log in via your OIDC provider.\n2. Go to **Settings** and connect a YouTube account. If you manage brand channels, Google will prompt you to choose which channel's subscriptions to link.\n3. Trigger a sync from Settings (or wait for the automatic 6-hour sync).\n4. Go to **Channels** to organize subscriptions into categories, ignore channels, or toggle shorts filtering.\n5. Generate an OPML token from Settings. Add the feed URLs to your RSS reader.\n\n### OPML feed URLs\n\n- All non-ignored subscriptions: `/opml/\u003ctoken\u003e/all.opml`\n- Single category: `/opml/\u003ctoken\u003e/\u003ccategory-slug\u003e.opml`\n\nThese endpoints are unauthenticated (the token acts as the credential) so RSS readers can fetch them directly.\n\n### Shorts filtering\n\nEach channel defaults to including shorts. This can be overridden at three levels, where the most specific non-null setting wins:\n\n1. **Subscription** (per-channel) -- set from the Channels page\n2. **Category** -- set from the Categories tab\n3. **User default** -- set from the Settings page\n\nWhen shorts are excluded for a channel, the OPML feed uses a YouTube playlist URL that serves only long-form videos.\n\n## Local development\n\n```bash\ndocker compose up -d db\nuv sync --extra web\nuv run --extra web alembic upgrade head\nuv run --extra web uvicorn youtube_subs_opml.web.main:app --reload\n```\n\n`DATABASE_URL` must be set for Alembic when running outside Docker:\n\n```bash\nDATABASE_URL=postgresql+psycopg://yts:\u003cpassword\u003e@localhost:5432/yts uv run --extra web alembic upgrade head\n```\n\n## CLI\n\nA standalone CLI tool is available for one-shot OPML export without the web app:\n\n```bash\nuv sync\nuv run youtube-subs-opml\n```\n\nThis uses a separate Desktop OAuth flow and writes OPML to stdout.\n\n## Notes\n\n- Rotating `FERNET_KEY` invalidates all stored YouTube refresh tokens. Users will need to reconnect their YouTube accounts.\n- YouTube API quota is 10,000 units/day. Subscription listing costs 1 unit per page (50 subs/page), so even thousands of subscriptions across users stay well within the free tier.\n- FreshRSS supports only single-level OPML categories. The feed output is flat by design.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgizmo385%2Fyoutube-rss-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgizmo385%2Fyoutube-rss-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgizmo385%2Fyoutube-rss-manager/lists"}