Skip to content

Commit 52ea534

Browse files
committed
feat: enhancing the schedule of scraping
1 parent 6cd6d11 commit 52ea534

File tree

3 files changed

+179
-24
lines changed

3 files changed

+179
-24
lines changed

src/bot.js

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,8 @@ import axios from 'axios';
1717

1818
dotenv.config();
1919

20-
// Global handler for unhandled promise rejections
2120
process.on('unhandledRejection', error => {
2221
console.error('Unhandled promise rejection (this caused the bot to crash):', error);
23-
// Optionally, you can send a message to an admin channel here
24-
// For example:
25-
// client.channels.cache.get('ADMIN_LOG_CHANNEL_ID').send(`Unhandled Rejection: ${error.message}`);
26-
// process.exit(1); // You might want to remove this line during debugging to keep the bot running
2722
});
2823

2924

@@ -75,7 +70,6 @@ class PulchowkBot {
7570

7671
this._initializeCommands();
7772
this._registerEventListeners();
78-
this._scheduleJobs();
7973
}
8074

8175
_initializeCommands() {
@@ -107,6 +101,7 @@ class PulchowkBot {
107101
this._registerSlashCommands();
108102
initializeGoogleCalendarClient();
109103
this._loadActiveVoiceSessions();
104+
this._scheduleJobs();
110105
});
111106

112107
this.client.on(Events.InteractionCreate, this._onInteractionCreate.bind(this));
@@ -188,9 +183,6 @@ class PulchowkBot {
188183
}
189184
else if (interaction.isButton()) {
190185
const customId = interaction.customId;
191-
192-
// Handle buttons that immediately show a modal first.
193-
// These buttons should NOT be deferred here, as showModal is an initial response.
194186
if (customId.startsWith('verify_start_button_')) {
195187
const verifyCmd = this.client.commands.get('verify');
196188
if (verifyCmd && typeof verifyCmd.handleButtonInteraction === 'function') {
@@ -238,7 +230,6 @@ class PulchowkBot {
238230
return;
239231
}
240232

241-
// For all other buttons, defer the reply.
242233
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] }).catch(e => {
243234
console.error("Error deferring button interaction:", e);
244235
return;
@@ -344,7 +335,6 @@ class PulchowkBot {
344335
}
345336
}
346337

347-
// Then, attempt to send farewell DM
348338
const farewellEmbed = new EmbedBuilder()
349339
.setColor('#FF0000')
350340
.setTitle(`Goodbye from ${member.guild.name}!`)
@@ -358,12 +348,10 @@ class PulchowkBot {
358348
});
359349
console.log(`Successfully attempted to send farewell DM to ${member.user.tag}.`);
360350

361-
// Add a small, non-blocking delay to allow async operations to complete
362351
await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay
363352

364353
} catch (error) {
365354
console.error('An unexpected error occurred during guild member removal process:', error);
366-
// If the bot is crashing, this log will help identify the source.
367355
}
368356
}
369357

@@ -821,7 +809,10 @@ class PulchowkBot {
821809
);
822810
}
823811

