Readme
  • Console DevTools
  • devtools.fm
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
import { Hono } from "https://deno.land/x/hono@v3.11.1/mod.ts";
import { getItunesId, isApplePodcast } from "https://esm.town/v/vladimyr/applePodcast?v=5";
import { getLastPubDate } from "https://esm.town/v/vladimyr/podcast?v=21";
import { getFeedUrl } from "https://esm.town/v/vladimyr/podnews?v=16";
import { getSpotifyId, isSpotifyPodcast } from "https://esm.town/v/vladimyr/spotifyPodcast?v=1";
import { badgen as createBadge, BadgenOptions } from "npm:badgen@3.2.3";
import friendlyTime from "npm:friendly-time@1.1.1";
import ky, { HTTPError } from "npm:ky@1.1.3";
import { withQuery } from "npm:ufo@1.3.2";
const BADGE_LABEL = "last episode";
const app = new Hono();
app.get("/", async (c) => {
const { q: feedUrl, ...query } = c.req.query();
if (!feedUrl) return c.body("");
if (isApplePodcast(feedUrl)) {
return c.redirect(withQuery(`/i/${getItunesId(feedUrl)}`, query));
}
if (isSpotifyPodcast(feedUrl)) {
return c.redirect(withQuery(`/s/${getSpotifyId(feedUrl)}`, query));
}
const badgeOptions = await getBadgeOptions(feedUrl);
if (query.format === "json") {
return c.json({
schemaVersion: 1,
label: badgeOptions.label,
message: badgeOptions.status,
color: badgeOptions.color,
});
}
c.header("Content-Type", "image/svg+xml");
return c.body(createBadge(badgeOptions));
});
app.get("/:service{(?:i(?:tunes)?|s(?:potify)?)}/:podcastId", async (c) => {
const podcastId = c.req.param("podcastId");
const { format, debug } = c.req.query();
const feedUrl = await getFeedUrl({ podcastId });
const badgeOptions = await getBadgeOptions(feedUrl);
if (format === "json") {
const badgeInfo = {
schemaVersion: 1,
label: badgeOptions.label,
message: badgeOptions.status,
color: badgeOptions.color,
};
if (debug) {
Object.assign(badgeInfo, {
_debug: { feedUrl },
});
}
return c.json(badgeInfo);
}
c.header("Content-Type", "image/svg+xml");
return c.body(createBadge(badgeOptions));
});
export default app.fetch;
async function getBadgeOptions(rssUrl: string | URL): Promise<BadgenOptions> {
const label = BADGE_LABEL;
try {
rssUrl = new URL(rssUrl);
} catch (err) {
if (err instanceof TypeError) {
return { label, status: "400", color: "red" };
}
}
let xml;
try {
xml = await ky.get(rssUrl).text();
} catch (err) {
if (err instanceof HTTPError) {
const status = String(err.response.status);
return { label, status, color: "red" };
}
}
let pubDate;
try {
pubDate = await getLastPubDate(xml);
} catch (err) {
if (err instanceof TypeError) {
return { label, status: "invalid", color: "grey" };
}
}
if (!pubDate) {
return { label, status: "unknown", color: "grey" };
}
return { label, status: friendlyTime(pubDate), color: "blue" };
}
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.