summaryrefslogtreecommitdiff
path: root/lib/teawie.ts
blob: b5c68c7cba6da80e55d1414d4ec74690e6d4c514 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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;
};