Merge pull request 'add direct linking to individual items from feeds' (#1) from update/direct-links into main

Reviewed-on: https://source.tube/dylan/now-updater/pulls/1
This commit is contained in:
dylan 2025-12-25 19:27:25 +00:00
commit a3ed5a4a82
4 changed files with 59 additions and 26 deletions

View file

@ -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),
},
]

View file

@ -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)

View file

@ -15,7 +15,10 @@
I rated this<br>{letterboxd} <a href="https://letterboxd.com/STFUDonny" style="color: #f38ba8 !important;">tktk</a>
</div>
<div style="background: rgba(250, 179, 135, 0.15); padding: 1rem; border: 4px solid #fab387; color:white;">
I watched this<br>{trakt} <a href="https://trakt.tv/users/crankle" style="color: #fab387 !important;">tktk</a>
I watched this episode<br>
<i class="fa-solid fa-tv"></i> <a href="https://trakt.tv/users/crankle" style="color: #fab387 !important;">tktk</a><br>
and this movie<br>
<i class="fa-solid fa-film"></i> <a href="https://trakt.tv/users/crankle" style="color: #fab387 !important;">tktk</a>
</div>
</div>

View file

@ -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(/<img[^>]+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} <br>and<br> ${movie}`
return {
text: `${episode.text} <br>and<br> ${movie.text}`,
url: `https://trakt.tv/users/${username}`,
}
}
export async function getReadingBooklogr(booklogrUrl, booklogrUser) {