mirror of
https://github.com/p-stream/p-stream.git
synced 2026-03-28 19:38:43 +00:00
Add custom febbox ui token support
This commit is contained in:
parent
cb10a604ff
commit
9d42c4e8d1
9 changed files with 251 additions and 6 deletions
|
|
@ -8,6 +8,7 @@ export interface SettingsInput {
|
|||
applicationTheme?: string | null;
|
||||
defaultSubtitleLanguage?: string;
|
||||
proxyUrls?: string[] | null;
|
||||
febboxToken?: string | null;
|
||||
}
|
||||
|
||||
export interface SettingsResponse {
|
||||
|
|
@ -15,6 +16,7 @@ export interface SettingsResponse {
|
|||
applicationLanguage?: string | null;
|
||||
defaultSubtitleLanguage?: string | null;
|
||||
proxyUrls?: string[] | null;
|
||||
febboxToken?: string | null;
|
||||
}
|
||||
|
||||
export function updateSettings(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ export function MwLink(props: {
|
|||
</span>
|
||||
);
|
||||
|
||||
if (isExternal) return <a href={props.url}>{content}</a>;
|
||||
if (isExternal)
|
||||
return (
|
||||
<a href={props.url} target="_blank" rel="noreferrer">
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
if (isInternal) return <LinkRouter to={props.to ?? ""}>{content}</LinkRouter>;
|
||||
return (
|
||||
<span onClick={() => props.onClick && props.onClick()}>{content}</span>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export function useAuthData() {
|
|||
const setAccount = useAuthStore((s) => s.setAccount);
|
||||
const removeAccount = useAuthStore((s) => s.removeAccount);
|
||||
const setProxySet = useAuthStore((s) => s.setProxySet);
|
||||
const setFebboxToken = useAuthStore((s) => s.setFebboxToken);
|
||||
const clearBookmarks = useBookmarkStore((s) => s.clear);
|
||||
const clearProgress = useProgressStore((s) => s.clear);
|
||||
const setTheme = useThemeStore((s) => s.setTheme);
|
||||
|
|
@ -85,6 +86,10 @@ export function useAuthData() {
|
|||
if (settings.proxyUrls) {
|
||||
setProxySet(settings.proxyUrls);
|
||||
}
|
||||
|
||||
if (settings.febboxToken) {
|
||||
setFebboxToken(settings.febboxToken);
|
||||
}
|
||||
},
|
||||
[
|
||||
replaceBookmarks,
|
||||
|
|
@ -93,6 +98,7 @@ export function useAuthData() {
|
|||
importSubtitleLanguage,
|
||||
setTheme,
|
||||
setProxySet,
|
||||
setFebboxToken,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export function useSettingsState(
|
|||
deviceName: string,
|
||||
proxyUrls: string[] | null,
|
||||
backendUrl: string | null,
|
||||
febboxToken: string | null,
|
||||
profile:
|
||||
| {
|
||||
colorA: string;
|
||||
|
|
@ -60,6 +61,12 @@ export function useSettingsState(
|
|||
useDerived(proxyUrls);
|
||||
const [backendUrlState, setBackendUrl, resetBackendUrl, backendUrlChanged] =
|
||||
useDerived(backendUrl);
|
||||
const [
|
||||
febboxTokenState,
|
||||
setFebboxToken,
|
||||
resetFebboxToken,
|
||||
febboxTokenChanged,
|
||||
] = useDerived(febboxToken);
|
||||
const [themeState, setTheme, resetTheme, themeChanged] = useDerived(theme);
|
||||
const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
|
||||
const resetPreviewTheme = useCallback(
|
||||
|
|
@ -120,6 +127,7 @@ export function useSettingsState(
|
|||
resetSubStyling();
|
||||
resetProxyUrls();
|
||||
resetBackendUrl();
|
||||
resetFebboxToken();
|
||||
resetDeviceName();
|
||||
resetProfile();
|
||||
resetEnableThumbnails();
|
||||
|
|
@ -136,6 +144,7 @@ export function useSettingsState(
|
|||
deviceNameChanged ||
|
||||
backendUrlChanged ||
|
||||
proxyUrlsChanged ||
|
||||
febboxTokenChanged ||
|
||||
profileChanged ||
|
||||
enableThumbnailsChanged ||
|
||||
enableAutoplayChanged ||
|
||||
|
|
@ -176,6 +185,11 @@ export function useSettingsState(
|
|||
set: setBackendUrl,
|
||||
changed: backendUrlChanged,
|
||||
},
|
||||
febboxToken: {
|
||||
state: febboxTokenState,
|
||||
set: setFebboxToken,
|
||||
changed: febboxTokenChanged,
|
||||
},
|
||||
profile: {
|
||||
state: profileState,
|
||||
set: setProfileState,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,9 @@ export function SettingsPage() {
|
|||
const backendUrlSetting = useAuthStore((s) => s.backendUrl);
|
||||
const setBackendUrl = useAuthStore((s) => s.setBackendUrl);
|
||||
|
||||
const febboxToken = useAuthStore((s) => s.febboxToken);
|
||||
const setFebboxToken = useAuthStore((s) => s.setFebboxToken);
|
||||
|
||||
const enableThumbnails = usePreferencesStore((s) => s.enableThumbnails);
|
||||
const setEnableThumbnails = usePreferencesStore((s) => s.setEnableThumbnails);
|
||||
|
||||
|
|
@ -168,6 +171,7 @@ export function SettingsPage() {
|
|||
decryptedName,
|
||||
proxySet,
|
||||
backendUrlSetting,
|
||||
febboxToken,
|
||||
account?.profile,
|
||||
enableThumbnails,
|
||||
enableAutoplay,
|
||||
|
|
@ -216,12 +220,14 @@ export function SettingsPage() {
|
|||
if (
|
||||
state.appLanguage.changed ||
|
||||
state.theme.changed ||
|
||||
state.proxyUrls.changed
|
||||
state.proxyUrls.changed ||
|
||||
state.febboxToken.changed
|
||||
) {
|
||||
await updateSettings(backendUrl, account, {
|
||||
applicationLanguage: state.appLanguage.state,
|
||||
applicationTheme: state.theme.state,
|
||||
proxyUrls: state.proxyUrls.state?.filter((v) => v !== "") ?? null,
|
||||
febboxToken: state.febboxToken.state,
|
||||
});
|
||||
}
|
||||
if (state.deviceName.changed) {
|
||||
|
|
@ -250,6 +256,7 @@ export function SettingsPage() {
|
|||
setSubStyling(state.subtitleStyling.state);
|
||||
setProxySet(state.proxyUrls.state?.filter((v) => v !== "") ?? null);
|
||||
setEnableSourceOrder(state.enableSourceOrder.state);
|
||||
setFebboxToken(state.febboxToken.state);
|
||||
|
||||
if (state.profile.state) {
|
||||
updateProfile(state.profile.state);
|
||||
|
|
@ -270,6 +277,7 @@ export function SettingsPage() {
|
|||
account,
|
||||
backendUrl,
|
||||
setEnableThumbnails,
|
||||
setFebboxToken,
|
||||
state,
|
||||
setEnableAutoplay,
|
||||
setEnableDiscover,
|
||||
|
|
@ -352,6 +360,8 @@ export function SettingsPage() {
|
|||
setBackendUrl={state.backendUrl.set}
|
||||
proxyUrls={state.proxyUrls.state}
|
||||
setProxyUrls={state.proxyUrls.set}
|
||||
febboxToken={state.febboxToken.state}
|
||||
setFebboxToken={state.febboxToken.set}
|
||||
/>
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@ import { useState } from "react";
|
|||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { Toggle } from "@/components/buttons/Toggle";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { SettingsCard } from "@/components/layout/SettingsCard";
|
||||
import { Stepper } from "@/components/layout/Stepper";
|
||||
import { BiggerCenterContainer } from "@/components/layout/ThinContainer";
|
||||
import { VerticalLine } from "@/components/layout/VerticalLine";
|
||||
import { Modal, ModalCard, useModal } from "@/components/overlays/Modal";
|
||||
import { MwLink } from "@/components/text/Link";
|
||||
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||
import { Divider } from "@/components/utils/Divider";
|
||||
import { Heading1, Heading2, Paragraph } from "@/components/utils/Text";
|
||||
import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
|
||||
import {
|
||||
|
|
@ -20,10 +25,85 @@ import {
|
|||
MiniCardContent,
|
||||
} from "@/pages/onboarding/utils";
|
||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { getProxyUrls } from "@/utils/proxyUrls";
|
||||
|
||||
import { PopupModal } from "../parts/home/PopupModal";
|
||||
|
||||
export function OptionalDropdown() {
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const febboxToken = useAuthStore((s) => s.febboxToken);
|
||||
const setFebboxToken = useAuthStore((s) => s.setFebboxToken);
|
||||
|
||||
return (
|
||||
<div className="mt-12">
|
||||
<SettingsCard>
|
||||
<div className="flex justify-between items-center gap-4">
|
||||
<div className="my-3">
|
||||
<p className="text-white font-bold mb-3">
|
||||
Optional: FED API (Febbox) UI token
|
||||
</p>
|
||||
<p className="max-w-[30rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.febbox.description">
|
||||
Bringing your own UI token allows you to get faster 4K streams.
|
||||
We only have a limited number of tokens, so bringing your own
|
||||
helps speed your streams when traffic is high.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Toggle
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
enabled={isExpanded}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded ? (
|
||||
<>
|
||||
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||
|
||||
<div className="my-3">
|
||||
<p className="max-w-[30rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.febbox.description">
|
||||
To get your UI token:
|
||||
<br />
|
||||
1. Go to <MwLink to="https://febbox.com">
|
||||
febbox.com
|
||||
</MwLink>{" "}
|
||||
and log in with Google
|
||||
<br />
|
||||
2. Open DevTools or inspect the page
|
||||
<br />
|
||||
3. Go to Application tab → Cookies
|
||||
<br />
|
||||
4. Copy the "ui" cookie.
|
||||
<br />
|
||||
5. Close the tab, but do NOT logout!
|
||||
</Trans>
|
||||
</p>
|
||||
<p className="text-xs mt-2">
|
||||
(This is not a sensitive login cookie or account token)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||
<p className="text-white font-bold mb-3">
|
||||
{t("settings.connections.febbox.tokenLabel", "Token")}
|
||||
</p>
|
||||
<AuthInputBox
|
||||
onChange={(newToken) => {
|
||||
setFebboxToken(newToken);
|
||||
}}
|
||||
value={febboxToken ?? ""}
|
||||
placeholder="eyABCdE..."
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</SettingsCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export function OnboardingPage() {
|
||||
const navigate = useNavigateOnboarding();
|
||||
const skipModal = useModal("skip");
|
||||
|
|
@ -97,6 +177,15 @@ export function OnboardingPage() {
|
|||
might be slower due to shared bandwidth.
|
||||
<br />
|
||||
<br />
|
||||
<strong>Optional FED API (Febbox) UI token</strong>
|
||||
<br />
|
||||
Bringing your own UI token allows you to get faster 4K streams.
|
||||
Each Febbox account has 100gb/mo bandwidth and we only have a
|
||||
limited ammount of accounts. By bringing your own you get that
|
||||
all to yourself! This is not an account token and is only used
|
||||
to get stream links from Febbox's API.
|
||||
<br />
|
||||
<br />
|
||||
If you have more questions on how this works, feel free to ask
|
||||
on the{" "}
|
||||
<a
|
||||
|
|
@ -239,6 +328,8 @@ export function OnboardingPage() {
|
|||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<OptionalDropdown />
|
||||
</BiggerCenterContainer>
|
||||
</MinimalPageLayout>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
|||
defaultSubtitleLanguage: defaultSubtitleLanguage ?? undefined,
|
||||
applicationTheme: applicationTheme ?? undefined,
|
||||
proxyUrls: undefined,
|
||||
febboxToken: undefined,
|
||||
});
|
||||
|
||||
await restore(account);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ interface BackendEditProps {
|
|||
setBackendUrl: Dispatch<SetStateAction<string | null>>;
|
||||
}
|
||||
|
||||
interface FebboxTokenProps {
|
||||
febboxToken: string | null;
|
||||
setFebboxToken: Dispatch<SetStateAction<string | null>>;
|
||||
}
|
||||
|
||||
function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
|
||||
const { t } = useTranslation();
|
||||
const add = useCallback(() => {
|
||||
|
|
@ -54,7 +59,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
|
|||
<p className="text-white font-bold mb-3">
|
||||
{t("settings.connections.workers.label")}
|
||||
</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
<p className="max-w-[30rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.workers.description">
|
||||
<MwLink to="https://docs.undi.rest/proxy/deploy">
|
||||
Proxy documentation
|
||||
|
|
@ -125,7 +130,7 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
|||
<p className="text-white font-bold mb-3">
|
||||
{t("settings.connections.server.label")}
|
||||
</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
<p className="max-w-[30rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.server.description">
|
||||
<MwLink to="https://docs.undi.rest/backend/deploy">
|
||||
Backend documentation
|
||||
|
|
@ -135,7 +140,7 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
|||
{user.account && (
|
||||
<div>
|
||||
<br />
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
<p className="max-w-[30rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.server.migration.description">
|
||||
<MwLink to="/migration">
|
||||
{t("settings.connections.server.migration.link")}
|
||||
|
|
@ -169,7 +174,75 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function ConnectionsPart(props: BackendEditProps & ProxyEditProps) {
|
||||
function FebboxTokenEdit({ febboxToken, setFebboxToken }: FebboxTokenProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SettingsCard>
|
||||
<div className="flex justify-between items-center gap-4">
|
||||
<div className="my-3">
|
||||
<p className="text-white font-bold mb-3">FED API (Febbox) UI token</p>
|
||||
<p className="max-w-[30rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.febbox.description">
|
||||
Bringing your own UI token allows you to get faster 4K streams. We
|
||||
only have a limited number of tokens, so bringing your own helps
|
||||
speed your streams when traffic is high.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Toggle
|
||||
onClick={() => setFebboxToken((s) => (s === null ? "" : null))}
|
||||
enabled={febboxToken !== null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{febboxToken !== null ? (
|
||||
<>
|
||||
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||
|
||||
<div className="my-3">
|
||||
<p className="max-w-[30rem] font-medium">
|
||||
<Trans i18nKey="settings.connections.febbox.description">
|
||||
To get your UI token:
|
||||
<br />
|
||||
1. Go to <MwLink to="https://febbox.com">febbox.com</MwLink> and
|
||||
log in with Google
|
||||
<br />
|
||||
2. Open DevTools or inspect the page
|
||||
<br />
|
||||
3. Go to Application tab → Cookies
|
||||
<br />
|
||||
4. Copy the "ui" cookie.
|
||||
<br />
|
||||
5. Close the tab, but do NOT logout!
|
||||
</Trans>
|
||||
</p>
|
||||
<p className="text-xs mt-2">
|
||||
(This is not a sensitive login cookie or account token)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Divider marginClass="my-6 px-8 box-content -mx-8" />
|
||||
<p className="text-white font-bold mb-3">
|
||||
{t("settings.connections.febbox.tokenLabel", "Token")}
|
||||
</p>
|
||||
<AuthInputBox
|
||||
onChange={(newToken) => {
|
||||
setFebboxToken(newToken);
|
||||
}}
|
||||
value={febboxToken ?? ""}
|
||||
placeholder="eyABCdE..."
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</SettingsCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function ConnectionsPart(
|
||||
props: BackendEditProps & ProxyEditProps & FebboxTokenProps,
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -184,6 +257,10 @@ export function ConnectionsPart(props: BackendEditProps & ProxyEditProps) {
|
|||
backendUrl={props.backendUrl}
|
||||
setBackendUrl={props.setBackendUrl}
|
||||
/>
|
||||
<FebboxTokenEdit
|
||||
febboxToken={props.febboxToken}
|
||||
setFebboxToken={props.setFebboxToken}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface AuthStore {
|
|||
account: null | AccountWithToken;
|
||||
backendUrl: null | string;
|
||||
proxySet: null | string[];
|
||||
febboxToken: null | string;
|
||||
removeAccount(): void;
|
||||
setAccount(acc: AccountWithToken): void;
|
||||
updateDeviceName(deviceName: string): void;
|
||||
|
|
@ -29,6 +30,7 @@ interface AuthStore {
|
|||
setAccountProfile(acc: Account["profile"]): void;
|
||||
setBackendUrl(url: null | string): void;
|
||||
setProxySet(urls: null | string[]): void;
|
||||
setFebboxToken(token: null | string): void;
|
||||
}
|
||||
|
||||
export const useAuthStore = create(
|
||||
|
|
@ -37,6 +39,7 @@ export const useAuthStore = create(
|
|||
account: null,
|
||||
backendUrl: null,
|
||||
proxySet: null,
|
||||
febboxToken: null,
|
||||
setAccount(acc) {
|
||||
set((s) => {
|
||||
s.account = acc;
|
||||
|
|
@ -57,6 +60,20 @@ export const useAuthStore = create(
|
|||
s.proxySet = urls;
|
||||
});
|
||||
},
|
||||
setFebboxToken(token) {
|
||||
set((s) => {
|
||||
s.febboxToken = token;
|
||||
});
|
||||
try {
|
||||
if (token === null) {
|
||||
localStorage.removeItem("febbox_ui_token");
|
||||
} else {
|
||||
localStorage.setItem("febbox_ui_token", token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to access localStorage:", e);
|
||||
}
|
||||
},
|
||||
setAccountProfile(profile) {
|
||||
set((s) => {
|
||||
if (s.account) {
|
||||
|
|
@ -82,6 +99,28 @@ export const useAuthStore = create(
|
|||
})),
|
||||
{
|
||||
name: "__MW::auth",
|
||||
migrate: (persistedState: any) => {
|
||||
// Migration from localStorage to Zustand store
|
||||
if (!persistedState.febboxToken) {
|
||||
try {
|
||||
const storedToken = localStorage.getItem("febbox_ui_token");
|
||||
if (storedToken) persistedState.febboxToken = storedToken;
|
||||
} catch (e) {
|
||||
console.warn("LocalStorage access failed during migration:", e);
|
||||
}
|
||||
}
|
||||
return persistedState;
|
||||
},
|
||||
onRehydrateStorage: () => (state) => {
|
||||
// After store rehydration
|
||||
if (state?.febboxToken) {
|
||||
try {
|
||||
localStorage.setItem("febbox_ui_token", state.febboxToken);
|
||||
} catch (e) {
|
||||
console.warn("Failed to sync token to localStorage:", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue