From e92861305df6b0eec8b6036686fc2f4efd7920fe Mon Sep 17 00:00:00 2001 From: defnone <141522332+defnone@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:13:11 +0700 Subject: [PATCH 1/2] server: add flaresolverr bypass settings --- .../settings/FlareSolverrSettings.tsx | 182 ++++++ client/src/routes/dashboard/settings.tsx | 35 +- server/src/db/app/app-schema.ts | 9 +- .../0001_add_flaresolverr_settings.sql | 2 + .../0002_add_flaresolverr_timeout.sql | 1 + .../src/db/migrations/meta/0001_snapshot.json | 607 +++++++++++++++++ .../src/db/migrations/meta/0002_snapshot.json | 615 ++++++++++++++++++ server/src/db/migrations/meta/_journal.json | 14 + .../adapters/tracker-data/flaresolverr.ts | 140 ++++ .../tracker-data/tracker-data.adapter.ts | 176 ++++- .../external/adapters/tracker-data/utils.ts | 21 +- server/src/index.ts | 4 +- server/src/routes/flaresolverr.verify.ts | 43 ++ .../test/download-worker-copy-error.test.ts | 38 +- .../download-worker-error-message.test.ts | 63 +- server/test/download-worker.test.ts | 17 +- server/test/flaresolverr.test.ts | 128 ++++ server/test/routes/flaresolverr-route.test.ts | 81 +++ server/test/routes/settings-route.test.ts | 20 +- server/test/settings.repo.test.ts | 10 +- server/test/settings.service.test.ts | 141 ++-- server/test/telegram-adapter.test.ts | 21 +- server/test/tracker-data.test.ts | 376 +++++++---- server/test/transmission-adapter.test.ts | 38 +- .../test/update-worker-error-message.test.ts | 41 +- server/test/update-worker.test.ts | 5 +- server/test/workers.repo.test.ts | 33 +- 27 files changed, 2578 insertions(+), 283 deletions(-) create mode 100644 client/src/components/settings/FlareSolverrSettings.tsx create mode 100644 server/src/db/migrations/0001_add_flaresolverr_settings.sql create mode 100644 server/src/db/migrations/0002_add_flaresolverr_timeout.sql create mode 100644 server/src/db/migrations/meta/0001_snapshot.json create mode 100644 server/src/db/migrations/meta/0002_snapshot.json create mode 100644 server/src/external/adapters/tracker-data/flaresolverr.ts create mode 100644 server/src/routes/flaresolverr.verify.ts create mode 100644 server/test/flaresolverr.test.ts create mode 100644 server/test/routes/flaresolverr-route.test.ts diff --git a/client/src/components/settings/FlareSolverrSettings.tsx b/client/src/components/settings/FlareSolverrSettings.tsx new file mode 100644 index 0000000..35c32ab --- /dev/null +++ b/client/src/components/settings/FlareSolverrSettings.tsx @@ -0,0 +1,182 @@ +import customSonner from '@/components/CustomSonner'; +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { rpc } from '@/lib/rpc'; +import type { DbUserSettings } from '@server/db/app/app-schema'; +import { Loader2 } from 'lucide-react'; +import { useState } from 'react'; +import type { Dispatch, SetStateAction } from 'react'; + +type FlareSolverrSettingsProps = { + enabled: boolean; + serverUrl: string; + timeoutSeconds: number; + setData: Dispatch>; +}; + +export default function FlareSolverrSettings({ + enabled, + serverUrl, + timeoutSeconds, + setData, +}: FlareSolverrSettingsProps) { + const [isTestingConnection, setIsTestingConnection] = useState(false); + + const handleTestConnection = async () => { + if (!enabled) { + customSonner({ variant: 'error', text: 'FlareSolverr is disabled' }); + return; + } + + if (!serverUrl) { + customSonner({ variant: 'error', text: 'FlareSolverr URL is required' }); + return; + } + + if (!isValidUrl(serverUrl)) { + customSonner({ + variant: 'error', + text: 'Invalid FlareSolverr URL. It should include the protocol.', + }); + return; + } + + try { + setIsTestingConnection(true); + const response = await rpc.api.flaresolverr.verify.$post({ + json: { + flaresolverrUrl: serverUrl, + timeoutSeconds, + }, + }); + const payload = await response.json(); + + if (!response.ok || !payload?.success) { + customSonner({ + variant: 'error', + text: payload?.message ?? 'Failed to test FlareSolverr connection', + }); + return; + } + + customSonner({ text: 'FlareSolverr connection successful' }); + } catch (error) { + const description = + error instanceof Error ? error.message : String(error); + customSonner({ + variant: 'error', + text: 'Failed to test FlareSolverr connection', + description, + }); + } finally { + setIsTestingConnection(false); + } + }; + + return ( +
+
+

+ FlareSolverr Settings +

+
+ +
+
+
+ + setData((data) => { + if (!data) return data; + return { + ...data, + flaresolverrEnabled: checked === true, + }; + }) + } + /> + +
+
+ +
+
+

Server URL

+ + setData((data) => { + if (!data) return data; + return { + ...data, + flaresolverrUrl: event.target.value, + }; + }) + } + /> +
+
+ +
+
+ +
+

Timeout

+ + setData((data) => { + if (!data) return data; + return { + ...data, + flaresolverrTimeoutSeconds: parseTimeoutSeconds( + event.target.value, + ), + }; + }) + } + /> +
+
+
+ ); +} + +// Helpers + +function isValidUrl(value: string): boolean { + try { + return Boolean(new URL(value)); + } catch { + return false; + } +} + +function parseTimeoutSeconds(value: string): number { + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed)) return 60; + return Math.min(Math.max(parsed, 1), 300); +} diff --git a/client/src/routes/dashboard/settings.tsx b/client/src/routes/dashboard/settings.tsx index 1d15f8c..8c274b2 100644 --- a/client/src/routes/dashboard/settings.tsx +++ b/client/src/routes/dashboard/settings.tsx @@ -1,5 +1,6 @@ import customSonner from '@/components/CustomSonner'; import GeneralSettings from '@/components/settings/GeneralSettings'; +import FlareSolverrSettings from '@/components/settings/FlareSolverrSettings'; import JackettSettings from '@/components/settings/JackettSettings'; import NotificationSettings from '@/components/settings/NotificationSettings'; import SettingsMenu from '@/components/settings/SettingsMenu'; @@ -36,7 +37,7 @@ export default function Settings() { if (typeof next === 'function') { const updater = next as ( - prevState: DbUserSettings | null | undefined + prevState: DbUserSettings | null | undefined, ) => DbUserSettings | null | undefined; const updated = updater(resolvedCurrentData); return updated ?? undefined; @@ -45,7 +46,7 @@ export default function Settings() { return next ?? undefined; }); }, - [settingsData] + [settingsData], ); const handleSave = async () => { @@ -66,11 +67,11 @@ export default function Settings() { if (isLoadingSettings) { return ( <> -
+
-
- +
+
); @@ -91,32 +92,40 @@ export default function Settings() { syncInterval={data?.syncInterval ?? 0} setData={setData} /> - + - + - + - + + +