allow filtering out members-only videos in subscriptions (closes #971)

This commit is contained in:
Alex Shnitman
2026-04-28 22:02:05 +03:00
parent 4f83174d05
commit 5d96a581b9
8 changed files with 341 additions and 7 deletions
+9
View File
@@ -515,6 +515,15 @@
ngbTooltip="In subscriptions, only titles matching this Python-style regex are queued. Empty = all. Case-sensitive; use (?i) in the pattern for case-insensitive.">
</div>
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkbox-skip-subscriber-only"
name="skipSubscriberOnly" [(ngModel)]="skipSubscriberOnly"
[disabled]="addInProgress || subscribeInProgress || downloads.loading"
ngbTooltip="When enabled, subscription checks skip videos marked members-only by yt-dlp (channel Join). Ignored for one-off downloads." />
<label class="form-check-label" for="checkbox-skip-subscriber-only">Skip members-only subscription videos</label>
</div>
</div>
</div>
<!-- yt-dlp -->
+14 -1
View File
@@ -190,8 +190,21 @@ describe('App', () => {
app.titleRegex = 'EPISODE';
app.addSubscription();
expect(subs.subscribeCalls.length).toBe(1);
const payload = subs.subscribeCalls[0] as { titleRegex: string };
const payload = subs.subscribeCalls[0] as { titleRegex: string; skipSubscriberOnly: boolean };
expect(payload.titleRegex).toBe('EPISODE');
expect(payload.skipSubscriberOnly).toBe(false);
});
it('includes skipSubscriberOnly true when checked', () => {
const fixture = TestBed.createComponent(App);
const app = fixture.componentInstance;
const subs = TestBed.inject(SubscriptionsService) as unknown as SubscriptionsServiceStub;
app.addUrl = 'https://example.com/channel';
app.skipSubscriberOnly = true;
app.addSubscription();
expect(subs.subscribeCalls.length).toBe(1);
const payload = subs.subscribeCalls[0] as { skipSubscriberOnly: boolean };
expect(payload.skipSubscriberOnly).toBe(true);
});
it('omits clip fields from subscribe payload', () => {
+3
View File
@@ -93,6 +93,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
subscribeInProgress = false;
checkIntervalMinutes = 60;
titleRegex = '';
skipSubscriberOnly = false;
editingTitleRegexId: string | null = null;
titleRegexEditDraft = '';
cachedSubs: [string, SubscriptionRow][] = [];
@@ -593,6 +594,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
...subscribeBase,
checkIntervalMinutes: this.checkIntervalMinutes,
titleRegex: tr,
skipSubscriberOnly: this.skipSubscriberOnly,
})
.pipe(
takeUntilDestroyed(this.destroyRef),
@@ -609,6 +611,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
} else {
this.addUrl = '';
this.titleRegex = '';
this.skipSubscriberOnly = false;
}
},
});
+1
View File
@@ -10,6 +10,7 @@ export interface SubscriptionRow {
quality: string;
folder: string;
title_regex?: string;
skip_subscriber_only?: boolean;
last_checked: number | null;
seen_count: number;
error: string | null;
+8 -1
View File
@@ -11,6 +11,7 @@ import { AddDownloadPayload } from './downloads.service';
export interface SubscribePayload extends AddDownloadPayload {
checkIntervalMinutes: number;
titleRegex: string;
skipSubscriberOnly: boolean;
}
@Injectable({
@@ -99,6 +100,7 @@ export class SubscriptionsService {
ytdl_options_overrides: payload.ytdlOptionsOverrides,
check_interval_minutes: payload.checkIntervalMinutes,
title_regex: payload.titleRegex,
skip_subscriber_only: payload.skipSubscriberOnly,
})
.pipe(catchError((err) => this.handleHTTPError(err)));
}
@@ -109,7 +111,12 @@ export class SubscriptionsService {
update(
id: string,
changes: Partial<Pick<SubscriptionRow, 'enabled' | 'check_interval_minutes' | 'name' | 'title_regex'>>,
changes: Partial<
Pick<
SubscriptionRow,
'enabled' | 'check_interval_minutes' | 'name' | 'title_regex' | 'skip_subscriber_only'
>
>,
) {
return this.http
.post('subscriptions/update', { id, ...changes })