1 Commits
Author SHA1 Message Date
Vladimir (unknowableshade) Eremin d068a6fb01 2302.2 Clean Up & Docker Fix 2023-02-26 20:40:43 +03:00
43 changed files with 1608 additions and 1083 deletions

No files matched your search

+14 -2
View File
@@ -1,2 +1,14 @@
node_modules # Ignore everything by default
Procfile *
# Dockerfile
!Dockerfile
# Source
!src/
# Dependencies
!package*.json
# Typescript compiller config
!tsconfig.json
+15 -10
View File
@@ -1,15 +1,20 @@
{ {
"env": { "browser": true, "es2021": true }, "root": true,
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "parserOptions": {
"plugins": ["@typescript-eslint"], "warnOnUnsupportedTypeScriptVersion": false,
"sourceType": "module"
},
"env": {
"browser": false,
"node": true,
"es6": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"plugins": ["@typescript-eslint", "import", "simple-import-sort"],
"ignorePatterns": ["**/node_modules/**", "/dist/**", "/tests/**"],
"rules": { "rules": {
"indent": ["error", 4], "simple-import-sort/imports": "error",
"linebreak-style": ["error", "windows"], "simple-import-sort/exports": "error"
"quotes": ["error", "double"],
"semi": ["error", "always"],
"brace-style": ["error", "stroustrup"],
"arrow-body-style": ["error", "as-needed"]
} }
} }
-63
View File
@@ -1,63 +0,0 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
+51 -364
View File
@@ -1,374 +1,61 @@
## Ignore Visual Studio temporary files, build results, and # Logs
## files generated by popular Visual Studio add-ons. logs
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log *.log
*.vspscc npm-debug.log*
*.vssscc yarn-debug.log*
.builds yarn-error.log*
*.pidb lerna-debug.log*
*.svclog .pnpm-debug.log*
*.scc
# Chutzpah Test files # Diagnostic reports (https://nodejs.org/api/report.html)
_Chutzpah* report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Visual C++ cache files # Runtime data
ipch/ pids
*.aps *.pid
*.ncb *.seed
*.opendb *.pid.lock
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler # Compiled binary addons (https://nodejs.org/api/addons.html)
*.psess build/Release
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files # Dependency directories
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/ node_modules/
jspm_packages/
# Visual Studio 6 build log # TypeScript cache
*.plg *.tsbuildinfo
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# assetscript restrictions
dist/ dist/
*.js
*.js.map
# binaries is not bundled # Optional npm cache directory
bin/ .npm
# Keep enviromental variables out of version control # Optional eslint cache
*.env .eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# VSCode
.vscode-test
.vscode
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
+5
View File
@@ -0,0 +1,5 @@
# Node.js Tools
node_modules
# Typescript compiller artifacts
dist/
+23
View File
@@ -0,0 +1,23 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": false,
"printWidth": 100,
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "crlf",
"overrides": [
{
"files": ["**/*.json, **/*.yml"],
"options": {
"tabWidth": 2
}
},
{
"files": ["**/*.ts"],
"options": {
"tabWidth": 4
}
}
]
}
+1 -1
View File
@@ -16,6 +16,6 @@ COPY package*.json ./
RUN npm ci RUN npm ci
COPY . . COPY . .
run npm run build RUN npm run build
CMD [ "npm", "start" ] CMD [ "npm", "start" ]
+1 -1
View File
@@ -1,4 +1,4 @@
# Quetza - 2302.1 <soundrewrite> # Quetza - 2302.1
_Neat typescript discord bot._ _Neat typescript discord bot._
+970 -3
View File
File diff suppressed because it is too large. Load diff
+13 -9
View File
@@ -5,7 +5,7 @@
"npm": "*" "npm": "*"
}, },
"type": "module", "type": "module",
"version": "2302.1.0", "version": "2302.1",
"description": "Discord multifunctional bot", "description": "Discord multifunctional bot",
"author": { "author": {
"name": "unknowableshade", "name": "unknowableshade",
@@ -23,24 +23,28 @@
"license": "MIT", "license": "MIT",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"poststart": "npm run build",
"start": "node dist/index.js", "start": "node dist/index.js",
"build": "tsc --build", "build": "npx tsc --build",
"clean": "tsc --build --clean" "clean": "npx tsc --build --clean",
"lint": "npx prettier --write . && npx eslint --fix .",
"prestart": "npm run build"
}, },
"dependencies": { "dependencies": {
"discord.js": "^14.3.0",
"@discordjs/opus": "^0.8.0", "@discordjs/opus": "^0.8.0",
"@discordjs/voice": "^0.11.0", "@discordjs/voice": "^0.11.0",
"ytdl-core": "^4.11.1",
"discord-ytdl-core": "^5.0.4", "discord-ytdl-core": "^5.0.4",
"discord.js": "^14.3.0", "tweetnacl": "^1.0.3"
"tweetnacl": "^1.0.3",
"ytdl-core": "^4.11.1"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^4.8.2",
"@types/node": "^18.7.13", "@types/node": "^18.7.13",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.6.0",
"@typescript-eslint/eslint-plugin": "^5.35.1", "@typescript-eslint/eslint-plugin": "^5.35.1",
"@typescript-eslint/parser": "^5.35.1", "@typescript-eslint/parser": "^5.35.1",
"eslint": "^8.22.0", "eslint-plugin-import": "^2.27.5",
"typescript": "^4.8.2" "eslint-plugin-simple-import-sort": "^10.0.0"
} }
} }
+4 -6
View File
@@ -1,7 +1,7 @@
import Client from "../lib/Client.js";
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import I18n from "../lib/I18n.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -17,8 +17,6 @@ export async function run(client: Client, ctx: CommandInteraction) {
await ctx.reply({ embeds: [I18n.en.cleared(amount)] }); await ctx.reply({ embeds: [I18n.en.cleared(amount)] });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder().setName("clear").setDescription("Clear the player's queue.");
.setName("clear")
.setDescription("Clear the player's queue.");
export { data }; export { data };
+5 -7
View File
@@ -1,7 +1,7 @@
import Client from "../lib/Client.js";
import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from "discord.js";
import I18n from "../lib/I18n.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel || !ctx.member) return; if (!ctx.guild || !ctx.channel || !ctx.member) return;
@@ -18,15 +18,13 @@ export async function run(client: Client, ctx: CommandInteraction) {
player.connect(channel); player.connect(channel);
await ctx.editReply({ embeds: [I18n.en.okConnected(channel.name)] }); await ctx.editReply({ embeds: [I18n.en.okConnected(channel.name)] });
} } else {
else {
client.modules.music.del(ctx.guild.id); client.modules.music.del(ctx.guild.id);
await ctx.editReply({ embeds: [I18n.en.notConnected()] }); await ctx.editReply({ embeds: [I18n.en.notConnected()] });
return undefined; return undefined;
} }
} } else {
else {
await ctx.editReply({ embeds: [I18n.en.wasConnected()] }); await ctx.editReply({ embeds: [I18n.en.wasConnected()] });
} }
+30 -27
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const filter = ctx.options.get("filter")?.value as string; const filter = ctx.options.get("filter")?.value as string;
@@ -19,102 +20,104 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("filter") .setName("filter")
.setDescription("Apply unique filters to the tracks.") .setDescription("Apply unique filters to the tracks.")
.addStringOption(option => option .addStringOption((option) =>
option
.setName("filter") .setName("filter")
.setDescription("Filter to apply.") .setDescription("Filter to apply.")
.setChoices( .setChoices(
{ {
name: "bassboost_low", name: "bassboost_low",
value: "bassboost_low", value: "bassboost_low"
}, },
{ {
name: "bassboost", name: "bassboost",
value: "bassboost", value: "bassboost"
}, },
{ {
name: "bassboost_high", name: "bassboost_high",
value: "bassboost_high", value: "bassboost_high"
}, },
{ {
name: "8D", name: "8D",
value: "8D", value: "8D"
}, },
{ {
name: "vaporwave", name: "vaporwave",
value: "vaporwave", value: "vaporwave"
}, },
{ {
name: "nightcore", name: "nightcore",
value: "nightcore", value: "nightcore"
}, },
{ {
name: "phaser", name: "phaser",
value: "phaser", value: "phaser"
}, },
{ {
name: "tremolo", name: "tremolo",
value: "tremolo", value: "tremolo"
}, },
{ {
name: "vibrato", name: "vibrato",
value: "vibrato", value: "vibrato"
}, },
{ {
name: "reverse", name: "reverse",
value: "reverse", value: "reverse"
}, },
{ {
name: "treble", name: "treble",
value: "treble", value: "treble"
}, },
{ {
name: "normalizer", name: "normalizer",
value: "normalizer", value: "normalizer"
}, },
{ {
name: "surrounding", name: "surrounding",
value: "surrounding", value: "surrounding"
}, },
{ {
name: "pulsator", name: "pulsator",
value: "pulsator", value: "pulsator"
}, },
{ {
name: "subboost", name: "subboost",
value: "subboost", value: "subboost"
}, },
{ {
name: "mono", name: "mono",
value: "mono", value: "mono"
}, },
{ {
name: "compressor", name: "compressor",
value: "compressor", value: "compressor"
}, },
{ {
name: "expander", name: "expander",
value: "expander", value: "expander"
}, },
{ {
name: "softlimiter", name: "softlimiter",
value: "softlimiter", value: "softlimiter"
}, },
{ {
name: "chorus", name: "chorus",
value: "chorus", value: "chorus"
}, },
{ {
name: "fadein", name: "fadein",
value: "fadein", value: "fadein"
}, },
{ {
name: "dim", name: "dim",
value: "dim", value: "dim"
}, },
{ {
name: "earrape", name: "earrape",
value: "earrape", value: "earrape"
}) }
)
.setRequired(true) .setRequired(true)
); );
+3 -3
View File
@@ -1,7 +1,7 @@
import Client from "../lib/Client.js";
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import I18n from "../lib/I18n.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel || !ctx.member) return; if (!ctx.guild || !ctx.channel || !ctx.member) return;
+7 -10
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const pos = ctx.options.get("position")?.value as number; const pos = ctx.options.get("position")?.value as number;
@@ -22,15 +23,11 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("insert") .setName("insert")
.setDescription("Insert a new track to specific position in the queue.") .setDescription("Insert a new track to specific position in the queue.")
.addStringOption(option => option .addStringOption((option) =>
.setName("query") option.setName("query").setDescription("Title or URL of the song.").setRequired(true)
.setDescription("Title or URL of the song.")
.setRequired(true)
) )
.addIntegerOption(option => option .addIntegerOption((option) =>
.setName("position") option.setName("position").setDescription("Position to insert.").setRequired(true)
.setDescription("Position to insert.",)
.setRequired(true)
); );
export { data }; export { data };
+9 -7
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("query")?.value as string; const query = ctx.options.get("query")?.value as string;
@@ -11,7 +12,10 @@ export async function run(client: Client, ctx: CommandInteraction) {
if (!player) return; if (!player) return;
const state = !isNaN(+query) && isFinite(+query) && !/e/i.test(query) ? player.jump(parseInt(query) - 1) : player.jump(query); const state =
!isNaN(+query) && isFinite(+query) && !/e/i.test(query)
? player.jump(parseInt(query) - 1)
: player.jump(query);
await ctx.reply({ embeds: [I18n.en.jumped(state)] }); await ctx.reply({ embeds: [I18n.en.jumped(state)] });
} }
@@ -19,10 +23,8 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("jump") .setName("jump")
.setDescription("Jump to specific track in the queue.") .setDescription("Jump to specific track in the queue.")
.addStringOption(option => option .addStringOption((option) =>
.setName("query") option.setName("query").setDescription("Position or title to jump to.").setRequired(true)
.setDescription("Position or title to jump to.")
.setRequired(true)
); );
export { data }; export { data };
+12 -7
View File
@@ -1,7 +1,8 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import { loopOption } from "../lib/Types.js"; import Client from "../lib/client.js";
import I18n from "../lib/I18n.js"; import I18n from "../lib/i18n.js";
import { loopOption } from "../lib/types.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("option")?.value as string; const query = ctx.options.get("option")?.value as string;
@@ -20,13 +21,17 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("loop") .setName("loop")
.setDescription("Select player's loop mode.") .setDescription("Select player's loop mode.")
.addStringOption(option => option .addStringOption((option) =>
option
.setName("option") .setName("option")
.setDescription("Select loop option.") .setDescription("Select loop option.")
.setChoices( { name: "loop queue", value: "LOOP" }, .setChoices(
{ name: "loop queue", value: "LOOP" },
{ name: "loop single track", value: "SONG" }, { name: "loop single track", value: "SONG" },
{ name: "Till end", value: "NONE" }, { name: "Till end", value: "NONE" },
{ name: "Random order", value: "AUTO" }) { name: "Random order", value: "AUTO" }
.setRequired(true)); )
.setRequired(true)
);
export { data }; export { data };
+4 -5
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -14,8 +15,6 @@ export async function run(client: Client, ctx: CommandInteraction) {
await ctx.reply({ embeds: [I18n.en.skipped()] }); await ctx.reply({ embeds: [I18n.en.skipped()] });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder().setName("next").setDescription("Skip current song.");
.setName("next")
.setDescription("Skip current song.");
export { data }; export { data };
+4 -5
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -14,8 +15,6 @@ export async function run(client: Client, ctx: CommandInteraction) {
await ctx.reply({ embeds: [I18n.en.paused(state)] }); await ctx.reply({ embeds: [I18n.en.paused(state)] });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder().setName("pause").setDescription("Toggle pause on player.");
.setName("pause")
.setDescription("Toggle pause on player.");
export { data }; export { data };
+4 -5
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js"; import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const start = Date.now(); const start = Date.now();
@@ -15,8 +16,6 @@ export async function run(client: Client, ctx: CommandInteraction) {
await ctx.reply({ embeds: [I18n.en.ping(Date.now() - start)] }); await ctx.reply({ embeds: [I18n.en.ping(Date.now() - start)] });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder().setName("ping").setDescription("Try to ping me.");
.setName("ping")
.setDescription("Try to ping me.");
export { data }; export { data };
+5 -5
View File
@@ -1,7 +1,8 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js"; import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import Client from "../lib/Client.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
import { run as connect } from "./connect.js"; import { run as connect } from "./connect.js";
import I18n from "../lib/I18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("query")?.value as string; const query = ctx.options.get("query")?.value as string;
@@ -24,9 +25,8 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("play") .setName("play")
.setDescription("Launch player and add songs to the queue.") .setDescription("Launch player and add songs to the queue.")
.addStringOption(option => option .addStringOption((option) =>
.setName("query") option.setName("query").setDescription("Title or URL of the song.")
.setDescription("Title or URL of the song.")
); );
export { data }; export { data };
+9 -23
View File
@@ -10,8 +10,8 @@ import {
TextChannel TextChannel
} from "discord.js"; } from "discord.js";
import Client from "../lib/Client.js"; import Client from "../lib/client.js";
import I18n from "../lib/I18n.js"; import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -30,27 +30,15 @@ export async function run(client: Client, ctx: CommandInteraction) {
let page = 0; let page = 0;
const row = new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents( const row = new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
new ButtonBuilder() new ButtonBuilder().setCustomId("QueueTop").setLabel("🔼").setStyle(ButtonStyle.Primary),
.setCustomId("QueueTop") new ButtonBuilder().setCustomId("QueueUp").setLabel("🔺").setStyle(ButtonStyle.Secondary),
.setLabel("🔼") new ButtonBuilder().setCustomId("QueueDown").setLabel("🔻").setStyle(ButtonStyle.Secondary),
.setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId("QueueEnd").setLabel("🔽").setStyle(ButtonStyle.Primary)
new ButtonBuilder()
.setCustomId("QueueUp")
.setLabel("🔺")
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId("QueueDown")
.setLabel("🔻")
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId("QueueEnd")
.setLabel("🔽")
.setStyle(ButtonStyle.Primary)
); );
const message = (await ctx.editReply({ const message = (await ctx.editReply({
embeds: [I18n.en.queueDesigner(player.Queue, 0, player.Resource)], embeds: [I18n.en.queueDesigner(player.Queue, 0, player.Resource)],
components: [row], components: [row]
})) as Message; })) as Message;
const collector = message.createMessageComponentCollector({ time: 30000 }); const collector = message.createMessageComponentCollector({ time: 30000 });
@@ -74,13 +62,11 @@ export async function run(client: Client, ctx: CommandInteraction) {
} }
await btn.update({ await btn.update({
embeds: [I18n.en.queueDesigner(player.Queue, page, player.Resource)], embeds: [I18n.en.queueDesigner(player.Queue, page, player.Resource)]
}); });
}); });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder().setName("queue").setDescription("Print out the queue.");
.setName("queue")
.setDescription("Print out the queue.");
export { data }; export { data };
+9 -7
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const query = ctx.options.get("query")?.value as string; const query = ctx.options.get("query")?.value as string;
@@ -11,7 +12,10 @@ export async function run(client: Client, ctx: CommandInteraction) {
if (!player) return; if (!player) return;
const track = !isNaN(+query) && isFinite(+query) && !/e/i.test(query) ? player.remove(parseInt(query) - 1) : player.remove(query); const track =
!isNaN(+query) && isFinite(+query) && !/e/i.test(query)
? player.remove(parseInt(query) - 1)
: player.remove(query);
await ctx.reply({ embeds: [I18n.en.removed(track)] }); await ctx.reply({ embeds: [I18n.en.removed(track)] });
} }
@@ -19,10 +23,8 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("remove") .setName("remove")
.setDescription("Remove specific track in the queue.") .setDescription("Remove specific track in the queue.")
.addStringOption(option => option .addStringOption((option) =>
.setName("query") option.setName("query").setDescription("Position or title to remove.").setRequired(true)
.setDescription("Position or title to remove.")
.setRequired(true)
); );
export { data }; export { data };
+4 -5
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -14,8 +15,6 @@ export async function run(client: Client, ctx: CommandInteraction) {
await ctx.reply({ embeds: [I18n.en.reshuffle()] }); await ctx.reply({ embeds: [I18n.en.reshuffle()] });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder().setName("reshuffle").setDescription("Shuffle the queue.");
.setName("reshuffle")
.setDescription("Shuffle the queue.");
export { data }; export { data };
+10 -18
View File
@@ -1,11 +1,12 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const hours = ctx.options.get("hrs")?.value as number || 0; const hours = (ctx.options.get("hrs")?.value as number) || 0;
const minutes = ctx.options.get("mins")?.value as number || 0; const minutes = (ctx.options.get("mins")?.value as number) || 0;
const seconds = ctx.options.get("secs")?.value as number || 0; const seconds = (ctx.options.get("secs")?.value as number) || 0;
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -18,24 +19,15 @@ export async function run(client: Client, ctx: CommandInteraction) {
player.seek(time * 1000); player.seek(time * 1000);
await ctx.reply({ await ctx.reply({
embeds: [I18n.en.fastForwarded(time)], embeds: [I18n.en.fastForwarded(time)]
}); });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("seek") .setName("seek")
.setDescription("Fast-forward by the time.") .setDescription("Fast-forward by the time.")
.addIntegerOption(option => option .addIntegerOption((option) => option.setName("hrs").setDescription("Hours to seek."))
.setName("hrs") .addIntegerOption((option) => option.setName("mins").setDescription("Minutes to seek."))
.setDescription("Hours to seek.") .addIntegerOption((option) => option.setName("secs").setDescription("Seconds to seek."));
)
.addIntegerOption(option => option
.setName("mins")
.setDescription("Minutes to seek.")
)
.addIntegerOption(option => option
.setName("secs")
.setDescription("Seconds to seek.")
);
export { data }; export { data };
+4 -5
View File
@@ -1,6 +1,7 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -14,8 +15,6 @@ export async function run(client: Client, ctx: CommandInteraction) {
await ctx.reply({ embeds: [I18n.en.destroyed()] }); await ctx.reply({ embeds: [I18n.en.destroyed()] });
} }
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder().setName("stop").setDescription("Destroy the player.");
.setName("stop")
.setDescription("Destroy the player.");
export { data }; export { data };
+6 -7
View File
@@ -1,9 +1,10 @@
import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js"; import { CommandInteraction, SlashCommandBuilder, TextChannel } from "discord.js";
import Client from "../lib/Client.js";
import I18n from "../lib/I18n.js"; import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
export async function run(client: Client, ctx: CommandInteraction) { export async function run(client: Client, ctx: CommandInteraction) {
const volume = ctx.options.get("volume")?.value as number || 100; const volume = (ctx.options.get("volume")?.value as number) || 100;
if (!ctx.guild || !ctx.channel) return; if (!ctx.guild || !ctx.channel) return;
@@ -19,10 +20,8 @@ export async function run(client: Client, ctx: CommandInteraction) {
const data = new SlashCommandBuilder() const data = new SlashCommandBuilder()
.setName("volume") .setName("volume")
.setDescription("Change player's volume amount.") .setDescription("Change player's volume amount.")
.addIntegerOption(option => option .addIntegerOption((option) =>
.setName("volume") option.setName("volume").setDescription("Volume in percents.").setRequired(true)
.setDescription("Volume in percents.")
.setRequired(true)
); );
export { data }; export { data };
+3 -1
View File
@@ -4,7 +4,9 @@ import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const config = { const config = {
token: process.env.DISCORD_TOKEN, token:
process.env.DISCORD_TOKEN ??
"NzgzNDQwMjU3NjE3MzYyOTQ1.GQ0lLK.VftrGanpOtqfoKUQRkoXDCcbQqwMqBsybmwjGE",
commands: path.join(__dirname, "/commands/"), commands: path.join(__dirname, "/commands/"),
events: path.join(__dirname, "/events/"), events: path.join(__dirname, "/events/"),
binaries: path.join(__dirname, "/../bin/") binaries: path.join(__dirname, "/../bin/")
@@ -1,5 +1,6 @@
import { Interaction } from "discord.js"; import { Interaction } from "discord.js";
import Client from "../lib/Client.js";
import Client from "../lib/client.js";
export async function run(client: Client, args: Interaction[]): Promise<void> { export async function run(client: Client, args: Interaction[]): Promise<void> {
const [interaction] = args; const [interaction] = args;
+3 -4
View File
@@ -1,7 +1,8 @@
import Client from "../lib/Client.js";
import { generateDependencyReport } from "@discordjs/voice"; import { generateDependencyReport } from "@discordjs/voice";
import { ActivityType } from "discord.js"; import { ActivityType } from "discord.js";
import Client from "../lib/client.js";
export async function run(client: Client): Promise<void> { export async function run(client: Client): Promise<void> {
if (!client.user || !client.application) { if (!client.user || !client.application) {
console.log("No user or application is provided!"); console.log("No user or application is provided!");
@@ -12,9 +13,7 @@ export async function run(client: Client): Promise<void> {
console.log(generateDependencyReport()); console.log(generateDependencyReport());
client.user.setActivity("over you", { type: ActivityType.Watching }); client.user.setActivity("over you", { type: ActivityType.Watching });
const restCommands = Array.from(client.Commands.values()).map( const restCommands = Array.from(client.Commands.values()).map((value) => value.data);
value => value.data
);
client.application.commands.set(restCommands); client.application.commands.set(restCommands);
} }
+23
View File
@@ -0,0 +1,23 @@
import { VoiceState } from "discord.js";
import Client from "../lib/client.js";
import I18n from "../lib/i18n.js";
import { asleep } from "../lib/misc.js";
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: [I18n.en.fullyAlone()] });
player.destroy();
}
}
}
export const name = "voiceStateUpdate";
-24
View File
@@ -1,24 +0,0 @@
import { VoiceState } from "discord.js";
import I18n from "../lib/I18n.js";
import Client from "../lib/Client.js";
import { asleep } from "../lib/Misc.js";
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: [I18n.en.fullyAlone()] });
player.destroy();
}
}
}
export const name = "voiceStateUpdate";
+1 -1
View File
@@ -1,5 +1,5 @@
import Client from "./lib/Client.js";
import config from "./config.js"; import config from "./config.js";
import Client from "./lib/client.js";
const client = new Client(); const client = new Client();
-294
View File
@@ -1,294 +0,0 @@
import Player from "./Player.js";
import { loopOption, track } from "./Types.js";
import { loopMoji, secToISO, statusBarGenerator, volumeMoji } from "./Misc.js";
import Queue from "./Queue.js";
import { AudioResource } from "@discordjs/voice";
import { EmbedBuilder } from "discord.js";
export default class I18n {
public static en = {
nowPlaying: (track: track) => new EmbedBuilder()
.setColor("LuminousVividPink")
.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) => new EmbedBuilder()
.setColor("Green")
.setTitle(
`**♻️ ${amount} tracks have been cleared from the queue.**`
)
.setTimestamp(new Date()),
okConnected: (channel: string) => new EmbedBuilder()
.setColor("Green")
.setTitle(`**✅ Successfuly connected to:** _${channel}_.`),
notConnected: () => new EmbedBuilder()
.setColor("Red")
.setTitle(
"**🚫 Make yourself connected to a voice channel beforehand.**"
),
wasConnected: () => new EmbedBuilder()
.setColor("Yellow")
.setTitle("**⚠️ Already connected to a voice channel.**"),
filtered: (filter?: string) => new EmbedBuilder()
.setColor("Green")
.setTitle(filter ? `**✅ ${filter} was applied to the player.**` : "**✅ All filters were declined.**"),
appended: (track?: track) => !track
? new EmbedBuilder()
.setColor("Red")
.setTitle("**🚫 Could not add track to the queue.**")
: new EmbedBuilder()
.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) => state
? new EmbedBuilder()
.setColor("Green")
.setTitle("_**Perfoming jump.**_")
: new EmbedBuilder()
.setColor("Red")
.setTitle("**❗ Bad _jump_ request.**"),
looped: (query: loopOption) => {
switch (query) {
case "AUTO": {
return new EmbedBuilder()
.setColor("LuminousVividPink")
.setTitle(
"**🔀 Shuffle mode** _(does not affect the queue order)_."
);
}
case "LOOP": {
return new EmbedBuilder()
.setColor("Blue")
.setTitle("**🔁 Looping over the queue.**");
}
case "NONE": {
return new EmbedBuilder()
.setColor("Red")
.setTitle("**⤵️ Player will stop on the queue end.**");
}
case "SONG": {
return new EmbedBuilder()
.setColor("Yellow")
.setTitle("**🔂 Looping a single track.**");
}
}
},
skipped: () => new EmbedBuilder()
.setColor("Green")
.setTitle("**⏭️ Skipping.**"),
paused: (state: boolean) => state
? new EmbedBuilder()
.setColor("Green")
.setTitle("_**▶️ Resumed.**_")
: new EmbedBuilder()
.setColor("Yellow")
.setTitle("_**⏸️ Paused.**_"),
removed: (track?: track) => track
? new EmbedBuilder()
.setColor("Green")
.setTitle(track.title)
.setDescription("**was removed from the queue.**")
.setTimestamp(new Date())
.setURL(track.url)
.setThumbnail(track.thumbnail)
: new EmbedBuilder()
.setColor("Red")
.setTitle("**❗ Bad _remove_ request.**"),
fastForwarded: (amount: number) => new EmbedBuilder()
.setColor("Blue")
.setTitle(
`**⏩ Fast-forwarding to ${secToISO(
amount
)}**`
),
destroyed: () => new EmbedBuilder()
.setColor("Red")
.setTitle("**💀 Destroying the player.**"),
volumeSet: (amount: number) => new EmbedBuilder()
.setColor("Green")
.setTitle(`**${volumeMoji(amount)} Volume has been set to ${amount}%**`),
queueEmpty: () => new EmbedBuilder()
.setColor("Yellow")
.setTitle("**❎ Queue is empty.**"),
playerInfo: (player?: Player) => {
if (!player)
return new EmbedBuilder()
.setColor("Red")
.setTitle("**🚫 Player does not exist.**");
if (player.Resource) {
return new EmbedBuilder()
.setColor("LuminousVividPink")
.setTitle(
`${player.Queue.Position + 1}. ${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 EmbedBuilder()
.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 EmbedBuilder()
.setColor("LuminousVividPink")
.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) => ({
name: `${
page * 10 + index + 1
}. ${value.title.padEnd(35)}`,
value: `**[${secToISO(value.duration)}]**`,
})
)
);
},
fullyAlone: () => new EmbedBuilder()
.setColor("LuminousVividPink")
.setTitle("🥬 I have decided to leave to conserve my energy.")
.setDescription(
"_**Since there was nobody in the voice channel.**_"
),
reshuffle: () => new EmbedBuilder()
.setColor("Green")
.setTitle("**✅ Successfuly reshuffled queue.**"),
ping: (ping?: number) => {
const title = ping ? `I have pinged ${ping} ms late.` : "There is no application.";
const comment = !ping ? "How am I speaking then?" : ping > 250 ? "I'm kinda slowpoke ngl." :
ping > 200 ? "I can do better than this." :
"Dream's speed!";
return new EmbedBuilder()
.setColor("Random")
.setTitle(`**${title}**`)
.setDescription(`_**${comment}**_`);
}
};
}
+14 -23
View File
@@ -1,14 +1,12 @@
import { import { Client as DiscordClient, GatewayIntentBits } from "discord.js";
Client as DiscordClient,
GatewayIntentBits
} from "discord.js";
import { existsSync, readdirSync } from "fs"; import { existsSync, readdirSync } from "fs";
import path from "path"; import path from "path";
import Music from "./Music.js";
import { command, event } from "./Types.js";
import config from "../config.js";
import { pathToFileURL } from "url"; import { pathToFileURL } from "url";
import config from "../config.js";
import Music from "./music.js";
import { command, event } from "./types.js";
export default class Client extends DiscordClient { export default class Client extends DiscordClient {
private commands = new Map<string, command>(); private commands = new Map<string, command>();
@@ -22,35 +20,28 @@ export default class Client extends DiscordClient {
public constructor() { public constructor() {
super({ super({
intents: [ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates]
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates
],
}); });
if (existsSync(config.commands)) { if (existsSync(config.commands)) {
const files = readdirSync(config.commands).filter((file) => const files = readdirSync(config.commands).filter((file) => file.endsWith(".js"));
file.endsWith(".js")
);
for (const file of files) { for (const file of files) {
import(pathToFileURL(path.join(config.commands, file)).toString()).then((cmd: command) => import(pathToFileURL(path.join(config.commands, file)).toString()).then(
this.commands.set(cmd.data.name, cmd) (cmd: command) => this.commands.set(cmd.data.name, cmd)
); );
} }
} }
if (existsSync(config.events)) { if (existsSync(config.events)) {
const files = readdirSync(config.events).filter((file) => const files = readdirSync(config.events).filter((file) => file.endsWith(".js"));
file.endsWith(".js")
);
for (const file of files) { for (const file of files) {
import(pathToFileURL(path.join(config.events, file)).toString()).then((evnt: event) => { import(pathToFileURL(path.join(config.events, file)).toString()).then(
this.on(evnt.name, (...args: unknown[]) => (evnt: event) => {
evnt.run(this, args) this.on(evnt.name, (...args: unknown[]) => evnt.run(this, args));
}
); );
});
} }
} }
} }
+9 -18
View File
@@ -1,8 +1,9 @@
import { ytdlArgs, ytdlResponse } from "./Types.js";
import { execFile } from "child_process"; import { execFile } from "child_process";
import { promisify } from "util";
import config from "../config.js";
import path from "path"; import path from "path";
import { promisify } from "util";
import config from "../config.js";
import { ytdlArgs, ytdlResponse } from "./types.js";
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@@ -11,27 +12,20 @@ function argumentsResolver(args: ytdlArgs): string[] {
return options.map(([key, value]) => { return options.map(([key, value]) => {
if (typeof value === "boolean" && value) { if (typeof value === "boolean" && value) {
return `--${key.split(/(?=[A-Z])/).join("-")}`.toLowerCase(); return `--${key.split(/(?=[A-Z])/).join("-")}`.toLowerCase();
} } else {
else { return `--${key.split(/(?=[A-Z])/).join("-")}=${value}`.toLowerCase();
return `--${key
.split(/(?=[A-Z])/)
.join("-")}=${value}`.toLowerCase();
} }
}); });
} }
async function ytdlExec( async function ytdlExec(query: string, args: ytdlArgs): Promise<ytdlResponse | undefined> {
query: string,
args: ytdlArgs
): Promise<ytdlResponse | undefined> {
try { try {
const executable = await execFileAsync( const executable = await execFileAsync(
path.join(config.binaries, "/youtube-dl"), path.join(config.binaries, "/youtube-dl"),
[query].concat(argumentsResolver(args)) [query].concat(argumentsResolver(args))
); );
return JSON.parse(executable.stdout); return JSON.parse(executable.stdout);
} } catch (error) {
catch (error) {
console.log(error); console.log(error);
return undefined; return undefined;
} }
@@ -44,10 +38,7 @@ export async function fetchStream(
let response = await ytdlExec(query, args); let response = await ytdlExec(query, args);
if (response && !response.extractor.includes("youtube")) { if (response && !response.extractor.includes("youtube")) {
response = await ytdlExec( response = await ytdlExec(`${response.title} - ${response.creator}`, args);
`${response.title} - ${response.creator}`,
args
);
} }
return response; return response;
+9 -17
View File
@@ -24,35 +24,27 @@ export default class Filter {
mono: [false, "pan=mono|c0=.5*c0+.5*c1"], mono: [false, "pan=mono|c0=.5*c0+.5*c1"],
mstlr: [false, "stereotools=mode=ms>lr"], mstlr: [false, "stereotools=mode=ms>lr"],
mstrr: [false, "stereotools=mode=ms>rr"], mstrr: [false, "stereotools=mode=ms>rr"],
compressor: [ compressor: [false, "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6"],
false,
"compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
],
expander: [ expander: [
false, 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", "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",
], ],
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"], 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"], chorus2d: [false, "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3"],
chorus3d: [ 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"],
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"], fadein: [false, "afade=t=in:ss=0:d=10"],
dim: [ dim: [
false, false,
"afftfilt=\"'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'\"", "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"], earrape: [false, "channelsplit,sidechaingate=level_in=64"]
}; };
public get Filters(): [string, string][] { public get Filters(): [string, string][] {
return Object.entries(this.filters) return Object.entries(this.filters)
.filter(value => value[1][0]).map(value => [value[0], value[1][1]]); .filter((value) => value[1][0])
.map((value) => [value[0], value[1][1]]);
} }
public toggle(filter?: string): void { public toggle(filter?: string): void {
@@ -68,7 +60,7 @@ export default class Filter {
} }
public toString(): string { public toString(): string {
return this.Filters.map(value => value[1]).join(","); return this.Filters.map((value) => value[1]).join(",");
} }
public empty(): boolean { public empty(): boolean {
+270
View File
@@ -0,0 +1,270 @@
import { AudioResource } from "@discordjs/voice";
import { EmbedBuilder } from "discord.js";
import { loopMoji, secToISO, statusBarGenerator, volumeMoji } from "./misc.js";
import Player from "./player.js";
import Queue from "./queue.js";
import { loopOption, track } from "./types.js";
export default class I18n {
public static en = {
nowPlaying: (track: track) =>
new EmbedBuilder()
.setColor("LuminousVividPink")
.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) =>
new EmbedBuilder()
.setColor("Green")
.setTitle(`**♻️ ${amount} tracks have been cleared from the queue.**`)
.setTimestamp(new Date()),
okConnected: (channel: string) =>
new EmbedBuilder()
.setColor("Green")
.setTitle(`**✅ Successfuly connected to:** _${channel}_.`),
notConnected: () =>
new EmbedBuilder()
.setColor("Red")
.setTitle("**🚫 Make yourself connected to a voice channel beforehand.**"),
wasConnected: () =>
new EmbedBuilder()
.setColor("Yellow")
.setTitle("**⚠️ Already connected to a voice channel.**"),
filtered: (filter?: string) =>
new EmbedBuilder()
.setColor("Green")
.setTitle(
filter
? `**✅ ${filter} was applied to the player.**`
: "**✅ All filters were declined.**"
),
appended: (track?: track) =>
!track
? new EmbedBuilder()
.setColor("Red")
.setTitle("**🚫 Could not add track to the queue.**")
: new EmbedBuilder()
.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) =>
state
? new EmbedBuilder().setColor("Green").setTitle("_**Perfoming jump.**_")
: new EmbedBuilder().setColor("Red").setTitle("**❗ Bad _jump_ request.**"),
looped: (query: loopOption) => {
switch (query) {
case "AUTO": {
return new EmbedBuilder()
.setColor("LuminousVividPink")
.setTitle("**🔀 Shuffle mode** _(does not affect the queue order)_.");
}
case "LOOP": {
return new EmbedBuilder()
.setColor("Blue")
.setTitle("**🔁 Looping over the queue.**");
}
case "NONE": {
return new EmbedBuilder()
.setColor("Red")
.setTitle("**⤵️ Player will stop on the queue end.**");
}
case "SONG": {
return new EmbedBuilder()
.setColor("Yellow")
.setTitle("**🔂 Looping a single track.**");
}
}
},
skipped: () => new EmbedBuilder().setColor("Green").setTitle("**⏭️ Skipping.**"),
paused: (state: boolean) =>
state
? new EmbedBuilder().setColor("Green").setTitle("_**▶️ Resumed.**_")
: new EmbedBuilder().setColor("Yellow").setTitle("_**⏸️ Paused.**_"),
removed: (track?: track) =>
track
? new EmbedBuilder()
.setColor("Green")
.setTitle(track.title)
.setDescription("**was removed from the queue.**")
.setTimestamp(new Date())
.setURL(track.url)
.setThumbnail(track.thumbnail)
: new EmbedBuilder().setColor("Red").setTitle("**❗ Bad _remove_ request.**"),
fastForwarded: (amount: number) =>
new EmbedBuilder()
.setColor("Blue")
.setTitle(`**⏩ Fast-forwarding to ${secToISO(amount)}**`),
destroyed: () =>
new EmbedBuilder().setColor("Red").setTitle("**💀 Destroying the player.**"),
volumeSet: (amount: number) =>
new EmbedBuilder()
.setColor("Green")
.setTitle(`**${volumeMoji(amount)} Volume has been set to ${amount}%**`),
queueEmpty: () => new EmbedBuilder().setColor("Yellow").setTitle("**❎ Queue is empty.**"),
playerInfo: (player?: Player) => {
if (!player)
return new EmbedBuilder()
.setColor("Red")
.setTitle("**🚫 Player does not exist.**");
if (player.Resource) {
return new EmbedBuilder()
.setColor("LuminousVividPink")
.setTitle(`${player.Queue.Position + 1}. ${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 EmbedBuilder()
.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 EmbedBuilder()
.setColor("LuminousVividPink")
.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) => ({
name: `${page * 10 + index + 1}. ${value.title.padEnd(35)}`,
value: `**[${secToISO(value.duration)}]**`
}))
);
},
fullyAlone: () =>
new EmbedBuilder()
.setColor("LuminousVividPink")
.setTitle("🥬 I have decided to leave to conserve my energy.")
.setDescription("_**Since there was nobody in the voice channel.**_"),
reshuffle: () =>
new EmbedBuilder().setColor("Green").setTitle("**✅ Successfuly reshuffled queue.**"),
ping: (ping?: number) => {
const title = ping ? `I have pinged ${ping} ms late.` : "There is no application.";
const comment = !ping
? "How am I speaking then?"
: ping > 250
? "I'm kinda slowpoke ngl."
: ping > 200
? "I can do better than this."
: "Dream's speed!";
return new EmbedBuilder()
.setColor("Random")
.setTitle(`**${title}**`)
.setDescription(`_**${comment}**_`);
}
};
}
+8 -20
View File
@@ -1,19 +1,17 @@
import { loopOption } from "./Types.js"; import { loopOption } from "./types.js";
export function statusBarGenerator(duration: number, length: number): string { export function statusBarGenerator(duration: number, length: number): string {
duration = Math.max(0, duration); duration = Math.max(0, duration);
length = Math.max(0, length); length = Math.max(0, length);
if (duration > length) duration = length; if (duration > length) duration = length;
const filled = Array<string>( const filled = Array<string>(Math.floor((15 * duration) / (length === 0 ? 1 : length))).fill(
Math.floor((15 * duration) / (length === 0 ? 1 : length)) ":small_blue_diamond:"
).fill(":small_blue_diamond:"); );
const empty = Array<string>( const empty = Array<string>(
15 - Math.floor((15 * duration) / (length === 0 ? 1 : length)) 15 - Math.floor((15 * duration) / (length === 0 ? 1 : length))
).fill(":white_small_square:"); ).fill(":white_small_square:");
return `**[${filled.join("")}${empty.join("")}] ${secToISO( return `**[${filled.join("")}${empty.join("")}] ${secToISO(duration)}/${secToISO(length)}**`;
duration
)}/${secToISO(length)}**`;
} }
export function secToISO(amount: number): string { export function secToISO(amount: number): string {
@@ -36,27 +34,17 @@ export function volumeMoji(amount: number): string {
} }
export function loopMoji(loop: loopOption): string { export function loopMoji(loop: loopOption): string {
return loop === "AUTO" return loop === "AUTO" ? "🔀" : loop === "LOOP" ? "🔁" : loop === "NONE" ? "⤵️" : "🔂";
? "🔀"
: loop === "LOOP"
? "🔁"
: loop === "NONE"
? "⤵️"
: "🔂";
} }
export function largestCommonSequence(s1: string, s2: string) { export function largestCommonSequence(s1: string, s2: string) {
s1 = s1.toLowerCase().slice(0, 100); s1 = s1.toLowerCase().slice(0, 100);
s2 = s2.toLowerCase().slice(0, 100); s2 = s2.toLowerCase().slice(0, 100);
const buffer = Array.from( const buffer = Array.from(Array(s1.length + 1), () => new Array<number>(s2.length + 1).fill(0));
Array(s1.length + 1),
() => new Array<number>(s2.length + 1).fill(0)
);
for (let i = 1; i <= s1.length; i++) { for (let i = 1; i <= s1.length; i++) {
for (let j = 1; j <= s2.length; j++) { for (let j = 1; j <= s2.length; j++) {
if (s1[i - 1] === s2[j - 1]) if (s1[i - 1] === s2[j - 1]) buffer[i][j] = buffer[i - 1][j - 1] + 1;
buffer[i][j] = buffer[i - 1][j - 1] + 1;
else buffer[i][j] = Math.max(buffer[i - 1][j], buffer[i][j - 1]); else buffer[i][j] = Math.max(buffer[i - 1][j], buffer[i][j - 1]);
} }
} }
+2 -1
View File
@@ -1,5 +1,6 @@
import { Guild, TextChannel } from "discord.js"; import { Guild, TextChannel } from "discord.js";
import Player from "./Player.js";
import Player from "./player.js";
export default class Music { export default class Music {
private players = new Map<string, Player>(); private players = new Map<string, Player>();
+34 -64
View File
@@ -1,33 +1,25 @@
import { import {
VoiceConnection,
AudioPlayer, AudioPlayer,
createAudioResource,
AudioPlayerStatus, AudioPlayerStatus,
joinVoiceChannel,
createAudioPlayer,
AudioResource, AudioResource,
createAudioPlayer,
createAudioResource,
DiscordGatewayAdapterCreator, DiscordGatewayAdapterCreator,
VoiceConnectionStatus,
entersState, entersState,
joinVoiceChannel,
VoiceConnection,
VoiceConnectionStatus
} from "@discordjs/voice"; } from "@discordjs/voice";
import { Guild, Message, TextChannel, User, VoiceBasedChannel } from "discord.js";
import {
Guild,
Message,
TextChannel,
User,
VoiceBasedChannel,
} from "discord.js";
import Music from "./Music.js";
import Filter from "./Filter.js";
import Queue from "./Queue.js";
import { fetchStream } from "./Fetch.js";
import I18n from "./I18n.js";
import ytdl from "discord-ytdl-core"; import ytdl from "discord-ytdl-core";
import { track } from "./Types.js";
import { largestCommonSequence } from "./Misc.js"; import { fetchStream } from "./fetch.js";
import Filter from "./filter.js";
import I18n from "./i18n.js";
import { largestCommonSequence } from "./misc.js";
import Music from "./music.js";
import Queue from "./queue.js";
import { track } from "./types.js";
export default class Player { export default class Player {
private music: Music; private music: Music;
@@ -78,23 +70,18 @@ export default class Player {
public set Volume(x: number) { public set Volume(x: number) {
this.volume = Math.max(0, Math.min(150, x)); this.volume = Math.max(0, Math.min(150, x));
if (this.resource && this.resource.volume) this.resource.volume.setVolumeLogarithmic(this.volume / 100); if (this.resource && this.resource.volume)
this.resource.volume.setVolumeLogarithmic(this.volume / 100);
} }
public constructor( public constructor(guild: Guild, music: Music, textChannel: TextChannel) {
guild: Guild,
music: Music,
textChannel: TextChannel
) {
this.music = music; this.music = music;
this.guild = guild; this.guild = guild;
this.channel = textChannel; this.channel = textChannel;
this.player = createAudioPlayer(); this.player = createAudioPlayer();
this.player.on(AudioPlayerStatus.Idle, async () => this.player.on(AudioPlayerStatus.Idle, async () => this.resourceEndResolvable());
this.resourceEndResolvable()
);
} }
private resourceGenerator(track: track, seek?: number) { private resourceGenerator(track: track, seek?: number) {
@@ -104,14 +91,12 @@ export default class Player {
filter: "audioonly", filter: "audioonly",
highWaterMark: 1 << 25, highWaterMark: 1 << 25,
seek: seek ? seek / 1000 : undefined, seek: seek ? seek / 1000 : undefined,
encoderArgs: !this.filter.empty() encoderArgs: !this.filter.empty() ? ["-af", this.filter.toString()] : undefined
? ["-af", this.filter.toString()]
: undefined,
}); });
this.resource = createAudioResource(stream, { this.resource = createAudioResource(stream, {
inlineVolume: true, inlineVolume: true,
metadata: track, metadata: track
}); });
if (seek) this.resource.playbackDuration = seek; if (seek) this.resource.playbackDuration = seek;
@@ -125,8 +110,7 @@ export default class Player {
if (this.queue.next()) { if (this.queue.next()) {
this.play(); this.play();
} } else {
else {
this.resource = undefined; this.resource = undefined;
this.player.stop(); this.player.stop();
} }
@@ -136,21 +120,18 @@ export default class Player {
if (this.message) await this.message.delete(); if (this.message) await this.message.delete();
this.message = await this.channel.send({ this.message = await this.channel.send({
embeds: [I18n.en.nowPlaying(track)], embeds: [I18n.en.nowPlaying(track)]
}); });
} }
private async search( private async search(query: string, requester: User): Promise<track[] | undefined> {
query: string,
requester: User
): Promise<track[] | undefined> {
const response = await fetchStream(query, { const response = await fetchStream(query, {
dumpSingleJson: true, dumpSingleJson: true,
noWarnings: true, noWarnings: true,
noCheckCertificate: true, noCheckCertificate: true,
flatPlaylist: true, flatPlaylist: true,
skipDownload: true, skipDownload: true,
defaultSearch: "ytsearch", defaultSearch: "ytsearch"
}); });
if (!response) return undefined; if (!response) return undefined;
@@ -164,17 +145,16 @@ export default class Player {
title: entry.title, title: entry.title,
thumbnail: `https://img.youtube.com/vi/${entry.id}/default.jpg`, thumbnail: `https://img.youtube.com/vi/${entry.id}/default.jpg`,
duration: entry.duration, duration: entry.duration,
requester: requester, requester: requester
}); });
} }
} } else {
else {
tracks.push({ tracks.push({
url: `https://youtube.com/watch?v=${response.id}`, url: `https://youtube.com/watch?v=${response.id}`,
title: response.title, title: response.title,
thumbnail: `https://img.youtube.com/vi/${response.id}/default.jpg`, thumbnail: `https://img.youtube.com/vi/${response.id}/default.jpg`,
duration: response.duration, duration: response.duration,
requester: requester, requester: requester
}); });
} }
@@ -199,13 +179,10 @@ export default class Player {
this.connection = joinVoiceChannel({ this.connection = joinVoiceChannel({
channelId: channel.id, channelId: channel.id,
guildId: channel.guildId, guildId: channel.guildId,
adapterCreator: channel.guild adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator
.voiceAdapterCreator as DiscordGatewayAdapterCreator,
}); });
this.connection.on( this.connection.on(VoiceConnectionStatus.Disconnected, async () => {
VoiceConnectionStatus.Disconnected,
async () => {
try { try {
await Promise.race([ await Promise.race([
entersState( entersState(
@@ -217,21 +194,15 @@ export default class Player {
this.connection as VoiceConnection, this.connection as VoiceConnection,
VoiceConnectionStatus.Connecting, VoiceConnectionStatus.Connecting,
10_000 10_000
), )
]); ]);
} } catch (error) {
catch (error) {
this.destroy(); this.destroy();
} }
} });
);
} }
public async add( public async add(query: string, requester: User, index?: number): Promise<track | undefined> {
query: string,
requester: User,
index?: number
): Promise<track | undefined> {
const tracks = await this.search(query, requester); const tracks = await this.search(query, requester);
if (!tracks) return; if (!tracks) return;
@@ -309,8 +280,7 @@ export default class Player {
if (this.player.state.status === AudioPlayerStatus.Paused) { if (this.player.state.status === AudioPlayerStatus.Paused) {
this.player.unpause(); this.player.unpause();
return false; return false;
} } else {
else {
this.player.pause(); this.player.pause();
return true; return true;
} }
+5 -8
View File
@@ -1,4 +1,4 @@
import { loopOption, track } from "./Types.js"; import { loopOption, track } from "./types.js";
export default class Queue { export default class Queue {
private tracks: track[] = []; private tracks: track[] = [];
@@ -34,12 +34,10 @@ export default class Queue {
if (this.position + 1 < this.tracks.length) { if (this.position + 1 < this.tracks.length) {
this.position++; this.position++;
return this.current(); return this.current();
} } else if (this.loop === "LOOP") {
else if (this.loop === "LOOP") {
this.position = 0; this.position = 0;
return this.current(); return this.current();
} } else if (this.loop === "NONE") {
else if (this.loop === "NONE") {
this.position = 0; this.position = 0;
return undefined; return undefined;
} }
@@ -54,7 +52,7 @@ export default class Queue {
[this.tracks[currentIndex], this.tracks[randomIndex]] = [ [this.tracks[currentIndex], this.tracks[randomIndex]] = [
this.tracks[randomIndex], this.tracks[randomIndex],
this.tracks[currentIndex], this.tracks[currentIndex]
]; ];
} }
@@ -64,8 +62,7 @@ export default class Queue {
public push(tracks: track[], index?: number): track { public push(tracks: track[], index?: number): track {
if (!index || index >= this.tracks.length) { if (!index || index >= this.tracks.length) {
this.tracks = this.tracks.concat(tracks); this.tracks = this.tracks.concat(tracks);
} } else {
else {
this.tracks.splice(Math.max(0, index), 0, ...tracks); this.tracks.splice(Math.max(0, index), 0, ...tracks);
if (this.position >= index) this.position += tracks.length; if (this.position >= index) this.position += tracks.length;
} }
+3 -2
View File
@@ -1,5 +1,6 @@
import { ApplicationCommandData, User, CommandInteraction } from "discord.js"; import { ApplicationCommandData, CommandInteraction, User } from "discord.js";
import Client from "./Client.js";
import Client from "./client.js";
export type track = { export type track = {
url: string; url: string;