Skip to content

feat: download members-only videos when cookies have access - #1008

Open
Joloxx9 wants to merge 4 commits into
DialmasterOrg:devfrom
Joloxx9:feat/members-only-access
Open

Joloxx9 wants to merge 4 commits into
DialmasterOrg:devfrom
Joloxx9:feat/members-only-access

Conversation

@Joloxx9

@Joloxx9 Joloxx9 commented Nov 7, 2026 •

Copy link
Copy Markdown

Refs #857

Lets Youtarr download members-only videos when the account in the configured cookies is a member, while keeping today's behavior without cookies. It is built around the review points in #857 and on #851 (I've been running a simpler version of this in my own build and wanted the gaps you listed closed). Happy to fold this into #851 instead if @Agile86 is still working on it.

How it works

Access records (video_members_access, server/modules/membersAccess.js): one row per members-only video with access (confirmed/denied), cookie_key (SHA-256 of the cookie file) and checked_at.

  • A verdict counts for 24 hours and only under the same cookie key. Replacing or editing the cookie file (external files included) retires every verdict without a write; settings saves and restarts keep them.
  • The cookie key is computed once per request/run and memoized by path, size and mtime, never per row, and no Python process is started for it (external files go through readExternalCookies).

What counts as a denial: only a "Join this channel" refusal from a run where yt-dlp really got --cookies (spawnYtDlp reports cookiesPassed; preparation drops the flag for an unusable file), the file holds a live login cookie (cookieDetails), and no cookies are no longer valid / rotated warning was printed. Timeouts, bot checks, network errors, anonymous auto-retries, logged-out or expired files and rejected cookies record nothing about membership.

Ids: only [A-Za-z0-9_-]{11} ids are stored or put into the filter, and the filter checks again, since ids come from yt-dlp output an uploader can shape with a title.

Recovery: any successful yt-dlp read or download of a members-only video records confirmed, including after an earlier denial (validation, modal metadata fetch, downloads). A successful read showing another availability deletes the row and resets the channelvideos availability, so a video that went public stops reading as members-only.

