Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
c93769e
Update bot activity message to reflect community focus
maxsasukeuchiha271-create Apr 23, 2026
b865055
Update bot activity type and change brand colors
maxsasukeuchiha271-create Apr 23, 2026
64aae95
Increase daily reward amount from 100 to 500
maxsasukeuchiha271-create Apr 23, 2026
d604c76
Update bot.js
maxsasukeuchiha271-create Apr 23, 2026
7e56c64
Change warning color from yellow to red
maxsasukeuchiha271-create Apr 23, 2026
6a960f8
Update bot configuration settings
maxsasukeuchiha271-create Apr 23, 2026
726b2a9
Correct leveling feature value from null to true
maxsasukeuchiha271-create Apr 23, 2026
62f5849
Update bot.js
maxsasukeuchiha271-create Apr 23, 2026
caa54e2
Update bot.js
maxsasukeuchiha271-create Apr 23, 2026
49eaab2
Remove hardcoded OWNER_IDS from bot.js
maxsasukeuchiha271-create Apr 23, 2026
0d3cff7
Change owner IDs environment variable reference
maxsasukeuchiha271-create Apr 24, 2026
dec1793
Update bot.js
maxsasukeuchiha271-create Apr 24, 2026
975324d
Change color codes to lowercase hex values
maxsasukeuchiha271-create Apr 24, 2026
938954f
Update primary and secondary embed colors to #ff0000
maxsasukeuchiha271-create Apr 24, 2026
f591963
Fix syntax error in bot.js embed colors
maxsasukeuchiha271-create Apr 24, 2026
cee60c8
Update bot.js
maxsasukeuchiha271-create Apr 24, 2026
44bd821
Fix color format in bot configuration
maxsasukeuchiha271-create Apr 24, 2026
95ef83c
Fix embeds color format in bot.js
maxsasukeuchiha271-create Apr 24, 2026
3570ada
Correct embed color format in bot.js
maxsasukeuchiha271-create Apr 24, 2026
17b01ea
Update bot.js
maxsasukeuchiha271-create Apr 24, 2026
6858fdf
Change red color hex values to lowercase
maxsasukeuchiha271-create Apr 24, 2026
62b31ef
Change pending status color from orange to red
maxsasukeuchiha271-create Apr 24, 2026
ac98693
Change bot color codes from #ff0000 to #ff0001
maxsasukeuchiha271-create Apr 24, 2026
12052da
Update info color to red in bot configuration
maxsasukeuchiha271-create Apr 24, 2026
05e3539
Fix typo in bot.js embed colors section
maxsasukeuchiha271-create Apr 25, 2026
cac5e5a
Fix syntax error in bot.js embeds definition
maxsasukeuchiha271-create Apr 25, 2026
f9ddae9
Change bot status from 'online' to 'dnd'
maxsasukeuchiha271-create Apr 25, 2026
d3d71c9
Change bot status from 'dnd' to 'online'
maxsasukeuchiha271-create Apr 25, 2026
e963f0b
Change bot color codes to new green values
maxsasukeuchiha271-create May 4, 2026
5ec7228
Fix typo in info color code
maxsasukeuchiha271-create May 4, 2026
1b0e65c
Modify bot welcome message and update color scheme
maxsasukeuchiha271-create May 9, 2026
56ec059
Update managerRoles ID in bot configuration
maxsasukeuchiha271-create May 9, 2026
fddb3aa
Update logChannel and allowedRoles IDs
maxsasukeuchiha271-create May 9, 2026
17ddabc
Update logChannel ID for ticket logs
maxsasukeuchiha271-create May 10, 2026
0177895
Update starting balance and daily reward values
maxsasukeuchiha271-create May 12, 2026
c7bee4f
Add giveall command to distribute money to all members
maxsasukeuchiha271-create May 12, 2026
f9eb429
Add giveall configuration settings to bot.js
maxsasukeuchiha271-create May 12, 2026
5025036
Fix: Correct malformed hex color codes in bot config
maxsasukeuchiha271-create May 12, 2026
e4e7857
fix: correct and diversify hex color codes in bot config
maxsasukeuchiha271-create May 12, 2026
e6640fb
Fix color hex codes in bot.js
maxsasukeuchiha271-create May 12, 2026
b8fc994
Fix config syntax errors and color codes
maxsasukeuchiha271-create May 12, 2026
76816f7
Set default owner ID in giveall command
maxsasukeuchiha271-create May 12, 2026
ddbd9fb
Increase member fetch limit to 100000
maxsasukeuchiha271-create May 12, 2026
0710579
Change member fetch limit to unlimited
maxsasukeuchiha271-create May 12, 2026
bc4573c
Update giveall.js
maxsasukeuchiha271-create May 12, 2026
ad3edeb
Update giveall.js
maxsasukeuchiha271-create May 13, 2026
ce76cd1
Update giveall.js
maxsasukeuchiha271-create May 13, 2026
5ba93b0
Update bot.js
maxsasukeuchiha271-create May 13, 2026
fc59d79
Update bot.js
maxsasukeuchiha271-create May 13, 2026
fd4d702
Update bot.js
maxsasukeuchiha271-create May 13, 2026
cc6f597
Update mine.js
maxsasukeuchiha271-create May 13, 2026
a24ae13
Update mine.js
maxsasukeuchiha271-create May 13, 2026
6adef2d
Update bot.js
maxsasukeuchiha271-create May 13, 2026
31cad98
Update bot.js
maxsasukeuchiha271-create May 14, 2026
e125e38
Update mine.js
maxsasukeuchiha271-create May 14, 2026
2a63d30
Update mine.js
maxsasukeuchiha271-create May 14, 2026
b62296d
feat(session): add /startup command and session reaction handler
maxsasukeuchiha271-create Jul 30, 2026
09e897a
chore(register): wire session reaction handler into startup
maxsasukeuchiha271-create Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { logger, startupLog, shutdownLog } from './utils/logger.js';
import { checkBirthdays } from './services/birthdayService.js';
import { checkGiveaways } from './services/giveawayService.js';
import { loadCommands, registerCommands as registerSlashCommands } from './handlers/commandLoader.js';
import { registerSessionReactions } from './handlers/events/sessionReactions.js';

