Merge branch 'master' of github.com:bitsocialnet/5chan

This commit is contained in:
Tommaso Casaburi
2026-06-01 13:01:42 +07:00
47 changed files with 297 additions and 99 deletions
+35 -4
View File
@@ -20,9 +20,13 @@ vi.mock('@bitsocial/bitsocial-react-hooks/dist/lib/localforage-lru/index.js', ()
},
}));
vi.mock('../../../components/embed', () => ({
canEmbed: (url: URL) => testState.canEmbedHosts.has(url.hostname),
}));
vi.mock('../../../components/embed/embed-utils', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../../components/embed/embed-utils')>();
return {
...actual,
canEmbed: (url: URL) => testState.canEmbedHosts.has(url.hostname),
};
});
vi.mock('@capacitor/core', () => ({
Capacitor: {
@@ -33,7 +37,16 @@ vi.mock('@capacitor/core', () => ({
},
}));
import { fetchWebpageThumbnailIfNeeded, getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getLinkMediaInfo, getMediaDimensions } from '../media-utils';
import {
fetchWebpageThumbnailIfNeeded,
getCommentMediaInfo,
getDisplayMediaInfoType,
getHasThumbnail,
getLinkMediaInfo,
getMediaDimensions,
getPostMediaTypeLabel,
getYouTubeEmbedPostMediaFileLink,
} from '../media-utils';
const clearMemoizedCache = (fn: unknown) => {
const memoized = fn as { clear?: () => void };
@@ -102,6 +115,18 @@ describe('media-utils', () => {
expect(getDisplayMediaInfoType('unknown', t)).toBe('translated:webpage');
});
it('uses the youtube thumbnail url for post media file links and labels', () => {
const mediaInfo = {
patternThumbnailUrl: 'https://img.youtube.com/vi/abc123/0.jpg',
type: 'iframe',
url: 'https://www.youtube.com/watch?v=abc123',
};
expect(getYouTubeEmbedPostMediaFileLink(mediaInfo)).toBe('https://img.youtube.com/vi/abc123/0.jpg');
expect(getPostMediaTypeLabel(mediaInfo, 'iframe', (key) => key)).toBe('youtube_video');
expect(getYouTubeEmbedPostMediaFileLink({ type: 'iframe', url: 'https://streamable.com/clip123' })).toBeUndefined();
});
it('recognizes which media types expose thumbnails', () => {
expect(getHasThumbnail(undefined, 'https://example.com/file.png')).toBe(false);
expect(getHasThumbnail({ type: 'image', url: 'https://example.com/file.png' }, 'https://example.com/file.png')).toBe(true);
@@ -156,6 +181,12 @@ describe('media-utils', () => {
type: 'iframe',
url: 'https://yt.example/watch?v=yt123',
});
testState.canEmbedHosts = new Set(['yewtu.be']);
expect(getLinkMediaInfo('https://yewtu.be/invidious123')).toEqual({
patternThumbnailUrl: 'https://img.youtube.com/vi/invidious123/0.jpg',
type: 'iframe',
url: 'https://yewtu.be/invidious123',
});
});
it('builds comment media info and strips thumbnails for blacklisted domains', () => {
+26 -12
View File
@@ -1,5 +1,5 @@
import localForageLru from '@bitsocial/bitsocial-react-hooks/dist/lib/localforage-lru/index.js';
import { canEmbed } from '../../components/embed';
import { canEmbed, getYouTubeVideoId } from '../../components/embed/embed-utils';
import memoize from 'memoizee';
import { isPrivateNetworkHostname, isValidURL, parseHttpUrl } from './url-utils';
import { Capacitor, CapacitorHttp } from '@capacitor/core';
@@ -40,6 +40,31 @@ export const getDisplayMediaInfoType = (type: string, t: Translate) => {
}
};
export const getYouTubeEmbedPostMediaFileLink = (commentMediaInfo: CommentMediaInfo | undefined): string | undefined => {
if (!commentMediaInfo || commentMediaInfo.type !== 'iframe' || !commentMediaInfo.url) {
return undefined;
}
const parsedUrl = parseHttpUrl(commentMediaInfo.url);
if (!parsedUrl || !getYouTubeVideoId(parsedUrl)) {
return undefined;
}
return commentMediaInfo.patternThumbnailUrl || getPatternThumbnailUrl(parsedUrl);
};
export const getPostMediaTypeLabel = (commentMediaInfo: CommentMediaInfo | undefined, resolvedType: string | undefined, t: Translate): string => {
if (getYouTubeEmbedPostMediaFileLink(commentMediaInfo)) {
return t('youtube_video');
}
if (!resolvedType) {
return '';
}
return getDisplayMediaInfoType(resolvedType, t);
};
export const getHasThumbnail = memoize(
(commentMediaInfo: CommentMediaInfo | undefined, link: string | undefined): boolean => {
if (!link || !commentMediaInfo) return false;
@@ -55,17 +80,6 @@ export const getHasThumbnail = memoize(
{ max: 1000 },
);
const getYouTubeVideoId = (url: URL): string | null => {
if (url.host.includes('youtu.be')) {
return url.pathname.slice(1);
} else if (url.pathname.includes('/shorts/')) {
return url.pathname.split('/shorts/')[1].split('/')[0];
} else if (url.searchParams.has('v')) {
return url.searchParams.get('v');
}
return null;
};
const getPatternThumbnailUrl = (url: URL): string | undefined => {
const videoId = getYouTubeVideoId(url);
if (videoId) {