Skip to content

Commit e078e49

Browse files
committed
feat(preview): preview a direct image link as the image itself
1 parent 28e19a4 commit e078e49

2 files changed

Lines changed: 23 additions & 0 deletions

File tree

‎src/preview.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,23 @@ async function previewTweet(id: string): Promise<Preview | null> {
219219
return { tags, images, cover: mosaic ?? undefined };
220220
}
221221

222+
function imageType(page: Response): string | null {
223+
const type = /^\s*(image\/[\w.+-]+)/i.exec(page.headers.get('Content-Type') ?? '')?.[1].toLowerCase();
224+
return type && type !== 'image/svg+xml' ? type : null;
225+
}
226+
222227
async function previewPage(url: URL): Promise<Preview | null> {
223228
const endpoint = OEMBED_ENDPOINTS.find(([host]) => host.test(url.hostname))?.[1];
224229
const known = endpoint ? fetchJson<OEmbed>(new URL(`${endpoint}?format=json&url=${encodeURIComponent(url.href)}`)) : null;
225230

226231
const page = await fetchPublic(url, 'text/html');
227232
const base = page?.url || url;
233+
const type = page && imageType(page);
234+
if (page && type) {
235+
await page.body?.cancel();
236+
const image = toImage(base.toString());
237+
return image ? { tags: { 'og:image:type': type }, images: [image] } : null;
238+
}
228239
const html = page?.headers.get('Content-Type')?.includes('text/html') ? await readPage(page, base) : null;
229240
const knownOEmbed = await known;
230241
if (!html && !knownOEmbed) return null;

‎test/preview.spec.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ describe('preview_url', () => {
8989
expect((await preview('https://api.example/')).status).toBe(404);
9090
});
9191

92+
it('previews a direct image link as the image itself', async () => {
93+
servePage('', 'image/gif');
94+
95+
const response = await preview('https://cdn.example/clip.gif');
96+
const body = (await response.json()) as Record<string, unknown>;
97+
98+
expect(response.status).toBe(200);
99+
expect(body['og:image']).toBe(`mxc://gifs.example/${toMatrixID('https://cdn.example/clip.gif', 'og_')}`);
100+
expect(body['og:image:type']).toBe('image/gif');
101+
expect(body['og:title']).toBeUndefined();
102+
});
103+
92104
it('answers not found when the page cannot be fetched', async () => {
93105
vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('down'));
94106

0 commit comments

Comments
 (0)