-
Notifications
You must be signed in to change notification settings - Fork 1
feat: daily aoc post #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
7fe31aa
54e8f8b
cbdad05
f16dabf
297816f
0e59157
b741052
1bf9785
79c128e
fa1dabc
2224bc1
a69c0bd
949c3c1
f22a94d
014be19
e3fe8f1
34b9f0a
3ff2ee1
a06c6a8
ac64ff8
80a47fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,26 @@ | ||
| # Local Environment Variables (Secrets) | ||
| # Copy this file to .env and fill in your actual values | ||
| # .env is gitignored and should NEVER be committed | ||
| # Discord Bot Credentials (REQUIRED - Secret values, do not commit!) | ||
| DISCORD_TOKEN=your_discord_bot_token_here | ||
| CLIENT_ID=your_discord_client_id_here | ||
|
|
||
| # Discord Bot Token & Application ID (REQUIRED) | ||
| # Get this from: https://discord.com/developers/applications | ||
| DISCORD_TOKEN=your-bot-token-here | ||
| CLIENT_ID=your-bot-application-id | ||
| # Server Configuration (REQUIRED) | ||
| SERVER_ID=your_server_id_here | ||
|
|
||
| # Override any public config values for local testing | ||
| # Channel IDs (REQUIRED) | ||
| GUIDES_CHANNEL_ID=your_guides_channel_id_here | ||
| ADVENT_OF_CODE_CHANNEL_ID=your_advent_of_code_forum_channel_id_here | ||
| REPEL_LOG_CHANNEL_ID=your_repel_log_channel_id_here | ||
|
|
||
| # Discord Server ID (your dev server) | ||
| SERVER_ID=your-server-id | ||
| # Role IDs (REQUIRED) | ||
| MODERATORS_ROLE_IDS=role_id_1,role_id_2,role_id_3 | ||
| REPEL_ROLE_ID=your_repel_role_id_here | ||
|
|
||
| # Channel IDs (from your dev server) | ||
| GUIDES_CHANNEL_ID=your-guide-channel-id | ||
| REPEL_LOG_CHANNEL_ID=your-repel-log-channel-id | ||
| # Optional Role IDs | ||
| # ROLE_A_ID=optional_role_a_id | ||
| # ROLE_B_ID=optional_role_b_id | ||
| # ROLE_C_ID=optional_role_c_id | ||
|
|
||
| # Role IDs (from your dev server) | ||
| REPEL_ROLE_ID=your-repel-role-id | ||
| MODERATORS_ROLE_IDS=your-moderator-role-id | ||
|
|
||
| # Other | ||
| GUIDES_TRACKER_PATH=guides-tracker.json | ||
| # Data Persistence (OPTIONAL) | ||
| # Local development defaults to current directory | ||
| # Docker deployments should use /app/data for persistence | ||
| GUIDES_TRACKER_PATH=guides-tracker.json | ||
| ADVENT_OF_CODE_TRACKER_PATH=advent-of-code-tracker.json |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| import { ChannelType, type Client, type ForumChannel } from 'discord.js'; | ||
| import * as cron from 'node-cron'; | ||
| import { promises as fs } from 'node:fs'; | ||
| import { config } from '../env.js'; | ||
|
|
||
| const TRACKER_FILE = config.adventOfCodeTrackerPath; | ||
|
|
||
| type TrackerData = { | ||
| [year: string]: number[]; | ||
| }; | ||
|
|
||
| /** | ||
| * Load the tracker file to see which days have already been posted | ||
| */ | ||
| async function loadTracker(): Promise<TrackerData> { | ||
| try { | ||
| const data = await fs.readFile(TRACKER_FILE, 'utf-8'); | ||
| return JSON.parse(data); | ||
| } catch (_error) { | ||
|
wiktoriavh marked this conversation as resolved.
Outdated
|
||
| // If file doesn't exist or can't be read, return empty object | ||
| return {}; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Save the tracker file with updated data | ||
| */ | ||
|
wiktoriavh marked this conversation as resolved.
Outdated
|
||
| async function saveTracker(data: TrackerData): Promise<void> { | ||
| await fs.writeFile(TRACKER_FILE, JSON.stringify(data, null, 2), 'utf-8'); | ||
| } | ||
|
|
||
| /** | ||
| * Check if a specific day has already been posted for a given year | ||
| */ | ||
|
wiktoriavh marked this conversation as resolved.
Outdated
|
||
| async function isDayPosted(year: number, day: number): Promise<boolean> { | ||
| const tracker = await loadTracker(); | ||
| const yearData = tracker[year.toString()]; | ||
| return yearData ? yearData.includes(day) : false; | ||
| } | ||
|
|
||
| /** | ||
| * Mark a day as posted for a given year | ||
| */ | ||
|
wiktoriavh marked this conversation as resolved.
Outdated
|
||
| async function markDayAsPosted(year: number, day: number): Promise<void> { | ||
| const tracker = await loadTracker(); | ||
| const yearKey = year.toString(); | ||
|
|
||
| if (!tracker[yearKey]) { | ||
| tracker[yearKey] = []; | ||
| } | ||
|
|
||
| if (!tracker[yearKey].includes(day)) { | ||
| tracker[yearKey].push(day); | ||
| tracker[yearKey].sort((a, b) => a - b); | ||
| await saveTracker(tracker); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Create a forum post for a specific Advent of Code day | ||
| */ | ||
|
wiktoriavh marked this conversation as resolved.
Outdated
|
||
| async function createAdventPost( | ||
| client: Client, | ||
| channelId: string, | ||
| year: number, | ||
| day: number | ||
| ): Promise<boolean> { | ||
| try { | ||
| const channel = await client.channels.fetch(channelId); | ||
|
|
||
| if (!channel) { | ||
| console.error(`❌ Advent of Code channel not found: ${channelId}`); | ||
| return false; | ||
| } | ||
|
|
||
| if (channel.type !== ChannelType.GuildForum) { | ||
| console.error(`❌ Advent of Code channel is not a forum channel. Type: ${channel.type}`); | ||
| return false; | ||
| } | ||
|
|
||
| const forumChannel = channel as ForumChannel; | ||
|
wiktoriavh marked this conversation as resolved.
Outdated
|
||
| const title = `Day ${day}, ${year}`; | ||
| const content = `https://adventofcode.com/${year}/day/${day}`; | ||
|
|
||
| await forumChannel.threads.create({ | ||
| name: title, | ||
| message: { | ||
| content: content, | ||
| }, | ||
| }); | ||
|
|
||
| console.log(`✅ Created Advent of Code post: ${title}`); | ||
| return true; | ||
| } catch (error) { | ||
| console.error(`❌ Failed to create Advent of Code post for day ${day}:`, error); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check if today is during Advent of Code (December 1-25) and create post if needed | ||
| */ | ||
| async function checkAndCreateTodaysPost(client: Client, channelId: string): Promise<void> { | ||
| const now = new Date(); | ||
| const month = now.getUTCMonth(); // 0-indexed, so December is 11 | ||
| const day = now.getUTCDate(); | ||
| const year = now.getUTCFullYear(); | ||
|
|
||
| // Only run during December (month 11) | ||
| if (month !== 11) { | ||
| return; | ||
| } | ||
|
|
||
| // Only run for days 1-25 | ||
| if (day < 1 || day > 25) { | ||
| return; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this specific date range?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because advent of code runs till Christmas.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But isn't it 12 days only?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was always 25 days, this time it is only 12 puzzles. i don't know yet how it will be. i will update the job once i have more information, but for the first day this will run correctly.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's based on the advent calendar (24 or 25 days long), so it being only 12 is very weird. it could be that it only runs from day 1 to day 12, or it will run to day 24, where every other day a puzzle is posted? unclear to me yet. |
||
|
|
||
| // Check if we've already posted for this day this year | ||
| const alreadyPosted = await isDayPosted(year, day); | ||
| if (alreadyPosted) { | ||
| console.log(`ℹ️ Advent of Code post for ${year} day ${day} already exists`); | ||
| return; | ||
| } | ||
|
|
||
| // Create the post | ||
| const success = await createAdventPost(client, channelId, year, day); | ||
|
|
||
| // Mark as posted if successful | ||
| if (success) { | ||
| await markDayAsPosted(year, day); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Initialize the Advent of Code scheduler | ||
| * Runs every day at midnight UTC and checks if we should create a post | ||
| */ | ||
| export function initializeAdventScheduler(client: Client, channelId: string): void { | ||
| console.log('🎄 Initializing Advent of Code scheduler...'); | ||
|
|
||
| // Run immediately on startup to check if we need to post today | ||
| checkAndCreateTodaysPost(client, channelId).catch((error) => { | ||
| console.error('❌ Error checking for Advent of Code post on startup:', error); | ||
| }); | ||
|
|
||
| // Schedule to run every day at midnight UTC | ||
| // Cron pattern: '0 5 * * *' = At 05:00 UTC every day (midnight UTC-5) | ||
|
wiktoriavh marked this conversation as resolved.
Outdated
|
||
| cron.schedule('0 5 * * *', () => { | ||
| console.log('⏰ Running scheduled Advent of Code check...'); | ||
| checkAndCreateTodaysPost(client, channelId).catch((error) => { | ||
| console.error('❌ Error in scheduled Advent of Code check:', error); | ||
| }); | ||
| }); | ||
|
Comment on lines
+125
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cron.schedule(
'0 0 * * *',
() => {
console.log('⏰ Running scheduled Advent of Code check...');
checkAndCreateTodaysPost(client, channelId).catch((error) => {
console.error('❌ Error in scheduled Advent of Code check:', error);
});
},
{
timezone: 'America/New_York',
}
);This way the expression is more understandable, it uses
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to specifically have it run on UTC. It's the standard for the time. |
||
|
|
||
| console.log('✅ Advent of Code scheduler initialized (runs daily at midnight UTC)'); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.