mirror of
https://github.com/Ninluc/Dat_Boi.git
synced 2025-08-09 23:26:13 +02:00
Compare commits
21 Commits
fdf80eb6c5
...
master
Author | SHA1 | Date | |
---|---|---|---|
e196bb7115 | |||
1c4dcf427e | |||
e9b9e303a5 | |||
764aa7bced | |||
de33c4f63d | |||
c9868d72a1 | |||
15ff9c214a | |||
050f1eb92e | |||
8458095f79 | |||
225e7d0bad | |||
11020e0e87 | |||
90da9c78c5 | |||
ec04dba632 | |||
382830340e | |||
fed52827e8 | |||
89c5082f11 | |||
05fabcf65b | |||
76b76738c2 | |||
b17a8a90b3 | |||
1a9f533a12 | |||
6a5cc045ee |
@ -6,4 +6,4 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send webhook to portainer
|
||||
run: "curl -X POST -H 'Content-Type: application/json' -d '' http://172.23.0.1:20000/api/stacks/webhooks/6dbf44ed-eab0-4d52-a633-309d21ffd8c1"
|
||||
run: "curl -X POST -H 'Content-Type: application/json' -d '' http://172.23.0.1:20000/api/stacks/webhooks/0012fba5-2fc7-40e7-92d8-c3d14c7deae4"
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -7,4 +7,7 @@ package-lock.json
|
||||
.idea/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env
|
||||
|
||||
recordings/*
|
||||
tts.mp3
|
16
Dockerfile
16
Dockerfile
@ -10,11 +10,23 @@ ARG NODE_VERSION=18.4.0
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
|
||||
# Set the timezone
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ=Europe/Brussels
|
||||
|
||||
# Use production node environment by default.
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install python for dependencies that require it.
|
||||
RUN apk update && apk add \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
ffmpeg \
|
||||
curl
|
||||
|
||||
# Download dependencies as a separate step to take advantage of Docker's caching.
|
||||
# Leverage a cache mount to /root/.npm to speed up subsequent builds.
|
||||
# Leverage a bind mounts to package.json and package-lock.json to avoid having to copy them into
|
||||
@ -23,9 +35,9 @@ RUN --mount=type=bind,source=package.json,target=package.json \
|
||||
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||
--mount=type=cache,target=/root/.npm \
|
||||
npm ci
|
||||
|
||||
|
||||
# Run the application as a non-root user.
|
||||
USER node
|
||||
#USER node
|
||||
|
||||
# Copy the rest of the source files into the image.
|
||||
COPY . .
|
||||
|
@ -1,5 +1,4 @@
|
||||
const { MessageEmbed } = require("discord.js");
|
||||
const config = require("../../botconfig/config.json");
|
||||
const ee = require("../../botconfig/embed.json");
|
||||
const misc = require("../../botconfig/misc.json");
|
||||
const { delay } = require("../../handlers/functions");
|
||||
|
@ -13,6 +13,10 @@ services:
|
||||
context: .
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DISCORD_TOKEN: ${DISCORD_TOKEN}
|
||||
OPENAI_TOKEN: ${OPENAI_TOKEN}
|
||||
restart: unless-stopped
|
||||
pull_policy: build
|
||||
|
||||
# The commented out section below is an example of how to define a PostgreSQL
|
||||
# database that your application can use. `depends_on` tells Docker Compose to
|
||||
|
@ -1,5 +1,4 @@
|
||||
const { MessageEmbed } = require("discord.js");
|
||||
const config = require("../botconfig/config.json");
|
||||
const ee = require("../botconfig/embed.json");
|
||||
module.exports = {
|
||||
name: "dm",
|
||||
|
@ -1,124 +1,124 @@
|
||||
const fs = require("fs");
|
||||
const { MessageEmbed } = require("discord.js");
|
||||
const config = require("../botconfig/config.json");
|
||||
const ee = require("../botconfig/embed.json");
|
||||
const { choose } = require("../handlers/functions");
|
||||
const { createAudioPlayer, joinVoiceChannel, createAudioResource, StreamType } = require('@discordjs/voice');
|
||||
const { VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
// const fs = require("fs");
|
||||
// const { MessageEmbed } = require("discord.js");
|
||||
// const config = require("../botconfig/config.json");
|
||||
// const ee = require("../botconfig/embed.json");
|
||||
// const { choose } = require("../handlers/functions");
|
||||
// const { createAudioPlayer, joinVoiceChannel, createAudioResource, StreamType } = require('@discordjs/voice');
|
||||
// const { VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
|
||||
const player = createAudioPlayer();
|
||||
// const player = createAudioPlayer();
|
||||
|
||||
const ffmpeg = require("ffmpeg-static");
|
||||
// const ffmpeg = require("ffmpeg-static");
|
||||
|
||||
module.exports = {
|
||||
name: "joinvc",
|
||||
isPrivate: false,
|
||||
usage: "joinvc <ID_DE_LA_VOC>",
|
||||
description: "Rejoins le salon vocal spécifié à l'aide de son id (activer le mode développeur → clic droit sur la voc → copier l'identifiant).",
|
||||
run: async (client, message, text, args) => {
|
||||
try {
|
||||
if (!args[0]) {
|
||||
message.channel.send({embeds: [
|
||||
new MessageEmbed()
|
||||
.setColor(ee.wrongcolor)
|
||||
.setTitle(`❌ ERREUR | Pas assez d'arguments`)
|
||||
.setDescription("`[help joinvc` pour plus d'informations")
|
||||
]})
|
||||
return;
|
||||
}
|
||||
// module.exports = {
|
||||
// name: "joinvc",
|
||||
// isPrivate: false,
|
||||
// usage: "joinvc <ID_DE_LA_VOC>",
|
||||
// description: "Rejoins le salon vocal spécifié à l'aide de son id (activer le mode développeur → clic droit sur la voc → copier l'identifiant).",
|
||||
// run: async (client, message, text, args) => {
|
||||
// try {
|
||||
// if (!args[0]) {
|
||||
// message.channel.send({embeds: [
|
||||
// new MessageEmbed()
|
||||
// .setColor(ee.wrongcolor)
|
||||
// .setTitle(`❌ ERREUR | Pas assez d'arguments`)
|
||||
// .setDescription("`[help joinvc` pour plus d'informations")
|
||||
// ]})
|
||||
// return;
|
||||
// }
|
||||
|
||||
let channel = client.channels.cache.find(channel => channel.id == `${args[0]}`)
|
||||
// let channel = client.channels.cache.find(channel => channel.id == `${args[0]}`)
|
||||
|
||||
if (
|
||||
!channel || !channel.isVoice()
|
||||
|| !channel.permissionsFor(channel.guild.me).has("CONNECT")
|
||||
|| !channel.permissionsFor(channel.guild.me).has("SPEAK")
|
||||
) {
|
||||
// message.react("❌")
|
||||
return message.channel.send({embeds : [
|
||||
new MessageEmbed()
|
||||
.setColor(ee.wrongcolor)
|
||||
.setTitle(`❌ ERREUR | Pas de voc trouvée :(`)]}
|
||||
);
|
||||
}
|
||||
// if (
|
||||
// !channel || !channel.isVoice()
|
||||
// || !channel.permissionsFor(channel.guild.members.me).has("CONNECT")
|
||||
// || !channel.permissionsFor(channel.guild.members.me).has("SPEAK")
|
||||
// ) {
|
||||
// // message.react("❌")
|
||||
// return message.channel.send({embeds : [
|
||||
// new MessageEmbed()
|
||||
// .setColor(ee.wrongcolor)
|
||||
// .setTitle(`❌ ERREUR | Pas de voc trouvée :(`)]}
|
||||
// );
|
||||
// }
|
||||
|
||||
player.on('error', error => {
|
||||
// subscription.unsubscribe()
|
||||
// player.on('error', error => {
|
||||
// // subscription.unsubscribe()
|
||||
|
||||
if (connection.state.status != "destroyed") {
|
||||
connection.destroy();
|
||||
}
|
||||
});
|
||||
// if (connection.state.status != "destroyed") {
|
||||
// connection.destroy();
|
||||
// }
|
||||
// });
|
||||
|
||||
player.on('idle', () => {
|
||||
// subscription.unsubscribe()
|
||||
// player.on('idle', () => {
|
||||
// // subscription.unsubscribe()
|
||||
|
||||
if (connection.state.status != "destroyed") {
|
||||
connection.destroy();
|
||||
}
|
||||
console.log('Info : Ended track');
|
||||
});
|
||||
// if (connection.state.status != "destroyed") {
|
||||
// connection.destroy();
|
||||
// }
|
||||
// console.log('Info : Ended track');
|
||||
// });
|
||||
|
||||
|
||||
let videos = fs.readdirSync(`./sounds/`).filter((file) => file.endsWith(".mp3"))
|
||||
// let videos = fs.readdirSync(`./sounds/`).filter((file) => file.endsWith(".mp3"))
|
||||
|
||||
// voiceState.setSelfMute(0);
|
||||
// var channel = voiceState.channel
|
||||
const connection = joinVoiceChannel({
|
||||
channelId: channel.id,
|
||||
guildId: channel.guild.id,
|
||||
adapterCreator: channel.guild.voiceAdapterCreator,
|
||||
});
|
||||
// const subscription = connection.subscribe(player);
|
||||
var rdVideoLink = choose(videos)
|
||||
// // voiceState.setSelfMute(0);
|
||||
// // var channel = voiceState.channel
|
||||
// const connection = joinVoiceChannel({
|
||||
// channelId: channel.id,
|
||||
// guildId: channel.guild.id,
|
||||
// adapterCreator: channel.guild.voiceAdapterCreator,
|
||||
// });
|
||||
// // const subscription = connection.subscribe(player);
|
||||
// var rdVideoLink = choose(videos)
|
||||
|
||||
try {
|
||||
connection.on(VoiceConnectionStatus.Ready, async() => {
|
||||
connection;
|
||||
let subscription = connection.subscribe(player);
|
||||
// try {
|
||||
// connection.on(VoiceConnectionStatus.Ready, async() => {
|
||||
// connection;
|
||||
// let subscription = connection.subscribe(player);
|
||||
|
||||
const resource = createAudioResource("./sounds/" + rdVideoLink, {
|
||||
inputType: StreamType.Arbitrary
|
||||
});
|
||||
resource.playStream.on("finish", () => {
|
||||
setTimeout(() => {
|
||||
// subscription.unsubscribe()
|
||||
if (connection.state.status != "destroyed") {
|
||||
connection.destroy();
|
||||
}
|
||||
}, 2000)
|
||||
})
|
||||
// const resource = createAudioResource("./sounds/" + rdVideoLink, {
|
||||
// inputType: StreamType.Arbitrary
|
||||
// });
|
||||
// resource.playStream.on("finish", () => {
|
||||
// setTimeout(() => {
|
||||
// // subscription.unsubscribe()
|
||||
// if (connection.state.status != "destroyed") {
|
||||
// connection.destroy();
|
||||
// }
|
||||
// }, 2000)
|
||||
// })
|
||||
|
||||
// if (subscription) {
|
||||
// // Unsubscribe after 5 seconds (stop playing audio on the voice connection)
|
||||
// setTimeout(() => subscription.unsubscribe(), 5_000);
|
||||
// }
|
||||
// // if (subscription) {
|
||||
// // // Unsubscribe after 5 seconds (stop playing audio on the voice connection)
|
||||
// // setTimeout(() => subscription.unsubscribe(), 5_000);
|
||||
// // }
|
||||
|
||||
player.play(resource);
|
||||
})
|
||||
// player.play(resource);
|
||||
// })
|
||||
|
||||
|
||||
|
||||
// setTimeout(() => {
|
||||
// subscription.unsubscribe()
|
||||
// if (connection.state.status != "destroyed") {
|
||||
// connection.destroy();
|
||||
// }
|
||||
// }, 60 * 1000)
|
||||
// // setTimeout(() => {
|
||||
// // subscription.unsubscribe()
|
||||
// // if (connection.state.status != "destroyed") {
|
||||
// // connection.destroy();
|
||||
// // }
|
||||
// // }, 60 * 1000)
|
||||
|
||||
message.channel.send(`Je joue *${rdVideoLink.split('.')[0]}* dans le salon ${channel} du serveur **${channel.guild.name}**`);
|
||||
} catch (error) {
|
||||
console.log(error.message)
|
||||
}
|
||||
// message.channel.send(`Je joue *${rdVideoLink.split('.')[0]}* dans le salon ${channel} du serveur **${channel.guild.name}**`);
|
||||
// } catch (error) {
|
||||
// console.log(error.message)
|
||||
// }
|
||||
|
||||
} catch (e) {
|
||||
console.log(String(e.stack).bgRed);
|
||||
return message.channel.send({embeds : [
|
||||
new MessageEmbed()
|
||||
.setColor(ee.wrongcolor)
|
||||
.setTitle(`❌ ERREUR | Une erreur est survenue : `)
|
||||
.setDescription(`\`\`\`${e.stack}\`\`\``)]}
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
// } catch (e) {
|
||||
// console.log(String(e.stack).bgRed);
|
||||
// return message.channel.send({embeds : [
|
||||
// new MessageEmbed()
|
||||
// .setColor(ee.wrongcolor)
|
||||
// .setTitle(`❌ ERREUR | Une erreur est survenue : `)
|
||||
// .setDescription(`\`\`\`${e.stack}\`\`\``)]}
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// };
|
||||
|
@ -1,87 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const config = require("../../botconfig/config.json"); //loading config file with token and prefix, and settings
|
||||
const ee = require("../../botconfig/embed.json"); //Loading all embed settings like color footertext and icon ...
|
||||
const Discord = require("discord.js"); //this is the official discord.js wrapper for the Discord Api, which we use!
|
||||
const { sendNinluc } = require("../../handlers/functions.js")
|
||||
const { createAudioPlayer, joinVoiceChannel, createAudioResource, StreamType } = require('@discordjs/voice');
|
||||
const { VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
|
||||
const player = createAudioPlayer();
|
||||
|
||||
const ffmpeg = require("ffmpeg-static");
|
||||
|
||||
// const { generateDependencyReport } = require('@discordjs/voice');
|
||||
// console.log(generateDependencyReport().blue);
|
||||
|
||||
module.exports = async (client, oldState, voiceState) => {
|
||||
try {
|
||||
|
||||
if (voiceState === null || voiceState.channel === null || !voiceState.guild || oldState.channel == voiceState.channel || voiceState.channel.full || !voiceState.channel.joinable) return;
|
||||
if (!voiceState.channel.permissionsFor(voiceState.guild.me).has("CONNECT") || !voiceState.channel.permissionsFor(voiceState.guild.me).has("SPEAK")) {return;}
|
||||
|
||||
player.on('error', error => {
|
||||
// subscription.unsubscribe()
|
||||
connection.destroy();
|
||||
console.error('Error:', error.message, 'with track', error.resource.metadata.title);
|
||||
});
|
||||
|
||||
// Si c'est du bot alors se démute
|
||||
if (voiceState.member.user.id === client.user.id && voiceState.mute) {
|
||||
// Si il sait se demute
|
||||
if (voiceState.channel.permissionsFor(voiceState.guild.me).has("MUTE_MEMBERS")) {
|
||||
voiceState.setMute(false);
|
||||
return;
|
||||
} else {return;}
|
||||
}
|
||||
else if (voiceState.member.user.id === client.user.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var finish = true
|
||||
var playRick = false;
|
||||
if (Math.floor(Math.random() * 14) == 0) {playRick = true}
|
||||
|
||||
if (playRick && !voiceState.deaf && finish) {
|
||||
|
||||
let videos = fs.readdirSync(`./sounds/`).filter((file) => file.endsWith(".mp3"))
|
||||
|
||||
// voiceState.setSelfMute(0);
|
||||
// var channel = voiceState.channel
|
||||
const connection = joinVoiceChannel({
|
||||
channelId: voiceState.channel.id,
|
||||
guildId: voiceState.guild.id,
|
||||
adapterCreator: voiceState.guild.voiceAdapterCreator,
|
||||
});
|
||||
// const subscription = connection.subscribe(player);
|
||||
var rdVideoLink = videos[Math.floor(Math.random() * videos.length)]
|
||||
|
||||
try {
|
||||
connection.on(VoiceConnectionStatus.Ready, async() => {
|
||||
connection;
|
||||
let subscription = connection.subscribe(player);
|
||||
|
||||
const resource = createAudioResource("./sounds/" + rdVideoLink, {
|
||||
inputType: StreamType.Arbitrary
|
||||
});
|
||||
resource.playStream.on("finish", () => {
|
||||
setTimeout(() => {
|
||||
subscription.unsubscribe()
|
||||
connection.destroy();
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
player.play(resource);
|
||||
})
|
||||
|
||||
sendNinluc(client, `Je joue *${rdVideoLink.split('.')[0]}* dans le salon ${voiceState.channel} du serveur **${voiceState.guild.name}**`);
|
||||
} catch (error) {
|
||||
console.log(error.message)
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e.stack)
|
||||
}
|
||||
}
|
439
events/guild/voiceStateUpdate.js.deactivate
Normal file
439
events/guild/voiceStateUpdate.js.deactivate
Normal file
@ -0,0 +1,439 @@
|
||||
const fs = require("fs");
|
||||
const {
|
||||
joinVoiceChannel,
|
||||
createAudioPlayer,
|
||||
createAudioResource,
|
||||
AudioPlayerStatus,
|
||||
EndBehaviorType,
|
||||
StreamType,
|
||||
NoSubscriberBehavior,
|
||||
} = require("@discordjs/voice");
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
const prism = require("prism-media");
|
||||
const { getRandomInt, sendNinluc } = require("../../handlers/functions");
|
||||
|
||||
// const { generateDependencyReport } = require('@discordjs/voice');
|
||||
// console.log(generateDependencyReport().blue);
|
||||
|
||||
const CHAT_MAX_LENGTH = (3 * 2) + 1; // 3 LLM reply
|
||||
const SYSTEM_PROMPT =
|
||||
`Tu est DatBoi, un bot discord qui fait semblant d'être une personne réelle. `
|
||||
+ ` Ta personnalité est amicale mais ennuyant, tu aime faire des blagues nulles et ennuyantes de jeunes. `
|
||||
+ ` Tu parle en language familier et te fais passer pour un jeune adulte. Tout le monde qui te parle est ton ami. `
|
||||
+ ` Les entrées de l'utilisateur ont été transcrites par un service de transcription audio, il peut y paraitre fréquemment des erreurs, essaye d'interpreter le texte sans (par exemple Datboi est souvent transcrit "d'être beau" ou encore "Dead boy"). `
|
||||
+ ` Les entrées de l'utilisateur donne le prénom de la personne qui te parle. Tu ne dois pas donner ton nom dans ta réponse.`
|
||||
+ ` Ta sortie sera entièrement donnée à un service de synthèse vocale dans la variable "reply", donc tu ne dois pas dire "voici ma réponse" ou "ma réponse est", tu dois juste donner la réponse. `
|
||||
+ ` Tu dois aussi essayer de donner les mots de façon qu'il puissent être bien prononcés par la synthèse vocale, donc évite les abréviations et les mots trop compliqués. `
|
||||
+ ` Tu ne dois pas dire que tu es un bot, tu dois faire semblant d'être une personne réelle. `
|
||||
+ ` Cela signifie aussi d'éviter les caractères spéciaux comme les émojis, tu peux cependant utiliser de la ponctuation ou des accents. `
|
||||
+ ` Ta réponse doit être courte (max 3 phrases sauf si le contexte ne le permet pas comme une demande d'explication sur un gros sujet) et en français.`
|
||||
+ ` Le contexte ici est que tu rejoins un salon vocal quand il n'y a qu'une seule personne, histoire de pouvoir tranquillement discuter avec elle.`
|
||||
+ ` Voici un peu d'informations sur les personnes qui te parlent (tu n'est pas obligé d'utiliser) : `
|
||||
+ ` - Nicolas : est très beau, tout le monde est amoureux de lui (en blague)`
|
||||
+ ` - Matthias : ton maître (créateur), code beaucoup et pas très bon gamer`
|
||||
+ ` - Régis : va à l'armée et est un fou du gambling`
|
||||
+ ` - Hugo : Deviens fou après avoir "travailé à la poste"`
|
||||
+ ` - Noa : Un beau gosse`
|
||||
+ ` - Jean : un bon gamer`
|
||||
+ `Voici un peu les sujets du moment dans le serveur, tu n'est pas obligé d'utiliser : `
|
||||
+ ` - Le gambling`
|
||||
+ ` - Le jeu vidéo "Rainbow Six Siege" renommé récemment "Rainbow Six X"`
|
||||
+ `Voici un peu plus d'informations sur toi : `
|
||||
+ ` - Tu es un bot discord mais tu prétends d'être une personne réelle`
|
||||
+ ` - Tu fais des blagues du style "Quoi ? - Feur" ou tu lance une vidéo comme un rick roll quand quelqu'un rejoint un salon vocal`
|
||||
+ `Il y a un système de fin de conversation, pour cela, tu dois mettre la variable "end_conversation" à true.`
|
||||
+ `Tu peux enclencher la fin de la conversation si l'utilisateur te demande de le faire, s'il n'a pas l'air de vouloir te parler ou si tu n'as pas d'autres choses à dire.`
|
||||
+ `Au bout de maximum 3 réponses à l'utilisateur, tu devra enclencher la fin de la conversation, avec optionnelement une salutation ("à plus" ou encore "à demain" par exemple).`
|
||||
;
|
||||
|
||||
module.exports = async (client, oldState, voiceState) => {
|
||||
// DOn't do anything
|
||||
if (getRandomInt(5) == 1) return;
|
||||
|
||||
|
||||
var player, isRecording = false, isThinking = false, shouldStop = false, connection;
|
||||
let llmChat = [];
|
||||
llmChat.push({
|
||||
role: "system",
|
||||
content: SYSTEM_PROMPT.replace(/'/g, "’"),
|
||||
});
|
||||
|
||||
try {
|
||||
if (
|
||||
voiceState === null ||
|
||||
voiceState.channel === null ||
|
||||
!voiceState.guild ||
|
||||
oldState.channel == voiceState.channel ||
|
||||
voiceState.channel.full ||
|
||||
!voiceState.channel.joinable
|
||||
)
|
||||
return;
|
||||
if (
|
||||
!voiceState.channel.permissionsFor(voiceState.guild.members.me).has("CONNECT") ||
|
||||
!voiceState.channel.permissionsFor(voiceState.guild.members.me).has("SPEAK")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Si c'est du bot alors se démute
|
||||
if (voiceState.member.user.id === client.user.id && voiceState.mute) {
|
||||
// Si il sait se demute
|
||||
if (
|
||||
voiceState.channel
|
||||
.permissionsFor(voiceState.guild.members.me)
|
||||
.has("MUTE_MEMBERS")
|
||||
) {
|
||||
voiceState.setMute(false);
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (voiceState.member.user.id === client.user.id) {
|
||||
return;
|
||||
}
|
||||
// Not the only reel account in vocal channel
|
||||
if (voiceState.channel.members.filter(m => !m.user.bot && m.id !== client.user.id).size > 1) return;
|
||||
|
||||
// === Connection ===
|
||||
connection = joinVoiceChannel({
|
||||
channelId: voiceState.channel.id,
|
||||
guildId: voiceState.channel.guild.id,
|
||||
adapterCreator: voiceState.channel.guild.voiceAdapterCreator,
|
||||
selfDeaf: false,
|
||||
selfMute: false,
|
||||
});
|
||||
|
||||
// Fix no sound after idle for more than 60 seconds
|
||||
// https://github.com/discordjs/discord.js/issues/9185#issuecomment-1452514375
|
||||
const networkStateChangeHandler = (oldNetworkState, newNetworkState) => {
|
||||
const newUdp = Reflect.get(newNetworkState, 'udp');
|
||||
clearInterval(newUdp?.keepAliveInterval);
|
||||
}
|
||||
connection.on('stateChange', (oldState, newState) => {
|
||||
const oldNetworking = Reflect.get(oldState, 'networking');
|
||||
const newNetworking = Reflect.get(newState, 'networking');
|
||||
|
||||
oldNetworking?.off('stateChange', networkStateChangeHandler);
|
||||
newNetworking?.on('stateChange', networkStateChangeHandler);
|
||||
|
||||
// If alone in channel, quit
|
||||
if (!voiceState.channel?.members || voiceState.channel.members.filter(m => !m.user.bot && m.id !== client.user.id).size < 1) {
|
||||
shouldStop = true;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
player = createAudioPlayer({
|
||||
behaviors: {
|
||||
noSubscriber: NoSubscriberBehavior.Play, // Stop the player when there are no subscribers
|
||||
},
|
||||
debug: true,
|
||||
});
|
||||
player.on("error", (error) => {
|
||||
stop();
|
||||
console.error(`X Error playing audio: ${error.message}`, "error", 1);
|
||||
});
|
||||
player.on("stateChange", (oldState, newState) => {
|
||||
if (newState.status === AudioPlayerStatus.Idle) {
|
||||
console.log(`> Finished playing audio`);
|
||||
}
|
||||
console.log(`> Player state changed from ${oldState.status} to ${newState.status}`);
|
||||
});
|
||||
player.on("close", () => {
|
||||
console.log(`> Player closed for user ${userId}`, "info", 2);
|
||||
return;
|
||||
});
|
||||
// connection.subscribe(player);
|
||||
|
||||
// while(!shouldStop) {
|
||||
console.log(`> handling recording`);
|
||||
handleRecording(connection, voiceState.channel);
|
||||
// }
|
||||
// return;
|
||||
|
||||
} catch (e) {
|
||||
console.log(e.stack);
|
||||
}
|
||||
|
||||
// === Functions ===
|
||||
function handleRecording(connection, channel) {
|
||||
if (isRecording) return;
|
||||
isRecording = true;
|
||||
|
||||
// If alone in channel, quit
|
||||
if (!channel?.members || channel.members.filter(m => !m.user.bot && m.id !== client.user.id).size < 1) {
|
||||
shouldStop = true;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const receiver = connection.receiver;
|
||||
channel.members.forEach((member) => {
|
||||
if (member.user.bot) return;
|
||||
|
||||
const filePath = `./recordings/${member.user.id}.pcm`;
|
||||
const writeStream = fs.createWriteStream(filePath);
|
||||
const listenStream = receiver.subscribe(member.user.id, {
|
||||
end: {
|
||||
behavior: EndBehaviorType.AfterSilence,
|
||||
duration: 2500,
|
||||
},
|
||||
});
|
||||
|
||||
const opusDecoder = new prism.opus.Decoder({
|
||||
frameSize: 960,
|
||||
channels: 2,
|
||||
rate: 48000,
|
||||
});
|
||||
|
||||
listenStream.pipe(opusDecoder).pipe(writeStream);
|
||||
|
||||
writeStream.on("finish", () => {
|
||||
console.log(`> Audio recorded for ${member.user.username}`);
|
||||
if (!isThinking) {
|
||||
isThinking = true;
|
||||
convertAndHandleFile(filePath, member.user.id, connection, channel);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
isRecording = false;
|
||||
}
|
||||
|
||||
function convertAndHandleFile(filePath, userid, connection, channel) {
|
||||
const mp3Path = filePath.replace(".pcm", ".mp3");
|
||||
ffmpeg(filePath)
|
||||
.inputFormat("s16le")
|
||||
.audioChannels(1)
|
||||
.format("mp3")
|
||||
.on("error", (err) => {
|
||||
console.log(`X Error converting file: ${err.message}`, "error", 1);
|
||||
currentlythinking = false;
|
||||
})
|
||||
.save(mp3Path)
|
||||
.on("end", () => {
|
||||
console.log(`> Converted to MP3: ${mp3Path}`, "info", 2);
|
||||
// Remove the pcm file
|
||||
fs.unlink(filePath, (err) => {if (err != null) {console.log("error deleting cpm file : " + err);}});
|
||||
answerUser(mp3Path, userid, connection, channel);
|
||||
});
|
||||
}
|
||||
|
||||
async function answerUser(fileName, userId, connection, channel) {
|
||||
let transcription = await transcribeAudio(fileName);
|
||||
console.log(`> Transcription: ${transcription}`);
|
||||
|
||||
if (!transcription || transcription.trim().length < 1) {
|
||||
console.error(`X Transcription failed or is empty!`, "error", 1);
|
||||
isThinking = false;
|
||||
handleRecording(connection, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
let llmAnswer = await getLLMAnswer(transcription, userId);
|
||||
console.log(`> LLM Answer: ${llmAnswer.reply} ${llmAnswer.end_conversation ? "(end conversation)" : ""}`);
|
||||
// Fin de la conversation
|
||||
shouldStop = llmAnswer.end_conversation || llmChat.length >= CHAT_MAX_LENGTH || llmAnswer.reply.toLowerCase().includes("à plus");
|
||||
if (!llmAnswer || llmAnswer.length < 1) {
|
||||
console.error(`X LLM Answer failed or is empty!`, "error", 1);
|
||||
isThinking = false;
|
||||
handleRecording(connection, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
await getTTS(llmAnswer.reply);
|
||||
let ttsFile = `tts.mp3`;
|
||||
console.log(`> TTS File: ${ttsFile}`);
|
||||
if (!fs.existsSync(ttsFile)) {
|
||||
console.error(`TTS file ${ttsFile} not found!`);
|
||||
return;
|
||||
}
|
||||
// Play the tts file in the voice channel
|
||||
const resource = createAudioResource(ttsFile, {
|
||||
inputType: StreamType.Arbitrary,
|
||||
inlineVolume: true,
|
||||
});
|
||||
resource.volume.setVolume(1.3);
|
||||
connection;
|
||||
connection.subscribe(player);
|
||||
player.on("idle", () => {
|
||||
isThinking = false;
|
||||
// connection.destroy();
|
||||
if (!shouldStop) {
|
||||
handleRecording(connection, channel);
|
||||
}
|
||||
else {
|
||||
console.log(`> Ending conversation with user ${userId}`);
|
||||
sendConversationToNinluc(userId);
|
||||
stop();
|
||||
}
|
||||
return;
|
||||
});
|
||||
player.play(resource);
|
||||
// player.pause();
|
||||
// player.unpause();
|
||||
}
|
||||
|
||||
function transcribeAudio(filePath) {
|
||||
// Make a call to the API with curl
|
||||
// Example of working curl command:
|
||||
// curl -s "https://speaches.matthiasg.dev/v1/audio/transcriptions" -F "file=@/pathToFile.mp3" -F "model=Infomaniak-AI/faster-whisper-large-v3-turbo"
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const { exec } = require("child_process");
|
||||
exec(`curl -s "https://speaches.matthiasg.dev/v1/audio/transcriptions" -F "file=@${filePath}" -F "model=Systran/faster-whisper-small" -F "language=fr"`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`X Error during transcription: ${error.message}`, "error", 1);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(`X Transcription stderr: ${stderr}`, "error", 1);
|
||||
reject(stderr);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = JSON.parse(stdout);
|
||||
|
||||
// === FILTER OUTPUT ===
|
||||
response.text = response.text.replace(/Sous-titres réalisés par la communauté d'Amara.org/g, "");
|
||||
if (response.text.toLowerCase().includes("je vous remercie d'avoir regardé cette vidéo")) {
|
||||
return ""; // Output is trash, ignore it
|
||||
}
|
||||
|
||||
resolve(response.text || "");
|
||||
} catch (parseError) {
|
||||
console.error(`X Error parsing transcription response: ${parseError.message}`, "error", 1);
|
||||
reject(parseError);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getLLMAnswer(transcription, userId) {
|
||||
// get the name of the user
|
||||
let userName = getUserById(userId);
|
||||
|
||||
transcription = `(${userName}) : ${transcription}`;
|
||||
|
||||
// Add the transcription to the llmChat
|
||||
llmChat.push({
|
||||
role: "user",
|
||||
content: transcription.replace(/'/g, "’"),
|
||||
});
|
||||
|
||||
// Make a call to the OpenAI compatible API at https://chat.matthiasg.dev/ollama/chat/completions
|
||||
// With the token in the OPENAI_TOKEN env variable
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const { exec } = require("child_process");
|
||||
|
||||
let chatMessages = JSON.stringify(llmChat);
|
||||
|
||||
exec(`curl -s -X POST "https://chat.matthiasg.dev/ollama/api/chat" -H "Content-Type: application/json" -H "Authorization: Bearer ${process.env.OPENAI_TOKEN}" -d '{"model": "gemma3n:e2b", "stream": false, "messages": ${chatMessages}, "format": {"type": "object", "properties": {"reply": {"type": "string"}, "end_conversation": {"type": "boolean"}}, "required": ["reply", "end_conversation"]}}'`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`X Error during LLM request: ${error.message}`, "error", 1);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(`X LLM stderr: ${stderr}`, "error", 1);
|
||||
reject(stderr);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = JSON.parse(stdout);
|
||||
if (!response || !response.message || !response.message.content) {
|
||||
console.error("X Invalid LLM response format", "error", 1);
|
||||
reject(new Error("Invalid LLM response format"));
|
||||
return;
|
||||
}
|
||||
let llmResponse = JSON.parse(response.message.content) || "";
|
||||
// Add the LLM response to the chat history
|
||||
llmChat.push({
|
||||
role: "assistant",
|
||||
content: llmResponse.reply.replace(/'/g, "’"),
|
||||
});
|
||||
resolve(llmResponse);
|
||||
} catch (parseError) {
|
||||
console.error(`X Error parsing LLM response: ${parseError.message}`, "error", 1);
|
||||
reject(parseError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function getUserById(userId) {
|
||||
return [
|
||||
{ id: "613751180413108289", name: "Nicolas" },
|
||||
{ id: "417731861033385985", name: "Matthias" },
|
||||
{ id: "368119137957838849", name: "Régis" },
|
||||
{ id: "516294391439163403", name: "Hugo" },
|
||||
{ id: "344568112932192266", name: "Noa" },
|
||||
{ id: "520687970273984543", name: "Jean" }
|
||||
].map(user => {
|
||||
if (user.id === userId) {
|
||||
return user.name;
|
||||
}
|
||||
}).filter(name => name)[0] || null;
|
||||
}
|
||||
|
||||
function getTTS(text) {
|
||||
// Curl to the OpenAPI compatible API
|
||||
/*
|
||||
curl "https://speaches.matthiasg.dev/v1/audio/speech" -s -H "Content-Type: application/json" \
|
||||
--output audio.mp3 \
|
||||
--data @- << EOF
|
||||
{
|
||||
"input": "Hello World!",
|
||||
"model": "speaches-ai/piper-fr_FR-upmc-medium",
|
||||
"voice": "upmc"
|
||||
}
|
||||
EOF
|
||||
*/
|
||||
|
||||
|
||||
text = text.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"");
|
||||
text = JSON.stringify(text);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const { exec } = require("child_process");
|
||||
exec(`curl -s "https://speaches.matthiasg.dev/v1/audio/speech" -H "Content-Type: application/json" --output tts.mp3 --data @- << EOF\n{"input": ${text}, "model": "speaches-ai/piper-fr_FR-upmc-medium", "voice": "upmc"}\nEOF`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`X Error during TTS request: ${error.message}`, "error", 1);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(`X TTS stderr: ${stderr}`, "error", 1);
|
||||
reject(stderr);
|
||||
return;
|
||||
}
|
||||
resolve("tts.mp3");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
player.stop();
|
||||
//connection.removeAllListeners();
|
||||
try {
|
||||
connection.destroy();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function sendConversationToNinluc(userId) {
|
||||
sendNinluc(client, "Fin de la conversation avec l'utilisateur " + userId);
|
||||
|
||||
llmChat.forEach((message) => {
|
||||
if (message.role === "user") {
|
||||
sendNinluc(client, `(<@${userId}>) : ${message.content}`);
|
||||
} else if (message.role === "assistant") {
|
||||
sendNinluc(client, `DatBoi : ${message.content}`);
|
||||
}
|
||||
// ignore system message
|
||||
})
|
||||
}
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
const { MessageEmbed } = require("discord.js");
|
||||
const config = require("../botconfig/config.json");
|
||||
const ee = require("../botconfig/embed.json");
|
||||
const { choose, isNinluc } = require("../handlers/functions");
|
||||
|
||||
|
724
package-lock.json
generated
724
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,12 +9,16 @@
|
||||
"author": "Tomato#6966 (author of this template), Ninluc",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/opus": "^0.10.0",
|
||||
"@discordjs/voice": "^0.10.0",
|
||||
"ascii-table": "0.0.9",
|
||||
"colors": "^1.4.0",
|
||||
"discord.js": "^13.8.1",
|
||||
"ffmpeg-static": "^4.4.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"moment": "^2.29.4"
|
||||
"moment": "^2.29.4",
|
||||
"prism": "^4.1.2",
|
||||
"prism-media": "^1.3.5"
|
||||
}
|
||||
}
|
||||
|
0
recordings/.gitkeep
Normal file
0
recordings/.gitkeep
Normal file
@ -1,7 +1,5 @@
|
||||
const { MessageEmbed, Message } = require("discord.js");
|
||||
const config = require("../botconfig/config.json");
|
||||
const { MessageEmbed } = require("discord.js");
|
||||
const ee = require("../botconfig/embed.json");
|
||||
const { sendNinluc } = require("../handlers/functions");
|
||||
|
||||
const RAPPEL_CHANNEL_ID = "1296051297262370866";
|
||||
|
||||
|
Reference in New Issue
Block a user