This commit is contained in:
Xargana 2025-03-30 11:24:47 +03:00
parent b88e8c480b
commit d5ff48ec4f

View file

@ -5,7 +5,8 @@ const {
REST,
Routes,
ApplicationCommandOptionType,
ApplicationCommandType
ApplicationCommandType,
ChannelType
} = require("discord.js");
const axios = require("axios");
const ping = require("ping");
@ -25,15 +26,13 @@ const client = new Client({
partials: ['CHANNEL', 'MESSAGE'] // This is important for DM functionality
});
// Original slash commands
const slashCommands = [
// Commands that should work in DMs (registered globally)
const globalCommands = [
{
name: "fetch_data",
description: "Fetches data from an API",
type: ApplicationCommandType.ChatInput,
dm_permission: true,
options: [
{
name: "url",
@ -71,7 +70,6 @@ const slashCommands = [
},
],
},
// Add Cody command
{
name: "cody",
description: "Ask Cody (Sourcegraph AI) a coding question",
@ -86,7 +84,6 @@ const slashCommands = [
},
],
},
// Add Weather command
{
name: "weather",
description: "Get current weather for a location",
@ -101,7 +98,6 @@ const slashCommands = [
},
],
},
// Add Minecraft server status command
{
name: "mcstatus",
description: "Check the status of a Minecraft server",
@ -147,7 +143,6 @@ const slashCommands = [
},
],
},
// Add Anime commands
{
name: "anime",
description: "Get anime-related content",
@ -236,15 +231,13 @@ const slashCommands = [
}
]
},
// Add to slashCommands array
{
{
name: "stats",
description: "Show bot and server statistics",
type: ApplicationCommandType.ChatInput,
dm_permission: true
},
// Add to slashCommands array
{
},
{
name: "checkport",
description: "Check if specific ports are open on a domain",
type: ApplicationCommandType.ChatInput,
@ -263,12 +256,15 @@ const slashCommands = [
required: true,
}
]
}
}
];
// User context menu commands
// Commands that only make sense in a guild context
const guildCommands = [
// Add guild-specific commands here if needed
];
// User context menu commands (should also be registered globally)
const userCommands = [
{
name: "User Info",
@ -276,9 +272,18 @@ const userCommands = [
},
];
const commands = [...slashCommands, ...userCommands];
// Function to get existing commands without deleting them
async function getExistingCommands(rest, route) {
try {
return await rest.get(route);
} catch (error) {
console.error(`Error fetching commands from ${route}:`, error);
return [];
}
}
async function updateCommands() {
// New function to safely update commands
async function updateCommandsSafely() {
if (!process.env.DISCORD_TOKEN || !process.env.CLIENT_ID) {
console.error("Missing required environment variables: DISCORD_TOKEN or CLIENT_ID");
return;
@ -287,26 +292,98 @@ async function updateCommands() {
const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN);
try {
console.log("Fetching existing commands...");
const existingCommands = await rest.get(Routes.applicationCommands(process.env.CLIENT_ID));
console.log("Starting command registration...");
// Delete all existing commands if needed
// for (const command of existingCommands) {
// await rest.delete(`${Routes.applicationCommands(process.env.CLIENT_ID)}/${command.id}`);
// console.log(`Deleted command: ${command.name}`);
// }
// First, get all existing global commands to check for entry point commands
const existingGlobalCommands = await getExistingCommands(
rest,
Routes.applicationCommands(process.env.CLIENT_ID)
);
// Register new commands (slash + user commands)
console.log("Registering new commands...");
await rest.put(Routes.applicationCommands(process.env.CLIENT_ID), { body: commands });
console.log("Commands updated successfully!");
// Find any entry point or special commands we need to preserve
const entryPointCommands = existingGlobalCommands.filter(
cmd => cmd.integration_types && cmd.integration_types.includes(1)
);
// Create a map to track command names we've already added
const commandNameMap = new Map();
// Create a filtered array of commands without duplicates
const allGlobalCommands = [];
// First add global commands
for (const cmd of globalCommands) {
if (!commandNameMap.has(cmd.name)) {
commandNameMap.set(cmd.name, true);
allGlobalCommands.push(cmd);
} else {
console.warn(`Skipping duplicate global command: ${cmd.name}`);
}
}
// Then add user commands
for (const cmd of userCommands) {
if (!commandNameMap.has(cmd.name)) {
commandNameMap.set(cmd.name, true);
allGlobalCommands.push(cmd);
} else {
console.warn(`Skipping duplicate user command: ${cmd.name}`);
}
}
// Finally, add entry point commands that don't duplicate existing names
for (const cmd of entryPointCommands) {
if (!commandNameMap.has(cmd.name)) {
commandNameMap.set(cmd.name, true);
allGlobalCommands.push(cmd);
} else {
console.log(`Entry point command "${cmd.name}" already exists, keeping existing definition`);
}
}
console.log(`Registering ${allGlobalCommands.length} unique global commands...`);
// Update global commands (including DM-compatible commands)
await rest.put(
Routes.applicationCommands(process.env.CLIENT_ID),
{ body: allGlobalCommands }
);
console.log(`Successfully registered ${allGlobalCommands.length} global commands`);
// If we have guild-specific commands, register them for each guild
if (guildCommands.length > 0) {
// Wait for client to be ready to access guilds
if (!client.isReady()) {
await new Promise(resolve => {
client.once('ready', resolve);
});
}
// Register guild commands for each guild the bot is in
for (const guild of client.guilds.cache.values()) {
console.log(`Registering guild commands for ${guild.name} (${guild.id})...`);
await rest.put(
Routes.applicationGuildCommands(process.env.CLIENT_ID, guild.id),
{ body: guildCommands }
);
}
console.log(`Successfully registered ${guildCommands.length} guild commands per server`);
}
console.log("All commands registered successfully!");
} catch (error) {
console.error("Error updating commands:", error);
if (error.code === 50240) {
console.error("This error suggests you need to include all Entry Point commands in your update.");
console.error("Try using the updateCommandsSafely function which preserves Entry Point commands.");
}
}
}
// Function to ask Cody a question and parse the streaming response
async function askCody(question) {
// [Function implementation remains the same]
if (!process.env.SOURCEGRAPH_API_KEY) {
throw new Error("SOURCEGRAPH_API_KEY is not set in environment variables");
}
@ -338,7 +415,6 @@ async function askCody(question) {
responseType: 'text'
});
// Parse the streaming response
const events = response.data.split('\n\n').filter(line => line.trim() !== '');
let fullCompletion = '';
@ -353,7 +429,6 @@ async function askCody(question) {
try {
const jsonData = JSON.parse(dataLine.substring(6));
if (jsonData.completion) {
// This is the full completion up to this point, not just an increment
fullCompletion = jsonData.completion;
}
} catch (e) {
@ -373,10 +448,16 @@ async function askCody(question) {
throw error;
}
}
client.once("ready", async () => {
console.log(`Logged in as ${client.user.tag}`);
await updateCommands();
// Register commands after the bot is ready
await updateCommandsSafely();
// Send startup notification
if (process.env.OWNER_ID) {
try {
const ownerId = process.env.OWNER_ID;
const owner = await client.users.fetch(ownerId);
const startupEmbed = {
@ -401,14 +482,19 @@ client.once("ready", async () => {
};
await owner.send({ embeds: [startupEmbed] });
} catch (error) {
console.error("Failed to send startup notification:", error);
}
}
});
client.on("interactionCreate", async (interaction) => {
// Add debug logging to help diagnose DM issues
console.log(`Received interaction: ${interaction.commandName} | Channel type: ${interaction.channel.type} | DM: ${interaction.channel.type === ChannelType.DM}`);
// Handle slash commands
if (interaction.isChatInputCommand()) {
try {
switch (interaction.commandName) {
case "fetch_data":
try {
@ -464,6 +550,7 @@ client.on("interactionCreate", async (interaction) => {
await interaction.editReply({ content: "Failed to ping.", ephemeral: true });
}
break;
case "server_status":
try {
const response = await axios.get("https://blahaj.tr:2589/status");
@ -503,6 +590,7 @@ client.on("interactionCreate", async (interaction) => {
await interaction.reply({ content: "Failed to get status.", ephemeral: true });
}
break;
case "cody":
try {
await interaction.deferReply();
@ -543,7 +631,8 @@ client.on("interactionCreate", async (interaction) => {
content: "Sorry, I couldn't get an answer from Cody. Please try again later.",
ephemeral: true
});
} break;
}
break;
case "weather":
try {
@ -599,14 +688,13 @@ client.on("interactionCreate", async (interaction) => {
}
break;
// In the mcstatus command handler, replace the thumbnail section with this:
case "mcstatus":
// Handle all the other commands the same way as in the original code
case "mcstatus":
try {
await interaction.deferReply();
const serverAddress = interaction.options.getString("server");
const isBedrock = interaction.options.getBoolean("bedrock") ?? false;
// Determine which API endpoint to use based on server type
const apiUrl = isBedrock
? `https://api.mcsrvstat.us/bedrock/2/${encodeURIComponent(serverAddress)}`
: `https://api.mcsrvstat.us/2/${encodeURIComponent(serverAddress)}`;
@ -621,11 +709,9 @@ case "mcstatus":
return;
}
// Create a rich embed for the server status
const serverEmbed = {
title: `Minecraft Server Status: ${serverAddress}`,
color: 0x44FF44, // Green color
// Use a default Minecraft image instead of trying to use the base64 icon
color: 0x44FF44,
thumbnail: {
url: 'https://www.minecraft.net/content/dam/games/minecraft/key-art/MC_The-Wild-Update_540x300.jpg'
},
@ -638,14 +724,11 @@ case "mcstatus":
timestamp: new Date()
};
// Add MOTD if available
if (data.motd && data.motd.clean && data.motd.clean.length > 0) {
serverEmbed.description = `**MOTD:**\n${data.motd.clean.join('\n')}`;
}
// Add player list if available and not empty
if (data.players && data.players.list && data.players.list.length > 0) {
// Limit to first 20 players to avoid hitting Discord's limits
const playerList = data.players.list.slice(0, 20).join(', ');
const hasMore = data.players.list.length > 20;
@ -670,7 +753,6 @@ case "mcstatus":
await interaction.deferReply();
const animalType = interaction.options.getString("type");
// Get animal image
const imageResponse = await axios.get(`https://some-random-api.com/animal/${animalType}`);
const imageUrl = imageResponse.data.image;
@ -691,25 +773,24 @@ case "mcstatus":
}
break;
case "anime":
case "anime":
try {
await interaction.deferReply();
const action = interaction.options.getString("action");
const type = interaction.options.getString("type");
let apiUrl;
let isQuote = false;
if (action === "quote") {
if (type === "quote") {
apiUrl = "https://some-random-api.ml/animu/quote";
isQuote = true;
} else {
apiUrl = `https://some-random-api.ml/animu/${action}`;
apiUrl = `https://some-random-api.ml/animu/${type}`;
}
const response = await axios.get(apiUrl);
if (isQuote) {
// Handle quote response
const quote = response.data.sentence;
const character = response.data.character;
const anime = response.data.anime;
@ -728,10 +809,9 @@ case "anime":
await interaction.editReply({ embeds: [quoteEmbed] });
} else {
// Handle GIF response
const gifUrl = response.data.link;
const actionTitle = action.charAt(0).toUpperCase() + action.slice(1).replace('-', ' ');
const actionTitle = type.charAt(0).toUpperCase() + type.slice(1).replace('-', ' ');
const gifEmbed = {
title: `Anime ${actionTitle}`,
@ -751,6 +831,7 @@ case "anime":
});
}
break;
case "checkdns":
try {
await interaction.deferReply();
@ -814,6 +895,7 @@ case "anime":
});
}
break;
case "traceroute":
try {
await interaction.deferReply();
@ -869,6 +951,7 @@ case "anime":
});
}
break;
case "whois":
try {
await interaction.deferReply();
@ -917,6 +1000,7 @@ case "anime":
});
}
break;
case "stats":
try {
await interaction.deferReply();
@ -942,7 +1026,7 @@ case "anime":
`**Servers:** ${client.guilds.cache.size}`,
`**Users:** ${client.users.cache.size}`,
`**Channels:** ${client.channels.cache.size}`,
`**Commands:** ${slashCommands.length}`
`**Commands:** ${globalCommands.length}`
].join('\n'),
inline: true
},
@ -975,6 +1059,7 @@ case "anime":
});
}
break;
case "checkport":
try {
await interaction.deferReply();
@ -1030,6 +1115,98 @@ case "anime":
}
break;
}
}});
client.login(process.env.DISCORD_TOKEN);
default:
await interaction.reply({
content: `Command '${interaction.commandName}' not implemented yet.`,
ephemeral: true
});
}
} catch (error) {
console.error(`Error executing command ${interaction.commandName}:`, error);
// Try to respond if we haven't already
try {
const replyMethod = interaction.deferred ? interaction.editReply : interaction.reply;
await replyMethod.call(interaction, {
content: "An error occurred while executing this command.",
ephemeral: true
});
} catch (e) {
console.error("Could not send error response:", e);
}
}
} else if (interaction.isUserContextMenuCommand()) {
// Handle user context menu commands
if (interaction.commandName === "User Info") {
const user = interaction.targetUser;
const userInfoEmbed = {
title: "User Information",
color: 0x9B59B6,
thumbnail: { url: user.displayAvatarURL({ dynamic: true }) },
fields: [
{ name: "Username", value: user.username, inline: true },
{ name: "User ID", value: user.id, inline: true },
{ name: "Account Created", value: `<t:${Math.floor(user.createdTimestamp / 1000)}:R>`, inline: true }
],
footer: { text: "User Information" },
timestamp: new Date()
};
await interaction.reply({ embeds: [userInfoEmbed], ephemeral: true });
}
}
});
// Handle guild join events to register guild-specific commands if needed
client.on("guildCreate", async (guild) => {
console.log(`Joined new guild: ${guild.name} (${guild.id})`);
if (guildCommands.length > 0) {
try {
console.log(`Registering guild commands for ${guild.name}...`);
const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN);
await rest.put(
Routes.applicationGuildCommands(process.env.CLIENT_ID, guild.id),
{ body: guildCommands }
);
console.log(`Successfully registered guild commands for ${guild.name}`);
} catch (error) {
console.error(`Error registering guild commands for ${guild.name}:`, error);
}
}
// Notify owner if configured
if (process.env.OWNER_ID) {
try {
const owner = await client.users.fetch(process.env.OWNER_ID);
const guildJoinEmbed = {
title: "New Guild Joined",
color: 0x00ff00,
fields: [
{ name: "Guild Name", value: guild.name, inline: true },
{ name: "Guild ID", value: guild.id, inline: true },
{ name: "Member Count", value: guild.memberCount.toString(), inline: true }
],
timestamp: new Date(),
footer: { text: "Guild Join Event" }
};
await owner.send({ embeds: [guildJoinEmbed] });
} catch (error) {
console.error("Failed to notify owner of guild join:", error);
}
}
});
// Error handling
process.on('unhandledRejection', error => {
console.error('Unhandled promise rejection:', error);
});
// Start the bot
client.login(process.env.DISCORD_TOKEN).catch(error => {
console.error("Failed to login:", error);
process.exit(1);
});