尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Burn down YouTube subscription feed in chronological order.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
A purely client-side, single-page web app that turns your YouTube subscription feed into a chronological queue that plays in succession.檢視完整 README ↗Live app: https://branets-dasha.github.io/youtube-queue-app/
A purely client-side, single-page web app that turns your YouTube subscription feed into a chronological queue that plays in succession.
Features:
Built by Claude. This app was designed and built end to end by Claude (Anthropic's Claude Code, Opus) — from the initial architecture through every feature, refactor, and bug fix — working at its owner's direction. The whole codebase is Claude's work.
Anyone can use this app hosted at https://branets-dasha.github.io/youtube-queue-app/ or host it wherever they please.
No backend. No framework. No build step. No bundler. Plain HTML, CSS, and ES modules.
The only external network resources are:
https://accounts.google.com/gsi/client).https://www.googleapis.com/youtube/v3), for loading subscriptions and enabling "like" button.https://www.youtube.com/iframe_api), for the on-page player.To use the app you need to provide your own Google OAuth Client ID (see below). Your OAuth access token lives in memory only — it is never written to localStorage or IndexedDB. There is no client secret and no API key anywhere; the OAuth token authorizes every call.
The OAuth scope is https://www.googleapis.com/auth/youtube.force-ssl ("see, edit, and permanently delete your YouTube videos, ratings, comments and captions"). Such a permissive scope is needed to enable the "like" button. All the other operations the app does using YouTube Data API are read-only.
The app runs entirely in your browser. It fetches your data using the YouTube Data API. The API needs to know which app (client) is contacting it — to count request quotas and other things. I personally use a test Client ID that does not have the capacity to support many users, so each user brings their own Client ID from their own free Google Cloud project. The setup is a bit complicated, but you only need to do it once.
https://www.googleapis.com/auth/youtube.force-ssl — it covers reading your subscriptions/uploads and liking videos from the player.Application type: Web application.
Under Authorized JavaScript origins, add exactly:
https://branets-dasha.github.io
You do not need to set an Authorized redirect URI for the token model.
1234567890-abcdefg.apps.googleusercontent.com).The embedded video player is a cross-origin YouTube iframe — it's a window into youtube.com from the app page. If tracking prevention is enabled (in modern browsers it's often on by default), the browser will not send cookies belonging to youtube.com to that iframe. It means that the player will not recognize your YouTube login, and will run signed-out, showing ads even if you have YouTube Premium.
Disable tracking prevention for the app's page to connect the player to your YouTube account. This step is optional — playback works without it. It only affects account-connected / Premium (ad-free) playback. Note that connecting the player does not make your watches count toward a video's public view count — see Limitations. To connect the player, allow YouTube's third-party cookies for the app:
https://branets-dasha.github.io — via the site-info / shield icon in the address bar, or Settings → Privacy, search, and services → Tracking prevention → Exceptions. Alternatively, allow-list [*.]youtube.com under cookies.[*.]youtube.com.This is separate from signing into the app. Signing in with Google OAuth only authorizes the Data API (your subscriptions, marking, likes) — it does not sign the player in. Whether the player recognizes your account and Premium depends entirely on whether your browser lets the youtube.com iframe use its cookies as a third party.
Each YouTube Data API call spends quota units associated with your Client. The default daily quota is 10,000 units. If you hit it you'll see a friendly "daily quota reached" message; it resets at midnight Pacific time.
The app is deliberately quota-frugal. It uses the following methods:
subscriptions.list — gets a list of subscribed channels. 1 unit per page (page size is 50 results max).playlistItems.list — gets channel videos. 1 unit per page, requested per channel, paging stops once it reaches the cutoff point (for "Refresh all") or the point of the last saved video + 6h buffer (for "Fetch new"). Upload playlist ids are derived cheaply by replacing the leading UC of a channel id with UU (no extra call); the rare non-UC channel costs one additional channels.list request. Adding a playlist to the stash pages through it too (1 unit per 50 videos), plus one playlists.list for its title.videos.list?part=contentDetails,status,snippet — backfills video durations, descriptions and embeddability, and on "Refresh all" confirms that a video its channel's listing no longer returned is really gone before deleting it. Batched ≤ 50 ids per call, 1 unit each.videos.rate (the Like button) — roughly 50 units per like/unlike.localStorage:
yqa_client_id — your OAuth Client ID.yqa_start_cutoff — the floor (deletion/fetch boundary).yqa_cutoff — the live handled-prefix cutoff marker.yqa_channels — channel id → { title, avatarUrl } map for avatars.yqa_channel_prefs — channel id → { ignored?, speed? } from the Channels page (absent when nothing is set).yqa_playback_speed — the current player speed.yqa_default_speed — the "Default speed" setting (absent when off).yqa_hide_marked — the "Hide skipped" toggle.yqa, keyed by videoId, in two object stores — videos and stash. IndexedDB is required — if it can't be opened the app stops with an error.To reset, use Change Client ID / Change cutoff in the toolbar, or clear the site's storage in your browser dev tools.
isShort flag, so the "Shorts" badge can be wrong for edge cases.\ to put focus inside the frame on purpose: YouTube's own keys then work at once (c toggles captions), and Tab reaches its settings menu — caption language, audio track, quality. Shift+Tab brings you back from any of its controls; straight after \, press Tab once first (Tab forward is a long walk through the related-videos drawer). The app's shortcuts are off while you're inside.There is no build step — it's static HTML, CSS, and ES modules — but OAuth via Google Identity Services will not work if you open index.html directly as a file:// URL. You must serve the folder over http://localhost.
Pick one (run from inside this project directory):
# Python 3
python -m http.server 5173
# or Node
npx serve -l 5173
# or
npx http-server -p 5173
Then open:
http://localhost:5173
For local development, register your localhost origin instead of the hosted one: in your OAuth client's Authorized JavaScript origins, add http://localhost:5173 (or whichever port you serve on — localhost is a valid OAuth origin). Make sure the port matches. You can register both the localhost origin and the https://branets-dasha.github.io origin on the same Client ID.
| File | Purpose |
|---|---|
index.html | DOM skeleton + loads GIS, the IFrame API (via player.js), and js/subscriptions-page.js |
stash.html | DOM skeleton for the stash page; loads GIS, the IFrame API (via player.js), and js/stash-page.js |
channels.html | Standalone channel-list page (per-channel prefs); loads js/channels-page.js |
styles.css | Light/dark, responsive two-pane styling; player container query |
README.md | This file |
js/config.js | Constants (API base, OAuth scope, storage keys, IndexedDB names, limits) |
js/store.js | Persistence: IndexedDB video CRUD + localStorage for id/cutoff/settings/channels |
js/migrations.js | All data migrations, in one place (records, channel prefs, localStorage keys) |
js/migrations.test.mjs | Node unit tests for js/migrations.js (run: node js/migrations.test.mjs) |
js/auth.js | GIS token client: init, request/refresh/revoke, in-memory token |
js/api.js | YouTube fetch: subscriptions, uploads, videos.list details, videos.rate; errors |
js/queue.js | PURE queue logic (no browser globals; Node-importable & unit-testable) |
js/queue.test.mjs | Node unit tests for js/queue.js (run: node js/queue.test.mjs) |
js/player.js | YouTube IFrame Player API wrapper (load/play, speed, seek, resume, fullscreen) |
js/ui.js | XSS-safe DOM building (cards, player meta) and state screens |
js/toast.js | Top-right toast notifications (progress / success / error / info) |
js/page-chrome.js | Chrome shared by every page: tab lock, fatal-storage screens, curtain, iframe focus guard |
js/subscriptions-page.js | Wiring: auth → fetch → store → queue → ui/player, event binding, shortcuts |
js/stash-page.js | Stash page wiring: add by link, store → queue → ui/player, event binding, shortcuts |
js/queue.js references no browser globals, so it is imported directly by the test file. Run the suite from the repo root:
node js/queue.test.mjs
node js/migrations.test.mjs
js/channels-page.js | Channels page wiring: channel rows + ignore/speed prefs (no auth, no API) |