posts "question of the day" to a discord webhook

Halloween update

+144 -25
+20 -8
Code.gs
··· 1 1 // Google Sheets Apps Script 2 2 function doGet() { 3 - const doc = SpreadsheetApp.getActiveSpreadsheet(); 4 - const sheet = doc.getSheetByName("Questions"); 5 - const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2).getDisplayValues(); 6 - const result = values.map((item) => ({ 7 - question: item[0], 8 - by: item[1], 9 - })) 10 - return ContentService.createTextOutput(JSON.stringify({data:result})).setMimeType(ContentService.MimeType.JSON); 3 + const doc = SpreadsheetApp.getActiveSpreadsheet(); 4 + const sheetNames = ["Default", "Halloween"]; 5 + const result = {}; 6 + 7 + sheetNames.forEach((sheetName) => { 8 + const sheet = doc.getSheetByName(sheetName); 9 + if (sheet) { 10 + const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 2) 11 + .getDisplayValues(); 12 + const sheetData = values.map((item) => ({ 13 + question: item[0], 14 + by: item[1], 15 + })); 16 + const fieldName = sheetName.toLowerCase(); 17 + result[fieldName] = sheetData; 18 + } 19 + }); 20 + 21 + return ContentService.createTextOutput(JSON.stringify(result)) 22 + .setMimeType(ContentService.MimeType.JSON); 11 23 }
+60 -6
main.ts
··· 8 8 throw new Error("SHEET_ENDPOINT environment variable is not set"); 9 9 } 10 10 11 + function isDateInRange( 12 + date: Date, 13 + startMonth: number, 14 + startDay: number, 15 + endMonth: number, 16 + endDay: number, 17 + ): boolean { 18 + const month = date.getMonth() + 1; // getMonth() returns 0-11 19 + const day = date.getDate(); 20 + 21 + // Handle ranges that cross year boundary (e.g., Dec 15 - Jan 15) 22 + if (startMonth > endMonth) { 23 + return (month > startMonth || 24 + (month === startMonth && day >= startDay)) || 25 + (month < endMonth || (month === endMonth && day <= endDay)); 26 + } 27 + 28 + // Handle ranges within same year 29 + if (month < startMonth || month > endMonth) return false; 30 + if (month === startMonth && day < startDay) return false; 31 + if (month === endMonth && day > endDay) return false; 32 + 33 + return true; 34 + } 35 + 36 + function getCurrentSeason(): { 37 + name: string; 38 + kvKey: string[]; 39 + } { 40 + const now = new Date(); 41 + 42 + // Halloween: October 1-31 43 + if (isDateInRange(now, 10, 1, 10, 31)) { 44 + return { 45 + name: "halloween", 46 + kvKey: ["halloweenIndex"], 47 + }; 48 + } 49 + 50 + return { 51 + name: "default", 52 + kvKey: ["lastIndex"], 53 + }; 54 + } 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 + const season = getCurrentSeason(); 59 + 13 60 // Fetch data 14 61 const response = await fetch(endpoint); 15 - const { data } = await response.json(); 62 + const data = await response.json(); 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 - const current = await kv.get<number>(["lastIndex"]); 72 + const current = await kv.get<number>(season.kvKey); 25 73 const index = current.value ?? 0; 26 - const nextIndex = (index + 1) % data.length; 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 - .set(["lastIndex"], nextIndex) 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 - await sendDiscordNotification(data[index].question, data[index].by); 41 - console.log(`Cron: posted question of index ${index} to Discord.`); 88 + await sendDiscordNotification( 89 + deck[index].question, 90 + deck[index].by, 91 + season.name, 92 + ); 93 + console.log( 94 + `Cron: posted ${season.name} question of index ${index} to Discord.`, 95 + ); 42 96 });
+24 -11
utils/discordUtils.ts
··· 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 - export async function sendDiscordNotification(question: string, by?: string) { 6 + function getThemeForSeason(seasonName: string) { 7 + switch (seasonName) { 8 + case "halloween": 9 + return halloweenTheme; 10 + default: 11 + return defaultTheme; 12 + } 13 + } 14 + 15 + export async function sendDiscordNotification( 16 + question: string, 17 + by?: string, 18 + season: string = "default", 19 + ) { 20 + const theme = getThemeForSeason(season); 21 + 6 22 const embed1 = { 7 - color: 0xFEF250, // lemon yellow 8 - image: { url: Deno.env.get("IMAGE_URL") }, 23 + color: theme.color, 24 + image: theme.image, 9 25 }; 10 26 11 27 const embed2 = { 12 - author: { 13 - name: ".•° ✿ °•. punchy .•° ✿ °•.", 14 - url: "https://tangled.sh/@timtinkers.online/discord-qotd-webhook", 15 - }, 16 - title: `· · ─ ·𝒒𝒖𝒆𝒔𝒕𝒊𝒐𝒏 𝒐𝒇 𝒕𝒉𝒆 𝒅𝒂𝒚 · ─ · ·`, 17 - color: 0xFEF250, // lemon yellow 28 + author: theme.author, 29 + title: theme.title, 30 + color: theme.color, 18 31 description: question, 19 32 fields: [] as Array<{ name: string; value: string; inline: boolean }>, 20 - thumbnail: { url: Deno.env.get("THUMBNAIL_URL") }, 21 - footer: { text: `𐔌՞. .՞𐦯` }, 33 + thumbnail: theme.thumbnail, 34 + footer: theme.footer, 22 35 }; 23 36 24 37 if (by) {
+40
utils/themes.ts
··· 1 + import { load } from "jsr:@std/dotenv"; 2 + 3 + await load({ export: true }); 4 + 5 + export interface Theme { 6 + color?: number; 7 + image?: { url: string }; 8 + thumbnail?: { url: string }; 9 + title?: string; 10 + author?: { name: string; url: string }; 11 + footer?: { text: string }; 12 + } 13 + 14 + export const defaultTheme: Theme = { 15 + color: 0xFEF250, // lemon yellow 16 + image: { url: Deno.env.get("IMAGE_URL")! }, 17 + thumbnail: { url: Deno.env.get("THUMBNAIL_URL")! }, 18 + title: `· · ─ · 𝒒𝒖𝒆𝒔𝒕𝒊𝒐𝒏 𝒐𝒇 𝒕𝒉𝒆 𝒅𝒂𝒚 · ─ · ·`, 19 + author: { 20 + name: ".•° ✿ °•. punchy .•° ✿ °•.", 21 + url: "https://tangled.sh/@timtinkers.online/discord-qotd-webhook", 22 + }, 23 + footer: { text: `𐔌՞. .՞𐦯` }, 24 + }; 25 + 26 + export const halloweenTheme: Theme = { 27 + color: 0xF25C05, // pumpkin orange 28 + image: { 29 + url: "https://media.discordapp.net/attachments/1279476809653686324/1420214040667361321/tenor.gif?ex=68d494e5&is=68d34365&hm=c2cea6d29d7a58afbe732b7a11775b18f156940bc127aff817bbe005c6bbde38&=", 30 + }, 31 + thumbnail: { 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 + }, 34 + title: `₊˚🕯️♱‧₊˚. 𝖖𝖚𝖊𝖘𝖙𝖎𝖔𝖓 𝖔𝖋 𝖙𝖍𝖊 𝖉𝖆𝖞 .˚₊‧♱🕯️˚₊`, 35 + author: { 36 + name: ".˚⊹. ࣪𓉸 ࣪⊹˚. 𝔭𝔲𝔫𝔠𝔥𝔶 .˚⊹. ࣪𓉸 ࣪⊹˚.", 37 + url: "https://tangled.sh/@timtinkers.online/discord-qotd-webhook", 38 + }, 39 + footer: { text: `⛧°。 ⋆༺♱༻⋆。 °⛧` }, 40 + };