Initial commit

This commit is contained in:
2025-02-02 14:10:51 -05:00
commit 9c74e724a8
28 changed files with 10554 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
// ask.js - Discord bot AI question command
// Copyright (C) 2025 Luis Bauza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
const { SlashCommandBuilder } = require('discord.js');
const axios = require('axios');
const config = {
webSearch: {
enabled: false, // Default web search state
allowOverride: true, // Whether users can override the default state
},
};
module.exports = {
data: new SlashCommandBuilder()
.setName('ask')
.setDescription('Ask a question to the AI')
.addStringOption(option =>
option
.setName('prompt')
.setDescription('Your question or prompt')
.setRequired(true)
)
.addBooleanOption(option =>
option
.setName('websearch')
.setDescription('Enable web search for more up-to-date information')
.setRequired(false)
)
// Only show the websearch option if overrides are allowed
.setDMPermission(false),
async execute(interaction) {
await interaction.deferReply();
const prompt = interaction.options.getString('prompt');
const userWebSearchOption = interaction.options.getBoolean('websearch');
// Determine if web search should be enabled based on config and user option
const webSearchEnabled =
config.webSearch.allowOverride && userWebSearchOption !== null
? userWebSearchOption // Use user's choice if override is allowed and option was provided
: config.webSearch.enabled; // Otherwise use default config
try {
const response = await axios.post(
'https://openrouter.ai/api/v1/chat/completions',
{
model: webSearchEnabled
? 'google/gemini-2.0-flash-exp:free:online'
: 'google/gemini-2.0-flash-exp:free',
messages: [
{
role: 'system',
content:
'You are a helpful AI assistant. Provide clear, concise, and accurate responses. ' +
'Keep your answers brief while ensuring they are informative and to the point. ' +
'Avoid unnecessary elaboration or repetition. ',
},
{ role: 'user', content: prompt },
],
plugins: webSearchEnabled
? [
{
id: 'web',
max_results: 3,
search_prompt:
`A web search was conducted on ${new Date().toISOString()}. ` +
'Incorporate the following web search results into your response. ' +
'IMPORTANT: Cite them using markdown links named using the domain of the source. ' +
'Example: [nytimes.com](https://nytimes.com/some-page).',
},
]
: undefined,
},
{
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
'HTTP-Referer': 'https://github.com/hllywluis/kekbot.js',
'Content-Type': 'application/json',
},
}
);
const aiResponse = response.data.choices[0].message.content;
const webSearchStatus = webSearchEnabled
? '\n> *Web search enabled* 🔍\n'
: '';
const formattedResponse = `> **Question:** ${prompt}${webSearchStatus}\n${aiResponse}`;
if (formattedResponse.length <= 2000) {
// Send as a single message with proper formatting
await interaction.followUp({
content: formattedResponse,
split: false,
allowedMentions: { parse: [] },
});
} else {
// For longer messages, split while preserving markdown
const maxLength = 2000;
const chunks = [];
let remainingText = formattedResponse;
let isFirstChunk = true;
while (remainingText.length > 0) {
let chunk = remainingText.slice(0, maxLength);
// If we're in the middle of a code block, find a safe split point
const lastCodeBlock = chunk.lastIndexOf('```');
if (
lastCodeBlock !== -1 &&
!chunk.slice(lastCodeBlock).includes('\n```')
) {
// Find the last newline before maxLength
const lastNewline = chunk.lastIndexOf('\n');
if (lastNewline !== -1) {
chunk = chunk.slice(0, lastNewline);
}
}
// For subsequent chunks, add continuation indicator
if (!isFirstChunk) {
chunk = `(continued)\n${chunk}`;
}
chunks.push({
content: chunk,
split: false,
allowedMentions: { parse: [] },
});
remainingText = remainingText.slice(chunk.length);
isFirstChunk = false;
}
// Send all chunks in sequence
await Promise.all(chunks.map(chunk => interaction.followUp(chunk)));
}
} catch (error) {
console.error('Error:', error.response?.data || error.message);
await interaction.followUp({
content: 'Sorry, there was an error processing your request.',
ephemeral: true,
});
}
},
};
+32
View File
@@ -0,0 +1,32 @@
// help.js - Discord bot help command
// Copyright (C) 2025 Luis Bauza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('help')
.setDescription('Lists all available commands'),
async execute(interaction) {
const { commands } = interaction.client;
const helpEmbed = new EmbedBuilder()
.setColor('#5dc67b')
.setTitle('Available Commands')
.setDescription('Here are all my commands:')
.setTimestamp();
commands.forEach(command => {
helpEmbed.addFields({
name: `/${command.data.name}`,
value: command.data.description,
});
});
await interaction.reply({ embeds: [helpEmbed], ephemeral: true });
},
};
+59
View File
@@ -0,0 +1,59 @@
// kick.js - Discord bot kick command
// Copyright (C) 2025 Luis Bauza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('kick')
.setDescription('Kick a user from the server')
.addUserOption(option =>
option
.setName('target')
.setDescription('The user to kick')
.setRequired(true),
)
.addStringOption(option =>
option.setName('reason').setDescription('Reason for kicking'),
)
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
async execute(interaction) {
const target = interaction.options.getMember('target');
const reason =
interaction.options.getString('reason') ?? 'No reason provided';
if (!target) {
return interaction.reply({
content: 'That user is not in this server!',
ephemeral: true,
});
}
if (!target.kickable) {
return interaction.reply({
content:
'I cannot kick this user! They may have higher permissions than me.',
ephemeral: true,
});
}
try {
await target.kick(reason);
await interaction.reply({
content: `Successfully kicked ${target.user.tag}\nReason: ${reason}`,
ephemeral: true,
});
} catch (error) {
console.error(error);
await interaction.reply({
content: 'There was an error trying to kick this user!',
ephemeral: true,
});
}
},
};
+18
View File
@@ -0,0 +1,18 @@
// ping.js - Discord bot ping command
// Copyright (C) 2025 Luis Bauza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong!'),
async execute(interaction) {
await interaction.reply('Pong! 🏓');
},
};
+41
View File
@@ -0,0 +1,41 @@
// prune.js - Discord bot message pruning command
// Copyright (C) 2025 Luis Bauza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('prune')
.setDescription('Prune up to 99 messages.')
.addIntegerOption(option =>
option
.setName('amount')
.setDescription('Number of messages to prune')
.setMinValue(1)
.setMaxValue(99)
.setRequired(true),
)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
async execute(interaction) {
const amount = interaction.options.getInteger('amount');
try {
const deleted = await interaction.channel.bulkDelete(amount, true);
await interaction.reply({
content: `Successfully deleted ${deleted.size} message(s).`,
ephemeral: true,
});
} catch (error) {
console.error(error);
await interaction.reply({
content: 'There was an error trying to prune messages in this channel!',
ephemeral: true,
});
}
},
};