gallery.badmanners.xyz/src/pages/api/export-story/[...slug].ts

203 lines
7.4 KiB
TypeScript

import type { APIRoute, GetStaticPaths } from "astro";
import { getCollection, type CollectionEntry, getEntries } from "astro:content";
import type { Website } from "../../../content/config";
import { t, type Lang } from "../../../i18n";
import { formatCopyrightedCharacters } from "../../../utils/format_copyrighted_characters";
import { markdownToBbcode } from "../../../utils/markdown_to_bbcode";
import { getUsernameForLang } from "../../../utils/get_username_for_lang";
import { isAnonymousUser } from "../../../utils/is_anonymous_user";
interface ExportWebsiteInfo {
website: Website;
exportFormat: "bbcode" | "markdown";
}
const WEBSITE_LIST = [
{ website: "eka", exportFormat: "bbcode" },
{ website: "furaffinity", exportFormat: "bbcode" },
{ website: "inkbunny", exportFormat: "bbcode" },
{ website: "sofurry", exportFormat: "bbcode" },
{ website: "weasyl", exportFormat: "markdown" },
] as const satisfies ExportWebsiteInfo[];
type ExportWebsiteName = typeof WEBSITE_LIST extends ReadonlyArray<{ website: infer K }> ? K : never;
function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website): string {
const link = user.data.links[website];
if (link && "username" in link && link.username) {
return link.username;
}
throw new Error(`Cannot get "${website}" username for user "${user.id}"`);
}
function isPreferredWebsite(user: CollectionEntry<"users">, website: Website): boolean {
const { preferredLink } = user.data;
return !preferredLink || preferredLink == website;
}
function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsiteName, lang: Lang): string {
const { links, preferredLink } = user.data;
switch (website) {
case "eka":
if ("eka" in links) {
return `:icon${getUsernameForWebsite(user, "eka")}:`;
}
break;
case "furaffinity":
if ("furaffinity" in links) {
return `:icon${getUsernameForWebsite(user, "furaffinity")}:`;
}
break;
case "weasyl":
if ("weasyl" in links) {
return `<!~${getUsernameForWebsite(user, "weasyl").replaceAll(" ", "")}>`;
} else if (isPreferredWebsite(user, "furaffinity")) {
return `<fa:${getUsernameForWebsite(user, "furaffinity").replaceAll("_", "")}>`;
} else if (isPreferredWebsite(user, "inkbunny")) {
return `<ib:${getUsernameForWebsite(user, "inkbunny")}>`;
} else if (isPreferredWebsite(user, "sofurry")) {
return `<sf:${getUsernameForWebsite(user, "sofurry")}>`;
}
break;
case "inkbunny":
if ("inkbunny" in links) {
return `[iconname]${getUsernameForWebsite(user, "inkbunny")}[/iconname]`;
} else if (isPreferredWebsite(user, "furaffinity")) {
return `[fa]${getUsernameForWebsite(user, "furaffinity")}[/fa]`;
} else if (isPreferredWebsite(user, "sofurry")) {
return `[sf]${getUsernameForWebsite(user, "sofurry")}[/sf]`;
} else if (isPreferredWebsite(user, "weasyl")) {
return `[weasyl]${getUsernameForWebsite(user, "weasyl")}[/weasyl]`;
}
break;
case "sofurry":
if ("sofurry" in links) {
return `:icon${getUsernameForWebsite(user, "sofurry")}:`;
} else if (isPreferredWebsite(user, "furaffinity")) {
return `fa!${getUsernameForWebsite(user, "furaffinity")}`;
} else if (isPreferredWebsite(user, "inkbunny")) {
return `ib!${getUsernameForWebsite(user, "inkbunny")}`;
}
break;
default:
const unknown: never = website;
throw new Error(`Unhandled export website "${unknown}"`);
}
if (preferredLink) {
const preferred = links[preferredLink]!;
return `[${getUsernameForLang(user, lang)}](${preferred.link})`;
}
throw new Error(
`No matching "${website}" link for user "${user.id}" (consider setting their "preferredLink" property)`,
);
}
type Props = {
story: CollectionEntry<"stories">;
};
type Params = {
slug: CollectionEntry<"stories">["slug"];
};
export const getStaticPaths: GetStaticPaths = async () => {
if (import.meta.env.PROD) {
return [];
}
return (await getCollection("stories")).map((story) => ({
params: { slug: story.slug } satisfies Params,
props: { story } satisfies Props,
}));
};
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
const { lang } = story.data;
const copyrightedCharacters = await formatCopyrightedCharacters(story.data.copyrightedCharacters);
const authorsList = await getEntries(story.data.authors);
const commissionersList = story.data.commissioner && (await getEntries(story.data.commissioner));
const requestersList = story.data.requester && (await getEntries(story.data.requester));
const description = Object.fromEntries(
WEBSITE_LIST.map<[ExportWebsiteName, string]>(({ website, exportFormat }) => {
const u = (user: CollectionEntry<"users">) =>
isAnonymousUser(user) ? getUsernameForLang(user, lang) : getLinkForUser(user, website, lang);
const storyDescription = (
[
story.data.description,
`*${t(lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}*`,
t(
lang,
"export_story/authors",
authorsList.map((author) => u(author)),
),
requestersList &&
t(
lang,
"export_story/request_for",
requestersList.map((requester) => u(requester)),
),
commissionersList &&
t(
lang,
"export_story/commissioned_by",
commissionersList.map((commissioner) => u(commissioner)),
),
...copyrightedCharacters.map(([user, characterList]) =>
characterList[0] == ""
? t(lang, "characters/all_characters_are_copyrighted_by", u(user))
: t(lang, "characters/characters_are_copyrighted_by", u(user), characterList),
),
].filter((data) => data) as string[]
)
.join("\n\n")
.replaceAll(
/\[([^\]]+)\]\((\/[^\)]+)\)/g,
(_, group1, group2) => `[${group1}](${new URL(group2, site).toString()})`,
);
switch (exportFormat) {
case "bbcode":
return [website, markdownToBbcode(storyDescription).replaceAll(/\n\n\n+/g, "\n\n")];
case "markdown":
return [website, storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()];
default:
const unknown: never = exportFormat;
throw new Error(`Unknown export format "${unknown}"`);
}
}),
);
const storyHeader =
`${story.data.title}\n` +
`${t(
lang,
"story/authors",
authorsList.map((author) => getUsernameForLang(author, lang)),
)}\n` +
(requestersList
? `${t(
lang,
"story/requested_by",
requestersList.map((requester) => getUsernameForLang(requester, lang)),
)}\n`
: "") +
(commissionersList
? `${t(
lang,
"story/commissioned_by",
commissionersList.map((commissioner) => getUsernameForLang(commissioner, lang)),
)}\n`
: "");
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll(/\\([=*])/g, "$1")}`
.replaceAll(/\n\n\n+/g, "\n\n")
.trim();
return new Response(
JSON.stringify({
story: storyText,
description,
thumbnail: story.data.thumbnail ? story.data.thumbnail.src : null,
}),
{ headers: { "Content-Type": "application/json; charset=utf-8" } },
);
};