mirror of
https://github.com/logos-storage/discord-bot.git
synced 2026-01-02 13:13:08 +00:00
421 lines
14 KiB
JavaScript
421 lines
14 KiB
JavaScript
require('dotenv').config();
|
|
|
|
const { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder } = require('discord.js');
|
|
const { createClient } = require('@supabase/supabase-js');
|
|
|
|
// Initialize Discord client
|
|
const client = new Client({
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildMembers,
|
|
],
|
|
});
|
|
|
|
// Initialize Supabase client
|
|
const supabase = createClient(
|
|
process.env.SUPABASE_URL,
|
|
process.env.SUPABASE_SERVICE_ROLE_KEY
|
|
);
|
|
|
|
// Command registration
|
|
const commands = [
|
|
{
|
|
name: 'node',
|
|
description: 'Verify your node and get roles',
|
|
options: [{
|
|
name: 'nodeid',
|
|
description: 'Your Node ID',
|
|
type: 3, // STRING type
|
|
required: true
|
|
}],
|
|
default_member_permissions: null, // Allow everyone to use the command
|
|
dm_permission: false
|
|
},
|
|
{
|
|
name: 'checkroles',
|
|
description: 'Check if your node is still active',
|
|
default_member_permissions: null, // Allow everyone to use the command
|
|
dm_permission: false
|
|
}
|
|
];
|
|
|
|
// Register commands when bot starts
|
|
client.once('ready', async () => {
|
|
try {
|
|
console.log('Started refreshing application (/) commands.');
|
|
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN);
|
|
|
|
// Register commands for the first guild the bot is in
|
|
const guild = client.guilds.cache.first();
|
|
if (!guild) {
|
|
console.error('No guild found!');
|
|
return;
|
|
}
|
|
|
|
console.log(`Registering commands for guild: ${guild.name} (${guild.id})`);
|
|
|
|
try {
|
|
// Delete existing commands first
|
|
console.log('Deleting existing commands...');
|
|
|
|
// Delete guild commands
|
|
await rest.put(Routes.applicationGuildCommands(client.user.id, guild.id), { body: [] });
|
|
console.log('Successfully deleted existing commands.');
|
|
|
|
// Register new commands
|
|
console.log('Registering new commands...');
|
|
const data = await rest.put(
|
|
Routes.applicationGuildCommands(client.user.id, guild.id),
|
|
{ body: commands }
|
|
);
|
|
|
|
console.log(`Successfully registered ${data.length} commands!`);
|
|
|
|
// Start the role check interval
|
|
setInterval(() => checkInactiveNodes(guild), 24 * 60 * 60 * 1000);
|
|
} catch (error) {
|
|
console.error('Error managing commands:', error);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in ready event:', error);
|
|
}
|
|
});
|
|
|
|
// Function to check and remove roles from inactive nodes
|
|
async function checkInactiveNodes(guild, specificMember = null) {
|
|
try {
|
|
console.log('Checking for inactive nodes...');
|
|
// Get both roles
|
|
const activeRole = guild.roles.cache.find(r => r.name === 'Active Participant');
|
|
const inactiveRole = guild.roles.cache.find(r => r.name === 'Inactive Participant');
|
|
|
|
if (!activeRole) {
|
|
console.error('Active Participant role not found');
|
|
return false;
|
|
}
|
|
if (!inactiveRole) {
|
|
console.error('Inactive Participant role not found');
|
|
return false;
|
|
}
|
|
|
|
// If checking a specific member
|
|
if (specificMember) {
|
|
console.log(`Checking status for member: ${specificMember.user.tag} (${specificMember.user.id})`);
|
|
|
|
// Get the most recent node record for this specific Discord user
|
|
const { data: nodeRecords, error } = await supabase
|
|
.from('node_records')
|
|
.select('*')
|
|
.eq('discord_user_id', specificMember.user.id)
|
|
.order('timestamp', { ascending: false })
|
|
.limit(1);
|
|
|
|
if (error) {
|
|
console.error('Error checking node records:', error);
|
|
return false;
|
|
}
|
|
|
|
// Get one week ago timestamp
|
|
const oneWeekAgo = new Date();
|
|
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
|
|
|
// Check if node is inactive
|
|
const isInactive = !nodeRecords.length || new Date(nodeRecords[0].timestamp) < oneWeekAgo;
|
|
console.log(`Node status - Records exist: ${nodeRecords.length > 0}, Last timestamp: ${nodeRecords.length ? new Date(nodeRecords[0].timestamp).toISOString() : 'none'}, Is inactive: ${isInactive}`);
|
|
|
|
if (isInactive) {
|
|
// Remove Active role and add Inactive role
|
|
if (specificMember.roles.cache.has(activeRole.id)) {
|
|
await specificMember.roles.remove(activeRole);
|
|
await specificMember.roles.add(inactiveRole);
|
|
console.log(`Changed ${specificMember.user.tag} to inactive status`);
|
|
}
|
|
return false;
|
|
} else {
|
|
// Add Active role and remove Inactive role if needed
|
|
if (specificMember.roles.cache.has(inactiveRole.id)) {
|
|
await specificMember.roles.remove(inactiveRole);
|
|
await specificMember.roles.add(activeRole);
|
|
console.log(`Changed ${specificMember.user.tag} to active status`);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If checking all members
|
|
const membersWithRole = guild.members.cache.filter(member =>
|
|
member.roles.cache.has(activeRole.id)
|
|
);
|
|
|
|
const oneWeekAgo = new Date();
|
|
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
|
|
|
for (const [_, member] of membersWithRole) {
|
|
const { data: nodeRecords, error } = await supabase
|
|
.from('node_records')
|
|
.select('*')
|
|
.eq('discord_user_id', member.user.id)
|
|
.order('timestamp', { ascending: false })
|
|
.limit(1);
|
|
|
|
if (error) {
|
|
console.error('Error checking node records:', error);
|
|
continue;
|
|
}
|
|
|
|
if (!nodeRecords.length || new Date(nodeRecords[0].timestamp) < oneWeekAgo) {
|
|
try {
|
|
await member.roles.remove(activeRole);
|
|
await member.roles.add(inactiveRole);
|
|
console.log(`Changed ${member.user.tag} to inactive status due to inactivity`);
|
|
} catch (error) {
|
|
console.error(`Error updating roles for ${member.user.tag}:`, error);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in checkInactiveNodes:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Handle slash commands
|
|
client.on('interactionCreate', async interaction => {
|
|
if (!interaction.isChatInputCommand()) return;
|
|
|
|
if (interaction.commandName === 'node') {
|
|
try {
|
|
await interaction.deferReply({ ephemeral: true });
|
|
|
|
// Check bot permissions first
|
|
if (!interaction.guild.members.me.permissions.has('ManageRoles')) {
|
|
await interaction.editReply({
|
|
content: '❌ Bot is missing permissions. Please give the bot "Manage Roles" permission and make sure its role is above the roles.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
const nodeId = interaction.options.getString('nodeid');
|
|
console.log(`Processing verification for user: ${interaction.user.tag}`);
|
|
|
|
// First, check if this Discord user already has a node
|
|
const { data: existingUserNodes, error: userCheckError } = await supabase
|
|
.from('node_records')
|
|
.select('node_id')
|
|
.eq('discord_user_id', interaction.user.id)
|
|
.limit(1);
|
|
|
|
if (userCheckError) {
|
|
console.error('Error checking existing user nodes:', userCheckError);
|
|
await interaction.editReply({
|
|
content: '❌ Error checking user status.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (existingUserNodes && existingUserNodes.length > 0) {
|
|
await interaction.editReply({
|
|
content: '❌ You already have a node associated with your Discord account. Please contact the moderators if you need to change your node association.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Then, check if the node is already associated with another Discord user
|
|
const { data: existingNodeUser, error: nodeCheckError } = await supabase
|
|
.from('node_records')
|
|
.select('discord_user_id')
|
|
.eq('node_id', nodeId)
|
|
.not('discord_user_id', 'is', null)
|
|
.limit(1);
|
|
|
|
if (nodeCheckError) {
|
|
console.error('Error checking node association:', nodeCheckError);
|
|
await interaction.editReply({
|
|
content: '❌ Error checking node status.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (existingNodeUser && existingNodeUser.length > 0) {
|
|
await interaction.editReply({
|
|
content: '❌ This node is already associated with another Discord user. Please contact the moderators if you believe this is an error.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Now check if the node exists and is active (within last 24 hours)
|
|
const { data: existingNode, error: findError } = await supabase
|
|
.from('node_records')
|
|
.select('*')
|
|
.eq('node_id', nodeId)
|
|
.order('timestamp', { ascending: false })
|
|
.limit(1);
|
|
|
|
if (findError) {
|
|
console.error('Error finding node:', findError);
|
|
await interaction.editReply({
|
|
content: '❌ Error checking node status.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!existingNode || existingNode.length === 0) {
|
|
console.log('Node verification failed for user:', interaction.user.tag);
|
|
await interaction.editReply({
|
|
content: '❌ No node found with this ID. However, the bot only works for nodes setup using the [Codex CLI](https://github.com/codex-storage/cli) and does not work on manual installation of Codex as we do not log node information from the manual process.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check if node is active (last update within 24 hours)
|
|
const oneDayAgo = new Date();
|
|
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
|
|
|
|
if (new Date(existingNode[0].timestamp) < oneDayAgo) {
|
|
await interaction.editReply({
|
|
content: '❌ This node has not been active in the last 24 hours. Please make sure your node is running and try again.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Get all roles first
|
|
const altruisticRole = interaction.guild.roles.cache.find(r => r.name === 'Altruistic Mode');
|
|
const activeRole = interaction.guild.roles.cache.find(r => r.name === 'Active Participant');
|
|
const inactiveRole = interaction.guild.roles.cache.find(r => r.name === 'Inactive Participant');
|
|
|
|
if (!altruisticRole || !activeRole || !inactiveRole) {
|
|
await interaction.editReply({
|
|
content: '❌ Could not find one or more required roles.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check bot permissions
|
|
if (!interaction.guild.members.me.permissions.has('ManageRoles')) {
|
|
await interaction.editReply({
|
|
content: '❌ Bot is missing "Manage Roles" permission.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check if bot's role is higher than the roles it needs to assign
|
|
const botRole = interaction.guild.members.me.roles.highest;
|
|
if (botRole.position <= altruisticRole.position ||
|
|
botRole.position <= activeRole.position ||
|
|
botRole.position <= inactiveRole.position) {
|
|
await interaction.editReply({
|
|
content: '❌ Bot\'s role must be higher than the roles it needs to assign. Please move the bot\'s role up in the server settings.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Now update the discord_user_id
|
|
console.log(`Attempting to update discord_user_id for node ${nodeId} to ${interaction.user.id}`);
|
|
|
|
const { error: updateError } = await supabase
|
|
.from('node_records')
|
|
.update({ discord_user_id: interaction.user.id })
|
|
.eq('node_id', nodeId)
|
|
.eq('timestamp', existingNode[0].timestamp);
|
|
|
|
if (updateError) {
|
|
console.error('Error updating node:', updateError);
|
|
await interaction.editReply({
|
|
content: '❌ Error updating node information.',
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.log(`Successfully updated discord_user_id for node ${nodeId}`);
|
|
|
|
// Add Altruistic and Active roles, remove Inactive role
|
|
try {
|
|
await interaction.member.roles.add([altruisticRole, activeRole]);
|
|
if (interaction.member.roles.cache.has(inactiveRole.id)) {
|
|
await interaction.member.roles.remove(inactiveRole);
|
|
}
|
|
console.log('Roles updated successfully for user:', interaction.user.tag);
|
|
|
|
// Send private success message to user
|
|
await interaction.editReply({
|
|
content: '✅ Your node has been verified and roles have been granted!',
|
|
ephemeral: true
|
|
});
|
|
|
|
} catch (roleError) {
|
|
console.error('Role assignment error:', roleError);
|
|
await interaction.editReply({
|
|
content: '❌ Failed to update roles. The bot\'s role must be higher than the roles it needs to assign.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
try {
|
|
if (!interaction.replied && !interaction.deferred) {
|
|
await interaction.reply({
|
|
content: '❌ An error occurred.',
|
|
ephemeral: true
|
|
});
|
|
} else {
|
|
await interaction.editReply({
|
|
content: '❌ An error occurred.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
} catch (replyError) {
|
|
console.error('Error sending error message:', replyError);
|
|
}
|
|
}
|
|
} else if (interaction.commandName === 'checkroles') {
|
|
try {
|
|
await interaction.deferReply({ ephemeral: true });
|
|
|
|
const isActive = await checkInactiveNodes(interaction.guild, interaction.member);
|
|
|
|
if (isActive) {
|
|
await interaction.editReply({
|
|
content: '✅ Your node is active and roles are up to date.',
|
|
ephemeral: true
|
|
});
|
|
} else {
|
|
await interaction.editReply({
|
|
content: '❌ No node found with this ID. However, the bot only works for nodes setup using the [Codex CLI](https://github.com/codex-storage/cli) and does not work on manual installation of Codex as we do not log node information from the manual process.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in role check:', error);
|
|
try {
|
|
if (!interaction.replied && !interaction.deferred) {
|
|
await interaction.reply({
|
|
content: '❌ An error occurred.',
|
|
ephemeral: true
|
|
});
|
|
} else {
|
|
await interaction.editReply({
|
|
content: '❌ An error occurred.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
} catch (replyError) {
|
|
console.error('Error sending error message:', replyError);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
client.login(process.env.DISCORD_BOT_TOKEN); |