move web components to web/
This commit is contained in:
120
web/backend/src/InvidiousAPI.ts
Normal file
120
web/backend/src/InvidiousAPI.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* InvidiousAPI.ts
|
||||
* Copyleft 2025 James Magahern <buzzert@buzzert.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License,
|
||||
* or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
interface InvidiousVideoThumbnail {
|
||||
quality: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface InvidiousResult {
|
||||
type: string;
|
||||
title: string;
|
||||
videoId: string;
|
||||
playlistId: string;
|
||||
author: string;
|
||||
videoThumbnails?: InvidiousVideoThumbnail[];
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
type: string;
|
||||
title: string;
|
||||
author: string;
|
||||
mediaUrl: string;
|
||||
thumbnailUrl: string;
|
||||
}
|
||||
|
||||
export interface ThumbnailResponse {
|
||||
data: NodeJS.ReadableStream;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
const USE_INVIDIOUS = process.env.USE_INVIDIOUS || true;
|
||||
const INVIDIOUS_BASE_URL = process.env.INVIDIOUS_BASE_URL || process.env.INVIDIOUS_URL || 'http://invidious.nor';
|
||||
const INVIDIOUS_API_ENDPOINT = `${INVIDIOUS_BASE_URL}/api/v1`;
|
||||
|
||||
export const getInvidiousSearchURL = (query: string): string =>
|
||||
`${INVIDIOUS_API_ENDPOINT}/search?q=${encodeURIComponent(query)}`;
|
||||
|
||||
export const getInvidiousThumbnailURL = (url: string): string =>
|
||||
`${INVIDIOUS_BASE_URL}/${url}`;
|
||||
|
||||
const preferredThumbnailAPIURL = (thumbnails: InvidiousVideoThumbnail[] | undefined): string => {
|
||||
if (!thumbnails || thumbnails.length === 0) {
|
||||
return '/assets/placeholder.jpg';
|
||||
}
|
||||
|
||||
const mediumThumbnail = thumbnails.find(t => t.quality === 'medium');
|
||||
const thumbnail = mediumThumbnail || thumbnails[0];
|
||||
return `/api/thumbnail?url=${encodeURIComponent(thumbnail.url)}`;
|
||||
};
|
||||
|
||||
const getMediaURL = (result: InvidiousResult): string => {
|
||||
if (result.type === 'video') {
|
||||
return `https://www.youtube.com/watch?v=${result.videoId}`;
|
||||
} else if (result.type === 'playlist') {
|
||||
return `https://www.youtube.com/playlist?list=${result.playlistId}`;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown result type: ${result.type}`);
|
||||
};
|
||||
|
||||
export const searchInvidious = async (query: string): Promise<SearchResult[]> => {
|
||||
try {
|
||||
const response = await fetch(getInvidiousSearchURL(query));
|
||||
if (!response.ok) {
|
||||
throw new Error(`Invidious HTTP error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json() as Array<InvidiousResult>;
|
||||
return data.filter(item => {
|
||||
return item.type === 'video' || item.type === 'playlist';
|
||||
}).map(item => ({
|
||||
type: item.type,
|
||||
title: item.title,
|
||||
author: item.author,
|
||||
mediaUrl: getMediaURL(item),
|
||||
thumbnailUrl: preferredThumbnailAPIURL(item.videoThumbnails)
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to search Invidious:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchThumbnail = async (thumbnailUrl: string): Promise<ThumbnailResponse> => {
|
||||
let path = thumbnailUrl;
|
||||
if (thumbnailUrl.startsWith('http://') || thumbnailUrl.startsWith('https://')) {
|
||||
const url = new URL(thumbnailUrl);
|
||||
path = url.pathname + url.search;
|
||||
}
|
||||
path = path.replace(/^\/+/, ''); // Strip leading slash
|
||||
|
||||
const response = await fetch(getInvidiousThumbnailURL(path));
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return {
|
||||
data: response.body,
|
||||
contentType: response.headers.get('content-type') || 'image/jpeg'
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user