class TitanBot extends Client {
constructor() {
Expand Down Expand Up @@ -79,6 +80,14 @@ class TitanBot extends Client {
startupLog('Loading handlers...');
await this.loadHandlers();
startupLog('Handlers loaded');

// Register session reaction listeners (if available)
try {
registerSessionReactions(this);
startupLog('Session reaction handler registered');
} catch (err) {
logger.warn('Failed to register session reaction handler:', err && err.message ? err.message : err);
}

startupLog('Logging into Discord...');
await this.login(this.config.bot.token);
Expand Down Expand Up @@ -347,7 +356,7 @@ class TitanBot extends Client {

logger.info('✅ Graceful shutdown complete');
shutdownLog('Bot stopped successfully.');
process.exit(0);
process.exit(0);
} catch (error) {
logger.error('Error during graceful shutdown:', error);
process.exit(1);
Expand Down Expand Up @@ -381,6 +390,3 @@ try {
}

export default TitanBot;



183 changes: 183 additions & 0 deletions src/commands/Economy/giveall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
import { getEconomyData, addMoney, removeMoney, setEconomyData } from '../../utils/economy.js';
import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
import { logger } from '../../utils/logger.js';
import { InteractionHelper } from '../../utils/interactionHelper.js';
import { MessageTemplates } from '../../utils/messageTemplates.js';
import EconomyService from '../../services/economyService.js';

export default {
data: new SlashCommandBuilder()
.setName('giveall')
.setDescription('Give money to all server members (Owner/Admin only)')
.addIntegerOption(option =>
option
.setName('amount')
.setDescription('Amount to give to each member')
.setRequired(true)
.setMinValue(0)
)
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),

execute: withErrorHandling(async (interaction, config, client) => {
const deferred = await InteractionHelper.safeDefer(interaction, true);
if (!deferred) return;

const executorId = interaction.user.id;
const amount = interaction.options.getInteger("amount");
const guildId = interaction.guildId;

// Check if user is a bot owner
const ownerIds = config.commands.owners || [1022692130512191518];
const isOwner = ownerIds.includes(interaction.user.id);
const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.Administrator);

if (!isOwner && !isAdmin) {
throw createError(
"Permission denied",
ErrorTypes.VALIDATION,
"Only bot owners and server administrators can use this command.",
{ userId: executorId, guildId }
);
}

if (amount <= 0) {
throw createError(
"Invalid amount",
ErrorTypes.VALIDATION,
"Amount must be greater than zero.",
{ amount }
);
}

logger.debug(`[ECONOMY] GiveAll command initiated`, {
executorId,
amount,
guildId
});

try {
// Fetch all members
const guild = interaction.guild;
if (!guild) {
throw createError(
"Guild not found",
ErrorTypes.VALIDATION,
"Could not find the server.",
{ guildId }
);
}

await InteractionHelper.safeEditReply(interaction, {
content: `⏳ Processing giveaway to **${guild.memberCount}** members...`
});

// Fetch all members from the guild
const members = await guild.members.fetch({ limit: 0 });
const memberIds = members
.filter(m => !m.user.bot) // Exclude bots
.map(m => m.user.id);

logger.info(`[ECONOMY] Starting money distribution`, {
executorId,
memberCount: memberIds.length,
amountPerMember: amount,
guildId
});

let successCount = 0;
let failureCount = 0;
const errors = [];

// Process each member in batches to avoid rate limiting
const batchSize = 10;
for (let i = 0; i < memberIds.length; i += batchSize) {
const batch = memberIds.slice(i, i + batchSize);

await Promise.all(
batch.map(async (memberId) => {
try {
// Get member's current economy data
const memberData = await getEconomyData(client, guildId, memberId);

if (!memberData) {
failureCount++;
return;
}

// Add money to member's wallet
const newWallet = memberData.wallet + amount;
memberData.wallet = newWallet;

// Update the economy data
await setEconomyData(client, guildId, memberId, memberData);

successCount++;
} catch (err) {
failureCount++;
errors.push(`${memberId}: ${err.message}`);
logger.warn(`Failed to give money to ${memberId}`, { error: err.message });
}
})
);
}

const embed = MessageTemplates.SUCCESS.DATA_UPDATED(
"giveall",
`Successfully distributed **$${amount.toLocaleString()}** to **${successCount}** members!`
)
.addFields(
{
name: "✅ Successful",
value: `${successCount} members`,
inline: true,
},
{
name: "❌ Failed",
value: `${failureCount} members`,
inline: true,
},
{
name: "💵 Amount Per Member",
value: `$${amount.toLocaleString()}`,
inline: true,
},
{
name: "👥 Total Members Processed",
value: `${memberIds.length}`,
inline: true,
}
)
.setFooter({
text: `Executed by ${interaction.user.username}`,
iconURL: interaction.user.displayAvatarURL(),
});

if (errors.length > 0 && errors.length <= 5) {
embed.addFields({
name: "⚠️ Error Details",
value: errors.join("\n").substring(0, 1024)
});
}

await InteractionHelper.safeEditReply(interaction, { embeds: [embed] });

logger.info(`[ECONOMY] GiveAll completed`, {
executorId,
successCount,
failureCount,
totalAmount: successCount * amount,
guildId
});

} catch (err) {
logger.error(`[ECONOMY] GiveAll command failed`, {
error: err.message,
executorId,
guildId
});
throw err;
}
}, { command: 'giveall' })
};
101 changes: 101 additions & 0 deletions src/commands/Session/startup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { SlashCommandBuilder } from 'discord.js';
import { createEmbed } from '../../utils/embeds.js';

/**
* /startup required_reactions: int
* Checks staff role from guild config, enforces allowed channels from guild config,
* posts the startup embed, pings everyone and stores session state/pending sessions on the client.
*/
export default {
slashOnly: true,
data: new SlashCommandBuilder()
.setName('startup')
.setDescription('Start a roleplay session')
.addIntegerOption((opt) =>
opt
.setName('required_reactions')
.setDescription('Number of reactions needed to start the session')
.setRequired(true)
.setMinValue(1),
),

category: 'Session',

async execute(interaction, guildConfig = {}, client) {
const required = interaction.options.getInteger('required_reactions');

// Resolve staff role ID from guild config: config.roles.staff_team
const staffRoleId = String(guildConfig?.roles?.staff_team || '');
const memberRoles = interaction.member?.roles?.cache;

if (!staffRoleId || !(memberRoles && memberRoles.has && memberRoles.has(staffRoleId))) {
await interaction.reply({ content: 'Only staff team members can use this command.', ephemeral: true });
return;
}

// Enforce allowed channels from guild config: channels.session_commands_channel_id / _2
const channels = guildConfig?.channels || {};
const allowed1 = String(channels.session_commands_channel_id || '');
const allowed2 = String(channels.session_commands_channel_id_2 || '');

if (![String(interaction.channelId), String(allowed1), String(allowed2)].includes(String(interaction.channelId))) {
await interaction.reply({
content: `This command can only be used in <#${allowed1}> or <#${allowed2}>.`,
ephemeral: true,
});
return;
}

// Build embed from guild embed config (keys: embeds.startup)
const ecfg = (guildConfig?.embeds && guildConfig.embeds.startup) || {};
const title = ecfg.title || '_Greenville Roleplay Legacy_ - ___Session Startup___';
// replace placeholders {user} and {required} if present
let description = ecfg.description || '';
description = description.replace(/\{user\}/g, interaction.user?.toString() || '')
.replace(/\{required\}/g, String(required));
const embed = createEmbed({
title,
description,
color: 'success',
});
if (ecfg.image_url) embed.setImage(ecfg.image_url);
embed.setFooter({ text: guildConfig?.bot?.footer_text || '', iconURL: guildConfig?.bot?.footer_icon || '' });

// Reply to invoker, then post the announcement (ping everyone)
await interaction.reply({ content: 'Startup initiated.', ephemeral: true });

const channel = interaction.channel;
const announcement = await channel.send({
content: '@everyone',
embeds: [embed],
allowedMentions: { parse: ['everyone'] },
});

// Add reaction (custom emoji string works: <:name:id>)
const targetEmoji = '<:pinkcheckmark:1502780778449342494>'; // same emoji used in your python
try {
await announcement.react(targetEmoji);
} catch (err) {
// Reaction might fail if emoji not available; ignore silently
// If you have a logger, you can log this error
}

// Ensure client-side stores exist
if (!client.sessionStates) client.sessionStates = new Map();
if (!client.pendingSessions) client.pendingSessions = new Map();

// Save session state and pending session entry
client.sessionStates.set(String(channel.id), {
messageId: announcement.id,
time: Math.floor(Date.now() / 1000),
completed: true,
reactors: new Set(),
});

client.pendingSessions.set(String(announcement.id), {
type: 'startup',
required,
user: interaction.user?.toString() || interaction.user?.tag || 'unknown',
});
},
};
Loading