mirror of
https://github.com/KevinMidboe/seasoned.git
synced 2026-03-11 03:49:07 +00:00
Split large PlexSettings component into reusable pieces: Composables: - usePlexApi.ts: API functions for user data, servers, libraries - usePlexAuth.ts: OAuth authentication flow, PIN generation, polling Components: - PlexAuthButton.vue: Sign-in button with OAuth popup - PlexProfileCard.vue: User profile with badges (Pass, 2FA, Labs, years) Benefits: - Better code organization and maintainability - Reusable authentication logic - Cleaner separation of concerns - Easier testing and debugging
196 lines
5.5 KiB
TypeScript
196 lines
5.5 KiB
TypeScript
import { ref } from "vue";
|
|
|
|
const CLIENT_IDENTIFIER =
|
|
"seasoned-plex-app-" + Math.random().toString(36).substring(7);
|
|
const APP_NAME = "Seasoned";
|
|
|
|
export function usePlexApi() {
|
|
const plexServerUrl = ref("");
|
|
|
|
// Fetch Plex user data
|
|
async function fetchPlexUserData(authToken: string) {
|
|
try {
|
|
const response = await fetch("https://plex.tv/api/v2/user", {
|
|
method: "GET",
|
|
headers: {
|
|
accept: "application/json",
|
|
"X-Plex-Product": APP_NAME,
|
|
"X-Plex-Client-Identifier": CLIENT_IDENTIFIER,
|
|
"X-Plex-Token": authToken
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Failed to fetch Plex user info");
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log("[PlexAPI] Raw Plex API response:", data);
|
|
|
|
// Convert Unix timestamp to ISO date string if needed
|
|
let joinedDate = null;
|
|
if (data.joinedAt) {
|
|
if (typeof data.joinedAt === "number") {
|
|
joinedDate = new Date(data.joinedAt * 1000).toISOString();
|
|
} else {
|
|
joinedDate = data.joinedAt;
|
|
}
|
|
}
|
|
|
|
const userData = {
|
|
id: data.id,
|
|
uuid: data.uuid,
|
|
username: data.username || data.title || "Plex User",
|
|
email: data.email,
|
|
thumb: data.thumb,
|
|
joined_at: joinedDate,
|
|
two_factor_enabled: data.twoFactorEnabled || false,
|
|
experimental_features: data.experimentalFeatures || false,
|
|
subscription: {
|
|
active: data.subscription?.active,
|
|
plan: data.subscription?.plan,
|
|
features: data.subscription?.features
|
|
},
|
|
profile: {
|
|
auto_select_audio: data.profile?.autoSelectAudio,
|
|
default_audio_language: data.profile?.defaultAudioLanguage,
|
|
default_subtitle_language: data.profile?.defaultSubtitleLanguage
|
|
},
|
|
entitlements: data.entitlements || [],
|
|
roles: data.roles || [],
|
|
created_at: new Date().toISOString()
|
|
};
|
|
|
|
console.log("[PlexAPI] Processed user data:", userData);
|
|
localStorage.setItem("plex_user_data", JSON.stringify(userData));
|
|
return userData;
|
|
} catch (error) {
|
|
console.error("[PlexAPI] Error fetching Plex user data:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Fetch Plex servers
|
|
async function fetchPlexServers(authToken: string) {
|
|
try {
|
|
const response = await fetch(
|
|
"https://plex.tv/api/v2/resources?includeHttps=1&includeRelay=1",
|
|
{
|
|
method: "GET",
|
|
headers: {
|
|
accept: "application/json",
|
|
"X-Plex-Token": authToken,
|
|
"X-Plex-Client-Identifier": CLIENT_IDENTIFIER
|
|
}
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Failed to fetch Plex servers");
|
|
}
|
|
|
|
const servers = await response.json();
|
|
const ownedServer = servers.find(
|
|
(s: any) => s.owned && s.provides === "server"
|
|
);
|
|
|
|
if (ownedServer) {
|
|
const connection =
|
|
ownedServer.connections?.find((c: any) => c.local === false) ||
|
|
ownedServer.connections?.[0];
|
|
if (connection) {
|
|
plexServerUrl.value = connection.uri;
|
|
}
|
|
return { name: ownedServer.name, url: plexServerUrl.value };
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
console.error("[PlexAPI] Error fetching Plex servers:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Fetch library sections
|
|
async function fetchLibrarySections(authToken: string) {
|
|
if (!plexServerUrl.value) return [];
|
|
|
|
try {
|
|
const response = await fetch(`${plexServerUrl.value}/library/sections`, {
|
|
method: "GET",
|
|
headers: {
|
|
accept: "application/json",
|
|
"X-Plex-Token": authToken
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Failed to fetch library sections");
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.MediaContainer?.Directory || [];
|
|
} catch (error) {
|
|
console.error("[PlexAPI] Error fetching library sections:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Fetch library details
|
|
async function fetchLibraryDetails(authToken: string, sectionKey: string) {
|
|
if (!plexServerUrl.value) return null;
|
|
|
|
try {
|
|
// Fetch all items
|
|
const allResponse = await fetch(
|
|
`${plexServerUrl.value}/library/sections/${sectionKey}/all`,
|
|
{
|
|
method: "GET",
|
|
headers: {
|
|
accept: "application/json",
|
|
"X-Plex-Token": authToken
|
|
}
|
|
}
|
|
);
|
|
|
|
if (!allResponse.ok) throw new Error("Failed to fetch all items");
|
|
const allData = await allResponse.json();
|
|
|
|
// Fetch recently added
|
|
const recentResponse = await fetch(
|
|
`${plexServerUrl.value}/library/sections/${sectionKey}/recentlyAdded?X-Plex-Container-Start=0&X-Plex-Container-Size=5`,
|
|
{
|
|
method: "GET",
|
|
headers: {
|
|
accept: "application/json",
|
|
"X-Plex-Token": authToken
|
|
}
|
|
}
|
|
);
|
|
|
|
if (!recentResponse.ok) throw new Error("Failed to fetch recently added");
|
|
const recentData = await recentResponse.json();
|
|
|
|
return {
|
|
all: allData,
|
|
recent: recentData,
|
|
metadata: allData.MediaContainer?.Metadata || [],
|
|
recentMetadata: recentData.MediaContainer?.Metadata || []
|
|
};
|
|
} catch (error) {
|
|
console.error("[PlexAPI] Error fetching library details:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return {
|
|
CLIENT_IDENTIFIER,
|
|
APP_NAME,
|
|
plexServerUrl,
|
|
fetchPlexUserData,
|
|
fetchPlexServers,
|
|
fetchLibrarySections,
|
|
fetchLibraryDetails
|
|
};
|
|
}
|