mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 06:24:25 +01:00
feat: support thumbnail for twitter and discord
This commit is contained in:
@@ -46,7 +46,12 @@ const Post: Component<PostProps> = (props) => {
|
||||
{(url) => (
|
||||
<LazyLoad>
|
||||
{() => (
|
||||
<img src={thumbnailUrl(url)} alt="icon" class="h-full w-full object-cover" />
|
||||
<img
|
||||
src={thumbnailUrl(url, 'icon')}
|
||||
alt="icon"
|
||||
referrerpolicy="no-referrer"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</LazyLoad>
|
||||
)}
|
||||
|
||||
@@ -51,7 +51,7 @@ const ReactionDisplay: Component<ReactionDisplayProps> = (props) => {
|
||||
<Show when={profile()?.picture} keyed>
|
||||
{(url) => (
|
||||
<img
|
||||
src={thumbnailUrl(url)}
|
||||
src={thumbnailUrl(url, 'icon')}
|
||||
alt="icon"
|
||||
// TODO autofit
|
||||
class="h-full w-full object-cover"
|
||||
|
||||
@@ -15,6 +15,7 @@ import lud06ToLnurlPayUrl from '@/nostr/zap/lud06ToLnurlPayUrl';
|
||||
import lud16ToLnurlPayUrl from '@/nostr/zap/lud16ToLnurlPayUrl';
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
import { formatSiPrefix } from '@/utils/siPrefix';
|
||||
import { thumbnailUrl } from '@/utils/url';
|
||||
|
||||
export type ZapReceiptProps = {
|
||||
event: NostrEvent;
|
||||
@@ -82,7 +83,7 @@ const ZapReceiptDisplay: Component<ZapReceiptProps> = (props) => {
|
||||
<div class="author-icon h-5 w-5 shrink-0 overflow-hidden rounded">
|
||||
<Show when={senderProfile()?.picture != null}>
|
||||
<img
|
||||
src={senderProfile()?.picture}
|
||||
src={thumbnailUrl(senderProfile()?.picture, 'icon')}
|
||||
alt="icon"
|
||||
// TODO autofit
|
||||
class="h-full w-full object-cover"
|
||||
|
||||
@@ -5,9 +5,15 @@ import { describe, it } from 'vitest';
|
||||
import { thumbnailUrl } from '@/utils/url';
|
||||
|
||||
describe('thumbnailUrl', () => {
|
||||
it('should return an image url for a given imgur.com URL with additional path', () => {
|
||||
const actual = thumbnailUrl('https://imgur.com/uBf5Qts.jpeg');
|
||||
const expected = 'https://i.imgur.com/uBf5Qtsl.webp';
|
||||
it('should return thumbnail url for imgur.com', () => {
|
||||
const actual = thumbnailUrl('https://i.imgur.com/p05kUim.gif');
|
||||
const expected = 'https://i.imgur.com/p05kUiml.gif';
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should return thumbnail url for imgur.com', () => {
|
||||
const actual = thumbnailUrl('https://i.imgur.com/p05kUim.gif', 'icon');
|
||||
const expected = 'https://i.imgur.com/p05kUims.gif';
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
@@ -16,25 +22,41 @@ describe('thumbnailUrl', () => {
|
||||
'https://nostr.build/i/2489ee648a4fef6943f4a7c88349477e78a91e28232246b801fe8ce86e64624e.png',
|
||||
);
|
||||
const expected =
|
||||
'https://nostr.build/responsive/240p/i/2489ee648a4fef6943f4a7c88349477e78a91e28232246b801fe8ce86e64624e.png';
|
||||
'https://image.nostr.build/resp/240p/2489ee648a4fef6943f4a7c88349477e78a91e28232246b801fe8ce86e64624e.png';
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should return url for image.nostr.build', () => {
|
||||
const actual = thumbnailUrl(
|
||||
'https://image.nostr.build/f56ee902307158c1ebbcb5ac00430dbf1425eac12d55e4277ebccbe54d09671b.jpg',
|
||||
'https://image.nostr.build/78fc3c02f0488e2f3efb818adf1421bcee8c1612189e217c5ced1c2785eee1a8.jpg',
|
||||
);
|
||||
const expected =
|
||||
'https://nostr.build/responsive/240p/i/f56ee902307158c1ebbcb5ac00430dbf1425eac12d55e4277ebccbe54d09671b.jpg';
|
||||
'https://image.nostr.build/resp/240p/78fc3c02f0488e2f3efb818adf1421bcee8c1612189e217c5ced1c2785eee1a8.jpg';
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should return url for cdn.nostr.build', () => {
|
||||
const actual = thumbnailUrl(
|
||||
'https://cdn.nostr.build/i/6a2868ebb53da2c295e3a2a20a29fa009f230f721b71e88c7ffc3ec8eaae870f.png',
|
||||
'https://cdn.nostr.build/i/78fc3c02f0488e2f3efb818adf1421bcee8c1612189e217c5ced1c2785eee1a8.jpg',
|
||||
);
|
||||
const expected =
|
||||
'https://nostr.build/responsive/240p/i/6a2868ebb53da2c295e3a2a20a29fa009f230f721b71e88c7ffc3ec8eaae870f.png';
|
||||
'https://image.nostr.build/resp/240p/78fc3c02f0488e2f3efb818adf1421bcee8c1612189e217c5ced1c2785eee1a8.jpg';
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should return url for pbs.twimg.com/profile_images/', () => {
|
||||
const actual = thumbnailUrl(
|
||||
'https://pbs.twimg.com/profile_images/1713367977725509632/iLgoXgtx_400x400.jpg',
|
||||
);
|
||||
const expected = 'https://pbs.twimg.com/profile_images/1713367977725509632/iLgoXgtx_normal.jpg';
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('should return url for pbs.twimg.com/media/', () => {
|
||||
const actual = thumbnailUrl(
|
||||
'https://pbs.twimg.com/media/FPUltrpaAAQQdIO?format=png&name=900x900',
|
||||
);
|
||||
const expected = 'https://pbs.twimg.com/media/FPUltrpaAAQQdIO?format=jpg&name=small';
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
101
src/utils/url.ts
101
src/utils/url.ts
@@ -28,7 +28,10 @@ export const isWebSocketUrl = (urlString: string): boolean => {
|
||||
/**
|
||||
* Generate a URL of thumbnail for a given URL.
|
||||
*/
|
||||
export const thumbnailUrl = (urlString: string): string => {
|
||||
export const thumbnailUrl = (
|
||||
urlString: string,
|
||||
size: 'icon' | 'thumbnail' = 'thumbnail',
|
||||
): string => {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
// Imgur
|
||||
@@ -37,8 +40,16 @@ export const thumbnailUrl = (urlString: string): string => {
|
||||
if (match != null) {
|
||||
const result = new URL(url);
|
||||
const imageId = match[1];
|
||||
const ext = match[2];
|
||||
result.host = 'i.imgur.com';
|
||||
result.pathname = `${imageId}l.webp`;
|
||||
if (size === 'icon') {
|
||||
result.pathname = `${imageId}s.${ext}`;
|
||||
} else if (size === 'thumbnail') {
|
||||
result.pathname = `${imageId}l.${ext}`;
|
||||
} else {
|
||||
// fallback
|
||||
return urlString;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
return url.toString();
|
||||
@@ -48,32 +59,80 @@ export const thumbnailUrl = (urlString: string): string => {
|
||||
if (url.host === 'i.gyazo.com') {
|
||||
const result = new URL(url);
|
||||
result.host = 'thumb.gyazo.com';
|
||||
if (size === 'icon') {
|
||||
result.pathname = `/thumb/160${url.pathname}`;
|
||||
} else if (size === 'thumbnail') {
|
||||
result.pathname = `/thumb/640${url.pathname}`;
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// nostr.build
|
||||
// https://github.com/nostrbuild/nostr.build/blob/main/api/v2/routes_upload.php
|
||||
if (
|
||||
url.host === 'nostr.build' ||
|
||||
url.host === 'image.nostr.build' ||
|
||||
url.host === 'cdn.nostr.build'
|
||||
) {
|
||||
const result = new URL(url);
|
||||
result.host = 'nostr.build';
|
||||
// profile pic (PFP)
|
||||
if (url.pathname.startsWith('/i/p/')) return urlString;
|
||||
|
||||
if (url.pathname.startsWith('/i/')) {
|
||||
result.pathname = `/responsive/240p${url.pathname}`;
|
||||
} else if (url.pathname.match(/^\/[0-9a-zA-Z]+\.(jpeg|jpg|png|gif|webp|avif|apng)$/)) {
|
||||
result.pathname = `/responsive/240p/i${url.pathname}`;
|
||||
} else {
|
||||
// fallback
|
||||
return urlString;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// nostr.build
|
||||
// https://github.com/nostrbuild/nostr.build/blob/main/SiteConfig.php#L71-L75
|
||||
// https://github.com/nostrbuild/nostr.build/blob/main/api/v2/routes_upload.php
|
||||
if (
|
||||
url.host === 'nostr.build' ||
|
||||
url.host === 'i.nostr.build' ||
|
||||
url.host === 'image.nostr.build' ||
|
||||
url.host === 'cdn.nostr.build'
|
||||
) {
|
||||
// profile pic (PFP)
|
||||
if (url.pathname.startsWith('/i/p/')) return urlString;
|
||||
|
||||
const result = new URL(url);
|
||||
if (url.pathname.startsWith('/i/')) {
|
||||
const withoutI = url.pathname.replace(/^\/i/, '');
|
||||
result.hostname = 'image.nostr.build';
|
||||
result.pathname = `/resp/240p${withoutI}`;
|
||||
} else if (url.pathname.match(/^\/[0-9a-zA-Z]+\.(jpeg|jpg|png|gif|webp|avif|apng)$/)) {
|
||||
result.pathname = `/resp/240p${url.pathname}`;
|
||||
} else {
|
||||
// fallback
|
||||
return urlString;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// pbs.twimg.com
|
||||
// https://qiita.com/ma7ma7pipipi/items/713460b24710e0a46242
|
||||
if (url.host === 'pbs.twimg.com') {
|
||||
if (url.pathname.startsWith('/profile_images/')) {
|
||||
const result = new URL(url);
|
||||
result.pathname = url.pathname.replace(
|
||||
/(?:_(?:mini|normal|bigger|200x200|400x400))?\.(jpg|png)$/,
|
||||
'_normal.$1',
|
||||
);
|
||||
return result.toString();
|
||||
}
|
||||
if (url.pathname.startsWith('/media/')) {
|
||||
const result = new URL(url);
|
||||
result.searchParams.set('format', 'jpg');
|
||||
result.searchParams.set('name', 'small');
|
||||
return result.toString();
|
||||
}
|
||||
return urlString;
|
||||
}
|
||||
|
||||
// media.discrodapp.net
|
||||
if (
|
||||
url.hostname === 'media.discordapp.net' &&
|
||||
url.pathname.match(/^\/attachments\/\d+\/\d+\/[a-z0-9]+\.(png|jpg|gif)/)
|
||||
) {
|
||||
const result = new URL(url);
|
||||
result.searchParams.set('format', 'webp');
|
||||
if (size === 'icon') {
|
||||
result.searchParams.set('width', '100');
|
||||
result.searchParams.set('height', '100');
|
||||
} else if (size === 'thumbnail') {
|
||||
result.searchParams.set('width', '320');
|
||||
result.searchParams.set('height', '320');
|
||||
} else {
|
||||
return urlString;
|
||||
}
|
||||
}
|
||||
return url.toString();
|
||||
} catch {
|
||||
return urlString;
|
||||
|
||||
Reference in New Issue
Block a user