824-
_scheduleJobs() {
812+
async _scheduleJobs() {
813+
schedule.scheduleJob('0 0 * * *', () => {
814+
this._announceBirthdays();
815+
});
825816
const NOTICE_CHECK_INTERVAL_MS = parseInt(process.env.NOTICE_CHECK_INTERVAL_MS || '1800000');
826817
if (NOTICE_CHECK_INTERVAL_MS > 0) {
827818
console.log(`[Scheduler] Initializing notice checking. Interval: ${NOTICE_CHECK_INTERVAL_MS / 1000} seconds.`);
@@ -877,8 +868,9 @@ class PulchowkBot {
877868
let noticeChannel;
878869
try {
879870
noticeChannel = await this.client.channels.fetch(TARGET_NOTICE_CHANNEL_ID);
880-
if (!noticeChannel || (noticeChannel.type === ChannelType.GuildText || noticeChannel.type === ChannelType.GuildAnnouncement)) {
881-
console.error(`[Scheduler] Configured notice channel (${TARGET_NOTICE_CHANNEL_ID}) not found or is not a text/announcement channel.`);
871+
console.log(noticeChannel);
872+
if (!noticeChannel || !(noticeChannel.type === ChannelType.GuildText || noticeChannel.type === ChannelType.GuildAnnouncement)) {
873+
console.error(`[Scheduler] Configured notice channel not found or is not a text/announcement channel.`);
882874
return;
883875
}
884876
} catch (error) {
@@ -1217,14 +1209,8 @@ class PulchowkBot {
12171209
}
12181210
}
12191211

1220-
_scheduleJobs() {
1221-
schedule.scheduleJob('0 0 * * *', () => {
1222-
this._announceBirthdays();
1223-
});
1224-
}
1225-
12261212
start() {
1227-
this.client.login(this.token);
1213+
this.client.login(this.token).catch(console.error);
12281214
}
12291215
}
12301216

src/commands/slash/remindVerify.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import {
2+
SlashCommandBuilder,
3+
EmbedBuilder,
4+
PermissionsBitField,
5+
MessageFlags,
6+
ChannelType,
7+
ActionRowBuilder,
8+
ButtonBuilder,
9+
ButtonStyle
10+
} from 'discord.js';
11+
import dotenv from 'dotenv';
12+
13+
dotenv.config();
14+
15+
export const data = new SlashCommandBuilder()
16+
.setName('remindverify')
17+
.setDescription('Sends a verification reminder to unverified users in their DMs.')
18+
.setDefaultMemberPermissions(PermissionsBitField.Flags.ManageGuild)
19+
.addStringOption(option =>
20+
option.setName('message')
21+
.setDescription('Optional custom message to include in the reminder.')
22+
.setRequired(false))
23+
.addUserOption(option =>
24+
option.setName('target_user')
25+
.setDescription('Optional: Send reminder only to a specific user (for testing).')
26+
.setRequired(false));
27+
28+
/**
29+
* Executes the /remindverify slash command.
30+
* Fetches unverified users and sends them a DM reminder.
31+
* @param {import('discord.js').ChatInputCommandInteraction} interaction - The interaction object.
32+
*/
33+
export async function execute(interaction) {
34+
if (!interaction.guild) {
35+
return interaction.reply({ content: 'This command can only be used in a server.', flags: [MessageFlags.Ephemeral] });
36+
}
37+
38+
// Defer the reply as fetching members and sending DMs can take time
39+
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
40+
41+
const VERIFIED_ROLE_ID = process.env.VERIFIED_ROLE_ID;
42+
const GUILD_ID = process.env.GUILD_ID; // The main guild ID where verification applies
43+
44+
if (!VERIFIED_ROLE_ID || VERIFIED_ROLE_ID === 'YOUR_VERIFIED_ROLE_ID_HERE') {
45+
return interaction.editReply({ content: '❌ Verification is not properly configured (VERIFIED_ROLE_ID is missing). Please contact an administrator.' });
46+
}
47+
if (!GUILD_ID || GUILD_ID === 'YOUR_GUILD_ID_HERE') {
48+
return interaction.editReply({ content: '❌ The bot\'s main guild ID is not configured (GUILD_ID is missing). Please contact an administrator.' });
49+
}
50+
51+
const customMessage = interaction.options.getString('message');
52+
const targetUser = interaction.options.getUser('target_user'); // Get the target user if provided
53+
54+
let targetGuild;
55+
try {
56+
targetGuild = await interaction.client.guilds.fetch(GUILD_ID);
57+
} catch (error) {
58+
console.error(`Error fetching target guild (${GUILD_ID}):`, error);
59+
return interaction.editReply({ content: '❌ Could not fetch the main guild to find unverified users. Please check the GUILD_ID environment variable.' });
60+
}
61+
62+
let members;
63+
try {
64+
// Fetch all members to check their roles
65+
members = await targetGuild.members.fetch();
66+
} catch (error) {
67+
console.error(`Error fetching members for guild ${GUILD_ID}:`, error);
68+
return interaction.editReply({ content: '❌ Could not fetch members from the main guild. Please ensure the bot has the "Guild Members Intent" enabled and sufficient permissions.' });
69+
}
70+
71+
let unverifiedMembers = members.filter(member =>
72+
!member.user.bot && // Exclude bots
73+
!member.roles.cache.has(VERIFIED_ROLE_ID) // Include members without the verified role
74+
);
75+
76+
// --- START: Temporary filter for testing specific user ---
77+
if (targetUser) {
78+
// For testing purposes, filter to only include the target user
79+
// REMOVE this block for production use to send to all unverified members
80+
const specificUnverifiedMember = unverifiedMembers.find(member => member.user.id === targetUser.id);
81+
if (specificUnverifiedMember) {
82+
unverifiedMembers = new Map([[specificUnverifiedMember.id, specificUnverifiedMember]]);
83+
console.log(`[RemindVerify] Testing mode: Targeting only user ${targetUser.tag} (${targetUser.id}).`);
84+
} else {
85+
return interaction.editReply({ content: `⚠️ User ${targetUser.tag} is either a bot, already verified, or not found in the main guild. Cannot send reminder.` });
86+
}
87+
}
88+
// --- END: Temporary filter for testing specific user ---
89+
90+
91+
if (unverifiedMembers.size === 0) {
92+
return interaction.editReply({ content: '✅ No unverified members found in the server at this time.' });
93+
}
94+
95+
let sentCount = 0;
96+
let failedCount = 0;
97+
const failedUsers = [];
98+
99+
const reminderEmbed = new EmbedBuilder()
100+
.setColor('#FFA500') // Orange color for reminder
101+
.setTitle('🔔 Verification Reminder!')
102+
.setDescription('It looks like you haven\'t completed your verification yet. To gain full access to the server\'s channels, please complete the verification process.')
103+
.addFields(
104+
{ name: 'How to Verify:', value: 'Please use the `/verify` command in any channel (or in my DMs) and follow the instructions. If you already started, you can use `/confirmotp` with your code.' },
105+
{ name: 'Need Help?', value: 'If you encounter any issues, please reach out to an administrator in the server.' }
106+
)
107+
.setTimestamp();
108+
109+
if (customMessage) {
110+
reminderEmbed.addFields({ name: 'Important Note:', value: customMessage });
111+
}
112+
113+
// Send DMs sequentially to avoid hitting Discord rate limits too hard
114+
for (const member of unverifiedMembers.values()) {
115+
// Create the "Verify Your Account" button for EACH RECIPIENT
116+
// The customId must contain the recipient's ID for correct handling in verify.js
117+
const verifyButton = new ButtonBuilder()
118+
.setCustomId(`verify_start_button_${member.user.id}`) // CORRECTED: Use member.user.id for the recipient
119+
.setLabel('Verify Your Account')
120+
.setStyle(ButtonStyle.Primary);
121+
122+
const actionRow = new ActionRowBuilder().addComponents(verifyButton);
123+
124+
try {
125+
await member.send({ embeds: [reminderEmbed], components: [actionRow] }); // Include the button in the DM
126+
sentCount++;
127+
await new Promise(resolve => setTimeout(resolve, 500)); // Small delay to prevent rate limits
128+
} catch (error) {
129+
console.warn(`Failed to send verification reminder DM to ${member.user.tag} (${member.user.id}):`, error.message);
130+
failedCount++;
131+
failedUsers.push(member.user.tag);
132+
}
133+
}
134+
135+
let replyContent = `✅ Sent **${sentCount}** verification reminders.`;
136+
if (failedCount > 0) {
137+
replyContent += `\n❌ Failed to send **${failedCount}** reminders (users might have DMs disabled or blocked the bot). Failed users: ${failedUsers.slice(0, 5).join(', ')}${failedUsers.length > 5 ? '...' : ''}`;
138+
}
139+
140+
await interaction.editReply({ content: replyContent });
141+
142+
// Optional: Log to an admin channel if configured
143+
const ADMIN_LOG_CHANNEL_ID = process.env.ADMIN_LOG_CHANNEL_ID; // Add this to your .env
144+
if (ADMIN_LOG_CHANNEL_ID && ADMIN_LOG_CHANNEL_ID !== 'YOUR_ADMIN_LOG_CHANNEL_ID_HERE') {
145+
try {
146+
const adminLogChannel = await interaction.client.channels.fetch(ADMIN_LOG_CHANNEL_ID);
147+
if (adminLogChannel && (adminLogChannel.type === ChannelType.GuildText || adminLogChannel.type === ChannelType.GuildAnnouncement)) {
148+
const logEmbed = new EmbedBuilder()
149+
.setColor('#007BFF')
150+
.setTitle('Verification Reminders Sent')
151+
.setDescription(`**${interaction.user.tag}** sent verification reminders.`)
152+
.addFields(
153+
{ name: 'Total Unverified', value: unverifiedMembers.size.toString(), inline: true },
154+
{ name: 'Reminders Sent', value: sentCount.toString(), inline: true },
155+
{ name: 'Reminders Failed', value: failedCount.toString(), inline: true }
156+
)
157+
.setTimestamp();
158+
if (failedUsers.length > 0) {
159+
logEmbed.addFields({ name: 'Failed Users (Sample)', value: failedUsers.slice(0, 10).join(', ') });
160+
}
161+
await adminLogChannel.send({ embeds: [logEmbed] }).catch(e => console.error("Error sending admin log for reminders:", e));
162+
}
163+
} catch (logError) {
164+
console.error('Error fetching or sending to admin log channel:', logError);
165+
}
166+
}
167+
}

src/commands/slash/verify.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,15 @@ const otpEmailTemplate = `
172172
{{OTP_CODE}}
173173
</div>
174174
<p>This OTP is valid for <strong>5 minutes</strong>. Do not share this code with anyone.</p>
175+
<center>
175176
<div class="button-container">
176177
<a href="https://discord.gg/YaQxWnqJVx" class="button">🔗 Join Our Discord Server</a>
177178
</div>
179+
</center>
178180
<p class="important-note">If you did not request this OTP, please ignore this email.</p>
179181
</div>
180182
<div class="footer">
181-
<p>&copy; 2025 Your Bot Name. All rights reserved.</p>
183+
<p>&copy; 2025 FSU Bot. All rights reserved.</p>
182184
<p>This is an automated message, please do not reply.</p>
183185
</div>
184186
</div>

0 commit comments

Comments
 (0)