From ba922bb3850ace726f49c9d7417be39c906c7f38 Mon Sep 17 00:00:00 2001 From: DebaucheryLibrarian Date: Mon, 17 Aug 2026 23:31:29 +0200 Subject: [PATCH] Fixed .false file extension. --- src/interpolate.js | 15 ++++++++++++--- src/methods/redditAlbum.js | 25 ++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/interpolate.js b/src/interpolate.js index 39be44e..312bac4 100644 --- a/src/interpolate.js +++ b/src/interpolate.js @@ -27,6 +27,17 @@ function safeFormat(date, dateFormat, label) { return dateFns.format(date, dateFormat); } +// mime.extension() returns the boolean `false` (not undefined/null) for a mime type it +// doesn't recognize - reddit's gallery API is a known offender, sending the non-standard +// 'image/jpg' instead of 'image/jpeg' - which without this check ends up interpolated +// verbatim as the literal filename extension '.false'. Fall back to whatever extension the +// URL itself has instead, same as when there's no type at all. +function resolveExt(item) { + const extFromType = item.type && mime.extension(item.type); + + return extFromType ? `.${extFromType}` : path.extname(url.parse(item.url).pathname); +} + function interpolate(pattern, item = null, content = null, host = null, post = null, user = null, strip = true, dateFormat = config.library.dateFormat) { const data = { tags: {}, @@ -41,9 +52,7 @@ function interpolate(pattern, item = null, content = null, host = null, post = n date: safeFormat(item.datetime, dateFormat, 'item'), index: item.index + config.library.indexOffset, }, - ext: item.type - ? `.${mime.extension(item.type)}` - : path.extname(url.parse(item.url).pathname), + ext: resolveExt(item), tags: { ...data.tags, extracted: item.extracted && config.library.tags.extracted, diff --git a/src/methods/redditAlbum.js b/src/methods/redditAlbum.js index 38b7ef4..8694d35 100644 --- a/src/methods/redditAlbum.js +++ b/src/methods/redditAlbum.js @@ -8,22 +8,41 @@ function resolveMediaUrl(media) { 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 }) { // fetched through the authenticated/throttled client, not a raw fetch()/bhttp request - // reddit's anonymous-traffic bot challenge blocks plain unauthenticated .json requests const data = await reddit.getSubmission(post.id); + const mediaMetadata = data.media_metadata || {}; - const items = (data.gallery_data?.items || []) + // 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 = data.media_metadata?.[item.media_id]; + const media = mediaMetadata[item.media_id]; const url = resolveMediaUrl(media); return url && { id: item.media_id, url, datetime: post.datetime, - type: media.m || 'image/jpeg', + type: normalizeType(media.m) || 'image/jpeg', }; }) .filter(Boolean);