Multiple feeds and improve rendering

This commit is contained in:
Bad Manners 2024-09-14 21:55:46 -03:00
parent dadbd32e1b
commit d56a8cc95d
No known key found for this signature in database
GPG key ID: 8C88292CCB075609
13 changed files with 273 additions and 184 deletions

View file

@ -1,139 +1,9 @@
import rss, { type RSSFeedItem } from "@astrojs/rss";
import rss from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection, getEntries, type CollectionEntry, type CollectionKey } from "astro:content";
import { markdown } from "@astropub/md";
import sanitizeHtml from "sanitize-html";
import { t, type Lang } from "@i18n";
import { markdownToPlaintext } from "@utils/markdown_to_plaintext";
import { getUsernameForLang } from "@utils/get_username_for_lang";
import { qualifyLocalURLsInMarkdown } from "@utils/qualify_local_urls_in_markdown";
import { getCollection } from "astro:content";
import { blogFeedItem, gameFeedItem, storyFeedItem, type EntryWithPubDate } from "@utils/feed";
type FeedItem = RSSFeedItem &
Required<Pick<RSSFeedItem, "title" | "pubDate" | "link" | "description" | "categories" | "content">>;
type EntryWithPubDate<C extends CollectionKey> = CollectionEntry<C> & { data: { pubDate: Date } };
const MAX_ITEMS = 6;
function toNoonUTCDate(date: Date) {
const adjustedDate = new Date(date);
adjustedDate.setUTCHours(12);
return adjustedDate;
}
const getLinkForUser = (user: CollectionEntry<"users">, lang: Lang) => {
const userName = getUsernameForLang(user, lang);
if (user.data.preferredLink) {
return `<a href="${user.data.links[user.data.preferredLink]!.link}">${userName}</a>`;
}
return userName;
};
async function storyFeedItem(
site: URL | undefined,
data: EntryWithPubDate<"stories">["data"],
slug: CollectionEntry<"stories">["slug"],
body: string,
): Promise<FeedItem> {
return {
title: `New story! "${data.title}"`,
pubDate: toNoonUTCDate(data.pubDate),
link: `/stories/${slug}`,
description:
`${t(data.lang, "story/warnings", data.wordCount, data.contentWarning)}\n\n${markdownToPlaintext(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`.replaceAll(
/[\n ]+/g,
" ",
),
categories: ["story"],
commentsUrl: data.posts.mastodon?.link,
content: sanitizeHtml(
`<h1>${data.title}</h1>` +
`<p>${t(
data.lang,
"story/authors",
(await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
)}</p>` +
(data.requesters
? `<p>${t(
data.lang,
"story/requested_by",
(await getEntries(data.requesters)).map((requester) => getLinkForUser(requester, data.lang)),
)}</p>`
: "") +
(data.commissioners
? `<p>${t(
data.lang,
"story/commissioned_by",
(await getEntries(data.commissioners)).map((commissioner) => getLinkForUser(commissioner, data.lang)),
)}</p>`
: "") +
`<hr><p><em>${t(data.lang, "story/warnings", data.wordCount, data.contentWarning)}</em></p>` +
`<hr>${await markdown(body)}` +
`<hr>${await markdown(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`,
),
};
}
async function gameFeedItem(
site: URL | undefined,
data: EntryWithPubDate<"games">["data"],
slug: CollectionEntry<"games">["slug"],
body: string,
): Promise<FeedItem> {
return {
title: `New game! "${data.title}"`,
pubDate: toNoonUTCDate(data.pubDate),
link: `/games/${slug}`,
description:
`${t(data.lang, "game/warnings", data.platforms, data.contentWarning)}\n\n${markdownToPlaintext(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`.replaceAll(
/[\n ]+/g,
" ",
),
categories: ["game"],
commentsUrl: data.posts.mastodon?.link,
content: sanitizeHtml(
`<h1>${data.title}</h1>` +
`<p>${t(
data.lang,
"story/authors",
(await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
)}</p>` +
`<hr><p>${t(data.lang, "game/platforms", data.platforms)}</p>` +
`<hr><p><em>${data.contentWarning}</em></p>` +
`<hr>${await markdown(body)}` +
`<hr>${await markdown(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}`,
),
};
}
async function blogFeedItem(
site: URL | undefined,
data: EntryWithPubDate<"blog">["data"],
slug: CollectionEntry<"blog">["slug"],
body: string,
): Promise<FeedItem> {
return {
title: `New blog post! "${data.title}"`,
pubDate: toNoonUTCDate(data.pubDate),
link: `/blog/${slug}`,
description: markdownToPlaintext(await qualifyLocalURLsInMarkdown(data.description, data.lang, site)).replaceAll(
/[\n ]+/g,
" ",
),
categories: ["blog post"],
commentsUrl: data.posts.mastodon?.link,
content: sanitizeHtml(
`<h1>${data.title}</h1>` +
`<p>${t(
data.lang,
"story/authors",
(await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
)}</p>` +
`<hr>${await markdown(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}` +
`<hr>${await markdown(body)}`,
),
};
}
const MAX_ITEMS = 8;
export const GET: APIRoute = async ({ site }) => {
const stories = (
@ -157,21 +27,21 @@ export const GET: APIRoute = async ({ site }) => {
return rss({
title: "Gallery | Bad Manners",
description: "Stories, games, and (possibly) more by Bad Manners",
description: "Stories, games, and more by Bad Manners.",
site: site!,
items: await Promise.all(
[
stories.map(({ data, slug, body }) => ({
stories.map(({ data, slug, render }) => ({
date: data.pubDate,
fn: () => storyFeedItem(site, data, slug, body),
fn: async () => storyFeedItem(site, data, slug, (await render()).Content),
})),
games.map(({ data, slug, body }) => ({
games.map(({ data, slug, render }) => ({
date: data.pubDate,
fn: () => gameFeedItem(site, data, slug, body),
fn: async () => gameFeedItem(site, data, slug, (await render()).Content),
})),
posts.map(({ data, slug, body }) => ({
posts.map(({ data, slug, render }) => ({
date: data.pubDate,
fn: () => blogFeedItem(site, data, slug, body),
fn: async () => blogFeedItem(site, data, slug, (await render()).Content),
})),
]
.flat()