Update bot to new discord API and add simple chatgpt module

master
Talon 2023-03-21 13:35:28 +01:00
parent 136173f4de
commit b6008e0ad3
11 changed files with 6383 additions and 4183 deletions

View File

@ -1,6 +1,9 @@
const Voice = require("@discordjs/voice");
module.exports = class AudioQueue {
constructor(connection) {
constructor(connection, api) {
this.connection = connection;
this.api = api;
this.queue = [];
this.current = undefined;
}
@ -9,10 +12,11 @@ module.exports = class AudioQueue {
this.current = undefined;
return;
}
this.current = this.connection.play(this.queue[0]);
this.current.on('speaking', (val) => {if (val == 0) this.handleStop(this.current)});
this.current = this.api.play(this.queue[0]);
this.api.player.on(Voice.AudioPlayerStatus.Idle, this.handleStop.bind(this));
}
handleStop(current) {
console.log(`Handling stop`);
this.queue.shift();
this.playNext();
}

62
adapter.js 100644
View File

@ -0,0 +1,62 @@
const { Snowflake, Client, Guild, VoiceBasedChannel, Events, Status, GatewayDispatchEvents } = require("discord.js");
const adapters = new Map();
const trackedClients = new Set();
const trackedShards = new Map();
function trackClient(client) {
if (trackedClients.has(client)) return;
trackedClients.add(client);
client.ws.on(GatewayDispatchEvents.VoiceServerUpdate, (payload) => {
adapters.get(payload.guild_id)?.onVoiceServerUpdate(payload);
});
client.ws.on(GatewayDispatchEvents.VoiceStateUpdate, (payload) => {
if (payload.guild_id && payload.session_id && payload.user_id === client.user?.id) {
adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload);
}
});
client.on(Events.ShardDisconnect, (_, shardId) => {
const guilds = trackedShards.get(shardId);
if (guilds) {
for (const guildID of guilds.values()) {
adapters.get(guildID)?.destroy();
}
}
trackedShards.delete(shardId);
});
}
function trackGuild(guild) {
let guilds = trackedShards.get(guild.shardId);
if (!guilds) {
guilds = new Set();
trackedShards.set(guild.shardId, guilds);
}
guilds.add(guild.id);
}
module.exports = function (channel) {
return (methods) => {
adapters.set(channel.guild.id, methods);
trackClient(channel.client);
trackGuild(channel.guild);
return {
sendPayload(data) {
console.log(channel.guild.shard.status);
// if (channel.guild.shard.status === Status.READY) {
console.log("Sending shard data");
channel.guild.shard.send(data);
return true;
// }
console.log("Unable to send channel payload");
return false;
},
destroy() {
console.log("Destroying adapter");
return adapters.delete(channel.guild.id);
},
};
};
}

View File

