From 9f5be225b5722630cecdd60da796f8b82c6ab819 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:09:43 -0700 Subject: [PATCH] Inject popup ad for xprime sources Xprime's own site has ads, but people have found pstream (which doesnt have ads) and moves here since there are no ads. Xprime is losing money and is finding it difficult to support the proxies and servers. --- src/assets/locales/en.json | 3 + src/backend/accounts/settings.ts | 2 + .../player/atoms/XPrimeAdOverlay.tsx | 56 +++++++++++++++++++ src/components/player/display/base.ts | 20 +++++++ src/hooks/useSettingsState.ts | 16 +++++- src/pages/Settings.tsx | 12 +++- src/pages/parts/player/PlayerPart.tsx | 2 + src/pages/parts/settings/PreferencesPart.tsx | 27 +++++++++ src/setup/config.ts | 4 ++ src/stores/preferences/index.tsx | 8 +++ 10 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 src/components/player/atoms/XPrimeAdOverlay.tsx diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 0ab32907..10960722 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -1126,6 +1126,9 @@ "doubleClickToSeek": "Double tap to seek", "doubleClickToSeekDescription": "Double tap on the left or right side of the player to seek 10 seconds forward or backward.", "doubleClickToSeekLabel": "Enable double tap to seek", + "disableXPrimeAds": "XPrime ads", + "disableXPrimeAdsDescription": "Disable popup ads and notifications when watching content from XPrime sources. XPrime uses ads to support their service and keep it free for you and us to use.", + "disableXPrimeAdsLabel": "Disable XPrime ads", "sourceOrder": "Reordering sources", "sourceOrderDescription": "Drag and drop to reorder sources. This will determine the order in which sources are checked for the media you are trying to watch. If a source is greyed out, it means the extension is required for that source.

