'use strict'; // reddit gallery images: `s.u` (still) or `s.gif`/`s.mp4` (animated) - URLs come back // HTML-entity-encoded (e.g. '&' instead of '&') function resolveMediaUrl(media) { const url = media?.s?.u || media?.s?.gif || media?.s?.mp4; return url ? url.replace(/&/g, '&') : null; } // reddit sends the non-standard 'image/jpg' instead of the real IANA 'image/jpeg' for // gallery items - left as-is, this silently breaks anything matching against real mime // types downstream (interpolate.js's extension lookup ends up with a '.false' extension, // fetch/content.js's addMeta() skips embedding metadata since it only matches 'image/jpeg') function normalizeType(type) { return type === 'image/jpg' ? 'image/jpeg' : type; } async function redditAlbum(host, post, { reddit }) { // reddit's submission listing already embeds media_metadata/gallery_data for gallery posts, // and curate/posts.js carries them through as post.mediaMetadata/post.galleryData - reuse // those instead of re-fetching the same post individually through reddit's own client // (reservoir: 6/min, maxConcurrent: 1 - see src/reddit/client.js), which would otherwise cost // one full throttled round trip per gallery post. Only fall back to a fresh fetch if the // listing didn't have it for some reason (e.g. gallery_data null on the listing too). const data = post.mediaMetadata ? { media_metadata: post.mediaMetadata, gallery_data: post.galleryData } : await reddit.getSubmission(post.id); const mediaMetadata = data.media_metadata || {}; // gallery_data (the ordered {media_id, is_deleted, ...} list for this gallery) has been // seen coming back null on a freshly-posted gallery even though media_metadata itself // was already fully populated - fall back to media_metadata's own keys, in the order the // JSON provided them (reddit sends them in gallery order, and object key order is // preserved through JSON.parse), skipping anything not (yet, or any longer) valid const galleryItems = data.gallery_data?.items || Object.entries(mediaMetadata) .filter(([, media]) => media.status === 'valid') .map(([mediaId]) => ({ media_id: mediaId })); const items = galleryItems .filter((item) => !item.is_deleted) .map((item) => { const media = mediaMetadata[item.media_id]; const url = resolveMediaUrl(media); return url && { id: item.media_id, url, datetime: post.datetime, type: normalizeType(media.m) || 'image/jpeg', }; }) .filter(Boolean); return { album: { id: host.id, url: host.url, title: post.title, }, items, }; } module.exports = redditAlbum;