UNPKG

rsshub

Version:
184 lines (183 loc) 6.95 kB
import { t as rofetch } from "./ofetch-C3ts-Hud.mjs"; import { t as config } from "./config-CCmw1BNE.mjs"; import { t as logger } from "./logger-BKy98Y8B.mjs"; import { t as parseDate } from "./parse-date-Cr0wQjV_.mjs"; import { t as cache_default } from "./cache-BkqOokyU.mjs"; import { t as InvalidParameterError } from "./invalid-parameter-CGxhjomn.mjs"; import { load } from "cheerio"; //#region lib/routes/theinitium/utils.ts const stripLangSuffix = (name) => name.replace(/-zh-hans$/i, ""); const GHOST_API_BASE = "https://production-initium-media.ghost.io/ghost/api/content"; const GHOST_CONTENT_KEY = "a44a0409c222328d39e2c75293"; const CHANNEL_TAG_MAP = { latest: "", whatsnew: "whatsnew", "news-brief": "whatsnew", opinion: "opinion", international: "international", mainland: "mainland", hongkong: "hong-kong", taiwan: "taiwan", technology: "technology", feature: "report", report: "report", "daily-brief": "daily-brief", weekly: "weekly" }; function applyLanguageToTagSlug(tagSlug, language) { if (language === "zh-hans") return `${tagSlug}-zh-hans`; return tagSlug; } async function ghostFetch(endpoint, params = {}) { const url = new URL(`${GHOST_API_BASE}/${endpoint}/`); url.searchParams.set("key", GHOST_CONTENT_KEY); for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v); return await rofetch(url.href); } async function scrapeFullArticle(url, cookie) { try { const article = load(await rofetch(url, { headers: { Cookie: cookie }, parseResponse: (txt) => txt }))("article"); if (article.length === 0) return null; if (article.find(".gh-post-upgrade-cta").length > 0) return null; return article.html(); } catch (error) { logger.warn(`Failed to scrape Initium article: ${url}`, error); return null; } } /** * Clean Ghost Koenig editor card HTML for RSS consumption. * Strips kg-* wrapper divs, converts bookmark cards to simple links, * removes callout markup, etc. */ function cleanGhostHtml(html) { const $ = load(html, null, false); $("a.kg-bookmark-container, a.kg-bookmark-card").each((_, el) => { const $el = $(el); const href = $el.attr("href") || ""; const title = $el.find(".kg-bookmark-title").text().trim(); const desc = $el.find(".kg-bookmark-description").text().trim(); const replacement = title ? `<p><a href="${href}">${title}</a>${desc ? ` — ${desc}` : ""}</p>` : `<p><a href="${href}">${href}</a></p>`; $el.replaceWith(replacement); }); $(".kg-callout-card").each((_, el) => { const $el = $(el); const text = $el.find(".kg-callout-text").html() || $el.html() || ""; $el.replaceWith(`<blockquote>${text}</blockquote>`); }); $(".kg-toggle-card").each((_, el) => { const $el = $(el); const heading = $el.find(".kg-toggle-heading-text").text().trim(); const content = $el.find(".kg-toggle-content").html() || ""; $el.replaceWith(`${heading ? `<p><strong>${heading}</strong></p>` : ""}${content}`); }); $(".kg-card").each((_, el) => { const $el = $(el); $el.replaceWith($el.html() || ""); }); $("figure.kg-bookmark-card").each((_, el) => { const $el = $(el); $el.replaceWith($el.html() || ""); }); $("p:contains(\"<!--members-only-->\")").remove(); return $.html(); } async function postsToItems(posts) { const memberCookie = config.initium?.memberCookie; return await Promise.all(posts.map(async (post) => { const authors = post.authors?.map((a) => stripLangSuffix(a.name)) ?? []; const categories = post.tags?.filter((t) => t.visibility === "public").map((t) => stripLangSuffix(t.name)) ?? []; let description = post.html ? cleanGhostHtml(post.html) : post.html; if (!post.access && memberCookie) { const fullHtml = await cache_default.tryGet(`theinitium:full:${post.slug}`, () => scrapeFullArticle(post.url, memberCookie), config.cache.contentExpire); if (fullHtml) description = cleanGhostHtml(fullHtml); } return { title: post.title, author: authors.join(", ") || post.primary_author?.name || "", category: categories, description, link: post.url, pubDate: parseDate(post.published_at), updated: parseDate(post.updated_at), guid: post.uuid, banner: post.feature_image ?? void 0 }; })); } const processFeed = async (model, ctx) => { const type = ctx.req.param("type") ?? "latest"; const language = ctx.req.param("language") ?? ""; let filter = ""; let listLink = ""; let feedName = ""; switch (model) { case "channel": { const baseTag = CHANNEL_TAG_MAP[type] ?? type; if (baseTag === "") { if (language === "zh-hans" || language === "zh-hant") filter = `tag:hash-${language}`; } else filter = `tag:${language ? applyLanguageToTagSlug(baseTag, language) : baseTag}`; listLink = type === "latest" ? "https://theinitium.com/latest/" : `https://theinitium.com/tag/${baseTag}/`; feedName = type; break; } case "tags": filter = `tag:${language ? applyLanguageToTagSlug(type, language) : type}`; listLink = `https://theinitium.com/tag/${type}/`; feedName = type; break; case "author": filter = `author:${language === "zh-hans" ? `${type}-zh-hans` : type}`; listLink = `https://theinitium.com/author/${type}/`; feedName = type; break; default: throw new InvalidParameterError(`Unsupported model: ${model}`); } const cacheKey = `theinitium:ghost:${model}:${type}:${language}`; const data = await cache_default.tryGet(cacheKey, async () => { const params = { include: "tags,authors", limit: "20" }; if (filter) params.filter = filter; return await ghostFetch("posts", params); }, config.cache.routeExpire, false); const items = await postsToItems(data.posts); let displayName = feedName; if (data.posts.length > 0) switch (model) { case "channel": { const baseTag = CHANNEL_TAG_MAP[type] ?? type; if (baseTag) { const langTag = language === "zh-hans" ? `${baseTag}-zh-hans` : baseTag; const matchedTag = data.posts[0].tags?.find((t) => t.slug === langTag || t.slug === baseTag); if (matchedTag) displayName = stripLangSuffix(matchedTag.name); } else displayName = "最新"; break; } case "tags": { const langTag = language === "zh-hans" ? `${type}-zh-hans` : type; const matchedTag = data.posts[0].tags?.find((t) => t.slug === langTag || t.slug === type); if (matchedTag) displayName = stripLangSuffix(matchedTag.name); break; } case "author": { const authorSlug = language === "zh-hans" ? `${type}-zh-hans` : type; const matchedAuthor = data.posts[0].authors?.find((a) => a.slug === authorSlug || a.slug === type); if (matchedAuthor) displayName = stripLangSuffix(matchedAuthor.name); break; } default: } return { title: `端傳媒 - ${displayName}`, link: listLink, icon: "https://theinitium.com/favicon.ico", language: language === "zh-hans" ? "zh-CN" : "zh-TW", item: items }; }; //#endregion export { processFeed as a, postsToItems as i, applyLanguageToTagSlug as n, ghostFetch as r, CHANNEL_TAG_MAP as t };