Implemented channel avatars on feed
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ChannelPage> {
|
||||
const innertube = await getInnertube();
|
||||
@@ -23,6 +24,7 @@ export async function getChannelYTjs(channelId: string): Promise<ChannelPage> {
|
||||
innerResults.header.content?.is(YTNodes.PageHeaderView)
|
||||
) {
|
||||
authorBanners = innerResults.header.content.banner?.image ?? [];
|
||||
associateAvatar(channelId, authorBanners);
|
||||
}
|
||||
|
||||
const description = innerResults.metadata.description ?? '';
|
||||
|
||||
@@ -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<VideoPlay> {
|
||||
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 = [];
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
<span class="bold">{letterCase(video.title.trimEnd())}</span>
|
||||
</a>
|
||||
|
||||
<div>
|
||||
{#if 'authorId' in video && video.authorId}
|
||||
<a
|
||||
tabindex="-1"
|
||||
class:author={!sideways}
|
||||
href={resolve(`/channel/[authorId]`, { authorId: video.authorId })}
|
||||
data-sveltekit-preload-data="off"
|
||||
>{video.author}
|
||||
</a>
|
||||
{:else}
|
||||
<p>{video.author}</p>
|
||||
<nav>
|
||||
{#if !sideways}
|
||||
<img class="circle small" {...authorAvatar.image} alt="Channel profile" />
|
||||
<button
|
||||
class="secondary-container"
|
||||
{...mergeAttrs(authorAvatar.fallback, {
|
||||
style: 'text-transform: uppercase;border-radius: 2.5rem !important;'
|
||||
})}>{video.author[0]}</button
|
||||
>
|
||||
{/if}
|
||||
<div>
|
||||
{#if 'authorId' in video && video.authorId}
|
||||
<a
|
||||
tabindex="-1"
|
||||
class:author={!sideways}
|
||||
href={resolve(`/channel/[authorId]`, { authorId: video.authorId })}
|
||||
data-sveltekit-preload-data="off"
|
||||
>{video.author}
|
||||
</a>
|
||||
{:else}
|
||||
<p>{video.author}</p>
|
||||
{/if}
|
||||
|
||||
{#if 'promotedBy' in video && video.promotedBy === 'favourited'}
|
||||
<i>star</i>
|
||||
{/if}
|
||||
{#if 'promotedBy' in video && video.promotedBy === 'favourited'}
|
||||
<i>star</i>
|
||||
{/if}
|
||||
|
||||
{#if !('publishedText' in video) && 'viewCountText' in video}
|
||||
•
|
||||
{video.viewCountText ?? cleanNumber(video.viewCount ?? 0)}
|
||||
{$_('views')}
|
||||
{/if}
|
||||
|
||||
{#if 'published' in video}
|
||||
<div class="max">
|
||||
{video.viewCountText ?? cleanNumber(video.viewCount ?? 0)}
|
||||
{#if !('publishedText' in video) && 'viewCountText' in video}
|
||||
•
|
||||
{video.published && video.published !== 0
|
||||
? relativeTimestamp(video.published * 1000, false)
|
||||
: video.publishedText}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{video.viewCountText ?? cleanNumber(video.viewCount ?? 0)}
|
||||
{$_('views')}
|
||||
{/if}
|
||||
|
||||
{#if 'published' in video}
|
||||
<div>
|
||||
{video.viewCountText ?? cleanNumber(video.viewCount ?? 0)}
|
||||
•
|
||||
{video.published && video.published !== 0
|
||||
? relativeTimestamp(video.published * 1000, false)
|
||||
: video.publishedText}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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<FavouriteChannels>;
|
||||
channelSubscriptions!: Table<ChannelSubscriptions>;
|
||||
subscriptionFeed!: Table<Video>;
|
||||
watchHistory!: Table<VideoWatchHistory>;
|
||||
channelAvatars!: Table<ChannelAvatars>;
|
||||
|
||||
constructor() {
|
||||
super('materialious');
|
||||
this.version(3).stores({
|
||||
this.version(4).stores({
|
||||
favouriteChannels: 'channelId',
|
||||
channelSubscriptions: 'channelId',
|
||||
subscriptionFeed: 'videoId, authorId, published',
|
||||
watchHistory: 'videoId'
|
||||
watchHistory: 'videoId',
|
||||
channelAvatars: 'channelId'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { Image } from './api/model';
|
||||
import { localDb } from './dexie';
|
||||
import { getBestThumbnail } from './images';
|
||||
|
||||
export async function associateAvatar(channelId: string, avatars: Image[]) {
|
||||
if (avatars.length === 0) return;
|
||||
|
||||
await localDb.channelAvatars.put(
|
||||
{
|
||||
channelId: channelId,
|
||||
avatarUrl: getBestThumbnail(avatars),
|
||||
updated: new Date()
|
||||
},
|
||||
{ channelId: channelId }
|
||||
);
|
||||
|
||||
const oneMonthAgo = new Date();
|
||||
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
||||
localDb.channelAvatars.where('updated').below(oneMonthAgo).delete();
|
||||
}
|
||||
|
||||
export async function avatarFromChannelId(channelId: string): Promise<void | string> {
|
||||
const result = await localDb.channelAvatars.get({ channelId });
|
||||
if (result) {
|
||||
return result.avatarUrl;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user