Downloads

  • No cookies: availability!=subscriber_only stays in every match filter, exactly as today.
  • Cookies: scheduled channel sweeps add id!~='^(?:id1|id2)$' for fresh denials (your suggestion on feat: support members-only video downloads when cookies are enabled #851), so a denied video is skipped at the flat-listing stage without another request. Checked against yt-dlp 2026.10.26's match_filter_func.
  • URL-list jobs never rely on that filter, because yt-dlp checks it on a video URL only after fetching it: playlist jobs (discoveries and retries) drop fresh denials before queuing, and Download All already did.
  • Manual downloads clear the requested videos' verdicts and always ask again.
  • External API requests keep availability!=subscriber_only, so an External API key never uses the owner's membership. Legacy API-key jobs behave like manual downloads.
  • Download All's preview mirrors the same rule.

Listings and UI

  • Channel and playlist listings return members_access (no_cookies | unchecked | confirmed | denied) for every video known to be members-only. A recorded check also covers videos from untracked channels on playlist pages.
  • One selection rule (isMembersOnlyBlocked in videoStatus.tsx) and one chip (shared/MembersAccessChip) with the same colors on both pages. A local file, or its removal, takes priority over membership.
  • The video modal checks access when it fetches metadata, offers Check access for an unchecked video (a cached .info.json is never re-fetched) and Check again for a denied one (POST /api/videos/:youtubeId/members-access/check, 10/min). A null answer means the video is no longer members-only. The channel and playlist pages both update the row from the modal's answer.
  • Manual URL checks explain no cookies, no membership and unusable cookies separately; a members-only video the cookies can open gets the members chip in the list.
  • Playlist listings stay anonymous; no change to playlist fetching or pruning.

Cookie settings text, docs/CONFIG.md, the playlist texts in the app and docs/USAGE_GUIDE.md / docs/YOUTARR_VS_ALTERNATIVES.md describe the behavior; Swagger covers the new fields on /getchannelvideos, /api/videos/{id}/metadata and /api/checkYoutubeVideoURL; docs/DATABASE.md, CLAUDE.md and the rule files are updated.

Testing

  • Backend: 248 suites / 6929 tests pass. Frontend: 303 suites / 5631 tests pass. npm run lint and npm run lint:ts are clean.
  • New tests: membersAccess.test.js (including the denied-list query: denied only, same cookie key, under 24 hours; crafted ids), membersAccessState.test.js (the real isBlockedState, shared by the Jest mock), scheduled channel runs passing the denial and no-cookies filters, playlist denial pre-filtering, External API jobs, cookiesPassed/usedCookies, the check route, validation/metadata/finalizer paths, listing fields, the members chip and checkbox in the card, list and table views, MembersAccessChip, useMembersAccessCheck, the modal check flow (including an inline parent callback that sets state) and the shared selection rule.
  • Not run here: the migration against a real MariaDB (no Docker in my environment), and npm run docs:check (website deps not installed; no links were added).

🤖 Generated with Claude Code

Joloxx9 and others added 2 commits November 7, 2026 13:46
With cookies configured, members-only videos are attempted like any other
video, and Youtarr remembers whether the account in the cookies could open
each one. Without cookies nothing changes: downloads keep filtering
members-only videos out.

- video_members_access stores one verdict per video (confirmed or denied)
  with the SHA-256 of the cookie file it was made with. A verdict counts for
  24 hours and only while the same cookies are configured, so replacing or
  editing the file (external files included) retires every verdict, while
  settings saves and restarts keep them.
- Only a "Join this channel" refusal from a run that passed the cookies
  records a denial. Timeouts, bot checks, anonymous auto-retries and cookies
  YouTube rejected ("cookies are no longer valid", rotated) record nothing
  about membership.
- Every successful yt-dlp read or download of a members-only video records
  confirmed access, including after an earlier denial.
- Scheduled channel sweeps, playlist downloads, retries and Download All
  leave fresh denials out through the yt-dlp match filter (id!~=), so a
  denied video is not requested again for 24 hours. Manual downloads clear
  the requested videos' verdicts and always ask again.
- Channel and playlist listings return members_access (no_cookies,
  unchecked, confirmed, denied) for every video known to be members-only,
  including videos of untracked channels, with one cookie-key lookup per
  request. Both pages share one selection rule (isMembersOnlyBlocked) and
  one chip; a local file or its removal takes priority over membership.
- The video modal checks access when it opens an unchecked video and offers
  "Check again" for a denied one (POST /api/videos/:id/members-access/check,
  rate-limited). Manual URL checks explain no cookies, no membership and
  rejected cookies separately.

Refs DialmasterOrg#857

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dialmaster dialmaster left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for submitting this PR. You addressed a lot of the gaps from #851.

There are still some things to fix before this can go in. I left inline comments on each one.

A few things are in files the PR doesn't touch, so I couldn't comment on them inline:

  • These still say members-only videos can't be downloaded:
    • client/src/components/PlaylistPage.tsx line 473
    • client/src/components/Subscriptions/PlaylistHelpDialog.tsx line 75
    • client/src/utils/playlistCounts.ts line 11
    • docs/USAGE_GUIDE.md line 214
    • docs/YOUTARR_VS_ALTERNATIVES.md line 94
  • Swagger needs the new fields on /getchannelvideos, /api/videos/{id}/metadata and /api/checkYoutubeVideoURL.
  • The playlist page doesn't pass onAvailabilityDetected, so a row doesn't update after a check in the modal.

Can you also add tests for these? Right now they could break without any test failing:

  • the query behind the denied list (denied only, same cookie key, under 24 hours)
  • scheduled channel runs passing the denial filter and the no-cookies filter to yt-dlp
  • the real isBlockedState (the shared mock has its own copy)
  • the members-only chip and checkbox in the channel views

Comment thread server/modules/membersAccess.js Resolved
Comment thread server/modules/membersAccess.js Resolved
Comment thread server/modules/membersAccess.js Resolved
Comment thread server/modules/downloadModule.js Resolved
Comment thread server/modules/downloadModule.js Resolved
Comment thread server/modules/playlistModule.js Resolved
Comment thread client/src/utils/videoStatus.tsx Resolved
@BigFlubba BigFlubba added the enhancement New feature or request label Nov 7, 2026
- Only 11-character video ids are stored or put in the yt-dlp filter.
- A denial needs yt-dlp to really get --cookies (spawnYtDlp reports
  cookiesPassed) and a live login cookie in the file.
- Playlist jobs drop fresh denials before queuing instead of relying on
  the match filter, which yt-dlp checks only after fetching a video URL.
- External API requests keep members-only videos out.
- A successful read of a video that is no longer members-only forgets it;
  the modal treats a null check answer as not members-only.
- The modal notifies the parent once per check answer (no render loop),
  offers Check access for unchecked videos, and the playlist page passes
  onAvailabilityDetected.
- Playlist rows: file status comes before the members-only rule.
- Manual download list shows the members chip for openable videos.
- Members-only UUM playlists are listed anonymously again.
- Swagger fields, stale "cannot be downloaded" texts, and tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Joloxx9

Joloxx9 commented Nov 7, 2026

Copy link
Copy Markdown
Author

Thanks for the thorough review. Everything is in 02ff63b:

  • Stale text: the PlaylistPage note, PlaylistHelpDialog, playlistCounts.ts, docs/USAGE_GUIDE.md and docs/YOUTARR_VS_ALTERNATIVES.md no longer say members-only videos cannot be downloaded.
  • Swagger: members_access/availability on /getchannelvideos, membersAccess on /api/videos/{id}/metadata, and isMembersOnly/membersAccess/errorCode on /api/checkYoutubeVideoURL.
  • Playlist page: now passes onAvailabilityDetected, so a row updates after a check in the modal (tested).
  • Tests:
    • The denied-list query: denied only, same cookie key, cutoff at 24 hours.
    • Scheduled channel runs passing the denial filter and the no-cookies filter to getBaseCommandArgs.
    • The real isBlockedState, moved to membersAccessState.js, which the shared mock now imports instead of copying.
    • The members chip and checkbox in the card, list and table views.

Backend 6929 / frontend 5631 tests pass; lint and typecheck are clean.

@dialmaster dialmaster left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for addressing my first review so quickly! I resolved all the existing comments, but when I re-reviewed I found a few more issues. See my new comments.

Comment thread server/modules/membersAccess.js Resolved
Comment thread server/modules/downloadModule.js Resolved
- Downloads keep availability!=subscriber_only when the cookie file has
  no live login or cannot be read, since such refusals never record a
  denial and every run would retry them.
- Auto-retries of External API requests carry an externalRequest flag,
  so they keep the members-only filter despite their Auto-retry label.
- The members-only overlay offers Check again without cookies too, so a
  video that went public can be cleared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Joloxx9

Joloxx9 commented Nov 10, 2026

Copy link
Copy Markdown
Author

Thanks for the second look! All three comments are addressed in 7a1763a, each with a test, and docs/CONFIG.md now covers the expired-cookies case. Ready for another review.

@dialmaster dialmaster left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks again for the additional changes. Everything I raised in the last round was addressed, but I thought about this a bit more and added a few more comments.

Also, a few comments for files that are not in the diff that need a line each:

  • docs/EXTERNAL_API.md ("Create requests"): External API requests never use the owner's membership, so members-only videos are skipped. Only CONFIG.md says this right now
  • docs/API_INTEGRATION.md (POST /api/videos/download): downloads with a legacy API key can now get members-only videos using the configured cookies. That's a change for anyone who has given out a key.

Can you add those too?

Once that's done... I think we've covered everything and this will be good to merge :)

