feat(channel): identite chaine partagee cliquable partout + logos fiabilises
CI / build-and-test (push) Successful in 13m25s

- Nouveau app-channel-identity (avatar+nom -> /channel, fallback resolve /api/channels, initiales sinon)
- ProviderBadge: accepte les ids courts yt/dm/tw/pt/od/ru
- Search adapters yt/dm/pt/od: conservent channelExternalId/channelAvatarUrl (pt derive instance|channel)
- video-card/themes/watch/related/suggestions/subscriptions/channel-header uniformises sur l'identite partagee
This commit is contained in:
2026-09-25 10:37:36 -04:00
parent 97e5de74f5
commit 579354cdb3
20 changed files with 355 additions and 159 deletions
+2
View File
@@ -22,6 +22,8 @@ export class DmAdapter implements ProviderAdapter<VideoItem> {
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: it.channelId || undefined,
channelAvatarUrl: it.uploaderAvatar || undefined,
}));
return { items, total: Array.isArray(res?.groups?.dm) ? res.groups.dm.length : 0 };
}
+3
View File
@@ -30,6 +30,9 @@ export class OdAdapter implements ProviderAdapter<VideoItem> {
viewCount: undefined,
publishedAt: undefined,
slug,
// Nom de chaîne LBRY (ex. @MaChaine) : le backend /api/channels/od
// sait le résoudre avec ou sans préfixe @.
channelExternalId: it.uploaderName || undefined,
} as VideoItem;
});
return { items, total: Array.isArray(res?.groups?.od) ? res.groups.od.length : 0 };
+20 -10
View File
@@ -12,16 +12,26 @@ export class PtAdapter implements ProviderAdapter<VideoItem> {
if (params.pageToken) httpParams = httpParams.set('page', params.pageToken);
if (params.sort) httpParams = httpParams.set('sort', params.sort);
const res = await firstValueFrom(this.http.get<any>(`/api/search`, { params: httpParams }));
const items: VideoItem[] = (res?.groups?.pt || []).map((it: any) => ({
id: it.id,
provider: 'peertube',
title: it.title,
channelName: it.uploaderName,
durationSec: it.duration,
thumbnailUrl: it.thumbnail,
viewCount: undefined,
publishedAt: undefined,
}));
const items: VideoItem[] = (res?.groups?.pt || []).map((it: any) => {
// PeerTube identifie une chaîne par `instance|channel` : l'instance est
// dérivée de l'URL vidéo, le nom de chaîne de `channelId`.
let channelExternalId: string | undefined;
try {
const host = it?.url ? new URL(String(it.url)).hostname : '';
if (host && it?.channelId) channelExternalId = `${host}|${it.channelId}`;
} catch {}
return {
id: it.id,
provider: 'peertube',
title: it.title,
channelName: it.uploaderName,
durationSec: it.duration,
thumbnailUrl: it.thumbnail,
viewCount: undefined,
publishedAt: undefined,
channelExternalId,
};
});
return { items, total: Array.isArray(res?.groups?.pt) ? res.groups.pt.length : 0 };
}
}
+3
View File
@@ -21,6 +21,9 @@ export class YtAdapter implements ProviderAdapter<VideoItem> {
thumbnailUrl: it.thumbnail,
viewCount: typeof it.views === 'number' ? it.views : (typeof it.viewCount === 'number' ? it.viewCount : undefined),
publishedAt: it.publishedAt,
// ID chaîne conservé : rend le logo/nom cliquables vers /channel
// (l'avatar est résolu via /api/channels, l'API search n'en fournit pas).
channelExternalId: it.channelExternalId || it.channelId || undefined,
}));
return { items, total: Array.isArray(res?.groups?.yt) ? res.groups.yt.length : 0 };
}