From 2b141b82ee1f9ab06aceec8ad00884090554b42a Mon Sep 17 00:00:00 2001 From: WardPearce Date: Sat, 7 Mar 2026 23:59:03 +1300 Subject: [PATCH] Implemented channel avatars on feed --- materialious/src/lib/api/invidious/channel.ts | 9 +- materialious/src/lib/api/invidious/video.ts | 10 ++- materialious/src/lib/api/youtubejs/channel.ts | 2 + materialious/src/lib/api/youtubejs/video.ts | 3 + .../thumbnail/VideoThumbnail.svelte | 83 ++++++++++++------- materialious/src/lib/dexie.ts | 12 ++- materialious/src/lib/thumbnail.ts | 27 ++++++ 7 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 materialious/src/lib/thumbnail.ts diff --git a/materialious/src/lib/api/invidious/channel.ts b/materialious/src/lib/api/invidious/channel.ts index 98212cb3..a49e4e84 100644 --- a/materialious/src/lib/api/invidious/channel.ts +++ b/materialious/src/lib/api/invidious/channel.ts @@ -1,3 +1,4 @@ +import { associateAvatar } from '$lib/thumbnail'; import type { ChannelContent, ChannelOptions, ChannelPage } from '../model'; import { buildPath, fetchErrorHandle } from './request'; @@ -8,7 +9,13 @@ export async function getChannelInvidious( const resp = await fetchErrorHandle( await fetch(buildPath(`channels/${channelId}`), fetchOptions) ); - return await resp.json(); + const respJson = await resp.json(); + + if (resp.ok) { + associateAvatar(channelId, respJson.authorBanners); + } + + return respJson; } export async function getChannelContentInvidious( diff --git a/materialious/src/lib/api/invidious/video.ts b/materialious/src/lib/api/invidious/video.ts index 0bd15d5b..bfd3fd23 100644 --- a/materialious/src/lib/api/invidious/video.ts +++ b/materialious/src/lib/api/invidious/video.ts @@ -4,6 +4,7 @@ import { buildPath, fetchErrorHandle } from './request'; import { getVideoYTjs } from '../youtubejs/video'; import { playerYouTubeJsFallback } from '$lib/store'; import { isUnrestrictedPlatform } from '$lib/misc'; +import { associateAvatar } from '$lib/thumbnail'; export async function getVideoInvidious( videoId: string, @@ -20,5 +21,12 @@ export async function getVideoInvidious( } else { await fetchErrorHandle(resp); } - return await resp.json(); + + const respJson = await resp.json(); + + if (resp.ok) { + associateAvatar(respJson.authorId, respJson.authorThumbnails); + } + + return respJson; } diff --git a/materialious/src/lib/api/youtubejs/channel.ts b/materialious/src/lib/api/youtubejs/channel.ts index fd77bc6a..82240788 100644 --- a/materialious/src/lib/api/youtubejs/channel.ts +++ b/materialious/src/lib/api/youtubejs/channel.ts @@ -9,6 +9,7 @@ import type { Video } from '../model'; import { invidiousItemSchema } from './schema'; +import { associateAvatar } from '$lib/thumbnail'; export async function getChannelYTjs(channelId: string): Promise { const innertube = await getInnertube(); @@ -23,6 +24,7 @@ export async function getChannelYTjs(channelId: string): Promise { innerResults.header.content?.is(YTNodes.PageHeaderView) ) { authorBanners = innerResults.header.content.banner?.image ?? []; + associateAvatar(channelId, authorBanners); } const description = innerResults.metadata.description ?? ''; diff --git a/materialious/src/lib/api/youtubejs/video.ts b/materialious/src/lib/api/youtubejs/video.ts index f9d89d3e..1c245cf1 100644 --- a/materialious/src/lib/api/youtubejs/video.ts +++ b/materialious/src/lib/api/youtubejs/video.ts @@ -17,6 +17,7 @@ import { Utils, YT, YTNodes, Platform, type IGetChallengeResponse } from 'youtub import { getInnertube } from '.'; import { isUnrestrictedPlatform } from '$lib/misc'; import { webPoTokenMinter } from '$lib/web/youtube/minter'; +import { associateAvatar } from '$lib/thumbnail'; Platform.shim.eval = async ( data: Types.BuildScriptResult, @@ -149,6 +150,8 @@ export async function getVideoYTjs(videoId: string): Promise { if (video.basic_info.channel_id) { const channel = await innertube.getChannel(video.basic_info.channel_id); authorThumbnails = channel.metadata.avatar as Image[]; + + associateAvatar(video.basic_info.channel_id, authorThumbnails); } else { authorThumbnails = []; } diff --git a/materialious/src/lib/components/thumbnail/VideoThumbnail.svelte b/materialious/src/lib/components/thumbnail/VideoThumbnail.svelte index f3996605..d00a75a1 100644 --- a/materialious/src/lib/components/thumbnail/VideoThumbnail.svelte +++ b/materialious/src/lib/components/thumbnail/VideoThumbnail.svelte @@ -19,6 +19,8 @@ import { queueGetWatchHistory } from '$lib/api/historyPool'; import { page } from '$app/state'; import { getDeArrow, getThumbnailDeArrow } from '$lib/api/dearrow'; + import { avatarFromChannelId } from '$lib/thumbnail'; + import { mergeAttrs } from 'melt'; interface Props { video: VideoBase | Video | Notification | PlaylistPageVideo | VideoWatchHistory; @@ -70,6 +72,7 @@ } let thumbnailHeight = $state(0); + let thumbnailHTMLElement: HTMLImageElement | undefined = $state(); const thumbnail = new Avatar({ src: () => thumbnailSrc, @@ -78,7 +81,10 @@ } }); - let thumbnailHTMLElement: HTMLImageElement | undefined = $state(); + let authorAvatarSrc = $state(''); + const authorAvatar = new Avatar({ + src: () => authorAvatarSrc + }); let startedSideways = sideways === true; function disableSideways() { @@ -90,6 +96,12 @@ } onMount(async () => { + if ('authorId' in video) { + avatarFromChannelId(video.authorId).then((url) => { + if (url) authorAvatarSrc = url; + }); + } + // Check if sideways should be enabled or disabled. disableSideways(); @@ -181,39 +193,50 @@ {letterCase(video.title.trimEnd())} -
- {#if 'authorId' in video && video.authorId} - {video.author} - - {:else} -

{video.author}

+
+ diff --git a/materialious/src/lib/dexie.ts b/materialious/src/lib/dexie.ts index 982d4714..80851826 100644 --- a/materialious/src/lib/dexie.ts +++ b/materialious/src/lib/dexie.ts @@ -13,19 +13,27 @@ export interface ChannelSubscriptions { lastRSSFetch: Date; } +export interface ChannelAvatars { + channelId: string; + avatarUrl: string; + updated: Date; +} + export class MaterialiousDb extends Dexie { favouriteChannels!: Table; channelSubscriptions!: Table; subscriptionFeed!: Table