1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-12-27 04:15:38 -05:00

Alpha stage of audio player system

This commit is contained in:
AnxietyisReal 2023-06-25 03:47:42 +10:00
parent 0c0216eec6
commit 03f5586dcc
6 changed files with 124 additions and 15 deletions

View File

@ -26,7 +26,12 @@
"dependencies": { "dependencies": {
"axios": "1.4.0", "axios": "1.4.0",
"canvas": "2.11.2", "canvas": "2.11.2",
"@discord-player/extractor": "4.3.1",
"@ffmpeg-installer/ffmpeg": "1.1.0",
"@discordjs/opus": "0.9.0",
"discord-player": "6.5.0",
"discord.js": "14.11.0", "discord.js": "14.11.0",
"ytdl-core": "4.11.4",
"moment": "2.29.4", "moment": "2.29.4",
"ms": "2.1.3", "ms": "2.1.3",
"mongoose": "7.3.1", "mongoose": "7.3.1",

View File

@ -54,7 +54,8 @@ export default class TClient extends Client {
GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildInvites, GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildInvites,
GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildPresences,
GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessages GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildVoiceStates
], partials: [ ], partials: [
Partials.Channel, Partials.Reaction, Partials.Message Partials.Channel, Partials.Reaction, Partials.Message
], allowedMentions: {users:[],roles:[]} ], allowedMentions: {users:[],roles:[]}
@ -205,6 +206,15 @@ export default class TClient extends Client {
(this.channels.resolve(DCChannelID) as Discord.TextChannel).send(`**${YTChannelName}** just uploaded a video!\n${Data.feed.entry[0].link._attributes.href}`) (this.channels.resolve(DCChannelID) as Discord.TextChannel).send(`**${YTChannelName}** just uploaded a video!\n${Data.feed.entry[0].link._attributes.href}`)
} }
} }
// Bytes conversion
formatBytes(bytes:number, decimals:number = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};
} }
export class WClient extends WebhookClient { export class WClient extends WebhookClient {

83
src/commands/music.ts Normal file
View File

@ -0,0 +1,83 @@
import Discord from 'discord.js';
import TClient from '../client.js';
import {Player,useTimeline,useQueue} from 'discord-player';
import {SpotifyExtractor} from '@discord-player/extractor';
export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
if (!client.isStaff(interaction.member) && !client.config.whitelist.includes(interaction.member.id)) return interaction.reply('This command is in early stages of development, some parts may be missing or broken.\nIt has been restricted to staff for time-being.');
const player = Player.singleton(client);
await player.extractors.register(SpotifyExtractor, {
clientId: client.tokens.dontlookatme.client,
clientSecret: client.tokens.dontlookatme.secret
});
const voiceCh = interaction.member.voice.channel;
if (!voiceCh) return interaction.reply('Please join a voice channel first to use the command.');
player.nodes.create(interaction.guildId, {
metadata: {
channel: interaction.channel,
client: interaction.guild.members.me,
requestedBy: interaction.member
},
selfDeaf: true,
volume: 75,
skipOnNoStream: true,
leaveOnEnd: false,
leaveOnEmpty: false,
bufferingTimeout: 30000,
connectionTimeout: 25000,
strategy: 'FIFO'
});
({
play: async()=>{
const url = interaction.options.getString('url');
if (!url.includes('https://open.spotify.com/')) return interaction.reply('Sorry, I can\'t play that. I can only accept Spotify links that contains `https://open.spotify.com/`');
player.play(interaction.member.voice.channel, url);
await interaction.reply(`Added the song to the queue.`);
},
stop: async()=>{
player.destroy();
await interaction.reply('Player destroyed.')
},
now_playing: ()=>{
const {volume,timestamp,track} = useTimeline(interaction.guildId);
interaction.reply({embeds:[
new client.embed().setColor(client.config.embedColor).setTitle(`${track.title} - ${track.author}`).setThumbnail(track.thumbnail).addFields(
{name: 'Timestamp', value: `**${timestamp.current.label}**/**${timestamp.total.label}**`, inline: true},
{name: 'Volume', value: `${volume}%`, inline: true}
)
]})
},
volume: ()=>{
const vol = interaction.options.getNumber('percentage');
const queue = useQueue(interaction.guildId);
queue.node.setVolume(vol);
interaction.reply(`Successfully adjusted the player's volume to ${vol}%`)
}
} as any)[interaction.options.getSubcommand()]();
},
data: new Discord.SlashCommandBuilder()
.setName('music')
.setDescription('Music module')
.addSubcommand(x=>x
.setName('play')
.setDescription('Play a Spotify song')
.addStringOption(x=>x
.setName('url')
.setDescription('Spotify URL')
.setRequired(true)))
.addSubcommand(x=>x
.setName('stop')
.setDescription('Stop playing music and disconnect the bot from voice channel'))
.addSubcommand(x=>x
.setName('now_playing')
.setDescription('Check what song is currently playing'))
.addSubcommand(x=>x
.setName('volume')
.setDescription('Adjust the player\'s volume')
.addNumberOption(x=>x
.setName('percentage')
.setDescription('Volume level to adjust, ranges from 5 to 100, default is 75')
.setMaxValue(100)
.setMinValue(5)
.setRequired(true)))
}