(The default order is best for most users)", "sourceOrderEnableLabel": "Custom source order", diff --git a/src/backend/accounts/settings.ts b/src/backend/accounts/settings.ts index f0f69b33..6e719609 100644 --- a/src/backend/accounts/settings.ts +++ b/src/backend/accounts/settings.ts @@ -35,6 +35,7 @@ export interface SettingsInput { homeSectionOrder?: string[] | null; manualSourceSelection?: boolean; enableDoubleClickToSeek?: boolean; + disableXPrimeAds?: boolean; } export interface SettingsResponse { @@ -69,6 +70,7 @@ export interface SettingsResponse { homeSectionOrder?: string[] | null; manualSourceSelection?: boolean; enableDoubleClickToSeek?: boolean; + disableXPrimeAds?: boolean; } export function updateSettings( diff --git a/src/components/player/atoms/XPrimeAdOverlay.tsx b/src/components/player/atoms/XPrimeAdOverlay.tsx new file mode 100644 index 00000000..f364a20f --- /dev/null +++ b/src/components/player/atoms/XPrimeAdOverlay.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from "react"; + +import { Icon, Icons } from "@/components/Icon"; +import { Flare } from "@/components/utils/Flare"; +import { Transition } from "@/components/utils/Transition"; +import { usePlayerStore } from "@/stores/player/store"; +import { usePreferencesStore } from "@/stores/preferences"; + +export function XPrimeAdOverlay() { + const sourceId = usePlayerStore((s) => s.sourceId); + const status = usePlayerStore((s) => s.status); + const disableXPrimeAds = usePreferencesStore((s) => s.disableXPrimeAds); + const [show, setShow] = useState(false); + + useEffect(() => { + if (sourceId === "xprime" && status === "playing" && !disableXPrimeAds) { + setShow(true); + const timer = setTimeout(() => { + setShow(false); + }, 5000); // Hide after 5 seconds + + return () => clearTimeout(timer); + } + setShow(false); + }, [sourceId, status, disableXPrimeAds]); + + if (!show) { + return null; + } + + return ( + + + + + +
+ + XPrime uses ads, but they can be disabled from settings! + +
+
+
+
+ ); +} diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index 3ccd5756..3b40fc5b 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -18,11 +18,13 @@ import { isUrlAlreadyProxied, } from "@/components/player/utils/proxy"; import { useLanguageStore } from "@/stores/language"; +import { usePlayerStore } from "@/stores/player/store"; import { LoadableSource, SourceQuality, getPreferredQuality, } from "@/stores/player/utils/qualities"; +import { usePreferencesStore } from "@/stores/preferences"; import { processCdnLink } from "@/utils/cdn"; import { canChangeVolume, @@ -381,6 +383,24 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { }); } } + + // Inject popup ad for xprime sources + const sourceId = usePlayerStore.getState().sourceId; + const disableXPrimeAds = usePreferencesStore.getState().disableXPrimeAds; + if ( + sourceId === "xprime" && + !disableXPrimeAds && + !document.querySelector( + 'script[data-cfasync="false"][src*="jg.prisagedibbuk.com"]', + ) + ) { + const script = document.createElement("script"); + script.setAttribute("data-cfasync", "false"); + script.async = true; + script.type = "text/javascript"; + script.src = "//jg.prisagedibbuk.com/r47OViiCQMeGnyQ/131974"; + document.head.appendChild(script); + } }); videoElement.addEventListener("waiting", () => emit("loading", true)); videoElement.addEventListener("volumechange", () => diff --git a/src/hooks/useSettingsState.ts b/src/hooks/useSettingsState.ts index 3ab198b0..7789c6d7 100644 --- a/src/hooks/useSettingsState.ts +++ b/src/hooks/useSettingsState.ts @@ -79,6 +79,7 @@ export function useSettingsState( homeSectionOrder: string[], manualSourceSelection: boolean, enableDoubleClickToSeek: boolean, + disableXPrimeAds: boolean, ) { const [proxyUrlsState, setProxyUrls, resetProxyUrls, proxyUrlsChanged] = useDerived(proxyUrls); @@ -262,6 +263,12 @@ export function useSettingsState( resetEnableDoubleClickToSeek, enableDoubleClickToSeekChanged, ] = useDerived(enableDoubleClickToSeek); + const [ + disableXPrimeAdsState, + setDisableXPrimeAdsState, + resetDisableXPrimeAds, + disableXPrimeAdsChanged, + ] = useDerived(disableXPrimeAds); function reset() { resetTheme(); @@ -299,6 +306,7 @@ export function useSettingsState( resetHomeSectionOrder(); resetManualSourceSelection(); resetEnableDoubleClickToSeek(); + resetDisableXPrimeAds(); } const changed = @@ -336,7 +344,8 @@ export function useSettingsState( enableHoldToBoostChanged || homeSectionOrderChanged || manualSourceSelectionChanged || - enableDoubleClickToSeekChanged; + enableDoubleClickToSeekChanged || + disableXPrimeAdsChanged; return { reset, @@ -516,5 +525,10 @@ export function useSettingsState( set: setEnableDoubleClickToSeekState, changed: enableDoubleClickToSeekChanged, }, + disableXPrimeAds: { + state: disableXPrimeAdsState, + set: setDisableXPrimeAdsState, + changed: disableXPrimeAdsChanged, + }, }; } diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index c4bb6f90..ec961705 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -486,6 +486,9 @@ export function SettingsPage() { (s) => s.setEnableDoubleClickToSeek, ); + const disableXPrimeAds = usePreferencesStore((s) => s.disableXPrimeAds); + const setDisableXPrimeAds = usePreferencesStore((s) => s.setDisableXPrimeAds); + const account = useAuthStore((s) => s.account); const updateProfile = useAuthStore((s) => s.setAccountProfile); const updateDeviceName = useAuthStore((s) => s.updateDeviceName); @@ -557,6 +560,7 @@ export function SettingsPage() { homeSectionOrder, manualSourceSelection, enableDoubleClickToSeek, + disableXPrimeAds, ); const availableSources = useMemo(() => { @@ -622,7 +626,8 @@ export function SettingsPage() { state.enableHoldToBoost.changed || state.homeSectionOrder.changed || state.manualSourceSelection.changed || - state.enableDoubleClickToSeek + state.enableDoubleClickToSeek || + state.disableXPrimeAds.changed ) { await updateSettings(backendUrl, account, { applicationLanguage: state.appLanguage.state, @@ -651,6 +656,7 @@ export function SettingsPage() { homeSectionOrder: state.homeSectionOrder.state, manualSourceSelection: state.manualSourceSelection.state, enableDoubleClickToSeek: state.enableDoubleClickToSeek.state, + disableXPrimeAds: state.disableXPrimeAds.state, }); } if (state.deviceName.changed) { @@ -705,6 +711,7 @@ export function SettingsPage() { setHomeSectionOrder(state.homeSectionOrder.state); setManualSourceSelection(state.manualSourceSelection.state); setEnableDoubleClickToSeek(state.enableDoubleClickToSeek.state); + setDisableXPrimeAds(state.disableXPrimeAds.state); if (state.profile.state) { updateProfile(state.profile.state); @@ -757,6 +764,7 @@ export function SettingsPage() { setHomeSectionOrder, setManualSourceSelection, setEnableDoubleClickToSeek, + setDisableXPrimeAds, ]); return ( @@ -838,6 +846,8 @@ export function SettingsPage() { setManualSourceSelection={state.manualSourceSelection.set} enableDoubleClickToSeek={state.enableDoubleClickToSeek.state} setEnableDoubleClickToSeek={state.enableDoubleClickToSeek.set} + disableXPrimeAds={state.disableXPrimeAds.state} + setDisableXPrimeAds={state.disableXPrimeAds.set} /> )} diff --git a/src/pages/parts/player/PlayerPart.tsx b/src/pages/parts/player/PlayerPart.tsx index 66c7234f..146dd173 100644 --- a/src/pages/parts/player/PlayerPart.tsx +++ b/src/pages/parts/player/PlayerPart.tsx @@ -5,6 +5,7 @@ import { Player } from "@/components/player"; import { SkipIntroButton } from "@/components/player/atoms/SkipIntroButton"; import { UnreleasedEpisodeOverlay } from "@/components/player/atoms/UnreleasedEpisodeOverlay"; import { WatchPartyStatus } from "@/components/player/atoms/WatchPartyStatus"; +import { XPrimeAdOverlay } from "@/components/player/atoms/XPrimeAdOverlay"; import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls"; import { useSkipTime } from "@/components/player/hooks/useSkipTime"; import { useIsMobile } from "@/hooks/useIsMobile"; @@ -227,6 +228,7 @@ export function PlayerPart(props: PlayerPartProps) { + void; enableDoubleClickToSeek: boolean; setEnableDoubleClickToSeek: (v: boolean) => void; + disableXPrimeAds: boolean; + setDisableXPrimeAds: (v: boolean) => void; }) { const { t } = useTranslation(); const sorted = sortLangCodes(appLanguageOptions.map((item) => item.code)); @@ -184,6 +187,7 @@ export function PreferencesPart(props: { )} + {/* Low Performance Mode */}

@@ -244,6 +248,29 @@ export function PreferencesPart(props: {

+ + {/* Disable XPrime Ads */} + {conf().XPRIME_ADS && ( +
+

+ {t("settings.preferences.disableXPrimeAds")} +

+

+ {t("settings.preferences.disableXPrimeAdsDescription")} +

+
+ props.setDisableXPrimeAds(!props.disableXPrimeAds) + } + className="bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg" + > + +

+ {t("settings.preferences.disableXPrimeAdsLabel")} +

+
+
+ )} {/* Column */} diff --git a/src/setup/config.ts b/src/setup/config.ts index d84bf55c..1e2eaf03 100644 --- a/src/setup/config.ts +++ b/src/setup/config.ts @@ -33,6 +33,7 @@ interface Config { BANNER_MESSAGE: string; BANNER_ID: string; USE_TRAKT: boolean; + XPRIME_ADS: boolean; } export interface RuntimeConfig { @@ -62,6 +63,7 @@ export interface RuntimeConfig { BANNER_MESSAGE: string | null; BANNER_ID: string | null; USE_TRAKT: boolean; + XPRIME_ADS: boolean; } const env: Record = { @@ -94,6 +96,7 @@ const env: Record = { BANNER_MESSAGE: import.meta.env.VITE_BANNER_MESSAGE, BANNER_ID: import.meta.env.VITE_BANNER_ID, USE_TRAKT: import.meta.env.VITE_USE_TRAKT, + XPRIME_ADS: import.meta.env.VITE_XPRIME_ADS, }; function coerceUndefined(value: string | null | undefined): string | undefined { @@ -169,5 +172,6 @@ export function conf(): RuntimeConfig { BANNER_MESSAGE: getKey("BANNER_MESSAGE"), BANNER_ID: getKey("BANNER_ID"), USE_TRAKT: getKey("USE_TRAKT", "false") === "true", + XPRIME_ADS: getKey("XPRIME_ADS", "false") === "true", }; } diff --git a/src/stores/preferences/index.tsx b/src/stores/preferences/index.tsx index b5ef6ff1..f3a9c54a 100644 --- a/src/stores/preferences/index.tsx +++ b/src/stores/preferences/index.tsx @@ -30,6 +30,7 @@ export interface PreferencesStore { homeSectionOrder: string[]; manualSourceSelection: boolean; enableDoubleClickToSeek: boolean; + disableXPrimeAds: boolean; setEnableThumbnails(v: boolean): void; setEnableAutoplay(v: boolean): void; @@ -58,6 +59,7 @@ export interface PreferencesStore { setHomeSectionOrder(v: string[]): void; setManualSourceSelection(v: boolean): void; setEnableDoubleClickToSeek(v: boolean): void; + setDisableXPrimeAds(v: boolean): void; } export const usePreferencesStore = create( @@ -90,6 +92,7 @@ export const usePreferencesStore = create( homeSectionOrder: ["watching", "bookmarks"], manualSourceSelection: false, enableDoubleClickToSeek: false, + disableXPrimeAds: false, setEnableThumbnails(v) { set((s) => { s.enableThumbnails = v; @@ -230,6 +233,11 @@ export const usePreferencesStore = create( s.enableDoubleClickToSeek = v; }); }, + setDisableXPrimeAds(v) { + set((s) => { + s.disableXPrimeAds = v; + }); + }, })), { name: "__MW::preferences",