Version 3.0 sortie, fusion avec JujulBot

This commit is contained in:
Angels-dev
2024-08-07 01:09:05 +02:00
parent 696b284b6c
commit 586f68b0df
51 changed files with 2472 additions and 1144 deletions

View File

@@ -1,4 +1,4 @@
FROM node:lts-alpine as base FROM node:lts-alpine
ENV NODE_ENV=production ENV NODE_ENV=production
WORKDIR /usr/src/app WORKDIR /usr/src/app
@@ -12,18 +12,4 @@ COPY . .
RUN chown -R node /usr/src/app RUN chown -R node /usr/src/app
USER node USER node
FROM base as tamiseur
RUN mv .env1 .env
RUN rm .env2 .env3
CMD ["npm", "start"]
FROM base as funky
RUN mv .env2 .env
RUN rm .env1 .env3
CMD ["npm", "start"]
FROM base as groove
RUN mv .env3 .env
RUN rm .env1 .env2
CMD ["npm", "start"] CMD ["npm", "start"]

23
eslint.config.mjs Normal file
View File

@@ -0,0 +1,23 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin"
import tsParser from "@typescript-eslint/parser"
import { FlatCompat } from "@eslint/eslintrc"
import { fileURLToPath } from "node:url"
import path from "node:path"
import js from "@eslint/js"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
})
export default [
...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
{
plugins: { "@typescript-eslint": typescriptEslint },
languageOptions: { parser: tsParser },
rules: { "prefer-const": "off" }
}
]

2312
package-lock.json generated Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "bot_tamiseur", "name": "bot_tamiseur",
"description": "Listen to music and use fun commands with your friends!", "description": "Listen to music and use fun commands with your friends!",
"version": "2.3.1", "version": "3.0.0",
"author": { "author": {
"name": "Zachary Guénot" "name": "Zachary Guénot"
}, },
@@ -10,36 +10,49 @@
"format": "prettier --write .", "format": "prettier --write .",
"start": "ts-node src/index.ts", "start": "ts-node src/index.ts",
"dev": "nodemon -e ts src/index.ts", "dev": "nodemon -e ts src/index.ts",
"build": "ncc build src/index.ts -o dist" "build": "tsc && terser ./dist -o ./dist",
"lint": "eslint src/**/*.ts"
}, },
"//": [
"Garder chalk à la version 4.1.2 pour éviter un bug ESM avec la version >=5.0.0"
],
"dependencies": { "dependencies": {
"@discord-player/equalizer": "^0.2.3", "@discord-player/equalizer": "^0.2.3",
"@discord-player/extractor": "^4.4.7", "@discord-player/extractor": "^4.5.0",
"@discordjs/opus": "^0.9.0",
"@discordjs/voice": "^0.17.0", "@discordjs/voice": "^0.17.0",
"@types/parse-torrent": "^5.8.7", "@types/parse-torrent": "^5.8.7",
"axios": "^1.6.8", "axios": "^1.7.3",
"bufferutil": "^4.0.8", "bufferutil": "^4.0.8",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"discord-player": "^6.6.8", "discord-player": "^6.7.1",
"discord.js": "^14.15.2", "discord-player-youtubei": "^1.2.4",
"discord.js": "^14.15.3",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"jsdom": "^24.0.0", "jsdom": "^24.1.1",
"libsodium-wrappers": "^0.7.13", "libsodium-wrappers": "^0.7.14",
"mariadb": "^3.3.0", "mariadb": "^3.3.1",
"mongoose": "^8.3.5", "mediaplex": "^0.0.9",
"mongoose": "^8.5.2",
"parse-torrent": "^9.1.5", "parse-torrent": "^9.1.5",
"play-dl": "^1.9.7",
"require-all": "^3.0.0", "require-all": "^3.0.0",
"rss-parser": "^3.13.0", "rss-parser": "^3.13.0",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"utf-8-validate": "^6.0.4" "ts-node": "^10.9.2",
"utf-8-validate": "^6.0.4",
"websocket": "^1.0.35"
}, },
"devDependencies": { "devDependencies": {
"@swc/core": "^1.5.7", "@eslint/eslintrc": "^3.1.0",
"eslint": "^9.2.0", "@eslint/js": "^9.8.0",
"nodemon": "^3.1.0", "@swc/core": "^1.7.6",
"prettier": "^3.2.5" "@types/node": "^22.1.0",
"@types/websocket": "^1.0.10",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"eslint": "^9.8.0",
"nodemon": "^3.1.4",
"prettier": "^3.3.3",
"terser": "^5.31.3"
} }
} }

View File

