mirror of
https://github.com/kirameki-cafe/Yumi.git
synced 2026-09-13 10:49:20 +00:00
@@ -0,0 +1,98 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
/build
|
||||
/configs/*
|
||||
!/configs/*.example.json
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# IDEs and editors
|
||||
.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
#!.vscode/settings.json Not needed for now
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
.sass-cache
|
||||
connect.lock
|
||||
typings
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# Lerna
|
||||
lerna-debug.log
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,12 @@
|
||||
NODE_ENV=development
|
||||
#development
|
||||
#production
|
||||
|
||||
DATABASE_URL=postgresql://user:password@host:port/dbName?schema=public
|
||||
|
||||
DISCORD_TOKEN=SuperSecretDiscordToken
|
||||
DEVELOPER_IDS=DEV1_ID,DEV2_ID
|
||||
PRIVATE_BOT=true
|
||||
|
||||
OSU_API_KEY=SuperSecretosu!Key
|
||||
YOUTUBE_COOKIE_BASE64=SuperSecretCookie_EncodeItInBase64_AndThen_RemoveTheLastEqualToo
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
/build
|
||||
/configs
|
||||
/configs/*
|
||||
!/configs/*.example.json
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
FROM debian
|
||||
|
||||
RUN mkdir -p /home/node/app/node_modules
|
||||
WORKDIR /home/node/app
|
||||
|
||||
COPY package*.json ./
|
||||
COPY prisma ./prisma/
|
||||
|
||||
RUN DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update -y
|
||||
RUN apt-get -y install curl gnupg git python2 make g++ iputils-ping ffmpeg
|
||||
RUN curl -sL https://deb.nodesource.com/setup_17.x | bash -
|
||||
RUN apt-get -y install nodejs
|
||||
|
||||
RUN npm i -g yarn
|
||||
|
||||
RUN yarn
|
||||
RUN yarn prisma generate
|
||||
COPY . .
|
||||
|
||||
RUN yarn build
|
||||
CMD [ "yarn", "start"]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"type": "ping",
|
||||
"title": "☁️ Internet | ",
|
||||
"host": "1.1.1.1"
|
||||
},
|
||||
{
|
||||
"type": "discordwebsocket",
|
||||
"title": "🚀 Discord Websocket | "
|
||||
},
|
||||
{
|
||||
"type": "discordhttp",
|
||||
"title": "🏓 Discord HTTP | "
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"News": {
|
||||
"color": "#FAEDF0",
|
||||
"title": "📰 Newsletter",
|
||||
"description": "\u200B\n**{bot_username} have been super-charged and upgraded!**\nThe long-awaited update is now here, running latest and greatest version!\n\n**What's new?**\n\u200B\n",
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔥 Feature 1!",
|
||||
"value": "\u200B\nSuch wow! New feature so cool 10/10!!!!\n\u200B\n",
|
||||
"inline": false
|
||||
},
|
||||
{
|
||||
"name": "✨ Shiny and new feature!",
|
||||
"value": "\u200B\nNow brighter than ever!\n\u200B\n",
|
||||
"inline": false
|
||||
},
|
||||
{
|
||||
"name": "🪲 Bugs squashed",
|
||||
"value": "\u200B\nBe gone, bugs!\n\u200B\n",
|
||||
"inline": false
|
||||
}
|
||||
],
|
||||
"thumbnail": {
|
||||
"url": "bot_avatar"
|
||||
}
|
||||
},
|
||||
"Maintenance": {
|
||||
"color": "#383b80",
|
||||
"title": "🔧 Maintenance",
|
||||
"description": "{bot_username} will be down for maintenance\nThe maintenance includes feature improvement to bug fixes\n\nDuring the maintenance, {bot_username}'s commands and features will be unavailable",
|
||||
"fields": [
|
||||
{ "name": "Start", "value": "<t:1812519286:R>", "inline": true},
|
||||
{ "name": "End", "value": "<t:3390442486:R>", "inline": true}
|
||||
],
|
||||
"thumbnail": {
|
||||
"url": "bot_avatar"
|
||||
}
|
||||
},
|
||||
"Message": {
|
||||
"color": "#A1DE93",
|
||||
"title": "✉️ Message",
|
||||
"description": "Hello everyone!",
|
||||
"thumbnail": {
|
||||
"url": "bot_avatar"
|
||||
}
|
||||
},
|
||||
"Alert": {
|
||||
"color": "#EC255A",
|
||||
"title": "🚨 Alert",
|
||||
"description": "{bot_username} currently has an issue with lack of food, {bot_username} need more FOOD! MORE AND MORE!.\nWe're fixing it ASAP",
|
||||
"thumbnail": {
|
||||
"url": "bot_avatar"
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-1
@@ -1,19 +1,29 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.7.0",
|
||||
"@discordjs/opus": "^0.7.0",
|
||||
"@discordjs/rest": "^0.3.0",
|
||||
"@discordjs/voice": "^0.8.0",
|
||||
"@types/color-thief-node": "^1.0.1",
|
||||
"bluebird": "^3.7.2",
|
||||
"bufferutil": "^4.0.6",
|
||||
"color-thief-node": "^1.0.4",
|
||||
"country-code-emoji": "^2.3.0",
|
||||
"country-code-lookup": "^0.0.19",
|
||||
"discord-api-types": "^0.26.1",
|
||||
"discord.js": "^13.6.0",
|
||||
"dotenv": "^10.0.0",
|
||||
|
||||
"libsodium-wrappers": "^0.7.9",
|
||||
"node-osu": "^2.2.1",
|
||||
"ping": "^0.4.1",
|
||||
"play-dl": "^1.8.6",
|
||||
"ts-custom-error": "^3.2.0",
|
||||
"typescript": "^4.4.4",
|
||||
"utf-8-validate": "^5.0.8",
|
||||
"validator": "^13.7.0",
|
||||
"winston": "^3.3.3"
|
||||
"winston": "3.3.3",
|
||||
"zlib-sync": "^0.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@prisma/client": "^3.3.0",
|
||||
@@ -27,6 +37,8 @@
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
"development": "ts-node-dev ./src/index.ts --respawn --transpileOnly",
|
||||
"build": "tsc -b",
|
||||
"start": "node ./build/index.js",
|
||||
"production": "tsc && node ./build/index.js",
|
||||
"test": "mocha -r ts-node/register tests/**/*.ts"
|
||||
},
|
||||
|
||||
+23
-8
@@ -17,22 +17,37 @@ const EMBEDS = {
|
||||
let _e = makeInfoEmbed({
|
||||
icon: "💌",
|
||||
title: `Help - ${DiscordProvider.client.user?.username}`,
|
||||
description: `\u200b\n📰 **Info**\nYumi is currently undergoing complete rewrite, as expected, losing many of her functionality. All of the features will be re-implemented.\n\n🤔 **Why?**\nYumi was specifically written for use in a only 1 private server, now it's time to extend the border and globalize it (just kidding lol)\n\n🏷️ **Prefix**\nYou can call me using \`\`${prefix.replaceAll('`','`')}\`\`, <@${DiscordProvider.client.user?.id}> or \`\`/slash command\`\`\n\n💻 **Available commands**`,
|
||||
fields: [
|
||||
{
|
||||
name: 'Info',
|
||||
value: `Yumi is currently undergoing complete rewrite, as expected, losing many of her functionality. All of the features will be re-implemented.`
|
||||
name: '☕ General',
|
||||
value: `help, ping, invite, userinfo`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Why?',
|
||||
value: `Yumi was specifically written for use in a only 1 private server, now it's time to extend the border and globalize it (just kidding lol)`
|
||||
name: '🎵 Music',
|
||||
value: `play (p), search, skip, queue, nowplaying (np), join, leave`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Prefix',
|
||||
value: `You can call me using \`\`${prefix.replaceAll('`','`')}\`\`, <@${DiscordProvider.client.user?.id}> or \`\`/slash command\`\``
|
||||
name: '🎮 Games',
|
||||
value: `osu`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Available commands',
|
||||
value: '``help``\n``ping``\n``invite``\n``osu``\n``membershipscreening (ms)``\n``interaction`'
|
||||
name: '🔐 Admin',
|
||||
value: `settings, say, membershipscreening (ms)`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '🔧 Developer',
|
||||
value: 'interactions, serviceannouncement',
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: '\u200b',
|
||||
value: '**Made with 💖 and [open source](https://github.com/YuzuZensai/Yumi)**',
|
||||
inline: false
|
||||
}
|
||||
],
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
import { Message, CommandInteraction, Interaction, VoiceChannel, Permissions, GuildMember, DMChannel, StageChannel, MessageActionRow, MessageButton, MessageAttachment, TextChannel } from "discord.js";
|
||||
import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage";
|
||||
import DiscordProvider from "../../providers/Discord";
|
||||
import Users from "../../services/Users"
|
||||
import Environment from "../../providers/Environment";
|
||||
import DiscordMusicPlayer, { PlayerPlayingEvent, ValidTracks, DiscordMusicPlayerInstance } from "../../providers/DiscordMusicPlayer";
|
||||
import Discord from "../../providers/Discord";
|
||||
|
||||
const EMBEDS = {
|
||||
VOICECHANNEL_JOINED: (data: Message | Interaction) => {
|
||||
return makeSuccessEmbed({
|
||||
title: `Success`,
|
||||
description: `Joined voice channel`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
VOICECHANNEL_INUSE: (data: Message | Interaction) => {
|
||||
|
||||
let haveForceMove = (data instanceof Message) ? (data as Message).member!.permissions.has([Permissions.FLAGS.MOVE_MEMBERS]) : ((data as Interaction).member! as GuildMember).permissions.has([Permissions.FLAGS.MOVE_MEMBERS]);
|
||||
|
||||
return makeErrorEmbed({
|
||||
title: `I can't open portal to parallel universe`,
|
||||
description: `I can't join mutiple voice channel on the same guild.
|
||||
I wish I had a superpower, maybe... one day I will.
|
||||
|
||||
There are also somebody listening to the music in the voice channel I'm currently in.${!haveForceMove ? ` Wait until I finish playing there or I'm alone there. Better yet, join them!` : " Wait until I finish playing there or I'm alone there. Better yet, join them! \n\n**Or... just (ab)use your admin permissions and move me to where you want!**"}`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
VOICECHANNEL_ALREADY_JOINED: (data: Message | Interaction) => {
|
||||
return makeInfoEmbed({
|
||||
title: `I'm already here`,
|
||||
description: `Already in the voice channel you are currently connected to`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You need to be in the voice channel first!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
NOW_PLAYING: (data: Message | Interaction, track: ValidTracks) => {
|
||||
const embed = makeInfoEmbed({
|
||||
title: ' Now playing',
|
||||
icon: '🎵',
|
||||
description: `${track.title}`,
|
||||
user: DiscordProvider.client.user
|
||||
});
|
||||
|
||||
embed.setImage(track.thumbnails[0].url);
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
|
||||
export async function joinVoiceChannelProcedure (data: Interaction | Message, instance: (DiscordMusicPlayerInstance | null), voiceChannel: (VoiceChannel | StageChannel)) {
|
||||
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isAcceptableInteraction = data instanceof Interaction && data.isSelectMenu();
|
||||
const isMessage = data instanceof Message;
|
||||
if ((!isSlashCommand && !isAcceptableInteraction) && !isMessage) return;
|
||||
|
||||
if (!data.member) return;
|
||||
if (!data.channel) return;
|
||||
if (!data.guildId) return;
|
||||
|
||||
if (data.channel instanceof DMChannel) return;
|
||||
if (!(data.channel instanceof TextChannel)) return;
|
||||
|
||||
const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel;
|
||||
if (!channel) return;
|
||||
|
||||
|
||||
// If already in VoiceChannel
|
||||
if (DiscordProvider.client.guilds.cache.get(data.guildId!)!.me!.voice.channelId) {
|
||||
// But, no music instance yet (The bot might just restarted)
|
||||
if (!instance) {
|
||||
// User is in different VoiceChannel
|
||||
if (channel.id !== DiscordProvider.client.guilds.cache.get(data.guildId)?.me?.voice) {
|
||||
//Disconnect it
|
||||
await DiscordProvider.client.guilds.cache.get(data.guildId)?.me?.voice.disconnect();
|
||||
}
|
||||
|
||||
//Create instance for a new one
|
||||
DiscordMusicPlayer.createGuildInstance(data.guildId, voiceChannel);
|
||||
instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
|
||||
instance!.joinVoiceChannel(voiceChannel, data.channel);
|
||||
if(!isAcceptableInteraction)
|
||||
await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] });
|
||||
}
|
||||
// And, already have instance on the guild
|
||||
else {
|
||||
// And, User VoiceChannel is same as the instance
|
||||
if (channel.id === instance.voiceChannel.id) {
|
||||
if(!isAcceptableInteraction)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_ALREADY_JOINED(data)] });
|
||||
else return;
|
||||
}
|
||||
// But, not the same VoiceChannel
|
||||
else {
|
||||
let activeMembers = instance.voiceChannel.members.filter(member => member.user.bot === false);
|
||||
// And someone else is using the bot
|
||||
if(activeMembers.size > 0) {
|
||||
if(!isAcceptableInteraction)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_INUSE(data)] });
|
||||
else return;
|
||||
}
|
||||
// And alone in the VoiceChannel
|
||||
else {
|
||||
// Move to the new voice channel
|
||||
await DiscordProvider.client.guilds.cache.get(data.guildId)?.me?.voice.setChannel(voiceChannel);
|
||||
if(!isAcceptableInteraction)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] });
|
||||
else return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Not in any VoiceChannel
|
||||
else {
|
||||
|
||||
if (instance)
|
||||
await DiscordMusicPlayer.destoryGuildInstance(data.guildId!);
|
||||
|
||||
DiscordMusicPlayer.createGuildInstance(data.guildId, voiceChannel);
|
||||
instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
|
||||
instance!.joinVoiceChannel(voiceChannel, data.channel);
|
||||
if(!isAcceptableInteraction)
|
||||
await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.VOICECHANNEL_JOINED(data)] });
|
||||
}
|
||||
|
||||
if (!instance) return;
|
||||
|
||||
let nowPlayingMessage: any;
|
||||
|
||||
// Register Event Listeners
|
||||
instance.events.on('playing', async (event: PlayerPlayingEvent) => {
|
||||
|
||||
const row = new MessageActionRow()
|
||||
.addComponents(
|
||||
new MessageButton()
|
||||
.setEmoji('▶️')
|
||||
.setLabel(' Open on YouTube')
|
||||
.setURL(encodeURI(`https://www.youtube.com/watch?v=${event.instance.queue.track[0].id}`))
|
||||
.setStyle('LINK'),
|
||||
)
|
||||
|
||||
if (event.instance.textChannel) {
|
||||
await sendMessage(event.instance.textChannel, undefined, { embeds: [EMBEDS.NOW_PLAYING(data, event.instance.queue.track[0])], components: [row] });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default class Join {
|
||||
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if (command.toLowerCase() !== 'join') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if (interaction.isCommand()) {
|
||||
if (typeof interaction.commandName === 'undefined') return;
|
||||
if ((interaction.commandName).toLowerCase() !== 'join') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if (!isSlashCommand && !isMessage) return;
|
||||
|
||||
if (!data.member) return;
|
||||
|
||||
this.joinCommands(data, args);
|
||||
|
||||
}
|
||||
|
||||
public async joinCommands(data: Interaction | Message, args: any) {
|
||||
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
if (!isSlashCommand && !isMessage) return;
|
||||
|
||||
if (!data.member) return;
|
||||
if (!data.channel) return;
|
||||
if (!data.guildId) return;
|
||||
|
||||
if (data.channel instanceof DMChannel) return;
|
||||
if (!(data.channel instanceof TextChannel)) return;
|
||||
|
||||
const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel;
|
||||
if (!channel) return;
|
||||
|
||||
if (!data.member.voice.channel)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] });
|
||||
|
||||
let voiceChannel = data.member.voice.channel;
|
||||
|
||||
|
||||
|
||||
//if(!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) {
|
||||
// DiscordMusicPlayer.createGuildInstance(data.guildId, voiceChannel);
|
||||
//}
|
||||
|
||||
let instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
|
||||
await joinVoiceChannelProcedure(data, instance!, voiceChannel);
|
||||
|
||||
//if(!instance.isConnected())
|
||||
// instance.joinVoiceChannel(voiceChannel, data.channel ? data.channel : undefined);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Message, CommandInteraction, Interaction, VoiceChannel } from "discord.js";
|
||||
import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage";
|
||||
import DiscordProvider from "../../providers/Discord";
|
||||
import Users from "../../services/Users"
|
||||
import Environment from "../../providers/Environment";
|
||||
import DiscordMusicPlayer, { DiscordMusicPlayerInstance } from "../../providers/DiscordMusicPlayer";
|
||||
|
||||
const EMBEDS = {
|
||||
VOICECHANNEL_LEFT: (data: Message | Interaction) => {
|
||||
return makeSuccessEmbed({
|
||||
title: `Success`,
|
||||
description: `Left the voice channel`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You need to be in the voice channel first!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
NO_MUSIC_PLAYING: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `There are no music playing`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You are not in the same voice channel!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class Leave {
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if(command.toLowerCase() !== 'leave') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if(interaction.isCommand()) {
|
||||
if(typeof interaction.commandName === 'undefined') return;
|
||||
if((interaction.commandName).toLowerCase() !== 'leave') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if(!isSlashCommand && !isMessage) return;
|
||||
|
||||
if(!data.member) return;
|
||||
if(!data.guildId) return;
|
||||
|
||||
const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel;
|
||||
if (!data.member.voice.channel)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] });
|
||||
|
||||
if(!channel) return;
|
||||
|
||||
|
||||
let instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
if(!instance)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] });
|
||||
|
||||
if(instance.voiceChannel.id !== data.member.voice.channel.id)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] });
|
||||
|
||||
|
||||
DiscordMusicPlayer.destoryGuildInstance(data.guild?.id!);
|
||||
await sendMessageOrInteractionResponse(data, { embeds:[EMBEDS.VOICECHANNEL_LEFT(data)] });
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Message, CommandInteraction, Interaction, MessageActionRow, TextChannel, MessageButton } from "discord.js";
|
||||
import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage";
|
||||
import DiscordProvider from "../../providers/Discord";
|
||||
import Users from "../../services/Users"
|
||||
import Environment from "../../providers/Environment";
|
||||
import DiscordMusicPlayer, { DiscordMusicPlayerInstance, ValidTracks, Queue } from "../../providers/DiscordMusicPlayer";
|
||||
|
||||
const EMBEDS = {
|
||||
NOW_PLAYING: (data: Message | Interaction, track: ValidTracks) => {
|
||||
const embed = makeInfoEmbed({
|
||||
title: ' Now playing',
|
||||
icon: '🎵',
|
||||
description: `${track.title}`,
|
||||
user: DiscordProvider.client.user
|
||||
});
|
||||
|
||||
embed.setImage(track.thumbnails[0].url);
|
||||
return embed;
|
||||
},
|
||||
NO_MUSIC_PLAYING: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `There are no music playing`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class NowPlaying {
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if(command.toLowerCase() !== 'nowplaying' && command.toLowerCase() !== 'np') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if(interaction.isCommand()) {
|
||||
if(typeof interaction.commandName === 'undefined') return;
|
||||
if((interaction.commandName).toLowerCase() !== 'nowplaying') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if(!isSlashCommand && !isMessage) return;
|
||||
|
||||
if(!data.member) return;
|
||||
if(!data.guildId) return;
|
||||
if(!(data.channel instanceof TextChannel)) return;
|
||||
|
||||
//const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel;
|
||||
//if(!channel) return;
|
||||
|
||||
const instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
if(!instance || !instance.queue.track[0]) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] });
|
||||
|
||||
const row = new MessageActionRow()
|
||||
.addComponents(
|
||||
new MessageButton()
|
||||
.setEmoji('▶️')
|
||||
.setLabel(' Open on YouTube')
|
||||
.setURL(encodeURI(`https://www.youtube.com/watch?v=${instance.queue.track[0].id}`))
|
||||
.setStyle('LINK'),
|
||||
)
|
||||
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NOW_PLAYING(data, instance.queue.track[0])], components: [row] });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import { Message, CommandInteraction, Interaction, VoiceChannel, MessageActionRow, Permissions, DMChannel, TextChannel } from "discord.js";
|
||||
import { makeErrorEmbed, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage";
|
||||
import DiscordProvider from "../../providers/Discord";
|
||||
import Environment from "../../providers/Environment";
|
||||
import DiscordMusicPlayer, { ValidTracks } from "../../providers/DiscordMusicPlayer";
|
||||
import Prisma from "../../providers/Prisma";
|
||||
import { joinVoiceChannelProcedure } from "./Join";
|
||||
|
||||
const EMBEDS = {
|
||||
YOUTUBE_ONLY: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `Only youtube.com link is supported for now`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
INVALID_LINK: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `Invalid Link`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
ADDED_QUEUE: (data: Message | Interaction, track: ValidTracks) => {
|
||||
let embed = makeSuccessEmbed({
|
||||
title: `Song added to queue!`,
|
||||
description: `${track.title}`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
embed.setImage(track.thumbnails[0].url);
|
||||
return embed;
|
||||
},
|
||||
USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You need to be in the voice channel first!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You are not in the same voice channel!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export default class Play {
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if (command.toLowerCase() !== 'play' && command.toLowerCase() !== 'p') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: Interaction) {
|
||||
if (interaction.isCommand()) {
|
||||
if (typeof interaction.commandName === 'undefined') return;
|
||||
if ((interaction.commandName).toLowerCase() !== 'play') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
else if (interaction.isSelectMenu()) {
|
||||
if(!interaction.guild || !interaction.guildId) return;
|
||||
if (!this.tryParseJSONObject(interaction.customId)) return;
|
||||
|
||||
let payload = JSON.parse(interaction.customId);
|
||||
if(!interaction.member?.user?.id) return;
|
||||
|
||||
/*
|
||||
Discord have 100 char custom id char limit
|
||||
So we need to shorten our json.
|
||||
MP_SM stands for MusicPlayer_SearchMenu
|
||||
data.r stands for data.requester
|
||||
data.v stands for data.voiceChannel
|
||||
*/
|
||||
if (typeof payload.module === 'undefined' ||
|
||||
typeof payload.action === 'undefined' ||
|
||||
payload.module !== 'MP_SM' ||
|
||||
payload.action !== 'play') return;
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
let voiceChannel = DiscordProvider.client.guilds.cache.get(interaction.guildId)?.channels.cache.get(payload.data.v);
|
||||
let member = DiscordProvider.client.guilds.cache.get(interaction.guildId)?.members.cache.get(interaction.member?.user?.id);
|
||||
|
||||
if(!member) return;
|
||||
if (!voiceChannel || !(voiceChannel instanceof VoiceChannel)) return;
|
||||
|
||||
|
||||
if (!member.voice.channel)
|
||||
return await sendMessageOrInteractionResponse(interaction, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(interaction)] }, true);
|
||||
|
||||
if (!DiscordMusicPlayer.isGuildInstanceExists(interaction.guildId)) {
|
||||
await joinVoiceChannelProcedure(interaction, null, voiceChannel);
|
||||
}
|
||||
|
||||
let instance = DiscordMusicPlayer.getGuildInstance(interaction.guildId);
|
||||
|
||||
if(instance!.voiceChannel.id !== member.voice.channel.id)
|
||||
return await sendMessageOrInteractionResponse(interaction, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(interaction)] }, true);
|
||||
|
||||
if (!instance!.isConnected()) {
|
||||
await joinVoiceChannelProcedure(interaction, instance!, voiceChannel);
|
||||
instance = DiscordMusicPlayer.getGuildInstance(interaction.guildId);
|
||||
}
|
||||
|
||||
if (!instance) return;
|
||||
|
||||
let result = await DiscordMusicPlayer.searchYouTubeByYouTubeLink(DiscordMusicPlayer.parseYouTubeLink(interaction.values[0]));
|
||||
if (!result) return;
|
||||
|
||||
instance.addTrackToQueue(result);
|
||||
//interaction.editReply({ embeds: [EMBEDS.ADDED_QUEUE(interaction, result)] });
|
||||
return await sendMessageOrInteractionResponse(interaction, { embeds: [EMBEDS.ADDED_QUEUE(interaction, result)] }, true);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if (!isSlashCommand && !isMessage) return;
|
||||
|
||||
if (!data.member) return;
|
||||
if (!data.guild) return;
|
||||
if (!data.guildId) return;
|
||||
|
||||
let query;
|
||||
|
||||
if (isMessage) {
|
||||
if (data === null || !data.guildId || data.member === null || data.guild === null) return;
|
||||
|
||||
if (args.length === 0) {
|
||||
// TODO: Show info
|
||||
//return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.MSINFO(data)] });
|
||||
return;
|
||||
}
|
||||
query = args[0];
|
||||
|
||||
}
|
||||
else if (isSlashCommand) {
|
||||
query = args.getSubcommand();
|
||||
}
|
||||
|
||||
if (!query) return;
|
||||
if (!(data.channel instanceof TextChannel)) return;
|
||||
|
||||
if (!data.member.voice.channel)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] });
|
||||
|
||||
let voiceChannel = data.member.voice.channel;
|
||||
|
||||
if (!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) {
|
||||
await joinVoiceChannelProcedure(data, null, voiceChannel);
|
||||
}
|
||||
|
||||
let instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
|
||||
if(instance!.voiceChannel.id !== data.member.voice.channel.id)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] }, true);
|
||||
|
||||
if (!instance!.isConnected()) {
|
||||
await joinVoiceChannelProcedure(data, instance!, voiceChannel);
|
||||
instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
}
|
||||
|
||||
if (!instance) return;
|
||||
|
||||
if (DiscordMusicPlayer.isYouTubeLink(query)) {
|
||||
let result = await DiscordMusicPlayer.searchYouTubeByYouTubeLink(DiscordMusicPlayer.parseYouTubeLink(query));
|
||||
|
||||
if (!result) return;
|
||||
|
||||
if (isMessage && (data as Message).embeds.length > 0) {
|
||||
(data as Message).suppressEmbeds(true);
|
||||
}
|
||||
|
||||
instance.addTrackToQueue(result);
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.ADDED_QUEUE(data, result)] });
|
||||
} else {
|
||||
let result = await DiscordMusicPlayer.searchYouTubeByQuery(query);
|
||||
|
||||
if (!result) return;
|
||||
instance.addTrackToQueue(result[0]);
|
||||
|
||||
// TODO: Let user choose the video
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.ADDED_QUEUE(data, result[0])] });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
tryParseJSONObject(jsonString: string) {
|
||||
try {
|
||||
let o = JSON.parse(jsonString);
|
||||
if (o && typeof o === "object") {
|
||||
return true;
|
||||
//return o;
|
||||
}
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Message, CommandInteraction, Interaction, VoiceChannel, TextChannel } from "discord.js";
|
||||
import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage";
|
||||
import DiscordProvider from "../../providers/Discord";
|
||||
import Users from "../../services/Users"
|
||||
import Environment from "../../providers/Environment";
|
||||
import DiscordMusicPlayer, { DiscordMusicPlayerInstance, ValidTracks, Queue } from "../../providers/DiscordMusicPlayer";
|
||||
|
||||
const EMBEDS = {
|
||||
QUEUE: (data: Message | Interaction, queue: Queue) => {
|
||||
if(!queue.track[0])
|
||||
return makeInfoEmbed({
|
||||
title: `Queue`,
|
||||
description: `The queue is empty!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
else {
|
||||
if(queue.track.length == 1) {
|
||||
let queueString = `1. [${queue.track[0].title}](${queue.track[0].url})`
|
||||
return makeInfoEmbed({
|
||||
title: `Queue`,
|
||||
description: `Now playing: ${queue.track[0].title}\n\nThere are ${queue.track.length} song in the queue!\n${queueString}`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
else if(queue.track.length >= 10) {
|
||||
let first10 = queue.track.slice(0, 10);
|
||||
let queueString = first10.map((track, index) => `${index + 1}. [${track.title}](${track.url})`).join("\n");
|
||||
return makeInfoEmbed({
|
||||
title: `Queue`,
|
||||
description: `Now playing: ${queue.track[0].title}\nUpcoming song: ${queue.track[1].title}\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}\n${queue.track.length > 10 ? `...${queue.track.length - 10} more songs` : ""}`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
} else {
|
||||
let queueString = queue.track.map((track, index) => `${index + 1}. [${track.title}](${track.url})`).join("\n");
|
||||
return makeInfoEmbed({
|
||||
title: `Queue`,
|
||||
description: `Now playing: ${queue.track[0].title}\nUpcoming song: ${queue.track[1].title}\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
NO_MUSIC_PLAYING: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `There are no music playing`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class QueueCommand {
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if(command.toLowerCase() !== 'queue') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if(interaction.isCommand()) {
|
||||
if(typeof interaction.commandName === 'undefined') return;
|
||||
if((interaction.commandName).toLowerCase() !== 'queue') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if(!isSlashCommand && !isMessage) return;
|
||||
|
||||
if(!data.member) return;
|
||||
if(!data.guildId) return;
|
||||
if(!(data.channel instanceof TextChannel)) return;
|
||||
|
||||
//const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel;
|
||||
//if(!channel) return;
|
||||
|
||||
const instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
if(!instance) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] });
|
||||
|
||||
await sendMessageOrInteractionResponse(data, { embeds:[EMBEDS.QUEUE(data, instance.queue)] });
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Message, CommandInteraction, Interaction, VoiceChannel, MessageActionRow, Permissions, DMChannel, TextChannel, MessageSelectMenu, MessageSelectOptionData } from "discord.js";
|
||||
import { makeErrorEmbed, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage";
|
||||
import DiscordProvider from "../../providers/Discord";
|
||||
import Environment from "../../providers/Environment";
|
||||
import DiscordMusicPlayer, { ValidTracks } from "../../providers/DiscordMusicPlayer";
|
||||
import Prisma from "../../providers/Prisma";
|
||||
import { joinVoiceChannelProcedure } from "./Join";
|
||||
|
||||
const EMBEDS = {
|
||||
SEARCH_RESULT: (data: Message | Interaction, result: ValidTracks[]) => {
|
||||
return makeInfoEmbed({
|
||||
title: `Search result`,
|
||||
description: `${result.length} results found\n\n${result.map(track => `- [${track.title}](${track.url})`).join('\n')}`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
INVALID_LINK: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `Invalid Link`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
ADDED_QUEUE: (data: Message | Interaction, track: ValidTracks) => {
|
||||
let embed = makeSuccessEmbed({
|
||||
title: `Song added to queue!`,
|
||||
description: `${track.title}`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
embed.setImage(track.thumbnails[0].url);
|
||||
return embed;
|
||||
},
|
||||
USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You need to be in the voice channel first!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You are not in the same voice channel!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class Search {
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if (command.toLowerCase() !== 'search') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if (interaction.isCommand()) {
|
||||
if (typeof interaction.commandName === 'undefined') return;
|
||||
if ((interaction.commandName).toLowerCase() !== 'search') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if (!isSlashCommand && !isMessage) return;
|
||||
|
||||
if (!data.member) return;
|
||||
if (!data.guild) return;
|
||||
if (!data.guildId) return;
|
||||
|
||||
let query;
|
||||
|
||||
if (isMessage) {
|
||||
if (data === null || !data.guildId || data.member === null || data.guild === null) return;
|
||||
|
||||
if (args.length === 0) {
|
||||
// TODO: Show info
|
||||
//return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.MSINFO(data)] });
|
||||
return;
|
||||
}
|
||||
query = args[0];
|
||||
|
||||
}
|
||||
else if (isSlashCommand) {
|
||||
query = args.getSubcommand();
|
||||
}
|
||||
|
||||
if (!query) return;
|
||||
if (!(data.channel instanceof TextChannel)) return;
|
||||
|
||||
|
||||
if (!data.member.voice.channel)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] });
|
||||
|
||||
let voiceChannel = data.member.voice.channel;
|
||||
|
||||
if (!DiscordMusicPlayer.isGuildInstanceExists(data.guildId)) {
|
||||
await joinVoiceChannelProcedure(data, null, voiceChannel);
|
||||
}
|
||||
|
||||
let instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
|
||||
if (instance!.voiceChannel.id !== voiceChannel.id)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] });
|
||||
|
||||
if (!instance!.isConnected()) {
|
||||
await joinVoiceChannelProcedure(data, instance!, voiceChannel);
|
||||
instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
}
|
||||
|
||||
if (!instance) return;
|
||||
|
||||
let result = await DiscordMusicPlayer.searchYouTubeByQuery(query);
|
||||
|
||||
if (!result) return;
|
||||
|
||||
const menuOptions: MessageSelectOptionData[] = [];
|
||||
|
||||
const messageSelectMenu = new MessageSelectMenu();
|
||||
/*
|
||||
Discord have 100 char custom id char limit
|
||||
So we need to shorten our json.
|
||||
MP_SM stands for MusicPlayer_SearchMenu
|
||||
data.r stands for data.requester
|
||||
data.v stands for data.voiceChannel
|
||||
*/
|
||||
messageSelectMenu.setCustomId(JSON.stringify({
|
||||
module: 'MP_SM',
|
||||
action: 'play',
|
||||
data: {
|
||||
r: data.member.id,
|
||||
v: voiceChannel.id
|
||||
}
|
||||
}))
|
||||
messageSelectMenu.setPlaceholder('Nothing selected');
|
||||
|
||||
for(let song of result) {
|
||||
if(!song.title) continue;
|
||||
menuOptions.push({
|
||||
label: song.title,
|
||||
description: `${song.durationInSec} sec | ${song.views} views`,
|
||||
value: song.url
|
||||
});
|
||||
}
|
||||
|
||||
messageSelectMenu.addOptions(menuOptions);
|
||||
|
||||
const row = new MessageActionRow();
|
||||
row.addComponents(messageSelectMenu);
|
||||
|
||||
await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SEARCH_RESULT(data, result)], components: [row] });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Message, CommandInteraction, Interaction, VoiceChannel } from "discord.js";
|
||||
import { getEmotes, makeSuccessEmbed, makeErrorEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../../utils/DiscordMessage";
|
||||
import DiscordProvider from "../../providers/Discord";
|
||||
import Users from "../../services/Users"
|
||||
import Environment from "../../providers/Environment";
|
||||
import DiscordMusicPlayer from "../../providers/DiscordMusicPlayer";
|
||||
|
||||
const EMBEDS = {
|
||||
SKIPPED: (data: Message | Interaction) => {
|
||||
return makeSuccessEmbed({
|
||||
title: `Skipped`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
SKIPPED_LASTSONG: (data: Message | Interaction) => {
|
||||
return makeSuccessEmbed({
|
||||
title: `Skipped, that was the last song`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
NO_MUSIC_PLAYING: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `There are no music playing`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You need to be in the voice channel first!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
},
|
||||
USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => {
|
||||
return makeErrorEmbed({
|
||||
title: `You are not in the same voice channel!`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class Skip {
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if (command.toLowerCase() !== 'skip') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if (interaction.isCommand()) {
|
||||
if (typeof interaction.commandName === 'undefined') return;
|
||||
if ((interaction.commandName).toLowerCase() !== 'skip') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if (!isSlashCommand && !isMessage) return;
|
||||
|
||||
if (!data.member) return;
|
||||
if (!data.guildId) return;
|
||||
|
||||
const channel: any = isMessage ? data.member.voice.channel : DiscordProvider.client.guilds.cache.get((data as Interaction).guildId!)!.members.cache.get((data as Interaction).user.id)?.voice.channel;
|
||||
|
||||
if (!data.member.voice.channel)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data)] });
|
||||
|
||||
if (!channel) return;
|
||||
|
||||
let voiceChannel = data.member.voice.channel;
|
||||
|
||||
if(!DiscordMusicPlayer.isGuildInstanceExists(data.guildId))
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] });
|
||||
|
||||
const instance = DiscordMusicPlayer.getGuildInstance(data.guildId);
|
||||
|
||||
if(instance!.voiceChannel.id !== data.member.voice.channel.id)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data)] }, true);
|
||||
|
||||
if(instance?.queue.track.length === 0)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data)] });
|
||||
|
||||
instance!.skipTrack();
|
||||
|
||||
if(instance?.queue.track.length === 0)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SKIPPED_LASTSONG(data)] });
|
||||
else
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SKIPPED(data)] });
|
||||
}
|
||||
}
|
||||
+27
-37
@@ -14,31 +14,7 @@ enum MeasureType {
|
||||
DiscordWebsocket = "discordwebsocket"
|
||||
}
|
||||
|
||||
let measureList = [
|
||||
{
|
||||
type: MeasureType.Ping,
|
||||
title: "☁️ Internet | ",
|
||||
host: "1.1.1.1"
|
||||
},
|
||||
{
|
||||
type: MeasureType.DiscordWebsocket,
|
||||
title: "🚀 Discord Websocket | "
|
||||
},
|
||||
{
|
||||
type: MeasureType.DiscordHTTPPing,
|
||||
title: "🏓 Discord HTTP | "
|
||||
}
|
||||
];
|
||||
|
||||
if(fs.existsSync(path.join(process.cwd(), 'configs/Ping.json'))) {
|
||||
try {
|
||||
const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/Ping.json'));
|
||||
const jsonData = JSON.parse(rawData.toString());
|
||||
measureList = jsonData;
|
||||
} catch (err) {
|
||||
Logger.error("Unable to load custom Ping config: " + err);
|
||||
}
|
||||
}
|
||||
let measureList: any = [];
|
||||
|
||||
const EMBEDS = {
|
||||
PING_INFO: (data: Message | Interaction, description: string) => {
|
||||
@@ -53,15 +29,29 @@ const EMBEDS = {
|
||||
|
||||
export default class Ping {
|
||||
|
||||
async init() {
|
||||
|
||||
if (fs.existsSync(path.join(process.cwd(), 'configs/Ping.json'))) {
|
||||
try {
|
||||
const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/Ping.json'));
|
||||
const jsonData = JSON.parse(rawData.toString());
|
||||
measureList = jsonData;
|
||||
} catch (err) {
|
||||
Logger.error("Unable to load custom Ping config: " + err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if(command.toLowerCase() !== 'ping') return;
|
||||
if (command.toLowerCase() !== 'ping') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if(interaction.isCommand()) {
|
||||
if(typeof interaction.commandName === 'undefined') return;
|
||||
if((interaction.commandName).toLowerCase() !== 'ping') return;
|
||||
if (interaction.isCommand()) {
|
||||
if (typeof interaction.commandName === 'undefined') return;
|
||||
if ((interaction.commandName).toLowerCase() !== 'ping') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
@@ -70,7 +60,7 @@ export default class Ping {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if(!isSlashCommand && !isMessage) return;
|
||||
if (!isSlashCommand && !isMessage) return;
|
||||
|
||||
let placeholder;
|
||||
|
||||
@@ -81,7 +71,7 @@ export default class Ping {
|
||||
});
|
||||
|
||||
let beforeEditDate = Date.now();
|
||||
placeholder = await sendMessageOrInteractionResponse(data, { embeds:[loadingEmbed]} );
|
||||
placeholder = await sendMessageOrInteractionResponse(data, { embeds: [loadingEmbed] });
|
||||
let afterEditDate = Date.now();
|
||||
|
||||
|
||||
@@ -89,19 +79,19 @@ export default class Ping {
|
||||
|
||||
// TODO: Optimize speed of this
|
||||
|
||||
for(let entry of measureList) {
|
||||
for (let entry of measureList) {
|
||||
|
||||
let stringCurrent = `${entry.title}`;
|
||||
|
||||
if(entry.type === MeasureType.Ping) {
|
||||
if (entry.type === MeasureType.Ping) {
|
||||
const res = await NodePing.promise.probe(entry.host!, { min_reply: 4 });
|
||||
|
||||
if(!res.alive) {
|
||||
if (!res.alive) {
|
||||
stringCurrent += "Failed";
|
||||
return desString.push(stringCurrent);
|
||||
}
|
||||
|
||||
if(res.avg === 'unknown' || res.min === 'unknown' || res.max === 'unknown') {
|
||||
if (res.avg === 'unknown' || res.min === 'unknown' || res.max === 'unknown') {
|
||||
stringCurrent += "Failed";
|
||||
return desString.push(stringCurrent);
|
||||
}
|
||||
@@ -121,8 +111,8 @@ export default class Ping {
|
||||
desString.push("");
|
||||
desString.push("💻 Running on " + `${os.hostname()}${Environment.get().NODE_ENV === "development" ? ' / Development Environment' : ''}`);
|
||||
|
||||
if(isSlashCommand) return await data.editReply({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] });
|
||||
else if(typeof placeholder !== "undefined" && isMessage && placeholder instanceof Message) return await placeholder.edit({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] });
|
||||
if (isSlashCommand) return await data.editReply({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] });
|
||||
else if (typeof placeholder !== "undefined" && isMessage && placeholder instanceof Message) return await placeholder.edit({ embeds: [EMBEDS.PING_INFO(data, desString.join('\n'))] });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Message, CommandInteraction, Interaction } from "discord.js";
|
||||
import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, sendMessage, sendMessageOrInteractionResponse, makeInfoEmbed } from "../utils/DiscordMessage";
|
||||
import DiscordProvider from "../providers/Discord";
|
||||
import Users from "../services/Users"
|
||||
import Environment from "../providers/Environment";
|
||||
|
||||
const EMBEDS = {
|
||||
STATS_INFO: (data: Message | Interaction) => {
|
||||
let message;
|
||||
|
||||
return makeInfoEmbed({
|
||||
title: `Stats`,
|
||||
icon: '📊',
|
||||
description: `Serving in ${DiscordProvider.client.guilds.cache.size} servers\n${DiscordProvider.client.guilds.cache.map((g) => g.memberCount).reduce((a, c) => a + c)} users`,
|
||||
user: (data instanceof Interaction) ? data.user : data.author
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class Stats {
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if(command.toLowerCase() !== 'stats') return;
|
||||
await this.process(message, args);
|
||||
}
|
||||
|
||||
async interactionCreate(interaction: CommandInteraction) {
|
||||
if(interaction.isCommand()) {
|
||||
if(typeof interaction.commandName === 'undefined') return;
|
||||
if((interaction.commandName).toLowerCase() !== 'stats') return;
|
||||
await this.process(interaction, interaction.options);
|
||||
}
|
||||
}
|
||||
|
||||
async process(data: Interaction | Message, args: any) {
|
||||
const isSlashCommand = data instanceof CommandInteraction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if(!isSlashCommand && !isMessage) return;
|
||||
|
||||
return await sendMessageOrInteractionResponse(data, { embeds:[EMBEDS.STATS_INFO(data)] });
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { registerAllGuildsCommands, unregisterAllGuildsCommands } from "../../ut
|
||||
import Prisma from "../../providers/Prisma";
|
||||
import Users from "../../services/Users";
|
||||
|
||||
import {Promise, reject} from "bluebird";
|
||||
import { Promise, reject } from "bluebird";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Logger from "../../libs/Logger";
|
||||
@@ -124,20 +124,21 @@ let Announcements = {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'))) {
|
||||
try {
|
||||
const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'));
|
||||
const jsonData = JSON.parse(rawData.toString());
|
||||
Announcements = jsonData;
|
||||
} catch (err) {
|
||||
Logger.error("Unable to load custom ServiceAnnouncement config: " + err);
|
||||
}
|
||||
} else {
|
||||
fs.writeFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'), JSON.stringify(Announcements, null, 4), 'utf8');
|
||||
}
|
||||
|
||||
export default class InteractionManager {
|
||||
|
||||
async init() {
|
||||
|
||||
if (fs.existsSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'))) {
|
||||
try {
|
||||
const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'));
|
||||
const jsonData = JSON.parse(rawData.toString());
|
||||
Announcements = jsonData;
|
||||
} catch (err) {
|
||||
Logger.error("Unable to load custom ServiceAnnouncement config: " + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onCommand(command: string, args: any, message: Message) {
|
||||
if (command.toLowerCase() !== 'serviceannouncement') return;
|
||||
await this.process(message, args);
|
||||
@@ -215,7 +216,7 @@ export default class InteractionManager {
|
||||
toSend.set(Guild, channel);
|
||||
}
|
||||
|
||||
let placeholder = await sendMessage(data.channel!, undefined, { embeds:[EMBEDS.SENDING_SERVICE_ANNOUNCEMENT(data)] });
|
||||
let placeholder = await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.SENDING_SERVICE_ANNOUNCEMENT(data)] });
|
||||
|
||||
await Promise.map(toSend, element => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
@@ -223,14 +224,14 @@ export default class InteractionManager {
|
||||
let Channel: TextChannel = element[1];
|
||||
try {
|
||||
const guildObject = DiscordProvider.client.guilds.cache.get(Guild.id);
|
||||
if(!guildObject)
|
||||
if (!guildObject)
|
||||
throw new Error('Guild not found');
|
||||
Logger.info(`Sending Service Announcement to ${guildObject?.name} (${guildObject.id}) #${Channel.name} (${Channel.id})`);
|
||||
await sendMessage(Channel, undefined, { embeds:[EMBEDS.MAKE_PAYLOAD(data, embed)] })
|
||||
await sendMessage(Channel, undefined, { embeds: [EMBEDS.MAKE_PAYLOAD(data, embed)] })
|
||||
} catch (err) {
|
||||
withError = true;
|
||||
const guildObject = DiscordProvider.client.guilds.cache.get(Guild.id);
|
||||
if(guildObject)
|
||||
if (guildObject)
|
||||
Logger.info(`Unable to send Service Announcement to ${guildObject?.name} (${guildObject.id}) #${Channel.name} (${Channel.id})`);
|
||||
|
||||
reject(err);
|
||||
@@ -241,16 +242,16 @@ export default class InteractionManager {
|
||||
});
|
||||
}, { concurrency: 2 });
|
||||
|
||||
if(withError) {
|
||||
if(isMessage)
|
||||
if (withError) {
|
||||
if (isMessage)
|
||||
(placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT_WITH_ERRORS(data)] });
|
||||
else if(isSlashCommand)
|
||||
else if (isSlashCommand)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT_WITH_ERRORS(data)] }, true);
|
||||
}
|
||||
else {
|
||||
if(isMessage)
|
||||
if (isMessage)
|
||||
(placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT(data)] });
|
||||
else if(isSlashCommand)
|
||||
else if (isSlashCommand)
|
||||
return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT(data)] }, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import Configuration from './Configuration';
|
||||
|
||||
class App {
|
||||
|
||||
public readonly version = `0.05${Environment.get().NODE_ENV === "development" ? ' / Development Build' : ''}`;
|
||||
public readonly version = `0.07${Environment.get().NODE_ENV === "development" ? ' / Development Build' : ''}`;
|
||||
|
||||
public loadConfig(): void {
|
||||
Logger.log('info', 'Loading configuration');
|
||||
|
||||
@@ -10,6 +10,15 @@ class Configuration {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
this.copyExampleIfNotExists("Ping.json");
|
||||
this.copyExampleIfNotExists("ServiceAnnouncement.json");
|
||||
}
|
||||
|
||||
private copyExampleIfNotExists(file: string): void {
|
||||
const dir = path.join(process.cwd(), 'configs/');
|
||||
if (!fs.existsSync(path.join(dir, file))) {
|
||||
fs.copyFileSync(path.join(process.cwd(), 'configs/', `${file.replace('.json', '.example.json')}`), path.join(dir, file));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make a real providers like Environment
|
||||
|
||||
@@ -13,6 +13,16 @@ import Discord_InteractionManager from "../discord/InteractionManager";
|
||||
import Discord_MembershipScreening from "../discord/MembershipScreening";
|
||||
import Discord_osu from "../discord/osu";
|
||||
import Discord_UserInfo from "../discord/UserInfo";
|
||||
import Discord_Stats from "../discord/Stats";
|
||||
|
||||
import Discord_MusicPlayer_Play from "../discord/MusicPlayer/Play";
|
||||
import Discord_MusicPlayer_Skip from "../discord/MusicPlayer/Skip";
|
||||
import Discord_MusicPlayer_Join from "../discord/MusicPlayer/Join";
|
||||
import Discord_MusicPlayer_Leave from "../discord/MusicPlayer/Leave";
|
||||
import Discord_MusicPlayer_Queue from "../discord/MusicPlayer/Queue";
|
||||
import Discord_MusicPlayer_Search from "../discord/MusicPlayer/Search";
|
||||
import Discord_MusicPlayer_Search_NowPlaying from "../discord/MusicPlayer/NowPlaying";
|
||||
|
||||
import Discord_Developer_ServiceAnnouncement from "../discord/developer/ServiceAnnouncement"
|
||||
|
||||
import Cache from "./Cache";
|
||||
@@ -24,8 +34,8 @@ class Discord {
|
||||
|
||||
constructor () {
|
||||
this.client = new Client({
|
||||
partials: ['MESSAGE', 'CHANNEL', 'REACTION'],
|
||||
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_PRESENCES]
|
||||
//partials: ['MESSAGE', 'CHANNEL', 'REACTION'],
|
||||
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_PRESENCES, Intents.FLAGS.GUILD_VOICE_STATES]
|
||||
});
|
||||
this.loaded_module = {};
|
||||
}
|
||||
@@ -45,6 +55,15 @@ class Discord {
|
||||
this.loaded_module["Discord_Say"] = new Discord_Say();
|
||||
this.loaded_module["Discord_osu"] = new Discord_osu();
|
||||
this.loaded_module["Discord_UserInfo"] = new Discord_UserInfo();
|
||||
this.loaded_module["Discord_Stats"] = new Discord_Stats();
|
||||
|
||||
this.loaded_module["Discord_MusicPlayer_Play"] = new Discord_MusicPlayer_Play();
|
||||
this.loaded_module["Discord_MusicPlayer_Skip"] = new Discord_MusicPlayer_Skip();
|
||||
this.loaded_module["Discord_MusicPlayer_Join"] = new Discord_MusicPlayer_Join();
|
||||
this.loaded_module["Discord_MusicPlayer_Leave"] = new Discord_MusicPlayer_Leave();
|
||||
this.loaded_module["Discord_MusicPlayer_Queue"] = new Discord_MusicPlayer_Queue();
|
||||
this.loaded_module["Discord_MusicPlayer_Search"] = new Discord_MusicPlayer_Search();
|
||||
this.loaded_module["Discord_MusicPlayer_Search_NowPlaying"] = new Discord_MusicPlayer_Search_NowPlaying();
|
||||
|
||||
this.loaded_module["MembershipScreening"] = new Discord_MembershipScreening();
|
||||
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
import playdl, { YouTubeVideo, InfoData } from "play-dl";
|
||||
import { Message, VoiceChannel, Snowflake, TextChannel, StageChannel, Guild } from "discord.js";
|
||||
import { AudioPlayer, VoiceConnection, createAudioPlayer, joinVoiceChannel, createAudioResource, StreamType, VoiceConnectionStatus, AudioPlayerStatus, NoSubscriberBehavior, VoiceConnectionDestroyedState, VoiceConnectionState, DiscordGatewayAdapterCreator, AudioResource } from "@discordjs/voice";
|
||||
import { EventEmitter } from "stream";
|
||||
|
||||
import DiscordProvider from "./Discord";
|
||||
import Environment from "./Environment";
|
||||
|
||||
export type ValidTracks = YouTubeVideo;
|
||||
|
||||
if (Environment.get().YOUTUBE_COOKIE_BASE64) {
|
||||
playdl.setToken({
|
||||
youtube: {
|
||||
cookie: Buffer.from(Environment.get().YOUTUBE_COOKIE_BASE64, 'base64').toString()
|
||||
}
|
||||
})
|
||||
}
|
||||
export class Queue {
|
||||
public track: (ValidTracks)[] = [];
|
||||
}
|
||||
|
||||
export class YouTubeLink {
|
||||
public videoId: string;
|
||||
public list?: string;
|
||||
|
||||
constructor(videoId: string, list: string) {
|
||||
this.videoId = videoId;
|
||||
this.list = list;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayerPlayingEvent {
|
||||
public instance: DiscordMusicPlayerInstance;
|
||||
|
||||
constructor(instance: DiscordMusicPlayerInstance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
}
|
||||
|
||||
export class DiscordMusicPlayerInstance {
|
||||
public queue: Queue;
|
||||
public player: AudioPlayer;
|
||||
public textChannel?: TextChannel;
|
||||
public voiceChannel: (VoiceChannel | StageChannel);
|
||||
public voiceConnection?: VoiceConnection;
|
||||
|
||||
public readonly events: EventEmitter;
|
||||
|
||||
constructor({ voiceChannel }: { voiceChannel: (VoiceChannel | StageChannel) }) {
|
||||
this.queue = new Queue();
|
||||
this.player = createAudioPlayer({
|
||||
behaviors: {
|
||||
noSubscriber: NoSubscriberBehavior.Pause,
|
||||
maxMissedFrames: 1000
|
||||
}
|
||||
});
|
||||
this.voiceChannel = voiceChannel;
|
||||
this.events = new EventEmitter();
|
||||
|
||||
this.player.on(AudioPlayerStatus.Idle, async (oldStage, newStage) => {
|
||||
//The player stopped
|
||||
if (newStage.status === AudioPlayerStatus.Idle && oldStage.status !== AudioPlayerStatus.Idle) {
|
||||
if (this.queue.track.length !== 0) {
|
||||
this.queue.track.shift();
|
||||
if (this.queue.track.length > 0) {
|
||||
this.playTrack(this.queue.track[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.player.on(AudioPlayerStatus.Playing, (oldState: any, newState: any) => {
|
||||
this.events.emit('playing', new PlayerPlayingEvent(this));
|
||||
});
|
||||
}
|
||||
|
||||
public isReady() {
|
||||
if (!this.voiceConnection) return false;
|
||||
return this.voiceConnection.state.status === VoiceConnectionStatus.Ready;
|
||||
}
|
||||
|
||||
public isConnected() {
|
||||
if (!this.voiceConnection) return false;
|
||||
|
||||
if (this.voiceConnection.state.status === VoiceConnectionStatus.Destroyed) return false;
|
||||
if (this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public joinVoiceChannel(voiceChannel: (VoiceChannel | StageChannel), textChannel?: TextChannel) {
|
||||
const permissions = voiceChannel.permissionsFor(DiscordProvider.client.user!);
|
||||
if (!permissions)
|
||||
throw new Error("No permissions");
|
||||
|
||||
if (!voiceChannel.joinable || !permissions.has("CONNECT"))
|
||||
throw new Error("No permissions");
|
||||
|
||||
if (textChannel)
|
||||
this.textChannel = textChannel;
|
||||
|
||||
this.voiceConnection = joinVoiceChannel({
|
||||
channelId: this.voiceChannel.id,
|
||||
guildId: this.voiceChannel.guild.id,
|
||||
adapterCreator: this.voiceChannel.guild.voiceAdapterCreator
|
||||
});
|
||||
|
||||
this.voiceConnection.on(VoiceConnectionStatus.Ready, (oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||
let currentVC = DiscordProvider.client.guilds.cache.get(voiceChannel.guild.id)?.me?.voice.channel;
|
||||
if (currentVC && currentVC.id !== this.voiceChannel.id) {
|
||||
this.voiceChannel = currentVC;
|
||||
}
|
||||
});
|
||||
|
||||
this.voiceConnection.on(VoiceConnectionStatus.Disconnected, (oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||
// TODO: Handle on Disconnect by user (Right-click > Disconnect)
|
||||
});
|
||||
}
|
||||
|
||||
public async leaveVoiceChannel() {
|
||||
|
||||
if (this.player)
|
||||
this.player.pause();
|
||||
|
||||
if (DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice) {
|
||||
await DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public addTrackToQueue(track: (ValidTracks)) {
|
||||
|
||||
if (this.queue.track.length === 0) {
|
||||
this.queue.track.push(track);
|
||||
this.playTrack(this.queue.track[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.queue.track.push(track);
|
||||
}
|
||||
|
||||
public async playTrack(track: ValidTracks) {
|
||||
|
||||
if (!this.voiceConnection) throw new Error("No voice connection");
|
||||
|
||||
const stream = await playdl.stream(track.url);
|
||||
const resource = createAudioResource(stream.stream, {
|
||||
inputType: stream.type
|
||||
});
|
||||
|
||||
this.player.play(resource)
|
||||
this.voiceConnection.subscribe(this.player)
|
||||
}
|
||||
|
||||
public async skipTrack() {
|
||||
if (!this.voiceConnection) throw new Error("No voice connection");
|
||||
|
||||
if (this.queue.track.length > 1) {
|
||||
this.queue.track.shift();
|
||||
this.playTrack(this.queue.track[0]);
|
||||
}
|
||||
else {
|
||||
this.queue.track.shift();
|
||||
this.player.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public async destroy() {
|
||||
await this.leaveVoiceChannel();
|
||||
|
||||
if (this.voiceConnection) {
|
||||
this.voiceConnection.removeAllListeners();
|
||||
if (this.voiceConnection.state.status !== 'destroyed')
|
||||
this.voiceConnection.destroy();
|
||||
}
|
||||
|
||||
if (this.player) {
|
||||
this.player.removeAllListeners();
|
||||
this.player.stop(true);
|
||||
}
|
||||
|
||||
this.queue.track = [];
|
||||
this.textChannel = undefined;
|
||||
this.voiceConnection = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DiscordMusicPlayer {
|
||||
|
||||
public GuildQueue = new Map();
|
||||
|
||||
public getGuildInstance(guildId: Snowflake): (DiscordMusicPlayerInstance | null) {
|
||||
if (!this.isGuildInstanceExists(guildId)) return null;
|
||||
return this.GuildQueue.get(guildId);
|
||||
}
|
||||
|
||||
public isGuildInstanceExists(guildId: Snowflake) {
|
||||
return this.GuildQueue.has(guildId);
|
||||
}
|
||||
|
||||
public createGuildInstance(guildId: Snowflake, voiceChannel: (VoiceChannel | StageChannel)) {
|
||||
this.GuildQueue.set(guildId, new DiscordMusicPlayerInstance({ voiceChannel }));
|
||||
}
|
||||
|
||||
public async destoryGuildInstance(guild: (Guild | Snowflake)) {
|
||||
|
||||
let guildId: Snowflake = guild instanceof Guild ? guild.id : guild;
|
||||
|
||||
if (this.isGuildInstanceExists(guildId)) {
|
||||
await this.GuildQueue.get(guildId).destroy();
|
||||
this.GuildQueue.delete(guildId);
|
||||
}
|
||||
}
|
||||
|
||||
public async searchYouTubeByQuery(query: string) {
|
||||
const searched: YouTubeVideo[] = await playdl.search(query, { source: { youtube: "video" } });
|
||||
if (searched.length == 0) return null;
|
||||
return searched;
|
||||
}
|
||||
|
||||
public async searchYouTubeByYouTubeLink(youtubeLink: YouTubeLink) {
|
||||
|
||||
// Search the url
|
||||
const searched: YouTubeVideo[] = await playdl.search("https://www.youtube.com/watch?v=" + youtubeLink.videoId, { source: { youtube: "video" } });
|
||||
for (let video of searched) {
|
||||
if (video.id === youtubeLink.videoId)
|
||||
return video;
|
||||
}
|
||||
|
||||
// Serch the video Id
|
||||
const searched2: YouTubeVideo[] = await playdl.search(youtubeLink.videoId, { source: { youtube: "video" } });
|
||||
for (let video of searched2) {
|
||||
if (video.id === youtubeLink.videoId)
|
||||
return video;
|
||||
}
|
||||
|
||||
// Last resort, search the title
|
||||
const videoInfo = await playdl.video_basic_info("https://www.youtube.com/watch?v=" + youtubeLink.videoId);
|
||||
if (videoInfo?.video_details?.title) {
|
||||
const searched: YouTubeVideo[] = await playdl.search(videoInfo.video_details.title, { source: { youtube: "video" } });
|
||||
for (let video of searched) {
|
||||
if (video.id === youtubeLink.videoId)
|
||||
return video;
|
||||
}
|
||||
}
|
||||
|
||||
let yt_info = await playdl.video_info("https://www.youtube.com/watch?v=" + youtubeLink.videoId);
|
||||
if(yt_info) {
|
||||
return new YouTubeVideo({
|
||||
id: yt_info.video_details.id,
|
||||
url: yt_info.video_details.url,
|
||||
type: yt_info.video_details.type,
|
||||
title: yt_info.video_details.title,
|
||||
description: yt_info.video_details.description,
|
||||
durationRaw: yt_info.video_details.durationRaw,
|
||||
durationInSec: yt_info.video_details.durationInSec,
|
||||
uploadedAt: yt_info.video_details.uploadedAt,
|
||||
upcoming: yt_info.video_details.upcoming,
|
||||
views: yt_info.video_details.views,
|
||||
thumbnails: yt_info.video_details.thumbnails,
|
||||
channel: yt_info.video_details.channel,
|
||||
likes: yt_info.video_details.likes,
|
||||
live: yt_info.video_details.live,
|
||||
private: yt_info.video_details.private,
|
||||
tags: yt_info.video_details.tags,
|
||||
discretionAdvised: yt_info.video_details.discretionAdvised,
|
||||
music: yt_info.video_details.music
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public isYouTubeLink(link: string): boolean {
|
||||
try {
|
||||
this.parseYouTubeLink(link);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public parseYouTubeLink(query: string): YouTubeLink {
|
||||
if (query.startsWith('https://www.youtube.com/watch?v=') || query.startsWith('http://www.youtube.com/watch?v=')) {
|
||||
let data = this.parseURLQuery(query);
|
||||
if (!data.v)
|
||||
throw new Error('YouTube link is invalid');
|
||||
return {
|
||||
videoId: data.v,
|
||||
list: (data.list ? data.list : undefined)
|
||||
}
|
||||
}
|
||||
else if (query.startsWith('https://youtu.be/') || query.startsWith('http://youtu.be/')) {
|
||||
//Get youtube video id after the url
|
||||
let videoId = query.split('/')[3];
|
||||
|
||||
if (!videoId) throw new Error('YouTube link is invalid');
|
||||
if (query.split('/')[4]) throw new Error('YouTube link is invalid');
|
||||
|
||||
return {
|
||||
videoId: videoId
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('YouTube link is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
private parseURLQuery(query: string) {
|
||||
let queryObject: any = {};
|
||||
if (query.indexOf('?') >= 0) {
|
||||
let queryString = query.split('?')[1];
|
||||
let queryArray = queryString.split('&');
|
||||
for (let i = 0; i < queryArray.length; i++) {
|
||||
let queryPair = queryArray[i].split('=');
|
||||
queryObject[queryPair[0]] = queryPair[1];
|
||||
}
|
||||
}
|
||||
return queryObject;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const DiscordMusicPlayer_Instance = new DiscordMusicPlayer();
|
||||
export default DiscordMusicPlayer_Instance;
|
||||
@@ -41,6 +41,7 @@ class Environment {
|
||||
const PRIVATE_BOT = process.env.PRIVATE_BOT;
|
||||
|
||||
const OSU_API_KEY = process.env.OSU_API_KEY;
|
||||
const YOUTUBE_COOKIE_BASE64 = process.env.YOUTUBE_COOKIE_BASE64;
|
||||
|
||||
return {
|
||||
NODE_ENV,
|
||||
@@ -49,7 +50,8 @@ class Environment {
|
||||
DEVELOPER_IDS,
|
||||
PRIVATE_BOT,
|
||||
|
||||
OSU_API_KEY
|
||||
OSU_API_KEY,
|
||||
YOUTUBE_COOKIE_BASE64
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ export async function sendMessage(channel: TextChannel | DMChannel | BaseGuildTe
|
||||
|
||||
try { message = await channel.send(options); }
|
||||
catch(error) {
|
||||
if(typeof user === 'undefined') return;
|
||||
if(typeof user === 'undefined') {
|
||||
return Logger.error(`Cannot find available destinations to send the message CID: ${channel.id} C_ERR: ${error}`);
|
||||
}
|
||||
try { message = await user.send(options); }
|
||||
catch(errorDM) {
|
||||
Logger.error(`Cannot find available destinations to send the message CID: ${channel.id} UID: ${user.id} C_ERR: ${error} DM_ERR: ${errorDM}`);
|
||||
@@ -99,7 +101,7 @@ export async function sendMessageOrInteractionResponse(data: Message | Interacti
|
||||
const isSlashCommand = data instanceof Interaction && data.isCommand();
|
||||
const isMessage = data instanceof Message;
|
||||
|
||||
if(isSlashCommand) {
|
||||
if(isSlashCommand || (data instanceof Interaction && data.isSelectMenu())) {
|
||||
if(!data.replied) {
|
||||
let message;
|
||||
try {
|
||||
|
||||
@@ -38,6 +38,54 @@
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.4.0.tgz#b6488286a1cc7b41b644d7e6086f25a1c1e6f837"
|
||||
integrity sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==
|
||||
|
||||
"@discordjs/node-pre-gyp@^0.4.2":
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/node-pre-gyp/-/node-pre-gyp-0.4.2.tgz#37dd27f8d353eeb87372fcce059c5249550b37ab"
|
||||
integrity sha512-V239Czn+DXFGLhhuccwEDBoTdgMGrRu30dOlzm1GzrSIjwFj01ZJerNX7x+CEX1NG1Q/1gGfOOkeZFNHjycrRA==
|
||||
dependencies:
|
||||
detect-libc "^1.0.3"
|
||||
https-proxy-agent "^5.0.0"
|
||||
make-dir "^3.1.0"
|
||||
node-fetch "^2.6.5"
|
||||
nopt "^5.0.0"
|
||||
npmlog "^5.0.1"
|
||||
rimraf "^3.0.2"
|
||||
semver "^7.3.5"
|
||||
tar "^6.1.11"
|
||||
|
||||
"@discordjs/opus@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/opus/-/opus-0.7.0.tgz#69e9611a6412ea470f8e640f54478da89326ca3b"
|
||||
integrity sha512-3Xxa3dh7taSDwBAR5fLALZ/KTxvbMmHCMxYLYve6NlPO7Ms1CLmKqp/R4ZoVzkRGQVUVWEhaB1s0v9jfa2tfDg==
|
||||
dependencies:
|
||||
"@discordjs/node-pre-gyp" "^0.4.2"
|
||||
node-addon-api "^4.2.0"
|
||||
|
||||
"@discordjs/rest@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-0.3.0.tgz#89e06a42b168c91598891d6bf860353e28fba5d2"
|
||||
integrity sha512-F9aeP3odlAlllM1ciBZLdd+adiAyBj4VaZBejj4UMj4afE2wfCkNTGvYYiRxrXUE9fN7e/BuDP2ePl0tVA2m7Q==
|
||||
dependencies:
|
||||
"@discordjs/collection" "^0.4.0"
|
||||
"@sapphire/async-queue" "^1.1.9"
|
||||
"@sapphire/snowflake" "^3.0.1"
|
||||
discord-api-types "^0.26.1"
|
||||
form-data "^4.0.0"
|
||||
node-fetch "^2.6.5"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@discordjs/voice@^0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/voice/-/voice-0.8.0.tgz#5d790fc25b883698f6eb7762efe1af00b6440947"
|
||||
integrity sha512-o0JfVLMs3eLjUzPf6oxMydEeum40I7xzfUc66SLN+RrKpSAsTbngf5qnCF53nm+KDNSvrwg1AZqNm4LEAdxJIA==
|
||||
dependencies:
|
||||
"@types/ws" "^8.2.0"
|
||||
discord-api-types "^0.26.1"
|
||||
prism-media "^1.3.2"
|
||||
tiny-typed-emitter "^2.1.0"
|
||||
tslib "^2.3.1"
|
||||
ws "^8.4.2"
|
||||
|
||||
"@mapbox/node-pre-gyp@^1.0.0":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58"
|
||||
@@ -71,14 +119,19 @@
|
||||
integrity sha512-bHYubuItSN/DGYo36aDu7xJiJmK52JOSHs4MK+KbceAtwS20BCWadRgtpQ3iZ2EXfN/B1T0iCXlNraaNwnpU2w==
|
||||
|
||||
"@sapphire/async-queue@^1.1.9":
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.1.9.tgz#ce69611c8753c4affd905a7ef43061c7eb95c01b"
|
||||
integrity sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ==
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.2.0.tgz#7a56afd318101d338433d7180ebd6af349243268"
|
||||
integrity sha512-O5ND5Ljpef86X5oy8zXorQ754TMjWALcPSAgPBu4+76HLtDTrNoDyzU2uGE2G4A8Wv51u0MXHzGQ0WZ4GMtpIw==
|
||||
|
||||
"@sapphire/snowflake@^3.0.1":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.1.0.tgz#1c2d9781d3b1de0bcb02b7079898947ce754d394"
|
||||
integrity sha512-K+OiqXSx4clIaXcoaghrCV56zsm3bZZ5SBpgJkgvAKegFFdETMntHviUfypjt8xVleIuDaNyQA4APOIl3BMcxg==
|
||||
|
||||
"@sindresorhus/is@^4.0.1", "@sindresorhus/is@^4.2.0":
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.3.0.tgz#344fd9bf808a84567ba563d00cc54b2f428dbab1"
|
||||
integrity sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.4.0.tgz#e277e5bdbdf7cb1e20d320f02f5e2ed113cd3185"
|
||||
integrity sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ==
|
||||
|
||||
"@types/bluebird@^3.5.36":
|
||||
version "3.5.36"
|
||||
@@ -99,9 +152,9 @@
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*":
|
||||
version "17.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.12.tgz#f7aa331b27f08244888c47b7df126184bc2339c5"
|
||||
integrity sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==
|
||||
version "17.0.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.13.tgz#5ed7ed7c662948335fcad6c412bb42d99ea754e3"
|
||||
integrity sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw==
|
||||
|
||||
"@types/node@^16.11.6":
|
||||
version "16.11.21"
|
||||
@@ -128,7 +181,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.1.tgz#cdab1b4779f6b1718a08de89d92d2603b71950cb"
|
||||
integrity sha512-I6OUIZ5cYRk5lp14xSOAiXjWrfVoMZVjDuevBYgQDYzZIjsf2CAISpEcXOkFAtpAHbmWIDLcZObejqny/9xq5Q==
|
||||
|
||||
"@types/ws@^8.2.2":
|
||||
"@types/ws@^8.2.0", "@types/ws@^8.2.2":
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21"
|
||||
integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==
|
||||
@@ -178,7 +231,7 @@ arg@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||
|
||||
async@^3.2.3:
|
||||
async@^3.1.0:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
||||
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
||||
@@ -223,6 +276,13 @@ buffer-from@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
bufferutil@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433"
|
||||
integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==
|
||||
dependencies:
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
call-bind@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||
@@ -404,7 +464,7 @@ discord-api-types@^0.24.0:
|
||||
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.24.0.tgz#9e429b8a1ddb4147134dfb3109093422de7ec549"
|
||||
integrity sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==
|
||||
|
||||
discord-api-types@^0.26.0:
|
||||
discord-api-types@^0.26.0, discord-api-types@^0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.26.1.tgz#726f766ddc37d60da95740991d22cb6ef2ed787b"
|
||||
integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==
|
||||
@@ -657,12 +717,24 @@ kuler@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
|
||||
integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
|
||||
|
||||
libsodium-wrappers@^0.7.9:
|
||||
version "0.7.9"
|
||||
resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#4ffc2b69b8f7c7c7c5594a93a4803f80f6d0f346"
|
||||
integrity sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ==
|
||||
dependencies:
|
||||
libsodium "^0.7.0"
|
||||
|
||||
libsodium@^0.7.0:
|
||||
version "0.7.9"
|
||||
resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b"
|
||||
integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A==
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
||||
|
||||
logform@^2.3.2:
|
||||
logform@^2.2.0, logform@^2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/logform/-/logform-2.3.2.tgz#68babe6a74ab09a1fd15a9b1e6cbc7713d41cb5b"
|
||||
integrity sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA==
|
||||
@@ -761,11 +833,16 @@ ms@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
nan@^2.15.0:
|
||||
nan@^2.14.0, nan@^2.15.0:
|
||||
version "2.15.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
|
||||
integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
|
||||
|
||||
node-addon-api@^4.2.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
|
||||
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
|
||||
|
||||
node-fetch@^2.6.1, node-fetch@^2.6.5:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
@@ -773,6 +850,11 @@ node-fetch@^2.6.1, node-fetch@^2.6.5:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-gyp-build@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
|
||||
integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==
|
||||
|
||||
node-osu@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/node-osu/-/node-osu-2.2.1.tgz#2dcf9bef2e92490cab7a215842559f003d702ac1"
|
||||
@@ -861,6 +943,23 @@ ping@^0.4.1:
|
||||
q "1.x"
|
||||
underscore "^1.12.0"
|
||||
|
||||
play-audio@^0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/play-audio/-/play-audio-0.5.2.tgz#191dd45b95ff64ae20bbd718671da4e987d4edd1"
|
||||
integrity sha512-ZAqHUKkQLix2Iga7pPbsf1LpUoBjcpwU93F1l3qBIfxYddQLhxS6GKmS0d3jV8kSVaUbr6NnOEcEMFvuX93SWQ==
|
||||
|
||||
play-dl@^1.8.6:
|
||||
version "1.8.6"
|
||||
resolved "https://registry.yarnpkg.com/play-dl/-/play-dl-1.8.6.tgz#483420c0273ad38f0a07346dcd78fb76ce31c2c9"
|
||||
integrity sha512-NOnSmg6ikdrf6x6R4ML5QY6AJS4h4KkIIynlKuDx7RVDPE7C2kowgtS+e6RRMuuAXXXESkmlWrpUb+bnZFWy1w==
|
||||
dependencies:
|
||||
play-audio "^0.5.2"
|
||||
|
||||
prism-media@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.2.tgz#a1f04423ec15d22f3d62b1987b6a25dc49aad13b"
|
||||
integrity sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g==
|
||||
|
||||
prisma@^3.3.0:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.8.1.tgz#44395cef7cbb1ea86216cb84ee02f856c08a7873"
|
||||
@@ -1071,6 +1170,11 @@ text-hex@1.0.x:
|
||||
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
|
||||
integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
|
||||
|
||||
tiny-typed-emitter@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5"
|
||||
integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||
@@ -1161,6 +1265,13 @@ underscore@^1.12.0:
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.2.tgz#276cea1e8b9722a8dbed0100a407dda572125881"
|
||||
integrity sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==
|
||||
|
||||
utf-8-validate@^5.0.8:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.8.tgz#4a735a61661dbb1c59a0868c397d2fe263f14e58"
|
||||
integrity sha512-k4dW/Qja1BYDl2qD4tOMB9PFVha/UJtxTc1cXYOe3WwA/2m0Yn4qB7wLMpJyLJ/7DR0XnTut3HsCSzDT4ZvKgA==
|
||||
dependencies:
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
@@ -1196,7 +1307,7 @@ wide-align@^1.1.2:
|
||||
dependencies:
|
||||
string-width "^1.0.2 || 2 || 3 || 4"
|
||||
|
||||
winston-transport@^4.4.2:
|
||||
winston-transport@^4.4.0:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.2.tgz#554efe3fce229d046df006e0e3c411d240652e51"
|
||||
integrity sha512-9jmhltAr5ygt5usgUTQbEiw/7RYXpyUbEAFRCSicIacpUzPkrnQsQZSPGEI12aLK9Jth4zNcYJx3Cvznwrl8pw==
|
||||
@@ -1205,27 +1316,27 @@ winston-transport@^4.4.2:
|
||||
readable-stream "^3.4.0"
|
||||
triple-beam "^1.2.0"
|
||||
|
||||
winston@^3.3.3:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/winston/-/winston-3.4.0.tgz#7080f24b02a0684f8a37f9d5c6afb1ac23e95b84"
|
||||
integrity sha512-FqilVj+5HKwCfIHQzMxrrd5tBIH10JTS3koFGbLVWBODjiIYq7zir08rFyBT4rrTYG/eaTqDcfSIbcjSM78YSw==
|
||||
winston@3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170"
|
||||
integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==
|
||||
dependencies:
|
||||
"@dabh/diagnostics" "^2.0.2"
|
||||
async "^3.2.3"
|
||||
async "^3.1.0"
|
||||
is-stream "^2.0.0"
|
||||
logform "^2.3.2"
|
||||
logform "^2.2.0"
|
||||
one-time "^1.0.0"
|
||||
readable-stream "^3.4.0"
|
||||
stack-trace "0.0.x"
|
||||
triple-beam "^1.3.0"
|
||||
winston-transport "^4.4.2"
|
||||
winston-transport "^4.4.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
ws@^8.4.0:
|
||||
ws@^8.4.0, ws@^8.4.2:
|
||||
version "8.4.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
|
||||
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==
|
||||
@@ -1245,6 +1356,13 @@ yn@3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||
|
||||
zlib-sync@^0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/zlib-sync/-/zlib-sync-0.1.7.tgz#b0b24f9428e434943e306ad586428ef8baf3ed60"
|
||||
integrity sha512-UmciU6ZrIwtwPC8noMzq+kGMdiWwNRZ3wC0SbED4Ew5Ikqx14MqDPRs/Pbk+3rZPh5SzsOgUBs1WRE0iieddpg==
|
||||
dependencies:
|
||||
nan "^2.14.0"
|
||||
|
||||
zod@^3.11.6:
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483"
|
||||
|
||||
Reference in New Issue
Block a user