tangled
alpha
login
or
join now
timtinkers.online
/
discord-qotd-webhook
1
fork
atom
posts "question of the day" to a discord webhook
1
fork
atom
overview
issues
pulls
pipelines
Halloween update
timtinkers.online
6 months ago
123c976d
83f26924
1/1
deploy.yaml
success
10s
+144
-25
4 changed files
expand all
collapse all
unified
split
Code.gs
main.ts
utils
discordUtils.ts
themes.ts
+20
-8
Code.gs
reviewed
···
1
1
// Google Sheets Apps Script
2
2
function doGet() {
3
3
-
const doc = SpreadsheetApp.getActiveSpreadsheet();
4
4
-
const sheet = doc.getSheetByName("Questions");
5
5
-
const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2).getDisplayValues();
6
6
-
const result = values.map((item) => ({
7
7
-
question: item[0],
8
8
-
by: item[1],
9
9
-
}))
10
10
-
return ContentService.createTextOutput(JSON.stringify({data:result})).setMimeType(ContentService.MimeType.JSON);
3
3
+
const doc = SpreadsheetApp.getActiveSpreadsheet();
4
4
+
const sheetNames = ["Default", "Halloween"];
5
5
+
const result = {};
6
6
+
7
7
+
sheetNames.forEach((sheetName) => {
8
8
+
const sheet = doc.getSheetByName(sheetName);
9
9
+
if (sheet) {
10
10
+
const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2)
11
11
+
.getDisplayValues();
12
12
+
const sheetData = values.map((item) => ({
13
13
+
question: item[0],
14
14
+
by: item[1],
15
15
+
}));
16
16
+
const fieldName = sheetName.toLowerCase();
17
17
+
result[fieldName] = sheetData;
18
18
+
}
19
19
+
});
20
20
+
21
21
+
return ContentService.createTextOutput(JSON.stringify(result))
22
22
+
.setMimeType(ContentService.MimeType.JSON);
11
23
}
+60
-6
main.ts
reviewed
···
8
8
throw new Error("SHEET_ENDPOINT environment variable is not set");
9
9
}
10
10
11
11
+
function isDateInRange(
12
12
+
date: Date,
13
13
+
startMonth: number,
14
14
+
startDay: number,
15
15
+
endMonth: number,
16
16
+
endDay: number,
17
17
+
): boolean {
18
18
+
const month = date.getMonth() + 1; // getMonth() returns 0-11
19
19
+
const day = date.getDate();
20
20
+
21
21
+
// Handle ranges that cross year boundary (e.g., Dec 15 - Jan 15)
22
22
+
if (startMonth > endMonth) {
23
23
+
return (month > startMonth ||
24
24
+
(month === startMonth && day >= startDay)) ||
25
25
+
(month < endMonth || (month === endMonth && day <= endDay));
26
26
+
}
27
27
+
28
28
+
// Handle ranges within same year
29
29
+
if (month < startMonth || month > endMonth) return false;
30
30
+
if (month === startMonth && day < startDay) return false;
31
31
+
if (month === endMonth && day > endDay) return false;
32
32
+
33
33
+
return true;
34
34
+
}
35
35
+
36
36
+
function getCurrentSeason(): {
37
37
+
name: string;
38
38
+
kvKey: string[];
39
39
+
} {
40
40
+
const now = new Date();
41
41
+
42
42
+
// Halloween: October 1-31
43
43
+
if (isDateInRange(now, 10, 1, 10, 31)) {
44
44
+
return {
45
45
+
name: "halloween",
46
46
+
kvKey: ["halloweenIndex"],
47
47
+
};
48
48
+
}
49
49
+
50
50
+
return {
51
51
+
name: "default",
52
52
+
kvKey: ["lastIndex"],
53
53
+
};
54
54
+
}
55
55
+
11
56
// @ts-ignore Deno.cron is unstable, run with --unstable-cron flag
12
57
Deno.cron("QOTD", Deno.env.get("CRON_STRING"), async () => {
58
58
+
const season = getCurrentSeason();
59
59
+
13
60
// Fetch data
14
61
const response = await fetch(endpoint);
15
15
-
const { data } = await response.json();
62
62
+
const data = await response.json();
63
63
+
const deck = data[season.name];
16
64
17
65
// Open KV and fetch last index
18
66
// @ts-ignore Deno.openKv is unstable, run with --unstable-kv flag
···
21
69
// Deno.env.get("DENO_KV_DATABASE_ID")
22
70
// }/connect`,
23
71
);
24
24
-
const current = await kv.get<number>(["lastIndex"]);
72
72
+
const current = await kv.get<number>(season.kvKey);
25
73
const index = current.value ?? 0;
26
26
-
const nextIndex = (index + 1) % data.length;
74
74
+
const nextIndex = (index + 1) % deck.length;
27
75
28
76
// Perform atomic operation to update index
29
77
const result = await kv.atomic()
30
78
.check(current) // Ensure the value hasn't changed
31
31
-
.set(["lastIndex"], nextIndex)
79
79
+
.set(season.kvKey, nextIndex)
32
80
.commit();
33
81
34
82
if (!result.ok) {
···
37
85
}
38
86
39
87
// Post to Discord webhook
40
40
-
await sendDiscordNotification(data[index].question, data[index].by);
41
41
-
console.log(`Cron: posted question of index ${index} to Discord.`);
88
88
+
await sendDiscordNotification(
89
89
+
deck[index].question,
90
90
+
deck[index].by,
91
91
+
season.name,
92
92
+
);
93
93
+
console.log(
94
94
+
`Cron: posted ${season.name} question of index ${index} to Discord.`,
95
95
+
);
42
96
});
+24
-11
utils/discordUtils.ts
reviewed
···
1
1
+
import { defaultTheme, halloweenTheme } from "./themes.ts";
1
2
import { load } from "jsr:@std/dotenv";
2
3
3
4
await load({ export: true });
4
5
5
5
-
export async function sendDiscordNotification(question: string, by?: string) {
6
6
+
function getThemeForSeason(seasonName: string) {
7
7
+
switch (seasonName) {
8
8
+
case "halloween":
9
9
+
return halloweenTheme;
10
10
+
default:
11
11
+
return defaultTheme;
12
12
+
}
13
13
+
}
14
14
+
15
15
+
export async function sendDiscordNotification(
16
16
+
question: string,
17
17
+
by?: string,
18
18
+
season: string = "default",
19
19
+
) {
20
20
+
const theme = getThemeForSeason(season);
21
21
+
6
22
const embed1 = {
7
7
-
color: 0xFEF250, // lemon yellow
8
8
-
image: { url: Deno.env.get("IMAGE_URL") },
23
23
+
color: theme.color,
24
24
+
image: theme.image,
9
25
};
10
26
11
27
const embed2 = {
12
12
-
author: {
13
13
-
name: ".•° ✿ °•. punchy .•° ✿ °•.",
14
14
-
url: "https://tangled.sh/@timtinkers.online/discord-qotd-webhook",
15
15
-
},
16
16
-
title: `· · ─ ·𝒒𝒖𝒆𝒔𝒕𝒊𝒐𝒏 𝒐𝒇 𝒕𝒉𝒆 𝒅𝒂𝒚 · ─ · ·`,
17
17
-
color: 0xFEF250, // lemon yellow
28
28
+
author: theme.author,
29
29
+
title: theme.title,
30
30
+
color: theme.color,
18
31
description: question,
19
32
fields: [] as Array<{ name: string; value: string; inline: boolean }>,
20
20
-
thumbnail: { url: Deno.env.get("THUMBNAIL_URL") },
21
21
-
footer: { text: `𐔌՞. .՞𐦯` },
33
33
+
thumbnail: theme.thumbnail,
34
34
+
footer: theme.footer,
22
35
};
23
36
24
37
if (by) {
+40
utils/themes.ts
reviewed
···
1
1
+
import { load } from "jsr:@std/dotenv";
2
2
+
3
3
+
await load({ export: true });
4
4
+
5
5
+
export interface Theme {
6
6
+
color?: number;
7
7
+
image?: { url: string };
8
8
+
thumbnail?: { url: string };
9
9
+
title?: string;
10
10
+
author?: { name: string; url: string };
11
11
+
footer?: { text: string };
12
12
+
}
13
13
+
14
14
+
export const defaultTheme: Theme = {
15
15
+
color: 0xFEF250, // lemon yellow
16
16
+
image: { url: Deno.env.get("IMAGE_URL")! },
17
17
+
thumbnail: { url: Deno.env.get("THUMBNAIL_URL")! },
18
18
+
title: `· · ─ · 𝒒𝒖𝒆𝒔𝒕𝒊𝒐𝒏 𝒐𝒇 𝒕𝒉𝒆 𝒅𝒂𝒚 · ─ · ·`,
19
19
+
author: {
20
20
+
name: ".•° ✿ °•. punchy .•° ✿ °•.",
21
21
+
url: "https://tangled.sh/@timtinkers.online/discord-qotd-webhook",
22
22
+
},
23
23
+
footer: { text: `𐔌՞. .՞𐦯` },
24
24
+
};
25
25
+
26
26
+
export const halloweenTheme: Theme = {
27
27
+
color: 0xF25C05, // pumpkin orange
28
28
+
image: {
29
29
+
url: "https://media.discordapp.net/attachments/1279476809653686324/1420214040667361321/tenor.gif?ex=68d494e5&is=68d34365&hm=c2cea6d29d7a58afbe732b7a11775b18f156940bc127aff817bbe005c6bbde38&=",
30
30
+
},
31
31
+
thumbnail: {
32
32
+
url: "https://cdn.discordapp.com/attachments/1279476809653686324/1420213543961366618/jack_o_lantern__png_by_doloresminette_d5g6dbe-375w-2x.png?ex=68d4946f&is=68d342ef&hm=9d88e5dda9a0ae2ad5f4837ab3a519ad6904c1612f623e327dc4aaf1cc392317",
33
33
+
},
34
34
+
title: `₊˚🕯️♱‧₊˚. 𝖖𝖚𝖊𝖘𝖙𝖎𝖔𝖓 𝖔𝖋 𝖙𝖍𝖊 𝖉𝖆𝖞 .˚₊‧♱🕯️˚₊`,
35
35
+
author: {
36
36
+
name: ".˚⊹. ࣪𓉸 ࣪⊹˚. 𝔭𝔲𝔫𝔠𝔥𝔶 .˚⊹. ࣪𓉸 ࣪⊹˚.",
37
37
+
url: "https://tangled.sh/@timtinkers.online/discord-qotd-webhook",
38
38
+
},
39
39
+
footer: { text: `⛧°。 ⋆༺♱༻⋆。 °⛧` },
40
40
+
};