203 lines
7.4 KiB
TypeScript
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" } },
|
|
);
|
|
};
|