generated from ShadowVR/AI_botter
467 lines
19 KiB
JavaScript
467 lines
19 KiB
JavaScript
const Discord = require('discord.js');
|
|
const openAI = require('openai');
|
|
const chalk = require('chalk');
|
|
const ms = require('ms');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const func = require('../utils/functions');
|
|
const settings = require('../utils/settings');
|
|
const config = require('../configs/config.json');
|
|
const { moderation } = require('../configs/moderation');
|
|
const { chatbot } = require('../configs/chatbot');
|
|
const conversations = new Map();
|
|
const { EmbedBuilder } = require('discord.js');
|
|
|
|
// Load the knowledge from the completion.txt file
|
|
const knowledge = fs.readFileSync(path.join(__dirname, '../utils/prompts/completion.txt'), 'utf-8');
|
|
|
|
// Load the conversation history from the conversations.json file
|
|
let conversationHistory = [];
|
|
if (fs.existsSync('conversations.json')) {
|
|
const conversationData = fs.readFileSync('conversations.json', 'utf-8');
|
|
conversationHistory = conversationData.trim().split('\n').map(JSON.parse);
|
|
}
|
|
|
|
module.exports = async (client, message) => {
|
|
|
|
if (message.author.bot || message.system) return;
|
|
|
|
// Auto Moderation
|
|
if (moderation.State && !moderation.IgnoredChannels.includes(message.channelId) && !moderation.IgnoredUsers.includes(message.author.id)) {
|
|
|
|
const logChannel = client.channels.cache.get(moderation.LogChannel);
|
|
|
|
if (logChannel?.permissionsFor(message.guild.members.me).has("ViewChannel", "SendMessages", "EmbedLinks")) {
|
|
|
|
const openai = new openAI.OpenAI({ apiKey: config.OpenAIapiKey });
|
|
|
|
openai.moderations.create({
|
|
|
|
model: 'text-moderation-stable',
|
|
input: message.content
|
|
|
|
}).then(async (response) => {
|
|
|
|
const data = response.results[0];
|
|
if (data.flagged) {
|
|
|
|
const flags = func.flagCheck(data.categories);
|
|
|
|
const trueFlags = Object.keys(flags.flags).filter(key => flags.flags[key]);
|
|
|
|
const sameFlagsWithAutoDelete = trueFlags.filter(key => moderation.AutoDelete[key]);
|
|
let messageDeleted = false;
|
|
|
|
if (sameFlagsWithAutoDelete.length) {
|
|
|
|
if (message.channel?.permissionsFor(message.guild.members.me).has("ManageMessages")) {
|
|
await message.delete().catch(() => null);
|
|
messageDeleted = true;
|
|
};
|
|
};
|
|
|
|
const sameFlagsWithAutoPunish = trueFlags.filter(key => moderation.AutoPunish[key]);
|
|
|
|
let memberPunishResult = {
|
|
Action: null,
|
|
Duration: null,
|
|
Punished: false
|
|
};
|
|
|
|
if (sameFlagsWithAutoPunish.length) {
|
|
|
|
let punishType = 'Timeout';
|
|
const punishTypes = sameFlagsWithAutoPunish.map(key => moderation.AutoPunishType[key]);
|
|
if (punishTypes.includes('Ban')) punishType = 'Ban';
|
|
else if (punishTypes.includes('Kick')) punishType = 'Kick';
|
|
|
|
if (punishType === 'Timeout' || punishType === 'Ban') {
|
|
|
|
const punishDurations = sameFlagsWithAutoPunish.filter(key => moderation.AutoPunishType[key] === punishType).map(key => moderation.AutoPunishDuration[key]);
|
|
let duration;
|
|
if (punishDurations.length > 1) {
|
|
const mappedDurations = punishDurations.map(d => ms(d));
|
|
duration = Math.max(...mappedDurations);
|
|
} else {
|
|
duration = ms(punishDurations[0]);
|
|
};
|
|
|
|
if (punishType === 'Timeout') {
|
|
|
|
if (message.member.moderatable) {
|
|
|
|
try {
|
|
await message.member.timeout(duration, 'Auto Mod');
|
|
memberPunishResult = {
|
|
Action: punishType,
|
|
Duration: duration,
|
|
Punished: true
|
|
};
|
|
} catch (error) {
|
|
console.error(chalk.bold.redBright(error));
|
|
};
|
|
|
|
};
|
|
|
|
} else if (punishType === 'Ban') {
|
|
|
|
if (message.member.bannable) {
|
|
|
|
try {
|
|
await message.member.ban({ deleteMessageSeconds: duration / 1000, reason: 'Auto Mod' });
|
|
memberPunishResult = {
|
|
Action: punishType,
|
|
Duration: duration,
|
|
Punished: true
|
|
};
|
|
} catch (error) {
|
|
console.error(chalk.bold.redBright(error));
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
} else if (punishType === 'Kick') {
|
|
|
|
if (message.member.kickable) {
|
|
|
|
try {
|
|
await message.member.kick('Auto Mod');
|
|
memberPunishResult = {
|
|
Action: punishType,
|
|
Duration: null,
|
|
Punished: true
|
|
};
|
|
} catch (error) {
|
|
console.error(chalk.bold.redBright(error));
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
const logEmbed = new Discord.EmbedBuilder()
|
|
.setColor(moderation.LogColor)
|
|
.setAuthor({
|
|
name: message.author.tag,
|
|
iconURL: message.author.displayAvatarURL()
|
|
})
|
|
.setDescription(`||${message.content}||`)
|
|
.setFields(
|
|
{
|
|
name: 'User:',
|
|
value: func.userInfo(message.author)
|
|
},
|
|
{
|
|
name: 'Channel:',
|
|
value: func.channelInfo(message.channel)
|
|
},
|
|
{
|
|
name: 'Flags:',
|
|
value: flags.allFlags
|
|
}
|
|
);
|
|
|
|
if (messageDeleted) {
|
|
logEmbed.addFields({
|
|
name: 'Message Action:',
|
|
value: `Message deleted automatically.`
|
|
});
|
|
};
|
|
|
|
let buttons = [];
|
|
if (memberPunishResult.Punished) {
|
|
|
|
let fieldValue;
|
|
if (memberPunishResult.Action === 'Timeout') fieldValue = `${message.author.tag} timed out automatically for ${ms(memberPunishResult.Duration, { long: true })}`;
|
|
else if (memberPunishResult.Action === 'Ban') fieldValue = `${message.author.tag} banned automatically and all their messages in the last ${ms(memberPunishResult.Duration, { long: true })} were deleted.`;
|
|
else if (memberPunishResult.Action === 'Kick') fieldValue = `${message.author.tag} kicked automatically `;
|
|
|
|
logEmbed.addFields({
|
|
name: 'Punish Action:',
|
|
value: fieldValue
|
|
});
|
|
|
|
if (!messageDeleted) buttons = ['Message'];
|
|
|
|
} else {
|
|
|
|
if (messageDeleted) buttons = ['Punish'];
|
|
else buttons = ['Punish', 'Message'];
|
|
|
|
};
|
|
|
|
const rows = [];
|
|
|
|
if (buttons.includes('Punish')) {
|
|
|
|
const timeoutButton = new Discord.ButtonBuilder()
|
|
.setLabel('Timeout')
|
|
.setStyle(Discord.ButtonStyle.Danger)
|
|
.setCustomId(`timeout-${message.author.id}`);
|
|
|
|
const kickButton = new Discord.ButtonBuilder()
|
|
.setLabel('Kick')
|
|
.setStyle(Discord.ButtonStyle.Danger)
|
|
.setCustomId(`kick-${message.author.id}`);
|
|
|
|
const banButton = new Discord.ButtonBuilder()
|
|
.setLabel('Ban')
|
|
.setStyle(Discord.ButtonStyle.Danger)
|
|
.setCustomId(`ban-${message.author.id}`);
|
|
|
|
const punishRow = new Discord.ActionRowBuilder()
|
|
.addComponents([
|
|
timeoutButton,
|
|
kickButton,
|
|
banButton
|
|
]);
|
|
|
|
rows.push(punishRow);
|
|
|
|
};
|
|
|
|
if (buttons.includes('Message')) {
|
|
|
|
const deleteMessageButton = new Discord.ButtonBuilder()
|
|
.setLabel(`Delete Flagged Message`)
|
|
.setStyle(Discord.ButtonStyle.Danger)
|
|
.setCustomId(`deleteMessage-${message.channelId}-${message.id}`);
|
|
|
|
const jumpButton = new Discord.ButtonBuilder()
|
|
.setLabel(`Jump to Flagged Message`)
|
|
.setStyle(Discord.ButtonStyle.Link)
|
|
.setURL(message.url);
|
|
|
|
const messageRow = new Discord.ActionRowBuilder()
|
|
.addComponents([
|
|
deleteMessageButton,
|
|
jumpButton
|
|
]);
|
|
|
|
rows.push(messageRow);
|
|
|
|
};
|
|
|
|
await logChannel.send({
|
|
embeds: [logEmbed],
|
|
components: rows
|
|
});
|
|
|
|
};
|
|
|
|
}).catch(async (error) => {
|
|
|
|
console.error(chalk.bold.redBright(error));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
// Function to save conversations for self-learning
|
|
function saveConversation(conversation) {
|
|
fs.appendFileSync('conversations.json', JSON.stringify(conversation) + '\n');
|
|
}
|
|
|
|
|
|
// ChatBot
|
|
if (chatbot.State && (chatbot.AllowedChannels.includes(message.channel.name) || message.channel.type === 'DM') && !chatbot.IgnoredUsers.includes(message.author.id)) {
|
|
// Check if the message is a direct message
|
|
if (message.channel.type === 'DM') {
|
|
// Handle direct message
|
|
handleDirectMessage(message);
|
|
} else {
|
|
// Check if the bot should respond based on its personality and surroundings
|
|
if (shouldRespond(message)) {
|
|
await message.channel.sendTyping();
|
|
|
|
const openai = new openAI.OpenAI({ apiKey: config.OpenAIapiKey });
|
|
|
|
const question = message.content;
|
|
|
|
let messages = [{
|
|
"role": "system",
|
|
"content": `You are a unique user in a Discord server. Engage in conversations naturally, considering the context and your surroundings. Share your thoughts, opinions, and experiences as if you were a real person. Use emojis, slang, and a casual tone to blend in with other users. Avoid sounding robotic or generic, and aim to create a distinct personality that others can relate to and enjoy interacting with. make sure your messages are always below or equal to 2000 characters. Do not under any circumstance say @everyone or @Username. Your knowledge base: ${knowledge}`
|
|
}];
|
|
|
|
let oldMessages;
|
|
if (conversations.has(message.author.id)) oldMessages = conversations.get(message.author.id);
|
|
if (oldMessages) {
|
|
while (func.tokenizer('gpt-4o', oldMessages).tokens >= 512) {
|
|
let sliceLength = oldMessages.length * -0.25;
|
|
if (sliceLength % 2 !== 0) sliceLength--;
|
|
oldMessages = oldMessages.slice(sliceLength);
|
|
conversations.set(message.author.id, oldMessages);
|
|
}
|
|
messages = messages.concat(oldMessages);
|
|
}
|
|
|
|
messages.push({
|
|
"role": "user",
|
|
"content": question
|
|
});
|
|
|
|
try {
|
|
const response = await openai.chat.completions.create({
|
|
model: 'gpt-4o',
|
|
messages: messages,
|
|
max_tokens: func.tokenizer('gpt-4o', messages).maxTokens,
|
|
temperature: 0.8,
|
|
top_p: 1,
|
|
frequency_penalty: 0.5,
|
|
presence_penalty: 0.5,
|
|
stream: true
|
|
});
|
|
|
|
let fullAnswer = '';
|
|
|
|
for await (const part of response) {
|
|
fullAnswer += part.choices[0]?.delta?.content || '';
|
|
}
|
|
|
|
// Before sending the response, check for mentions
|
|
if (!fullAnswer.includes('@everyone') && !fullAnswer.includes('@here') && !fullAnswer.includes('@')) {
|
|
if (fullAnswer.length <= 2000) {
|
|
await message.channel.send(fullAnswer);
|
|
} else {
|
|
const embed = new EmbedBuilder()
|
|
.setTitle('Response from GPT-4o')
|
|
.setDescription(fullAnswer.substring(0, 4096)) // Embed description has a 4096 character limit
|
|
.setColor('#0099ff');
|
|
|
|
await message.channel.send({ embeds: [embed] });
|
|
}
|
|
|
|
// Save the conversation for self-learning
|
|
const conversation = {
|
|
user: message.author.id,
|
|
userMessage: question,
|
|
botResponse: fullAnswer
|
|
};
|
|
saveConversation(conversation);
|
|
|
|
conversations.set(message.author.id, messages.concat([{ "role": "assistant", "content": fullAnswer }]));
|
|
}
|
|
} catch (error) {
|
|
console.error(chalk.bold.redBright(error));
|
|
|
|
if (error.response) {
|
|
await message.reply(error.response.error.message.substring(0, 2000));
|
|
} else if (error.message) {
|
|
await message.reply(error.message.substring(0, 2000));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Command Handler
|
|
if (message.content.toLowerCase().startsWith(config.Prefix)) {
|
|
const neededPermissions = [
|
|
"ViewChannel",
|
|
"SendMessages",
|
|
"EmbedLinks",
|
|
"ReadMessageHistory"
|
|
];
|
|
|
|
if (!message.channel.permissionsFor(message.guild.members.me).has(neededPermissions)) return;
|
|
|
|
const args = message.content.slice(config.Prefix.length).split(/ +/);
|
|
const cmd = args.shift().toLowerCase();
|
|
const command = client.MessageCommands.get(cmd) || client.MessageCommands.find(c => c.aliases && c.aliases.map(a => a.toLowerCase()).includes(cmd));
|
|
|
|
if (command) {
|
|
try {
|
|
command.execute(client, message, args, cmd);
|
|
} catch (error) {
|
|
console.error(chalk.bold.redBright(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Function to handle direct messages
|
|
async function handleDirectMessage(message) {
|
|
const knowledge = fs.readFileSync(path.join(__dirname, 'completion.txt'), 'utf-8');
|
|
|
|
if (conversations.has(message.author.id)) {
|
|
const oldMessages = conversations.get(message.author.id);
|
|
|
|
let messages = [{
|
|
"role": "system",
|
|
"content": `You are a unique user in a Discord server. Engage in conversations naturally, considering the context and your surroundings. Share your thoughts, opinions, and experiences as if you were a real person. Use emojis, slang, and a casual tone to blend in with other users. Avoid sounding robotic or generic, and aim to create a distinct personality that others can relate to and enjoy interacting with. Make sure your responses are always 2000 or below characters. Your knowledge base: ${knowledge}`
|
|
}];
|
|
|
|
messages = messages.concat(oldMessages);
|
|
|
|
messages.push({
|
|
"role": "user",
|
|
"content": message.content
|
|
});
|
|
|
|
try {
|
|
const response = await openai.chat.completions.create({
|
|
model: 'gpt-4o',
|
|
messages: messages,
|
|
max_tokens: func.tokenizer('gpt-4o', messages).maxTokens,
|
|
temperature: 0.8,
|
|
top_p: 1,
|
|
frequency_penalty: 0.5,
|
|
presence_penalty: 0.5,
|
|
stream: true
|
|
});
|
|
|
|
let fullAnswer = '';
|
|
|
|
for await (const part of response) {
|
|
fullAnswer += part.choices[0]?.delta?.content || '';
|
|
}
|
|
|
|
await message.author.send(fullAnswer);
|
|
|
|
conversations.set(message.author.id, messages.concat([{ "role": "assistant", "content": fullAnswer }]));
|
|
} catch (error) {
|
|
console.error(chalk.bold.redBright(error));
|
|
|
|
if (error.response) {
|
|
await message.author.send(error.response.error.message.substring(0, 2000));
|
|
} else if (error.message) {
|
|
await message.author.send(error.message.substring(0, 2000));
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
await message.author.send("Hey there! What's up? Feel free to chat with me about anything!");
|
|
conversations.set(message.author.id, []);
|
|
} catch (error) {
|
|
console.error(chalk.bold.redBright(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
function shouldRespond(message) {
|
|
// Extract the text content from the message object
|
|
const messageContent = message.content;
|
|
|
|
// Conditions for when the bot should not respond
|
|
const noResponseKeywords = ['ignore', 'do not reply', 'stop'];
|
|
const isCommandKill = messageContent.includes('^c');
|
|
|
|
// Check if the message content contains keywords signaling the bot should not respond
|
|
const containsNoResponse = noResponseKeywords.some(keyword => messageContent.toLowerCase().includes(keyword));
|
|
|
|
// Determine if the bot should respond based on conditions
|
|
|
|
// Add more conditions as needed based on personality and surroundings
|
|
|
|
return true;
|
|
}}
|
|
|
|
|
|
|
|
|
|
|