2025-12-18 05:53:32 +00:00
|
|
|
import 'dotenv/config'
|
|
|
|
|
import { JSDOM } from 'jsdom'
|
|
|
|
|
import sanitizeHtml from 'sanitize-html'
|
2025-12-18 01:57:14 +00:00
|
|
|
|
|
|
|
|
export function markdownLinkRegex(url) {
|
|
|
|
|
return new RegExp(
|
2025-12-18 05:53:32 +00:00
|
|
|
`(\\[).*?(\\]\\(https:\\/\\/${url.replaceAll('.', '\\.')}\\))`,
|
|
|
|
|
'i',
|
|
|
|
|
)
|
2025-12-18 01:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-25 17:23:40 +00:00
|
|
|
export function htmlLinkRegex(url) {
|
2025-12-25 18:07:09 +00:00
|
|
|
const escapedUrl = url.replaceAll('.', '\\.')
|
2025-12-25 17:23:40 +00:00
|
|
|
return new RegExp(
|
2025-12-25 18:07:09 +00:00
|
|
|
`(<a[^>]+href=["']https?:\\/\\/${escapedUrl}[^"']*["'][^>]*>).*?(<\\/a>)`,
|
2025-12-25 17:23:40 +00:00
|
|
|
'i',
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 01:13:31 +00:00
|
|
|
export function markdownCharEscape(text) {
|
2025-12-22 01:52:53 +00:00
|
|
|
return text
|
|
|
|
|
.replaceAll('`', '\\`')
|
|
|
|
|
.replaceAll('*', '\\*')
|
|
|
|
|
.replaceAll('_', '\\_')
|
|
|
|
|
.replaceAll('{', '\\{')
|
|
|
|
|
.replaceAll('}', '\\}')
|
|
|
|
|
.replaceAll('[', '\\[')
|
|
|
|
|
.replaceAll(']', '\\]')
|
|
|
|
|
.replaceAll('(', '\\(')
|
|
|
|
|
.replaceAll(')', '\\)')
|
|
|
|
|
.replaceAll('#', '\\#')
|
|
|
|
|
.replaceAll('+', '\\+')
|
|
|
|
|
.replaceAll('-', '\\-')
|
|
|
|
|
.replaceAll('.', '\\.')
|
|
|
|
|
.replaceAll('!', '\\!')
|
2025-12-22 01:13:31 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-18 06:21:52 +00:00
|
|
|
export async function getRSSItemTitle(feedUrl) {
|
|
|
|
|
const res = await fetch(feedUrl)
|
|
|
|
|
const data = await res.text()
|
2025-12-25 18:52:25 +00:00
|
|
|
const dom = new JSDOM(data, { contentType: 'text/xml' })
|
2025-12-18 06:21:52 +00:00
|
|
|
const item = dom.window.document.querySelector('item')
|
|
|
|
|
const title = item.querySelector('title').textContent
|
2025-12-25 18:52:25 +00:00
|
|
|
const link = item.querySelector('link').textContent.trim()
|
2025-12-25 20:01:35 +00:00
|
|
|
const cleanTitle = sanitizeHtml(title, {
|
|
|
|
|
allowedTags: [],
|
|
|
|
|
allowedAttributes: {},
|
|
|
|
|
})?.trim()
|
2025-12-25 18:30:27 +00:00
|
|
|
return { text: cleanTitle, url: link }
|
2025-12-18 06:21:52 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 04:12:11 +00:00
|
|
|
export async function getJsonFeedItemTitle(feedUrl, showImage = true) {
|
2025-12-18 05:53:32 +00:00
|
|
|
const res = await fetch(feedUrl)
|
|
|
|
|
const data = await res.json()
|
|
|
|
|
const post = data.items[0]
|
2025-12-25 18:30:27 +00:00
|
|
|
const { title, image, url } = post
|
|
|
|
|
const text = !showImage
|
2025-12-25 20:01:35 +00:00
|
|
|
? title
|
2025-12-25 18:30:27 +00:00
|
|
|
: image
|
2025-12-25 20:01:35 +00:00
|
|
|
? `${title} `
|
|
|
|
|
: title
|
2025-12-25 18:30:27 +00:00
|
|
|
return { text, url }
|
2025-12-18 01:57:14 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 04:12:11 +00:00
|
|
|
export async function getJsonFeedItemContent(feedUrl, showImage = true) {
|
2025-12-18 05:53:32 +00:00
|
|
|
const res = await fetch(feedUrl)
|
|
|
|
|
const data = await res.json()
|
|
|
|
|
const post = data.items[0]
|
2025-12-25 20:01:35 +00:00
|
|
|
const { content_html, image, url } = post
|
2025-12-18 01:57:14 +00:00
|
|
|
const title = sanitizeHtml(content_html, {
|
|
|
|
|
allowedTags: [],
|
|
|
|
|
allowedAttributes: {},
|
2025-12-18 05:53:32 +00:00
|
|
|
})?.trim()
|
2025-12-25 20:01:35 +00:00
|
|
|
const text = !showImage
|
|
|
|
|
? title
|
|
|
|
|
: image
|
|
|
|
|
? `${title} `
|
|
|
|
|
: title
|
|
|
|
|
return { text, url }
|
2025-12-18 01:57:14 +00:00
|
|
|
}
|
2025-12-18 02:39:27 +00:00
|
|
|
|
|
|
|
|
export async function getMalojaScrobble(malojaUrl) {
|
2025-12-18 05:53:32 +00:00
|
|
|
const res = await fetch(`${malojaUrl}/apis/mlj_1/scrobbles?perpage=1`)
|
|
|
|
|
const data = await res.json()
|
|
|
|
|
const scrobble = data.list[0]
|
2025-12-18 02:39:27 +00:00
|
|
|
const {
|
|
|
|
|
title,
|
|
|
|
|
album: { artists },
|
2025-12-18 05:53:32 +00:00
|
|
|
} = scrobble.track
|
2025-12-25 20:01:35 +00:00
|
|
|
return `${title}<br />by ${artists.join(', ')}`
|
2025-12-18 02:39:27 +00:00
|
|
|
}
|
2025-12-18 05:35:31 +00:00
|
|
|
|
2025-12-23 04:12:11 +00:00
|
|
|
export async function getLetterboxdActivity(username, showImage = true) {
|
2025-12-18 05:53:32 +00:00
|
|
|
const res = await fetch(`https://letterboxd.com/${username}/rss/`)
|
|
|
|
|
const data = await res.text()
|
2025-12-25 18:52:25 +00:00
|
|
|
const dom = new JSDOM(data, { contentType: 'text/xml' })
|
2025-12-18 05:53:32 +00:00
|
|
|
const item = dom.window.document.querySelector('item')
|
|
|
|
|
const title = item.querySelector('title').textContent
|
2025-12-25 18:52:25 +00:00
|
|
|
const link = item.querySelector('link').textContent.trim()
|
2025-12-25 19:19:09 +00:00
|
|
|
|
2025-12-25 20:01:35 +00:00
|
|
|
let text = title
|
2025-12-25 19:19:09 +00:00
|
|
|
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
|
2025-12-25 20:01:35 +00:00
|
|
|
text = image ? `${title} ` : title
|
2025-12-25 19:19:09 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-25 18:30:27 +00:00
|
|
|
return { text, url: link }
|
2025-12-18 05:53:32 +00:00
|
|
|
}
|
2025-12-18 05:35:31 +00:00
|
|
|
|
2025-12-23 04:12:11 +00:00
|
|
|
export async function getTraktEpisode(username, id, showImage = true) {
|
2025-12-18 05:53:32 +00:00
|
|
|
const res = await fetch(
|
|
|
|
|
`https://trakt.tv/users/${username}/history/episodes/added/asc.atom?slurm=${process.env.TRAKT_SLURM}`,
|
|
|
|
|
)
|
|
|
|
|
const data = await res.text()
|
|
|
|
|
const dom = new JSDOM(data)
|
|
|
|
|
const entry = dom.window.document.querySelector('entry')
|
|
|
|
|
const title = entry.querySelector('title').textContent
|
2025-12-25 18:30:27 +00:00
|
|
|
const link = entry.querySelector('link').getAttribute('href')
|
|
|
|
|
const text = !showImage
|
2025-12-25 20:01:35 +00:00
|
|
|
? title
|
|
|
|
|
: `${title} `
|
2025-12-25 18:30:27 +00:00
|
|
|
return { text, url: link }
|
2025-12-18 05:35:31 +00:00
|
|
|
}
|
2025-12-22 02:42:53 +00:00
|
|
|
|
2025-12-25 18:20:44 +00:00
|
|
|
export async function getTraktMovie(username, showImage = true) {
|
|
|
|
|
const res = await fetch(
|
|
|
|
|
`https://trakt.tv/users/${username}/history/movies/added/asc.atom?slurm=${process.env.TRAKT_SLURM}`,
|
|
|
|
|
)
|
|
|
|
|
const data = await res.text()
|
|
|
|
|
const dom = new JSDOM(data)
|
|
|
|
|
const entry = dom.window.document.querySelector('entry')
|
|
|
|
|
const title = entry.querySelector('title').textContent
|
2025-12-25 18:30:27 +00:00
|
|
|
const link = entry.querySelector('link').getAttribute('href')
|
2025-12-25 20:01:35 +00:00
|
|
|
return { text: title, url: link }
|
2025-12-25 18:20:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getTraktEpisodeAndMovie(username, id, showImage = true) {
|
|
|
|
|
const episode = await getTraktEpisode(username, id, showImage)
|
|
|
|
|
const movie = await getTraktMovie(username, showImage)
|
2025-12-25 18:30:27 +00:00
|
|
|
return {
|
|
|
|
|
text: `${episode.text} <br>and<br> ${movie.text}`,
|
|
|
|
|
url: `https://trakt.tv/users/${username}`,
|
|
|
|
|
}
|
2025-12-25 18:20:44 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-22 02:42:53 +00:00
|
|
|
export async function getReadingBooklogr(booklogrUrl, booklogrUser) {
|
|
|
|
|
const res = await fetch(`${booklogrUrl}/v1/profiles/${booklogrUser}`)
|
|
|
|
|
const data = await res.json()
|
|
|
|
|
const books = data.books.filter(
|
|
|
|
|
({ reading_status }) => reading_status === 'Currently reading',
|
|
|
|
|
)
|
2025-12-24 06:31:37 +00:00
|
|
|
return books.map(({ title }) => title).join(',<br />')
|
2025-12-22 02:42:53 +00:00
|
|
|
}
|