summaryrefslogtreecommitdiff
path: root/lib/teawie.ts
diff options
context:
space:
mode:
authorseth <[email protected]>2024-10-12 14:28:54 -0400
committerseth <[email protected]>2024-10-12 14:59:04 -0400
commit6a2d9e752fab27b59da4f194b0ef6daf7e8b6d81 (patch)
treeed8f9f07861a0a4463dcd910baa349b6cc6656aa /lib/teawie.ts
parent08912b439bd61088dd849b9342a81341fa9e4a23 (diff)
port to deno
Diffstat (limited to 'lib/teawie.ts')
-rw-r--r--lib/teawie.ts108
1 files changed, 108 insertions, 0 deletions
diff --git a/lib/teawie.ts b/lib/teawie.ts
new file mode 100644
index 0000000..b5c68c7
--- /dev/null
+++ b/lib/teawie.ts
@@ -0,0 +1,108 @@
+import { USER_AGENT } from "./consts.ts";
+import { Endpoints } from "@octokit/types";
+
+const URL_CACHE_KEY = ["urls"];
+const URL_CACHE_TTL_MS = 1000 * 60 * 60; // 1 hour
+
+type repositoryPathContentsResponse =
+ Endpoints["GET /repos/{owner}/{repo}/contents/{path}"]["response"];
+
+const GITHUB_API = "https://api.github.com";
+
+// Teawie repository owner and name
+const REPO_OWNER = "SympathyTea";
+const REPO_NAME = "Teawie-Archive";
+
+// Subdirectories of the above repository containing files we want
+const SUBDIRS = [
+ "teawie-media/Original Teawies",
+ "teawie-media/Teawie Variants",
+ "teawie-media/Teawie in Places",
+ "teawie-media/Unfinished Teawies",
+];
+
+// File extensions we consider to be images
+const IMAGE_EXTENSIONS = ["gif", "jpg", "jpeg", "png", "svg", "webp"];
+
+const contentsOf = (
+ path: string,
+): Promise<repositoryPathContentsResponse["data"]> =>
+ fetch(`${GITHUB_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${path}`, {
+ headers: {
+ accept: "application/vnd.github+json",
+ "user-agent": USER_AGENT,
+ },
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(
+ `HTTP Error ${response.status}: ${response.statusText}`,
+ );
+ }
+
+ return response.json();
+ })
+ .then((json) => {
+ return json as repositoryPathContentsResponse["data"];
+ });
+
+const imageUrlsIn = (
+ files: repositoryPathContentsResponse["data"],
+): string[] => {
+ // NOTE: This is done because the response may only contain data
+ // for a single file's path
+ const filesArray = Array.isArray(files) ? files : [files];
+
+ return (
+ filesArray
+ // Find *files* that are (probably) images and have a download URL
+ .filter(
+ (file) =>
+ !Array.isArray(file) &&
+ file.download_url &&
+ file.type == "file" &&
+ IMAGE_EXTENSIONS.includes(
+ file.name.split(".").at(-1) ?? "",
+ ),
+ )
+ .map((file) => {
+ // Should this happen? No
+ // Could it? I don't know
+ // But let's be safe :steamhappy:
+ if (!file.download_url) {
+ throw new Error(
+ `Could not find download URL for file "${file.name}"`,
+ );
+ }
+
+ return file.download_url;
+ })
+ );
+};
+
+export const imageUrls = async (kv: Deno.Kv): Promise<string[]> => {
+ const cached = await kv.get(URL_CACHE_KEY);
+ const urls = cached.value;
+ if (typeof urls == "string") {
+ console.trace("Found Teawie URLs in cache!");
+ return JSON.parse(urls);
+ }
+
+ console.warn("Couldn't find Teawie URLs in cache! Fetching fresh ones");
+ const fresh = await Promise.all(SUBDIRS.map(contentsOf)).then(
+ (responses) => {
+ // See the note above
+ const flatResponses = responses.flatMap((response) =>
+ Array.isArray(response) ? response : [response]
+ );
+
+ return imageUrlsIn(flatResponses);
+ },
+ );
+
+ await kv.set(URL_CACHE_KEY, JSON.stringify(fresh), {
+ expireIn: URL_CACHE_TTL_MS,
+ });
+
+ return fresh;
+};