mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-21 14:52:18 +00:00
add realdebrid setup
This commit is contained in:
parent
29c412707c
commit
715c26e6ab
9 changed files with 378 additions and 8 deletions
|
|
@ -977,6 +977,16 @@
|
||||||
"invalid_token": "Failed to fetch a 'VIP' stream. Your token is invalid!"
|
"invalid_token": "Failed to fetch a 'VIP' stream. Your token is invalid!"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"realdebrid": {
|
||||||
|
"title": "Real Debrid (Beta)",
|
||||||
|
"description": "Enter your Real Debrid API key to access Real Debrid. Extension required.",
|
||||||
|
"tokenLabel": "API Key",
|
||||||
|
"status": {
|
||||||
|
"failure": "Failed to connect to Real Debrid. Please check your API key.",
|
||||||
|
"api_down": "Real Debrid API is currently unavailable. Please try again later.",
|
||||||
|
"invalid_token": "Invalid API key or non-premium account. Real Debrid requires a premium account."
|
||||||
|
}
|
||||||
|
},
|
||||||
"watchParty": {
|
"watchParty": {
|
||||||
"status": {
|
"status": {
|
||||||
"inSync": "In sync",
|
"inSync": "In sync",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export interface SettingsInput {
|
||||||
defaultSubtitleLanguage?: string;
|
defaultSubtitleLanguage?: string;
|
||||||
proxyUrls?: string[] | null;
|
proxyUrls?: string[] | null;
|
||||||
febboxKey?: string | null;
|
febboxKey?: string | null;
|
||||||
|
realDebridKey?: string | null;
|
||||||
enableThumbnails?: boolean;
|
enableThumbnails?: boolean;
|
||||||
enableAutoplay?: boolean;
|
enableAutoplay?: boolean;
|
||||||
enableSkipCredits?: boolean;
|
enableSkipCredits?: boolean;
|
||||||
|
|
@ -28,6 +29,7 @@ export interface SettingsResponse {
|
||||||
defaultSubtitleLanguage?: string | null;
|
defaultSubtitleLanguage?: string | null;
|
||||||
proxyUrls?: string[] | null;
|
proxyUrls?: string[] | null;
|
||||||
febboxKey?: string | null;
|
febboxKey?: string | null;
|
||||||
|
realDebridKey?: string | null;
|
||||||
enableThumbnails?: boolean;
|
enableThumbnails?: boolean;
|
||||||
enableAutoplay?: boolean;
|
enableAutoplay?: boolean;
|
||||||
enableSkipCredits?: boolean;
|
enableSkipCredits?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export function useSettingsState(
|
||||||
proxyUrls: string[] | null,
|
proxyUrls: string[] | null,
|
||||||
backendUrl: string | null,
|
backendUrl: string | null,
|
||||||
febboxKey: string | null,
|
febboxKey: string | null,
|
||||||
|
realDebridKey: string | null,
|
||||||
profile:
|
profile:
|
||||||
| {
|
| {
|
||||||
colorA: string;
|
colorA: string;
|
||||||
|
|
@ -69,6 +70,12 @@ export function useSettingsState(
|
||||||
useDerived(backendUrl);
|
useDerived(backendUrl);
|
||||||
const [febboxKeyState, setFebboxKey, resetFebboxKey, febboxKeyChanged] =
|
const [febboxKeyState, setFebboxKey, resetFebboxKey, febboxKeyChanged] =
|
||||||
useDerived(febboxKey);
|
useDerived(febboxKey);
|
||||||
|
const [
|
||||||
|
realDebridKeyState,
|
||||||
|
setRealDebridKey,
|
||||||
|
resetRealDebridKey,
|
||||||
|
realDebridKeyChanged,
|
||||||
|
] = useDerived(realDebridKey);
|
||||||
const [themeState, setTheme, resetTheme, themeChanged] = useDerived(theme);
|
const [themeState, setTheme, resetTheme, themeChanged] = useDerived(theme);
|
||||||
const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
|
const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
|
||||||
const resetPreviewTheme = useCallback(
|
const resetPreviewTheme = useCallback(
|
||||||
|
|
@ -162,6 +169,7 @@ export function useSettingsState(
|
||||||
resetProxyUrls();
|
resetProxyUrls();
|
||||||
resetBackendUrl();
|
resetBackendUrl();
|
||||||
resetFebboxKey();
|
resetFebboxKey();
|
||||||
|
resetRealDebridKey();
|
||||||
resetDeviceName();
|
resetDeviceName();
|
||||||
resetProfile();
|
resetProfile();
|
||||||
resetEnableThumbnails();
|
resetEnableThumbnails();
|
||||||
|
|
@ -185,6 +193,7 @@ export function useSettingsState(
|
||||||
backendUrlChanged ||
|
backendUrlChanged ||
|
||||||
proxyUrlsChanged ||
|
proxyUrlsChanged ||
|
||||||
febboxKeyChanged ||
|
febboxKeyChanged ||
|
||||||
|
realDebridKeyChanged ||
|
||||||
profileChanged ||
|
profileChanged ||
|
||||||
enableThumbnailsChanged ||
|
enableThumbnailsChanged ||
|
||||||
enableAutoplayChanged ||
|
enableAutoplayChanged ||
|
||||||
|
|
@ -236,6 +245,11 @@ export function useSettingsState(
|
||||||
set: setFebboxKey,
|
set: setFebboxKey,
|
||||||
changed: febboxKeyChanged,
|
changed: febboxKeyChanged,
|
||||||
},
|
},
|
||||||
|
realDebridKey: {
|
||||||
|
state: realDebridKeyState,
|
||||||
|
set: setRealDebridKey,
|
||||||
|
changed: realDebridKeyChanged,
|
||||||
|
},
|
||||||
profile: {
|
profile: {
|
||||||
state: profileState,
|
state: profileState,
|
||||||
set: setProfileState,
|
set: setProfileState,
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,9 @@ export function SettingsPage() {
|
||||||
const febboxKey = usePreferencesStore((s) => s.febboxKey);
|
const febboxKey = usePreferencesStore((s) => s.febboxKey);
|
||||||
const setFebboxKey = usePreferencesStore((s) => s.setFebboxKey);
|
const setFebboxKey = usePreferencesStore((s) => s.setFebboxKey);
|
||||||
|
|
||||||
|
const realDebridKey = usePreferencesStore((s) => s.realDebridKey);
|
||||||
|
const setRealDebridKey = usePreferencesStore((s) => s.setRealDebridKey);
|
||||||
|
|
||||||
const enableThumbnails = usePreferencesStore((s) => s.enableThumbnails);
|
const enableThumbnails = usePreferencesStore((s) => s.enableThumbnails);
|
||||||
const setEnableThumbnails = usePreferencesStore((s) => s.setEnableThumbnails);
|
const setEnableThumbnails = usePreferencesStore((s) => s.setEnableThumbnails);
|
||||||
|
|
||||||
|
|
@ -195,10 +198,13 @@ export function SettingsPage() {
|
||||||
if (settings.febboxKey) {
|
if (settings.febboxKey) {
|
||||||
setFebboxKey(settings.febboxKey);
|
setFebboxKey(settings.febboxKey);
|
||||||
}
|
}
|
||||||
|
if (settings.realDebridKey) {
|
||||||
|
setRealDebridKey(settings.realDebridKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadSettings();
|
loadSettings();
|
||||||
}, [account, backendUrl, setFebboxKey]);
|
}, [account, backendUrl, setFebboxKey, setRealDebridKey]);
|
||||||
|
|
||||||
const state = useSettingsState(
|
const state = useSettingsState(
|
||||||
activeTheme,
|
activeTheme,
|
||||||
|
|
@ -208,6 +214,7 @@ export function SettingsPage() {
|
||||||
proxySet,
|
proxySet,
|
||||||
backendUrlSetting,
|
backendUrlSetting,
|
||||||
febboxKey,
|
febboxKey,
|
||||||
|
realDebridKey,
|
||||||
account ? account.profile : undefined,
|
account ? account.profile : undefined,
|
||||||
enableThumbnails,
|
enableThumbnails,
|
||||||
enableAutoplay,
|
enableAutoplay,
|
||||||
|
|
@ -264,6 +271,7 @@ export function SettingsPage() {
|
||||||
state.theme.changed ||
|
state.theme.changed ||
|
||||||
state.proxyUrls.changed ||
|
state.proxyUrls.changed ||
|
||||||
state.febboxKey.changed ||
|
state.febboxKey.changed ||
|
||||||
|
state.realDebridKey.changed ||
|
||||||
state.enableThumbnails.changed ||
|
state.enableThumbnails.changed ||
|
||||||
state.enableAutoplay.changed ||
|
state.enableAutoplay.changed ||
|
||||||
state.enableSkipCredits.changed ||
|
state.enableSkipCredits.changed ||
|
||||||
|
|
@ -281,6 +289,7 @@ export function SettingsPage() {
|
||||||
applicationTheme: state.theme.state,
|
applicationTheme: state.theme.state,
|
||||||
proxyUrls: state.proxyUrls.state?.filter((v) => v !== "") ?? null,
|
proxyUrls: state.proxyUrls.state?.filter((v) => v !== "") ?? null,
|
||||||
febboxKey: state.febboxKey.state,
|
febboxKey: state.febboxKey.state,
|
||||||
|
realDebridKey: state.realDebridKey.state,
|
||||||
enableThumbnails: state.enableThumbnails.state,
|
enableThumbnails: state.enableThumbnails.state,
|
||||||
enableAutoplay: state.enableAutoplay.state,
|
enableAutoplay: state.enableAutoplay.state,
|
||||||
enableSkipCredits: state.enableSkipCredits.state,
|
enableSkipCredits: state.enableSkipCredits.state,
|
||||||
|
|
@ -325,6 +334,7 @@ export function SettingsPage() {
|
||||||
setProxySet(state.proxyUrls.state?.filter((v) => v !== "") ?? null);
|
setProxySet(state.proxyUrls.state?.filter((v) => v !== "") ?? null);
|
||||||
setEnableSourceOrder(state.enableSourceOrder.state);
|
setEnableSourceOrder(state.enableSourceOrder.state);
|
||||||
setFebboxKey(state.febboxKey.state);
|
setFebboxKey(state.febboxKey.state);
|
||||||
|
setRealDebridKey(state.realDebridKey.state);
|
||||||
setProxyTmdb(state.proxyTmdb.state);
|
setProxyTmdb(state.proxyTmdb.state);
|
||||||
setEnableCarouselView(state.enableCarouselView.state);
|
setEnableCarouselView(state.enableCarouselView.state);
|
||||||
|
|
||||||
|
|
@ -348,6 +358,7 @@ export function SettingsPage() {
|
||||||
backendUrl,
|
backendUrl,
|
||||||
setEnableThumbnails,
|
setEnableThumbnails,
|
||||||
setFebboxKey,
|
setFebboxKey,
|
||||||
|
setRealDebridKey,
|
||||||
state,
|
state,
|
||||||
setEnableAutoplay,
|
setEnableAutoplay,
|
||||||
setEnableSkipCredits,
|
setEnableSkipCredits,
|
||||||
|
|
@ -448,6 +459,8 @@ export function SettingsPage() {
|
||||||
setProxyUrls={state.proxyUrls.set}
|
setProxyUrls={state.proxyUrls.set}
|
||||||
febboxKey={state.febboxKey.state}
|
febboxKey={state.febboxKey.state}
|
||||||
setFebboxKey={state.febboxKey.set}
|
setFebboxKey={state.febboxKey.set}
|
||||||
|
realDebridKey={state.realDebridKey.state}
|
||||||
|
setRealDebridKey={state.realDebridKey.set}
|
||||||
proxyTmdb={state.proxyTmdb.state}
|
proxyTmdb={state.proxyTmdb.state}
|
||||||
setProxyTmdb={state.proxyTmdb.set}
|
setProxyTmdb={state.proxyTmdb.set}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,14 @@ import {
|
||||||
} from "@/pages/onboarding/utils";
|
} from "@/pages/onboarding/utils";
|
||||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
|
||||||
import { usePreferencesStore } from "@/stores/preferences";
|
import { usePreferencesStore } from "@/stores/preferences";
|
||||||
import { getProxyUrls } from "@/utils/proxyUrls";
|
import { getProxyUrls } from "@/utils/proxyUrls";
|
||||||
|
|
||||||
import { Status, testFebboxKey } from "../parts/settings/SetupPart";
|
import {
|
||||||
|
Status,
|
||||||
|
testFebboxKey,
|
||||||
|
testRealDebridKey,
|
||||||
|
} from "../parts/settings/SetupPart";
|
||||||
|
|
||||||
async function getFebboxKeyStatus(febboxKey: string | null) {
|
async function getFebboxKeyStatus(febboxKey: string | null) {
|
||||||
if (febboxKey) {
|
if (febboxKey) {
|
||||||
|
|
@ -218,12 +221,130 @@ export function FEDAPISetup() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getRealDebridKeyStatus(realDebridKey: string | null) {
|
||||||
|
if (realDebridKey) {
|
||||||
|
const status: Status = await testRealDebridKey(realDebridKey);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
return "unset";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RealDebridSetup() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const realDebridKey = usePreferencesStore((s) => s.realDebridKey);
|
||||||
|
const setRealDebridKey = usePreferencesStore((s) => s.setRealDebridKey);
|
||||||
|
|
||||||
|
// Initialize isExpanded based on whether realDebridKey has a value
|
||||||
|
const [isExpanded, setIsExpanded] = useState(
|
||||||
|
realDebridKey !== null && realDebridKey !== "",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a separate effect to set the initial state
|
||||||
|
useEffect(() => {
|
||||||
|
// If we have a valid key, make sure the section is expanded
|
||||||
|
if (realDebridKey && realDebridKey.length > 0) {
|
||||||
|
setIsExpanded(true);
|
||||||
|
}
|
||||||
|
}, [realDebridKey]);
|
||||||
|
|
||||||
|
const [status, setStatus] = useState<Status>("unset");
|
||||||
|
const statusMap: Record<Status, StatusCircleProps["type"]> = {
|
||||||
|
error: "error",
|
||||||
|
success: "success",
|
||||||
|
unset: "noresult",
|
||||||
|
api_down: "error",
|
||||||
|
invalid_token: "error",
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkTokenStatus = async () => {
|
||||||
|
const result = await getRealDebridKeyStatus(realDebridKey);
|
||||||
|
setStatus(result);
|
||||||
|
};
|
||||||
|
checkTokenStatus();
|
||||||
|
}, [realDebridKey]);
|
||||||
|
|
||||||
|
// Toggle handler that preserves the key
|
||||||
|
const toggleExpanded = () => {
|
||||||
|
if (isExpanded) {
|
||||||
|
// Store the key temporarily instead of setting to null
|
||||||
|
setRealDebridKey("");
|
||||||
|
setIsExpanded(false);
|
||||||
|
} else {
|
||||||
|
setIsExpanded(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (conf().ALLOW_REAL_DEBRID_KEY) {
|
||||||
|
return (
|
||||||
|
<div className="mt-6">
|
||||||
|
<SettingsCard>
|
||||||
|
<div className="flex justify-between items-center gap-4">
|
||||||
|
<div className="my-3">
|
||||||
|
<p className="text-white font-bold mb-3">
|
||||||
|
{t("settings.connections.realdebrid.title", "Real Debrid API")}
|
||||||
|
</p>
|
||||||
|
<p className="max-w-[30rem] font-medium">
|
||||||
|
{t(
|
||||||
|
"settings.connections.realdebrid.description",
|
||||||
|
"Enter your Real Debrid API key to access premium sources.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Toggle onClick={toggleExpanded} enabled={isExpanded} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isExpanded ? (
|
||||||
|
<>
|
||||||
|
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||||
|
<p className="text-white font-bold mb-3">
|
||||||
|
{t("settings.connections.realdebrid.tokenLabel", "API Key")}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<StatusCircle type={statusMap[status]} className="mx-2 mr-4" />
|
||||||
|
<AuthInputBox
|
||||||
|
onChange={(newToken) => {
|
||||||
|
setRealDebridKey(newToken);
|
||||||
|
}}
|
||||||
|
value={realDebridKey ?? ""}
|
||||||
|
placeholder="API Key"
|
||||||
|
passwordToggleable
|
||||||
|
className="flex-grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{status === "error" && (
|
||||||
|
<p className="text-type-danger mt-4">
|
||||||
|
{t(
|
||||||
|
"settings.connections.realdebrid.status.failure",
|
||||||
|
"Failed to connect to Real Debrid. Please check your API key.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{status === "api_down" && (
|
||||||
|
<p className="text-type-danger mt-4">
|
||||||
|
{t(
|
||||||
|
"settings.connections.realdebrid.status.api_down",
|
||||||
|
"Real Debrid API is currently unavailable. Please try again later.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{status === "invalid_token" && (
|
||||||
|
<p className="text-type-danger mt-4">
|
||||||
|
{t(
|
||||||
|
"settings.connections.realdebrid.status.invalid_token",
|
||||||
|
"Invalid API key or non-premium account. Real Debrid requires a premium account.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</SettingsCard>
|
</SettingsCard>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Item(props: { title: string; children: React.ReactNode }) {
|
function Item(props: { title: string; children: React.ReactNode }) {
|
||||||
|
|
@ -474,6 +595,7 @@ export function OnboardingPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* <RealDebridSetup /> */}
|
||||||
<FEDAPISetup />
|
<FEDAPISetup />
|
||||||
</BiggerCenterContainer>
|
</BiggerCenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { isExtensionActive } from "@/backend/extension/messaging";
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
import { Toggle } from "@/components/buttons/Toggle";
|
import { Toggle } from "@/components/buttons/Toggle";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
|
@ -23,6 +24,7 @@ import {
|
||||||
SetupPart,
|
SetupPart,
|
||||||
Status,
|
Status,
|
||||||
testFebboxKey,
|
testFebboxKey,
|
||||||
|
testRealDebridKey,
|
||||||
} from "@/pages/parts/settings/SetupPart";
|
} from "@/pages/parts/settings/SetupPart";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
@ -45,6 +47,11 @@ interface FebboxKeyProps {
|
||||||
setFebboxKey: Dispatch<SetStateAction<string | null>>;
|
setFebboxKey: Dispatch<SetStateAction<string | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RealDebridKeyProps {
|
||||||
|
realDebridKey: string | null;
|
||||||
|
setRealDebridKey: Dispatch<SetStateAction<string | null>>;
|
||||||
|
}
|
||||||
|
|
||||||
function ProxyEdit({
|
function ProxyEdit({
|
||||||
proxyUrls,
|
proxyUrls,
|
||||||
setProxyUrls,
|
setProxyUrls,
|
||||||
|
|
@ -368,8 +375,134 @@ function FebboxKeyEdit({ febboxKey, setFebboxKey }: FebboxKeyProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getRealDebridKeyStatus(realDebridKey: string | null) {
|
||||||
|
if (realDebridKey) {
|
||||||
|
const status: Status = await testRealDebridKey(realDebridKey);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
return "unset";
|
||||||
|
}
|
||||||
|
|
||||||
|
function RealDebridKeyEdit({
|
||||||
|
realDebridKey,
|
||||||
|
setRealDebridKey,
|
||||||
|
}: RealDebridKeyProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const user = useAuthStore();
|
||||||
|
const preferences = usePreferencesStore();
|
||||||
|
const [hasExtension, setHasExtension] = useState(false);
|
||||||
|
|
||||||
|
// Check for extension
|
||||||
|
useEffect(() => {
|
||||||
|
isExtensionActive().then(setHasExtension);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Enable Real Debrid token when account is loaded and we have a token
|
||||||
|
useEffect(() => {
|
||||||
|
if (user.account && realDebridKey === null && preferences.realDebridKey) {
|
||||||
|
setRealDebridKey(preferences.realDebridKey);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
user.account,
|
||||||
|
realDebridKey,
|
||||||
|
preferences.realDebridKey,
|
||||||
|
setRealDebridKey,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [status, setStatus] = useState<Status>("unset");
|
||||||
|
const statusMap: Record<Status, StatusCircleProps["type"]> = {
|
||||||
|
error: "error",
|
||||||
|
success: "success",
|
||||||
|
unset: "noresult",
|
||||||
|
api_down: "error",
|
||||||
|
invalid_token: "error",
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkTokenStatus = async () => {
|
||||||
|
const result = await getRealDebridKeyStatus(realDebridKey);
|
||||||
|
setStatus(result);
|
||||||
|
};
|
||||||
|
checkTokenStatus();
|
||||||
|
}, [realDebridKey]);
|
||||||
|
|
||||||
|
if (conf().ALLOW_REAL_DEBRID_KEY) {
|
||||||
|
return (
|
||||||
|
<SettingsCard>
|
||||||
|
<div className="flex justify-between items-center gap-4">
|
||||||
|
<div className="my-3">
|
||||||
|
<p className="text-white font-bold mb-3">{t("realdebrid.title")}</p>
|
||||||
|
<p className="max-w-[30rem] font-medium">
|
||||||
|
{t("realdebrid.description")}
|
||||||
|
</p>
|
||||||
|
<MwLink>
|
||||||
|
<a
|
||||||
|
href="https://real-debrid.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
real-debrid.com
|
||||||
|
</a>
|
||||||
|
</MwLink>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{!hasExtension && <Icon icon={Icons.UNPLUG} className="text-lg" />}
|
||||||
|
<Toggle
|
||||||
|
onClick={() =>
|
||||||
|
hasExtension
|
||||||
|
? setRealDebridKey((s) => (s === null ? "" : null))
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
enabled={realDebridKey !== null && hasExtension}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{realDebridKey !== null && hasExtension ? (
|
||||||
|
<>
|
||||||
|
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||||
|
<p className="text-white font-bold mb-3">
|
||||||
|
{t("realdebrid.tokenLabel")}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<StatusCircle type={statusMap[status]} className="mx-2 mr-4" />
|
||||||
|
<AuthInputBox
|
||||||
|
onChange={(newToken) => {
|
||||||
|
setRealDebridKey(newToken);
|
||||||
|
}}
|
||||||
|
value={realDebridKey ?? ""}
|
||||||
|
placeholder="ABC123..."
|
||||||
|
passwordToggleable
|
||||||
|
className="flex-grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{status === "error" && (
|
||||||
|
<p className="text-type-danger mt-4">
|
||||||
|
{t("realdebrid.status.failure")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{status === "api_down" && (
|
||||||
|
<p className="text-type-danger mt-4">
|
||||||
|
{t("realdebrid.status.api_down")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{status === "invalid_token" && (
|
||||||
|
<p className="text-type-danger mt-4">
|
||||||
|
{t("realdebrid.status.invalid_token")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</SettingsCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function ConnectionsPart(
|
export function ConnectionsPart(
|
||||||
props: BackendEditProps & ProxyEditProps & FebboxKeyProps,
|
props: BackendEditProps &
|
||||||
|
ProxyEditProps &
|
||||||
|
FebboxKeyProps &
|
||||||
|
RealDebridKeyProps,
|
||||||
) {
|
) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
|
|
@ -387,6 +520,10 @@ export function ConnectionsPart(
|
||||||
backendUrl={props.backendUrl}
|
backendUrl={props.backendUrl}
|
||||||
setBackendUrl={props.setBackendUrl}
|
setBackendUrl={props.setBackendUrl}
|
||||||
/>
|
/>
|
||||||
|
<RealDebridKeyEdit
|
||||||
|
realDebridKey={props.realDebridKey}
|
||||||
|
setRealDebridKey={props.setRealDebridKey}
|
||||||
|
/>
|
||||||
<FebboxKeyEdit
|
<FebboxKeyEdit
|
||||||
febboxKey={props.febboxKey}
|
febboxKey={props.febboxKey}
|
||||||
setFebboxKey={props.setFebboxKey}
|
setFebboxKey={props.setFebboxKey}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { useNavigate } from "react-router-dom";
|
||||||
import { useAsync } from "react-use";
|
import { useAsync } from "react-use";
|
||||||
|
|
||||||
import { isExtensionActive } from "@/backend/extension/messaging";
|
import { isExtensionActive } from "@/backend/extension/messaging";
|
||||||
import { singularProxiedFetch } from "@/backend/helpers/fetch";
|
import { proxiedFetch, singularProxiedFetch } from "@/backend/helpers/fetch";
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
import { Icon, Icons } from "@/components/Icon";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import { Loading } from "@/components/layout/Loading";
|
import { Loading } from "@/components/layout/Loading";
|
||||||
|
|
@ -74,6 +74,7 @@ type SetupData = {
|
||||||
proxy: Status;
|
proxy: Status;
|
||||||
defaultProxy: Status;
|
defaultProxy: Status;
|
||||||
febboxKeyTest?: Status;
|
febboxKeyTest?: Status;
|
||||||
|
realDebridKeyTest?: Status;
|
||||||
};
|
};
|
||||||
|
|
||||||
function testProxy(url: string) {
|
function testProxy(url: string) {
|
||||||
|
|
@ -174,9 +175,59 @@ export async function testFebboxKey(febboxKey: string | null): Promise<Status> {
|
||||||
return "api_down";
|
return "api_down";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function testRealDebridKey(
|
||||||
|
realDebridKey: string | null,
|
||||||
|
): Promise<Status> {
|
||||||
|
if (!realDebridKey) {
|
||||||
|
return "unset";
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxAttempts = 2;
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
while (attempts < maxAttempts) {
|
||||||
|
try {
|
||||||
|
console.log(`RD API attempt ${attempts + 1}`);
|
||||||
|
const data = await proxiedFetch(
|
||||||
|
"https://api.real-debrid.com/rest/1.0/user",
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${realDebridKey}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we have data and it indicates premium status, return success immediately
|
||||||
|
if (data && typeof data === "object" && data.type === "premium") {
|
||||||
|
console.log("RD premium status confirmed");
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("RD response did not indicate premium status");
|
||||||
|
attempts += 1;
|
||||||
|
if (attempts === maxAttempts) {
|
||||||
|
return "invalid_token";
|
||||||
|
}
|
||||||
|
await sleep(3000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("RD API error:", error);
|
||||||
|
attempts += 1;
|
||||||
|
if (attempts === maxAttempts) {
|
||||||
|
return "api_down";
|
||||||
|
}
|
||||||
|
await sleep(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "api_down";
|
||||||
|
}
|
||||||
|
|
||||||
function useIsSetup() {
|
function useIsSetup() {
|
||||||
const proxyUrls = useAuthStore((s) => s.proxySet);
|
const proxyUrls = useAuthStore((s) => s.proxySet);
|
||||||
const febboxKey = usePreferencesStore((s) => s.febboxKey);
|
const febboxKey = usePreferencesStore((s) => s.febboxKey);
|
||||||
|
const realDebridKey = usePreferencesStore((s) => s.realDebridKey);
|
||||||
const { loading, value } = useAsync(async (): Promise<SetupData> => {
|
const { loading, value } = useAsync(async (): Promise<SetupData> => {
|
||||||
const extensionStatus: Status = (await isExtensionActive())
|
const extensionStatus: Status = (await isExtensionActive())
|
||||||
? "success"
|
? "success"
|
||||||
|
|
@ -192,6 +243,7 @@ function useIsSetup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const febboxKeyStatus: Status = await testFebboxKey(febboxKey);
|
const febboxKeyStatus: Status = await testFebboxKey(febboxKey);
|
||||||
|
const realDebridKeyStatus: Status = await testRealDebridKey(realDebridKey);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
extension: extensionStatus,
|
extension: extensionStatus,
|
||||||
|
|
@ -200,20 +252,23 @@ function useIsSetup() {
|
||||||
...(conf().ALLOW_FEBBOX_KEY && {
|
...(conf().ALLOW_FEBBOX_KEY && {
|
||||||
febboxKeyTest: febboxKeyStatus,
|
febboxKeyTest: febboxKeyStatus,
|
||||||
}),
|
}),
|
||||||
|
realDebridKeyTest: realDebridKeyStatus,
|
||||||
};
|
};
|
||||||
}, [proxyUrls, febboxKey]);
|
}, [proxyUrls, febboxKey, realDebridKey]);
|
||||||
|
|
||||||
let globalState: Status = "unset";
|
let globalState: Status = "unset";
|
||||||
if (
|
if (
|
||||||
value?.extension === "success" ||
|
value?.extension === "success" ||
|
||||||
value?.proxy === "success" ||
|
value?.proxy === "success" ||
|
||||||
value?.febboxKeyTest === "success"
|
value?.febboxKeyTest === "success" ||
|
||||||
|
value?.realDebridKeyTest === "success"
|
||||||
)
|
)
|
||||||
globalState = "success";
|
globalState = "success";
|
||||||
if (
|
if (
|
||||||
value?.proxy === "error" ||
|
value?.proxy === "error" ||
|
||||||
value?.extension === "error" ||
|
value?.extension === "error" ||
|
||||||
value?.febboxKeyTest === "error"
|
value?.febboxKeyTest === "error" ||
|
||||||
|
value?.realDebridKeyTest === "error"
|
||||||
)
|
)
|
||||||
globalState = "error";
|
globalState = "error";
|
||||||
|
|
||||||
|
|
@ -354,6 +409,11 @@ export function SetupPart() {
|
||||||
>
|
>
|
||||||
{t("settings.connections.setup.items.default")}
|
{t("settings.connections.setup.items.default")}
|
||||||
</SetupCheckList>
|
</SetupCheckList>
|
||||||
|
{conf().ALLOW_REAL_DEBRID_KEY && (
|
||||||
|
<SetupCheckList status={setupStates.realDebridKeyTest || "unset"}>
|
||||||
|
Real Debrid token
|
||||||
|
</SetupCheckList>
|
||||||
|
)}
|
||||||
{conf().ALLOW_FEBBOX_KEY && (
|
{conf().ALLOW_FEBBOX_KEY && (
|
||||||
<SetupCheckList status={setupStates.febboxKeyTest || "unset"}>
|
<SetupCheckList status={setupStates.febboxKeyTest || "unset"}>
|
||||||
Febbox UI token
|
Febbox UI token
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ interface Config {
|
||||||
ONBOARDING_PROXY_INSTALL_LINK: string;
|
ONBOARDING_PROXY_INSTALL_LINK: string;
|
||||||
ALLOW_AUTOPLAY: boolean;
|
ALLOW_AUTOPLAY: boolean;
|
||||||
ALLOW_FEBBOX_KEY: boolean;
|
ALLOW_FEBBOX_KEY: boolean;
|
||||||
|
ALLOW_REAL_DEBRID_KEY: boolean;
|
||||||
SHOW_AD: boolean;
|
SHOW_AD: boolean;
|
||||||
AD_CONTENT_URL: string;
|
AD_CONTENT_URL: string;
|
||||||
TRACK_SCRIPT: string;
|
TRACK_SCRIPT: string;
|
||||||
|
|
@ -38,6 +39,7 @@ export interface RuntimeConfig {
|
||||||
DMCA_EMAIL: string | null;
|
DMCA_EMAIL: string | null;
|
||||||
TWITTER_LINK: string;
|
TWITTER_LINK: string;
|
||||||
TMDB_READ_API_KEY: string | null;
|
TMDB_READ_API_KEY: string | null;
|
||||||
|
ALLOW_REAL_DEBRID_KEY: boolean;
|
||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
PROXY_URLS: string[];
|
PROXY_URLS: string[];
|
||||||
M3U8_PROXY_URLS: string[];
|
M3U8_PROXY_URLS: string[];
|
||||||
|
|
@ -79,6 +81,7 @@ const env: Record<keyof Config, undefined | string> = {
|
||||||
HAS_ONBOARDING: import.meta.env.VITE_HAS_ONBOARDING,
|
HAS_ONBOARDING: import.meta.env.VITE_HAS_ONBOARDING,
|
||||||
ALLOW_AUTOPLAY: import.meta.env.VITE_ALLOW_AUTOPLAY,
|
ALLOW_AUTOPLAY: import.meta.env.VITE_ALLOW_AUTOPLAY,
|
||||||
ALLOW_FEBBOX_KEY: import.meta.env.VITE_ALLOW_FEBBOX_KEY,
|
ALLOW_FEBBOX_KEY: import.meta.env.VITE_ALLOW_FEBBOX_KEY,
|
||||||
|
ALLOW_REAL_DEBRID_KEY: import.meta.env.VITE_ALLOW_REAL_DEBRID_KEY,
|
||||||
SHOW_AD: import.meta.env.VITE_SHOW_AD,
|
SHOW_AD: import.meta.env.VITE_SHOW_AD,
|
||||||
AD_CONTENT_URL: import.meta.env.VITE_AD_CONTENT_URL,
|
AD_CONTENT_URL: import.meta.env.VITE_AD_CONTENT_URL,
|
||||||
TRACK_SCRIPT: import.meta.env.VITE_TRACK_SCRIPT,
|
TRACK_SCRIPT: import.meta.env.VITE_TRACK_SCRIPT,
|
||||||
|
|
@ -147,6 +150,7 @@ export function conf(): RuntimeConfig {
|
||||||
)
|
)
|
||||||
.filter((v) => v.length === 2), // The format is <beforeA>:<afterA>,<beforeB>:<afterB>
|
.filter((v) => v.length === 2), // The format is <beforeA>:<afterA>,<beforeB>:<afterB>
|
||||||
ALLOW_FEBBOX_KEY: getKey("ALLOW_FEBBOX_KEY", "false") === "true",
|
ALLOW_FEBBOX_KEY: getKey("ALLOW_FEBBOX_KEY", "false") === "true",
|
||||||
|
ALLOW_REAL_DEBRID_KEY: getKey("ALLOW_REAL_DEBRID_KEY", "false") === "true",
|
||||||
SHOW_AD: getKey("SHOW_AD", "false") === "true",
|
SHOW_AD: getKey("SHOW_AD", "false") === "true",
|
||||||
AD_CONTENT_URL: getKey("AD_CONTENT_URL", "")
|
AD_CONTENT_URL: getKey("AD_CONTENT_URL", "")
|
||||||
.split(",")
|
.split(",")
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export interface PreferencesStore {
|
||||||
enableSourceOrder: boolean;
|
enableSourceOrder: boolean;
|
||||||
proxyTmdb: boolean;
|
proxyTmdb: boolean;
|
||||||
febboxKey: string | null;
|
febboxKey: string | null;
|
||||||
|
realDebridKey: string | null;
|
||||||
|
|
||||||
setEnableThumbnails(v: boolean): void;
|
setEnableThumbnails(v: boolean): void;
|
||||||
setEnableAutoplay(v: boolean): void;
|
setEnableAutoplay(v: boolean): void;
|
||||||
|
|
@ -28,6 +29,7 @@ export interface PreferencesStore {
|
||||||
setEnableSourceOrder(v: boolean): void;
|
setEnableSourceOrder(v: boolean): void;
|
||||||
setProxyTmdb(v: boolean): void;
|
setProxyTmdb(v: boolean): void;
|
||||||
setFebboxKey(v: string | null): void;
|
setFebboxKey(v: string | null): void;
|
||||||
|
setRealDebridKey(v: string | null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePreferencesStore = create(
|
export const usePreferencesStore = create(
|
||||||
|
|
@ -45,6 +47,7 @@ export const usePreferencesStore = create(
|
||||||
enableSourceOrder: false,
|
enableSourceOrder: false,
|
||||||
proxyTmdb: false,
|
proxyTmdb: false,
|
||||||
febboxKey: null,
|
febboxKey: null,
|
||||||
|
realDebridKey: null,
|
||||||
setEnableThumbnails(v) {
|
setEnableThumbnails(v) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.enableThumbnails = v;
|
s.enableThumbnails = v;
|
||||||
|
|
@ -105,6 +108,11 @@ export const usePreferencesStore = create(
|
||||||
s.febboxKey = v;
|
s.febboxKey = v;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setRealDebridKey(v) {
|
||||||
|
set((s) => {
|
||||||
|
s.realDebridKey = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "__MW::preferences",
|
name: "__MW::preferences",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue