From 33ea657a5c4627a44ab632240473bcf74233f774 Mon Sep 17 00:00:00 2001 From: sarendsen Date: Thu, 9 Jan 2025 10:13:57 +0100 Subject: [PATCH 1/3] Filter out duplicate names in media sources --- components/MediaSourceSelector.tsx | 31 +++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/components/MediaSourceSelector.tsx b/components/MediaSourceSelector.tsx index 34f02fd9..4888692a 100644 --- a/components/MediaSourceSelector.tsx +++ b/components/MediaSourceSelector.tsx @@ -29,6 +29,27 @@ export const MediaSourceSelector: React.FC = ({ [item, selected] ); + const commonPrefix = useMemo(() => { + const mediaSources = item.MediaSources || []; + if (!mediaSources.length) return ""; + + let commonPrefix = ""; + for (let i = 0; i < mediaSources[0].Name.length; i++) { + const char = mediaSources[0].Name[i]; + if (mediaSources.every((source) => source.Name[i] === char)) { + commonPrefix += char; + } else { + commonPrefix = commonPrefix.slice(0, -1); + break; + } + } + return commonPrefix; + }, [item.MediaSources]); + + const name = (name?: string | null) => { + return name?.replace(commonPrefix, "").toLowerCase(); + }; + return ( = ({ }} > - {`${name(source.Name)} - ${convertBitsToMegabitsOrGigabits( - source.Size - )}`} + {`${name(source.Name)}`} ))} @@ -74,9 +93,3 @@ export const MediaSourceSelector: React.FC = ({ ); }; - -const name = (name?: string | null) => { - if (name && name.length > 40) - return name.substring(0, 20) + " [...] " + name.substring(name.length - 20); - return name; -}; From e1720a00da611794cbd5ea950a476c41619f9a2b Mon Sep 17 00:00:00 2001 From: herrrta <73949927+herrrta@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:52:31 -0500 Subject: [PATCH 2/3] initial changes --- augmentations/mmkv.ts | 7 +++- providers/JellyfinProvider.tsx | 18 +++++++++- utils/atoms/settings.ts | 60 ++++++++++++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/augmentations/mmkv.ts b/augmentations/mmkv.ts index 80fbeede..5667502f 100644 --- a/augmentations/mmkv.ts +++ b/augmentations/mmkv.ts @@ -13,5 +13,10 @@ MMKV.prototype.get = function (key: string): T | undefined { } MMKV.prototype.setAny = function (key: string, value: any | undefined): void { - this.set(key, JSON.stringify(value)); + if (value === undefined) { + this.delete(key) + } + else { + this.set(key, JSON.stringify(value)); + } } \ No newline at end of file diff --git a/providers/JellyfinProvider.tsx b/providers/JellyfinProvider.tsx index f4ccce75..2b602323 100644 --- a/providers/JellyfinProvider.tsx +++ b/providers/JellyfinProvider.tsx @@ -1,3 +1,4 @@ +import "@/augmentations"; import { useInterval } from "@/hooks/useInterval"; import { storage } from "@/utils/mmkv"; import { Api, Jellyfin } from "@jellyfin/sdk"; @@ -19,7 +20,8 @@ import React, { import { Platform } from "react-native"; import uuid from "react-native-uuid"; import { getDeviceName } from "react-native-device-info"; -import { toast } from "sonner-native"; +import { useSettings } from "@/utils/atoms/settings"; +import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr"; interface Server { address: string; @@ -70,6 +72,8 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ const [user, setUser] = useAtom(userAtom); const [isPolling, setIsPolling] = useState(false); const [secret, setSecret] = useState(null); + const [settings, updateSettings, pluginSettings, setPluginSettings, refreshStreamyfinPluginSettings] = useSettings(); + const { clearAllJellyseerData, setJellyseerrUser } = useJellyseerr(); useQuery({ queryKey: ["user", api], @@ -226,6 +230,16 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ storage.set("user", JSON.stringify(auth.data.User)); setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken)); storage.set("token", auth.data?.AccessToken); + + const recentPluginSettings = await refreshStreamyfinPluginSettings(); + if (recentPluginSettings?.jellyseerrServerUrl?.value) { + const jellyseerrApi = new JellyseerrApi(recentPluginSettings.jellyseerrServerUrl.value); + await jellyseerrApi.test().then((result) => { + if (result.isValid && result.requiresPass) { + jellyseerrApi.login(username, password).then(setJellyseerrUser); + } + }) + } } } catch (error) { if (axios.isAxiosError(error)) { @@ -262,6 +276,8 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({ mutationFn: async () => { storage.delete("token"); setUser(null); + setPluginSettings(undefined); + await clearAllJellyseerData(); }, onError: (error) => { console.error("Logout failed:", error); diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index b473198d..eac6826e 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -1,12 +1,19 @@ import { atom, useAtom } from "jotai"; -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; import * as ScreenOrientation from "expo-screen-orientation"; import { storage } from "../mmkv"; import { Platform } from "react-native"; import { CultureDto, + PluginStatus, SubtitlePlaybackMode, } from "@jellyfin/sdk/lib/generated-client"; +import {apiAtom} from "@/providers/JellyfinProvider"; +import {getPluginsApi} from "@jellyfin/sdk/lib/utils/api"; +import {writeErrorLog} from "@/utils/log"; +import {AUTHORIZATION_HEADER} from "@jellyfin/sdk"; + +const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS" export type DownloadQuality = "original" | "high" | "low"; @@ -92,6 +99,13 @@ export type Settings = { hiddenLibraries?: string[]; }; +export interface Lockable { + lockable: boolean; + value: T +} + +export type PluginLockableSettings = { [K in keyof Settings]: Lockable }; + const loadSettings = (): Settings => { const defaultValues: Settings = { autoRotate: true, @@ -150,9 +164,12 @@ const saveSettings = (settings: Settings) => { }; export const settingsAtom = atom(null); +export const pluginSettingsAtom = atom(storage.get(STREAMYFIN_PLUGIN_SETTINGS)); export const useSettings = () => { + const [api] = useAtom(apiAtom); const [settings, setSettings] = useAtom(settingsAtom); + const [pluginSettings, _setPluginSettings] = useAtom(pluginSettingsAtom); useEffect(() => { if (settings === null) { @@ -161,6 +178,45 @@ export const useSettings = () => { } }, [settings, setSettings]); + const setPluginSettings = useCallback((settings: PluginLockableSettings | undefined) => { + storage.setAny(STREAMYFIN_PLUGIN_SETTINGS, settings) + _setPluginSettings(settings) + }, + [_setPluginSettings] + ) + + const refreshStreamyfinPluginSettings = useCallback( + async () => { + if (!api) + return + + const plugins = await getPluginsApi(api).getPlugins().then(({data}) => data); + + if (plugins && plugins.length > 0) { + const streamyfinPlugin = plugins.find(plugin => plugin.Name === "Streamyfin"); + + if (streamyfinPlugin?.Status != PluginStatus.Active) { + writeErrorLog( + "Streamyfin plugin is currently not active.\n" + + `Current status is: ${streamyfinPlugin?.Status}` + ); + setPluginSettings(undefined); + return; + } + + const settings = await api.axiosInstance + .get(`${api.basePath}/Streamyfin/config`, { headers: { [AUTHORIZATION_HEADER]: api.authorizationHeader } }) + .then(response => { + return response.data['settings'] as PluginLockableSettings + }) + + setPluginSettings(settings); + return settings; + } + }, + [api] + ) + const updateSettings = (update: Partial) => { if (settings) { const newSettings = { ...settings, ...update }; @@ -170,5 +226,5 @@ export const useSettings = () => { } }; - return [settings, updateSettings] as const; + return [settings, updateSettings, pluginSettings, setPluginSettings, refreshStreamyfinPluginSettings] as const; }; From 54af64abeffe79ccae9110a35845bd7895ae4710 Mon Sep 17 00:00:00 2001 From: herrrta <73949927+herrrta@users.noreply.github.com> Date: Thu, 9 Jan 2025 08:51:53 -0500 Subject: [PATCH 3/3] api augmentations & added streamyfin plugin id --- augmentations/api.ts | 30 ++++++++++++++++++++++++++++++ utils/atoms/settings.ts | 16 ++++++++-------- 2 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 augmentations/api.ts diff --git a/augmentations/api.ts b/augmentations/api.ts new file mode 100644 index 00000000..17673298 --- /dev/null +++ b/augmentations/api.ts @@ -0,0 +1,30 @@ +import {Api, AUTHORIZATION_HEADER} from "@jellyfin/sdk"; +import {AxiosRequestConfig, AxiosResponse} from "axios"; +import {StreamyfinPluginConfig} from "@/utils/atoms/settings"; + +declare module '@jellyfin/sdk' { + interface Api { + get(url: string, config?: AxiosRequestConfig): Promise> + post(url: string, data: D, config?: AxiosRequestConfig): Promise> + getStreamyfinPluginConfig(): Promise> + } +} + +Api.prototype.get = function (url: string, config: AxiosRequestConfig = {}): Promise> { + return this.axiosInstance.get(`${this.basePath}${url}`, { + ...(config ?? {}), + headers: { [AUTHORIZATION_HEADER]: this.authorizationHeader } + }) +} + +Api.prototype.post = function (url: string, data: D, config: AxiosRequestConfig): Promise> { + return this.axiosInstance.post(`${this.basePath}${url}`, { + ...(config || {}), + data, + headers: { [AUTHORIZATION_HEADER]: this.authorizationHeader }} + ) +} + +Api.prototype.getStreamyfinPluginConfig = function (): Promise> { + return this.get("/Streamyfin/config") +} \ No newline at end of file diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index eac6826e..becb1ea8 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -11,8 +11,8 @@ import { import {apiAtom} from "@/providers/JellyfinProvider"; import {getPluginsApi} from "@jellyfin/sdk/lib/utils/api"; import {writeErrorLog} from "@/utils/log"; -import {AUTHORIZATION_HEADER} from "@jellyfin/sdk"; +const STREAMYFIN_PLUGIN_ID = "1e9e5d38-6e67-4615-8719-e98a5c34f004" const STREAMYFIN_PLUGIN_SETTINGS = "STREAMYFIN_PLUGIN_SETTINGS" export type DownloadQuality = "original" | "high" | "low"; @@ -105,6 +105,9 @@ export interface Lockable { } export type PluginLockableSettings = { [K in keyof Settings]: Lockable }; +export type StreamyfinPluginConfig = { + settings: PluginLockableSettings +} const loadSettings = (): Settings => { const defaultValues: Settings = { @@ -193,9 +196,9 @@ export const useSettings = () => { const plugins = await getPluginsApi(api).getPlugins().then(({data}) => data); if (plugins && plugins.length > 0) { - const streamyfinPlugin = plugins.find(plugin => plugin.Name === "Streamyfin"); + const streamyfinPlugin = plugins.find(plugin => plugin.Id === STREAMYFIN_PLUGIN_ID); - if (streamyfinPlugin?.Status != PluginStatus.Active) { + if (!streamyfinPlugin || streamyfinPlugin.Status != PluginStatus.Active) { writeErrorLog( "Streamyfin plugin is currently not active.\n" + `Current status is: ${streamyfinPlugin?.Status}` @@ -204,11 +207,8 @@ export const useSettings = () => { return; } - const settings = await api.axiosInstance - .get(`${api.basePath}/Streamyfin/config`, { headers: { [AUTHORIZATION_HEADER]: api.authorizationHeader } }) - .then(response => { - return response.data['settings'] as PluginLockableSettings - }) + const settings = await api.getStreamyfinPluginConfig() + .then(({data}) => data.settings) setPluginSettings(settings); return settings;