-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.ts
81 lines (74 loc) · 2.23 KB
/
main.ts
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
import { Collection, isActor, lookupObject } from "@fedify/fedify";
import { makeBadge } from "badge-maker";
import { Hono } from "hono";
type Counter = "followers" | "following" | "posts";
const kv = await Deno.openKv();
async function count(handle: string, counter: Counter): Promise<number | null> {
const cached = await kv.get<number | null>(["count", handle, counter]);
if (cached != null && cached.value != null) return cached.value;
let actor;
try {
actor = await lookupObject(handle);
} catch (_) {
return null;
}
if (!isActor(actor)) return null;
let collection: Collection | null = null;
try {
collection = counter == "followers"
? await actor.getFollowers()
: counter == "following"
? await actor.getFollowing()
: await actor.getOutbox();
} catch (_) {
return null;
}
const num = collection?.totalItems ?? null;
await kv.set(["count", handle, counter], num, { expireIn: 60 * 60 * 1000 });
return num;
}
const app = new Hono();
app.get("/", (c) => c.redirect("https://github.com/dahlia/fedi-badge"));
app.get(
"/:handle{@[^@]+@[^@]+}/:counter{followers[.]svg|following[.]svg|posts[.]svg}",
async (c) => {
const { handle, counter } = c.req.param();
const query = c.req.query();
const num = await count(
handle,
counter === "followers.svg"
? "followers"
: counter === "following.svg"
? "following"
: "posts",
);
const style:
| "plastic"
| "flat"
| "flat-square"
| "for-the-badge"
| "social" =
["plastic", "flat", "flat-square", "for-the-badge", "social"].includes(
query.style,
)
// deno-lint-ignore no-explicit-any
? query.style as unknown as any
: "social";
const svg = makeBadge({
label: query.label == null ? `Follow ${handle}` : query.label,
message: num?.toString() ?? "N/A",
style,
});
return c.body(svg, {
headers: {
"Cache-Control": "public, max-age=3600",
"Content-Type": "image/svg+xml",
"Last-Modified": new Date().toUTCString(),
"Expires": new Date(Date.now() + 3600 * 1000).toUTCString(),
},
});
},
);
if (import.meta.main) {
Deno.serve(app.fetch);
}