Cookies: keep every site of an imported file (no site picker); only the cookies box takes drops on Settings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-09 23:26:55 +03:00
1 parent 75065f90cd
commit 05f9e794a9
4 files changed
+11 -58

No files matched your search

+1 -1
View File
@@ -337,7 +337,7 @@ shows its version and has "Update now"), so there is nothing to install.
Sites that refuse guests (Bilibili answers "HTTP Error 412", YouTube may Sites that refuse guests (Bilibili answers "HTTP Error 412", YouTube may
ask to sign in) need cookies: Settings → Player → yt-dlp → Cookies reads ask to sign in) need cookies: Settings → Player → yt-dlp → Cookies reads
them from a browser, or from a cookies.txt or a cookie extension's JSON them from a browser, or from a cookies.txt or a cookie extension's JSON
export (picked or dropped; tick the sites to keep). They stay on this export (picked or dropped; every site in it is kept). They stay on this
computer and are used by its yt-dlp only (your own playback, "through computer and are used by its yt-dlp only (your own playback, "through
me", and a server you host). Cookies log yt-dlp in as you on those me", and a server you host). Cookies log yt-dlp in as you on those
sites: keep the file private, and for YouTube consider a throwaway sites: keep the file private, and for YouTube consider a throwaway
@@ -501,7 +501,9 @@ class AppShellState extends State<AppShell> with SingleTickerProviderStateMixin
_selectTab(ShellTab.library); _selectTab(ShellTab.library);
} }
// Off on Settings: its own drop targets (cookies) take the files there.
Widget _dropTarget(Widget child) => DropTarget( Widget _dropTarget(Widget child) => DropTarget(
enable: _tab != ShellTab.settings,
onDragEntered: (_) => setState(() => _dragging = _onTop), onDragEntered: (_) => setState(() => _dragging = _onTop),
onDragExited: (_) => setState(() => _dragging = false), onDragExited: (_) => setState(() => _dragging = false),
onDragDone: (d) { onDragDone: (d) {
+6 -55
View File
@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:fox_utils/fox_utils.dart' show DesktopDropX, DropTarget; import 'package:fox_utils/fox_utils.dart' show DesktopDropX, DropTarget;
import 'package:signals/signals_flutter.dart'; import 'package:signals/signals_flutter.dart';
import 'package:sync_relay_core/core.dart' import 'package:sync_relay_core/core.dart'
show CookieBrowser, CookieFile, NetscapeCookie, YtDlp, YtDlpCookies, cookieBrowsers, cookiesBySite, parseCookies, suggestedCookieSites; show CookieBrowser, CookieFile, NetscapeCookie, YtDlp, YtDlpCookies, cookieBrowsers, parseCookies;
import '../controls/player_settings.dart'; import '../controls/player_settings.dart';
import '../jellyfin/accounts.dart'; import '../jellyfin/accounts.dart';
@@ -461,7 +461,8 @@ class _YtDlpCookieSettingsState extends State<YtDlpCookieSettings> {
} }
String get _summary => switch (_source) { String get _summary => switch (_source) {
CookieFile f => '${f.count} cookie${f.count == 1 ? '' : 's'} for ${f.sites.join(', ')}', CookieFile f => '${f.count} cookie${f.count == 1 ? '' : 's'} for '
'${f.sites.length <= 3 ? f.sites.join(', ') : '${f.sites.length} sites'}',
CookieBrowser b => 'From ${b.browser}${b.profile.isEmpty ? '' : ' (profile ${b.profile})'}', CookieBrowser b => 'From ${b.browser}${b.profile.isEmpty ? '' : ' (profile ${b.profile})'}',
_ => 'No cookies: yt-dlp visits sites as a guest', _ => 'No cookies: yt-dlp visits sites as a guest',
}; };
@@ -494,14 +495,9 @@ class _YtDlpCookieSettingsState extends State<YtDlpCookieSettings> {
return; return;
} }
if (!mounted) return; if (!mounted) return;
final bySite = cookiesBySite(all); // Every site in the file: an export is "my logins", not a pick list.
final sites = await showDialog<Set<String>>(
context: context,
builder: (_) => _CookieSitesDialog(bySite: {for (final e in bySite.entries) e.key: e.value.length}),
);
if (sites == null || sites.isEmpty) return;
try { try {
await widget.cookies.saveFile([for (final s in sites) ...?bySite[s]]); await widget.cookies.saveFile(all);
_reload(); _reload();
} catch (e) { } catch (e) {
_reload('Could not keep the cookies: $e'); _reload('Could not keep the cookies: $e');
@@ -586,7 +582,7 @@ class _YtDlpCookieSettingsState extends State<YtDlpCookieSettings> {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'For sites that refuse guests (Bilibili, YouTube asking to sign in). Drop a cookies.txt or a ' 'For sites that refuse guests (Bilibili, YouTube asking to sign in). Drop a cookies.txt or a '
'cookie-extension JSON export here, or pick one; only the sites you tick are kept, on this computer only. ' 'cookie-extension JSON export here, or pick one; all of it is kept, on this computer only. '
'Cookies log yt-dlp in as you on those sites. Keep the file private; for YouTube consider a ' 'Cookies log yt-dlp in as you on those sites. Keep the file private; for YouTube consider a '
'throwaway account (Google may flag accounts used with yt-dlp).', 'throwaway account (Google may flag accounts used with yt-dlp).',
style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12), style: TextStyle(color: scheme.onSurfaceVariant, fontSize: 12),
@@ -598,51 +594,6 @@ class _YtDlpCookieSettingsState extends State<YtDlpCookieSettings> {
} }
} }
/// Which sites of an imported cookie file to keep; pops the ticked ones.
class _CookieSitesDialog extends StatefulWidget {
/// Site -> number of cookies.
final Map<String, int> bySite;
const _CookieSitesDialog({required this.bySite});
@override
State<_CookieSitesDialog> createState() => _CookieSitesDialogState();
}
class _CookieSitesDialogState extends State<_CookieSitesDialog> {
late final _on = {for (final s in widget.bySite.keys) if (suggestedCookieSites.contains(s)) s};
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Keep cookies for'),
content: SizedBox(
width: 380,
child: ListView(
shrinkWrap: true,
children: [
for (final e in widget.bySite.entries)
CheckboxListTile(
dense: true,
value: _on.contains(e.key),
title: Text(e.key),
subtitle: Text('${e.value} cookie${e.value == 1 ? '' : 's'}'),
onChanged: (v) => setState(() => v == true ? _on.add(e.key) : _on.remove(e.key)),
),
],
),
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Cancel')),
FilledButton(
onPressed: _on.isEmpty ? null : () => Navigator.pop(context, {..._on}),
child: const Text('Keep'),
),
],
);
}
}
/// Which browser (and profile) yt-dlp reads cookies from.
class _CookieBrowserDialog extends StatefulWidget { class _CookieBrowserDialog extends StatefulWidget {
final CookieBrowser? initial; final CookieBrowser? initial;
const _CookieBrowserDialog({this.initial}); const _CookieBrowserDialog({this.initial});
+2 -2
View File
@@ -560,8 +560,8 @@ all three link modes. Nothing is bundled or installed by hand.
registrable domain by a short rule, with google.com filed under registrable domain by a short rule, with google.com filed under
youtube.com and bilibili's CDNs under bilibili.com). The app: Settings youtube.com and bilibili's CDNs under bilibili.com). The app: Settings
→ Player → yt-dlp → Cookies (a browser and profile, or a file picked or → Player → yt-dlp → Cookies (a browser and profile, or a file picked or
dropped, then the sites to keep, bilibili.com and youtube.com ticked dropped, kept whole: every site in it; "Remove cookies"; the shell's
when present; "Remove cookies"); its hosted server shares the folder, own drop target is off on Settings so only this box takes the file); its hosted server shares the folder,
so it uses them too. mpv's hook gets the same as `ytdl-raw-options` so it uses them too. mpv's hook gets the same as `ytdl-raw-options`
(`cookies=%N%<path>` / `cookies-from-browser=%N%<spec>`, length-quoted), (`cookies=%N%<path>` / `cookies-from-browser=%N%<spec>`, length-quoted),
re-set before each external link when it changed. The dedicated re-set before each external link when it changed. The dedicated