Files
NewTube/src/app/search/adapters/dm.ts
T
bruno 8e5f1d6c65
CI / build-and-test (push) Successful in 14m56s
fix(shorts): son par defaut, avance auto, anti-Forbidden DM, autoplay Twitch/Rumble
Son active par defaut (YT/OD/DM/PT avec son ; clips Twitch en muet force, sans API de volume). Avance auto en fin de lecture (infoDelivery YT + minuteur duree generique garde par index/videoId, ignore si onglet cache). Dailymotion : allow_embed demande cote serveur, mappe en embeddable, propage en raw.allowEmbed pour le garde isPlayable (fini les Forbidden). Rumble autoplay=1/2 selon muet + nudge playVideo YT.
2026-09-30 10:40:03 -04:00

53 lines
2.9 KiB
TypeScript

import { HttpClient, HttpParams } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import type { ProviderAdapter, ProviderSearchParams, SearchResult } from '../models';
import { VideoItem } from 'src/app/shared/models/video-item.model';
import { withFilterParams } from './filter-params';
import { readSearchGroup } from '../search-contract';
import { mapProvenance } from './provenance';
import { serverRefValue } from '../../shared/providers/channel-ref';
// TODO: Make SearchResult generic
export class DmAdapter implements ProviderAdapter<VideoItem> {
key = 'dm' as const;
label = 'Dailymotion';
constructor(private http: HttpClient) {}
async search(params: ProviderSearchParams, _signal: AbortSignal): Promise<SearchResult<VideoItem>> {
let httpParams = new HttpParams().set('q', params.q).set('providers', 'dm');
if (params.pageToken) httpParams = httpParams.set('page', params.pageToken);
if (params.sort) httpParams = httpParams.set('sort', params.sort);
// Filtres (type / durée / période) : appliqués côté serveur quand le provider
// le sait, affinés en post-traitement sinon.
httpParams = withFilterParams(httpParams, params.filters);
const res = await firstValueFrom(this.http.get<any>(`/api/search`, { params: httpParams }));
const { items: rawItems, providerError } = readSearchGroup(res, 'dm');
const items: VideoItem[] = rawItems.map((it: any) => ({
// Phase 7.3 — provenance (instant de collecte + voie) : propriété de la
// donnée, pas d'un provider — mapping commun aux 6 adaptateurs.
...mapProvenance(it),
id: it.id,
provider: 'dailymotion',
title: it.title,
channelName: it.uploaderName,
durationSec: it.duration,
thumbnailUrl: it.thumbnail,
viewCount: undefined, // Dailymotion API does not provide view count in search results
publishedAt: undefined, // Dailymotion API does not provide published date in search results
channelExternalId: serverRefValue(it.channelRef, 'dm') || it.channelId || undefined,
channelAvatarUrl: it.uploaderAvatar || undefined,
// Jouabilité : `allow_embed=false` (propriétaire) => le player répond
// "Forbidden". Propagé tel quel : `accepts()` l'exclut via le garde
// `raw.allowEmbed` (même garde que le chemin historique direct).
// Champ absent = on ne sait pas (jamais de `false` inventé).
embeddable: typeof it.embeddable === 'boolean' ? it.embeddable : undefined,
// Signaux de type pour le raffinement client des filtres.
type: typeof it.type === 'string' ? it.type : undefined,
isShort: it.isShort === true ? true : undefined,
// Dimensions (signal d'orientation pour la détection Short).
width: typeof it.width === 'number' ? it.width : undefined,
height: typeof it.height === 'number' ? it.height : undefined,
} as VideoItem));
return { items, total: rawItems.length, providerError };
}
}