Skip to content

Commit cbdad05

Browse files
committed
feat: implement Advent of Code scheduler to automate daily post creation in Discord forum
1 parent 54e8f8b commit cbdad05

1 file changed

Lines changed: 156 additions & 0 deletions

File tree

src/util/advent-scheduler.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { ChannelType, type Client, type ForumChannel } from 'discord.js';
2+
import * as cron from 'node-cron';
3+
import { promises as fs } from 'node:fs';
4+
5+
const TRACKER_FILE = 'advent-of-code-tracker.json';
6+
7+
type TrackerData = {
8+
[year: string]: number[];
9+
};
10+
11+
/**
12+
* Load the tracker file to see which days have already been posted
13+
*/
14+
async function loadTracker(): Promise<TrackerData> {
15+
try {
16+
const data = await fs.readFile(TRACKER_FILE, 'utf-8');
17+
return JSON.parse(data);
18+
} catch (_error) {
19+
// If file doesn't exist or can't be read, return empty object
20+
return {};
21+
}
22+
}
23+
24+
/**
25+
* Save the tracker file with updated data
26+
*/
27+
async function saveTracker(data: TrackerData): Promise<void> {
28+
await fs.writeFile(TRACKER_FILE, JSON.stringify(data, null, 2), 'utf-8');
29+
}
30+
31+
/**
32+
* Check if a specific day has already been posted for a given year
33+
*/
34+
async function isDayPosted(year: number, day: number): Promise<boolean> {
35+
const tracker = await loadTracker();
36+
const yearData = tracker[year.toString()];
37+
return yearData ? yearData.includes(day) : false;
38+
}
39+
40+
/**
41+
* Mark a day as posted for a given year
42+
*/
43+
async function markDayAsPosted(year: number, day: number): Promise<void> {
44+
const tracker = await loadTracker();
45+
const yearKey = year.toString();
46+
47+
if (!tracker[yearKey]) {
48+
tracker[yearKey] = [];
49+
}
50+
51+
if (!tracker[yearKey].includes(day)) {
52+
tracker[yearKey].push(day);
53+
tracker[yearKey].sort((a, b) => a - b);
54+
await saveTracker(tracker);
55+
}
56+
}
57+
58+
/**
59+
* Create a forum post for a specific Advent of Code day
60+
*/
61+
async function createAdventPost(
62+
client: Client,
63+
channelId: string,
64+
year: number,
65+
day: number
66+
): Promise<boolean> {
67+
try {
68+
const channel = await client.channels.fetch(channelId);
69+
70+
if (!channel) {
71+
console.error(`❌ Advent of Code channel not found: ${channelId}`);
72+
return false;
73+
}
74+
75+
if (channel.type !== ChannelType.GuildForum) {
76+
console.error(`❌ Advent of Code channel is not a forum channel. Type: ${channel.type}`);
77+
return false;
78+
}
79+
80+
const forumChannel = channel as ForumChannel;
81+
const title = `Day ${day}, ${year}`;
82+
const content = `https://adventofcode.com/${year}/day/${day}`;
83+
84+
await forumChannel.threads.create({
85+
name: title,
86+
message: {
87+
content: content,
88+
},
89+
});
90+
91+
console.log(`✅ Created Advent of Code post: ${title}`);
92+
return true;
93+
} catch (error) {
94+
console.error(`❌ Failed to create Advent of Code post for day ${day}:`, error);
95+
return false;
96+
}
97+
}
98+
99+
/**
100+
* Check if today is during Advent of Code (December 1-25) and create post if needed
101+
*/
102+
async function checkAndCreateTodaysPost(client: Client, channelId: string): Promise<void> {
103+
const now = new Date();
104+
const month = now.getUTCMonth(); // 0-indexed, so December is 11
105+
const day = now.getUTCDate();
106+
const year = now.getUTCFullYear();
107+
108+
// Only run during December (month 11)
109+
if (month !== 10) {
110+
return;
111+
}
112+
113+
// Only run for days 1-25
114+
if (day < 1 || day > 25) {
115+
return;
116+
}
117+
118+
// Check if we've already posted for this day this year
119+
const alreadyPosted = await isDayPosted(year, day);
120+
if (alreadyPosted) {
121+
console.log(`ℹ️ Advent of Code post for ${year} day ${day} already exists`);
122+
return;
123+
}
124+
125+
// Create the post
126+
const success = await createAdventPost(client, channelId, year, day);
127+
128+
// Mark as posted if successful
129+
if (success) {
130+
await markDayAsPosted(year, day);
131+
}
132+
}
133+
134+
/**
135+
* Initialize the Advent of Code scheduler
136+
* Runs every day at midnight UTC and checks if we should create a post
137+
*/
138+
export function initializeAdventScheduler(client: Client, channelId: string): void {
139+
console.log('🎄 Initializing Advent of Code scheduler...');
140+
141+
// Run immediately on startup to check if we need to post today
142+
checkAndCreateTodaysPost(client, channelId).catch((error) => {
143+
console.error('❌ Error checking for Advent of Code post on startup:', error);
144+
});
145+
146+
// Schedule to run every day at midnight UTC
147+
// Cron pattern: '0 5 * * *' = At 05:00 UTC every day (midnight UTC-5)
148+
cron.schedule('0 5 * * *', () => {
149+
console.log('⏰ Running scheduled Advent of Code check...');
150+
checkAndCreateTodaysPost(client, channelId).catch((error) => {
151+
console.error('❌ Error in scheduled Advent of Code check:', error);
152+
});
153+
});
154+
155+
console.log('✅ Advent of Code scheduler initialized (runs daily at midnight UTC)');
156+
}

0 commit comments

Comments
 (0)