Refactoring and patches

This commit is contained in:
unknowableshade committed 2021-11-06 23:43:07 +03:00
1 parent 7c438ed37b
commit 4368353a1e
32 files changed
+749 -521

No files matched your search

-17
View File
@@ -1,17 +0,0 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
# Node.js dependencies:
node_modules/
-3
View File
@@ -364,6 +364,3 @@ FodyWeavers.xsd
# typescript restrictions
dist/
# safety
env.yaml
+29 -14
View File
@@ -4,6 +4,7 @@
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<Name>5a0b-bot</Name>
<RootNamespace>NodejsConsoleApp1</RootNamespace>
<SaveNodeJsSettingsInProjectFile>True</SaveNodeJsSettingsInProjectFile>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@@ -17,11 +18,11 @@
<SearchPath>
</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<OutputPath>./dist</OutputPath>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
<EnableTypeScript>true</EnableTypeScript>
<StartWebBrowser>false</StartWebBrowser>
<StartWebBrowser>False</StartWebBrowser>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
@@ -38,24 +39,38 @@
<ItemGroup>
<Folder Include="src\" />
<Folder Include="src\commands\" />
<Folder Include="src\commands\music\" />
<Folder Include="src\events\" />
<Folder Include="src\types\" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="src\app.ts" />
<TypeScriptCompile Include="src\commands\clear.ts" />
<TypeScriptCompile Include="src\commands\connect.ts" />
<TypeScriptCompile Include="src\commands\insert.ts" />
<TypeScriptCompile Include="src\commands\jump.ts" />
<TypeScriptCompile Include="src\commands\next.ts" />
<TypeScriptCompile Include="src\commands\pause.ts" />
<TypeScriptCompile Include="src\commands\play.ts" />
<TypeScriptCompile Include="src\commands\queue.ts" />
<TypeScriptCompile Include="src\commands\remove.ts" />
<TypeScriptCompile Include="src\commands\seek.ts" />
<TypeScriptCompile Include="src\commands\stop.ts" />
<TypeScriptCompile Include="src\commands\music\clear.ts" />
<TypeScriptCompile Include="src\commands\music\connect.ts" />
<TypeScriptCompile Include="src\commands\music\insert.ts" />
<TypeScriptCompile Include="src\commands\music\jump.ts" />
<TypeScriptCompile Include="src\commands\music\loop.ts">
<SubType>Code</SubType>
</TypeScriptCompile>
<TypeScriptCompile Include="src\commands\music\next.ts" />
<TypeScriptCompile Include="src\commands\music\pause.ts" />
<TypeScriptCompile Include="src\commands\music\play.ts" />
<TypeScriptCompile Include="src\commands\music\queue.ts" />
<TypeScriptCompile Include="src\commands\music\remove.ts" />
<TypeScriptCompile Include="src\commands\music\seek.ts" />
<TypeScriptCompile Include="src\commands\music\stop.ts" />
<TypeScriptCompile Include="src\config.ts" />
<TypeScriptCompile Include="src\events\interactionCreate.ts">
<SubType>Code</SubType>
</TypeScriptCompile>
<TypeScriptCompile Include="src\events\messageCreate.ts">
<SubType>Code</SubType>
</TypeScriptCompile>
<TypeScriptCompile Include="src\events\ready.ts">
<SubType>Code</SubType>
</TypeScriptCompile>
<TypeScriptCompile Include="src\types\Client.ts" />
<TypeScriptCompile Include="src\types\interfaces.ts" />
<TypeScriptCompile Include="src\types\Types.ts" />
<TypeScriptCompile Include="src\types\Misc.ts" />
<TypeScriptCompile Include="src\types\Music.ts" />
</ItemGroup>
+75 -12
View File
@@ -19,28 +19,39 @@
"ytdl-core": "4.9.1"
},
"devDependencies": {
"@discordjs/builders": "0.8.2",
"@types/yt-search": "^2.3.2"
},
"engines": {
"node": "16.9.1"
"node": "16.x.x"
}
},
"node_modules/@discordjs/builders": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.6.0.tgz",
"integrity": "sha512-mH3Gx61LKk2CD05laCI9K5wp+a3NyASHDUGx83DGJFkqJlRlSV5WMJNY6RS37A5SjqDtGMF4wVR9jzFaqShe6Q==",
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.2.tgz",
"integrity": "sha512-/YRd11SrcluqXkKppq/FAVzLIPRVlIVmc6X8ZklspzMIHDtJ+A4W37D43SHvLdH//+NnK+SHW/WeOF4Ts54PeQ==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^4.0.1",
"discord-api-types": "^0.22.0",
"@sindresorhus/is": "^4.2.0",
"discord-api-types": "^0.24.0",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
},
"engines": {
"node": ">=14.0.0",
"node": ">=16.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@discordjs/builders/node_modules/discord-api-types": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz",
"integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==",
"dev": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@discordjs/collection": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz",
@@ -515,6 +526,30 @@
"npm": ">=7.0.0"
}
},
"node_modules/discord.js/node_modules/@discordjs/builders": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.6.0.tgz",
"integrity": "sha512-mH3Gx61LKk2CD05laCI9K5wp+a3NyASHDUGx83DGJFkqJlRlSV5WMJNY6RS37A5SjqDtGMF4wVR9jzFaqShe6Q==",
"dependencies": {
"@sindresorhus/is": "^4.0.1",
"discord-api-types": "^0.22.0",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
},
"engines": {
"node": ">=14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/discord.js/node_modules/@discordjs/builders/node_modules/discord-api-types": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz",
"integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==",
"engines": {
"node": ">=12"
}
},
"node_modules/discord.js/node_modules/@types/ws": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz",
@@ -1451,15 +1486,24 @@
},
"dependencies": {
"@discordjs/builders": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.6.0.tgz",
"integrity": "sha512-mH3Gx61LKk2CD05laCI9K5wp+a3NyASHDUGx83DGJFkqJlRlSV5WMJNY6RS37A5SjqDtGMF4wVR9jzFaqShe6Q==",
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.2.tgz",
"integrity": "sha512-/YRd11SrcluqXkKppq/FAVzLIPRVlIVmc6X8ZklspzMIHDtJ+A4W37D43SHvLdH//+NnK+SHW/WeOF4Ts54PeQ==",
"dev": true,
"requires": {
"@sindresorhus/is": "^4.0.1",
"discord-api-types": "^0.22.0",
"@sindresorhus/is": "^4.2.0",
"discord-api-types": "^0.24.0",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
},
"dependencies": {
"discord-api-types": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz",
"integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==",
"dev": true
}
}
},
"@discordjs/collection": {
@@ -1842,6 +1886,25 @@
"ws": "^8.2.3"
},
"dependencies": {
"@discordjs/builders": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.6.0.tgz",
"integrity": "sha512-mH3Gx61LKk2CD05laCI9K5wp+a3NyASHDUGx83DGJFkqJlRlSV5WMJNY6RS37A5SjqDtGMF4wVR9jzFaqShe6Q==",
"requires": {
"@sindresorhus/is": "^4.0.1",
"discord-api-types": "^0.22.0",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
},
"dependencies": {
"discord-api-types": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz",
"integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg=="
}
}
},
"@types/ws": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz",
+1 -2
View File
@@ -12,8 +12,7 @@
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean",
"start": "node dist/app.js",
"gcp-build": "tsc -p tsconfig.json"
"start": "node dist/app.js"
},
"dependencies": {
"@discordjs/opus": "^0.6.0",
+1 -20
View File
@@ -2,27 +2,8 @@
import { MyClient } from "./types/Client";
const token = process.env.DISCORD_TOKEN;
const token = "NzIyMjE0MTA3MjM5MDIyNjcy.Xuf0TQ.9uJbIhlTUDCsKnt0_UcuHOG7i94";
const client = new MyClient("./commands", "./events");
client.on("ready", () => {
if (!client.user)
return;
console.info(`Logged in as ${client.user.tag}!`);
client.user.setActivity("on sound waves.", { type: "PLAYING" });
});
client.on("messageCreate", async (message) => {
if (message.guild)
await message.guild.commands.set(client.restCommands);
});
client.on("interactionCreate", interaction => {
if (interaction.isCommand() && client.commands.has(interaction.commandName)) {
const command = client.commands.get(interaction.commandName);
command.run(client, interaction);
}
});
client.login(token);
-34
View File
@@ -1,34 +0,0 @@
import { design } from "../config";
import { ButtonInteraction, ColorResolvable, CommandInteraction, Message, MessageActionRow, MessageButton, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { generateQueue } from "../types/Misc";
import { EmojiIdentifierResolvable } from "discord.js";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guildId === null)
return;
const player = client.players.getPlayer(ctx.guildId);
if (player) {
player.clear();
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Queue has been cleared.");
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
}
const data = {
name: "clear",
description: "Clears queue.",
}
export { data }
-60
View File
@@ -1,60 +0,0 @@
import { design } from "../config";
import { ColorResolvable, CommandInteraction, GuildMember, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { VoiceConnectionStatus } from "@discordjs/voice";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guildId === null)
return;
let player = client.players.getPlayer(ctx.guildId);
if (!player)
player = client.players.genPlayer(ctx.guildId);
if (!player.connection || player.connection.state.status === VoiceConnectionStatus.Disconnected) {
const channel = (ctx.member as GuildMember).voice.channel;
if (channel) {
await player.connect(channel);
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(`Successfully connected to ${channel.name}.`)
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
else {
client.players.delPlayer(ctx.guildId);
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Please connect to a voice channel.")
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
}
else {
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Already connected.")
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
}
const data = {
name: "connect",
description: "Connect to voice channel."
}
export { data }
-56
View File
@@ -1,56 +0,0 @@
import { design } from "../config";
import { ColorResolvable, CommandInteraction, GuildMember, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
const pos = ctx.options.getString("position");
const query = ctx.options.getString("query");
if (ctx.guildId === null || query === null)
return;
let player = client.players.getPlayer(ctx.guildId);
if (!player)
return;
let state: boolean;
if (!isNaN(Number(query))) {
await player.addTrack(query, ctx.user, Number(query) - 1);
}
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Inserted.");
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
const data = {
name: "insert",
description: "Insert new track to specific position.",
options: [
{
name: "query",
description: "Title or URL of the song.",
type: "STRING",
required: true
},
{
name: "position",
description: "Position to insert.",
type: "STRING",
required: true
}
]
}
export { data }
+32
View File
@@ -0,0 +1,32 @@
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
await ctx.deferReply();
if (ctx.guild === null)
return;
const player = client.players.getPlayer(ctx.guild.id);
if (player) {
player.clear();
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Queue has been cleared.");
await ctx.editReply({ embeds: [embed] });
}
}
const data = {
name: "clear",
description: "Clears queue.",
}
export { data }
+49
View File
@@ -0,0 +1,49 @@
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, GuildMember, MessageEmbed, TextChannel } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction, reply = true) {
if (ctx.guild === null)
return;
if (reply)
await ctx.deferReply();
const player = client.players.genPlayer(ctx.guild, client.players, ctx.channel as TextChannel);
let embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Mistake in connection.")
if (!player.connection) {
const channel = (ctx.member as GuildMember).voice.channel;
if (channel) {
await player.connect(channel);
embed.setTitle(`Successfully connected to ${channel.name}.`)
}
else {
client.players.delPlayer(ctx.guild.id);
embed.setTitle("Please connect to a voice channel.")
}
}
else {
embed.setTitle("Already connected to ${channel.name}..")
}
if (reply)
await ctx.editReply({ embeds: [embed] });
else
return player;
}
const data = {
name: "connect",
description: "Connect to voice channel."
}
export { data }
+56
View File
@@ -0,0 +1,56 @@
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
const pos = ctx.options.getInteger("position");
const query = ctx.options.getString("query");
if (!ctx.guild || !query || pos === null)
return;
const player = client.players.getPlayer(ctx.guild.id);
if (!player)
return;
await ctx.deferReply()
const track = await player.addTrack(query, ctx.user, pos - 1);
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(track.title)
.setDescription("Has been inserted to queue.")
.setURL(track.url)
.setThumbnail(track.thumbnail)
.setAuthor(`@${ctx.user.tag}`, ctx.user.avatarURL() as string);
console.log(`${track.title} added to ${ctx.guildId}`);
await ctx.editReply({ embeds: [embed] });
}
const data = {
name: "insert",
description: "Insert new track to specific position.",
options: [
{
name: "query",
description: "Title or URL of the song.",
type: "STRING",
required: true
},
{
name: "position",
description: "Position to insert.",
type: "INTEGER",
required: true
}
]
}
export { data }
@@ -1,22 +1,24 @@
import { design } from "../config";
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, GuildMember, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
const query = ctx.options.getString("position");
if (ctx.guildId === null || query === null)
if (ctx.guild === null || query === null)
return;
let player = client.players.getPlayer(ctx.guildId);
let player = client.players.getPlayer(ctx.guild.id);
if (!player)
return;
await ctx.deferReply();
let state: boolean;
if (!isNaN(Number(query))) {
@@ -25,16 +27,17 @@ export async function run(client: MyClient, ctx: CommandInteraction) {
state = await player.jump(query);
}
let embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Could not find track specified.");
if (state) {
const embed = new MessageEmbed()
embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Jumped.");
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
await ctx.editReply({ embeds: [embed] });
}
const data = {
+61
View File
@@ -0,0 +1,61 @@
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
const query = ctx.options.getString("position");
if (ctx.guild === null || query === null)
return;
let player = client.players.getPlayer(ctx.guild.id);
if (!player)
return;
await ctx.deferReply();
let state: boolean;
if (!isNaN(Number(query))) {
state = await player.jump(Number(query) - 1);
} else {
state = await player.jump(query);
}
let embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Could not find track specified.");
if (state) {
embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Jumped.");
}
await ctx.editReply({ embeds: [embed] });
}
const data = {
name: "loop",
description: "Set player loop option.",
options: [
{
name: "option",
description: "Set player to...",
type: "STRING",
required: true,
choices: [
{ name: "loop queue", value: "LOOP" },
{ name: "loop current track", value: "SONG" },
{ name: "Destroy player on end", value: "NONE" }
]
}
]
}
export { data }
@@ -1,27 +1,26 @@
import { design } from "../config";
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guildId === null)
if (ctx.guild === null)
return;
const player = client.players.getPlayer(ctx.guildId);
const player = client.players.getPlayer(ctx.guild.id);
if (player)
if (player) {
player.skip();
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Skipped.");
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
}
const data = {
@@ -1,28 +1,26 @@
import { design } from "../config";
import { design } from "../../config";
import { ButtonInteraction, ColorResolvable, CommandInteraction, Message, MessageActionRow, MessageButton, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { generateQueue } from "../types/Misc";
import { MyClient } from "../../types/Client";
import { generateQueue } from "../../types/Misc";
import { EmojiIdentifierResolvable } from "discord.js";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guildId === null)
if (ctx.guild === null)
return;
const player = client.players.getPlayer(ctx.guildId);
const player = client.players.getPlayer(ctx.guild.id);
if (player) {
const state = player.pause();
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(`Pause is now ${state ? "on" : "off"}.`);
.setTitle(`Pause is now ${state ? "resumed" : "paused"}.`);
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
+57
View File
@@ -0,0 +1,57 @@
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
import { run as connect } from "./connect";
export async function run(client: MyClient, ctx: CommandInteraction) {
const query = ctx.options.getString("query");
const player = await connect(client, ctx, false);
if (!player)
return;
await ctx.deferReply();
let embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Now playing.")
.setAuthor(`@${ctx.user.tag}`, ctx.user.avatarURL() as string);
if (query) {
const track = await player.addTrack(query, ctx.user);
embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(track.title)
.setDescription("Has been pushed to queue.")
.setURL(track.url)
.setThumbnail(track.thumbnail)
.setAuthor(`@${ctx.user.tag}`, ctx.user.avatarURL() as string);
console.log(`${track.title} added to ${ctx.guildId}`);
}
await ctx.editReply({ embeds: [embed] });
if (!player.nowPlaying) {
await player.play();
}
}
const data = {
name: "play",
description: "Play a song or add it to queue.",
options: [
{
name: "query",
description: "Title or URL of the song.",
type: "STRING",
required: true
}
]
}
export { data }
+85
View File
@@ -0,0 +1,85 @@
import { design } from "../../config";
import { ButtonInteraction, CollectorFilter, ColorResolvable, CommandInteraction, Message, MessageActionRow, MessageButton, MessageComponentInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
import { generateQueue } from "../../types/Misc";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guild === null)
return;
const player = client.players.getPlayer(ctx.guild.id);
if (!player)
return;
await ctx.deferReply();
const queue = player.queue;
if (queue.length === 0) {
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Queue is empty.");
await ctx.editReply({ embeds: [embed] });
return;
}
let page = 0;
const maxPage = Math.floor(queue.length / 11);
const row = new MessageActionRow()
.addComponents(
new MessageButton()
.setCustomId("QueueTop")
.setLabel("TOP")
.setStyle("SECONDARY"),
new MessageButton()
.setCustomId("QueueUp")
.setLabel("UP")
.setStyle("SECONDARY"),
new MessageButton()
.setCustomId("QueueDown")
.setLabel("DOWN")
.setStyle("SECONDARY"),
new MessageButton()
.setCustomId("QueueEnd")
.setLabel("END")
.setStyle("SECONDARY")
);
await ctx.editReply({
content: generateQueue(queue, page, player.queuePosition),
components: [row]
});
const filter: CollectorFilter<MessageComponentInteraction[]> = (btn) => {
return btn.message === ctx.fetchReply() as unknown as Message;
};
if (ctx.channel) {
const collector = ctx.channel.createMessageComponentCollector({ filter, time: 15000 });
collector.on("collect", async (btn: ButtonInteraction) => {
switch (btn.customId) {
case "QueueTop": page = 0;
case "QueueUp": page = Math.max(page - 1, 0);
case "QueueDown": page = Math.min(page + 1, maxPage);
case "QueueEnd": page = maxPage;
}
await btn.update({
content: generateQueue(queue, page, player.queuePosition)
});
});
}
}
const data = {
name: "queue",
description: "Send player queue.",
}
export { data }
@@ -1,22 +1,24 @@
import { design } from "../config";
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, GuildMember, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
const query = ctx.options.getString("position");
if (ctx.guildId === null || query === null)
if (ctx.guild === null || query === null)
return;
let player = client.players.getPlayer(ctx.guildId);
let player = client.players.getPlayer(ctx.guild.id);
if (!player)
return;
await ctx.deferReply();
let state: boolean;
if (!isNaN(Number(query))) {
@@ -25,16 +27,17 @@ export async function run(client: MyClient, ctx: CommandInteraction) {
state = await player.remove(query);
}
let embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Could not find track specified.");
if (state) {
const embed = new MessageEmbed()
embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Removed.");
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
await ctx.editReply({ embeds: [embed] });
}
const data = {
+66
View File
@@ -0,0 +1,66 @@
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
const hours = ctx.options.getInteger("hrs");
const minutes = ctx.options.getInteger("mins");
const seconds = ctx.options.getInteger("secs");
if (ctx.guild === null || (hours === null && minutes === null && seconds === null))
return;
let time = 0;
if (hours !== null)
time += hours * 3600;
if (minutes !== null)
time += minutes * 60;
if (seconds !== null)
time += seconds;
let player = client.players.getPlayer(ctx.guild.id);
if (player) {
await ctx.deferReply();
await player.seek(time);
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(`Fastforwarded to ${hours}:${minutes}.${seconds}.`);
await ctx.editReply({ embeds: [embed] });
}
}
const data = {
name: "seek",
description: "Fastforward to timecode.",
options: [
{
name: "hrs",
description: "Hour to seek.",
type: "INTEGER",
required: false
},
{
name: "mins",
description: "Minute to seek.",
type: "INTEGER",
required: false
},
{
name: "secs",
description: "Second to seek.",
type: "INTEGER",
required: false
}
]
}
export { data }
+32
View File
@@ -0,0 +1,32 @@
import { design } from "../../config";
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guild === null)
return;
const player = client.players.getPlayer(ctx.guild.id);
if (player) {
player.destroy();
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(`Player is now destroyed.`);
await ctx.reply({ embeds: [embed] });
}
}
const data = {
name: "stop",
description: "Destroys player.",
}
export { data }
-52
View File
@@ -1,52 +0,0 @@
import { design } from "../config";
import { ColorResolvable, CommandInteraction, GuildMember, Message, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { VoiceConnectionStatus } from "@discordjs/voice";
import { run as connect } from "./connect";
export async function run(client: MyClient, ctx: CommandInteraction) {
const query = ctx.options.getString("query");
if (ctx.guildId === null || query === null)
return;
connect(client, ctx);
const player = client.players.genPlayer(ctx.guildId);
const track = await player.addTrack(query, ctx.user);
if (!player.nowPlaying) {
await player.play();
}
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(track.title)
.setDescription("Has been pushed to queue.")
.setURL(track.url)
.setThumbnail(track.thumbnail)
.setAuthor(`@${ctx.user.tag}`, ctx.user.avatarURL() as string);
if (ctx.channel)
player.setMessage(await ctx.channel.send({ embeds: [embed] }));
console.log(`${track.title} added to ${ctx.guildId}`);
}
const data = {
name: "play",
description: "Play a song or add it to queue.",
options: [
{
name: "query",
description: "Title or URL of the song.",
type: "STRING",
required: true
}
]
}
export { data }
-89
View File
@@ -1,89 +0,0 @@
import { design } from "../config";
import { ButtonInteraction, CollectorFilter, ColorResolvable, CommandInteraction, Message, MessageActionRow, MessageButton, MessageComponentInteraction, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { generateQueue } from "../types/Misc";
import { EmojiIdentifierResolvable } from "discord.js";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guildId === null)
return;
const player = client.players.getPlayer(ctx.guildId);
if (player) {
const queue = player.queue;
if (queue.length === 0) {
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle("Queue is empty.");
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
else {
let page = 0;
const maxPage = Math.floor(queue.length / 11);
const row = new MessageActionRow()
.addComponents(
new MessageButton()
.setCustomId("QueueTop")
.setLabel("TOP")
.setStyle("SECONDARY"),
new MessageButton()
.setCustomId("QueueUp")
.setLabel("UP")
.setStyle("SECONDARY"),
new MessageButton()
.setCustomId("QueueDown")
.setLabel("DOWN")
.setStyle("SECONDARY"),
new MessageButton()
.setCustomId("QueueEnd")
.setLabel("END")
.setStyle("SECONDARY")
);
await ctx.reply({
content: generateQueue(queue, page, player.queuePosition),
components: [row]
});
const filter: CollectorFilter<MessageComponentInteraction[]> = (btn) => {
return btn.message === ctx.fetchReply() as unknown as Message;
};
if (ctx.channel) {
const collector = ctx.channel.createMessageComponentCollector({ filter, time: 15000 });
collector.on("collect", async (btn: ButtonInteraction) => {
switch (btn.customId) {
case "QueueTop": page = 0;
case "QueueUp": page = Math.max(page - 1, 0);
case "QueueDown": page = Math.min(page + 1, maxPage);
case "QueueEnd": page = maxPage;
}
await btn.update({
content: generateQueue(queue, page, player.queuePosition)
});
});
}
}
}
}
const data = {
name: "queue",
description: "Send player queue.",
}
export { data }
-34
View File
@@ -1,34 +0,0 @@
import { design } from "../config";
import { ColorResolvable, CommandInteraction, GuildMember, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
export async function run(client: MyClient, ctx: CommandInteraction) {
const time = ctx.options.getInteger("time");
if (ctx.guildId === null || time === null)
return;
let player = client.players.getPlayer(ctx.guildId);
if (player)
await player.seek(time);
}
const data = {
name: "seek",
description: "Fastforward to timecode.",
options: [
{
name: "time",
description: "Timecode to seek.",
type: "INTEGER",
required: true
}
]
}
export { data }
-36
View File
@@ -1,36 +0,0 @@
import { design } from "../config";
import { ButtonInteraction, ColorResolvable, CommandInteraction, Message, MessageActionRow, MessageButton, MessageEmbed } from "discord.js";
import { MyClient } from "../types/Client";
import { generateQueue } from "../types/Misc";
import { EmojiIdentifierResolvable } from "discord.js";
export async function run(client: MyClient, ctx: CommandInteraction) {
if (ctx.guildId === null)
return;
const player = client.players.getPlayer(ctx.guildId);
if (player) {
player.destroy();
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
.setTitle(`Destroyed.`);
try {
await ctx.reply({ embeds: [embed] });
} catch { }
}
}
const data = {
name: "stop",
description: "Destroys player.",
}
export { data }
+13
View File
@@ -0,0 +1,13 @@
import { Interaction } from "discord.js";
import { MyClient } from "../types/Client";
export async function run(args: Interaction[], client: MyClient): Promise<void> {
const [interaction] = args;
if (interaction.isCommand() && client.commands.has(interaction.commandName)) {
const command = client.commands.get(interaction.commandName);
command.run(client, interaction);
}
}
export const name = "interactionCreate";
+12
View File
@@ -0,0 +1,12 @@
import { Message } from "discord.js";
import { MyClient } from "../types/Client";
export async function run(args: Message[], client: MyClient): Promise<void> {
const [message] = args;
if (message.guild)
await message.guild.commands.set(client.restCommands);
}
export const name = "messageCreate";
+11
View File
@@ -0,0 +1,11 @@
import { MyClient } from "../types/Client";
export async function run(args: unknown[], client: MyClient): Promise<void> {
if (!client.user)
return;
console.log(`Logged in as ${client.user.tag}!`);
client.user.setActivity("on sound waves.", { type: "PLAYING" });
}
export const name = "ready";
+18 -4
View File
@@ -1,9 +1,9 @@
import { ApplicationCommandData, Client, Intents } from "discord.js";
import { ApplicationCommandData, Client as DiscordClient, Intents } from "discord.js";
import { readdirSync } from "fs";
import { Music } from "./Music";
export class MyClient extends Client {
export class MyClient extends DiscordClient {
commands = new Map<string, any>();
restCommands: ApplicationCommandData[] = [];
players = new Music();
@@ -18,14 +18,28 @@ export class MyClient extends Client {
]
});
const cmdFiles = readdirSync(commandsPath).filter(file => file.endsWith(".js"));
readdirSync(commandsPath).forEach((dir) => {
const cmdFiles = readdirSync(`${commandsPath}/${dir}`).filter(file => file.endsWith(".js"));
for (const file of cmdFiles) {
import(`.${commandsPath}/${file}`).then((command) => {
import(`.${commandsPath}/${dir}/${file}`).then((command) => {
const name = command.data.name;
this.restCommands.push(command.data);
this.commands.set(name, command);
});
}
});
const evntFiles = readdirSync(eventsPath).filter(file => file.endsWith(".js"));
for (const file of evntFiles) {
import(`.${eventsPath}/${file}`).then((event) => {
this.on(event.name, (...args: unknown[]) => {
event.run(args, this);
});
});
}
}
}
+6 -2
View File
@@ -1,4 +1,8 @@
import { track } from "./interfaces";
import { track } from "./Types";
export async function sleep(ms: number): Promise<unknown> {
return new Promise(r => setTimeout(r, ms));
}
export function random_shuffle(array: Array<any>) {
let currentIndex = array.length, randomIndex;
@@ -20,7 +24,7 @@ export function generateQueue(queue: track[], page: number, pos: number): string
const track = queue[i];
if (i === pos)
response += `${i + 1}. ${'Current =>' + track.title.slice(0, 35).padEnd(45, " ")} ${track.duration / 60}:${track.duration % 60}\n`;
response += `${i + 1}. ${"Current => " + track.title.slice(0, 35).padEnd(45, " ")} ${Math.floor(track.duration / 60)}:${track.duration % 60}\n`;
else
response += `${ i + 1 }. ${track.title.slice(0, 35).padEnd(45, " ")} ${track.duration / 60}:${track.duration % 60}\n`;
+105 -51
View File
@@ -1,36 +1,46 @@
import { VoiceConnection, AudioPlayer, createAudioResource, AudioPlayerStatus, joinVoiceChannel, createAudioPlayer, AudioResource, DiscordGatewayAdapterCreator } from "@discordjs/voice";
import { ColorResolvable, Guild, Message, MessageEmbed, StageChannel, User, VoiceChannel } from "discord.js";
import {
VoiceConnection, AudioPlayer, createAudioResource,
AudioPlayerStatus, joinVoiceChannel, createAudioPlayer,
AudioResource, DiscordGatewayAdapterCreator, VoiceConnectionStatus,
entersState
} from "@discordjs/voice";
import {
ColorResolvable, Guild, Message,
MessageEmbed, StageChannel, TextChannel, User,
VoiceChannel
} from "discord.js";
import { design } from "../config";
import ytdl from "discord-ytdl-core";
import { track, filter } from "./interfaces";
import { track, filter, loopOption } from "./Types";
import { random_shuffle } from "./Misc";
import yts from "yt-search";
export class Player {
guild: Guild;
music: Music;
public guild: Guild;
public music: Music;
connection: VoiceConnection | undefined;
player: AudioPlayer | undefined;
message: Message | undefined;
public connection: VoiceConnection | undefined;
public player: AudioPlayer | undefined;
queue: track[] = [];
queuePosition = 0;
nowPlaying: AudioResource | undefined = undefined;
filters: filter[] = [];
repeatMode = 0;
public queue: track[] = [];
public queuePosition: number = 0;
public nowPlaying: AudioResource | undefined;
public filters: filter = {};
public repeatMode: loopOption = "LOOP";
constructor(clientMusic: Music, playerGuild: Guild) {
public channel: TextChannel;
public message: Message | undefined;
constructor(playerGuild: Guild, clientMusic: Music, ctxChannel: TextChannel) {
this.music = clientMusic;
this.guild = playerGuild;
this.channel = ctxChannel;
}
async play(seek = 0) {
if (!this.connection || this.queue.length === 0)
return;
const track = this.queue[this.queuePosition];
private async _playerCreator(track: track, seek: number): Promise<AudioPlayer> {
const player = createAudioPlayer();
const stream = ytdl(track.url, {
opusEncoded: true,
@@ -39,24 +49,47 @@ export class Player {
seek: seek
});
const resource = createAudioResource(stream, {
inlineVolume: true
this.nowPlaying = createAudioResource(stream, {
inlineVolume: true,
metadata: track
});
this.player = createAudioPlayer();
this.connection.subscribe(this.player);
await player.play(this.nowPlaying);
await this.player.play(resource);
this.nowPlaying = resource;
return player;
}
private _resourceEndResolvable(): boolean {
switch (this.repeatMode) {
case "NONE": {
if (++this.queuePosition >= this.queue.length) {
this.queuePosition = 0;
this.nowPlaying = undefined;
return false;
}
return true;
}
case "LOOP": {
this.queuePosition = (this.queuePosition + 1) % this.queue.length;
return true;
}
case "SONG": return true;
}
return true;
}
private async _messageResolvable(track: track): Promise<void> {
if (this.message) {
const channel = this.message.channel;
this.channel = this.message.channel as TextChannel;
try {
await this.message.delete();
} catch (err) {
console.log(err);
}
}
const embed = new MessageEmbed()
.setColor(design.color as ColorResolvable)
@@ -66,27 +99,53 @@ export class Player {
.setAuthor(`@${track.requester.tag}`, track.requester.avatarURL() as string)
.setDescription("Is now beeing played.");
this.message = await channel.send({ embeds: [embed] });
this.message = await this.channel.send({ embeds: [embed] });
}
private async _songFinder(query: string) {
return (await yts(query)).videos[0];
}
public async play(seek: number = 0): Promise<void> {
if (!this.connection || this.queue.length === 0)
return;
const track = this.queue[this.queuePosition];
this.player = await this._playerCreator(track, seek);
this.connection.subscribe(this.player);
this.player.on(AudioPlayerStatus.Idle, async () => {
this.queuePosition = (this.queuePosition + 1) % this.queue.length;
if (this._resourceEndResolvable())
await this.play();
});
await this._messageResolvable(track);
}
async connect(channel: VoiceChannel | StageChannel) {
connect(channel: VoiceChannel | StageChannel): void {
this.connection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guildId,
selfDeaf: true,
adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator
});
this.connection.on(VoiceConnectionStatus.Disconnected, async (oldState, newState) => {
try {
await Promise.race([
entersState(this.connection as VoiceConnection, VoiceConnectionStatus.Signalling, 5_000),
entersState(this.connection as VoiceConnection, VoiceConnectionStatus.Connecting, 5_000),
]);
}
catch (error) {
this.destroy();
}
});
}
async addTrack(query: string, requester: User, index?: number): Promise<track> {
const songInfo = (await yts(query)).videos[0];
const songInfo = await this._songFinder(query);
const metadata: track = {
url: songInfo.url,
@@ -96,10 +155,10 @@ export class Player {
requester: requester
};
if (!index)
if (!index || index >= this.queue.length)
this.queue.push(metadata);
else {
this.queue.splice(index, 0, metadata)
this.queue.splice(Math.max(0, index), 0, metadata)
if (this.queuePosition >= index)
this.queuePosition++;
}
@@ -117,7 +176,7 @@ export class Player {
destroy() {
if (this.connection)
this.connection.disconnect();
this.connection.destroy();
this.music.delPlayer(this.guild.id);
}
@@ -143,20 +202,20 @@ export class Player {
return false;
}
remove(query: number | string): boolean {
async remove(query: number | string): Promise<boolean> {
if (typeof query === "number" && query < this.queue.length && query >= 0) {
if (query === this.queuePosition && this.player)
this.player.stop();
this.queue.splice(query);
if (query === this.queuePosition)
await this.play();
return true;
}
else {
for (let i = 0; i < this.queue.length; i++) {
const track = this.queue[i];
if (track.title.toLowerCase().includes(String(query).toLowerCase())) {
if (i === this.queuePosition && this.player)
this.player.stop();
this.queue.splice(i);
if (i === this.queuePosition)
await this.play();
return true;
}
}
@@ -182,7 +241,7 @@ export class Player {
}
}
repeat(mode: number) {
repeat(mode: loopOption): void {
this.repeatMode = mode;
}
@@ -194,19 +253,14 @@ export class Player {
return false;
}
shuffle() {
shuffle(): void {
this.queue = random_shuffle(this.queue);
}
skip() {
skip(): void {
if (this.player)
this.player.stop();
}
setMessage(message: Message) {
this.message = message;
}
}
export class Music {
@@ -218,11 +272,11 @@ export class Music {
return player;
}
genPlayer(guildId: string): Player {
const player = this.players.get(guildId);
genPlayer(guild: Guild, music: Music, channel: TextChannel): Player {
const player = this.players.get(guild.id);
if (!player) {
const newPlayer = new module.exports.Player();
this.players.set(guildId, newPlayer);
const newPlayer = new module.exports.Player(guild, music, channel);
this.players.set(guild.id, newPlayer);
return newPlayer;
}
else {
@@ -43,3 +43,5 @@ export interface filter {
dim?: boolean;
earrape?: boolean;
}
export type loopOption = "LOOP" | "SONG" | "NONE" | "AUTO";