<Typography variant="body2" className="text-muted-foreground">
Cookies let yt-dlp act as a signed-in YouTube account, which can get past
"Sign in to confirm you're not a bot" checks.
"Sign in to confirm you're not a bot" checks. They also unlock members-only

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This text now gives people a reason to use cookies from their real account, since that's usually the one with the membership. But YouTube can restrict or ban accounts that are used for automated downloads, and we don't say that anywhere here. The card right below says "use a throwaway account", but only because the file gives access to the account.

Can you add a sentence here saying that using a main account is at the user's own risk, with a link to the yt-dlp warning? https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies

I think the same note should go in the "Members-Only Videos" section of docs/CONFIG.md, and the warning in CookieFileCard.tsx should mention the ban risk too so they all say the same thing.

Comment thread docs/CONFIG.md
channel, or add it manually: a manual download always asks again.
- Nothing is recorded about membership when the cookies could not really be
used: the file has no signed-in YouTube session, its login cookies have
expired, YouTube rejected them (rotated), or the file could not be read for

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think this is right for rejected cookies. The file still has unexpired login cookies, so loginCookiesLive is true and the old filter is dropped. The refusal isn't saved as a denial either, and nothing remembers that the cookies were rejected. So each scheduled channel run tries the members-only videos again until the cookies are replaced.

It's the same loop as the expired cookies one, just a different way in. Can you either remember the rejection for that cookie key and keep availability!=subscriber_only while it stands, or change this sentence to match what happens?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants