From 689079cab4b5c5c71e032825484f65b360494fab Mon Sep 17 00:00:00 2001 From: dylan Date: Sun, 28 Dec 2025 17:45:59 +0000 Subject: [PATCH] updated to include image extraction and separate image urls --- index.js | 17 ++++++++++++++++- services.js | 7 ++++--- utils.js | 51 +++++++++++++++++++++++++++++---------------------- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index c387272..15d15c4 100644 --- a/index.js +++ b/index.js @@ -79,7 +79,7 @@ export default async function now() { } // generic handlers expect just feedUrl string, special handlers expect config object - const handlerParam = ['rss', 'atom', 'json', 'mastodon', 'maloja'].includes( + const handlerParam = ['rss', 'atom', 'json', 'maloja'].includes( item.feedType, ) ? feedUrl @@ -109,6 +109,21 @@ export default async function now() { ) return `${updatedOpenTag}${displayText}${closeTag}` }) + + // replace image placeholders if image is available + if (latest.image) { + const imageId = `${item.id}-image` + // html image replacement + newNow = newNow.replace( + new RegExp(`(]+src=["'])${imageId}(["'][^>]*>)`, 'gi'), + `$1${latest.image}$2`, + ) + // markdown image replacement + newNow = newNow.replace( + new RegExp(`(!\\[[^\\]]*\\]\\()${imageId}(\\))`, 'gi'), + `$1${latest.image}$2`, + ) + } } } catch (e) { console.warn(`⚠️ Failed to fetch ${item.id}`, e) diff --git a/services.js b/services.js index 7d8c344..a52273a 100644 --- a/services.js +++ b/services.js @@ -64,6 +64,7 @@ export const services = [ instance: 'social.lol', // your mastodon instance feedUrlTemplate: 'https://{instance}/@{userId}.rss', feedType: 'mastodon', + showImage: false, }, { id: 'pixelfed', @@ -90,7 +91,7 @@ export const services = [ userId: 'stfudonny', // your letterboxd username feedUrlTemplate: 'https://letterboxd.com/{userId}/rss/', feedType: 'letterboxd', - showImage: false, + showImage: true, }, { id: 'trakt-episode', @@ -98,7 +99,7 @@ export const services = [ userId: 'crankle', // your trakt username traktId: '78e1e87b446901f7e4f0883dd4995cec', // your trakt ID feedType: 'trakt-episode', - showImage: false, + showImage: true, }, { id: 'trakt-movie', @@ -106,7 +107,7 @@ export const services = [ userId: 'crankle', // your trakt username traktId: '78e1e87b446901f7e4f0883dd4995cec', // your trakt ID feedType: 'trakt-movie', - showImage: false, + showImage: true, }, // ======================================================================== diff --git a/utils.js b/utils.js index 0a59f69..bb76488 100644 --- a/utils.js +++ b/utils.js @@ -78,18 +78,13 @@ export async function getAtomFeed(url) { } // json feed parser - grabs the latest item title -// optionally includes images in markdown format +// optionally includes images separately export async function getJsonFeedItemTitle(feedUrl, showImage = true) { const res = await fetch(feedUrl) const data = await res.json() const post = data.items[0] const { title, image, url } = post - const text = !showImage - ? title - : image - ? `${title} ![${title}](${image})` - : title - return { text, url } + return { text: title, url, image: showImage ? image : null } } // ======================================================================== @@ -133,7 +128,10 @@ export async function getMalojaScrobble(malojaUrl) { // mastodon rss feed parser - filters out replies (posts starting with @) // also replaces urls with 🔗 emoji to keep things tidy -export async function getMastodonPost(feedUrl) { +// extracts images from posts if available +export async function getMastodonPost(config) { + const { feedUrl, showImage = false } = + typeof config === 'string' ? { feedUrl: config } : config const res = await fetch(feedUrl) const data = await res.text() const dom = new JSDOM(data, { contentType: 'text/xml' }) @@ -152,6 +150,10 @@ export async function getMastodonPost(feedUrl) { const link = item.querySelector('link')?.textContent.trim() + // extract image from description html if available + const imgMatch = description.match(/]+src="([^"]+)"/) + const image = imgMatch ? imgMatch[1] : null + // use description as the text, stripping HTML let cleanText = sanitizeHtml(description, { allowedTags: [], @@ -161,20 +163,26 @@ export async function getMastodonPost(feedUrl) { // replace URLs with a link emoji cleanText = cleanText.replace(/https?:\/\/\S+/g, '🔗').trim() - return { text: cleanText, url: link } + return { text: cleanText, url: link, image: showImage ? image : null } } // fallback to first item if no non-reply found const firstItem = items[0] const description = firstItem.querySelector('description')?.textContent || '' const link = firstItem.querySelector('link')?.textContent.trim() + + // extract image from description html if available + const imgMatch = description.match(/]+src="([^"]+)"/) + const image = imgMatch ? imgMatch[1] : null + let cleanText = sanitizeHtml(description, { allowedTags: [], allowedAttributes: {}, })?.trim() // replace URLs with link emoji cleanText = cleanText.replace(/https?:\/\/\S+/g, '🔗').trim() - return { text: cleanText, url: link } + + return { text: cleanText, url: link, image: showImage ? image : null } } // ======================================================================== @@ -192,16 +200,15 @@ export async function getLetterboxdActivity(config) { const title = item.querySelector('title').textContent const link = item.querySelector('link').textContent.trim() - let text = title + let image = null if (showImage) { // Parse CDATA content as HTML to extract image const description = item.querySelector('description').textContent const imgMatch = description.match(/]+src="([^"]+)"/) - const image = imgMatch ? imgMatch[1] : null - text = image ? `${title} ![${title}](${image})` : title + image = imgMatch ? imgMatch[1] : null } - return { text, url: link } + return { text: title, url: link, image } } // trakt episode history - requires TRAKT_SLURM variable (see readme for info) @@ -216,10 +223,10 @@ export async function getTraktEpisode(config) { const entry = dom.window.document.querySelector('entry') const title = entry.querySelector('title').textContent const link = entry.querySelector('link').getAttribute('href') - const text = !showImage - ? title - : `${title} ![${title}](https://widgets.trakt.tv/users/${traktId}/watched/thumb@2x.jpg?type=episode&image_only=1)` - return { text, url: link } + const image = showImage + ? `https://widgets.trakt.tv/users/${traktId}/watched/thumb@2x.jpg?type=episode&image_only=1` + : null + return { text: title, url: link, image } } // trakt movie history - requires TRAKT_SLURM variable (see readme for info) @@ -234,10 +241,10 @@ export async function getTraktMovie(config) { const entry = dom.window.document.querySelector('entry') const title = entry.querySelector('title').textContent const link = entry.querySelector('link').getAttribute('href') - const text = !showImage - ? title - : `${title} ![${title}](https://widgets.trakt.tv/users/${traktId}/watched/thumb@2x.jpg?type=movie&image_only=1)` - return { text, url: link } + const image = showImage + ? `https://widgets.trakt.tv/users/${traktId}/watched/thumb@2x.jpg?type=movie&image_only=1` + : null + return { text: title, url: link, image } } // ========================================================================