mirror of
https://github.com/ahmadk953/poixpixel-discord-bot.git
synced 2025-04-01 01:04:16 +00:00
Added Counting Feature
This commit is contained in:
parent
de599534f0
commit
95143d8c93
7 changed files with 343 additions and 3 deletions
|
@ -6,7 +6,8 @@
|
|||
"redisConnectionString": "REDIS_CONNECTION_STRING",
|
||||
"channels": {
|
||||
"welcome": "WELCOME_CHANNEL_ID",
|
||||
"logs": "LOG_CHAANNEL_ID"
|
||||
"logs": "LOG_CHANNEL_ID",
|
||||
"counting": "COUNTING_CHANNEL_ID"
|
||||
},
|
||||
"roles": {
|
||||
"joinRoles": [
|
||||
|
|
117
src/commands/fun/counting.ts
Normal file
117
src/commands/fun/counting.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
EmbedBuilder,
|
||||
PermissionsBitField,
|
||||
} from 'discord.js';
|
||||
|
||||
import { SubcommandCommand } from '../../types/CommandTypes.js';
|
||||
import { getCountingData, setCount } from '../../util/countingManager.js';
|
||||
import { loadConfig } from '../../util/configLoader.js';
|
||||
|
||||
const command: SubcommandCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('counting')
|
||||
.setDescription('Commands related to the counting channel')
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand
|
||||
.setName('status')
|
||||
.setDescription('Check the current counting status'),
|
||||
)
|
||||
.addSubcommand((subcommand) =>
|
||||
subcommand
|
||||
.setName('setcount')
|
||||
.setDescription(
|
||||
'Set the current count to a specific number (Admin only)',
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName('count')
|
||||
.setDescription('The number to set as the current count')
|
||||
.setRequired(true)
|
||||
.setMinValue(0),
|
||||
),
|
||||
),
|
||||
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
if (subcommand === 'status') {
|
||||
const countingData = await getCountingData();
|
||||
const countingChannelId = loadConfig().channels.counting;
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Counting Channel Status')
|
||||
.setColor(0x0099ff)
|
||||
.addFields(
|
||||
{
|
||||
name: 'Current Count',
|
||||
value: countingData.currentCount.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Next Number',
|
||||
value: (countingData.currentCount + 1).toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Highest Count',
|
||||
value: countingData.highestCount.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Total Correct Counts',
|
||||
value: countingData.totalCorrect.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Counting Channel',
|
||||
value: `<#${countingChannelId}>`,
|
||||
inline: true,
|
||||
},
|
||||
)
|
||||
.setFooter({ text: 'Remember: No user can count twice in a row!' })
|
||||
.setTimestamp();
|
||||
|
||||
if (countingData.lastUserId) {
|
||||
embed.addFields({
|
||||
name: 'Last Counter',
|
||||
value: `<@${countingData.lastUserId}>`,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
} else if (subcommand === 'setcount') {
|
||||
if (
|
||||
!interaction.memberPermissions?.has(
|
||||
PermissionsBitField.Flags.Administrator,
|
||||
)
|
||||
) {
|
||||
await interaction.reply({
|
||||
content: 'You need administrator permissions to use this command.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const count = interaction.options.getInteger('count');
|
||||
if (count === null) {
|
||||
await interaction.reply({
|
||||
content: 'Invalid count specified.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await setCount(count);
|
||||
await interaction.reply({
|
||||
content: `Count has been set to **${count}**. The next number should be **${count + 1}**.`,
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default command;
|
|
@ -14,7 +14,7 @@ class RedisError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
redis.on('error', (error) => {
|
||||
redis.on('error', (error: Error) => {
|
||||
console.error('Redis connection error:', error);
|
||||
throw new RedisError('Failed to connect to Redis instance: ', error);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { AuditLogEvent, Events, Message, PartialMessage } from 'discord.js';
|
||||
|
||||
import { Event } from '../types/EventTypes.js';
|
||||
import { loadConfig } from '../util/configLoader.js';
|
||||
import {
|
||||
addCountingReactions,
|
||||
processCountingMessage,
|
||||
resetCounting,
|
||||
} from '../util/countingManager.js';
|
||||
import logAction from '../util/logging/logAction.js';
|
||||
|
||||
export const messageDelete: Event<typeof Events.MessageDelete> = {
|
||||
|
@ -62,4 +68,56 @@ export const messageUpdate: Event<typeof Events.MessageUpdate> = {
|
|||
},
|
||||
};
|
||||
|
||||
export default [messageDelete, messageUpdate];
|
||||
export const messageCreate: Event<typeof Events.MessageCreate> = {
|
||||
name: Events.MessageCreate,
|
||||
execute: async (message: Message) => {
|
||||
try {
|
||||
if (message.author.bot) return;
|
||||
|
||||
const countingChannelId = loadConfig().channels.counting;
|
||||
const countingChannel =
|
||||
message.guild?.channels.cache.get(countingChannelId);
|
||||
|
||||
if (!countingChannel || message.channel.id !== countingChannelId) return;
|
||||
if (!countingChannel.isTextBased()) {
|
||||
console.error('Counting channel not found or is not a text channel');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await processCountingMessage(message);
|
||||
|
||||
if (result.isValid) {
|
||||
await addCountingReactions(message, result.milestoneType || 'normal');
|
||||
} else {
|
||||
let errorMessage: string;
|
||||
|
||||
switch (result.reason) {
|
||||
case 'not_a_number':
|
||||
errorMessage = `${message.author}, that's not a valid number! The count has been reset. The next number should be **1**.`;
|
||||
break;
|
||||
case 'too_high':
|
||||
errorMessage = `${message.author}, too high! The count was **${(result?.expectedCount ?? 0) - 1}** and the next number should have been **${result.expectedCount}**. The count has been reset.`;
|
||||
break;
|
||||
case 'too_low':
|
||||
errorMessage = `${message.author}, too low! The count was **${(result?.expectedCount ?? 0) - 1}** and the next number should have been **${result.expectedCount}**. The count has been reset.`;
|
||||
break;
|
||||
case 'same_user':
|
||||
errorMessage = `${message.author}, you can't count twice in a row! The count has been reset. The next number should be **1**.`;
|
||||
break;
|
||||
default:
|
||||
errorMessage = `${message.author}, something went wrong with the count. The count has been reset. The next number should be **1**.`;
|
||||
}
|
||||
|
||||
await resetCounting();
|
||||
|
||||
await countingChannel.send(errorMessage);
|
||||
|
||||
await message.react('❌');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling message create:', error);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default [messageCreate, messageDelete, messageUpdate];
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
CommandInteraction,
|
||||
SlashCommandBuilder,
|
||||
SlashCommandOptionsOnlyBuilder,
|
||||
SlashCommandSubcommandsOnlyBuilder,
|
||||
} from 'discord.js';
|
||||
|
||||
export interface Command {
|
||||
|
@ -13,3 +14,8 @@ export interface OptionsCommand {
|
|||
data: SlashCommandOptionsOnlyBuilder;
|
||||
execute: (interaction: CommandInteraction) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface SubcommandCommand {
|
||||
data: SlashCommandSubcommandsOnlyBuilder;
|
||||
execute: (interaction: CommandInteraction) => Promise<void>;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ export interface Config {
|
|||
channels: {
|
||||
welcome: string;
|
||||
logs: string;
|
||||
counting: string;
|
||||
};
|
||||
roles: {
|
||||
joinRoles: string[];
|
||||
|
|
157
src/util/countingManager.ts
Normal file
157
src/util/countingManager.ts
Normal file
|
@ -0,0 +1,157 @@
|
|||
import { Message } from 'discord.js';
|
||||
|
||||
import { getJson, setJson } from '../db/redis.js';
|
||||
|
||||
interface CountingData {
|
||||
currentCount: number;
|
||||
lastUserId: string | null;
|
||||
highestCount: number;
|
||||
totalCorrect: number;
|
||||
}
|
||||
|
||||
const MILESTONE_REACTIONS = {
|
||||
normal: '✅',
|
||||
multiples25: '✨',
|
||||
multiples50: '⭐',
|
||||
multiples100: '🎉',
|
||||
};
|
||||
|
||||
export async function initializeCountingData(): Promise<CountingData> {
|
||||
const exists = await getJson<CountingData>('counting');
|
||||
if (exists) return exists;
|
||||
|
||||
const initialData: CountingData = {
|
||||
currentCount: 0,
|
||||
lastUserId: null,
|
||||
highestCount: 0,
|
||||
totalCorrect: 0,
|
||||
};
|
||||
|
||||
await setJson<CountingData>('counting', initialData);
|
||||
return initialData;
|
||||
}
|
||||
|
||||
export async function getCountingData(): Promise<CountingData> {
|
||||
const data = await getJson<CountingData>('counting');
|
||||
if (!data) {
|
||||
return initializeCountingData();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateCountingData(
|
||||
data: Partial<CountingData>,
|
||||
): Promise<void> {
|
||||
const currentData = await getCountingData();
|
||||
const updatedData = { ...currentData, ...data };
|
||||
await setJson<CountingData>('counting', updatedData);
|
||||
}
|
||||
|
||||
export async function resetCounting(): Promise<void> {
|
||||
await updateCountingData({
|
||||
currentCount: 0,
|
||||
lastUserId: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
export async function processCountingMessage(message: Message): Promise<{
|
||||
isValid: boolean;
|
||||
expectedCount?: number;
|
||||
isMilestone?: boolean;
|
||||
milestoneType?: keyof typeof MILESTONE_REACTIONS;
|
||||
reason?: string;
|
||||
}> {
|
||||
try {
|
||||
const countingData = await getCountingData();
|
||||
|
||||
const content = message.content.trim();
|
||||
const count = Number(content);
|
||||
|
||||
if (isNaN(count) || !Number.isInteger(count)) {
|
||||
return {
|
||||
isValid: false,
|
||||
expectedCount: countingData.currentCount + 1,
|
||||
reason: 'not_a_number',
|
||||
};
|
||||
}
|
||||
|
||||
const expectedCount = countingData.currentCount + 1;
|
||||
if (count !== expectedCount) {
|
||||
return {
|
||||
isValid: false,
|
||||
expectedCount,
|
||||
reason: count > expectedCount ? 'too_high' : 'too_low',
|
||||
};
|
||||
}
|
||||
|
||||
if (countingData.lastUserId === message.author.id) {
|
||||
return { isValid: false, expectedCount, reason: 'same_user' };
|
||||
}
|
||||
|
||||
const newCount = countingData.currentCount + 1;
|
||||
const newHighestCount = Math.max(newCount, countingData.highestCount);
|
||||
|
||||
await updateCountingData({
|
||||
currentCount: newCount,
|
||||
lastUserId: message.author.id,
|
||||
highestCount: newHighestCount,
|
||||
totalCorrect: countingData.totalCorrect + 1,
|
||||
});
|
||||
|
||||
let isMilestone = false;
|
||||
let milestoneType: keyof typeof MILESTONE_REACTIONS = 'normal';
|
||||
|
||||
if (newCount % 100 === 0) {
|
||||
isMilestone = true;
|
||||
milestoneType = 'multiples100';
|
||||
} else if (newCount % 50 === 0) {
|
||||
isMilestone = true;
|
||||
milestoneType = 'multiples50';
|
||||
} else if (newCount % 25 === 0) {
|
||||
isMilestone = true;
|
||||
milestoneType = 'multiples25';
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
expectedCount: newCount + 1,
|
||||
isMilestone,
|
||||
milestoneType,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error processing counting message:', error);
|
||||
return { isValid: false, reason: 'error' };
|
||||
}
|
||||
}
|
||||
|
||||
export async function addCountingReactions(
|
||||
message: Message,
|
||||
milestoneType: keyof typeof MILESTONE_REACTIONS,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await message.react(MILESTONE_REACTIONS[milestoneType]);
|
||||
|
||||
if (milestoneType === 'multiples100') {
|
||||
await message.react('💯');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding counting reactions:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCountingStatus(): Promise<string> {
|
||||
const data = await getCountingData();
|
||||
return `Current count: ${data.currentCount}\nHighest count ever: ${data.highestCount}\nTotal correct counts: ${data.totalCorrect}`;
|
||||
}
|
||||
|
||||
export async function setCount(count: number): Promise<void> {
|
||||
if (!Number.isInteger(count) || count < 0) {
|
||||
throw new Error('Count must be a non-negative integer.');
|
||||
}
|
||||
|
||||
await updateCountingData({
|
||||
currentCount: count,
|
||||
lastUserId: null,
|
||||
});
|
||||
}
|
Loading…
Add table
Reference in a new issue