@@ -1,35 +1,45 @@
import { SlashCommandBuilder, ChatInputCommandInteraction, AutocompleteInteraction, EmbedBuilder, inlineCode } from 'discord.js' import { SlashCommandBuilder, ChatInputCommandInteraction, AutocompleteInteraction, ApplicationCommandOptionChoiceData, EmbedBuilder, inlineCode, PermissionFlagsBits } from 'discord.js'
import * as AMP from '../../utils/amp'
import dbGuild from '../../schemas/guild' import dbGuild from '../../schemas/guild'
import * as AMP from '../../utils/amp'
interface ListInstancesResult {
status: string
data: [
Host: {
AvailableInstances: any[]
FriendlyName: string
}
]
}
interface InstanceFields { interface InstanceFields {
name: string name: string
value: string value: string
inline: boolean inline: boolean
} }
interface InstanceResult {
interface failData { status: string
Title: string data: [
Message: string Host
]
} }
interface errorData { interface Host {
error_code: string AvailableInstances: Instance[]
FriendlyName: string
}
interface Instance {
InstanceID: string
FriendlyName: string
Running: boolean
Module: string
Port: number
}
interface FailMsgData {
Title: string
Message: string
}
interface ErrorMsgData {
error_code: string
} }
function failMsg(data: any) { return `La commande a échouée !\n${inlineCode(`${data.Title}: ${data.Message}`)}` } function failMsg(data: FailMsgData) { return `La commande a échouée !\n${inlineCode(`${data.Title}: ${data.Message}`)}` }
function errorMsg(data: any) { return `Y'a eu une erreur !\n${inlineCode(`${data.error_code}`)}` } function errorMsg(data: ErrorMsgData) { return `Y'a eu une erreur !\n${inlineCode(`${data.error_code}`)}` }
export default { export default {
data: new SlashCommandBuilder().setName('amp').setDescription('Accède à mon panel de jeu AMP !') data: new SlashCommandBuilder()
.setName('amp')
.setDescription('Accède à mon panel de jeu AMP !')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addSubcommand(subcommand => subcommand.setName('login').setDescription("Connectez-vous avant d'effectuer une autre commande !") .addSubcommand(subcommand => subcommand.setName('login').setDescription("Connectez-vous avant d'effectuer une autre commande !")
.addStringOption(option => option.setName('username').setDescription("Nom d'Utilisateur").setRequired(true)) .addStringOption(option => option.setName('username').setDescription("Nom d'Utilisateur").setRequired(true))
.addStringOption(option => option.setName('password').setDescription('Mot de Passe').setRequired(true)) .addStringOption(option => option.setName('password').setDescription('Mot de Passe').setRequired(true))
@@ -71,12 +81,12 @@ export default {
} }
else if (session.status === 'error') return interaction.respond([]) else if (session.status === 'error') return interaction.respond([])
let choices: any = [] let choices: ApplicationCommandOptionChoiceData[] = []
let result = await AMP.ADSModule.GetInstances(host, sessionID) let result = await AMP.ADSModule.GetInstances(host, sessionID)
if (result.status === 'success') { if (result.status === 'success') {
let hosts = result.data.result as any[] let hosts = result.data.result as Host[]
hosts.forEach(host => { hosts.forEach(host => {
let instances = host.AvailableInstances as any[] let instances = host.AvailableInstances as Instance[]
instances.forEach(instance => { instances.forEach(instance => {
if (instance.FriendlyName.includes(query)) choices.push({ name: `${host.FriendlyName} - ${instance.FriendlyName}`, value: instance.InstanceID }) if (instance.FriendlyName.includes(query)) choices.push({ name: `${host.FriendlyName} - ${instance.FriendlyName}`, value: instance.InstanceID })
}) })
@@ -105,10 +115,10 @@ export default {
await interaction.deferReply({ ephemeral: true }) await interaction.deferReply({ ephemeral: true })
let details = { let details = {
username: interaction.options.getString('username'), username: interaction.options.getString('username') || '',
password: interaction.options.getString('password'), password: interaction.options.getString('password') || '',
token: interaction.options.getString('otp') || '', rememberMe: interaction.options.getBoolean('remember') || '',
rememberMe: interaction.options.getBoolean('remember') token: interaction.options.getString('otp') || ''
} }
let result = await AMP.Core.Login(host, details) let result = await AMP.Core.Login(host, details)
@@ -146,13 +156,13 @@ export default {
if (interaction.options.getSubcommandGroup() == 'instances') { if (interaction.options.getSubcommandGroup() == 'instances') {
if (interaction.options.getSubcommand() == 'list') { if (interaction.options.getSubcommand() == 'list') {
let result = await AMP.ADSModule.GetInstances(host, sessionID) as ListInstancesResult let result = await AMP.ADSModule.GetInstances(host, sessionID) as InstanceResult
if (result.status === 'success') { if (result.status === 'success') {
await interaction.followUp({ content: `${result.data.length} hôte(s) trouvé(s) !` }) await interaction.followUp({ content: `${result.data.length} hôte(s) trouvé(s) !` })
result.data.forEach(async host => { result.data.forEach(async host => {
let fields = [] as InstanceFields[] let fields = [] as InstanceFields[]
host.AvailableInstances.forEach(instance => { host.AvailableInstances.forEach((instance: Instance) => {
fields.push({ fields.push({
name: instance.FriendlyName, name: instance.FriendlyName,
value: `**Running:** ${instance.Running}\n**Port:** ${instance.Port}\n**Module:** ${instance.Module}`, value: `**Running:** ${instance.Running}\n**Port:** ${instance.Port}\n**Module:** ${instance.Module}`,
@@ -168,8 +178,8 @@ export default {
return await interaction.channel?.send({ embeds: [embed] }) return await interaction.channel?.send({ embeds: [embed] })
}) })
} }
else if (result.status === 'fail') return await interaction.followUp(failMsg(result.data)) else if (result.status === 'fail') return await interaction.followUp(failMsg(result.data as unknown as FailMsgData))
else if (result.status === 'error') return await interaction.followUp(errorMsg(result.data)) else if (result.status === 'error') return await interaction.followUp(errorMsg(result.data as unknown as ErrorMsgData))
} }
else if (interaction.options.getSubcommand() == 'manage') { else if (interaction.options.getSubcommand() == 'manage') {
let instanceID = interaction.options.getString('instance', true) let instanceID = interaction.options.getString('instance', true)

View File

@@ -0,0 +1,37 @@
import { SlashCommandBuilder, EmbedBuilder, ChatInputCommandInteraction, TextChannel, PermissionFlagsBits } from 'discord.js'
module.exports = {
data: new SlashCommandBuilder()
.setName('boost')
.setDescription('Tester le boost du serveur !')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
async execute(interaction: ChatInputCommandInteraction) {
if (interaction.guild?.id !== '796327643783626782') return interaction.reply({ content: 'Non !' })// Jujul Community
let member = interaction.member
if (!member) return console.log(`\u001b[1;31m Aucun membre trouvé !`)
let guild = interaction.guild
if (!guild) return console.log(`\u001b[1;31m Aucun serveur trouvé !`)
let channel = guild.channels.cache.get('924353449930412153') as TextChannel
if (!channel) return console.log(`\u001b[1;31m Aucun channel trouvé avec l'id "924353449930412153" !`)
let boostRole = guild.roles.premiumSubscriberRole
if (!boostRole) return console.log(`\u001b[1;31m Aucun rôle de boost trouvé !`)
if (!guild.members.me) return console.log(`\u001b[1;31m Je ne suis pas sur le serveur !`)
let embed = new EmbedBuilder()
.setColor(guild.members.me.displayHexColor)
.setTitle(`Nouveau boost de ${member.user.username} !`)
.setDescription(`
Merci à toi pour ce boost.\n
Grâce à toi, on a atteint ${guild.premiumSubscriptionCount} boosts !
`)
.setThumbnail(member.user.avatar)
.setTimestamp(new Date())
await channel.send({ embeds: [embed] })
await interaction.reply({ content: 'Va voir dans <#924353449930412153> !' })
}
}

View File

@@ -1,4 +1,4 @@
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder, APIEmbedField } from 'discord.js' import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder, APIEmbedField, PermissionFlagsBits } from 'discord.js'
import dbGuildInit from '../../utils/dbGuildInit' import dbGuildInit from '../../utils/dbGuildInit'
import dbGuild from '../../schemas/guild' import dbGuild from '../../schemas/guild'
@@ -23,6 +23,7 @@ export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('database') .setName('database')
.setDescription('Communicate with the database') .setDescription('Communicate with the database')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addSubcommand(subcommand => subcommand.setName('info').setDescription('Returns information about the current guild')) .addSubcommand(subcommand => subcommand.setName('info').setDescription('Returns information about the current guild'))
.addSubcommand(subcommand => subcommand.setName('init').setDescription('Force initialize an entry for the current guild in the database')) .addSubcommand(subcommand => subcommand.setName('init').setDescription('Force initialize an entry for the current guild in the database'))
.addSubcommand(subcommand => subcommand.setName('edit').setDescription('Modify parameters for the current guild') .addSubcommand(subcommand => subcommand.setName('edit').setDescription('Modify parameters for the current guild')
@@ -44,7 +45,9 @@ export default {
.setDescription(`Guild **${guildProfile.guildName}** (ID: ${guildProfile.guildId})`) .setDescription(`Guild **${guildProfile.guildName}** (ID: ${guildProfile.guildId})`)
.setThumbnail(guildProfile.guildIcon as string) .setThumbnail(guildProfile.guildIcon as string)
.setTimestamp() .setTimestamp()
.addFields(fields as APIEmbedField[]) //.addFields(fields as APIEmbedField[])
// Limit the number of fields to 25
.addFields(fields.slice(0, 25) as APIEmbedField[])
return await interaction.reply({ embeds: [embed] }) return await interaction.reply({ embeds: [embed] })
} else if (interaction.options.getSubcommand() === 'init') { } else if (interaction.options.getSubcommand() === 'init') {

25
src/commands/player/panel.ts Executable file
View File

@@ -0,0 +1,25 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
import { playerGenerate } from '../../utils/player'
import getUptime from '../../utils/getUptime'
import { useQueue } from 'discord-player'
export default {
data: new SlashCommandBuilder()
.setName('panel')
.setDescription('Générer les infos de la lecture en cours.'),
async execute(interaction: ChatInputCommandInteraction) {
let queue = useQueue(interaction.guild?.id ?? '')
if (!queue) return interaction.followUp({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
let guild = interaction.guild
if (!guild) return await interaction.reply({ content: 'Cette commande n\'est pas disponible en message privé.', ephemeral: true })
let client = guild.client
let { embed, components } = await playerGenerate(guild)
if (components && embed.data.footer) embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)} \n ${embed.data.footer.text}` })
else embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)}` })
return interaction.reply({ embeds: [embed] })
}
}

View File

@@ -1,9 +1,8 @@
import { SlashCommandBuilder, ChatInputCommandInteraction, AutocompleteInteraction, GuildMember } from 'discord.js' import { SlashCommandBuilder, ChatInputCommandInteraction, AutocompleteInteraction, GuildMember } from 'discord.js'
import { useMainPlayer, useQueue, QueryType } from 'discord-player' import { useMainPlayer, useQueue, QueryType } from 'discord-player'
import dbGuild from '../../schemas/guild' import dbGuild from '../../schemas/guild'
export interface TrackSearchResult { name: string, value: string } interface TrackSearchResult { name: string, value: string }
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()

19
src/commands/player/queue.ts Executable file
View File

@@ -0,0 +1,19 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js'
import { useQueue } from 'discord-player'
export default {
data: new SlashCommandBuilder()
.setName('queue')
.setDescription("Récupérer la file d'attente."),
async execute(interaction: ChatInputCommandInteraction) {
let queue = useQueue(interaction.guild?.id ?? '')
if (!queue) return interaction.reply({ content: 'Aucune file d\'attente en cours, recherche une musique plutôt !' })
if (!queue.currentTrack) return interaction.reply({ content: 'Aucune musique en cours de lecture.' })
let track = `[${queue.currentTrack.title}](${queue.currentTrack.url})`
let tracks = queue.tracks.map((track, index) => { return `${index + 1}. [${track.title}](${track.url})` })
if (tracks.length === 0) return interaction.reply({ content: `Lecture en cours : ${track} \nAucune musique dans la file d'attente.` })
return interaction.reply({ content: `Lecture en cours : ${track} \nFile d'attente actuelle : \n${tracks.join('\n')}` })
}
}

View File

@@ -1,14 +1,19 @@
import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder, Message, inlineCode } from 'discord.js' import { SlashCommandBuilder, ChatInputCommandInteraction, EmbedBuilder, Message, inlineCode } from 'discord.js'
import https from 'https' import * as Freebox from '../../utils/freebox'
import dbGuild from '../../schemas/guild'
import crypto from 'crypto' import crypto from 'crypto'
import https from 'https'
//import path from 'path' //import path from 'path'
//import fs from 'fs' //import fs from 'fs'
import * as Freebox from '../../utils/freebox' interface ReturnMsgData {
import dbGuild from '../../schemas/guild' status: string
error_code?: string
Title?: string
Message?: string
}
function returnMsg(result: ReturnMsgData) {
function returnMsg(result: any) {
if (result.status === 'fail') return `La commande a échouée !\n${inlineCode(`${result.Title}: ${result.Message}`)}` if (result.status === 'fail') return `La commande a échouée !\n${inlineCode(`${result.Title}: ${result.Message}`)}`
if (result.status === 'error') return `Y'a eu une erreur !\n${inlineCode(`${result.error_code}`)}` if (result.status === 'error') return `Y'a eu une erreur !\n${inlineCode(`${result.error_code}`)}`
} }

View File

@@ -4,11 +4,11 @@ import dbGuildInit from '../../utils/dbGuildInit'
export default { export default {
name: Events.GuildCreate, name: Events.GuildCreate,
async execute(guild: Guild) { async execute(guild: Guild) {
console.log(`Joined ${guild.name} with ${guild.memberCount} members`) console.log(`Joined "${guild.name}" with ${guild.memberCount} members`)
let guildProfile = await dbGuildInit(guild) let guildProfile = await dbGuildInit(guild)
if (!guildProfile) return console.log(`An error occured while initializing database data for **${guild.name}** !`) if (!guildProfile) return console.log(`An error occured while initializing database data for "${guild.name}" !`)
console.log(`Database data for new guild **${guildProfile.guildName}** successfully initialized !`) console.log(`Database data for new guild "${guildProfile.guildName}" successfully initialized !`)
} }
} }

View File

@@ -1,19 +1,44 @@
import { Events, GuildMember } from 'discord.js' import { Events, GuildMember, EmbedBuilder, TextChannel } from 'discord.js'
export default { export default {
name: Events.GuildMemberAdd, name: Events.GuildMemberAdd,
async execute(member: GuildMember) { async execute(member: GuildMember) {
if (member.guild.id !== '1086577543651524699') return if (member.guild.id === '1086577543651524699') { // Salon posé tamisé
member.guild.members.fetch().then(() => { let guild = member.guild
var i = 0
member.guild.members.cache.forEach(async member => { if (!member.user.bot) i++ })
let channel = member.guild.channels.cache.get('1091140609139560508')
if (!channel) return
console.log(channel.name) guild.members.fetch().then(() => {
console.log(`${i} Gens Posés`) var i = 0
channel.setName('Changement...') guild.members.cache.forEach(async member => { if (!member.user.bot) i++ })
channel.setName(`${i} Gens Posés`)
}).catch(console.error) let channel = guild.channels.cache.get('1091140609139560508')
if (!channel) return
channel.setName('Changement...')
channel.setName(`${i} Gens Posés`)
}).catch(console.error)
} else if (member.guild.id === '796327643783626782') { // Jujul Community
let guild = member.guild
let channel = guild.channels.cache.get('837248593609097237') as TextChannel
if (!channel) return console.log(`\u001b[1;31m Aucun channel trouvé avec l'id "837248593609097237" !`)
if (!guild.members.me) return console.log(`\u001b[1;31m Je ne suis pas sur le serveur !`)
let embed = new EmbedBuilder()
.setColor(guild.members.me.displayHexColor)
.setTitle(`Salut ${member.user.username} !`)
.setDescription(`
Bienvenue sur le serveur de **Jujul** !
Nous sommes actuellement ${guild.memberCount} membres !\n
N'hésite pas à aller lire le <#797471924367786004> et à aller te présenter dans <#837138238417141791> !\n
Si tu as des questions,
n'hésite pas à les poser dans le <#837110617315344444> !\n
Bon séjour parmi nous !
`)
.setThumbnail(member.user.avatarURL())
.setTimestamp(new Date())
await channel.send({ embeds: [embed] })
}
} }
} }

View File

@@ -3,17 +3,19 @@ import { Events, GuildMember } from 'discord.js'
export default { export default {
name: Events.GuildMemberRemove, name: Events.GuildMemberRemove,
async execute(member: GuildMember) { async execute(member: GuildMember) {
if (member.guild.id !== '1086577543651524699') return if (member.guild.id === '1086577543651524699') { // Salon posé tamisé
member.guild.members.fetch().then(() => { let guild = member.guild
var i = 0
member.guild.members.cache.forEach(async member => { if (!member.user.bot) i++ })
let channel = member.guild.channels.cache.get('1091140609139560508')
if (!channel) return
console.log(channel.name) guild.members.fetch().then(() => {
console.log(`${i} Gens Posés`) var i = 0
channel.setName('Changement...') guild.members.cache.forEach(async member => { if (!member.user.bot) i++ })
channel.setName(`${i} Gens Posés`)
}).catch(console.error) let channel = guild.channels.cache.get('1091140609139560508')
if (!channel) return
channel.setName('Changement...')
channel.setName(`${i} Gens Posés`)
}).catch(console.error)
}
} }
} }

View File

@@ -0,0 +1,35 @@
import { Events, GuildMember, EmbedBuilder, TextChannel } from 'discord.js'
export default {
name: Events.GuildMemberUpdate,
async execute(oldMember: GuildMember, newMember: GuildMember) {
if (newMember.guild.id === '796327643783626782') { // Jujul Community
let guild = newMember.guild
let channel = guild.channels.cache.get('924353449930412153') as TextChannel
if (!channel) return console.log(`\u001b[1;31m Aucun channel trouvé avec l'id "924353449930412153" !`)
let boostRole = guild.roles.premiumSubscriberRole
if (!boostRole) return console.log(`\u001b[1;31m Aucun rôle de boost trouvé !`)
const hadRole = oldMember.roles.cache.find(role => role.id === boostRole.id)
const hasRole = newMember.roles.cache.find(role => role.id === boostRole.id)
if (!hadRole && hasRole) {
if (!guild.members.me) return console.log(`\u001b[1;31m Je ne suis pas sur le serveur !`)
let embed = new EmbedBuilder()
.setColor(guild.members.me.displayHexColor)
.setTitle(`Nouveau boost de ${newMember.user.username} !`)
.setDescription(`
Merci à toi pour ce boost.\n
Grâce à toi, on a atteint ${guild.premiumSubscriptionCount} boosts !
`)
.setThumbnail(newMember.user.avatarURL())
.setTimestamp(new Date())
await channel.send({ embeds: [embed] })
}
}
}
}

View File

@@ -8,9 +8,8 @@ export default {
console.log(`Guild ${oldGuild.name} updated`) console.log(`Guild ${oldGuild.name} updated`)
let guildProfile = await dbGuild.findOne({ guildId: newGuild.id }) let guildProfile = await dbGuild.findOne({ guildId: newGuild.id })
if (!guildProfile) { if (!guildProfile) guildProfile = await dbGuildInit(newGuild)
guildProfile = await dbGuildInit(newGuild) else {
} else {
guildProfile.guildName = newGuild.name guildProfile.guildName = newGuild.name
guildProfile.guildIcon = newGuild.iconURL() ?? 'None' guildProfile.guildIcon = newGuild.iconURL() ?? 'None'
await guildProfile.save().catch(console.error) await guildProfile.save().catch(console.error)

View File

@@ -1,6 +1,5 @@
import { Events, Interaction, ChatInputCommandInteraction, AutocompleteInteraction, ButtonInteraction } from 'discord.js' import { Events, Interaction, ChatInputCommandInteraction, AutocompleteInteraction, ButtonInteraction } from 'discord.js'
import playerButtons from '../../utilsPlayer/buttons' import { playerButtons, playerEdit } from '../../utils/player'
import editPlayer from '../../utilsPlayer/edit'
export default { export default {
name: Events.InteractionCreate, name: Events.InteractionCreate,
@@ -37,7 +36,7 @@ export default {
console.log(`Button '${interaction.customId}' clicked by ${interaction.user.tag}`) console.log(`Button '${interaction.customId}' clicked by ${interaction.user.tag}`)
if (playerButtons.includes(interaction.customId)) { await editPlayer(interaction) } if (playerButtons.includes(interaction.customId)) { await playerEdit(interaction) }
try { await button.execute(interaction) } try { await button.execute(interaction) }
catch (error) { console.error(`Error clicking ${interaction.customId}:`, error) } catch (error) { console.error(`Error clicking ${interaction.customId}:`, error) }

View File

@@ -1,16 +1,17 @@
import { Events, Client, ActivityType } from 'discord.js' import { Events, Client, ActivityType } from 'discord.js'
import { YoutubeiExtractor } from "discord-player-youtubei"
import { useMainPlayer } from 'discord-player' import { useMainPlayer } from 'discord-player'
import { connect } from 'mongoose' import { connect } from 'mongoose'
import WebSocket from 'websocket'
import chalk from 'chalk' import chalk from 'chalk'
import 'dotenv/config' import 'dotenv/config'
import dbGuildInit from '../../utils/dbGuildInit' import dbGuildInit from '../../utils/dbGuildInit'
import dbGuild from '../../schemas/guild' import dbGuild from '../../schemas/guild'
import replay from '../../utilsPlayer/replay' import { playerDisco, playerReplay } from '../../utils/player'
import disco from '../../utilsPlayer/disco' import * as Twitch from '../../utils/twitch'
import rss from '../../utils/rss' import rss from '../../utils/rss'
export default { export default {
name: Events.ClientReady, name: Events.ClientReady,
once: true, once: true,
@@ -18,7 +19,8 @@ export default {
console.log(chalk.blue(`[DiscordJS] Connected to Discord ! Logged in as ${client.user?.tag ?? 'unknown'}`)) console.log(chalk.blue(`[DiscordJS] Connected to Discord ! Logged in as ${client.user?.tag ?? 'unknown'}`))
client.user?.setActivity('some bangers...', { type: ActivityType.Listening }) client.user?.setActivity('some bangers...', { type: ActivityType.Listening })
await useMainPlayer().extractors.loadDefault(ext => ext === 'YouTubeExtractor' || ext === 'SpotifyExtractor').then(() => console.log(chalk.blue('[Discord-Player] YouTube and Spotify extractors loaded.'))).catch(console.error) await useMainPlayer().extractors.loadDefault(ext => ext === 'SpotifyExtractor').then(() => console.log(chalk.blue('[Discord-Player] Spotify extractor loaded.'))).catch(console.error)
await useMainPlayer().extractors.register(YoutubeiExtractor, {}).then(() => console.log(chalk.blue('[Discord-Player] Youtube extractor loaded.'))).catch(console.error)
let mongo_url = `mongodb://${process.env.MONGOOSE_USER}:${process.env.MONGOOSE_PASSWORD}@${process.env.MONGOOSE_HOST}/${process.env.MONGOOSE_DATABASE}` let mongo_url = `mongodb://${process.env.MONGOOSE_USER}:${process.env.MONGOOSE_PASSWORD}@${process.env.MONGOOSE_HOST}/${process.env.MONGOOSE_DATABASE}`
await connect(mongo_url).catch(console.error) await connect(mongo_url).catch(console.error)
@@ -29,14 +31,14 @@ export default {
let guildProfile = await dbGuild.findOne({ guildId: guild.id }) let guildProfile = await dbGuild.findOne({ guildId: guild.id })
if (!guildProfile) guildProfile = await dbGuildInit(guild) if (!guildProfile) guildProfile = await dbGuildInit(guild)
if (guildProfile.guildPlayer?.replay?.enabled && guildProfile.guildPlayer?.replay?.textChannelId) await replay(client, guildProfile) if (guildProfile.guildPlayer?.replay?.enabled && guildProfile.guildPlayer?.replay?.textChannelId) await playerReplay(client, guildProfile)
client.disco = { interval: {} as NodeJS.Timeout } client.disco = { interval: {} as NodeJS.Timeout }
client.disco.interval = setInterval(async () => { client.disco.interval = setInterval(async () => {
let guildProfile = await dbGuild.findOne({ guildId: guild.id }) let guildProfile = await dbGuild.findOne({ guildId: guild.id })
if (guildProfile?.guildPlayer?.disco?.enabled) { if (guildProfile?.guildPlayer?.disco?.enabled) {
let state = await disco(client, guildProfile) let state = await playerDisco(client, guildProfile)
if (state === 'clear') clearInterval(client.disco.interval) if (state === 'clear') clearInterval(client.disco.interval)
} }
}, 3000) }, 3000)
@@ -50,6 +52,64 @@ export default {
if (state === 'clear') clearInterval(client.rss.interval) if (state === 'clear') clearInterval(client.rss.interval)
} }
}, 30000) }, 30000)
// TWITCH EVENTSUB
let dbData = guildProfile.get('guildTwitch')
if (!dbData?.enabled) return console.log(chalk.magenta(`[Twitch] Module is disabled for "${guild?.name}", please activate with \`/database edit guildTwitch.enabled True\` !`))
let client_id = process.env.TWITCH_APP_ID as string
if (!client_id) return console.log(chalk.magenta('[Twitch] App ID is not defined !'))
let client_secret = process.env.TWITCH_APP_SECRET as string
if (!client_secret) return console.log(chalk.magenta('[Twitch] App Secret is not defined !'))
let twitch = new WebSocket.client().on('connect', async connection => {
console.log(chalk.magenta('[Twitch] EventSub WebSocket Connected !'))
connection.on('message', async message => { if (message.type === 'utf8') { try {
let data = JSON.parse(message.utf8Data)
let channel_access_token = guildProfile.get('guildTwitch')?.channelAccessToken as string
// Check when Twitch asks to login
if (data.metadata.message_type === 'session_welcome') {
// Check if the channel access token is still valid before connecting
channel_access_token = await Twitch.checkChannel(client_id, client_secret, channel_access_token, guild) as string
if (!channel_access_token) return console.log(chalk.magenta("[Twitch] Can't refresh channel access token !"))
// Get broadcaster user id and reward id
let broadcaster_user_id = await Twitch.getUserInfo(client_id, channel_access_token, 'id') as string
let topics: { [key: string]: { version: string; condition: { broadcaster_user_id: string } } } = {
'stream.online': { version: '1', condition: { broadcaster_user_id } },
'stream.offline': { version: '1', condition: { broadcaster_user_id } }
}
// Subscribe to all events required
for (let type in topics) {
console.log(chalk.magenta(`[Twitch] Creating ${type}...`))
let { version, condition } = topics[type]
let status = await Twitch.subscribeToEvents(client_id, channel_access_token, data.payload.session.id, type, version, condition)
if (!status) return console.error(chalk.magenta(`[Twitch] Failed to create ${type}`))
else if (status.error) return console.log(chalk.magenta('[Twitch] Erreur de connexion EventSub, veuillez vous reconnecter !'))
else console.log(chalk.magenta(`[Twitch] Successfully created ${type}`))
}
}
// Handle notification messages
else if (data.metadata.message_type === 'notification') Twitch.notification(client_id, channel_access_token, data, guild)
} catch (error) { console.error(chalk.magenta('[Twitch] ' + error)) } } })
.on('error', error => console.error(chalk.magenta('[Twitch] ' + error)))
.on('close', () => {
console.log(chalk.magenta('[Twitch] EventSub Connection Closed !'))
twitch.connect('wss://eventsub.wss.twitch.tv/ws')
})
}).on('connectFailed', error => console.error(chalk.magenta('[Twitch] ' + error)))
// LAUNCH TWITCH EVENTSUB
twitch.connect('wss://eventsub.wss.twitch.tv/ws')
}) })
} }
} }

View File

@@ -1,28 +1,29 @@
// PACKAGES // PACKAGES
import { Client, Collection, GatewayIntentBits, REST, Routes, ChatInputCommandInteraction, AutocompleteInteraction, ButtonInteraction } from 'discord.js' import { Client, Collection, GatewayIntentBits, REST, Routes, ChatInputCommandInteraction, AutocompleteInteraction, ButtonInteraction } from 'discord.js'
import { Player } from 'discord-player' import { Player } from 'discord-player'
import { connection } from 'mongoose' import { connection, Connection } from 'mongoose'
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
import 'dotenv/config' import 'dotenv/config'
import pjson from '../package.json' // CUSTOM TYPES
console.log('Running on version', pjson.version) interface CConnection extends Connection {
once: (event: string, listener: (...args: any[]) => void) => void
export interface Command { on: (event: string, listener: (...args: any[]) => void) => void
}
interface Command {
name: string name: string
description: string description: string
data: any data: any
autocompleteRun: (interaction: AutocompleteInteraction) => any autocompleteRun: (interaction: AutocompleteInteraction) => any
execute: (interaction: ChatInputCommandInteraction) => any execute: (interaction: ChatInputCommandInteraction) => any
} }
export interface Button { interface Button {
name: string name: string
description: string description: string
id: string id: string
execute: (interaction: ButtonInteraction) => any execute: (interaction: ButtonInteraction) => any
} }
declare module 'discord.js' { declare module 'discord.js' {
export interface Client { export interface Client {
commands: Collection<unknown, Command> commands: Collection<unknown, Command>
@@ -69,6 +70,8 @@ let commandsTotal = 0
let commandFolders = fs.readdirSync(path.join(__dirname, './commands')) let commandFolders = fs.readdirSync(path.join(__dirname, './commands'))
commandFolders.forEach(folder => { commandFolders.forEach(folder => {
if (folder === 'salonpostam' && process.env.DISCORD_APP_ID === '660961595006124052') return
let folderPath = path.join(__dirname, './commands', folder) let folderPath = path.join(__dirname, './commands', folder)
let commandFiles = fs.readdirSync(folderPath).filter(file => file.endsWith('.ts')) let commandFiles = fs.readdirSync(folderPath).filter(file => file.endsWith('.ts'))
commandsTotal += commandFiles.length commandsTotal += commandFiles.length
@@ -83,6 +86,7 @@ commandFolders.forEach(folder => {
commandsParsed++ commandsParsed++
if (commandsParsed === commandsTotal) { if (commandsParsed === commandsTotal) {
console.log(`[INFO] ${commandsParsed} commands parsed.`)
// COMMANDS REGISTRATION // COMMANDS REGISTRATION
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN as string); const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN as string);
(async () => { (async () => {
@@ -107,8 +111,8 @@ let eventClientFiles = fs.readdirSync(path.join(__dirname, './events/client')).f
eventClientFiles.forEach(async file => { eventClientFiles.forEach(async file => {
let event = await import(path.join(__dirname, './events/client', file)) let event = await import(path.join(__dirname, './events/client', file))
event = event.default event = event.default
if (event.once) client.once(event.name, (...args) => { event.execute(...args) }) if (event.once) client.once(event.name, (...args: any[]) => { event.execute(...args) })
else client.on(event.name, (...args) => { event.execute(...args) }) else client.on(event.name, (...args: any[]) => { event.execute(...args) })
}) })
// PLAYER EVENTS HANDLING // PLAYER EVENTS HANDLING
@@ -125,8 +129,8 @@ let eventsMongo = fs.readdirSync(path.join(__dirname, './events/mongo')).filter(
eventsMongo.forEach(async file => { eventsMongo.forEach(async file => {
let event = await import(path.join(__dirname, './events/mongo', file)) let event = await import(path.join(__dirname, './events/mongo', file))
event = event.default event = event.default
if (event.once) connection.once(event.name, (...args) => { event.execute(...args, client) }) if (event.once) (connection as CConnection).once(event.name, (...args: any[]) => { event.execute(...args, client) })
else connection.on(event.name, (...args) => { event.execute(...args, client) }) else (connection as CConnection).on(event.name, (...args: any[]) => { event.execute(...args, client) })
}) })

View File

@@ -41,6 +41,15 @@ const guildSchema = new Schema({
appToken: { type: String, required: false }, appToken: { type: String, required: false },
sessionToken: { type: String, required: false }, sessionToken: { type: String, required: false },
password_salt: { type: String, required: false } password_salt: { type: String, required: false }
},
guildTwitch: {
enabled: { type: Boolean, required: true },
channelName: { type: String, required: false },
channelAccessToken: { type: String, required: false },
channelRefreshToken: { type: String, required: false },
liveChannelId: { type: String, required: false },
liveMessageId: { type: String, required: false },
liveBroadcasterId: { type: String, required: false }
} }
}) })

View File

@@ -1,5 +1,12 @@
import axios from 'axios' import axios from 'axios'
export interface LoginDetails {
username: string
password: string
remember?: boolean
otp?: string
}
export const ADSModule = { export const ADSModule = {
async GetInstances(host: string, SESSIONID: string) { async GetInstances(host: string, SESSIONID: string) {
return await axios.post(host + '/API/ADSModule/GetInstances', { return await axios.post(host + '/API/ADSModule/GetInstances', {
@@ -56,7 +63,7 @@ export const ADSModule = {
} }
export const Core = { export const Core = {
async Login(host: string, details: any) { async Login(host: string, details: LoginDetails) {
return await axios.post(host + '/API/Core/Login', return await axios.post(host + '/API/Core/Login',
details details
).then(response => { ).then(response => {

View File

@@ -4,7 +4,7 @@ import axios from 'axios'
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
export declare class Game { interface Game {
name: string name: string
link: string link: string
} }

View File

@@ -14,7 +14,8 @@ export default async (guild: Guild) => {
}, },
guildRss: { enabled: false, feeds: [] }, guildRss: { enabled: false, feeds: [] },
guildAmp: { enabled: false }, guildAmp: { enabled: false },
guildFbx: { enabled: false } guildFbx: { enabled: false },
guildTwitch: { enabled: false }
}) })
await guildProfile.save().catch(console.error) await guildProfile.save().catch(console.error)
return guildProfile return guildProfile

View File

@@ -20,7 +20,7 @@ export const Core = {
return { status: 'error', data: error } return { status: 'error', data: error }
}) })
}, },
async Init(host: string, version: Number, httpsAgent: https.Agent, app: App, trackId: String) { async Init(host: string, version: number, httpsAgent: https.Agent, app: App, trackId: string) {
let request let request
if (trackId) request = axios.get(host + `/api/v${version}/login/authorize/` + trackId, { httpsAgent }) if (trackId) request = axios.get(host + `/api/v${version}/login/authorize/` + trackId, { httpsAgent })
@@ -37,7 +37,7 @@ export const Core = {
} }
export const Login = { export const Login = {
async Challenge(host: string, version: Number, httpsAgent: https.Agent) { async Challenge(host: string, version: number, httpsAgent: https.Agent) {
let request = axios.get(host + `/api/v${version}/login/`, { httpsAgent }) let request = axios.get(host + `/api/v${version}/login/`, { httpsAgent })
return await request.then(response => { return await request.then(response => {
@@ -49,7 +49,7 @@ export const Login = {
return { status: 'error', data: error } return { status: 'error', data: error }
}) })
}, },
async Session(host: string, version: Number, httpsAgent: https.Agent, app_id: string, password: string) { async Session(host: string, version: number, httpsAgent: https.Agent, app_id: string, password: string) {
let request = axios.post(host + `/api/v${version}/login/session/`, { app_id, password }, { httpsAgent }) let request = axios.post(host + `/api/v${version}/login/session/`, { app_id, password }, { httpsAgent })
return await request.then(response => { return await request.then(response => {
@@ -64,7 +64,7 @@ export const Login = {
} }
export const Get = { export const Get = {
async Connection(host: string, version: Number, httpsAgent: https.Agent, sessionToken: string) { async Connection(host: string, version: number, httpsAgent: https.Agent, sessionToken: string) {
let request = axios.get(host + `/api/v${version}/connection/`, { httpsAgent, headers: { 'X-Fbx-App-Auth': sessionToken } }) let request = axios.get(host + `/api/v${version}/connection/`, { httpsAgent, headers: { 'X-Fbx-App-Auth': sessionToken } })
return await request.then(response => { return await request.then(response => {

206
src/utils/player.ts Normal file
View File

@@ -0,0 +1,206 @@
import { Client, TextChannel, CommandInteraction, Guild, GuildChannel, TextBasedChannel, VoiceChannel, EmbedBuilder, ButtonBuilder, ActionRowBuilder, ButtonInteraction } from 'discord.js'
import { useMainPlayer, useQueue } from 'discord-player'
import { Document } from 'mongoose'
import getUptime from './getUptime'
type ChannelInferrable = {
channel: TextBasedChannel | VoiceChannel
guild?: Guild
}
export class PlayerMetadata {
public constructor(public data: ChannelInferrable) {
if (data.channel.isDMBased()) { throw new Error('PlayerMetadata cannot be created from a DM') }
if (!data.channel) { throw new Error('PlayerMetadata can only be created from a channel') }
}
public get channel() { return this.data.channel! }
public get guild() { return this.data.guild || (this.data.channel as GuildChannel).guild }
public static create(data: ChannelInferrable | CommandInteraction) {
if (data instanceof CommandInteraction) {
if (!data.inGuild()) { throw new Error('PlayerMetadata cannot be created from a DM') }
return new PlayerMetadata({ channel: data.channel!, guild: data.guild! })
}
return new PlayerMetadata(data);
}
}
export const bots = ['1065047326860783636', '1119343522059927684', '1119344050412204032', '1210714000321548329', '660961595006124052']
export const playerButtons = ['loop', 'pause', 'previous', 'resume', 'shuffle', 'skip', 'stop', 'volume_down', 'volume_up']
export async function playerDisco (client: Client, guildProfile: Document) {
try {
let guild = client.guilds.cache.get(guildProfile.get('guildId'))
if (!guild) {
clearInterval(client.disco.interval)
return 'clear'
}
let dbData = guildProfile.get('guildPlayer.disco')
let queue = useQueue(guild.id)
if (queue) if (queue.isPlaying()) {
dbData['progress'] = queue.node.playbackTime.toString()
guildProfile.set('guildPlayer.disco', dbData)
guildProfile.markModified('guildPlayer.disco')
await guildProfile.save().catch(console.error)
}
let channel = client.channels.cache.get(dbData.channelId) as TextChannel
if (!channel) {
console.log(`Aucun channel trouvé avec l'id \`${dbData.channelId}\`, veuillez utiliser la commande \`/database edit 'value': guildPlayer.disco.channelId\` !`)
clearInterval(client.disco.interval)
return 'clear'
}
let { embed, components } = await playerGenerate(guild)
if (components && embed.data.footer) embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)} \n ${embed.data.footer.text}` })
else embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)}` })
let messages = await channel.messages.fetch()
messages.forEach(msg => { if (!bots.includes(msg.author.id)) msg.delete() })
let botMessage = messages.find(msg => client.user && msg.author.id === client.user.id)
if (botMessage) {
if (!components && botMessage.components.length > 0) {
await botMessage.delete()
return channel.send({ embeds: [embed] })
} else if (components) return botMessage.edit({ embeds: [embed], components })
else return botMessage.edit({ embeds: [embed] })
} else return channel.send({ embeds: [embed] })
} catch (error) {
console.error(error);
return 'clear'
}
}
export async function playerEdit (interaction: ButtonInteraction) {
let guild = interaction.guild
if (!guild) return await interaction.reply({ content: 'Cette commande n\'est pas disponible en message privé.', ephemeral: true })
let { components } = await playerGenerate(guild)
if (!components) return
components.forEach((actionRow) => actionRow.components.forEach((button) => button.setDisabled(true)))
await interaction.update({ components })
}
export async function playerGenerate (guild: Guild) {
let embed = new EmbedBuilder().setColor('#ffc370')
let queue = useQueue(guild.id)
if (!queue) {
embed.setTitle('Aucune session d\'écoute en cours !')
return ({ embed, components: null })
}
let track = queue.currentTrack
if (!track) {
embed.setTitle('Aucune musique en cours de lecture !')
return ({ embed, components: null })
}
embed.setTitle(track.title)
.setAuthor({ name: track.author })
.setURL(track.url)
.setImage(track.thumbnail)
.addFields(
{ name: 'Durée', value: track.duration, inline: true },
{ name: 'Source', value: track.source === 'youtube' ? 'Youtube' : track.source === 'spotify' ? 'Spotify' : 'Inconnu', inline: true },
{ name: 'Volume', value: `${queue.node.volume}%`, inline: true },
{ name: queue.node.isPaused() ? 'Progression (en pause)' : 'Progression', value: queue.node.createProgressBar() || 'Aucune' },
{ name: 'Loop', value: queue.repeatMode === 3 ? 'Autoplay' : queue.repeatMode === 2 ? 'File d\'Attente' : queue.repeatMode === 1 ? 'Titre' : 'Off', inline: true }
)
.setDescription(`**Musique suivante :** ${queue.tracks.data[0] ? queue.tracks.data[0].title : 'Aucune'}`)
.setFooter({ text: `Demandé par ${track.requestedBy ? track.requestedBy.tag : 'Inconnu'}` })
let components = [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel(queue.node.isPaused() ? '▶️' : '⏸️')
.setStyle(2)
.setCustomId(queue.node.isPaused() ? 'resume' : 'pause'),
new ButtonBuilder()
.setLabel('⏹️')
.setStyle(2)
.setCustomId('stop'),
new ButtonBuilder()
.setLabel('⏭️')
.setStyle(2)
.setCustomId('skip')
.setDisabled(queue.tracks.data.length !== 0),
new ButtonBuilder()
.setLabel('🔉')
.setStyle(2)
.setCustomId('volume_down')
.setDisabled(queue.node.volume === 0),
new ButtonBuilder()
.setLabel('🔊')
.setStyle(2)
.setCustomId('volume_up')
.setDisabled(queue.node.volume === 100)
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel('🔀')
.setStyle(2)
.setCustomId('shuffle'),
new ButtonBuilder()
.setLabel('🔁')
.setStyle(2)
.setCustomId('loop'),
new ButtonBuilder()
.setLabel('⏮️')
.setStyle(2)
.setCustomId('previous')
.setDisabled(queue.history.previousTrack ? false : true)
)
]
return ({ embed, components })
}
export async function playerReplay (client: Client, guildProfile: Document) {
let dbData = guildProfile.get('guildPlayer.replay')
let textChannel = client.channels.cache.get(dbData.textChannelId) as TextChannel
if (!textChannel) return console.log(`Aucun channel trouvé avec l'id \`${dbData.textChannelId}\`, veuillez utiliser la commande \`/setchannel\` !`)
let voiceChannel = client.channels.cache.get(dbData.voiceChannelId) as VoiceChannel
if (!voiceChannel) return console.log(`Aucun channel trouvé avec l'id \`${dbData.voiceChannelId}\`, veuillez utiliser la commande \`/setchannel\` !`)
let player = useMainPlayer()
let queue = player.nodes.create(textChannel.guild, {
metadata: {
channel: textChannel,
client: textChannel.guild.members.me,
requestedBy: client.user
},
selfDeaf: true,
volume: 20,
leaveOnEmpty: true,
leaveOnEmptyCooldown: 30000,
leaveOnEnd: true,
leaveOnEndCooldown: 300000
})
try { if (!queue.connection) await queue.connect(voiceChannel) }
catch (error) { console.error(error) }
let result = await player.search(dbData.trackUrl as string, { requestedBy: client.user || undefined })
if (!result.hasTracks()) await textChannel.send(`Aucune musique trouvée pour **${dbData.trackUrl}** !`)
let track = result.tracks[0]
let entry = queue.tasksQueue.acquire()
await entry.getTask()
queue.addTrack(track)
try {
await queue.node.play()
await queue.node.seek(Number(dbData.progress) / 1000)
await textChannel.send(`Relancement de la musique suite à mon redémarrage...`)
} catch (error) { console.error(error) }
finally { queue.tasksQueue.release() }
}

View File

@@ -88,7 +88,7 @@ export default async (client: Client, guildProfile: Document) => {
] ]
if (!dbData.feeds) dbData.feeds = feeds if (!dbData.feeds) dbData.feeds = feeds
dbData.feeds.forEach((feed: Feed, i: Number) => { setTimeout(async () => { dbData.feeds.forEach((feed: Feed, i: number) => { setTimeout(async () => {
let parser = new Parser() let parser = new Parser()
if (feed.token) feed.url += feed.token if (feed.token) feed.url += feed.token
let feedData = await parser.parseURL(feed.url) let feedData = await parser.parseURL(feed.url)
@@ -119,5 +119,5 @@ export default async (client: Client, guildProfile: Document) => {
if (lastMessage.content !== `**${lastItem.title}**\n${lastItem.link}`) await thread.send({ content: `**${lastItem.title}**\n${lastItem.link}` }) if (lastMessage.content !== `**${lastItem.title}**\n${lastItem.link}`) await thread.send({ content: `**${lastItem.title}**\n${lastItem.link}` })
//else console.log('No new item found for ' + feed.name) //else console.log('No new item found for ' + feed.name)
}, Number(i) * 1000) }) }, Number(i) * 1000) })
} catch (error: any) { console.error(error); return 'clear' } } catch (error) { console.error(error); return 'clear' }
} }

173
src/utils/twitch.ts Normal file
View File

@@ -0,0 +1,173 @@
import { Guild, TextChannel, EmbedBuilder } from 'discord.js'
import axios, { AxiosHeaderValue } from 'axios'
import dbGuild from '../schemas/guild'
interface NotificationData {
metadata: {
message_type: string
}
payload: {
subscription: {
type: string
}
event: {
broadcaster_user_name: string
broadcaster_user_login: string
}
session: {
id: string
}
}
}
interface Condition {
broadcaster_user_id: string
}
export async function checkChannel (client_id: string, client_secret: string, channel_access_token: string, guild: Guild) {
let guildProfile = await dbGuild.findOne({ guildId: guild?.id })
if (!guildProfile) return console.log(`Database data for ${guild?.name} does not exist, please initialize with \`/database init\` !`)
let dbData = guildProfile.get('guildTwitch')
if (!dbData?.enabled) return console.log(`Twitch module is disabled for "${guild?.name}", please activate with \`/database edit guildTwitch.enabled True\` !`)
if (!await validateToken(channel_access_token)) {
let channel_refresh_token = dbData.channelRefreshToken
if (!channel_refresh_token) return console.log('No refresh token found in database !')
let GetAccessFromRefresh = await refreshToken(client_id, client_secret, channel_refresh_token)
if (!GetAccessFromRefresh) return false;
[channel_access_token, channel_refresh_token] = [dbData.channelAccessToken, dbData.channelRefreshToken] = GetAccessFromRefresh
guildProfile.set('guildTwitch', dbData)
guildProfile.markModified('guildTwitch')
await guildProfile.save().catch(console.error)
}
return channel_access_token
}
export async function validateToken (access_token: string) {
return await axios.get('https://id.twitch.tv/oauth2/validate', {
headers: {
'Authorization': `OAuth ${access_token}`,
}
}).then(() => {
return true
}).catch(error => { console.error(error.response.data) })
}
export async function refreshToken (client_id: string, client_secret: string, refresh_token: string) {
return await axios.post('https://id.twitch.tv/oauth2/token', {
client_id,
client_secret,
refresh_token,
grant_type: 'refresh_token'
}).then(response => {
if (response.data.token_type === 'bearer') return [response.data.access_token, response.data.refresh_token]
}).catch(error => { console.error(error.response.data) })
}
export async function getStreams (client_id: string, access_token: string, user_login: string) {
return await axios.get(`https://api.twitch.tv/helix/streams?user_login=${user_login}`, {
headers: {
'Authorization': `Bearer ${access_token}`,
'Client-Id': client_id as AxiosHeaderValue
}
}).then(response => {
return response.data.data[0]
}).catch(error => { console.error(error.response) })
}
export async function getUserInfo (client_id: string, access_token: string, type: string) {
return await axios.get('https://api.twitch.tv/helix/users', {
headers: {
'Authorization': `Bearer ${access_token}`,
'Client-Id': client_id as AxiosHeaderValue
}
}).then(response => {
if (type === 'login') return response.data.data[0].login
if (type === 'id') return response.data.data[0].id
if (type === 'profile_image_url') return response.data.data[0].profile_image_url
}).catch(error => { console.error(error.response.data) })
}
export async function notification (client_id: string, channel_access_token: string, data: NotificationData, guild: Guild) {
let { subscription, event } = data.payload
let guildProfile = await dbGuild.findOne({ guildId: guild?.id })
if (!guildProfile) return console.log(`Database data for ${guild?.name} does not exist, please initialize with \`/database init\` !`)
let dbData = guildProfile.get('guildTwitch')
if (!dbData?.enabled) return console.log(`Twitch module is disabled for "${guild?.name}", please activate with \`/database edit guildTwitch.enabled True\` !`)
let liveChannelId = dbData.liveChannelId
if (!liveChannelId) return console.log('No live channel id found in database !')
let liveChannel = guild.channels.cache.get(liveChannelId) as TextChannel
if (!liveChannel) return console.log(`\u001b[1;35m Can't find channel with id ${liveChannelId}`)
if (subscription.type === 'stream.online') {
console.log(`\u001b[1;35m Stream from ${event.broadcaster_user_name} is now online, sending Discord message...`)
let stream_data = await getStreams(client_id, channel_access_token, event.broadcaster_user_login)
let user_profile_image_url = await getUserInfo(client_id, channel_access_token, 'profile_image_url')
let embed = new EmbedBuilder()
.setColor('#6441a5')
.setTitle(stream_data.title)
.setURL(`https://twitch.tv/laytho_`)
.setAuthor({ name: `${event.broadcaster_user_login.toUpperCase()} EST ACTUELLEMENT EN LIVE !`, iconURL: user_profile_image_url })
.setDescription(`Joue à ${stream_data.game_name} avec ${stream_data.viewer_count} viewers`)
.setImage(stream_data.thumbnail_url.replace('{width}', '1920').replace('{height}', '1080'))
.setTimestamp()
let message = await liveChannel.send({ content: `Hey @everyone ! <@${dbData.liveBroadcasterId}> démarre son live sur **Twitch** !`, embeds: [embed] })
dbData.liveMessageId = message.id
guildProfile.set('guildTwitch', dbData)
guildProfile.markModified('guildTwitch')
await guildProfile.save().catch(console.error)
}
else if (subscription.type === 'stream.offline') {
console.log(`\u001b[1;35m Stream from laytho_ is now offline, editing Discord message...`)
let message = liveChannel.messages.cache.find(message => message.id === dbData.liveMessageId)
if (!message) return console.log(`\u001b[1;35m Can't find message with id ${dbData.liveMessageId}`)
if (!message.embeds[0]) return console.log(`\u001b[1;35m Can't find embed in message with id ${dbData.liveMessageId}`)
let duration = new Date().getTime() - new Date(message.embeds[0].data.timestamp ?? 0).getTime()
let seconds = Math.floor(duration / 1000)
let minutes = Math.floor(seconds / 60)
let hours = Math.floor(minutes / 60)
let duration_string = `${hours ? hours + 'H' : ''} ${minutes % 60 ? minutes % 60 + 'M' : ''} ${seconds % 60 ? seconds % 60 + 'S' : ''}`
let user_profile_image_url = await getUserInfo(client_id, channel_access_token, 'profile_image_url')
let embed = new EmbedBuilder()
.setColor('#6441a5')
.setAuthor({ name: `C'EST FINI, LE LIVE A DURÉ ${duration_string} !`, iconURL: user_profile_image_url })
.setTimestamp()
message.edit({ content: `Re @everyone ! <@${dbData.liveBroadcasterId}> a terminé son live sur **Twitch** !`, embeds: [embed] })
}
}
export async function subscribeToEvents (client_id: string, access_token: string, session_id: string, type: string, version: string, condition: Condition) {
return await axios.post('https://api.twitch.tv/helix/eventsub/subscriptions', {
type,
version,
condition,
transport: {
method: 'websocket',
session_id
}
}, {
headers: {
'Authorization': `Bearer ${access_token}`,
'Client-Id': client_id,
'Content-Type': 'application/json'
}
}).then(response => {
return response.data.data[0].status
}).catch(error => { return error.response.data })
}

View File

@@ -1,13 +0,0 @@
const axios = require('axios')
module.exports = (async (SESSIONID) => {
return await axios.post(`${process.env.AMP_HOST}/API/${__filename.split('utilsAMP/')[1].split('.js')[0]}`, {
SESSIONID
}).then(response => {
if (!response.data.result) return { status: 'fail', data: response.data }
return { status: 'success', data: response.data }
}).catch(error => {
console.error(error)
return { status: 'error', data: error }
})
})

View File

@@ -1,16 +0,0 @@
const axios = require('axios')
module.exports = (async (SESSIONID, InstanceId) => {
return await axios.post(`${process.env.AMP_HOST}/API/${__filename.split('utilsAMP/')[1].split('.js')[0]}`, {
SESSIONID,
InstanceId
}).then(response => {
console.log(response.data)
if (!response.data.result) return { status: 'fail', data: response.data }
return { status: 'success', data: response.data }
}).catch(error => {
console.error(error)
return { status: 'error', data: error }
})
})

View File

@@ -1,16 +0,0 @@
const axios = require('axios')
module.exports = (async (SESSIONID, InstanceName) => {
return await axios.post(`${process.env.AMP_HOST}/API/${__filename.split('utilsAMP/')[1].split('.js')[0]}`, {
SESSIONID,
InstanceName
}).then(response => {
console.log(response.data)
//if (!response.data.success) return { status: 'fail', data: response.data }
return { status: 'success', data: response.data }
}).catch(error => {
console.error(error)
return { status: 'error', data: error }
})
})

View File

@@ -1,17 +0,0 @@
const axios = require('axios')
module.exports = (async (SESSIONID, InstanceId) => {
return await axios.get(`${process.env.AMP_HOST}/API/${__filename.split('utilsAMP/')[1].split('.js')[0]}`, {
SESSIONID,
InstanceId
}).then(response => {
console.log(response)
console.log(response.data)
if (!response.data.result) return { status: 'fail', data: response.data }
return { status: 'success', data: response.data }
}).catch(error => {
console.error(error)
return { status: 'error', data: error }
})
})

View File

@@ -1,13 +0,0 @@
const axios = require('axios')
module.exports = (async (SESSIONID) => {
return await axios.post(`${process.env.AMP_HOST}/API/ADSModule/GetInstances`, {
SESSIONID
}).then(response => {
if (!response.data.result) return { status: 'fail', data: response.data }
return { status: 'success', data: response.data }
}).catch(error => {
console.error(error)
return { status: 'error', data: error }
})
})

View File

@@ -1,21 +0,0 @@
const axios = require('axios')
const appDir = require('path').dirname(require.main.filename)
const writeEnv = require(appDir + '/utils/writeEnv')
module.exports = (async (details) => {
return await axios.post(`${process.env.AMP_HOST}/API/${__filename.split('utilsAMP/')[1].split('.js')[0]}`,
details
).then(response => {
if (!response.data.success) return { status: 'fail', data: response.data }
writeEnv('AMP_USERNAME', response.data.userInfo.Username)
writeEnv('AMP_SESSIONID', response.data.sessionID)
writeEnv('AMP_REMEMBER_TOKEN', response.data.rememberMeToken)
return { status: 'success', data: response.data }
}).catch(error => {
console.error(error)
return { status: 'error', data: error }
})
})

View File

@@ -1,18 +0,0 @@
const axios = require('axios')
const fs = require('fs')
module.exports = download = (async (url, file, headers) => {
let path = `./cracks/${file}`
let writer = fs.createWriteStream(path)
try {
await axios({ url: url + file, method: 'GET', responseType: 'stream', headers }).then(response => {
return new Promise((resolve, reject) => {
response.data.pipe(writer)
let error = null
writer.on('error', err => { error = err; writer.close(); reject(err) })
writer.on('close', () => { if (!error) resolve(true) })
})
}).catch(console.error)
return path
} catch (error) { console.error(error) }
})

View File

@@ -1,23 +0,0 @@
const h1 = {
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"x-requested-with": "XMLHttpRequest"
}
const h2 = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
"cache-control": "no-cache",
"pragma": "no-cache",
"sec-ch-ua": "\"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"110\", \"Opera GX\";v=\"96\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"cookie": "online_fix_auth=gAAAAABkKM0s9WNLe_V6euTnJD7UQjppVty9B7OOyHBYOyVcbcejj8F6KveBcLxlf3mlx_vE7JEFPHlrpj-Aq6BFJyKPGzxds_wpcPV2MdXPyDGQLsz4mAvt3qgTgGg25MapWo_fSIOMiAAsF4Gv_uh4kUOiR_jgbHCZWJGPgpNQwU2HFFyvahYR6MzR7nYE9-fCmrev3obkRbro43vIVTTX4UyJMRHadrsY5Q-722TzinCZVmAuJfc=; dle_password=89465c26673e0199e5272e4730772c35; _ym_uid=1670534560361937997; _ym_d=1680394955; _ym_isad=2; dle_user_id=2619796; PHPSESSID=3v8sd281sr0n1n9f1p66q25sa2",
"Referer": "https://online-fix.me/",
"Referrer-Policy": "strict-origin-when-cross-origin"
}
module.exports = headers = { h1, h2 }

View File

@@ -1,12 +0,0 @@
const fs = require('fs')
module.exports = magnet = (async path => {
const parseTorrent = await import('parse-torrent')
const parse = parseTorrent.default
const magnetURI = parseTorrent.toMagnetURI
let data = await parse(fs.readFileSync(path))
let uri = await magnetURI({ infoHash: data.infoHash })
return uri
})

View File

@@ -1,13 +0,0 @@
const iconv = require('iconv-lite')
module.exports = repo = (async (game, headers) => {
let body = await fetch(game.link, { headers, body: null, method: "GET" })
.then(response => response.arrayBuffer())
.then(arrayBuffer => { return iconv.decode(Buffer.from(arrayBuffer), 'win1251') })
.catch(console.error)
try {
let name = body.split('https://uploads.online-fix.me:2053/torrents/')[1].split('"')[0]
let url = `https://uploads.online-fix.me:2053/torrents/${name}`
return url
} catch (error) { console.error(error) }
})

View File

@@ -1,19 +0,0 @@
const iconv = require('iconv-lite')
module.exports = search = (async (query, headers) => {
let body = await fetch("https://online-fix.me/engine/ajax/search.php", { headers, body: `query=${query}`, method: "POST" })
.then(response => response.arrayBuffer())
.then(arrayBuffer => { return iconv.decode(Buffer.from(arrayBuffer), 'win1251') })
.catch(console.error)
try {
let matches = body.split('</div>')[1].split('<span class="seperator fastfullsearch">')[0].split('</a>')
let games = []
matches.pop()
matches.forEach(async match => {
let name = match.split('"><span class="searchheading">')[1].split('</span>')[0].slice(0, -8)
let link = match.split('<a href="')[1].split('"><span class="searchheading">')[0]
games.push({ name, link })
})
return games
} catch (error) { return error }
})

View File

@@ -1,8 +0,0 @@
module.exports = torrent = (async (url, headers) => {
let response = await fetch(url, { headers, body: null, method: "GET" }).catch(console.error)
try {
let body = await response.text()
let file = body.split('<a href="')[2].split('">')[0]
return file
} catch (error) { console.error(error) }
})

View File

@@ -1 +0,0 @@
export default ['1065047326860783636', '1119343522059927684', '1119344050412204032', '1210714000321548329']

View File

@@ -1 +0,0 @@
export default ['loop', 'pause', 'previous', 'resume', 'shuffle', 'skip', 'stop', 'volume_down', 'volume_up']

View File

@@ -1,55 +0,0 @@
import { Client, TextChannel } from 'discord.js'
import { useQueue } from 'discord-player'
import { Document } from 'mongoose'
import getUptime from '../utils/getUptime'
import generate from './generate'
import bots from './bots'
export default async (client: Client, guildProfile: Document) => {
try {
let guild = client.guilds.cache.get(guildProfile.get('guildId'))
if (!guild) {
clearInterval(client.disco.interval)
return 'clear'
}
let dbData = guildProfile.get('guildPlayer.disco')
let queue = useQueue(guild.id)
if (queue) if (queue.isPlaying()) {
dbData['progress'] = queue.node.playbackTime.toString()
guildProfile.set('guildPlayer.disco', dbData)
guildProfile.markModified('guildPlayer.disco')
await guildProfile.save().catch(console.error)
}
let channel = client.channels.cache.get(dbData.channelId) as TextChannel
if (!channel) {
console.log(`Aucun channel trouvé avec l'id \`${dbData.channelId}\`, veuillez utiliser la commande \`/database edit 'value': guildPlayer.disco.channelId\` !`)
clearInterval(client.disco.interval)
return 'clear'
}
let { embed, components } = await generate(guild)
if (components && embed.data.footer) embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)} \n ${embed.data.footer.text}` })
else embed.setFooter({ text: `Uptime: ${getUptime(client.uptime)}` })
let messages = await channel.messages.fetch()
messages.forEach(msg => { if (!bots.includes(msg.author.id)) msg.delete() })
let botMessage = messages.find(msg => client.user && msg.author.id === client.user.id)
if (botMessage) {
if (!components && botMessage.components.length > 0) {
await botMessage.delete()
return channel.send({ embeds: [embed] })
} else if (components) return botMessage.edit({ embeds: [embed], components })
else return botMessage.edit({ embeds: [embed] })
} else return channel.send({ embeds: [embed] })
} catch (error: any) {
console.error(error);
return 'clear'
}
}

View File

@@ -1,13 +0,0 @@
import { ButtonInteraction } from 'discord.js'
import generatePlayer from './generate'
export default async (interaction: ButtonInteraction) => {
let guild = interaction.guild
if (!guild) return await interaction.reply({ content: 'Cette commande n\'est pas disponible en message privé.', ephemeral: true })
let { components } = await generatePlayer(guild)
if (!components) return
components.forEach((actionRow) => actionRow.components.forEach((button) => button.setDisabled(true)))
await interaction.update({ components })
}

View File

@@ -1,76 +0,0 @@
import { EmbedBuilder, ButtonBuilder, ActionRowBuilder, Guild } from 'discord.js'
import { useQueue } from 'discord-player'
export default async (guild: Guild) => {
let embed = new EmbedBuilder().setColor('#ffc370')
let queue = useQueue(guild.id)
if (!queue) {
embed.setTitle('Aucune session d\'écoute en cours !')
return ({ embed, components: null })
}
let track = queue.currentTrack
if (!track) {
embed.setTitle('Aucune musique en cours de lecture !')
return ({ embed, components: null })
}
embed.setTitle(track.title)
.setAuthor({ name: track.author })
.setURL(track.url)
.setImage(track.thumbnail)
.addFields(
{ name: 'Durée', value: track.duration, inline: true },
{ name: 'Source', value: track.source === 'youtube' ? 'Youtube' : track.source === 'spotify' ? 'Spotify' : 'Inconnu', inline: true },
{ name: 'Volume', value: `${queue.node.volume}%`, inline: true },
{ name: queue.node.isPaused() ? 'Progression (en pause)' : 'Progression', value: queue.node.createProgressBar() || 'Aucune' },
{ name: 'Loop', value: queue.repeatMode === 3 ? 'Autoplay' : queue.repeatMode === 2 ? 'File d\'Attente' : queue.repeatMode === 1 ? 'Titre' : 'Off', inline: true }
)
.setDescription(`**Musique suivante :** ${queue.tracks.data[0] ? queue.tracks.data[0].title : 'Aucune'}`)
.setFooter({ text: `Demandé par ${track.requestedBy ? track.requestedBy.tag : 'Inconnu'}` })
let components = [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel(queue.node.isPaused() ? '▶️' : '⏸️')
.setStyle(2)
.setCustomId(queue.node.isPaused() ? 'resume' : 'pause'),
new ButtonBuilder()
.setLabel('⏹️')
.setStyle(2)
.setCustomId('stop'),
new ButtonBuilder()
.setLabel('⏭️')
.setStyle(2)
.setCustomId('skip')
.setDisabled(queue.tracks.data.length !== 0),
new ButtonBuilder()
.setLabel('🔉')
.setStyle(2)
.setCustomId('volume_down')
.setDisabled(queue.node.volume === 0),
new ButtonBuilder()
.setLabel('🔊')
.setStyle(2)
.setCustomId('volume_up')
.setDisabled(queue.node.volume === 100)
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel('🔀')
.setStyle(2)
.setCustomId('shuffle'),
new ButtonBuilder()
.setLabel('🔁')
.setStyle(2)
.setCustomId('loop'),
new ButtonBuilder()
.setLabel('⏮️')
.setStyle(2)
.setCustomId('previous')
.setDisabled(queue.history.previousTrack ? false : true)
)
]
return ({ embed, components })
}

View File

@@ -1,24 +0,0 @@
import { CommandInteraction, Guild, GuildChannel, TextBasedChannel, VoiceChannel } from 'discord.js'
type ChannelInferrable = {
channel: TextBasedChannel | VoiceChannel
guild?: Guild
}
export class PlayerMetadata {
public constructor(public data: ChannelInferrable) {
if (data.channel.isDMBased()) { throw new Error('PlayerMetadata cannot be created from a DM') }
if (!data.channel) { throw new Error('PlayerMetadata can only be created from a channel') }
}
public get channel() { return this.data.channel! }
public get guild() { return this.data.guild || (this.data.channel as GuildChannel).guild }
public static create(data: ChannelInferrable | CommandInteraction) {
if (data instanceof CommandInteraction) {
if (!data.inGuild()) { throw new Error('PlayerMetadata cannot be created from a DM') }
return new PlayerMetadata({ channel: data.channel!, guild: data.guild! })
}
return new PlayerMetadata(data);
}
}

View File

@@ -1,45 +0,0 @@
import { Client, TextChannel, VoiceChannel } from 'discord.js'
import { useMainPlayer } from 'discord-player'
import { Document } from 'mongoose'
export default async (client: Client, guildProfile: Document) => {
let dbData = guildProfile.get('guildPlayer.replay')
let textChannel = client.channels.cache.get(dbData.textChannelId) as TextChannel
if (!textChannel) return console.log(`Aucun channel trouvé avec l'id \`${dbData.textChannelId}\`, veuillez utiliser la commande \`/setchannel\` !`)
let voiceChannel = client.channels.cache.get(dbData.voiceChannelId) as VoiceChannel
if (!voiceChannel) return console.log(`Aucun channel trouvé avec l'id \`${dbData.voiceChannelId}\`, veuillez utiliser la commande \`/setchannel\` !`)
let player = useMainPlayer()
let queue = player.nodes.create(textChannel.guild, {
metadata: {
channel: textChannel,
client: textChannel.guild.members.me,
requestedBy: client.user
},
selfDeaf: true,
volume: 20,
leaveOnEmpty: true,
leaveOnEmptyCooldown: 30000,
leaveOnEnd: true,
leaveOnEndCooldown: 300000
})
try { if (!queue.connection) await queue.connect(voiceChannel) }
catch (error: any) { console.error(error) }
let result = await player.search(dbData.trackUrl as string, { requestedBy: client.user || undefined })
if (!result.hasTracks()) await textChannel.send(`Aucune musique trouvée pour **${dbData.trackUrl}** !`)
let track = result.tracks[0]
let entry = queue.tasksQueue.acquire()
await entry.getTask()
queue.addTrack(track)
try {
await queue.node.play()
await queue.node.seek(Number(dbData.progress) / 1000)
await textChannel.send(`Relancement de la musique suite à mon redémarrage...`)
} catch (error: any) { console.error(error) }
finally { queue.tasksQueue.release() }
}