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,28 +1,32 @@
module.exports = class AudioQueue { const Voice = require("@discordjs/voice");
constructor(connection) {
this.connection = connection; module.exports = class AudioQueue {
this.queue = []; constructor(connection, api) {
this.current = undefined; this.connection = connection;
} this.api = api;
playNext() { this.queue = [];
if (this.queue.length == 0) { this.current = undefined;
this.current = undefined; }
return; playNext() {
} if (this.queue.length == 0) {
this.current = this.connection.play(this.queue[0]); this.current = undefined;
this.current.on('speaking', (val) => {if (val == 0) this.handleStop(this.current)}); return;
} }
handleStop(current) { this.current = this.api.play(this.queue[0]);
this.queue.shift(); this.api.player.on(Voice.AudioPlayerStatus.Idle, this.handleStop.bind(this));
this.playNext(); }
} handleStop(current) {
add(element) { console.log(`Handling stop`);
this.queue.push(element); this.queue.shift();
if (this.queue.length == 1) this.playNext(); this.playNext();
} }
flush() { add(element) {
this.current.setVolume(0); this.queue.push(element);
this.queue=[]; if (this.queue.length == 1) this.playNext();
this.playNext(); }
} flush() {
this.current.setVolume(0);
this.queue=[];
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 Discord = require('discord.js');
const Voice = require("@discordjs/voice");
const adapterCreator = require("./adapter");
require('dotenv').config(); require('dotenv').config();
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const fs = require('fs'); const fs = require('fs');
@ -7,13 +9,28 @@ const sqlite3 = require('sqlite3');
const { open } = require('sqlite') const { open } = require('sqlite')
let joinedVoiceChannels = []; let joinedVoiceChannels = [];
let modules = []; let joinedVoiceChannelConnections = new Map();
let commandHandlers = 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() { async function initDB() {
console.log(__dirname); console.log(__dirname);
api.db = await open({ api.db = await open({
filename: process.env["DB_FILE"], filename: process.env["DB_FILE"],
driver: sqlite3.Database driver: sqlite3.Database
@ -21,9 +38,10 @@ console.log(__dirname);
} }
const api = { const api = {
player: player,
db: undefined, db: undefined,
queue: undefined, queue: undefined,
strings: require('./strings/'+process.env.STRING_SET+'.json'), strings: require('./strings/' + process.env.STRING_SET + '.json'),
ttsEngines: (() => { ttsEngines: (() => {
let engines = {}; let engines = {};
console.log(`Registering TTS engines...`); console.log(`Registering TTS engines...`);
@ -39,6 +57,9 @@ const api = {
announcementVoice: process.env.ANNOUNCEMENT_VOICE, announcementVoice: process.env.ANNOUNCEMENT_VOICE,
announcementEngine: undefined, announcementEngine: undefined,
play: (file) => {
return player.play(Voice.createAudioResource(file));
},
respond: (message, text, voiceText) => { respond: (message, text, voiceText) => {
let toSend = message.member.displayName + ", " + (voiceText ? voiceText : text); let toSend = message.member.displayName + ", " + (voiceText ? voiceText : text);
if (message.member.voice.channel) { if (message.member.voice.channel) {
@ -49,12 +70,14 @@ const api = {
} }
}, },
getActiveVoiceChannel: () => joinedVoiceChannels[0],
isInVoiceChannel: (channel) => { isInVoiceChannel: (channel) => {
return joinedVoiceChannels.includes(channel); return joinedVoiceChannels.includes(channel);
}, },
getConnectionForVoiceChannel: (channel) => { getConnectionForVoiceChannel: (channel) => {
return bot.voice.connections.find((conn) => conn.channel === channel); return joinedVoiceChannelConnections.get(channel);
}, },
generateVoice: async (string, engine, voice, params) => { generateVoice: async (string, engine, voice, params) => {
@ -68,15 +91,24 @@ const api = {
joinChannel: async (channel) => { joinChannel: async (channel) => {
if (!api.isInVoiceChannel(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); joinedVoiceChannels.push(channel);
joinedVoiceChannelConnections.set(channel, res);
} }
}, },
leaveChannel: async (channel) => { leaveChannel: async (channel) => {
if (joinedVoiceChannels.includes(channel)) { if (joinedVoiceChannels.includes(channel)) {
let con = joinedVoiceChannelConnections.get(channel);
joinedVoiceChannels = joinedVoiceChannels.filter((chan) => chan !== 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'); const moduleDirectories = fs.readdirSync('./modules');
moduleDirectories.forEach((dir) => { moduleDirectories.forEach((dir) => {
if (dir.startsWith('.')) return; if (dir.startsWith('.')) return;
modules.push(require(`./modules/${dir}/index.js`)); modules.push(require(`./modules/${dir}`));
console.log(`Loading ./modules/${dir}/index.js`) console.log(`Loading ./modules/${dir}/index.js`)
}) })
modules.forEach((mod) => mod(bot, api)); modules.forEach((mod) => mod(bot, api));
} }
function handleMessage(message) { function handleMessage(message) {
console.log(`I got message`);
if (message.content.startsWith(process.env.PREFIX)) { if (message.content.startsWith(process.env.PREFIX)) {
const args = message.content.split(" "); const args = message.content.split(" ");
const command = args[0].substr(1, args[0].length); const command = args[0].substr(1, args[0].length);
@ -114,9 +147,12 @@ function handleMessage(message) {
api.announcementEngine = api.ttsEngines[process.env.ANNOUNCEMENT_ENGINE]; api.announcementEngine = api.ttsEngines[process.env.ANNOUNCEMENT_ENGINE];
(async () => {
async function start() {
await initDB(); await initDB();
registerModules(); registerModules();
})() }
bot.login(process.env.TOKEN); bot.login(process.env.TOKEN);
bot.on('message', handleMessage); bot.on('messageCreate', handleMessage);
start();

View File

@ -1,49 +1,49 @@
const printf=require('printf'); const printf=require('printf');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
module.exports = async (bot, api) => { module.exports = async (bot, api) => {
bot.on('message', async (message) => { bot.on('messageCreate', async (message) => {
if (!message.content.startsWith(process.env.PREFIX)) { if (!message.content.startsWith(process.env.PREFIX)) {
if (message.channel.id == process.env.TTS_CHANNEL) { if (message.channel.id == process.env.TTS_CHANNEL) {
let chan=message.member.voice.channel; let chan=message.member.voice.channel;
let userRow = await api.db.get('select * from TTSPreferences where user_id=?', message.author.id); let userRow = await api.db.get('select * from TTSPreferences where user_id=?', message.author.id);
if (!userRow) { if (!userRow) {
await api.db.run('insert into TTSPreferences (user_id,engine,voice) values (?,?,?)', [message.author.id, api.announcementEngine.shortName, api.announcementVoice]); await api.db.run('insert into TTSPreferences (user_id,engine,voice) values (?,?,?)', [message.author.id, api.announcementEngine.shortName, api.announcementVoice]);
userRow = await api.db.get('select * from TTSPreferences where user_id=?', message.author.id); userRow = await api.db.get('select * from TTSPreferences where user_id=?', message.author.id);
} }
if (api.ttsEngines[userRow.engine]) { if (api.ttsEngines[userRow.engine]) {
api.speak(chan,message.content, api.ttsEngines[userRow.engine], userRow.voice) api.speak(chan,message.content, api.ttsEngines[userRow.engine], userRow.voice)
} else { } else {
} }
} }
} }
}); });
api.registerCommand('myvoice', async (args, message) => { api.registerCommand('myvoice', async (args, message) => {
let userEngine, userVoice; let userEngine, userVoice;
if (args.length > 3) { if (args.length > 3) {
return api.respond(message, printf(api.strings.TOO_MANY_ARGUMENTS)); return api.respond(message, printf(api.strings.TOO_MANY_ARGUMENTS));
} }
if (api.ttsEngines[args[1]]) { if (api.ttsEngines[args[1]]) {
userEngine = args[1]; userEngine = args[1];
if (api.ttsEngines[userEngine].validateVoice(args[2].toLowerCase())) { if (api.ttsEngines[userEngine].validateVoice(args[2].toLowerCase())) {
userVoice = args[2].toLowerCase(); userVoice = args[2].toLowerCase();
api.respond(message, printf(api.strings.USER_VOICE_CHANGED, userVoice, api.ttsEngines[userEngine].longName)); api.respond(message, printf(api.strings.USER_VOICE_CHANGED, userVoice, api.ttsEngines[userEngine].longName));
} else { } else {
userVoice = api.ttsEngines[userEngine].getDefaultVoice(); userVoice = api.ttsEngines[userEngine].getDefaultVoice();
api.respond(message, printf(api.strings.INVALID_VOICE, userVoice, api.ttsEngines[userEngine].longName)); api.respond(message, printf(api.strings.INVALID_VOICE, userVoice, api.ttsEngines[userEngine].longName));
} }
await api.db.run('update TTSPreferences set engine=?, voice=? where user_id=?', userEngine, userVoice, message.author.id); await api.db.run('update TTSPreferences set engine=?, voice=? where user_id=?', userEngine, userVoice, message.author.id);
} else { } else {
api.respond(message, printf(api.strings.INVALID_ENGINE, args[1])); api.respond(message, printf(api.strings.INVALID_ENGINE, args[1]));
} }
}); });
api.registerCommand('random', async (args, message) => { api.registerCommand('random', async (args, message) => {
const files = fs.readdirSync(process.env["VOICE_TMP_PATH"]); const files = fs.readdirSync(process.env["VOICE_TMP_PATH"]);
const rnd = files[Math.floor(Math.random()*files.length)]; const rnd = files[Math.floor(Math.random()*files.length)];
console.log(rnd); console.log(rnd);
api.queue.add(__dirname + "/../../sysmsg.wav"); api.queue.add(__dirname + "/../../sysmsg.wav");
api.queue.add(process.env["VOICE_TMP_PATH"] + "/" + rnd); api.queue.add(process.env["VOICE_TMP_PATH"] + "/" + rnd);
}); });
} }

View File

@ -1,32 +1,33 @@
const printf=require('printf'); const printf=require('printf');
const AudioQueue=require('../../AudioQueue.js') const AudioQueue=require('../../AudioQueue.js')
module.exports = function (bot, api) { module.exports = function (bot, api) {
bot.on('voiceStateUpdate', async (oldState, newState) => { bot.on('voiceStateUpdate', async (oldState, newState) => {
if (newState.member.user.bot) return; console.log("Voice state update");
if (oldState.channel && newState.channel) return; if (newState.member.user.bot) return;
const channel = oldState.channel || newState.channel; if (oldState.channel && newState.channel) return;
if (!channel) return; const channel = oldState.channel || newState.channel;
if (channel.members.array().length < 2) { if (!channel) return;
return await api.leaveChannel(channel); if (channel.members.size < 1) {
} return await api.leaveChannel(channel);
await api.joinChannel(channel); }
let joined = false; await api.joinChannel(channel);
if (!oldState.channel) { let joined = false;
joined = true; if (!oldState.channel) {
let conn=api.getConnectionForVoiceChannel(channel); joined = true;
api.queue=new AudioQueue(conn); let conn=api.getConnectionForVoiceChannel(channel);
} api.queue=new AudioQueue(conn, api);
}
let username = newState.member.displayName;
let str = ""; let username = newState.member.displayName;
if (!joined) { let str = "";
str = printf(api.strings.USER_LEFT, username); if (!joined) {
} else { str = printf(api.strings.USER_LEFT, username);
str = printf(api.strings.USER_JOINED, username); } else {
} str = printf(api.strings.USER_JOINED, username);
const filepath = await api.generateVoice(str, api.announcementEngine, api.announcementVoice); }
api.queue.add(__dirname + "/sysmsg.wav"); const filepath = await api.generateVoice(str, api.announcementEngine, api.announcementVoice);
api.queue.add(filepath); api.queue.add(__dirname + "/sysmsg.wav");
}) api.queue.add(filepath);
} })
}

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

@ -1,16 +1,17 @@
const fetch = require('node-fetch'); const fetch = require('node-fetch');
module.exports = function (bot, api) { module.exports = function (bot, api) {
api.registerCommand('randomquote', async (args, message) => { api.registerCommand('randomquote', async (args, message) => {
const data = await fetch(process.env["QDB_URL"], { const data = await fetch(process.env["QDB_URL"], {
headers: { headers: {
Authorization: 'Basic ' + Buffer.from(`${process.env["QDB_USER"]}:${process.env["QDB_PASS"]}`).toString('base64') Authorization: 'Basic ' + Buffer.from(`${process.env["QDB_USER"]}:${process.env["QDB_PASS"]}`).toString('base64')
} }
}); });
const quotes = await data.json(); const quotes = await data.json();
const quote = quotes[Math.floor(Math.random()*quotes.length)]; const quote = quotes[Math.floor(Math.random()*quotes.length)];
let chan=message.member.voice.channel; let chan=message.member.voice.channel;
api.queue.add(__dirname + "/sysmsg.wav"); // api.queue.add(__dirname + "/sysmsg.wav");
api.speak(chan, `${quote.author}, on ${quote.medium}: ${quote.quote}`); // 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

@ -1,23 +1,23 @@
const printf=require('printf'); const printf=require('printf');
module.exports = function (bot, api) { module.exports = function (bot, api) {
api.registerCommand('announcevoice', (args, message) => { api.registerCommand('announcevoice', (args, message) => {
let channel = bot.voice.connections.first().channel; let channel = api.getActiveVoiceChannel();
if (args.length > 3) { if (args.length > 3) {
return api.respond(message, printf(api.strings.TOO_MANY_ARGUMENTS)); return api.respond(message, printf(api.strings.TOO_MANY_ARGUMENTS));
} }
if (api.ttsEngines[args[1]]) { if (api.ttsEngines[args[1]]) {
api.announcementEngine = api.ttsEngines[args[1]]; api.announcementEngine = api.ttsEngines[args[1]];
if (api.announcementEngine.validateVoice(args[2])) { if (api.announcementEngine.validateVoice(args[2])) {
api.announcementVoice = args[2]; api.announcementVoice = args[2];
api.respond(message, printf(api.strings.SYSTEM_VOICE_CHANGED, api.announcementVoice, api.announcementEngine.longName)); api.respond(message, printf(api.strings.SYSTEM_VOICE_CHANGED, api.announcementVoice, api.announcementEngine.longName));
} else { } else {
api.announcementVoice = api.announcementEngine.getDefaultVoice(); api.announcementVoice = api.announcementEngine.getDefaultVoice();
api.respond(message, printf(api.strings.INVALID_VOICE, api.announcementVoice, api.announcementEngine.longName)); api.respond(message, printf(api.strings.INVALID_VOICE, api.announcementVoice, api.announcementEngine.longName));
} }
} else { } else {
api.respond(message, printf(api.strings.INVALID_ENGINE, args[1])); api.respond(message, printf(api.strings.INVALID_ENGINE, args[1]));
} }
}); });
api.registerCommand('flush',()=>api.queue.flush()); api.registerCommand('flush',()=>api.queue.flush());
} }

View File

@ -1,14 +1,14 @@
const AudioQueue=require('../../AudioQueue.js') const AudioQueue=require('../../AudioQueue.js')
module.exports = function (bot, api) { module.exports = function (bot, api) {
bot.on('ready', async () => { bot.on('ready', async () => {
console.log("Bot initialized and listening"); console.log("Bot initialized and listening");
const guild = await bot.guilds.fetch(process.env.GUILD); const guild = await bot.guilds.fetch(process.env.GUILD);
const channel = await bot.channels.fetch(process.env.CHANNEL); const channel = await bot.channels.fetch(process.env.CHANNEL);
await api.joinChannel(channel); await api.joinChannel(channel);
let conn=api.getConnectionForVoiceChannel(channel); let conn=api.getConnectionForVoiceChannel(channel);
api.queue=new AudioQueue(conn); api.queue=new AudioQueue(conn, api);
api.queue.add(__dirname + "/../../sysstart.wav"); api.queue.add(__dirname + "/../../sysstart.wav");
api.speak(channel, api.strings.WELCOME); api.speak(channel, api.strings.WELCOME);
}) })
} }

10040
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,34 @@
{ {
"name": "tardis-bot", "name": "tardis-bot",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
}, "start": "node index.js"
"keywords": [], },
"author": "", "keywords": [],
"license": "ISC", "author": "",
"dependencies": { "license": "ISC",
"@google-cloud/text-to-speech": "^3.1.3", "dependencies": {
"discord.js": "^12.5.3", "@discordjs/voice": "^0.15.0",
"dotenv": "^8.2.0", "@google-cloud/text-to-speech": "^3.1.3",
"fast-levenshtein": "^3.0.0", "chatgpt": "^5.1.2",
"google-tts-api": "^2.0.2", "discord.js": "^14.8.0",
"is-string-int": "^1.0.1", "dotenv": "^8.2.0",
"microsoft-cognitiveservices-speech-sdk": "^1.16.0", "fast-levenshtein": "^3.0.0",
"node-fetch": "^2.6.1", "google-tts-api": "^2.0.2",
"node-google-translate-skidz": "^1.1.2", "is-string-int": "^1.0.1",
"opusscript": "^0.0.8", "libsodium-wrappers": "^0.7.11",
"printf": "^0.6.1", "microsoft-cognitiveservices-speech-sdk": "^1.16.0",
"sam-js": "^0.1.2", "node-fetch": "^2.6.1",
"sha1": "^1.1.1", "node-google-translate-skidz": "^1.1.2",
"sqlite": "^4.0.21", "opusscript": "^0.0.8",
"sqlite3": "^5.0.2", "printf": "^0.6.1",
"wavefile": "^11.0.0" "sam-js": "^0.1.2",
} "sha1": "^1.1.1",
} "sqlite": "^4.0.21",
"sqlite3": "^5.0.2",
"wavefile": "^11.0.0"
}
}