diff --git a/config.js b/config.js index 74f87e8..788bb68 100644 --- a/config.js +++ b/config.js @@ -5,7 +5,8 @@ import { getMalojaScrobble, getReadingBooklogr, getRSSItemTitle, - getTraktEpisodeAndMovie, + getTraktEpisode, + getTraktMovie, htmlLinkRegex, } from './utils.js' @@ -28,9 +29,14 @@ export const items = [ getLatest: async () => getLetterboxdActivity('stfudonny', false), }, { - id: 'trakt', + id: 'trakt-episode', regex: htmlLinkRegex('trakt.tv/users/crankle'), getLatest: async () => - getTraktEpisodeAndMovie('crankle', '78e1e87b446901f7e4f0883dd4995cec', false), + getTraktEpisode('crankle', '78e1e87b446901f7e4f0883dd4995cec', false), + }, + { + id: 'trakt-movie', + regex: htmlLinkRegex('trakt.tv/users/crankle'), + getLatest: async () => getTraktMovie('crankle', false), }, ] diff --git a/index.js b/index.js index 0b035a7..1a7fa34 100644 --- a/index.js +++ b/index.js @@ -34,9 +34,17 @@ export default async function now() { items.map(async ({ id, regex, getLatest }) => { try { const latest = await getLatest() - console.log(`${id}: ${latest}`) - if (latest) { - newNow = newNow.replace(regex, `$1${latest}$2`) + console.log(`${id}: ${latest.text}`) + console.log(`${id} URL: ${latest.url}`) + if (latest && latest.text && latest.url) { + newNow = newNow.replace(regex, (match, openTag, closeTag) => { + // Replace href in opening tag + const updatedOpenTag = openTag.replace( + /href=["'][^"']*["']/, + `href="${latest.url}"`, + ) + return `${updatedOpenTag}${latest.text}${closeTag}` + }) } } catch (e) { console.warn(`⚠️ Failed to fetch ${id}`, e) diff --git a/now-page-template.md b/now-page-template.md index 0964b6e..4c01547 100644 --- a/now-page-template.md +++ b/now-page-template.md @@ -15,8 +15,11 @@ I rated this
{letterboxd} tktk
- I watched this
{trakt} tktk -
+ I watched this episode
+ tktk
+ and this movie
+ tktk + --- diff --git a/utils.js b/utils.js index c9fd645..30a5c33 100644 --- a/utils.js +++ b/utils.js @@ -38,27 +38,31 @@ export function markdownCharEscape(text) { export async function getRSSItemTitle(feedUrl) { const res = await fetch(feedUrl) const data = await res.text() - const dom = new JSDOM(data) + const dom = new JSDOM(data, { contentType: 'text/xml' }) const item = dom.window.document.querySelector('item') const title = item.querySelector('title').textContent - return markdownCharEscape( + const link = item.querySelector('link').textContent.trim() + const cleanTitle = markdownCharEscape( sanitizeHtml(title, { allowedTags: [], allowedAttributes: {}, })?.trim(), ) + return { text: cleanTitle, url: link } } 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 } = post + const { title, image, url } = post const cleanTitle = markdownCharEscape(title) - if (!showImage) { - return cleanTitle - } - return image ? `${cleanTitle} ![${cleanTitle}](${image})` : cleanTitle + const text = !showImage + ? cleanTitle + : image + ? `${cleanTitle} ![${cleanTitle}](${image})` + : cleanTitle + return { text, url } } export async function getJsonFeedItemContent(feedUrl, showImage = true) { @@ -93,15 +97,22 @@ export async function getMalojaScrobble(malojaUrl) { export async function getLetterboxdActivity(username, showImage = true) { const res = await fetch(`https://letterboxd.com/${username}/rss/`) const data = await res.text() - const dom = new JSDOM(data) + const dom = new JSDOM(data, { contentType: 'text/xml' }) const item = dom.window.document.querySelector('item') const title = item.querySelector('title').textContent + const link = item.querySelector('link').textContent.trim() const cleanTitle = markdownCharEscape(title) - const image = item.querySelector('description > img').src - if (!showImage) { - return cleanTitle + + let text = cleanTitle + 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 ? `${cleanTitle} ![${cleanTitle}](${image})` : cleanTitle } - return `${cleanTitle} ![${cleanTitle}](${image})` + + return { text, url: link } } export async function getTraktEpisode(username, id, showImage = true) { @@ -112,11 +123,12 @@ export async function getTraktEpisode(username, id, showImage = true) { const dom = new JSDOM(data) const entry = dom.window.document.querySelector('entry') const title = entry.querySelector('title').textContent + const link = entry.querySelector('link').getAttribute('href') const cleanTitle = markdownCharEscape(title) - if (!showImage) { - return cleanTitle - } - return `${cleanTitle} ![${cleanTitle}](https://widgets.trakt.tv/users/${id}/watched/thumb@2x.jpg?type=episode&image_only=1)` + const text = !showImage + ? cleanTitle + : `${cleanTitle} ![${cleanTitle}](https://widgets.trakt.tv/users/${id}/watched/thumb@2x.jpg?type=episode&image_only=1)` + return { text, url: link } } export async function getTraktMovie(username, showImage = true) { @@ -127,13 +139,17 @@ export async function getTraktMovie(username, showImage = true) { const dom = new JSDOM(data) const entry = dom.window.document.querySelector('entry') const title = entry.querySelector('title').textContent - return markdownCharEscape(title) + const link = entry.querySelector('link').getAttribute('href') + return { text: markdownCharEscape(title), url: link } } export async function getTraktEpisodeAndMovie(username, id, showImage = true) { const episode = await getTraktEpisode(username, id, showImage) const movie = await getTraktMovie(username, showImage) - return `${episode}
and
${movie}` + return { + text: `${episode.text}
and
${movie.text}`, + url: `https://trakt.tv/users/${username}`, + } } export async function getReadingBooklogr(booklogrUrl, booklogrUser) {