@ -1,4 +1,6 @@
const Discord = require('discord.js');
const Voice = require("@discordjs/voice");
const adapterCreator = require("./adapter");
require('dotenv').config();
const fetch = require('node-fetch');
const fs = require('fs');
@ -7,13 +9,28 @@ const sqlite3 = require('sqlite3');
const { open } = require('sqlite')
let joinedVoiceChannels = [];
let modules = [];
let commandHandlers = new Map();
let joinedVoiceChannelConnections = new Map();
const bot = new Discord.Client();
let modules = [];
let commandHandlers = new Map();
const player = Voice.createAudioPlayer();
const rest = new Discord.REST({ version: '10' }).setToken(process.env["TOKEN"]);
const bot = new Discord.Client({
intents: [
Discord.GatewayIntentBits.GuildMembers,
Discord.GatewayIntentBits.GuildMessageReactions,
Discord.GatewayIntentBits.GuildMessages,
Discord.GatewayIntentBits.GuildPresences,
Discord.GatewayIntentBits.GuildVoiceStates,
Discord.GatewayIntentBits.Guilds,
Discord.GatewayIntentBits.MessageContent
]
});
async function initDB() {
console.log(__dirname);
console.log(__dirname);
api.db = await open({
filename: process.env["DB_FILE"],
driver: sqlite3.Database
@ -21,9 +38,10 @@ console.log(__dirname);
}
const api = {
player: player,
db: undefined,
queue: undefined,
strings: require('./strings/'+process.env.STRING_SET+'.json'),
strings: require('./strings/' + process.env.STRING_SET + '.json'),
ttsEngines: (() => {
let engines = {};
console.log(`Registering TTS engines...`);
@ -39,6 +57,9 @@ const api = {
announcementVoice: process.env.ANNOUNCEMENT_VOICE,
announcementEngine: undefined,
play: (file) => {
return player.play(Voice.createAudioResource(file));
},
respond: (message, text, voiceText) => {
let toSend = message.member.displayName + ", " + (voiceText ? voiceText : text);
if (message.member.voice.channel) {
@ -49,12 +70,14 @@ const api = {
}
},
getActiveVoiceChannel: () => joinedVoiceChannels[0],
isInVoiceChannel: (channel) => {
return joinedVoiceChannels.includes(channel);
},
getConnectionForVoiceChannel: (channel) => {
return bot.voice.connections.find((conn) => conn.channel === channel);
return joinedVoiceChannelConnections.get(channel);
},
generateVoice: async (string, engine, voice, params) => {
@ -68,15 +91,24 @@ const api = {
joinChannel: async (channel) => {
if (!api.isInVoiceChannel(channel)) {
const res = await channel.join();
const res = Voice.joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
adapterCreator: adapterCreator(channel)
});
res.subscribe(player);
joinedVoiceChannels.push(channel);
joinedVoiceChannelConnections.set(channel, res);
}
},
leaveChannel: async (channel) => {
if (joinedVoiceChannels.includes(channel)) {
let con = joinedVoiceChannelConnections.get(channel);
joinedVoiceChannels = joinedVoiceChannels.filter((chan) => chan !== channel);
await channel.leave();
con.disconnect();
joinedVoiceChannelConnections.delete(channel);
}
},
@ -95,13 +127,14 @@ function registerModules() {
const moduleDirectories = fs.readdirSync('./modules');
moduleDirectories.forEach((dir) => {
if (dir.startsWith('.')) return;
modules.push(require(`./modules/${dir}/index.js`));
modules.push(require(`./modules/${dir}`));
console.log(`Loading ./modules/${dir}/index.js`)
})
modules.forEach((mod) => mod(bot, api));
}
function handleMessage(message) {
console.log(`I got message`);
if (message.content.startsWith(process.env.PREFIX)) {
const args = message.content.split(" ");
const command = args[0].substr(1, args[0].length);
@ -114,9 +147,12 @@ function handleMessage(message) {
api.announcementEngine = api.ttsEngines[process.env.ANNOUNCEMENT_ENGINE];
(async () => {
async function start() {
await initDB();
registerModules();
})()
}
bot.login(process.env.TOKEN);
bot.on('message', handleMessage);
bot.on('messageCreate', handleMessage);
start();

View File

@ -3,7 +3,7 @@ const fs = require('fs');
const path = require('path');
module.exports = async (bot, api) => {
bot.on('message', async (message) => {
bot.on('messageCreate', async (message) => {
if (!message.content.startsWith(process.env.PREFIX)) {
if (message.channel.id == process.env.TTS_CHANNEL) {
let chan=message.member.voice.channel;

View File

@ -3,11 +3,12 @@ const AudioQueue=require('../../AudioQueue.js')
module.exports = function (bot, api) {
bot.on('voiceStateUpdate', async (oldState, newState) => {
console.log("Voice state update");
if (newState.member.user.bot) return;
if (oldState.channel && newState.channel) return;
const channel = oldState.channel || newState.channel;
if (!channel) return;
if (channel.members.array().length < 2) {
if (channel.members.size < 1) {
return await api.leaveChannel(channel);
}
await api.joinChannel(channel);
@ -15,7 +16,7 @@ module.exports = function (bot, api) {
if (!oldState.channel) {
joined = true;
let conn=api.getConnectionForVoiceChannel(channel);
api.queue=new AudioQueue(conn);
api.queue=new AudioQueue(conn, api);
}
let username = newState.member.displayName;

View File

@ -0,0 +1,20 @@
let ChatGPTAPI = null;
module.exports = function (bot, api) {
import("chatgpt").then((mod) => {
ChatGPTAPI = mod.ChatGPTAPI;
});
api.registerCommand('chat', async (args, message) => {
const response = await getChatGPTResponse(message.content.slice(6).trim());
api.respond(message, response);
});
}
async function getChatGPTResponse(prompt) {
const api = new ChatGPTAPI({
apiKey: process.env.OPENAI_API_KEY
})
const res = await api.sendMessage(prompt);
return res.text;
}

View File

@ -10,7 +10,8 @@ module.exports = function (bot, api) {
const quotes = await data.json();
const quote = quotes[Math.floor(Math.random()*quotes.length)];
let chan=message.member.voice.channel;
api.queue.add(__dirname + "/sysmsg.wav");
api.speak(chan, `${quote.author}, on ${quote.medium}: ${quote.quote}`);
// api.queue.add(__dirname + "/sysmsg.wav");
// api.speak(chan, `${quote.author}, on ${quote.medium}: ${quote.quote}`);
api.respond(message, `Here's your quote: ${quote.author}, on ${author.medium}: ${quote.quote}`);
})
}

View File

@ -2,7 +2,7 @@ const printf=require('printf');
module.exports = function (bot, api) {
api.registerCommand('announcevoice', (args, message) => {
let channel = bot.voice.connections.first().channel;
let channel = api.getActiveVoiceChannel();
if (args.length > 3) {
return api.respond(message, printf(api.strings.TOO_MANY_ARGUMENTS));
}

View File

@ -7,7 +7,7 @@ module.exports = function (bot, api) {
const channel = await bot.channels.fetch(process.env.CHANNEL);
await api.joinChannel(channel);
let conn=api.getConnectionForVoiceChannel(channel);
api.queue=new AudioQueue(conn);
api.queue=new AudioQueue(conn, api);
api.queue.add(__dirname + "/../../sysstart.wav");
api.speak(channel, api.strings.WELCOME);
})

4158
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,18 +4,22 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@discordjs/voice": "^0.15.0",
"@google-cloud/text-to-speech": "^3.1.3",
"discord.js": "^12.5.3",
"chatgpt": "^5.1.2",
"discord.js": "^14.8.0",
"dotenv": "^8.2.0",
"fast-levenshtein": "^3.0.0",
"google-tts-api": "^2.0.2",
"is-string-int": "^1.0.1",
"libsodium-wrappers": "^0.7.11",
"microsoft-cognitiveservices-speech-sdk": "^1.16.0",
"node-fetch": "^2.6.1",
"node-google-translate-skidz": "^1.1.2",