2205.1 major update!
This commit is contained in:
1 parent
913b718026
commit
a86faf06d5
75 files changed
+4458
-2705
No files matched your search
@@ -0,0 +1,25 @@
|
|||||||
|
env:
|
||||||
|
browser: true
|
||||||
|
es2021: true
|
||||||
|
extends:
|
||||||
|
- eslint:recommended
|
||||||
|
- plugin:@typescript-eslint/recommended
|
||||||
|
parser: '@typescript-eslint/parser'
|
||||||
|
parserOptions:
|
||||||
|
ecmaVersion: latest
|
||||||
|
sourceType: module
|
||||||
|
plugins:
|
||||||
|
- '@typescript-eslint'
|
||||||
|
rules:
|
||||||
|
indent:
|
||||||
|
- error
|
||||||
|
- 4
|
||||||
|
linebreak-style:
|
||||||
|
- error
|
||||||
|
- windows
|
||||||
|
quotes:
|
||||||
|
- error
|
||||||
|
- double
|
||||||
|
semi:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
+1
-1
@@ -367,5 +367,5 @@ dist/
|
|||||||
*.js
|
*.js
|
||||||
*.js.map
|
*.js.map
|
||||||
|
|
||||||
#binaries
|
# binaries will bot bundle
|
||||||
bin/
|
bin/
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.0.31808.319
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "5a0b-bot", "5a0b-bot.njsproj", "{0DDE9DED-3734-43AC-A218-393675657D81}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{0DDE9DED-3734-43AC-A218-393675657D81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{0DDE9DED-3734-43AC-A218-393675657D81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{0DDE9DED-3734-43AC-A218-393675657D81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0DDE9DED-3734-43AC-A218-393675657D81}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {86AD99D1-E808-449F-9D8E-174746181BE1}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
|
||||||
<PropertyGroup>
|
|
||||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
|
||||||
<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>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
|
||||||
<ProjectGuid>0dde9ded-3734-43ac-a218-393675657d81</ProjectGuid>
|
|
||||||
<ProjectHome>
|
|
||||||
</ProjectHome>
|
|
||||||
<StartupFile>dist\app.js</StartupFile>
|
|
||||||
<StartWebBrowser>False</StartWebBrowser>
|
|
||||||
<SearchPath>
|
|
||||||
</SearchPath>
|
|
||||||
<WorkingDirectory>.</WorkingDirectory>
|
|
||||||
<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>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include=".gitignore" />
|
|
||||||
<Content Include="package.json" />
|
|
||||||
<Content Include="README.md" />
|
|
||||||
<Content Include="requirements.txt" />
|
|
||||||
<Content Include="tsconfig.json" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="src\" />
|
|
||||||
<Folder Include="src\assets\" />
|
|
||||||
<Folder Include="src\assets\DiscordMusic\" />
|
|
||||||
<Folder Include="src\commands\" />
|
|
||||||
<Folder Include="src\commands\music\" />
|
|
||||||
<Folder Include="src\events\" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<TypeScriptCompile Include="src\app.ts" />
|
|
||||||
<TypeScriptCompile Include="src\assets\Misc.ts" />
|
|
||||||
<TypeScriptCompile Include="src\assets\DiscordMusic\Music.ts" />
|
|
||||||
<TypeScriptCompile Include="src\assets\QuetzaClient.ts" />
|
|
||||||
<TypeScriptCompile Include="src\assets\DiscordMusic\QueueStorage.ts">
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</TypeScriptCompile>
|
|
||||||
<TypeScriptCompile Include="src\assets\Types.ts" />
|
|
||||||
<TypeScriptCompile Include="src\commands\music\clear.ts" />
|
|
||||||
<TypeScriptCompile Include="src\commands\music\connect.ts" />
|
|
||||||
<TypeScriptCompile Include="src\commands\music\delete.ts">
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</TypeScriptCompile>
|
|
||||||
<TypeScriptCompile Include="src\commands\music\insert.ts" />
|
|
||||||
<TypeScriptCompile Include="src\commands\music\jump.ts" />
|
|
||||||
<TypeScriptCompile Include="src\commands\music\load.ts">
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</TypeScriptCompile>
|
|
||||||
<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\peak.ts">
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</TypeScriptCompile>
|
|
||||||
<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\save.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\events\voiceStateUpdate.ts">
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</TypeScriptCompile>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsToolsV2.targets" />
|
|
||||||
<ProjectExtensions>
|
|
||||||
<VisualStudio>
|
|
||||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
|
||||||
<WebProjectProperties>
|
|
||||||
<UseIIS>False</UseIIS>
|
|
||||||
<AutoAssignPort>True</AutoAssignPort>
|
|
||||||
<DevelopmentServerPort>0</DevelopmentServerPort>
|
|
||||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
|
||||||
<IISUrl>http://localhost:48022/</IISUrl>
|
|
||||||
<NTLMAuthentication>False</NTLMAuthentication>
|
|
||||||
<UseCustomServer>True</UseCustomServer>
|
|
||||||
<CustomServerUrl>http://localhost:1337</CustomServerUrl>
|
|
||||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
|
||||||
</WebProjectProperties>
|
|
||||||
</FlavorProperties>
|
|
||||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
|
|
||||||
<WebProjectProperties>
|
|
||||||
<StartPageUrl>
|
|
||||||
</StartPageUrl>
|
|
||||||
<StartAction>CurrentPage</StartAction>
|
|
||||||
<AspNetDebugging>True</AspNetDebugging>
|
|
||||||
<SilverlightDebugging>False</SilverlightDebugging>
|
|
||||||
<NativeDebugging>False</NativeDebugging>
|
|
||||||
<SQLDebugging>False</SQLDebugging>
|
|
||||||
<ExternalProgram>
|
|
||||||
</ExternalProgram>
|
|
||||||
<StartExternalURL>
|
|
||||||
</StartExternalURL>
|
|
||||||
<StartCmdLineArguments>
|
|
||||||
</StartCmdLineArguments>
|
|
||||||
<StartWorkingDirectory>
|
|
||||||
</StartWorkingDirectory>
|
|
||||||
<EnableENC>False</EnableENC>
|
|
||||||
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
|
|
||||||
</WebProjectProperties>
|
|
||||||
</FlavorProperties>
|
|
||||||
</VisualStudio>
|
|
||||||
</ProjectExtensions>
|
|
||||||
</Project>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021-present unknowableshade
|
Copyright (c) 2021-present Vladimir Eremin
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,59 +1,7 @@
|
|||||||
# QuetzaBot - 2201.1 <soundrewrite>
|
# Quetza - 2205.1 <soundrewrite>
|
||||||
Typescript/node js discord bot.
|
|
||||||
|
|
||||||
## Latest update
|
|
||||||
|
|
||||||
- Rebuilt structure allows to write seperated modules for bot.
|
|
||||||
|
|
||||||
## Features
|
> _**Higher perfomance - better design.**_
|
||||||
|
|
||||||
### Music
|
|
||||||
|
|
||||||
- Play music from YouTube and many other sites.
|
|
||||||
- Create your own queues and save them
|
|
||||||
- Loop queue, song.
|
|
||||||
- Play next, stop, pause, resume.
|
|
||||||
- Seek for a time code.
|
|
||||||
- Queue jump, remove.
|
|
||||||
- Lots of different filters (nightcore, earrape).
|
|
||||||
|
|
||||||
## Installation
|
## Links
|
||||||
|
|
||||||
### Download bot repository
|
|
||||||
|
|
||||||
- **[Download](https://github.com/unknowableshade/quetza-bot/archive/refs/heads/master.zip)**
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
> :warning: **Read the docs!**
|
|
||||||
|
|
||||||
- **Install** [nodejs](https://nodejs.org/en/download/)
|
|
||||||
|
|
||||||
- **Install** required libraries:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Install** [ffmpeg](https://www.ffmpeg.org/download.html)
|
|
||||||
- **Install** [youtube-dl](https://github.com/ytdl-org/youtube-dl/releases/tag/2021.12.17)
|
|
||||||
|
|
||||||
- **Place** youtube-dl in "/bin" directory.
|
|
||||||
|
|
||||||
### Bot startup
|
|
||||||
|
|
||||||
- **Configure** "config.ts" file.
|
|
||||||
|
|
||||||
- **Build**
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Start**
|
|
||||||
```bash
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
## Licence
|
|
||||||
|
|
||||||
**[Licence](https://github.com/unknowableshade/5a0b-bot/blob/master/LICENSE)**
|
**[Licence](https://github.com/unknowableshade/5a0b-bot/blob/master/LICENSE)**
|
||||||
Generated
+2550
-321
File diff suppressed because it is too large.
Load diff
+19
-13
@@ -1,39 +1,45 @@
|
|||||||
{
|
{
|
||||||
"name": "5a0a-bot",
|
"name": "quetza-bot",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "17.x.x",
|
"node": "16.14.x",
|
||||||
"npm": "*"
|
"npm": "*"
|
||||||
},
|
},
|
||||||
"version": "1.5.0",
|
"version": "2205.1",
|
||||||
"description": "Discord multifunctional bot",
|
"description": "Discord multifunctional bot",
|
||||||
"main": "dist/app.js",
|
|
||||||
"author": {
|
"author": {
|
||||||
"name": "unknowableshade"
|
"name": "unknowableshade",
|
||||||
|
"email": "60275123+unknowableshade@users.noreply.github.com",
|
||||||
|
"url": "https://unknowableshade.ru"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/unknowableshade/5a0b-bot.git"
|
"url": "git+https://github.com/unknowableshade/quetza-bot.git"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/unknowableshade/5a0b-bot/issues"
|
"url": "https://github.com/unknowableshade/quetza-bot/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/unknowableshade/5a0b-bot#readme",
|
"readme": "https://github.com/unknowableshade/quetza-bot#readme",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "node dist/index.js",
|
||||||
"build": "tsc --build",
|
"build": "tsc --build",
|
||||||
"clean": "tsc --build --clean",
|
"clean": "tsc --build --clean",
|
||||||
"start": "node dist/app.js"
|
"postinstall": "npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/opus": "^0.7.0",
|
"@discordjs/opus": "^0.7.0",
|
||||||
"@discordjs/voice": "^0.8.0",
|
"@discordjs/voice": "^0.8.0",
|
||||||
"discord-ytdl-core": "^5.0.4",
|
"discord-ytdl-core": "^5.0.4",
|
||||||
"discord.js": "^13.6.0",
|
"discord.js": "^13.6.0",
|
||||||
"libsodium-wrappers": "^0.7.9",
|
"tweetnacl": "^1.0.3",
|
||||||
"typescript": "^4.5.5",
|
|
||||||
"ytdl-core": "^4.10.1"
|
"ytdl-core": "^4.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.17"
|
"@types/node": "^17.0.17",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.26.0",
|
||||||
|
"@typescript-eslint/parser": "^5.26.0",
|
||||||
|
"eslint": "^8.16.0",
|
||||||
|
"typescript": "^4.5.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { QuetzaClient } from "./assets/QuetzaClient";
|
|
||||||
import { quetzaConfig } from "./config";
|
|
||||||
|
|
||||||
const client = new QuetzaClient();
|
|
||||||
|
|
||||||
client.login(quetzaConfig.token);
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import { ApplicationCommandData, Client as DiscordClient, Intents, CommandInteraction } from "discord.js";
|
|
||||||
import { existsSync, readdirSync } from "fs";
|
|
||||||
import { quetzaConfig } from "../config";
|
|
||||||
|
|
||||||
type commandMetadata = {
|
|
||||||
data: ApplicationCommandData,
|
|
||||||
run: (arg0: QuetzaClient, arg1: CommandInteraction) => Promise<void>
|
|
||||||
};
|
|
||||||
|
|
||||||
type eventMetadata = {
|
|
||||||
name: string,
|
|
||||||
run: (arg0: QuetzaClient, arg1: unknown[]) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
type moduleMetadata = {
|
|
||||||
data: {
|
|
||||||
key: string,
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
author: string
|
|
||||||
},
|
|
||||||
control: any
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extended client class for bot.
|
|
||||||
* @field commands Contains all bot commands.
|
|
||||||
* @field players Is a music feature.
|
|
||||||
*/
|
|
||||||
export class QuetzaClient extends DiscordClient {
|
|
||||||
private commands = new Map<string, commandMetadata>();
|
|
||||||
private modules: Record<string, moduleMetadata> = {};
|
|
||||||
|
|
||||||
get Commands(): Map<string, commandMetadata> {
|
|
||||||
return this.commands;
|
|
||||||
}
|
|
||||||
|
|
||||||
get Modules(): Record<string, moduleMetadata> {
|
|
||||||
return this.modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
super({
|
|
||||||
intents: [
|
|
||||||
Intents.FLAGS.GUILDS,
|
|
||||||
Intents.FLAGS.GUILD_MESSAGES,
|
|
||||||
Intents.FLAGS.GUILD_VOICE_STATES
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
quetzaConfig.modules.forEach((dir) => {
|
|
||||||
const moduleDir = quetzaConfig.rootDir + "modules/" + dir;
|
|
||||||
|
|
||||||
import(moduleDir + "qmodule.js")
|
|
||||||
.then((qmodule: moduleMetadata) => this.modules[qmodule.data.key] = qmodule);
|
|
||||||
|
|
||||||
if (existsSync(moduleDir + "commands")) {
|
|
||||||
|
|
||||||
const commandFiles = readdirSync(moduleDir + "commands").filter(file => file.endsWith(".js"));
|
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
|
||||||
import(moduleDir + "commands/" + file)
|
|
||||||
.then((command: commandMetadata) => this.commands.set(command.data.name, command));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existsSync(moduleDir + "events")) {
|
|
||||||
|
|
||||||
const eventFiles = readdirSync(moduleDir + "events").filter(file => file.endsWith(".js"));
|
|
||||||
|
|
||||||
for (const file of eventFiles) {
|
|
||||||
import(moduleDir + "events/" + file).then((event: eventMetadata) => {
|
|
||||||
this.on(event.name, (...args: unknown[]) => {
|
|
||||||
event.run(this, args);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import Client from "../lib/Client";
|
||||||
|
|
||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
player.clear();
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.cleared(player.Queue.Tracks.length)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "clear",
|
||||||
|
description: "Clears queue.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import Client from "../lib/Client";
|
||||||
|
|
||||||
|
import { CommandInteraction, GuildMember } from "discord.js";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel || !ctx.member) return;
|
||||||
|
|
||||||
|
await ctx.deferReply();
|
||||||
|
|
||||||
|
const player =
|
||||||
|
client.modules.music.get(ctx.guild.id, ctx.channel) ||
|
||||||
|
client.modules.music.set(ctx.guild, ctx.channel);
|
||||||
|
|
||||||
|
if (!player.Connection) {
|
||||||
|
const channel = (ctx.member as GuildMember).voice.channel;
|
||||||
|
if (channel) {
|
||||||
|
player.connect(channel);
|
||||||
|
|
||||||
|
await ctx.editReply({ embeds: [I8n.en.okConnected(channel.name)] });
|
||||||
|
} else {
|
||||||
|
client.modules.music.del(ctx.guild.id);
|
||||||
|
|
||||||
|
await ctx.editReply({ embeds: [I8n.en.notConnected()] });
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await ctx.editReply({ embeds: [I8n.en.wasConnected()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "connect",
|
||||||
|
description: "Connect to voice channel.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -1,36 +1,19 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const filter = ctx.options.getString("filter");
|
const filter = ctx.options.getString("filter");
|
||||||
|
|
||||||
if (ctx.guild === null)
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
return;
|
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
if (!player) return;
|
||||||
|
|
||||||
if (!player)
|
player.setFilter(filter || undefined);
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
await ctx.reply({ embeds: [I8n.en.filtered(filter || undefined)] });
|
||||||
|
|
||||||
if (await player.filter(filter)) {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(filter ? `${filter} filter is setted up.` : "Filters have been cleared.");
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`An error occurred while setting up the filter.`);
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@@ -41,7 +24,7 @@ const data = {
|
|||||||
name: "filter",
|
name: "filter",
|
||||||
description: "Filter to apply.",
|
description: "Filter to apply.",
|
||||||
type: "STRING",
|
type: "STRING",
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
name: "bassboost_low",
|
name: "bassboost_low",
|
||||||
value: "bassboost_low",
|
value: "bassboost_low",
|
||||||
@@ -133,11 +116,11 @@ const data = {
|
|||||||
{
|
{
|
||||||
name: "earrape",
|
name: "earrape",
|
||||||
value: "earrape",
|
value: "earrape",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
required: false
|
required: false,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
export { data }
|
export { data };
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import Client from "../lib/Client";
|
||||||
|
|
||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel || !ctx.member) return;
|
||||||
|
|
||||||
|
await ctx.deferReply();
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
await ctx.editReply({ embeds: [I8n.en.playerInfo(player)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "info",
|
||||||
|
description: "Information about the server's music player.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
const pos = ctx.options.getInteger("position");
|
||||||
|
const query = ctx.options.getString("query");
|
||||||
|
|
||||||
|
if (!ctx.guild || !query || !pos || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
await ctx.deferReply();
|
||||||
|
|
||||||
|
const track = await player.add(query, ctx.user, pos - 1);
|
||||||
|
|
||||||
|
await ctx.editReply({ embeds: [I8n.en.appended(track)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
const query = ctx.options.getInteger("position");
|
||||||
|
|
||||||
|
if (!ctx.guild || !query || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
const state = player.jump(query - 1);
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.jumped(state)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "jump",
|
||||||
|
description: "Jump to specific track in queue.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "position",
|
||||||
|
description: "Position to jump to.",
|
||||||
|
type: "INTEGER",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import { loopOption } from "../lib/Types";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
const query = ctx.options.getString("option");
|
||||||
|
|
||||||
|
if (!ctx.guild || !query || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
player.Queue.Loop = query as loopOption;
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.looped(query as loopOption)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "loop",
|
||||||
|
description: "Set player loop option.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "option",
|
||||||
|
description: "Update player loop option.",
|
||||||
|
type: "STRING",
|
||||||
|
required: true,
|
||||||
|
choices: [
|
||||||
|
{ name: "loop queue", value: "LOOP" },
|
||||||
|
{ name: "loop current track", value: "SONG" },
|
||||||
|
{ name: "Destroy player on end", value: "NONE" },
|
||||||
|
{ name: "Random order without suffling queue.", value: "AUTO" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
player.skip();
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.skipped()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "next",
|
||||||
|
description: "Skip current song.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
const state = player.pause();
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.paused(state)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "pause",
|
||||||
|
description: "Toggle pause.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import { run as connect } from "./connect";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
const query = ctx.options.getString("query");
|
||||||
|
|
||||||
|
const player = await connect(client, ctx);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
const track = await player.add(query, ctx.user);
|
||||||
|
|
||||||
|
await ctx.editReply({ embeds: [I8n.en.appended(track)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.Resource) {
|
||||||
|
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"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
ButtonInteraction,
|
||||||
|
CommandInteraction,
|
||||||
|
Message,
|
||||||
|
MessageActionRow,
|
||||||
|
MessageButton,
|
||||||
|
} from "discord.js";
|
||||||
|
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
await ctx.deferReply();
|
||||||
|
|
||||||
|
if (player.Queue.empty()) {
|
||||||
|
await ctx.editReply({ embeds: [I8n.en.queueEmpty()] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = 0;
|
||||||
|
|
||||||
|
const row = new MessageActionRow().addComponents(
|
||||||
|
new MessageButton()
|
||||||
|
.setCustomId("QueueTop")
|
||||||
|
.setLabel("🔼")
|
||||||
|
.setStyle("PRIMARY"),
|
||||||
|
new MessageButton()
|
||||||
|
.setCustomId("QueueUp")
|
||||||
|
.setLabel("🔺")
|
||||||
|
.setStyle("SECONDARY"),
|
||||||
|
new MessageButton()
|
||||||
|
.setCustomId("QueueDown")
|
||||||
|
.setLabel("🔻")
|
||||||
|
.setStyle("SECONDARY"),
|
||||||
|
new MessageButton()
|
||||||
|
.setCustomId("QueueEnd")
|
||||||
|
.setLabel("🔽")
|
||||||
|
.setStyle("PRIMARY")
|
||||||
|
);
|
||||||
|
|
||||||
|
const message = (await ctx.editReply({
|
||||||
|
embeds: [I8n.en.queueDesigner(player.Queue, 0, player.Resource)],
|
||||||
|
components: [row],
|
||||||
|
})) as Message;
|
||||||
|
|
||||||
|
const collector = message.createMessageComponentCollector({ time: 30000 });
|
||||||
|
|
||||||
|
collector.on("collect", async (btn: ButtonInteraction) => {
|
||||||
|
switch (btn.customId) {
|
||||||
|
case "QueueTop": {
|
||||||
|
page = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "QueueUp": {
|
||||||
|
page = Math.max(page - 1, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "QueueDown": {
|
||||||
|
page = Math.min(page + 1, Math.floor(player.Queue.Tracks.length / 11));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "QueueEnd":
|
||||||
|
page = Math.floor(player.Queue.Tracks.length / 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
await btn.update({
|
||||||
|
embeds: [I8n.en.queueDesigner(player.Queue, page, player.Resource)],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "queue",
|
||||||
|
description: "Send player queue.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
const query = ctx.options.getInteger("position");
|
||||||
|
|
||||||
|
if (!ctx.guild || !query || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
const track = player.remove(query - 1);
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.removed(track)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "remove",
|
||||||
|
description: "Remove specific track in queue.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "position",
|
||||||
|
description: "Position to remove.",
|
||||||
|
type: "INTEGER",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
player.shuffle();
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.reshuffle()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "reshuffle",
|
||||||
|
description: "Shuffle the queue.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
const hours = ctx.options.getInteger("hrs") || 0;
|
||||||
|
const minutes = ctx.options.getInteger("mins") || 0;
|
||||||
|
const seconds = ctx.options.getInteger("secs") || 0;
|
||||||
|
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
const time = hours * 3600 + minutes * 60 + seconds;
|
||||||
|
|
||||||
|
player.seek(time * 1000);
|
||||||
|
|
||||||
|
await ctx.reply({
|
||||||
|
embeds: [I8n.en.fastForwarded(time)],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "seek",
|
||||||
|
description: "Fast-forward to timecode.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "hrs",
|
||||||
|
description: "Hours to seek.",
|
||||||
|
type: "INTEGER",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mins",
|
||||||
|
description: "Minutes to seek.",
|
||||||
|
type: "INTEGER",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "secs",
|
||||||
|
description: "Seconds to seek.",
|
||||||
|
type: "INTEGER",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
player.destroy();
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.destroyed()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "stop",
|
||||||
|
description: "Destroys player.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { CommandInteraction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
|
||||||
|
export async function run(client: Client, ctx: CommandInteraction) {
|
||||||
|
const volume = ctx.options.getInteger("volume") || 100;
|
||||||
|
|
||||||
|
if (!ctx.guild || !ctx.channel) return;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(ctx.guild.id, ctx.channel);
|
||||||
|
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
player.Volume = volume;
|
||||||
|
|
||||||
|
await ctx.reply({ embeds: [I8n.en.volumeSet(player.Volume)] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: "volume",
|
||||||
|
description: "Set music volume.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: "volume",
|
||||||
|
description: "Volume in percents.",
|
||||||
|
type: "INTEGER",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { data };
|
||||||
+9
-20
@@ -1,21 +1,10 @@
|
|||||||
/**
|
import path from "path";
|
||||||
* Bot configuration file.
|
|
||||||
* @field opId Contains bot author Discord ID for moderating on go.
|
|
||||||
* @field token Discord API token.
|
|
||||||
* @field color Bot color theme for Embeds.
|
|
||||||
* @field rootDir Directory that goes before commands and events.
|
|
||||||
* @field modules Directories for bot's modules.
|
|
||||||
*/
|
|
||||||
const quetzaConfig = {
|
|
||||||
opId: "214422162906415106",
|
|
||||||
token: process.env.DISCORD_TOKEN || "NzgzNDQwMjU3NjE3MzYyOTQ1.X8axmQ.wu37vNARAr-NlexYIXL5zIxrHUM",
|
|
||||||
color: "#C4A4BE",
|
|
||||||
rootDir: __dirname + "/",
|
|
||||||
modules: [
|
|
||||||
"quetza/",
|
|
||||||
"music/",
|
|
||||||
"codeshr/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export {quetzaConfig}
|
const config = {
|
||||||
|
token: process.env.DISCORD_TOKEN,
|
||||||
|
commands: path.join(__dirname, "/commands/"),
|
||||||
|
events: path.join(__dirname, "/events/"),
|
||||||
|
binaries: path.join(__dirname, "/../bin/")
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Interaction } from "discord.js";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
|
||||||
|
export async function run(client: Client, args: Interaction[]): Promise<void> {
|
||||||
|
const [interaction] = args;
|
||||||
|
|
||||||
|
if (interaction.isCommand()) {
|
||||||
|
const command = client.Commands.get(interaction.commandName);
|
||||||
|
|
||||||
|
if (command) command.run(client, interaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const name = "interactionCreate";
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import Client from "../lib/Client";
|
||||||
|
import { generateDependencyReport } from "@discordjs/voice";
|
||||||
|
|
||||||
|
export async function run(client: Client): Promise<void> {
|
||||||
|
if (!client.user || !client.application) {
|
||||||
|
console.log("No user or application is provided!");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${client.user.username} logged in as ${client.user.tag}!`);
|
||||||
|
console.log(generateDependencyReport());
|
||||||
|
client.user.setActivity("over you.", { type: "WATCHING" });
|
||||||
|
|
||||||
|
const restCommands = Array.from(client.Commands.values()).map(
|
||||||
|
value => value.data
|
||||||
|
);
|
||||||
|
|
||||||
|
client.application.commands.set(restCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const name = "ready";
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { VoiceState } from "discord.js";
|
||||||
|
import I8n from "../lib/I8n";
|
||||||
|
import Client from "../lib/Client";
|
||||||
|
import { asleep } from "../lib/Misc";
|
||||||
|
|
||||||
|
export async function run(client: Client, args: VoiceState[]): Promise<void> {
|
||||||
|
const [oldState] = args;
|
||||||
|
|
||||||
|
const player = client.modules.music.get(oldState.guild.id);
|
||||||
|
|
||||||
|
if (!player || !oldState.channel || !client.user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (oldState.channel.members.filter(value => value.user !== client.user).size === 0) {
|
||||||
|
await asleep(30000);
|
||||||
|
if (oldState.channel.members.filter(value => value.user !== client.user).size === 0) {
|
||||||
|
await player.Channel.send({ embeds: [I8n.en.fullyAlone()] });
|
||||||
|
player.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const name = "voiceStateUpdate";
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Client from "./lib/Client";
|
||||||
|
import config from "./config";
|
||||||
|
|
||||||
|
const client = new Client();
|
||||||
|
|
||||||
|
client.login(config.token);
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
Client as DiscordClient,
|
||||||
|
Intents,
|
||||||
|
} from "discord.js";
|
||||||
|
import { existsSync, readdirSync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import Music from "./Music";
|
||||||
|
import { command, event } from "./Types";
|
||||||
|
import config from "../config";
|
||||||
|
|
||||||
|
export default class Client extends DiscordClient {
|
||||||
|
private commands = new Map<string, command>();
|
||||||
|
|
||||||
|
get Commands(): Map<string, command> {
|
||||||
|
return this.commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly modules = {
|
||||||
|
music: new Music(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
super({
|
||||||
|
intents: [
|
||||||
|
Intents.FLAGS.GUILDS,
|
||||||
|
Intents.FLAGS.GUILD_MESSAGES,
|
||||||
|
Intents.FLAGS.GUILD_VOICE_STATES,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existsSync(config.commands)) {
|
||||||
|
const files = readdirSync(config.commands).filter((file) =>
|
||||||
|
file.endsWith(".js")
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
import(path.join(config.commands, file)).then((cmd: command) =>
|
||||||
|
this.commands.set(cmd.data.name, cmd)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(config.events)) {
|
||||||
|
const files = readdirSync(config.events).filter((file) =>
|
||||||
|
file.endsWith(".js")
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
import(path.join(config.events, file)).then((evnt: event) => {
|
||||||
|
this.on(evnt.name, (...args: unknown[]) =>
|
||||||
|
evnt.run(this, args)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
export default class Filter {
|
||||||
|
private filters: Record<string, [boolean, string]> = {
|
||||||
|
bassboost_low: [false, "bass=g=15:f=110:w=0.3"],
|
||||||
|
bassboost: [false, "bass=g=20:f=110:w=0.3"],
|
||||||
|
bassboost_high: [false, "bass=g=30:f=110:w=0.3"],
|
||||||
|
"8D": [false, "apulsator=hz=0.09"],
|
||||||
|
vaporwave: [false, "aresample=48000,asetrate=48000*0.75"],
|
||||||
|
nightcore: [false, "aresample=48000,asetrate=48000*1.25"],
|
||||||
|
phaser: [false, "aphaser=in_gain=0.4"],
|
||||||
|
tremolo: [false, "tremolo"],
|
||||||
|
vibrato: [false, "vibrato=f=6.5"],
|
||||||
|
reverse: [false, "areverse"],
|
||||||
|
treble: [false, "treble=g=5"],
|
||||||
|
normalizer: [false, "dynaudnorm=g=101"],
|
||||||
|
normalizer2: [false, "acompressor"],
|
||||||
|
surrounding: [false, "surround"],
|
||||||
|
pulsator: [false, "apulsator=hz=1"],
|
||||||
|
subboost: [false, "asubboost"],
|
||||||
|
karaoke: [false, "stereotools=mlev=0.03"],
|
||||||
|
flanger: [false, "flanger"],
|
||||||
|
gate: [false, "agate"],
|
||||||
|
haas: [false, "haas"],
|
||||||
|
mcompand: [false, "mcompand"],
|
||||||
|
mono: [false, "pan=mono|c0=.5*c0+.5*c1"],
|
||||||
|
mstlr: [false, "stereotools=mode=ms>lr"],
|
||||||
|
mstrr: [false, "stereotools=mode=ms>rr"],
|
||||||
|
compressor: [
|
||||||
|
false,
|
||||||
|
"compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
|
||||||
|
],
|
||||||
|
expander: [
|
||||||
|
false,
|
||||||
|
"compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
|
||||||
|
],
|
||||||
|
softlimiter: [
|
||||||
|
false,
|
||||||
|
"compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
|
||||||
|
],
|
||||||
|
chorus: [false, "chorus=0.7:0.9:55:0.4:0.25:2"],
|
||||||
|
chorus2d: [false, "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3"],
|
||||||
|
chorus3d: [
|
||||||
|
false,
|
||||||
|
"chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
|
||||||
|
],
|
||||||
|
fadein: [false, "afade=t=in:ss=0:d=10"],
|
||||||
|
dim: [
|
||||||
|
false,
|
||||||
|
"afftfilt=\"'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'\"",
|
||||||
|
],
|
||||||
|
earrape: [false, "channelsplit,sidechaingate=level_in=64"],
|
||||||
|
};
|
||||||
|
|
||||||
|
public get Filters(): [string, string][] {
|
||||||
|
return Object.entries(this.filters)
|
||||||
|
.filter(value => value[1][0]).map(value => [value[0], value[1][1]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggle(filter?: string): void {
|
||||||
|
if (!filter) {
|
||||||
|
Object.values(this.filters).forEach((value) => {
|
||||||
|
value[0] = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.filters, filter))
|
||||||
|
this.filters[filter][0] = !this.filters[filter][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.Filters.map(value => value[1]).join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
public empty(): boolean {
|
||||||
|
return this.Filters.length === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
+317
@@ -0,0 +1,317 @@
|
|||||||
|
import { MessageEmbed } from "discord.js";
|
||||||
|
import Player from "./Player";
|
||||||
|
import { loopOption, track } from "./Types";
|
||||||
|
import { loopMoji, secToISO, statusBarGenerator, volumeMoji } from "./Misc";
|
||||||
|
import Queue from "./Queue";
|
||||||
|
import { AudioResource } from "@discordjs/voice";
|
||||||
|
|
||||||
|
export default class I8n {
|
||||||
|
public static en = {
|
||||||
|
nowPlaying: (track: track) => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("LUMINOUS_VIVID_PINK")
|
||||||
|
.setAuthor({
|
||||||
|
iconURL: track.requester.avatarURL() || undefined,
|
||||||
|
name: track.requester.tag,
|
||||||
|
})
|
||||||
|
.setTitle(track.title)
|
||||||
|
.setURL(track.url)
|
||||||
|
.setDescription("**playing now.**")
|
||||||
|
.setThumbnail(track.thumbnail);
|
||||||
|
},
|
||||||
|
cleared: (amount: number) => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle(
|
||||||
|
`**♻️ ${amount} tracks have been cleared from the queue.**`
|
||||||
|
)
|
||||||
|
.setTimestamp(new Date());
|
||||||
|
},
|
||||||
|
okConnected: (channel: string) => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle(`**✅ Successfuly connected to:** _${channel}_.`);
|
||||||
|
},
|
||||||
|
notConnected: () => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("RED")
|
||||||
|
.setTitle(
|
||||||
|
"**🚫 Make yourself connected to a voice channel beforehand.**"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
wasConnected: () => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("YELLOW")
|
||||||
|
.setTitle("**⚠️ Already connected to a voice channel.**");
|
||||||
|
},
|
||||||
|
filtered: (filter?: string) => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle(`**✅ ${filter} was applied to the player.**`);
|
||||||
|
},
|
||||||
|
appended: (track?: track) => {
|
||||||
|
return !track
|
||||||
|
? new MessageEmbed()
|
||||||
|
.setColor("RED")
|
||||||
|
.setTitle("**🚫 Could not add track to the queue.**")
|
||||||
|
: new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle(track.title)
|
||||||
|
.setURL(track.url)
|
||||||
|
.setThumbnail(track.thumbnail)
|
||||||
|
.setAuthor({
|
||||||
|
iconURL: track.requester.avatarURL() || undefined,
|
||||||
|
name: track.requester.tag,
|
||||||
|
})
|
||||||
|
.setDescription("**has been added to the queue.**")
|
||||||
|
.setTimestamp(new Date());
|
||||||
|
},
|
||||||
|
jumped: (state: boolean) => {
|
||||||
|
return state
|
||||||
|
? new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle("_**Perfoming jump.**_")
|
||||||
|
: new MessageEmbed()
|
||||||
|
.setColor("RED")
|
||||||
|
.setTitle("**❗ Bad _jump_ request.**");
|
||||||
|
},
|
||||||
|
looped: (query: loopOption) => {
|
||||||
|
switch (query) {
|
||||||
|
case "AUTO": {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("LUMINOUS_VIVID_PINK")
|
||||||
|
.setTitle(
|
||||||
|
"**🔀 Shuffle mode** _(does not affect the queue order)_."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "LOOP": {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("BLUE")
|
||||||
|
.setTitle("**🔁 Looping over the queue.**");
|
||||||
|
}
|
||||||
|
case "NONE": {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("RED")
|
||||||
|
.setTitle("**⤵️ Player will stop on the queue end.**");
|
||||||
|
}
|
||||||
|
case "SONG": {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("YELLOW")
|
||||||
|
.setTitle("**🔂 Looping a single track.**");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
skipped: () => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle("**⏭️ Skipping.**");
|
||||||
|
},
|
||||||
|
paused: (state: boolean) => {
|
||||||
|
return state
|
||||||
|
? new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle("_**▶️ Resumed.**_")
|
||||||
|
: new MessageEmbed()
|
||||||
|
.setColor("YELLOW")
|
||||||
|
.setTitle("_**⏸️ Paused.**_");
|
||||||
|
},
|
||||||
|
removed: (track?: track) => {
|
||||||
|
return track
|
||||||
|
? new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle(track.title)
|
||||||
|
.setDescription("**was removed from the queue.**")
|
||||||
|
.setTimestamp(new Date())
|
||||||
|
.setURL(track.url)
|
||||||
|
.setThumbnail(track.thumbnail)
|
||||||
|
: new MessageEmbed()
|
||||||
|
.setColor("RED")
|
||||||
|
.setTitle("**❗ Bad _remove_ request.**");
|
||||||
|
},
|
||||||
|
fastForwarded: (amount: number) => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("BLUE")
|
||||||
|
.setTitle(
|
||||||
|
`**⏩ Fast-forwarding to ${secToISO(
|
||||||
|
amount
|
||||||
|
)}**`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
destroyed: () => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("RED")
|
||||||
|
.setTitle("**💀 Destroying the player.**");
|
||||||
|
},
|
||||||
|
volumeSet: (amount: number) => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle(`**${volumeMoji(amount)} Volume has been set to ${amount}%**`);
|
||||||
|
},
|
||||||
|
queueEmpty: () => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("YELLOW")
|
||||||
|
.setTitle("**❎ Queue is empty.**");
|
||||||
|
},
|
||||||
|
playerInfo: (player?: Player) => {
|
||||||
|
if (!player)
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("RED")
|
||||||
|
.setTitle("**🚫 Player does not exist.**");
|
||||||
|
|
||||||
|
if (player.Resource) {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("LUMINOUS_VIVID_PINK")
|
||||||
|
.setTitle(
|
||||||
|
`${player.Queue.Position}. ${player.Resource.metadata.title}`
|
||||||
|
)
|
||||||
|
.setDescription(
|
||||||
|
statusBarGenerator(
|
||||||
|
player.Resource.playbackDuration / 1000,
|
||||||
|
player.Resource.metadata.duration
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setThumbnail(player.Resource.metadata.thumbnail)
|
||||||
|
.setURL(player.Resource.metadata.url)
|
||||||
|
.addFields([
|
||||||
|
{
|
||||||
|
name: "Applied Filters",
|
||||||
|
value:
|
||||||
|
player.Filter.Filters.map(
|
||||||
|
(value) => value[0]
|
||||||
|
).join("\n") + "\ndefault",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Queue Length",
|
||||||
|
value: `🔢 ${player.Queue.Tracks.length}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Queue Duration",
|
||||||
|
value: `💿 ${secToISO(player.Queue.Duration)}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Queue Looping",
|
||||||
|
value: `${loopMoji(player.Queue.Loop)} ${player.Queue.Loop}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Volume amount",
|
||||||
|
value: `${volumeMoji(player.Volume)} ${player.Volume}/150`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Player lifetime",
|
||||||
|
value: `🕓 ${secToISO((Date.now() - player.Created.getTime()) / 1000)}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("BLUE")
|
||||||
|
.setTitle("💿 Insert the disk...")
|
||||||
|
.setDescription(statusBarGenerator(0, 0))
|
||||||
|
.setURL(
|
||||||
|
"https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley"
|
||||||
|
)
|
||||||
|
.addFields([
|
||||||
|
{
|
||||||
|
name: "Applied Filters",
|
||||||
|
value:
|
||||||
|
player.Filter.Filters.map(
|
||||||
|
(value) => value[0]
|
||||||
|
).join("\n") + "\ndefault",
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Queue Length",
|
||||||
|
value: `🔢 ${player.Queue.Tracks.length}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Queue Duration",
|
||||||
|
value: `💿 ${secToISO(player.Queue.Duration)}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Queue Looping",
|
||||||
|
value: `${loopMoji(player.Queue.Loop)} ${player.Queue.Loop}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Volume amount",
|
||||||
|
value: `${volumeMoji(player.Volume)} ${player.Volume}/150`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Player lifetime",
|
||||||
|
value: `🕓 ${secToISO((Date.now() - player.Created.getTime()) / 1000)}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
queueDesigner: (
|
||||||
|
queue: Queue,
|
||||||
|
page: number,
|
||||||
|
resource: AudioResource<track> | undefined
|
||||||
|
) => {
|
||||||
|
page = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(Math.floor(queue.Tracks.length / 11), page)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("LUMINOUS_VIVID_PINK")
|
||||||
|
.setTitle(
|
||||||
|
resource
|
||||||
|
? `${queue.Position + 1}. ${resource.metadata.title}`
|
||||||
|
: "💿 Insert the disk..."
|
||||||
|
)
|
||||||
|
.setURL(
|
||||||
|
resource
|
||||||
|
? resource.metadata.url
|
||||||
|
: "https://www.youtube.com/watch?v=5mGuCdlCcNM&ab_channel=Ra%C3%BAlBlanco"
|
||||||
|
)
|
||||||
|
.setThumbnail(
|
||||||
|
resource
|
||||||
|
? resource.metadata.thumbnail
|
||||||
|
: "https/img.youtube.com/vi/5mGuCdlCcNM/default.jpg"
|
||||||
|
)
|
||||||
|
.setDescription(
|
||||||
|
resource
|
||||||
|
? statusBarGenerator(
|
||||||
|
resource.playbackDuration / 1000,
|
||||||
|
resource.metadata.duration
|
||||||
|
)
|
||||||
|
: statusBarGenerator(0, 0)
|
||||||
|
)
|
||||||
|
.setFields(
|
||||||
|
queue.Tracks.slice(page * 10, page * 10 + 10).map(
|
||||||
|
(value, index) => {
|
||||||
|
return {
|
||||||
|
name: `${
|
||||||
|
page * 10 + index + 1
|
||||||
|
}. ${value.title.padEnd(35)}`,
|
||||||
|
value: `**[${secToISO(value.duration)}]**`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
fullyAlone: () => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("LUMINOUS_VIVID_PINK")
|
||||||
|
.setTitle("🥬 I have decided to leave to conserve my energy.")
|
||||||
|
.setDescription(
|
||||||
|
"_**Since there was nobody in the voice channel.**_"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
reshuffle: () => {
|
||||||
|
return new MessageEmbed()
|
||||||
|
.setColor("GREEN")
|
||||||
|
.setTitle("✅ Successfuly reshuffled queue.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { loopOption } from "./Types";
|
||||||
|
|
||||||
|
export function statusBarGenerator(duration: number, length: number): string {
|
||||||
|
duration = Math.max(0, duration);
|
||||||
|
length = Math.max(0, length);
|
||||||
|
if (duration > length) duration = length;
|
||||||
|
const filled = Array<string>(Math.floor(15 * duration / (length === 0 ? 1 : length))).fill(":small_blue_diamond:");
|
||||||
|
const empty = Array<string>(15 - Math.floor(15 * duration / (length === 0 ? 1 : length))).fill(":white_small_square:");
|
||||||
|
|
||||||
|
return `**[${filled.join("")}${empty.join("")}] ${secToISO(duration)}/${secToISO(length)}**`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function secToISO(amount: number): string {
|
||||||
|
const h = Math.floor(amount / 3600);
|
||||||
|
const m = Math.floor(amount % 3600 / 60);
|
||||||
|
const s = Math.floor(amount % 3600 % 60);
|
||||||
|
|
||||||
|
const hDisplay = h > 0 ? `${h}:` : "";
|
||||||
|
const mDisplay = `${Math.floor(m / 10)}${Math.floor(m % 10)}:`;
|
||||||
|
const sDisplay = `${Math.floor(s / 10)}${Math.floor(s % 10)}`;
|
||||||
|
return hDisplay + mDisplay + sDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function asleep(amount: number): Promise<unknown> {
|
||||||
|
return new Promise(r => setTimeout(r, amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function volumeMoji(amount: number): string {
|
||||||
|
return amount > 100 ? "🔊" : amount > 50 ? "🔉" : amount > 0 ? "🔈" : "🔇";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loopMoji(loop: loopOption): string {
|
||||||
|
return loop === "AUTO" ? "🔀" : loop === "LOOP" ? "🔁" : loop === "NONE" ? "⤵️" : "🔂";
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Guild, TextBasedChannel } from "discord.js";
|
||||||
|
import Player from "./Player";
|
||||||
|
|
||||||
|
export default class Music {
|
||||||
|
private players = new Map<string, Player>();
|
||||||
|
|
||||||
|
public get(guildId: string, channel?: TextBasedChannel): Player | undefined {
|
||||||
|
const player = this.players.get(guildId);
|
||||||
|
if (player) {
|
||||||
|
if (channel) player.Channel = channel;
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(guild: Guild, channel: TextBasedChannel): Player {
|
||||||
|
const player = new Player(guild, this, channel);
|
||||||
|
this.players.set(guild.id, player);
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public del(guildId: string): void {
|
||||||
|
this.players.delete(guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
import {
|
||||||
|
VoiceConnection,
|
||||||
|
AudioPlayer,
|
||||||
|
createAudioResource,
|
||||||
|
AudioPlayerStatus,
|
||||||
|
joinVoiceChannel,
|
||||||
|
createAudioPlayer,
|
||||||
|
AudioResource,
|
||||||
|
DiscordGatewayAdapterCreator,
|
||||||
|
VoiceConnectionStatus,
|
||||||
|
entersState,
|
||||||
|
} from "@discordjs/voice";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Guild,
|
||||||
|
Message,
|
||||||
|
TextBasedChannel,
|
||||||
|
User,
|
||||||
|
VoiceBasedChannel,
|
||||||
|
} from "discord.js";
|
||||||
|
|
||||||
|
import Music from "./Music";
|
||||||
|
import Filter from "./Filter";
|
||||||
|
import Queue from "./Queue";
|
||||||
|
import trackDL from "./TrackDl";
|
||||||
|
import I8n from "./I8n";
|
||||||
|
|
||||||
|
import ytdl from "discord-ytdl-core";
|
||||||
|
import { track } from "./Types";
|
||||||
|
|
||||||
|
export default class Player {
|
||||||
|
private guild: Guild;
|
||||||
|
private music: Music;
|
||||||
|
|
||||||
|
private channel: TextBasedChannel;
|
||||||
|
public get Channel(): TextBasedChannel {
|
||||||
|
return this.channel;
|
||||||
|
}
|
||||||
|
public set Channel(x: TextBasedChannel) {
|
||||||
|
this.channel = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private connection: VoiceConnection | undefined;
|
||||||
|
public get Connection(): VoiceConnection | undefined {
|
||||||
|
return this.connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private player: AudioPlayer;
|
||||||
|
public get Player(): AudioPlayer {
|
||||||
|
return this.player;
|
||||||
|
}
|
||||||
|
|
||||||
|
private filter = new Filter();
|
||||||
|
public get Filter(): Filter {
|
||||||
|
return this.filter;
|
||||||
|
}
|
||||||
|
private queue = new Queue();
|
||||||
|
public get Queue(): Queue {
|
||||||
|
return this.queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resource: AudioResource<track> | undefined;
|
||||||
|
public get Resource(): AudioResource<track> | undefined {
|
||||||
|
return this.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private message: Message | undefined;
|
||||||
|
private created: Date = new Date();
|
||||||
|
public get Created(): Date {
|
||||||
|
return this.created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private volume = 100;
|
||||||
|
public get Volume(): number {
|
||||||
|
return this.volume;
|
||||||
|
}
|
||||||
|
public set Volume(x: number) {
|
||||||
|
this.volume = Math.max(0, Math.min(150, x));
|
||||||
|
|
||||||
|
if (this.resource && this.resource.volume) this.resource.volume.setVolumeLogarithmic(this.volume / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
guild: Guild,
|
||||||
|
music: Music,
|
||||||
|
textBasedChannel: TextBasedChannel
|
||||||
|
) {
|
||||||
|
this.music = music;
|
||||||
|
this.guild = guild;
|
||||||
|
this.channel = textBasedChannel;
|
||||||
|
|
||||||
|
this.player = createAudioPlayer();
|
||||||
|
|
||||||
|
this.player.on(AudioPlayerStatus.Idle, async () =>
|
||||||
|
this.resourceEndResolvable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private resourceGenerator(track: track, seek?: number) {
|
||||||
|
const stream = ytdl(track.url, {
|
||||||
|
opusEncoded: false,
|
||||||
|
fmt: "mp3",
|
||||||
|
filter: "audioonly",
|
||||||
|
highWaterMark: 1 << 25,
|
||||||
|
seek: seek ? seek / 1000 : undefined,
|
||||||
|
encoderArgs: !this.filter.empty()
|
||||||
|
? ["-af", this.filter.toString()]
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resource = createAudioResource(stream, {
|
||||||
|
inlineVolume: true,
|
||||||
|
metadata: track,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (seek) this.resource.playbackDuration = seek;
|
||||||
|
if (this.resource.volume) this.resource.volume.setVolumeLogarithmic(this.volume / 100);
|
||||||
|
|
||||||
|
return this.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resourceEndResolvable(): void {
|
||||||
|
if (this.resource) this.resource.encoder?.destroy();
|
||||||
|
|
||||||
|
if (this.queue.next()) {
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.resource = undefined;
|
||||||
|
this.player.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async messageResolvable(track: track) {
|
||||||
|
if (this.message) await this.message.delete();
|
||||||
|
|
||||||
|
this.message = await this.channel.send({
|
||||||
|
embeds: [I8n.en.nowPlaying(track)],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async search(
|
||||||
|
query: string,
|
||||||
|
requester: User
|
||||||
|
): Promise<track[] | undefined> {
|
||||||
|
const response = await trackDL(query, {
|
||||||
|
dumpSingleJson: true,
|
||||||
|
noWarnings: true,
|
||||||
|
noCheckCertificate: true,
|
||||||
|
flatPlaylist: true,
|
||||||
|
skipDownload: true,
|
||||||
|
defaultSearch: "ytsearch",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) return undefined;
|
||||||
|
|
||||||
|
const tracks: track[] = [];
|
||||||
|
|
||||||
|
if (response.entries) {
|
||||||
|
for (const entry of response.entries) {
|
||||||
|
tracks.push({
|
||||||
|
url: `https://youtube.com/watch?v=${entry.id}`,
|
||||||
|
title: entry.title,
|
||||||
|
thumbnail: `https://img.youtube.com/vi/${entry.id}/default.jpg`,
|
||||||
|
duration: entry.duration,
|
||||||
|
requester: requester,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracks.push({
|
||||||
|
url: `https://youtube.com/watch?v=${response.id}`,
|
||||||
|
title: response.title,
|
||||||
|
thumbnail: `https://img.youtube.com/vi/${response.id}/default.jpg`,
|
||||||
|
duration: response.duration,
|
||||||
|
requester: requester,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public play(seek?: number): void {
|
||||||
|
const track = this.queue.current();
|
||||||
|
|
||||||
|
if (!this.connection || !track) return;
|
||||||
|
|
||||||
|
this.player.play(this.resourceGenerator(track, seek));
|
||||||
|
|
||||||
|
this.connection.subscribe(this.player);
|
||||||
|
|
||||||
|
this.messageResolvable(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
public connect(channel: VoiceBasedChannel): void {
|
||||||
|
if (this.connection) this.connection.destroy();
|
||||||
|
|
||||||
|
this.connection = joinVoiceChannel({
|
||||||
|
channelId: channel.id,
|
||||||
|
guildId: channel.guildId,
|
||||||
|
adapterCreator: channel.guild
|
||||||
|
.voiceAdapterCreator as DiscordGatewayAdapterCreator,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.on(
|
||||||
|
VoiceConnectionStatus.Disconnected,
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
await Promise.race([
|
||||||
|
entersState(
|
||||||
|
this.connection as VoiceConnection,
|
||||||
|
VoiceConnectionStatus.Signalling,
|
||||||
|
10_000
|
||||||
|
),
|
||||||
|
entersState(
|
||||||
|
this.connection as VoiceConnection,
|
||||||
|
VoiceConnectionStatus.Connecting,
|
||||||
|
10_000
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async add(
|
||||||
|
query: string,
|
||||||
|
requester: User,
|
||||||
|
index?: number
|
||||||
|
): Promise<track | undefined> {
|
||||||
|
const tracks = await this.search(query, requester);
|
||||||
|
|
||||||
|
if (!tracks) return;
|
||||||
|
|
||||||
|
this.queue.push(tracks, index);
|
||||||
|
|
||||||
|
return tracks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public jump(query: number): boolean {
|
||||||
|
if (this.queue.jump(query)) {
|
||||||
|
this.play();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(query: number): track | undefined {
|
||||||
|
const oldPosition = this.queue.Position;
|
||||||
|
|
||||||
|
const track = this.queue.pop(query);
|
||||||
|
|
||||||
|
if (track) {
|
||||||
|
if (oldPosition === query) this.play();
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public seek(time: number): void {
|
||||||
|
this.play(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.queue.clear();
|
||||||
|
this.resource = undefined;
|
||||||
|
if (this.player) this.player.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
if (this.connection) this.connection.destroy();
|
||||||
|
this.player.stop();
|
||||||
|
this.music.del(this.guild.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause(): boolean {
|
||||||
|
if (this.player.state.status === AudioPlayerStatus.Paused) {
|
||||||
|
this.player.unpause();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.player.pause();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public shuffle(): void {
|
||||||
|
this.queue.shuffle();
|
||||||
|
if (this.resource) {
|
||||||
|
this.player.stop();
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public skip(): void {
|
||||||
|
this.player.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setFilter(filter?: string): void {
|
||||||
|
this.filter.toggle(filter);
|
||||||
|
if (this.resource) this.play(this.resource.playbackDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { loopOption, track } from "./Types";
|
||||||
|
|
||||||
|
export default class Queue {
|
||||||
|
private tracks: track[] = [];
|
||||||
|
public get Tracks(): track[] {
|
||||||
|
return this.tracks;
|
||||||
|
}
|
||||||
|
private position = 0;
|
||||||
|
public get Position(): number {
|
||||||
|
return this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loop: loopOption = "NONE";
|
||||||
|
public get Loop(): loopOption {
|
||||||
|
return this.loop;
|
||||||
|
}
|
||||||
|
public set Loop(x: loopOption) {
|
||||||
|
this.loop = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private duration = 0;
|
||||||
|
public get Duration(): number {
|
||||||
|
return this.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public next(): track | undefined {
|
||||||
|
if (this.loop === "SONG") return this.current();
|
||||||
|
|
||||||
|
if (this.loop === "AUTO") {
|
||||||
|
this.position = Math.floor(Math.random() * this.tracks.length);
|
||||||
|
return this.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.position + 1 < this.tracks.length) {
|
||||||
|
this.position++;
|
||||||
|
return this.current();
|
||||||
|
} else if (this.loop === "LOOP") {
|
||||||
|
this.position = 0;
|
||||||
|
return this.current();
|
||||||
|
} else if (this.loop === "NONE") {
|
||||||
|
this.position = 0;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public shuffle(): void {
|
||||||
|
let currentIndex = this.tracks.length;
|
||||||
|
|
||||||
|
while (currentIndex != 0) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * currentIndex);
|
||||||
|
currentIndex--;
|
||||||
|
|
||||||
|
[this.tracks[currentIndex], this.tracks[randomIndex]] = [
|
||||||
|
this.tracks[randomIndex],
|
||||||
|
this.tracks[currentIndex],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public push(tracks: track[], index?: number): track {
|
||||||
|
if (!index || index >= this.tracks.length) {
|
||||||
|
this.tracks = this.tracks.concat(tracks);
|
||||||
|
} else {
|
||||||
|
this.tracks.splice(Math.max(0, index), 0, ...tracks);
|
||||||
|
if (this.position >= index) this.position += tracks.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const track of tracks) {
|
||||||
|
this.duration += track.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tracks[index || this.tracks.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public pop(query: number): track | undefined {
|
||||||
|
if (query >= 0 && query < this.tracks.length) {
|
||||||
|
const track = this.tracks[query];
|
||||||
|
|
||||||
|
this.tracks.splice(query, 1);
|
||||||
|
this.position = Math.max(0, this.position - 1);
|
||||||
|
this.duration -= track.duration;
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public jump(query: number): track | undefined {
|
||||||
|
if (query >= 0 && query < this.tracks.length) {
|
||||||
|
const track = this.tracks[query];
|
||||||
|
this.position = query;
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public current(): track {
|
||||||
|
return this.tracks[this.position];
|
||||||
|
}
|
||||||
|
|
||||||
|
public empty(): boolean {
|
||||||
|
return this.tracks.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.tracks = [];
|
||||||
|
this.position = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { ytdlArgs, ytdlResponse } from "./Types";
|
||||||
|
import { execFile } from "child_process";
|
||||||
|
import { promisify } from "util";
|
||||||
|
import config from "../config";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
|
function argumentsResolver(args: ytdlArgs): string[] {
|
||||||
|
const options = Object.entries(args);
|
||||||
|
return options.map(([key, value]) => {
|
||||||
|
if (typeof value === "boolean" && value) {
|
||||||
|
return `--${key.split(/(?=[A-Z])/).join("-")}`.toLowerCase();
|
||||||
|
} else {
|
||||||
|
return `--${key
|
||||||
|
.split(/(?=[A-Z])/)
|
||||||
|
.join("-")}=${value}`.toLowerCase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ytdlExec(
|
||||||
|
query: string,
|
||||||
|
args: ytdlArgs
|
||||||
|
): Promise<ytdlResponse | undefined> {
|
||||||
|
try {
|
||||||
|
const executable = await execFileAsync(
|
||||||
|
path.join(config.binaries, "/youtube-dl"),
|
||||||
|
[`${query}`].concat(argumentsResolver(args))
|
||||||
|
);
|
||||||
|
return JSON.parse(executable.stdout);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function trackDL(
|
||||||
|
query: string,
|
||||||
|
args: ytdlArgs
|
||||||
|
): Promise<ytdlResponse | undefined> {
|
||||||
|
let response = await ytdlExec(query, args);
|
||||||
|
|
||||||
|
if (response && !response.extractor.includes("youtube")) {
|
||||||
|
response = await ytdlExec(
|
||||||
|
`${response.title} - ${response.creator}`,
|
||||||
|
args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
import { ApplicationCommandData, User, CommandInteraction } from "discord.js";
|
||||||
|
import Client from "./Client";
|
||||||
|
|
||||||
|
export type track = {
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
thumbnail: string;
|
||||||
|
duration: number;
|
||||||
|
requester: User;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type command = {
|
||||||
|
data: ApplicationCommandData;
|
||||||
|
run: (arg0: Client, arg1: CommandInteraction) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type event = {
|
||||||
|
name: string;
|
||||||
|
run: (arg0: Client, arg1: unknown[]) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type loopOption = "LOOP" | "SONG" | "NONE" | "AUTO";
|
||||||
|
|
||||||
|
export type ytdlArgs = {
|
||||||
|
help?: boolean;
|
||||||
|
version?: boolean;
|
||||||
|
update?: boolean;
|
||||||
|
ignoreErrors?: boolean;
|
||||||
|
abortOnError?: boolean;
|
||||||
|
dumpUserAgent?: boolean;
|
||||||
|
listExtractors?: boolean;
|
||||||
|
extractorDescriptions?: boolean;
|
||||||
|
forceGenericExtractor?: boolean;
|
||||||
|
defaultSearch?: string;
|
||||||
|
igonreConfig?: boolean;
|
||||||
|
configLocation?: string;
|
||||||
|
flatPlaylist?: boolean;
|
||||||
|
markWatched?: boolean;
|
||||||
|
noColor?: boolean;
|
||||||
|
proxy?: string;
|
||||||
|
socketTimeout?: number;
|
||||||
|
sourceAddress?: string;
|
||||||
|
forceIpv4?: boolean;
|
||||||
|
forceIpv6?: boolean;
|
||||||
|
geoVerificationProxy?: string;
|
||||||
|
geoBypass?: boolean;
|
||||||
|
geoBypassCountry?: string;
|
||||||
|
geoBypassIpBlock?: string;
|
||||||
|
playlistStart?: number;
|
||||||
|
playlistEnd?: number | "last";
|
||||||
|
playlistItems?: string;
|
||||||
|
matchTitle?: string;
|
||||||
|
rejectTitle?: string;
|
||||||
|
maxDownloads?: number;
|
||||||
|
minFilesize?: string;
|
||||||
|
maxFilesize?: string;
|
||||||
|
date?: string;
|
||||||
|
datebefore?: string;
|
||||||
|
dateafter?: string;
|
||||||
|
minViews?: number;
|
||||||
|
maxViews?: number;
|
||||||
|
matchFilter?: string;
|
||||||
|
noPlaylist?: boolean;
|
||||||
|
yesPlaylist?: boolean;
|
||||||
|
ageLimit?: number;
|
||||||
|
downloadArchive?: string;
|
||||||
|
includeAds?: boolean;
|
||||||
|
limitRate?: string;
|
||||||
|
retries?: number | "infinite";
|
||||||
|
skipUnavailableFragments?: boolean;
|
||||||
|
abortOnUnavailableFragment?: boolean;
|
||||||
|
keepFragments?: boolean;
|
||||||
|
bufferSize?: string;
|
||||||
|
noResizeBuffer?: boolean;
|
||||||
|
httpChunkSize?: string;
|
||||||
|
playlistReverse?: boolean;
|
||||||
|
playlistRandom?: boolean;
|
||||||
|
xattrSetFilesize?: boolean;
|
||||||
|
hlsPreferNative?: boolean;
|
||||||
|
hlsPreferFfmpeg?: boolean;
|
||||||
|
hlsUseMpegts?: boolean;
|
||||||
|
externalDownloader?: string;
|
||||||
|
externalDownloaderArgs?: string;
|
||||||
|
batchFile?: string;
|
||||||
|
id?: boolean;
|
||||||
|
output?: string;
|
||||||
|
outputNaPlaceholder?: string;
|
||||||
|
autonumberStart?: number;
|
||||||
|
restrictFilenames?: boolean;
|
||||||
|
noOverwrites?: boolean;
|
||||||
|
continue?: boolean;
|
||||||
|
noPart?: boolean;
|
||||||
|
noMtime?: boolean;
|
||||||
|
writeDescription?: boolean;
|
||||||
|
writeInfoJson?: boolean;
|
||||||
|
writeAnnotations?: boolean;
|
||||||
|
loadInfoJson?: string;
|
||||||
|
cookies?: string;
|
||||||
|
cacheDir?: string;
|
||||||
|
noCacheDir?: boolean;
|
||||||
|
rmCacheDir?: boolean;
|
||||||
|
writeThumbnail?: boolean;
|
||||||
|
writeAllThumbnails?: boolean;
|
||||||
|
listThumbnails?: boolean;
|
||||||
|
quiet?: boolean;
|
||||||
|
noWarnings?: boolean;
|
||||||
|
simulate?: boolean;
|
||||||
|
skipDownload?: boolean;
|
||||||
|
getUrl?: boolean;
|
||||||
|
getTitle?: boolean;
|
||||||
|
getId?: boolean;
|
||||||
|
getThumbnail?: boolean;
|
||||||
|
getDuration?: boolean;
|
||||||
|
getFilename?: boolean;
|
||||||
|
getFormat?: boolean;
|
||||||
|
dumpJson?: boolean;
|
||||||
|
dumpSingleJson?: boolean;
|
||||||
|
printJson?: boolean;
|
||||||
|
newline?: boolean;
|
||||||
|
noProgress?: boolean;
|
||||||
|
consoleTitle?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
dumpPages?: boolean;
|
||||||
|
writePages?: boolean;
|
||||||
|
printTraffic?: boolean;
|
||||||
|
callHome?: boolean;
|
||||||
|
encoding?: string;
|
||||||
|
noCheckCertificate?: boolean;
|
||||||
|
preferInsecure?: boolean;
|
||||||
|
userAgent?: string;
|
||||||
|
referer?: string;
|
||||||
|
addHeader?: string;
|
||||||
|
bidiWorkaround?: boolean;
|
||||||
|
sleepInterval?: number;
|
||||||
|
maxSleepInterval?: number;
|
||||||
|
format?: string;
|
||||||
|
allFormats?: boolean;
|
||||||
|
preferFreeFormats?: boolean;
|
||||||
|
listFormats?: boolean;
|
||||||
|
youtubeSkipDashManifest?: boolean;
|
||||||
|
mergeOutputFormat?: string;
|
||||||
|
writeSub?: boolean;
|
||||||
|
writeAutoSub?: boolean;
|
||||||
|
allSubs?: boolean;
|
||||||
|
listSubs?: boolean;
|
||||||
|
subFormat?: string;
|
||||||
|
subLang?: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
twofactor?: string;
|
||||||
|
netrc?: boolean;
|
||||||
|
videoPassword?: string;
|
||||||
|
apMso?: string;
|
||||||
|
apUsername?: string;
|
||||||
|
apPassword?: string;
|
||||||
|
apListMso?: boolean;
|
||||||
|
extractAudio?: boolean;
|
||||||
|
audioFormat?: string;
|
||||||
|
audioQuality?: number;
|
||||||
|
recodeVideo?: string;
|
||||||
|
postprocessorArgs?: string;
|
||||||
|
keepVideo?: boolean;
|
||||||
|
noPostOverwrites?: boolean;
|
||||||
|
embedSubs?: boolean;
|
||||||
|
embedThumbnail?: boolean;
|
||||||
|
addMetadata?: boolean;
|
||||||
|
metadataFromFile?: string;
|
||||||
|
xattrs?: boolean;
|
||||||
|
fixup?: string;
|
||||||
|
preferAvconv?: boolean;
|
||||||
|
preferFfmpeg?: boolean;
|
||||||
|
ffmpegLocation?: string;
|
||||||
|
exec?: string;
|
||||||
|
convertSubs?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ytdlFormat = {
|
||||||
|
asr: number;
|
||||||
|
filesize: number;
|
||||||
|
format_id: string;
|
||||||
|
format_note: string;
|
||||||
|
fps: number;
|
||||||
|
height: number;
|
||||||
|
quality: number;
|
||||||
|
tbr: number;
|
||||||
|
url: string;
|
||||||
|
width: number;
|
||||||
|
ext: string;
|
||||||
|
vcodec: string;
|
||||||
|
acodec: string;
|
||||||
|
abr: number;
|
||||||
|
downloader_options: unknown;
|
||||||
|
container: string;
|
||||||
|
format: string;
|
||||||
|
protocol: string;
|
||||||
|
http_headers: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ytdlThumbnail = {
|
||||||
|
height: number;
|
||||||
|
url: string;
|
||||||
|
width: number;
|
||||||
|
resolution: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ytdlEntry = {
|
||||||
|
uploader: string;
|
||||||
|
duration: number;
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ytdlResponse = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
formats: ytdlFormat[];
|
||||||
|
thumbnails: ytdlThumbnail[];
|
||||||
|
description: string;
|
||||||
|
upload_date: string;
|
||||||
|
uploader: string;
|
||||||
|
uploader_id: string;
|
||||||
|
uploader_url: string;
|
||||||
|
channel_id: string;
|
||||||
|
channel_url: string;
|
||||||
|
duration: number;
|
||||||
|
view_count: number;
|
||||||
|
average_rating: number;
|
||||||
|
age_limit: number;
|
||||||
|
webpage_url: string;
|
||||||
|
categories: string[];
|
||||||
|
tags: string[];
|
||||||
|
is_live: boolean;
|
||||||
|
like_count: number;
|
||||||
|
dislike_count: number;
|
||||||
|
channel: string;
|
||||||
|
track: string;
|
||||||
|
artist: string;
|
||||||
|
creator: string;
|
||||||
|
alt_title: string;
|
||||||
|
extractor: string;
|
||||||
|
webpage_url_basename: string;
|
||||||
|
extractor_key: string;
|
||||||
|
playlist: string;
|
||||||
|
playlist_index: number;
|
||||||
|
thumbnail: string;
|
||||||
|
display_id: string;
|
||||||
|
requested_subtitles: unknown;
|
||||||
|
asr: number;
|
||||||
|
filesize: number;
|
||||||
|
format_id: string;
|
||||||
|
format_note: string;
|
||||||
|
fps: number;
|
||||||
|
height: number;
|
||||||
|
quality: number;
|
||||||
|
tbr: number;
|
||||||
|
url: string;
|
||||||
|
width: number;
|
||||||
|
ext: string;
|
||||||
|
vcodec: string;
|
||||||
|
acodec: string;
|
||||||
|
abr: number;
|
||||||
|
downloader_options: { http_chunk_size: number };
|
||||||
|
container: string;
|
||||||
|
format: string;
|
||||||
|
protocol: string;
|
||||||
|
http_headers: unknown;
|
||||||
|
fulltitle: string;
|
||||||
|
_filename: string;
|
||||||
|
entries: ytdlEntry[];
|
||||||
|
};
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, ContextMenuInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import fetch from "node-fetch";
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const content = ctx.options.getString("content");
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response: {id: string} = await fetch("https://unknowableshade.ru/api/share", { method: "post", body: JSON.stringify({content: content, lang: "plain text", isBigId: false}), headers: {'Content-Type': 'application/json'} }).then(res => res.json());
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`https://unknowableshade.ru/csh/${response.id}`)
|
|
||||||
.setURL(`https://unknowableshade.ru/csh/${response.id}`);
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Unable to maintain this request.");
|
|
||||||
|
|
||||||
ctx.editReply({embeds: [embed]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const data = {
|
|
||||||
name: "csh",
|
|
||||||
description: "Create shareable text note.",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "content",
|
|
||||||
description: "Text to share.",
|
|
||||||
type: "STRING",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
const data = {
|
|
||||||
key: "codeshr",
|
|
||||||
name: "CodeShr",
|
|
||||||
description: "CodeShr module v.1.0.0",
|
|
||||||
author: "unknowableshade"
|
|
||||||
}
|
|
||||||
export {data};
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { AudioResource } from "@discordjs/voice";
|
|
||||||
import { ColorResolvable, MessageEmbed } from "discord.js";
|
|
||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
import { track } from "./QuetzaMusic/Types";
|
|
||||||
|
|
||||||
export function randomShuffle(array: Array<any>) {
|
|
||||||
let currentIndex = array.length;
|
|
||||||
|
|
||||||
while (currentIndex != 0) {
|
|
||||||
let randomIndex = Math.floor(Math.random() * currentIndex);
|
|
||||||
currentIndex--;
|
|
||||||
|
|
||||||
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queueDesigner(queue: track[], page: number, nowPlaying: AudioResource<track> | undefined, nowPlayingPos: number): MessageEmbed {
|
|
||||||
let response = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Server queue:");
|
|
||||||
|
|
||||||
if (nowPlaying) {
|
|
||||||
const hDuration = `${Math.floor(nowPlaying.metadata.duration / 60 / 10)}${Math.floor(nowPlaying.metadata.duration / 60) % 10}`;
|
|
||||||
const mDuration = `${Math.floor(nowPlaying.metadata.duration % 60 / 10)}${nowPlaying.metadata.duration % 60 % 10}`;
|
|
||||||
const hPlayed = `${Math.floor(nowPlaying.playbackDuration / 1000 / 60 / 10)}${Math.floor(nowPlaying.playbackDuration / 1000 / 60) % 10}`;
|
|
||||||
const mPlayed = `${Math.floor(Math.floor(nowPlaying.playbackDuration / 1000) % 60 / 10)}${Math.floor(nowPlaying.playbackDuration / 1000) % 60 % 10}`;
|
|
||||||
|
|
||||||
response.setTitle(`${nowPlayingPos + 1}. ${nowPlaying.metadata.title}`)
|
|
||||||
.setURL(nowPlaying.metadata.url)
|
|
||||||
.setThumbnail(nowPlaying.metadata.thumbnail)
|
|
||||||
.setDescription(`${hPlayed}:${mPlayed} / ${hDuration}:${mDuration}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = page * 10; i < Math.min(queue.length, (page + 1) * 10); i++) {
|
|
||||||
const track = queue[i];
|
|
||||||
|
|
||||||
const h = `${Math.floor(track.duration / 60 / 10)}${Math.floor(track.duration / 60) % 10}`;
|
|
||||||
const m = `${Math.floor(track.duration % 60 / 10)}${track.duration % 60 % 10}`;
|
|
||||||
|
|
||||||
response.addField(`${i + 1}. ${track.title.slice(0, 35)}`, `${h}:${m}\n`, true);
|
|
||||||
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function asleep(ms: number): Promise<unknown> {
|
|
||||||
return new Promise(r => setTimeout(r, ms));
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { ytdlArgs, ytdlResponse } from "./Types"
|
|
||||||
import { execFile } from "child_process"
|
|
||||||
import { promisify } from "util";
|
|
||||||
import { quetzaConfig } from "../../../../config";
|
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
|
||||||
|
|
||||||
function argumentsResolver(args: ytdlArgs): string[] {
|
|
||||||
const options = Object.entries(args);
|
|
||||||
return options.map(([key, value]) => {
|
|
||||||
if (typeof value === "boolean" && value) {
|
|
||||||
return `--${key.split(/(?=[A-Z])/).join("-")}`.toLowerCase();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return `--${key.split(/(?=[A-Z])/).join("-")}=${value}`.toLowerCase();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ytdlExec(query: string, args: ytdlArgs): Promise<ytdlResponse | undefined> {
|
|
||||||
try {
|
|
||||||
const executable = await execFileAsync(quetzaConfig.rootDir + "../bin/youtube-dl", [`${query}`].concat(argumentsResolver(args)));
|
|
||||||
return JSON.parse(executable.stdout);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function searchMusic(query: string, args: ytdlArgs): Promise<ytdlResponse | undefined> {
|
|
||||||
let response = await ytdlExec(query, args);
|
|
||||||
|
|
||||||
if (response && !response.extractor.includes("youtube")) {
|
|
||||||
response = await ytdlExec(`${response.title} - ${response.creator}`, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
import { User } from "discord.js";
|
|
||||||
|
|
||||||
export interface track {
|
|
||||||
url: string;
|
|
||||||
title: string;
|
|
||||||
thumbnail: string;
|
|
||||||
duration: number;
|
|
||||||
requester: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ytdlArgs {
|
|
||||||
help?: boolean,
|
|
||||||
version?: boolean,
|
|
||||||
update?: boolean,
|
|
||||||
ignoreErrors?: boolean,
|
|
||||||
abortOnError?: boolean,
|
|
||||||
dumpUserAgent?: boolean,
|
|
||||||
listExtractors?: boolean,
|
|
||||||
extractorDescriptions?: boolean,
|
|
||||||
forceGenericExtractor?: boolean,
|
|
||||||
defaultSearch?: string,
|
|
||||||
igonreConfig?: boolean,
|
|
||||||
configLocation?: string,
|
|
||||||
flatPlaylist?: boolean,
|
|
||||||
markWatched?: boolean,
|
|
||||||
noColor?: boolean,
|
|
||||||
proxy?: string,
|
|
||||||
socketTimeout?: number,
|
|
||||||
sourceAddress?: string,
|
|
||||||
forceIpv4?: boolean,
|
|
||||||
forceIpv6?: boolean,
|
|
||||||
geoVerificationProxy?: string,
|
|
||||||
geoBypass?: boolean,
|
|
||||||
geoBypassCountry?: string,
|
|
||||||
geoBypassIpBlock?: string,
|
|
||||||
playlistStart?: number,
|
|
||||||
playlistEnd?: number | "last",
|
|
||||||
playlistItems?: string,
|
|
||||||
matchTitle?: string,
|
|
||||||
rejectTitle?: string,
|
|
||||||
maxDownloads?: number,
|
|
||||||
minFilesize?: string,
|
|
||||||
maxFilesize?: string,
|
|
||||||
date?: string,
|
|
||||||
datebefore?: string,
|
|
||||||
dateafter?: string,
|
|
||||||
minViews?: number,
|
|
||||||
maxViews?: number,
|
|
||||||
matchFilter?: string,
|
|
||||||
noPlaylist?: boolean,
|
|
||||||
yesPlaylist?: boolean,
|
|
||||||
ageLimit?: number,
|
|
||||||
downloadArchive?: string,
|
|
||||||
includeAds?: boolean,
|
|
||||||
limitRate?: string,
|
|
||||||
retries?: number | "infinite",
|
|
||||||
skipUnavailableFragments?: boolean,
|
|
||||||
abortOnUnavailableFragment?: boolean,
|
|
||||||
keepFragments?: boolean,
|
|
||||||
bufferSize?: string,
|
|
||||||
noResizeBuffer?: boolean,
|
|
||||||
httpChunkSize?: string,
|
|
||||||
playlistReverse?: boolean,
|
|
||||||
playlistRandom?: boolean,
|
|
||||||
xattrSetFilesize?: boolean,
|
|
||||||
hlsPreferNative?: boolean,
|
|
||||||
hlsPreferFfmpeg?: boolean,
|
|
||||||
hlsUseMpegts?: boolean,
|
|
||||||
externalDownloader?: string,
|
|
||||||
externalDownloaderArgs?: string,
|
|
||||||
batchFile?: string,
|
|
||||||
id?: boolean,
|
|
||||||
output?: string,
|
|
||||||
outputNaPlaceholder?: string,
|
|
||||||
autonumberStart?: number,
|
|
||||||
restrictFilenames?: boolean,
|
|
||||||
noOverwrites?: boolean,
|
|
||||||
continue?: boolean,
|
|
||||||
noPart?: boolean,
|
|
||||||
noMtime?: boolean,
|
|
||||||
writeDescription?: boolean,
|
|
||||||
writeInfoJson?: boolean,
|
|
||||||
writeAnnotations?: boolean,
|
|
||||||
loadInfoJson?: string,
|
|
||||||
cookies?: string,
|
|
||||||
cacheDir?: string,
|
|
||||||
noCacheDir?: boolean,
|
|
||||||
rmCacheDir?: boolean,
|
|
||||||
writeThumbnail?: boolean,
|
|
||||||
writeAllThumbnails?: boolean,
|
|
||||||
listThumbnails?: boolean,
|
|
||||||
quiet?: boolean,
|
|
||||||
noWarnings?: boolean,
|
|
||||||
simulate?: boolean,
|
|
||||||
skipDownload?: boolean,
|
|
||||||
getUrl?: boolean,
|
|
||||||
getTitle?: boolean,
|
|
||||||
getId?: boolean,
|
|
||||||
getThumbnail?: boolean,
|
|
||||||
getDuration?: boolean,
|
|
||||||
getFilename?: boolean,
|
|
||||||
getFormat?: boolean,
|
|
||||||
dumpJson?: boolean,
|
|
||||||
dumpSingleJson?: boolean,
|
|
||||||
printJson?: boolean,
|
|
||||||
newline?: boolean,
|
|
||||||
noProgress?: boolean,
|
|
||||||
consoleTitle?: boolean,
|
|
||||||
verbose?: boolean,
|
|
||||||
dumpPages?: boolean,
|
|
||||||
writePages?: boolean,
|
|
||||||
printTraffic?: boolean,
|
|
||||||
callHome?: boolean,
|
|
||||||
encoding?: string,
|
|
||||||
noCheckCertificate?: boolean,
|
|
||||||
preferInsecure?: boolean,
|
|
||||||
userAgent?: string,
|
|
||||||
referer?: string,
|
|
||||||
addHeader?: string,
|
|
||||||
bidiWorkaround?: boolean,
|
|
||||||
sleepInterval?: number,
|
|
||||||
maxSleepInterval?: number,
|
|
||||||
format?: string,
|
|
||||||
allFormats?: boolean,
|
|
||||||
preferFreeFormats?: boolean,
|
|
||||||
listFormats?: boolean,
|
|
||||||
youtubeSkipDashManifest?: boolean,
|
|
||||||
mergeOutputFormat?: string,
|
|
||||||
writeSub?: boolean,
|
|
||||||
writeAutoSub?: boolean,
|
|
||||||
allSubs?: boolean,
|
|
||||||
listSubs?: boolean,
|
|
||||||
subFormat?: string,
|
|
||||||
subLang?: string,
|
|
||||||
username?: string,
|
|
||||||
password?: string,
|
|
||||||
twofactor?: string,
|
|
||||||
netrc?: boolean,
|
|
||||||
videoPassword?: string,
|
|
||||||
apMso?: string,
|
|
||||||
apUsername?: string,
|
|
||||||
apPassword?: string,
|
|
||||||
apListMso?: boolean,
|
|
||||||
extractAudio?: boolean,
|
|
||||||
audioFormat?: string,
|
|
||||||
audioQuality?: number,
|
|
||||||
recodeVideo?: string,
|
|
||||||
postprocessorArgs?: string,
|
|
||||||
keepVideo?: boolean,
|
|
||||||
noPostOverwrites?: boolean,
|
|
||||||
embedSubs?: boolean,
|
|
||||||
embedThumbnail?: boolean,
|
|
||||||
addMetadata?: boolean,
|
|
||||||
metadataFromFile?: string,
|
|
||||||
xattrs?: boolean,
|
|
||||||
fixup?: string,
|
|
||||||
preferAvconv?: boolean,
|
|
||||||
preferFfmpeg?: boolean,
|
|
||||||
ffmpegLocation?: string,
|
|
||||||
exec?: string,
|
|
||||||
convertSubs?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ytdlFormat {
|
|
||||||
asr: number,
|
|
||||||
filesize: number,
|
|
||||||
format_id: string,
|
|
||||||
format_note: string,
|
|
||||||
fps: number,
|
|
||||||
height: number,
|
|
||||||
quality: number,
|
|
||||||
tbr: number,
|
|
||||||
url: string,
|
|
||||||
width: number,
|
|
||||||
ext: string,
|
|
||||||
vcodec: string,
|
|
||||||
acodec: string,
|
|
||||||
abr: number,
|
|
||||||
downloader_options: unknown,
|
|
||||||
container: string,
|
|
||||||
format: string,
|
|
||||||
protocol: string,
|
|
||||||
http_headers: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ytdlThumbnail {
|
|
||||||
height: number,
|
|
||||||
url: string,
|
|
||||||
width: number,
|
|
||||||
resolution: string,
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ytdlEntry {
|
|
||||||
uploader: string,
|
|
||||||
duration: number,
|
|
||||||
id: string,
|
|
||||||
title: string,
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ytdlResponse {
|
|
||||||
id: string,
|
|
||||||
title: string,
|
|
||||||
formats: ytdlFormat[],
|
|
||||||
thumbnails: ytdlThumbnail[],
|
|
||||||
description: string,
|
|
||||||
upload_date: string,
|
|
||||||
uploader: string,
|
|
||||||
uploader_id: string,
|
|
||||||
uploader_url: string,
|
|
||||||
channel_id: string,
|
|
||||||
channel_url: string,
|
|
||||||
duration: number,
|
|
||||||
view_count: number,
|
|
||||||
average_rating: number,
|
|
||||||
age_limit: number,
|
|
||||||
webpage_url: string,
|
|
||||||
categories: string[],
|
|
||||||
tags: string[],
|
|
||||||
is_live: boolean,
|
|
||||||
like_count: number,
|
|
||||||
dislike_count: number,
|
|
||||||
channel: string,
|
|
||||||
track: string,
|
|
||||||
artist: string,
|
|
||||||
creator: string,
|
|
||||||
alt_title: string,
|
|
||||||
extractor: string,
|
|
||||||
webpage_url_basename: string,
|
|
||||||
extractor_key: string,
|
|
||||||
playlist: string,
|
|
||||||
playlist_index: number,
|
|
||||||
thumbnail: string,
|
|
||||||
display_id: string,
|
|
||||||
requested_subtitles: unknown,
|
|
||||||
asr: number,
|
|
||||||
filesize: number,
|
|
||||||
format_id: string,
|
|
||||||
format_note: string,
|
|
||||||
fps: number,
|
|
||||||
height: number,
|
|
||||||
quality: number,
|
|
||||||
tbr: number,
|
|
||||||
url: string,
|
|
||||||
width: number,
|
|
||||||
ext: string,
|
|
||||||
vcodec: string,
|
|
||||||
acodec: string,
|
|
||||||
abr: number,
|
|
||||||
downloader_options: { http_chunk_size: number },
|
|
||||||
container: string,
|
|
||||||
format: string,
|
|
||||||
protocol: string,
|
|
||||||
http_headers: unknown,
|
|
||||||
fulltitle: string,
|
|
||||||
_filename: string,
|
|
||||||
entries: ytdlEntry[]
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
export class Filter {
|
|
||||||
public readonly filters: Record<string, string> = {
|
|
||||||
"bassboost_low": "bass=g=15:f=110:w=0.3",
|
|
||||||
"bassboost": "bass=g=20:f=110:w=0.3",
|
|
||||||
"bassboost_high": "bass=g=30:f=110:w=0.3",
|
|
||||||
"8D": "apulsator=hz=0.09",
|
|
||||||
"vaporwave": "aresample=48000,asetrate=48000*0.75",
|
|
||||||
"nightcore": "aresample=48000,asetrate=48000*1.25",
|
|
||||||
"phaser": "aphaser=in_gain=0.4",
|
|
||||||
"tremolo": "tremolo",
|
|
||||||
"vibrato": "vibrato=f=6.5",
|
|
||||||
"reverse": "areverse",
|
|
||||||
"treble": "treble=g=5",
|
|
||||||
"normalizer": "dynaudnorm=g=101",
|
|
||||||
"normalizer2": "acompressor",
|
|
||||||
"surrounding": "surround",
|
|
||||||
"pulsator": "apulsator=hz=1",
|
|
||||||
"subboost": "asubboost",
|
|
||||||
"karaoke": "stereotools=mlev=0.03",
|
|
||||||
"flanger": "flanger",
|
|
||||||
"gate": "agate",
|
|
||||||
"haas": "haas",
|
|
||||||
"mcompand": "mcompand",
|
|
||||||
"mono": "pan=mono|c0=.5*c0+.5*c1",
|
|
||||||
"mstlr": "stereotools=mode=ms>lr",
|
|
||||||
"mstrr": "stereotools=mode=ms>rr",
|
|
||||||
"compressor": "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
|
|
||||||
"expander": "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
|
|
||||||
"softlimiter": "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
|
|
||||||
"chorus": "chorus=0.7:0.9:55:0.4:0.25:2",
|
|
||||||
"chorus2d": "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3",
|
|
||||||
"chorus3d": "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
|
|
||||||
"fadein": "afade=t=in:ss=0:d=10",
|
|
||||||
"dim": `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`,
|
|
||||||
"earrape": "channelsplit,sidechaingate=level_in=64"
|
|
||||||
};
|
|
||||||
|
|
||||||
private activeFilters: Map<string, string> = new Map();
|
|
||||||
|
|
||||||
public get ActiveFilters(): string[] {
|
|
||||||
return Array.from(this.activeFilters.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggleFilter(filter: string | null): boolean {
|
|
||||||
if (!filter) {
|
|
||||||
this.activeFilters.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const arg = this.filters[filter];
|
|
||||||
|
|
||||||
if (arg) {
|
|
||||||
this.activeFilters.has(filter) ? this.activeFilters.delete(filter) : this.activeFilters.set(filter, arg);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public empty(): boolean {
|
|
||||||
return this.activeFilters.size == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString(): string {
|
|
||||||
return Array.from(this.activeFilters.values()).join(",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
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 ytdl from "discord-ytdl-core";
|
|
||||||
import { track, loopOption } from "./Types";
|
|
||||||
import { randomShuffle } from "../Misc";
|
|
||||||
import { searchMusic } from "../Music-DL/SearchMusic";
|
|
||||||
import { Filter } from "./Filter";
|
|
||||||
import { quetzaConfig } from "../../../../config";
|
|
||||||
|
|
||||||
export class Player {
|
|
||||||
private guild: Guild;
|
|
||||||
private music: Music;
|
|
||||||
|
|
||||||
private channel: TextChannel;
|
|
||||||
public get Channel(): TextChannel {
|
|
||||||
return this.channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private connection: VoiceConnection | undefined;
|
|
||||||
public get Connection(): VoiceConnection | undefined {
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
private player: AudioPlayer | undefined;
|
|
||||||
public get Player(): AudioPlayer | undefined {
|
|
||||||
return this.player;
|
|
||||||
}
|
|
||||||
|
|
||||||
private queue: track[] = [];
|
|
||||||
public get Queue(): track[] {
|
|
||||||
return this.queue;
|
|
||||||
}
|
|
||||||
public set Queue(queue: track[]) {
|
|
||||||
this.queue = queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private nowPlayingPos: number;
|
|
||||||
public get NowPlayingPos(): number {
|
|
||||||
return this.nowPlayingPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private nowPlaying: AudioResource<track> | undefined;
|
|
||||||
public get NowPlaying(): AudioResource<track> | undefined {
|
|
||||||
return this.nowPlaying;
|
|
||||||
}
|
|
||||||
|
|
||||||
private filters: Filter;
|
|
||||||
public get Filters(): string[] {
|
|
||||||
return this.filters.ActiveFilters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private repeatMode: loopOption = "LOOP";
|
|
||||||
private message: Message | undefined;
|
|
||||||
|
|
||||||
public constructor(playerGuild: Guild, clientMusic: Music, ctxChannel: TextChannel) {
|
|
||||||
this.music = clientMusic;
|
|
||||||
this.guild = playerGuild;
|
|
||||||
this.channel = ctxChannel;
|
|
||||||
this.filters = new Filter();
|
|
||||||
this.nowPlayingPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async playerCreator(track: track, seek: number): Promise<AudioPlayer> {
|
|
||||||
const player = createAudioPlayer();
|
|
||||||
|
|
||||||
const stream = ytdl(track.url, {
|
|
||||||
opusEncoded: true,
|
|
||||||
filter: "audioonly",
|
|
||||||
highWaterMark: 1 << 25,
|
|
||||||
seek: seek,
|
|
||||||
encoderArgs: !this.filters.empty() ? ["-af", this.filters.toString()] : []
|
|
||||||
});
|
|
||||||
|
|
||||||
this.nowPlaying = createAudioResource(stream, {
|
|
||||||
inlineVolume: true,
|
|
||||||
metadata: track
|
|
||||||
});
|
|
||||||
|
|
||||||
player.play(this.nowPlaying);
|
|
||||||
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resourceEndResolvable(): boolean {
|
|
||||||
if (this.nowPlaying)
|
|
||||||
this.nowPlaying.encoder?.destroy();
|
|
||||||
|
|
||||||
switch (this.repeatMode) {
|
|
||||||
case "NONE": {
|
|
||||||
if (++this.nowPlayingPos >= this.queue.length) {
|
|
||||||
this.nowPlayingPos = 0;
|
|
||||||
this.nowPlaying = undefined;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case "LOOP": {
|
|
||||||
this.nowPlayingPos = (this.nowPlayingPos + 1) % this.queue.length;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case "SONG": return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async messageResolvable(track: track): Promise<void> {
|
|
||||||
|
|
||||||
if (this.message) {
|
|
||||||
this.channel = this.message.channel as TextChannel;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.message.delete();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(track.title)
|
|
||||||
.setURL(track.url)
|
|
||||||
.setThumbnail(track.thumbnail)
|
|
||||||
.setDescription("Is now beeing played.");
|
|
||||||
|
|
||||||
embed.setAuthor({ name: `${track.requester.tag}`, iconURL: track.requester.avatarURL() || track.requester.avatarURL as unknown as string});
|
|
||||||
|
|
||||||
this.message = await this.channel.send({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async searchStream(query: string, requester: User): Promise<track[] | undefined> {
|
|
||||||
const response = await searchMusic(query, {
|
|
||||||
dumpSingleJson: true,
|
|
||||||
noWarnings: true,
|
|
||||||
noCheckCertificate: true,
|
|
||||||
flatPlaylist: true,
|
|
||||||
skipDownload: true,
|
|
||||||
defaultSearch: "ytsearch"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response)
|
|
||||||
return undefined;
|
|
||||||
|
|
||||||
let tracks: track[] = [];
|
|
||||||
|
|
||||||
if (response.entries) {
|
|
||||||
for (const entry of response.entries) {
|
|
||||||
tracks.push({
|
|
||||||
url: `https://youtube.com/watch?v=${entry.id}`,
|
|
||||||
title: entry.title,
|
|
||||||
thumbnail: `https://img.youtube.com/vi/${entry.id}/default.jpg`,
|
|
||||||
duration: entry.duration,
|
|
||||||
requester: requester
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tracks.push({
|
|
||||||
url: `https://youtube.com/watch?v=${response.id}`,
|
|
||||||
title: response.title,
|
|
||||||
thumbnail: `https://img.youtube.com/vi/${response.id}/default.jpg`,
|
|
||||||
duration: response.duration,
|
|
||||||
requester: requester
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async play(seek = 0): Promise<void> {
|
|
||||||
if (!this.connection || this.queue.length === 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.nowPlaying)
|
|
||||||
this.nowPlaying.encoder?.destroy();
|
|
||||||
|
|
||||||
const track = this.queue[this.nowPlayingPos];
|
|
||||||
|
|
||||||
this.player = await this.playerCreator(track, seek);
|
|
||||||
|
|
||||||
this.connection.subscribe(this.player);
|
|
||||||
|
|
||||||
this.player.on(AudioPlayerStatus.Idle, async () => {
|
|
||||||
if (this.resourceEndResolvable())
|
|
||||||
await this.play();
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.messageResolvable(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
public connect(channel: VoiceChannel | StageChannel): void {
|
|
||||||
this.connection = joinVoiceChannel({
|
|
||||||
channelId: channel.id,
|
|
||||||
guildId: channel.guildId,
|
|
||||||
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, 10_000),
|
|
||||||
entersState(this.connection as VoiceConnection, VoiceConnectionStatus.Connecting, 10_000),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
this.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addTrack(query: string, requester: User, index?: number): Promise<track | undefined> {
|
|
||||||
const metadata = await this.searchStream(query, requester);
|
|
||||||
|
|
||||||
if (!metadata) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!index || index >= this.queue.length) {
|
|
||||||
this.queue = this.queue.concat(metadata);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.queue.splice(Math.max(0, index), 0, ...metadata)
|
|
||||||
if (this.nowPlayingPos >= index)
|
|
||||||
this.nowPlayingPos += metadata.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public clear() {
|
|
||||||
this.queue = [];
|
|
||||||
this.nowPlayingPos = 0;
|
|
||||||
this.nowPlaying = undefined;
|
|
||||||
if (this.player)
|
|
||||||
this.player.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
if (this.connection)
|
|
||||||
this.connection.destroy();
|
|
||||||
if (this.nowPlaying)
|
|
||||||
this.nowPlaying.encoder?.destroy;
|
|
||||||
this.music.delPlayer(this.guild.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async jump(query: number | string): Promise<boolean> {
|
|
||||||
if (typeof query === "number" && query >= 0 && query < this.queue.length) {
|
|
||||||
this.nowPlayingPos = query;
|
|
||||||
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())) {
|
|
||||||
this.nowPlayingPos = i;
|
|
||||||
await this.play();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async remove(query: number | string): Promise<boolean> {
|
|
||||||
if (typeof query === "number" && query >= 0 && query < this.queue.length) {
|
|
||||||
this.queue.splice(query, 1);
|
|
||||||
if (query === this.nowPlayingPos)
|
|
||||||
await this.play();
|
|
||||||
else if (query >= this.nowPlayingPos)
|
|
||||||
this.nowPlayingPos--;
|
|
||||||
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())) {
|
|
||||||
this.queue.splice(i, 1);
|
|
||||||
if (i === this.nowPlayingPos)
|
|
||||||
await this.play();
|
|
||||||
else if (i >= this.nowPlayingPos)
|
|
||||||
this.nowPlayingPos--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async seek(time: number): Promise<void> {
|
|
||||||
await this.play(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
public pause(): boolean {
|
|
||||||
if (!this.player)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (this.player.state.status === AudioPlayerStatus.Paused) {
|
|
||||||
this.player.unpause();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.player.pause();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public repeat(mode: loopOption): void {
|
|
||||||
this.repeatMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public volume(amount: number): boolean {
|
|
||||||
if (amount > 0 && amount < Infinity && this.nowPlaying && this.nowPlaying.volume) {
|
|
||||||
this.nowPlaying.volume.setVolumeLogarithmic(amount / 100);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reshuffle(): void {
|
|
||||||
this.queue = randomShuffle(this.queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public skip(): void {
|
|
||||||
if (this.player)
|
|
||||||
this.player.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async filter(filter: string | null): Promise<boolean> {
|
|
||||||
if (this.filters.toggleFilter(filter)) {
|
|
||||||
await this.play(this.nowPlaying?.playbackDuration);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Music {
|
|
||||||
private players = new Map<string, Player>();
|
|
||||||
|
|
||||||
public getPlayer(guildId: string): Player | undefined {
|
|
||||||
const player = this.players.get(guildId);
|
|
||||||
if (player)
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
|
|
||||||
public genPlayer(guild: Guild, music: Music, channel: TextChannel): Player {
|
|
||||||
const player = this.players.get(guild.id);
|
|
||||||
if (!player) {
|
|
||||||
const newPlayer = new module.exports.Player(guild, music, channel);
|
|
||||||
this.players.set(guild.id, newPlayer);
|
|
||||||
return newPlayer;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public delPlayer(guildId: string) {
|
|
||||||
this.players.delete(guildId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { readFile, readFileSync, writeFile, writeFileSync } from "fs";
|
|
||||||
import { saveEntry, track } from "./Types";
|
|
||||||
|
|
||||||
export class QueueStorage {
|
|
||||||
private data: Map<string, saveEntry>;
|
|
||||||
public get Data() {
|
|
||||||
return this.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public constructor(path: string) {
|
|
||||||
try {
|
|
||||||
this.data = new Map(Object.entries(JSON.parse(readFileSync(path).toString("utf-8"))));
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
this.data = new Map<string, saveEntry>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public createEntry(queue: track[], title: string, description: string, requester: string): void {
|
|
||||||
this.data.set(title, {
|
|
||||||
owner: requester,
|
|
||||||
description: description,
|
|
||||||
tracks: queue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeEntry(title: string): void {
|
|
||||||
this.data.delete(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEntry(title: string): saveEntry | undefined {
|
|
||||||
return this.data.get(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
public saveData(path: string): boolean {
|
|
||||||
try {
|
|
||||||
writeFileSync(path, JSON.stringify(Object.fromEntries(this.data)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { User } from "discord.js";
|
|
||||||
|
|
||||||
export interface track {
|
|
||||||
url: string;
|
|
||||||
title: string;
|
|
||||||
thumbnail: string;
|
|
||||||
duration: number;
|
|
||||||
requester: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface saveEntry {
|
|
||||||
description: string,
|
|
||||||
owner: string,
|
|
||||||
tracks: track[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type loopOption = "LOOP" | "SONG" | "NONE" | "AUTO";
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
await ctx.deferReply();
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (player) {
|
|
||||||
player.clear();
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Queue has been cleared.");
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "clear",
|
|
||||||
description: "Clears queue."
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ColorResolvable, CommandInteraction, GuildMember,
|
|
||||||
MessageEmbed, TextChannel
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction, reply = true) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (reply)
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.genPlayer(ctx.guild, client.Modules["music"].control, ctx.channel as TextChannel);
|
|
||||||
|
|
||||||
let embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.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.Modules["music"].control.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 }
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { QueueStorage } from "../assets/QuetzaMusic/QueueStorage";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const title = ctx.options.getString("title");
|
|
||||||
|
|
||||||
if (ctx.guild === null || title === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const data = new QueueStorage(`./data/queues/${ctx.guild.id}.json`);
|
|
||||||
|
|
||||||
const slot = data.getEntry(title);
|
|
||||||
|
|
||||||
if (slot) {
|
|
||||||
if (slot.owner === ctx.user.id) {
|
|
||||||
data.removeEntry(title);
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`${title} has been deleted.`)
|
|
||||||
.setDescription(slot.description);
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [embed] });
|
|
||||||
|
|
||||||
data.saveData(`./data/queues/${ctx.guild.id}.json`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Sorry, but ${title} save is not yours. You can not do that.`);
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "delete",
|
|
||||||
description: "Load queue to current queue.",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "title",
|
|
||||||
description: "Delete slot title.",
|
|
||||||
type: "STRING",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
const filters: string[] = player.Filters;
|
|
||||||
|
|
||||||
let embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(player.Filters.length != 0 ? `Player active filters` : `There are no active filters.`);
|
|
||||||
filters.forEach((filter: string) => {
|
|
||||||
embed.addField(
|
|
||||||
filter,
|
|
||||||
"*",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "filters",
|
|
||||||
description: "Print out list of active filters."
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const pos = ctx.options.getInteger("position");
|
|
||||||
const query = ctx.options.getString("query");
|
|
||||||
|
|
||||||
if (!ctx.guild || !query || pos === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply()
|
|
||||||
|
|
||||||
const track = await player.addTrack(query, ctx.user, pos - 1);
|
|
||||||
let embed: MessageEmbed;
|
|
||||||
|
|
||||||
if (!track) {
|
|
||||||
embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Cannot append the track or playlist.")
|
|
||||||
.setAuthor({ name: `@${ctx.user.tag}`, iconURL: ctx.user.avatarURL() as string });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(track.title)
|
|
||||||
.setDescription("Has been pushed to queue.")
|
|
||||||
.setURL(track.url)
|
|
||||||
.setThumbnail(track.thumbnail)
|
|
||||||
.setAuthor({ name: `@${ctx.user.tag}`, iconURL: 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,56 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const query = ctx.options.getString("position");
|
|
||||||
|
|
||||||
if (ctx.guild === null || query === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
let player = client.Modules["music"].control.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(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Could not find track specified.");
|
|
||||||
|
|
||||||
if (state) {
|
|
||||||
embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Jumped.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "jump",
|
|
||||||
description: "Jump to specific track in queue.",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "position",
|
|
||||||
description: "Position to jump to.",
|
|
||||||
type: "STRING",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { QueueStorage } from "../assets/QuetzaMusic/QueueStorage";
|
|
||||||
import { run as connect } from "./connect";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const title = ctx.options.getString("title");
|
|
||||||
|
|
||||||
if (ctx.guild === null || title === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
const player = await connect(client, ctx, false);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const data = new QueueStorage(`./data/queues/${ctx.guild.id}.json`);
|
|
||||||
|
|
||||||
const slot = data.getEntry(title);
|
|
||||||
|
|
||||||
if (slot) {
|
|
||||||
player.Queue = player.Queue.concat(slot.tracks);
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`${title} has been appended to queue.`)
|
|
||||||
.setDescription(slot.description);
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!player.NowPlaying) {
|
|
||||||
await player.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "load",
|
|
||||||
description: "Load queue to current queue.",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "title",
|
|
||||||
description: "Load slot title.",
|
|
||||||
type: "STRING",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const query = ctx.options.getString("option");
|
|
||||||
|
|
||||||
if (ctx.guild === null || query === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
let player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
player.repeat(query);
|
|
||||||
|
|
||||||
let embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable);
|
|
||||||
|
|
||||||
switch (query) {
|
|
||||||
case "LOOP": {
|
|
||||||
embed.setTitle("Now looping queue.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "SONG": {
|
|
||||||
embed.setTitle("Now looping current song.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "NONE": {
|
|
||||||
embed.setTitle("After queue end player will be destroyed.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,51 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { Music } from "../assets/QuetzaMusic/Music";
|
|
||||||
|
|
||||||
import fetch from "node-fetch";
|
|
||||||
|
|
||||||
|
|
||||||
const dividers = [
|
|
||||||
`</div></div></div></div><div class="hwc"><div class="BNeawe tAd8D AP7Wnd"><div><div class="BNeawe tAd8D AP7Wnd">`,
|
|
||||||
`</div></div></div></div></div><div><span class="hwc"><div class="BNeawe uEec3 AP7Wnd">`
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const player = (client.Modules["music"].control as Music).getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player || !player.NowPlaying)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(player.NowPlaying.metadata.title)
|
|
||||||
.setDescription("Lyrics powered by Google and their partners.");
|
|
||||||
|
|
||||||
const response = await fetch(`https://www.google.com/search?q=${player.NowPlaying.metadata.title.replace(" ", "+").replace(/ *\[[^)]*\] */g, "") + "+Lyrics"}`)
|
|
||||||
const data = await response.text();
|
|
||||||
|
|
||||||
if (!data.includes(dividers[0])) {
|
|
||||||
embed.setTitle("Could not find lyrics for that one.");
|
|
||||||
ctx.editReply({ embeds: [embed] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
await ctx.editReply({ content: `\`\`\`\n${data.split(dividers[0])[1].split(dividers[1])[0]}\`\`\``, embeds: [embed]});
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "lyrics",
|
|
||||||
description: "Get lyrics for current song.",
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (player) {
|
|
||||||
player.skip();
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Skipped.");
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "next",
|
|
||||||
description: "Skip current song."
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (player) {
|
|
||||||
const state = player.pause();
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Player is now ${state ? "paused" : "resumed"}.`);
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "pause",
|
|
||||||
description: "Toggle pause.",
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { QueueStorage } from "../assets/QuetzaMusic/QueueStorage";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
const data = new QueueStorage(`./data/queues/${ctx.guild.id}.json`);
|
|
||||||
|
|
||||||
let embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`${ctx.guild.name} queues:`);
|
|
||||||
|
|
||||||
data.Data.forEach((entry, title) => {
|
|
||||||
embed.addField(title, entry.description);
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "peak",
|
|
||||||
description: "Peak at guild storage."
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { run as connect } from "./connect";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, 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(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Now playing.")
|
|
||||||
.setAuthor({ name:`@${ctx.user.tag}`, iconURL: ctx.user.avatarURL() as string });
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
const track = await player.addTrack(query, ctx.user);
|
|
||||||
|
|
||||||
if (!track) {
|
|
||||||
embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Cannot append the track or playlist.")
|
|
||||||
.setAuthor({ name: `@${ctx.user.tag}`, iconURL: ctx.user.avatarURL() as string });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(track.title)
|
|
||||||
.setDescription("Has been pushed to queue.")
|
|
||||||
.setURL(track.url)
|
|
||||||
.setThumbnail(track.thumbnail)
|
|
||||||
.setAuthor({ name: `@${ctx.user.tag}`, iconURL: 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 }
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import {
|
|
||||||
ButtonInteraction, ColorResolvable,
|
|
||||||
CommandInteraction, Message, MessageActionRow,
|
|
||||||
MessageButton, MessageEmbed
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { queueDesigner } from "../assets/Misc"
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
const queue = player.Queue;
|
|
||||||
|
|
||||||
if (queue.length === 0) {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.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")
|
|
||||||
);
|
|
||||||
|
|
||||||
const message = await ctx.editReply({
|
|
||||||
embeds: [ queueDesigner(queue, page, player.NowPlaying, player.NowPlayingPos) ],
|
|
||||||
components: [row]
|
|
||||||
}) as Message;
|
|
||||||
|
|
||||||
const collector = message.createMessageComponentCollector({ time: 15000 });
|
|
||||||
|
|
||||||
collector.on("collect", async (btn: ButtonInteraction) => {
|
|
||||||
console.log(page);
|
|
||||||
switch (btn.customId) {
|
|
||||||
case "QueueTop": { page = 0; break; }
|
|
||||||
case "QueueUp": { page = Math.max(page - 1, 0); break; }
|
|
||||||
case "QueueDown": { page = Math.min(page + 1, maxPage); break; }
|
|
||||||
case "QueueEnd": page = maxPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
await btn.update({
|
|
||||||
embeds: [ queueDesigner(queue, page, player.NowPlaying, player.NowPlayingPos) ]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "queue",
|
|
||||||
description: "Send player queue.",
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const query = ctx.options.getString("position");
|
|
||||||
|
|
||||||
if (ctx.guild === null || query === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
let player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
let state: boolean;
|
|
||||||
|
|
||||||
if (!isNaN(Number(query))) {
|
|
||||||
state = await player.remove(Number(query) - 1);
|
|
||||||
} else {
|
|
||||||
state = await player.remove(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
let embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Could not find track specified.");
|
|
||||||
|
|
||||||
if (state) {
|
|
||||||
embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Removed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "remove",
|
|
||||||
description: "Remove specific track in queue.",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "position",
|
|
||||||
description: "Position to remove.",
|
|
||||||
type: "STRING",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { QueueStorage } from "../assets/QuetzaMusic/QueueStorage";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const title = ctx.options.getString("title");
|
|
||||||
const description = ctx.options.getString("description");
|
|
||||||
|
|
||||||
if (ctx.guild === null || title === null || description === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
let player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
if (player.Queue.length !== 0) {
|
|
||||||
const data = new QueueStorage(`./data/queues/${ctx.guild.id}.json`);
|
|
||||||
|
|
||||||
const slot = data.getEntry(title);
|
|
||||||
|
|
||||||
if (slot) {
|
|
||||||
if (slot.owner === ctx.user.id) {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Aww... ${title} already exists. Delete it first before saving a new one.`);
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Sorry, but ${title} save is not yours. You can not do that.`);
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data.createEntry(player.Queue, title, description, ctx.user.id);
|
|
||||||
data.saveData(`./data/queues/${ctx.guild.id}.json`)
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`I will create new save entry ${title}`);
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Looks like an issue. Make sure you are doing it well`);
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "save",
|
|
||||||
description: "Save queue to guild storage.",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "title",
|
|
||||||
description: "Save slot title.",
|
|
||||||
type: "STRING",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
description: "Save slot description.",
|
|
||||||
type: "STRING",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const hours = ctx.options.getInteger("hrs") as number;
|
|
||||||
const minutes = ctx.options.getInteger("mins") as number;
|
|
||||||
const seconds = ctx.options.getInteger("secs") as number;
|
|
||||||
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const time = hours * 3600 + minutes * 60 + seconds;
|
|
||||||
|
|
||||||
let player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (player) {
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
await player.seek(time);
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.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 }
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
if (ctx.guild === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (player) {
|
|
||||||
player.destroy();
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Player is now destroyed.`);
|
|
||||||
|
|
||||||
await ctx.reply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "stop",
|
|
||||||
description: "Destroys player.",
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
const volume = ctx.options.getInteger("volume");
|
|
||||||
|
|
||||||
if (ctx.guild === null || volume === null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
let player = client.Modules["music"].control.getPlayer(ctx.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
let embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Error occured while setting the volume.`);
|
|
||||||
|
|
||||||
if (player.volume(volume)) {
|
|
||||||
embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle(`Volume has been set to ${volume / 100}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "volume",
|
|
||||||
description: "Set music volume.",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "volume",
|
|
||||||
description: "Volume to switch to in percents.",
|
|
||||||
type: "INTEGER",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { ColorResolvable, MessageEmbed, VoiceState } from "discord.js";
|
|
||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
import { asleep } from "../assets/Misc";
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, args: VoiceState[]): Promise<void> {
|
|
||||||
const [oldState, newState] = args;
|
|
||||||
|
|
||||||
const player = client.Modules["music"].control.getPlayer(newState.guild.id);
|
|
||||||
|
|
||||||
if (!player)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (oldState.channel?.members.size === 1 && oldState.channel.members.first()?.id === client.user?.id) {
|
|
||||||
await asleep(30000);
|
|
||||||
const channel = player.Channel;
|
|
||||||
if (oldState.channel.members.size === 1) {
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("I've decided to leave to conserve my energy")
|
|
||||||
.setDescription("Since there was nobody in the voice channel.")
|
|
||||||
await channel.send({ embeds: [embed] });
|
|
||||||
player.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const name = "voiceStateUpdate";
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { Music } from "./assets/QuetzaMusic/Music";
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
key: "music",
|
|
||||||
name: "QuetzaMusic",
|
|
||||||
description: "Quetza music module v.1.5.0",
|
|
||||||
author: "unknowableshade"
|
|
||||||
}
|
|
||||||
|
|
||||||
const control = new Music();
|
|
||||||
|
|
||||||
export {data, control};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { quetzaConfig } from "../../../config";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
import { ColorResolvable, CommandInteraction, MessageEmbed } from "discord.js";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, ctx: CommandInteraction) {
|
|
||||||
await ctx.deferReply();
|
|
||||||
|
|
||||||
const embed = new MessageEmbed()
|
|
||||||
.setColor(quetzaConfig.color as ColorResolvable)
|
|
||||||
.setTitle("Quetza applied modules:");
|
|
||||||
|
|
||||||
for (const value of Object.values(client.Modules)) {
|
|
||||||
embed.addField(
|
|
||||||
value.data.name,
|
|
||||||
`${value.data.description} (${value.data.author})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.editReply({ embeds: [embed] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: "modules",
|
|
||||||
description: "Print out list of accessable modules."
|
|
||||||
}
|
|
||||||
|
|
||||||
export { data }
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Interaction } from "discord.js";
|
|
||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, args: Interaction[]): Promise<void> {
|
|
||||||
const [interaction] = args;
|
|
||||||
|
|
||||||
if (interaction.isCommand()) {
|
|
||||||
const command = client.Commands.get(interaction.commandName);
|
|
||||||
|
|
||||||
if (command)
|
|
||||||
command.run(client, interaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const name = "interactionCreate";
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { QuetzaClient } from "../../../assets/QuetzaClient";
|
|
||||||
|
|
||||||
export async function run(client: QuetzaClient, args: unknown[]): Promise<void> {
|
|
||||||
if (!client.user)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Object.values(client.Modules).forEach((value) => {
|
|
||||||
console.log(`Module ${value.data.name} is active...`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`${client.user.username} logged in as ${client.user.tag}!`);
|
|
||||||
client.user.setActivity("on sound waves.", { type: "PLAYING" });
|
|
||||||
|
|
||||||
let restCommands = [];
|
|
||||||
|
|
||||||
for (const value of client.Commands.values()) {
|
|
||||||
restCommands.push(value.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.application?.commands.set(restCommands);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const name = "ready";
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
const data = {
|
|
||||||
key: "quetza",
|
|
||||||
name: "Quetza Main Frame",
|
|
||||||
description: "Quetza default module v.1.5.0",
|
|
||||||
author: "unknowableshade"
|
|
||||||
}
|
|
||||||
|
|
||||||
const control = undefined;
|
|
||||||
|
|
||||||
export {data, control};
|
|
||||||
+2
-1
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src",
|
||||||
"module": "CommonJS",
|
"module": "CommonJS",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"lib": [ "ESNext", "DOM" ],
|
"lib": [ "ESNext" ],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"types": [ "node" ]
|
"types": [ "node" ]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user