chore: update Angular cache and TypeScript build info files
This commit is contained in:
@@ -376,15 +376,14 @@ export class YoutubeApiService {
|
||||
if (!key) return of({ items: [], nextCursor: null });
|
||||
const params = new URLSearchParams({
|
||||
type: 'video',
|
||||
part: 'snippet,contentDetails,statistics',
|
||||
// search.list only supports 'snippet'
|
||||
part: 'snippet',
|
||||
// Optimize for quota usage: reduce initial results
|
||||
maxResults: '12',
|
||||
q: String(query),
|
||||
regionCode: String(region),
|
||||
// Add safeSearch for better content filtering
|
||||
safeSearch: 'moderate',
|
||||
// Video category filter for better relevance
|
||||
videoCategoryId: '0', // All categories
|
||||
key: String(key),
|
||||
});
|
||||
// Prefer results matching user's language when available
|
||||
@@ -569,6 +568,7 @@ export class YoutubeApiService {
|
||||
url: `/watch/${id}`,
|
||||
videoId: id,
|
||||
type: 'video',
|
||||
provider: 'youtube',
|
||||
title: sn.title || '',
|
||||
thumbnail: thumb,
|
||||
uploaderName: sn.channelTitle || '',
|
||||
@@ -590,6 +590,7 @@ export class YoutubeApiService {
|
||||
url: `/watch/${id}`,
|
||||
videoId: id,
|
||||
type: 'video',
|
||||
provider: 'youtube',
|
||||
title: sn.title || '',
|
||||
thumbnail: thumb,
|
||||
uploaderName: sn.channelTitle || '',
|
||||
@@ -607,6 +608,7 @@ export class YoutubeApiService {
|
||||
url: `/watch/${i.id}`,
|
||||
videoId: i.id || '',
|
||||
type: 'video',
|
||||
provider: 'dailymotion',
|
||||
title: i.title || '',
|
||||
thumbnail: i.thumbnail_720_url || i.thumbnail_480_url || i.thumbnail_url || '',
|
||||
uploaderName: i['owner.screenname'] || '',
|
||||
@@ -634,6 +636,7 @@ export class YoutubeApiService {
|
||||
url: `https://${instance}/w/${i.uuid}`,
|
||||
videoId: i.uuid || '',
|
||||
type: 'video',
|
||||
provider: 'peertube',
|
||||
title: i.name || '',
|
||||
thumbnail,
|
||||
uploaderName,
|
||||
@@ -664,6 +667,7 @@ export class YoutubeApiService {
|
||||
url: webUrl,
|
||||
videoId: i?.claim_id || i?.txid || i?.claimId || i?.id || '',
|
||||
type: 'video',
|
||||
provider: 'odysee',
|
||||
title: val?.title || i?.name || '',
|
||||
thumbnail: thumb,
|
||||
uploaderName,
|
||||
@@ -702,6 +706,7 @@ export class YoutubeApiService {
|
||||
url,
|
||||
videoId: id,
|
||||
type: 'video',
|
||||
provider: 'rumble',
|
||||
title,
|
||||
thumbnail: thumb,
|
||||
uploaderName,
|
||||
@@ -719,6 +724,7 @@ export class YoutubeApiService {
|
||||
url: `/watch/${i.id}`,
|
||||
videoId: i.id || '',
|
||||
type: 'video',
|
||||
provider: 'twitch',
|
||||
title: i.title || '',
|
||||
thumbnail: i.thumbnail_url?.replace('%{width}', '640').replace('%{height}', '360') || '',
|
||||
uploaderName: i.user_name || '',
|
||||
@@ -735,6 +741,7 @@ export class YoutubeApiService {
|
||||
url: `https://www.twitch.tv/${i.user_login}`,
|
||||
videoId: i.user_login || i.id || '',
|
||||
type: 'channel',
|
||||
provider: 'twitch',
|
||||
title: i.title || '',
|
||||
thumbnail: (i.thumbnail_url || '').replace('{width}', '640').replace('{height}', '360'),
|
||||
uploaderName: i.user_name || '',
|
||||
@@ -750,17 +757,80 @@ export class YoutubeApiService {
|
||||
url: `https://www.twitch.tv/${i.broadcaster_login || i.user_login || ''}`,
|
||||
videoId: i.broadcaster_login || i.user_login || '',
|
||||
type: 'channel',
|
||||
title: i.display_name || i.title || '',
|
||||
provider: 'twitch',
|
||||
title: i.title || i.display_name || i.broadcaster_login || i.user_name || '',
|
||||
thumbnail: (i.thumbnail_url || '').replace('{width}', '640').replace('{height}', '360'),
|
||||
uploaderName: i.display_name || i.broadcaster_login || i.user_login || '',
|
||||
uploaderName: i.display_name || i.broadcaster_name || i.user_name || '',
|
||||
uploaderUrl: `https://www.twitch.tv/${i.broadcaster_login || i.user_login || ''}`,
|
||||
uploaderAvatar: (i.thumbnail_url || '').replace('{width}', '70').replace('{height}', '70'),
|
||||
uploadedDate: i.started_at || '',
|
||||
duration: 0, // Not a VOD
|
||||
views: 0, // Not applicable
|
||||
uploaded: i.started_at ? Date.parse(i.started_at) : 0,
|
||||
uploadedDate: '',
|
||||
duration: 0,
|
||||
views: 0,
|
||||
uploaded: 0,
|
||||
});
|
||||
|
||||
// Twitch Clips mapper (Shorts-equivalent)
|
||||
private mapTwitchClipToVideo = (i: any): Video => ({
|
||||
url: i.url || `https://clips.twitch.tv/${i.id}`,
|
||||
videoId: i.id || '',
|
||||
type: 'video',
|
||||
provider: 'twitch',
|
||||
kind: 'clip',
|
||||
title: i.title || '',
|
||||
// Clip thumbnails often have width/height placeholders or fixed preview sizes
|
||||
thumbnail: (i.thumbnail_url || '').replace('%{width}', '640').replace('%{height}', '360'),
|
||||
uploaderName: i.broadcaster_name || i.creator_name || '',
|
||||
uploaderUrl: i.broadcaster_name ? `https://www.twitch.tv/${i.broadcaster_name}` : '',
|
||||
uploaderAvatar: '',
|
||||
uploadedDate: i.created_at || '',
|
||||
duration: Number(i.duration || 0),
|
||||
views: Number(i.view_count || 0),
|
||||
uploaded: i.created_at ? Date.parse(i.created_at) : 0,
|
||||
});
|
||||
|
||||
/**
|
||||
* Search Twitch Clips by first finding channels for a query (or recent popular channels when empty),
|
||||
* then fetching recent clips per broadcaster. Returns up to ~24 clips with pagination via channel search cursor.
|
||||
*/
|
||||
public searchTwitchClipsPage(query: string, cursor?: string | null): Observable<PagedResult<Video>> {
|
||||
return this.getTwitchAuthToken().pipe(
|
||||
switchMap(token => {
|
||||
if (!token) return of({ items: [], nextCursor: null });
|
||||
const headers = new HttpHeaders({ 'Client-ID': this.resolveTwitchClientId()!, 'Authorization': `Bearer ${token}` });
|
||||
const after = cursor ? `&after=${encodeURIComponent(cursor)}` : '';
|
||||
const q = String(query || '');
|
||||
const chPrimary = `/api/twitch-api/helix/search/channels?query=${encodeURIComponent(q)}&first=10${after}`;
|
||||
const chFallback = `/proxy/twitch-api/helix/search/channels?query=${encodeURIComponent(q)}&first=10${after}`;
|
||||
return this.http.get<any>(chPrimary, { headers }).pipe(
|
||||
catchError(() => this.http.get<any>(chFallback, { headers })),
|
||||
switchMap(chRes => {
|
||||
const channels = Array.isArray(chRes?.data) ? chRes.data : [];
|
||||
const userIds: string[] = channels.map((c: any) => c?.id).filter(Boolean).slice(0, 10);
|
||||
if (userIds.length === 0) return of({ items: [], nextCursor: null } as PagedResult<Video>);
|
||||
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const perUserRequests = userIds.map(uid => {
|
||||
const cPrimary = `/api/twitch-api/helix/clips?broadcaster_id=${encodeURIComponent(uid)}&first=5&started_at=${encodeURIComponent(since)}`;
|
||||
const cFallback = `/proxy/twitch-api/helix/clips?broadcaster_id=${encodeURIComponent(uid)}&first=5&started_at=${encodeURIComponent(since)}`;
|
||||
return this.http.get<any>(cPrimary, { headers }).pipe(
|
||||
catchError(() => this.http.get<any>(cFallback, { headers })),
|
||||
map(res => Array.isArray(res?.data) ? res.data : [])
|
||||
);
|
||||
});
|
||||
return forkJoin(perUserRequests).pipe(
|
||||
map((lists: any[][]) => lists.flat().slice(0, 24)),
|
||||
map((clips: any[]) => ({
|
||||
items: clips.map((i: any) => this.mapTwitchClipToVideo(i)),
|
||||
nextCursor: chRes?.pagination?.cursor || null,
|
||||
} as PagedResult<Video>)),
|
||||
catchError(() => of({ items: [], nextCursor: null } as PagedResult<Video>))
|
||||
);
|
||||
}),
|
||||
catchError((e) => { console.error('Twitch clips search error', e); return of({ items: [], nextCursor: null }); })
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private parseISODurationToSeconds(iso: string): number {
|
||||
// ... (rest of the code remains the same)
|
||||
const m = /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/i.exec(iso || '');
|
||||
|
||||
Reference in New Issue
Block a user