mirror of
https://github.com/alexta69/metube.git
synced 2026-06-13 16:40:05 +00:00
Gate manual yt-dlp overrides behind flag
Agent-Logs-Url: https://github.com/alexta69/metube/sessions/31b4274d-cf48-4260-b73b-633cbcd2bb09 Co-authored-by: alexta69 <7450369+alexta69@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
565a715037
commit
6e9b2dd7b3
+3
-1
@@ -447,7 +447,7 @@
|
||||
ngbTooltip="How often to poll subscriptions for new videos">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-12" [class.col-md-6]="allowYtdlOptionsOverrides()">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Option Preset</span>
|
||||
<select class="form-select"
|
||||
@@ -463,6 +463,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (allowYtdlOptionsOverrides()) {
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Custom yt-dlp Options</span>
|
||||
@@ -476,6 +477,7 @@
|
||||
ngbTooltip="Optional per-download yt-dlp overrides as a JSON object">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="col-12">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-auto">
|
||||
|
||||
@@ -1,7 +1,101 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Subject, of } from 'rxjs';
|
||||
import { App } from './app';
|
||||
import { DownloadsService } from './services/downloads.service';
|
||||
import { SubscriptionsService } from './services/subscriptions.service';
|
||||
import { CookieService } from 'ngx-cookie-service';
|
||||
|
||||
class DownloadsServiceStub {
|
||||
loading = false;
|
||||
queue = new Map();
|
||||
done = new Map();
|
||||
configuration: Record<string, unknown> = { CUSTOM_DIRS: true, CREATE_CUSTOM_DIRS: true, ALLOW_YTDL_OPTIONS_OVERRIDES: false };
|
||||
customDirs = { download_dir: [], audio_download_dir: [] };
|
||||
queueChanged = new Subject<void>();
|
||||
doneChanged = new Subject<void>();
|
||||
configurationChanged = new Subject<Record<string, unknown>>();
|
||||
customDirsChanged = new Subject<Record<string, string[]>>();
|
||||
ytdlOptionsChanged = new Subject<Record<string, unknown>>();
|
||||
updated = new Subject<void>();
|
||||
|
||||
getCookieStatus() {
|
||||
return of({ status: 'ok', has_cookies: false });
|
||||
}
|
||||
|
||||
getPresets() {
|
||||
return of({ presets: ['Preset A'] });
|
||||
}
|
||||
|
||||
add() {
|
||||
return of({ status: 'ok' as const });
|
||||
}
|
||||
|
||||
cancelAdd() {
|
||||
return of({ status: 'ok' as const });
|
||||
}
|
||||
|
||||
startById() {
|
||||
return of({});
|
||||
}
|
||||
|
||||
delById() {
|
||||
return of({});
|
||||
}
|
||||
|
||||
delByFilter() {
|
||||
return of({});
|
||||
}
|
||||
|
||||
startByFilter() {
|
||||
return of({});
|
||||
}
|
||||
|
||||
uploadCookies() {
|
||||
return of({ status: 'ok' });
|
||||
}
|
||||
|
||||
deleteCookies() {
|
||||
return of({ status: 'ok' });
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionsServiceStub {
|
||||
subscriptions = new Map();
|
||||
subscriptionsChanged = new Subject<void>();
|
||||
|
||||
subscribe() {
|
||||
return of({ status: 'ok' as const });
|
||||
}
|
||||
|
||||
delete() {
|
||||
return of({});
|
||||
}
|
||||
|
||||
refreshList() {
|
||||
return of([]);
|
||||
}
|
||||
}
|
||||
|
||||
class CookieServiceStub {
|
||||
private cookies = new Map<string, string>();
|
||||
|
||||
get(name: string) {
|
||||
return this.cookies.get(name) ?? '';
|
||||
}
|
||||
|
||||
set(name: string, value: string) {
|
||||
this.cookies.set(name, value);
|
||||
}
|
||||
|
||||
check(name: string) {
|
||||
return this.cookies.has(name);
|
||||
}
|
||||
}
|
||||
|
||||
describe('App', () => {
|
||||
let downloads: DownloadsServiceStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
@@ -15,8 +109,20 @@ describe('App', () => {
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
downloads = new DownloadsServiceStub();
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App],
|
||||
providers: [
|
||||
{ provide: DownloadsService, useValue: downloads },
|
||||
{ provide: SubscriptionsService, useClass: SubscriptionsServiceStub },
|
||||
{ provide: CookieService, useClass: CookieServiceStub },
|
||||
{
|
||||
provide: HttpClient,
|
||||
useValue: {
|
||||
get: vi.fn().mockReturnValue(of({ 'yt-dlp': 'test', version: 'test' })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
@@ -25,4 +131,41 @@ describe('App', () => {
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hides manual override input when disabled', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.componentInstance.isAdvancedOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const root = fixture.nativeElement as HTMLElement;
|
||||
expect(root.querySelector('input[name="ytdlOptionsOverrides"]')).toBeNull();
|
||||
|
||||
const presetWrapper = root.querySelector('select[name="ytdlOptionsPreset"]')?.closest('.col-12');
|
||||
expect(presetWrapper?.classList.contains('col-md-6')).toBe(false);
|
||||
});
|
||||
|
||||
it('shows manual override input when enabled', () => {
|
||||
downloads.configuration.ALLOW_YTDL_OPTIONS_OVERRIDES = true;
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.componentInstance.isAdvancedOpen = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const root = fixture.nativeElement as HTMLElement;
|
||||
expect(root.querySelector('input[name="ytdlOptionsOverrides"]')).not.toBeNull();
|
||||
|
||||
const presetWrapper = root.querySelector('select[name="ytdlOptionsPreset"]')?.closest('.col-12');
|
||||
expect(presetWrapper?.classList.contains('col-md-6')).toBe(true);
|
||||
});
|
||||
|
||||
it('does not submit manual overrides when disabled', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const app = fixture.componentInstance;
|
||||
|
||||
app.ytdlOptionsOverrides = '{"exec":"echo hi"}';
|
||||
|
||||
const payload = app['buildAddPayload']();
|
||||
|
||||
expect(payload.ytdlOptionsOverrides).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
+11
-1
@@ -356,6 +356,10 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||
return this.downloads.configuration['CUSTOM_DIRS'];
|
||||
}
|
||||
|
||||
allowYtdlOptionsOverrides() {
|
||||
return this.downloads.configuration['ALLOW_YTDL_OPTIONS_OVERRIDES'] === true;
|
||||
}
|
||||
|
||||
allowCustomDir(tag: string) {
|
||||
if (this.downloads.configuration['CREATE_CUSTOM_DIRS']) {
|
||||
return tag;
|
||||
@@ -437,6 +441,9 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private validateYtdlOptionsOverrides(value: string): boolean {
|
||||
if (!this.allowYtdlOptionsOverrides()) {
|
||||
return true;
|
||||
}
|
||||
const trimmed = value?.trim() || '';
|
||||
if (!trimmed) {
|
||||
return true;
|
||||
@@ -930,6 +937,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private buildAddPayload(overrides: Partial<AddDownloadPayload> = {}): AddDownloadPayload {
|
||||
const allowYtdlOptionsOverrides = this.allowYtdlOptionsOverrides();
|
||||
return {
|
||||
url: overrides.url ?? this.addUrl,
|
||||
downloadType: overrides.downloadType ?? this.downloadType,
|
||||
@@ -945,7 +953,9 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||
subtitleLanguage: overrides.subtitleLanguage ?? this.subtitleLanguage,
|
||||
subtitleMode: overrides.subtitleMode ?? this.subtitleMode,
|
||||
ytdlOptionsPreset: overrides.ytdlOptionsPreset ?? this.ytdlOptionsPreset,
|
||||
ytdlOptionsOverrides: overrides.ytdlOptionsOverrides ?? this.ytdlOptionsOverrides,
|
||||
ytdlOptionsOverrides: allowYtdlOptionsOverrides
|
||||
? (overrides.ytdlOptionsOverrides ?? this.ytdlOptionsOverrides)
|
||||
: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user