first commit
This commit is contained in:
290
src/api.ts
Normal file
290
src/api.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { Media, Staff } from './types';
|
||||
|
||||
const BASE_URL = 'http://192.168.1.102:57000';
|
||||
|
||||
function normalizeUrl(url: string | null): string {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
// Remove leading slash if present and add base URL
|
||||
const cleanPath = url.startsWith('/') ? url.slice(1) : url;
|
||||
return `${BASE_URL}/${cleanPath}`;
|
||||
}
|
||||
|
||||
export interface ApiResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
items: ApiMediaItem[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApiMediaItem {
|
||||
id: number;
|
||||
title: string;
|
||||
overview: string;
|
||||
poster_url: string;
|
||||
poster_aspect_ratio: string | null;
|
||||
backdrop_url: string | null;
|
||||
backdrop_aspect_ratio: string | null;
|
||||
rating: string;
|
||||
runtime_minutes: number;
|
||||
release_date: string;
|
||||
director: string | null;
|
||||
writer: string | null;
|
||||
cast: string | null;
|
||||
genre: string | null;
|
||||
metadata: string;
|
||||
actors?: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
thumbnail_path: string | null;
|
||||
metadata?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ApiMetadata {
|
||||
xbvr_id: number;
|
||||
xbvr_url: string | null;
|
||||
cast: string[];
|
||||
actors: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
thumbnail_path: string | null;
|
||||
}>;
|
||||
tags: string[];
|
||||
is_available: boolean;
|
||||
is_watched: boolean;
|
||||
watch_count: number;
|
||||
video_length: number;
|
||||
video_width: number | null;
|
||||
video_height: number | null;
|
||||
video_codec: string | null;
|
||||
file_path: string | null;
|
||||
cover_url: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function convertApiToMedia(apiItem: ApiMediaItem): Media {
|
||||
let metadata: ApiMetadata;
|
||||
try {
|
||||
metadata = JSON.parse(apiItem.metadata);
|
||||
} catch (e) {
|
||||
metadata = {
|
||||
xbvr_id: 0,
|
||||
xbvr_url: null,
|
||||
cast: [],
|
||||
actors: [],
|
||||
tags: [],
|
||||
is_available: false,
|
||||
is_watched: false,
|
||||
watch_count: 0,
|
||||
video_length: 0,
|
||||
video_width: null,
|
||||
video_height: null,
|
||||
video_codec: null,
|
||||
file_path: null,
|
||||
cover_url: apiItem.poster_url,
|
||||
};
|
||||
}
|
||||
|
||||
// Use actors from the main item if available, otherwise from metadata
|
||||
const actors = apiItem.actors || metadata.actors || [];
|
||||
const staff: Staff[] = actors.map((actor, index) => ({
|
||||
id: `actor-${actor.id}`,
|
||||
name: actor.name,
|
||||
role: 'Actor',
|
||||
photo: normalizeUrl(actor.thumbnail_path) || `https://picsum.photos/seed/actor-${actor.id}/200/200`,
|
||||
characterName: actor.name,
|
||||
characterImage: normalizeUrl(actor.thumbnail_path) || `https://picsum.photos/seed/actor-${actor.id}/200/200`,
|
||||
}));
|
||||
|
||||
|
||||
// Determine aspect ratio from poster_aspect_ratio or default to 2/3
|
||||
let aspectRatio: '2/3' | '16/9' | '1/1' = '2/3';
|
||||
if (apiItem.poster_aspect_ratio) {
|
||||
const ratio = apiItem.poster_aspect_ratio.toLowerCase();
|
||||
if (ratio.includes('16:9') || ratio.includes('1.78')) {
|
||||
aspectRatio = '16/9';
|
||||
} else if (ratio.includes('1:1') || ratio.includes('1.00')) {
|
||||
aspectRatio = '1/1';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: apiItem.id.toString() || undefined,
|
||||
title: apiItem.title || undefined,
|
||||
year: apiItem.release_date ? new Date(apiItem.release_date).getFullYear().toString() : 'Unknown',
|
||||
poster: normalizeUrl(apiItem.poster_url) || `https://picsum.photos/seed/${apiItem.id}/400/600`,
|
||||
banner: normalizeUrl(apiItem.backdrop_url) || undefined,
|
||||
description: apiItem.overview || undefined,
|
||||
rating: apiItem.rating ? parseFloat(apiItem.rating) : undefined,
|
||||
genres: metadata.tags || [],
|
||||
tags: metadata.tags || [],
|
||||
studios: apiItem.director ? [apiItem.director] : undefined,
|
||||
type: 'Movie',
|
||||
status: 'completed',
|
||||
staff: staff.length > 0 ? staff : undefined,
|
||||
runtime: apiItem.runtime_minutes,
|
||||
director: apiItem.director || undefined,
|
||||
writer: apiItem.writer || undefined,
|
||||
releaseDate: apiItem.release_date || undefined,
|
||||
aspectRatio: aspectRatio
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchMediaFromApi(apiUrl: string = `${BASE_URL}/api/adult`): Promise<Media[]> {
|
||||
console.error('Error fetching');
|
||||
try {
|
||||
const response = await fetch(apiUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: ApiResponse = await response.json();
|
||||
|
||||
if (data.success && data.data.items) {
|
||||
return data.data.items.map(convertApiToMedia);
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching media from API:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMediaFromLocalJson(): Promise<Media[]> {
|
||||
try {
|
||||
const response = await fetch('/adult.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: ApiResponse = await response.json();
|
||||
if (data.data.items) {
|
||||
return data.data.items.map(convertApiToMedia);
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching media from local JSON:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMediaById(id: number): Promise<Media | null> {
|
||||
try {
|
||||
const response = await fetch('/adult.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: ApiResponse = await response.json();
|
||||
if (data.data.items) {
|
||||
const item = data.data.items.find(item => item.id === id);
|
||||
return item ? convertApiToMedia(item) : null;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching media by ID:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMediaByActor(actorName: string): Promise<Media[]> {
|
||||
try {
|
||||
const response = await fetch('/adult.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: ApiResponse = await response.json();
|
||||
if (data.data.items) {
|
||||
return data.data.items
|
||||
.filter(item => item.actors?.some(actor => actor.name.toLowerCase().includes(actorName.toLowerCase())))
|
||||
.map(convertApiToMedia);
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching media by actor:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMediaByTag(tag: string): Promise<Media[]> {
|
||||
try {
|
||||
const response = await fetch('/adult.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: ApiResponse = await response.json();
|
||||
if (data.data.items) {
|
||||
return data.data.items
|
||||
.filter(item => {
|
||||
try {
|
||||
const metadata = JSON.parse(item.metadata);
|
||||
return metadata.tags?.some(t => t.toLowerCase().includes(tag.toLowerCase()));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map(convertApiToMedia);
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching media by tag:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllActors(): Promise<Array<{id: number, name: string, thumbnail_path: string | null}>> {
|
||||
try {
|
||||
const response = await fetch('/adult.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: ApiResponse = await response.json();
|
||||
if (data.data.items) {
|
||||
const actorMap = new Map();
|
||||
data.data.items.forEach(item => {
|
||||
item.actors?.forEach(actor => {
|
||||
if (!actorMap.has(actor.id)) {
|
||||
actorMap.set(actor.id, {
|
||||
id: actor.id,
|
||||
name: actor.name,
|
||||
thumbnail_path: actor.thumbnail_path
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return Array.from(actorMap.values());
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching all actors:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllTags(): Promise<string[]> {
|
||||
try {
|
||||
const response = await fetch('/adult.json');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data: ApiResponse = await response.json();
|
||||
if (data.data.items) {
|
||||
const tagSet = new Set<string>();
|
||||
data.data.items.forEach(item => {
|
||||
try {
|
||||
const metadata = JSON.parse(item.metadata);
|
||||
metadata.tags?.forEach((tag: string) => tagSet.add(tag));
|
||||
} catch {
|
||||
// Ignore metadata parsing errors
|
||||
}
|
||||
});
|
||||
return Array.from(tagSet).sort();
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching all tags:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user