View File

@ -6,15 +6,6 @@ import os from 'node:os';
export default { export default {
async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){ async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
const waitForData = await interaction.reply({content: '<a:sakjdfsajkfhsdjhjfsa:1065342869428252743>', fetchReply:true}) const waitForData = await interaction.reply({content: '<a:sakjdfsajkfhsdjhjfsa:1065342869428252743>', fetchReply:true})
// Bytes conversion
function formatBytes(bytes:number, decimals:number = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};
const cpu = await si.cpu(); const cpu = await si.cpu();
const ram = await si.mem(); const ram = await si.mem();
const osInfo = await si.osInfo(); const osInfo = await si.osInfo();
@ -56,8 +47,8 @@ export default {
{name: '> __Host__', value: [ {name: '> __Host__', value: [
`**Operating System:** ${osInfo.distro + ' ' + osInfo.release}`, `**Operating System:** ${osInfo.distro + ' ' + osInfo.release}`,
`**CPU:** ${cpu.manufacturer} ${cpu.brand}`, `**CPU:** ${cpu.manufacturer} ${cpu.brand}`,
`**Memory:** ${formatBytes(ram.used)}/${formatBytes(ram.total)}`, `**Memory:** ${client.formatBytes(ram.used)}/${client.formatBytes(ram.total)}`,
`**NodeJS:** ${formatBytes(process.memoryUsage().heapUsed)}/${formatBytes(process.memoryUsage().heapTotal)}`, `**NodeJS:** ${client.formatBytes(process.memoryUsage().heapUsed)}/${client.formatBytes(process.memoryUsage().heapTotal)}`,
`**Load Usage:**\nUser: ${currentLoad.currentLoadUser.toFixed(1)}%\nSystem: ${currentLoad.currentLoadSystem.toFixed(1)}%`, `**Load Usage:**\nUser: ${currentLoad.currentLoadUser.toFixed(1)}%\nSystem: ${currentLoad.currentLoadSystem.toFixed(1)}%`,
`**Uptime:**\nHost: ${client.formatTime((os.uptime()*1000), 2, {longNames: true, commas: true})}\nBot: ${client.formatTime(client.uptime as number, 2, {commas: true, longNames: true})}` `**Uptime:**\nHost: ${client.formatTime((os.uptime()*1000), 2, {longNames: true, commas: true})}\nBot: ${client.formatTime(client.uptime as number, 2, {commas: true, longNames: true})}`
].join('\n')} ].join('\n')}

View File

@ -3,6 +3,8 @@ import TClient from './client.js';
const client = new TClient; const client = new TClient;
client.init(); client.init();
import MPLoop from './MPLoop.js'; import MPLoop from './MPLoop.js';
import {Player} from 'discord-player';
const player = Player.singleton(client);
import {writeFileSync, readFileSync} from 'node:fs'; import {writeFileSync, readFileSync} from 'node:fs';
client.on('ready', async()=>{ client.on('ready', async()=>{
@ -25,18 +27,32 @@ client.on('ready', async()=>{
console.timeEnd('Startup') console.timeEnd('Startup')
}) })
// Handle errors // Error handler
function DZ(error:Error, location:string){// Yes, I may have shiternet but I don't need to wake up to like a hundred messages or so. function DZ(error:Error, type:string){// Yes, I may have shiternet but I don't need to wake up to like a hundred messages or so.
if (['ENOTFOUND discord.com', 'EAI_AGAIN discord.com'].includes(error.message)) return; if (['ENOTFOUND discord.com', 'EAI_AGAIN discord.com'].includes(error.message)) return;
//console.error(error); //console.error(error);
const channel = client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel | null; const channel = client.channels.resolve(client.config.mainServer.channels.errors) as Discord.TextChannel | null;
channel?.send({embeds: [new client.embed().setColor('#420420').setTitle('Error caught!').setFooter({text: location}).setDescription(`**Error:** \`${error.message}\`\n\n**Stack:** \`${`${error.stack}`.slice(0, 2500)}\``)]}) // vvv Oh yes, that looks really hot.
channel?.send({embeds: [new client.embed().setColor('#560000').setTitle('Error caught!').setFooter({text: 'Error type: ' + type}).setDescription(`**Error:**\n\`\`\`${error.message}\`\`\`**Stack:**\n\`\`\`${`${error.stack}`.slice(0, 2500)}\`\`\``)]})
} }
process.on('unhandledRejection', (error: Error)=>DZ(error, 'unhandledRejection')); process.on('unhandledRejection', (error: Error)=>DZ(error, 'unhandledRejection'));
process.on('uncaughtException', (error: Error)=>DZ(error, 'uncaughtException')); process.on('uncaughtException', (error: Error)=>DZ(error, 'uncaughtException'));
process.on('error', (error: Error)=>DZ(error, 'process-error')); process.on('error', (error: Error)=>DZ(error, 'process-error'));
client.on('error', (error: Error)=>DZ(error, 'client-error')); client.on('error', (error: Error)=>DZ(error, 'client-error'));
// Audio Player event handling
player.events.on('playerStart', (queue,track)=>queue.channel.send({embeds:[new client.embed().setColor(client.config.embedColor).setTitle(`${track.raw.title} - ${track.raw.author}`).setFooter({text:`Playing in ${queue.channel.name}`}).setThumbnail(track.raw.thumbnail)]}));
player.events.on('playerFinish', (queue,track)=>{
if (queue.tracks.size < 1) return queue.channel.send('There\'s no songs left in the queue, leaving voice channel in 15 seconds.');
setTimeout(()=>queue.connection.disconnect(), 15000)
})
player.events.on('audioTrackAdd', (queue,track)=>queue.channel.send({embeds:[new client.embed().setColor(client.config.embedColorGreen).setTitle(`${track.raw.title} - ${track.raw.author}`).setFooter({text:`Added to queue`}).setThumbnail(track.raw.thumbnail)]}));
/* player.events.on('debug', (queue,message)=>{
console.log(client.logTime(), message)
}) */
player.events.on('playerError', (queue, error)=>DZ(error, 'playerError'));
player.events.on('error', (queue, error)=>DZ(error, 'playerInternalError'));
// YouTube Upload notification and Daggerwin MP loop // YouTube Upload notification and Daggerwin MP loop
setInterval(()=>MPLoop(client, client.config.MPStatsLocation.channel, client.config.MPStatsLocation.message, 'Daggerwin'), 60000); setInterval(()=>MPLoop(client, client.config.MPStatsLocation.channel, client.config.MPStatsLocation.message, 'Daggerwin'), 60000);
setInterval(async()=>{ setInterval(async()=>{

View File

@ -116,6 +116,10 @@ export interface Tokens {
main: string main: string
beta: string beta: string
toast: string toast: string
dontlookatme: {
client: string,
secret: string
}
octokit: string octokit: string
webhook_url: string webhook_url: string
webhook_url_test: string webhook_url_test: string