├── .dockerignore ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── SUPPORT.md └── stale.yml ├── .gitignore ├── .replit ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── Procfile ├── README.md ├── api ├── index.js ├── middlewares │ └── auth.js ├── router.js └── routes │ ├── dashboard.js │ └── data.js ├── app.json ├── assets └── logo.gif ├── commands ├── context │ └── play.js └── slash │ ├── 247.js │ ├── autoleave.js │ ├── autopause.js │ ├── autoqueue.js │ ├── clean.js │ ├── clear.js │ ├── filters.js │ ├── guildleave.js │ ├── help.js │ ├── invite.js │ ├── loop.js │ ├── loopq.js │ ├── lyrics.js │ ├── move.js │ ├── nowplaying.js │ ├── pause.js │ ├── ping.js │ ├── play.js │ ├── previous.js │ ├── queue.js │ ├── reload.js │ ├── remove.js │ ├── replay.js │ ├── resume.js │ ├── save.js │ ├── search.js │ ├── seek.js │ ├── shuffle.js │ ├── skip.js │ ├── skipto.js │ ├── stats.js │ ├── stop.js │ ├── summon.js │ └── volume.js ├── config.js ├── dashboard ├── .eslintrc.json ├── .gitignore ├── README.md ├── components │ ├── StatCard.tsx │ ├── content.tsx │ ├── navbar.tsx │ └── server.tsx ├── next-env.d.ts ├── next.config.js ├── out │ ├── 404.html │ ├── _next │ │ └── static │ │ │ ├── chunks │ │ │ ├── 123-d3ffcfb4730480c6.js │ │ │ ├── 732-e52c1d2253f458fa.js │ │ │ ├── framework-4556c45dd113b893.js │ │ │ ├── main-a19d41ac16dbce80.js │ │ │ ├── pages │ │ │ │ ├── _app-79511e227f7b8d22.js │ │ │ │ ├── _error-a4ba2246ff8fb532.js │ │ │ │ ├── dashboard-8fe77e1aeec6ff87.js │ │ │ │ ├── index-0494ad302e38da35.js │ │ │ │ ├── login-8481030b110c33c9.js │ │ │ │ ├── logout-7167c9506bd9bdd3.js │ │ │ │ ├── servers-b957468847859725.js │ │ │ │ └── servers │ │ │ │ │ └── [id]-9f8c48f5bf25bd78.js │ │ │ ├── polyfills-c67a75d1b6f99dc8.js │ │ │ └── webpack-fd1bc4a65a80e5c8.js │ │ │ └── wV3SzfWusZ8UapJ--_pvH │ │ │ ├── _buildManifest.js │ │ │ └── _ssgManifest.js │ ├── dashboard.html │ ├── index.html │ ├── login.html │ ├── logout.html │ ├── servers.html │ └── servers │ │ └── [id].html ├── package.json ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── dashboard.tsx │ ├── index.tsx │ ├── login.tsx │ ├── logout.tsx │ ├── servers.tsx │ └── servers │ │ └── [id].tsx ├── svgs │ ├── AudiotrackRounded.svg │ ├── DnsRounded.svg │ ├── PersonRounded.svg │ └── RocketLaunchRounded.svg ├── tsconfig.json └── utils │ ├── dashboard.ts │ └── data.ts ├── deploy ├── deployGlobal.js ├── deployGuild.js ├── destroyGlobal.js └── destroyGuild.js ├── docker-compose.yml ├── docker └── application.yml ├── events ├── interactionCreate.js ├── messageCreate.js ├── messageDelete.js ├── raw.js ├── ready.js └── voiceStateUpdate.js ├── index.js ├── kickstartReplit.sh ├── lib ├── DiscordMusicBot.js ├── EpicPlayer.d.ts ├── EpicPlayer.js ├── Logger.js └── SlashCommand.js ├── package.json ├── renovate.json ├── replit.nix └── util ├── Controller.js ├── db.js ├── getChannel.js ├── getConfig.js ├── getLavalink.js ├── guildDb.js └── loadCommands.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @SudhanPlayz @DarrenOfficial -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: SudhanPlayz 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report incorrect or unexpected behavior of the Music Bot 4 | title: "" 5 | labels: "s: unverified, type: bug" 6 | assignees: "" 7 | --- 8 | 9 | <!-- Use Discord for questions: https://discord.gg/sbySMS7m3v --> 10 | 11 | ## Please describe the problem you are having in as much detail as possible: 12 | 13 | ## Include a reproducible code sample here, if possible: 14 | 15 | ```js 16 | // Place your code here 17 | ``` 18 | 19 | ## Further details: 20 | 21 | - discord.js version: 22 | - Node.js version: 23 | - Operating system: 24 | - Priority this issue should have – please be realistic and elaborate if possible: 25 | 26 | ## Relevant client options: 27 | 28 | - partials: none 29 | - gateway intents: none 30 | - other: none 31 | 32 | <!-- 33 | Remove the comment and fill out the commit hash if this applies to you: 34 | (While it's not a requirement to test your issue on the master branch, it would make fixing the problem a lot easier for us, so please do so if possible.) 35 | 36 | --> 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord server 4 | url: https://discord.gg/sbySMS7m3v 5 | about: Please visit our Discord server for questions and support requests. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request a feature for the Music Bot 4 | title: "" 5 | labels: "type: enhancement" 6 | assignees: "" 7 | --- 8 | 9 | <!-- Use Discord for questions: https://discord.gg/bRCvFy9 --> 10 | 11 | ## Is your feature request related to a problem? Please describe. 12 | 13 | A clear and concise description of what the problem is. Eg. I'm always frustrated when [...] 14 | 15 | ## Describe the ideal solution 16 | 17 | A clear and concise description of what you want to happen. 18 | 19 | ## Describe alternatives you've considered 20 | 21 | A clear and concise description of any alternative solutions or features you've considered. 22 | 23 | ## Additional context 24 | 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Please describe the changes this PR makes and why it should be merged: 2 | 3 | ## Status and versioning classification: 4 | 5 | <!-- 6 | Please move lines that apply to you out of the comment: 7 | - Code changes have been tested against the Discord API, or there are no code changes 8 | - I know how to update typings and have done so, or typings don't need updating 9 | - This PR changes the library's interface (methods or parameters added) 10 | - This PR includes breaking changes (methods removed or renamed, parameters moved or removed) 11 | - This PR **only** includes non-code changes, like changes to documentation, README, etc. 12 | --> 13 | 14 | # Important. 15 | 16 | - Write in camelCase, not snake_case. 17 | - Do not push to master/main without testing your changes first, make a branch 18 | if you have to. -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Seeking support? 2 | 3 | We only use this issue tracker for bug reports and feature request. We are not able to provide general support or answer 4 | questions in the form of GitHub issues. 5 | 6 | For general questions about the Music Bot and use please use the dedicated support channels in our Discord 7 | server: https://discord.gg/sbySMS7m3v 8 | 9 | Any issues that don't directly involve a bug or a feature request will likely be closed and redirected. 10 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | daysUntilStale: 60 3 | 4 | daysUntilClose: 5 5 | exemptLabels: 6 | - Soon 7 | 8 | markComment: > 9 | This issue has been automatically marked as stale because it has not had 10 | recent activity. It will be closed if no further activity occurs. Thank you 11 | for your contributions. 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Actual ignored paths for this repo 2 | db.json 3 | dbList.json 4 | .guild_dbs/ 5 | dev-config.js 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache/ 64 | .rts2_cache_cjs/ 65 | .rts2_cache_es/ 66 | .rts2_cache_umd/ 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn 76 | .yarn-integrity 77 | yarn.lock 78 | 79 | # dotenv environment variables file 80 | .env 81 | .env.test 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | 86 | # Next.js build output 87 | .next 88 | 89 | # Nuxt.js build / generate output 90 | .nuxt 91 | dist 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | 114 | # Lockfiles 115 | package-lock.json 116 | yarn.lock 117 | 118 | # Volta 119 | .pnp.cjs 120 | .pnp.loader.mjs 121 | 122 | # IDE 123 | .idea/ 124 | .vscode/ 125 | .history/ 126 | -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | run = "node index.js" 2 | language = "Bash" 3 | 4 | [nix] 5 | channel = "stable-22_05" 6 | 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at SudhanPlayz@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | <h1 align="center">Contributing</h1> 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | - Using welcoming and inclusive language 36 | - Being respectful of differing viewpoints and experiences 37 | - Gracefully accepting constructive criticism 38 | - Focusing on what is best for the community 39 | - Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | - The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | - Trolling, insulting/derogatory comments, and personal or political attacks 46 | - Public or private harassment 47 | - Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | - Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at SudhanPlayz@gmail.com. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | 93 | [version]: http://contributor-covenant.org/version/1/4/ 94 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:17.9.1-alpine 2 | WORKDIR /usr/src/app 3 | COPY . . 4 | RUN npm install 5 | RUN npm run deploy 6 | CMD [ "node", "index.js" ] 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License for Discord-MusicBot 2 | 3 | - The credits should not be changed. 4 | - The bot-code should be used for **private hosting** and **personal usage** only. 5 | - Using the code for public usage is **not allowed**. 6 | 7 | > **Note:** if you are found to be violating any of the above stated rule you might be asked to takedown your bot, happy 8 | > listening!! Incase of any doubts in the license contact owner. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: npm start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <h1 align="center"><img src="./assets/logo.gif" width="30px"> Discord Music Bot <img src="./assets/logo.gif" width="30px"></h1> 2 | 3 | ## ✨Latest Updates 4 | 5 | v5.1 Is in development! Go check it out [HERE!](https://github.com/wtfnotavailable/Discord-MusicBot) 6 | 7 | What do you gain from it? Let us explain: 8 | - Completely modular docker environment for easier development and deployment 9 | - A WORKING DASHBOARD!!! 10 | - DB Integration for you to save your favorite songs in 11 | - Integrated self hosted Lavalink 12 | - Dedicated query channel 13 | - More commands and functionalities 14 | - And so much more to come! 15 | 16 | ## 🚧 | Prerequisites 17 | 18 | - [Node.js 16+](https://nodejs.org/en/download/) 19 | - [Lavalink Server](https://code.darrennathanael.com/how-to-lavalink) 20 | - You'll need to run `npm run deploy` or `yarn deploy`. to initialized the slash commands. _You can do this on your pc 21 | locally_ 22 | 23 | > NOTE: Lavalink is needed for music functionality. You need to have a working Lavalink server to make the bot work. 24 | 25 | ## 📝 | Important Note if you're Switching from v4 to v5 26 | 27 | 1. Download and configure v5 in a seperate folder. 28 | 2. Kick your bot out of your server. 29 | 3. Reinvite the Bot with the right 30 | scopes. [Example Invite URL (Change CLIENT_ID)](https://discord.com/oauth2/authorize?client_id=CLIENT_ID&permissions=277083450689&scope=bot%20applications.commands) 31 | 4. Run `npm run deploy` or `yarn deploy` to initialize the slash commands. _You can do this on your pc locally_ 32 | 33 | ## 📝 | Tutorial 34 | 35 | ### 🐳 Docker 36 | You should configure the `config.js` file with the host `"lavalink"`, using the same `password` and `port` as specified in `docker/application.yml`. 37 | 38 | Build and start bot and lavalink 39 | ```sh 40 | docker-compose up -d --build 41 | ``` 42 | ### 💪🏻 Non-Docker 43 | > The `config.js` file should be configured first. Don't forget to add a lavalink host 44 | 45 | Install all dependencies and deploy Slash Commands 46 | ```sh 47 | npm install 48 | npm run deploy 49 | ``` 50 | Start the bot 51 | ```sh 52 | node index.js 53 | ``` 54 | 55 | ## 📝 | [Support Server](https://discord.gg/sbySMS7m3v) 56 | 57 | If you have major coding issues with this bot, please join and ask for help. 58 | 59 | ## 📸 | Screenshots 60 | 61 | Soon 62 | 63 | ## 🚀 | Deploy 64 | 65 | [](https://heroku.com/deploy?template=https://github.com/SudhanPlayz/Discord-MusicBot/tree/v5) 66 | [](https://gitpod.io/#https://github.com/SudhanPlayz/Discord-MusicBot/tree/v5) 67 | 68 | ## ✨ | Contributors 69 | 70 | Contributions are always welcomed :D Make sure to follow [Contributing.md](/CONTRIBUTING.md) 71 | 72 | <a href="https://github.com/SudhanPlayz/Discord-MusicBot/graphs/contributors"> 73 | <img src="https://contributors-img.web.app/image?repo=SudhanPlayz/Discord-MusicBot" /> 74 | </a> 75 | 76 | ## 🌟 | Made with 77 | 78 | - [Discord.js](https://discord.js.org/) 79 | - [Lavalink](https://github.com/freyacodes/Lavalink) with erela.js 80 | - [Express](https://expressjs.com/) 81 | - [Next JS](https://nextjs.org/) 82 | - [Next UI](https://nextui.org) 83 | - [Material UI Icons](https://mui.com/material-ui/material-icons/) 84 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const fs = require("fs"); 3 | const { EventEmitter } = require("events"); 4 | const { join } = require("path"); 5 | const session = require("express-session"); 6 | const DiscordStrategy = require("passport-discord").Strategy; 7 | const passport = require("passport"); 8 | const getConfig = require("../util/getConfig"); 9 | const DiscordMusicBot = require("../lib/DiscordMusicBot"); 10 | const router = require("./router"); 11 | 12 | passport.serializeUser(function (user, done) { 13 | done(null, user); 14 | }); 15 | 16 | passport.deserializeUser(function (obj, done) { 17 | done(null, obj); 18 | }); 19 | 20 | class Server extends EventEmitter { 21 | /** 22 | * Create server ;-; 23 | * @param {DiscordMusicBot} client 24 | */ 25 | constructor(client) { 26 | super(); 27 | this.client = client; 28 | getConfig().then(this.init.bind(this)); 29 | } 30 | 31 | init(conf) { 32 | this.config = conf; 33 | this.app = express(); 34 | 35 | this.app.use(express.static(join(__dirname, "..", "public"))); 36 | 37 | // Static Routes for scripts 38 | const dist = join(__dirname, "..", "dashboard", "out", "_next") 39 | 40 | this.app.use("/_next", express.static(dist)); 41 | 42 | // Session and Passport 43 | this.app.use(session({ 44 | resave: false, 45 | saveUninitialized: false, 46 | secret: this.config.cookieSecret, 47 | cookie: { 48 | secure: this.config.website.startsWith("https://"), 49 | sameSite: true, 50 | }, 51 | })); 52 | 53 | this.initPassport(); 54 | 55 | this.app.use(router); 56 | 57 | //API 58 | fs.readdir(join(__dirname, "routes"), (err, files) => { 59 | if (err) { 60 | return console.log(err); 61 | } 62 | files.forEach((file) => { 63 | this.app.use( 64 | "/api/" + file.split(".")[0], 65 | require(join(__dirname, "routes") + "/" + file), 66 | ); 67 | }); 68 | }); 69 | 70 | this.listen(); 71 | } 72 | 73 | initPassport() { 74 | this.app.use(passport.initialize()); 75 | 76 | const strategy = new DiscordStrategy( 77 | { 78 | clientID: this.config.clientId, 79 | clientSecret: this.config.clientSecret, 80 | callbackURL: this.config.website + "/api/callback", 81 | scope: this.config.scopes.filter(a => !a.startsWith("app")), 82 | scopeSeparator: " ", 83 | }, 84 | function (accessToken, refreshToken, profile, done) { 85 | const data = { 86 | accessToken, 87 | refreshToken, 88 | profile, 89 | }; 90 | 91 | return done(null, data); 92 | }, 93 | ); 94 | passport.use(strategy); 95 | 96 | this.app.use(passport.session()); 97 | } 98 | 99 | listen() { 100 | this.app.listen(this.config.port); 101 | console.log("[SERVER] Listening on port:", this.config.port); 102 | } 103 | } 104 | 105 | module.exports = Server; 106 | -------------------------------------------------------------------------------- /api/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {import("express").Request} req 3 | * @param {import("express").Response} res 4 | * @param {import("express").NextFunction} next 5 | * @returns {Promise<void>} 6 | */ 7 | 8 | const Auth = (req, res, next) => { 9 | if (!req.user) { 10 | return res.redirect("/login"); 11 | } else { 12 | next(); 13 | } 14 | }; 15 | 16 | module.exports = Auth; 17 | -------------------------------------------------------------------------------- /api/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { Router } = require("express"); 4 | const passport = require("passport"); 5 | const { join } = require("path"); 6 | const Auth = require("./middlewares/auth"); 7 | 8 | const dist = join(__dirname, "..", "dashboard", "out"); 9 | 10 | const router = Router(); 11 | 12 | router.get("/", (req, res) => { 13 | res.sendFile(join(dist, "index.html")); 14 | }); 15 | 16 | router.get("/login", (req, res) => { 17 | res.sendFile(join(dist, "login.html")); 18 | }); 19 | 20 | router.get("/api/login", passport.authenticate("discord")); 21 | 22 | router.get("/logout", (req, res) => { 23 | res.sendFile(join(dist, "logout.html")); 24 | }); 25 | 26 | router.get("/api/logout", (req, res) => { 27 | req.session.destroy(() => { 28 | res.redirect("/"); 29 | }); 30 | }); 31 | 32 | router.get("/dashboard", Auth, (_req, res) => { 33 | res.sendFile(join(dist, "dashboard.html")); 34 | }); 35 | 36 | router.get("/servers", Auth, (_req, res) => { 37 | res.sendFile(join(dist, "servers.html")); 38 | }); 39 | 40 | router.get("/api/callback", passport.authenticate('discord', { 41 | failureRedirect: '/', 42 | }), (req, res ) => { 43 | req.session.save(() => { 44 | res.redirect("/"); 45 | }); 46 | }); 47 | 48 | module.exports = router; 49 | -------------------------------------------------------------------------------- /api/routes/dashboard.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const api = Router(); 3 | const { getClient } = require("../../"); 4 | const Auth = require("../middlewares/auth"); 5 | 6 | api.get("/", Auth, (req, res) => { 7 | const client = getClient(); 8 | let data = { 9 | commandsRan: client.commandsRan, 10 | users: client.users.cache.size, 11 | servers: client.guilds.cache.size, 12 | songsPlayed: client.songsPlayed, 13 | } 14 | res.json(data); 15 | }) 16 | 17 | module.exports = api 18 | -------------------------------------------------------------------------------- /api/routes/data.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const api = Router(); 3 | 4 | const package = require("../../package.json"); 5 | const { getClient } = require("../../"); 6 | 7 | api.get("/", (req, res) => { 8 | const client = getClient(); 9 | let data = { 10 | name: client.user.username, 11 | version: package.version, 12 | commands: client.slashCommands.map(cmd => { 13 | return { 14 | name: cmd.name, 15 | description: cmd.description, 16 | }; 17 | }), 18 | inviteURL: `https://discord.com/oauth2/authorize?client_id=${ client.config.clientId 19 | }&permissions=${ client.config.permissions 20 | }&scope=${ client.config.scopes.toString().replace(/,/g, "%20") }`, 21 | loggedIn: !!req.user, 22 | }; 23 | res.json(data); 24 | }); 25 | 26 | module.exports = api; 27 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Discord-MusicBot", 3 | "description": "Very simple discord music bot with the discord.js with Song Name playing. It can able to play music with the song name", 4 | "repository": "https://github.com/SudhanPlayz/Discord-MusicBot", 5 | "logo": "https://cdn.discordapp.com/avatars/750613142488481843/e6326038dbe2243ca551ba5b6ecd8bf2.png?size=1024", 6 | "keywords": [ 7 | "node", 8 | "discord", 9 | "youtube", 10 | "music", 11 | "bot", 12 | "lavalink", 13 | "dashboard" 14 | ], 15 | "image": "heroku/nodejs", 16 | "buildpacks": [ 17 | { 18 | "url": "heroku/nodejs" 19 | } 20 | ], 21 | "env": { 22 | "token": { 23 | "description": "The Discord Bot Token (https://discord.com/developers/applications)", 24 | "required": "true" 25 | }, 26 | "clientId": { 27 | "description": "The Discord Bot ClientID", 28 | "required": "true" 29 | }, 30 | "clientSecret": { 31 | "description": "The Discord Bot ClientSecret", 32 | "required": "true" 33 | }, 34 | "website": { 35 | "description": "URL of your webserver (Example: https://domain.xyz). Change this if you want to use the web-dashboard.", 36 | "value": "http://localhost" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /assets/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SudhanPlayz/Discord-MusicBot/4253446886d63b441ef9233008c052e409229658/assets/logo.gif -------------------------------------------------------------------------------- /commands/context/play.js: -------------------------------------------------------------------------------- 1 | const { ContextMenuCommandBuilder } = require("@discordjs/builders"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const escapeMarkdown = require("discord.js").Util.escapeMarkdown; 4 | 5 | module.exports = { 6 | command: new ContextMenuCommandBuilder().setName("Play Song").setType(3), 7 | 8 | /** 9 | * This function will handle context menu interaction 10 | * @param {import("../lib/DiscordMusicBot")} client 11 | * @param {import("discord.js").GuildContextMenuInteraction} interaction 12 | */ 13 | run: async (client, interaction, options) => { 14 | let channel = await client.getChannel(client, interaction); 15 | if (!channel) { 16 | return; 17 | } 18 | 19 | let node = await client.getLavalink(client); 20 | if (!node) { 21 | return interaction.reply({ 22 | embeds: [client.ErrorEmbed("Lavalink node is not connected")], 23 | }); 24 | } 25 | 26 | let player = client.createPlayer(interaction.channel, channel); 27 | 28 | if (player.state !== "CONNECTED") { 29 | player.connect(); 30 | } 31 | 32 | if (channel.type == "GUILD_STAGE_VOICE") { 33 | setTimeout(() => { 34 | if (interaction.guild.me.voice.suppress == true) { 35 | try { 36 | interaction.guild.me.voice.setSuppressed(false); 37 | } catch (e) { 38 | interaction.guild.me.voice.setRequestToSpeak(true); 39 | } 40 | } 41 | }, 2000); // Need this because discord api is buggy asf, and without this the bot will not request to speak on a stage - Darren 42 | } 43 | 44 | const ret = await interaction.reply({ 45 | embeds: [ 46 | new MessageEmbed() 47 | .setColor(client.config.embedColor) 48 | .setDescription(":mag_right: **Searching...**"), 49 | ], 50 | fetchReply: true, 51 | }); 52 | 53 | const query = 54 | interaction.channel.messages.cache.get(interaction.targetId).content ?? 55 | (await interaction.channel.messages.fetch(interaction.targetId)); 56 | let res = await player.search(query, interaction.user).catch((err) => { 57 | client.error(err); 58 | return { 59 | loadType: "LOAD_FAILED", 60 | }; 61 | }); 62 | 63 | if (res.loadType === "LOAD_FAILED") { 64 | if (!player.queue.current) { 65 | player.destroy(); 66 | } 67 | await interaction 68 | .editReply({ 69 | embeds: [ 70 | new MessageEmbed() 71 | .setColor("RED") 72 | .setDescription("There was an error while searching"), 73 | ], 74 | }) 75 | .catch(this.warn); 76 | } 77 | 78 | if (res.loadType === "NO_MATCHES") { 79 | if (!player.queue.current) { 80 | player.destroy(); 81 | } 82 | await interaction 83 | .editReply({ 84 | embeds: [ 85 | new MessageEmbed() 86 | .setColor("RED") 87 | .setDescription("No results were found"), 88 | ], 89 | }) 90 | .catch(this.warn); 91 | } 92 | 93 | if (res.loadType === "TRACK_LOADED" || res.loadType === "SEARCH_RESULT") { 94 | player.queue.add(res.tracks[0]); 95 | 96 | if (!player.playing && !player.paused && !player.queue.size) { 97 | player.play(); 98 | } 99 | var title = escapeMarkdown(res.tracks[0].title); 100 | var title = title.replace(/\]/g, ""); 101 | var title = title.replace(/\[/g, ""); 102 | let addQueueEmbed = new MessageEmbed() 103 | .setColor(client.config.embedColor) 104 | .setAuthor({ name: "Added to queue", iconURL: client.config.iconURL }) 105 | .setDescription(`[${title}](${res.tracks[0].uri})` || "No Title") 106 | .setURL(res.tracks[0].uri) 107 | .addFields( 108 | { 109 | name: "Added by", 110 | value: `<@${interaction.user.id}>`, 111 | inline: true, 112 | }, 113 | { 114 | name: "Duration", 115 | value: res.tracks[0].isStream 116 | ? `\`LIVE 🔴 \`` 117 | : `\`${client.ms(res.tracks[0].duration, { 118 | colonNotation: true, 119 | secondsDecimalDigits: 0, 120 | })}\``, 121 | inline: true, 122 | } 123 | ); 124 | 125 | try { 126 | addQueueEmbed.setThumbnail( 127 | res.tracks[0].displayThumbnail("maxresdefault") 128 | ); 129 | } catch (err) { 130 | addQueueEmbed.setThumbnail(res.tracks[0].thumbnail); 131 | } 132 | 133 | if (player.queue.totalSize > 1) { 134 | addQueueEmbed.addFields({ 135 | name: "Position in queue", 136 | value: `${player.queue.size}`, 137 | inline: true, 138 | }); 139 | } else { 140 | player.queue.previous = player.queue.current; 141 | } 142 | 143 | await interaction.editReply({ embeds: [addQueueEmbed] }).catch(this.warn); 144 | } 145 | 146 | if (res.loadType === "PLAYLIST_LOADED") { 147 | player.queue.add(res.tracks); 148 | 149 | if ( 150 | !player.playing && 151 | !player.paused && 152 | player.queue.totalSize === res.tracks.length 153 | ) { 154 | player.play(); 155 | } 156 | 157 | let playlistEmbed = new MessageEmbed() 158 | .setColor(client.config.embedColor) 159 | .setAuthor({ 160 | name: "Playlist added to queue", 161 | iconURL: client.config.iconURL, 162 | }) 163 | .setThumbnail(res.tracks[0].thumbnail) 164 | .setDescription(`[${res.playlist.name}](${query})`) 165 | .addFields( 166 | { 167 | name: "Enqueued", 168 | value: `\`${res.tracks.length}\` songs`, 169 | inline: true, 170 | }, 171 | { 172 | name: "Playlist duration", 173 | value: `\`${client.ms(res.playlist.duration, { 174 | colonNotation: true, 175 | secondsDecimalDigits: 0, 176 | })}\``, 177 | inline: true, 178 | } 179 | ); 180 | 181 | await interaction.editReply({ embeds: [playlistEmbed] }).catch(this.warn); 182 | } 183 | 184 | if (ret) setTimeout(() => ret.delete().catch(this.warn), 20000); 185 | return ret; 186 | }, 187 | }; 188 | -------------------------------------------------------------------------------- /commands/slash/247.js: -------------------------------------------------------------------------------- 1 | const colors = require("colors"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const SlashCommand = require("../../lib/SlashCommand"); 4 | 5 | const command = new SlashCommand() 6 | .setName("247") 7 | .setDescription("Prevents the bot from ever disconnecting from a VC (toggle)") 8 | .setRun(async (client, interaction, options) => { 9 | let channel = await client.getChannel(client, interaction); 10 | if (!channel) { 11 | return; 12 | } 13 | 14 | let player; 15 | if (client.manager) { 16 | player = client.manager.players.get(interaction.guild.id); 17 | } else { 18 | return interaction.reply({ 19 | embeds: [ 20 | new MessageEmbed() 21 | .setColor("RED") 22 | .setDescription("Lavalink node is not connected"), 23 | ], 24 | }); 25 | } 26 | 27 | if (!player) { 28 | return interaction.reply({ 29 | embeds: [ 30 | new MessageEmbed() 31 | .setColor("RED") 32 | .setDescription("There's nothing to play 24/7."), 33 | ], 34 | ephemeral: true, 35 | }); 36 | } 37 | 38 | let twentyFourSevenEmbed = new MessageEmbed().setColor( 39 | client.config.embedColor, 40 | ); 41 | const twentyFourSeven = player.get("twentyFourSeven"); 42 | 43 | if (!twentyFourSeven || twentyFourSeven === false) { 44 | player.set("twentyFourSeven", true); 45 | } else { 46 | player.set("twentyFourSeven", false); 47 | } 48 | twentyFourSevenEmbed 49 | .setDescription(`**24/7 mode is** \`${!twentyFourSeven ? "ON" : "OFF"}\``) 50 | .setFooter({ 51 | text: `The bot will ${!twentyFourSeven ? "now" : "no longer"} stay connected to the voice channel 24/7.` 52 | }); 53 | client.warn( 54 | `Player: ${ player.options.guild } | [${ colors.blue( 55 | "24/7", 56 | ) }] has been [${ colors.blue( 57 | !twentyFourSeven? "ENABLED" : "DISABLED", 58 | ) }] in ${ 59 | client.guilds.cache.get(player.options.guild) 60 | ? client.guilds.cache.get(player.options.guild).name 61 | : "a guild" 62 | }`, 63 | ); 64 | 65 | if (!player.playing && player.queue.totalSize === 0 && twentyFourSeven) { 66 | player.destroy(); 67 | } 68 | 69 | return interaction.reply({ embeds: [twentyFourSevenEmbed] }); 70 | }); 71 | 72 | module.exports = command; 73 | // check above message, it is a little bit confusing. and erros are not handled. probably should be fixed. 74 | // ok use catch ez kom follow meh ;_; 75 | // the above message meaning error, if it cant find it or take too long the bot crashed 76 | // play commanddddd, if timeout or takes 1000 years to find song it crashed 77 | // OKIE, leave the comment here for idk 78 | // Comment very useful, 247 good :+1: 79 | // twentyFourSeven = best; 80 | -------------------------------------------------------------------------------- /commands/slash/autoleave.js: -------------------------------------------------------------------------------- 1 | const colors = require("colors"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const SlashCommand = require("../../lib/SlashCommand"); 4 | 5 | const command = new SlashCommand() 6 | .setName("autoleave") 7 | .setDescription("Automatically leaves when everyone leaves the voice channel (toggle)") 8 | .setRun(async (client, interaction) => { 9 | let channel = await client.getChannel(client, interaction); 10 | if (!channel) return; 11 | 12 | let player; 13 | if (client.manager) 14 | player = client.manager.players.get(interaction.guild.id); 15 | else 16 | return interaction.reply({ 17 | embeds: [ 18 | new MessageEmbed() 19 | .setColor("RED") 20 | .setDescription("Lavalink node is not connected"), 21 | ], 22 | }); 23 | 24 | if (!player) { 25 | return interaction.reply({ 26 | embeds: [ 27 | new MessageEmbed() 28 | .setColor("RED") 29 | .setDescription("There's nothing playing in the queue"), 30 | ], 31 | ephemeral: true, 32 | }); 33 | } 34 | 35 | let autoLeaveEmbed = new MessageEmbed().setColor(client.config.embedColor); 36 | const autoLeave = player.get("autoLeave"); 37 | player.set("requester", interaction.guild.me); 38 | 39 | if (!autoLeave || autoLeave === false) { 40 | player.set("autoLeave", true); 41 | } else { 42 | player.set("autoLeave", false); 43 | } 44 | autoLeaveEmbed 45 | .setDescription(`**Auto Leave is** \`${!autoLeave ? "ON" : "OFF"}\``) 46 | .setFooter({ 47 | text: `The player will ${!autoLeave ? "now automatically" : "not automatically"} leave when the voice channel is empty.` 48 | }); 49 | client.warn( 50 | `Player: ${player.options.guild} | [${colors.blue( 51 | "autoLeave" 52 | )}] has been [${colors.blue(!autoLeave ? "ENABLED" : "DISABLED")}] in ${ 53 | client.guilds.cache.get(player.options.guild) 54 | ? client.guilds.cache.get(player.options.guild).name 55 | : "a guild" 56 | }` 57 | ); 58 | 59 | return interaction.reply({ embeds: [autoLeaveEmbed] }); 60 | }); 61 | 62 | module.exports = command; -------------------------------------------------------------------------------- /commands/slash/autopause.js: -------------------------------------------------------------------------------- 1 | const colors = require("colors"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const SlashCommand = require("../../lib/SlashCommand"); 4 | 5 | const command = new SlashCommand() 6 | .setName("autopause") 7 | .setDescription("Automatically pause when everyone leaves the voice channel (toggle)") 8 | .setRun(async (client, interaction) => { 9 | let channel = await client.getChannel(client, interaction); 10 | if (!channel) return; 11 | 12 | let player; 13 | if (client.manager) 14 | player = client.manager.players.get(interaction.guild.id); 15 | else 16 | return interaction.reply({ 17 | embeds: [ 18 | new MessageEmbed() 19 | .setColor("RED") 20 | .setDescription("Lavalink node is not connected"), 21 | ], 22 | }); 23 | 24 | if (!player) { 25 | return interaction.reply({ 26 | embeds: [ 27 | new MessageEmbed() 28 | .setColor("RED") 29 | .setDescription("There's nothing playing in the queue"), 30 | ], 31 | ephemeral: true, 32 | }); 33 | } 34 | 35 | let autoPauseEmbed = new MessageEmbed().setColor(client.config.embedColor); 36 | const autoPause = player.get("autoPause"); 37 | player.set("requester", interaction.guild.members.me); 38 | 39 | if (!autoPause || autoPause === false) { 40 | player.set("autoPause", true); 41 | } else { 42 | player.set("autoPause", false); 43 | } 44 | autoPauseEmbed 45 | .setDescription(`**Auto Pause is** \`${!autoPause ? "ON" : "OFF"}\``) 46 | .setFooter({ 47 | text: `The player will ${!autoPause ? "now be automatically" : "no longer be"} paused when everyone leaves the voice channel.` 48 | }); 49 | client.warn( 50 | `Player: ${player.options.guild} | [${colors.blue( 51 | "AUTOPAUSE" 52 | )}] has been [${colors.blue(!autoPause ? "ENABLED" : "DISABLED")}] in ${ 53 | client.guilds.cache.get(player.options.guild) 54 | ? client.guilds.cache.get(player.options.guild).name 55 | : "a guild" 56 | }` 57 | ); 58 | 59 | return interaction.reply({ embeds: [autoPauseEmbed] }); 60 | }); 61 | 62 | module.exports = command; 63 | -------------------------------------------------------------------------------- /commands/slash/autoqueue.js: -------------------------------------------------------------------------------- 1 | const colors = require("colors"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const SlashCommand = require("../../lib/SlashCommand"); 4 | 5 | const command = new SlashCommand() 6 | .setName("autoqueue") 7 | .setDescription("Automatically add songs to the queue (toggle)") 8 | .setRun(async (client, interaction) => { 9 | let channel = await client.getChannel(client, interaction); 10 | if (!channel) { 11 | return; 12 | } 13 | 14 | let player; 15 | if (client.manager) { 16 | player = client.manager.players.get(interaction.guild.id); 17 | } else { 18 | return interaction.reply({ 19 | embeds: [ 20 | new MessageEmbed() 21 | .setColor("RED") 22 | .setDescription("Lavalink node is not connected"), 23 | ], 24 | }); 25 | } 26 | 27 | if (!player) { 28 | return interaction.reply({ 29 | embeds: [ 30 | new MessageEmbed() 31 | .setColor("RED") 32 | .setDescription("There's nothing playing in the queue"), 33 | ], 34 | ephemeral: true, 35 | }); 36 | } 37 | 38 | let autoQueueEmbed = new MessageEmbed().setColor(client.config.embedColor); 39 | const autoQueue = player.get("autoQueue"); 40 | player.set("requester", interaction.guild.members.me); 41 | 42 | if (!autoQueue || autoQueue === false) { 43 | player.set("autoQueue", true); 44 | } else { 45 | player.set("autoQueue", false); 46 | } 47 | autoQueueEmbed 48 | .setDescription(`**Auto Queue is** \`${!autoQueue ? "ON" : "OFF"}\``) 49 | .setFooter({ 50 | text: `Related music will ${!autoQueue ? "now be automatically" : "no longer be"} added to the queue.` 51 | }); 52 | client.warn( 53 | `Player: ${ player.options.guild } | [${ colors.blue( 54 | "AUTOQUEUE", 55 | ) }] has been [${ colors.blue(!autoQueue? "ENABLED" : "DISABLED") }] in ${ 56 | client.guilds.cache.get(player.options.guild) 57 | ? client.guilds.cache.get(player.options.guild).name 58 | : "a guild" 59 | }`, 60 | ); 61 | 62 | return interaction.reply({ embeds: [autoQueueEmbed] }); 63 | }); 64 | 65 | module.exports = command; 66 | -------------------------------------------------------------------------------- /commands/slash/clean.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | 3 | const command = new SlashCommand() 4 | .setName("clean") 5 | .setDescription("Cleans the last 100 bot messages from channel.") 6 | .addIntegerOption((option) => 7 | option 8 | .setName("number") 9 | .setDescription("Number of messages to delete.") 10 | .setMinValue(2).setMaxValue(100) 11 | .setRequired(false), 12 | ) 13 | .setRun(async (client, interaction, options) => { 14 | 15 | await interaction.deferReply(); 16 | let number = interaction.options.getInteger("number"); 17 | number = number && number < 100? ++number : 100; 18 | 19 | 20 | interaction.channel.messages.fetch({ 21 | limit: number, 22 | }).then((messages) => { 23 | const botMessages = []; 24 | messages.filter(m => m.author.id === client.user.id).forEach(msg => botMessages.push(msg)) 25 | 26 | botMessages.shift(); 27 | interaction.channel.bulkDelete(botMessages, true) 28 | .then(async deletedMessages => { 29 | //Filtering out messages that did not get deleted. 30 | messages = messages.filter(msg => { 31 | !deletedMessages.some(deletedMsg => deletedMsg == msg); 32 | }); 33 | if (messages.size > 0) { 34 | client.log(`Deleting [${ messages.size }] messages older than 14 days.`) 35 | for (const msg of messages) { 36 | await msg.delete(); 37 | } 38 | } 39 | 40 | await interaction.editReply({ embeds: [client.Embed(`:white_check_mark: | Deleted ${ botMessages.length } bot messages`)] }); 41 | setTimeout(() => { 42 | interaction.deleteReply(); 43 | }, 5000); 44 | }) 45 | 46 | }); 47 | }) 48 | 49 | module.exports = command; 50 | -------------------------------------------------------------------------------- /commands/slash/clear.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("clear") 6 | .setDescription("Clear all tracks from queue") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("Nothing is playing right now."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | if (!player.queue || !player.queue.length || player.queue.length === 0) { 38 | let cembed = new MessageEmbed() 39 | .setColor(client.config.embedColor) 40 | .setDescription("❌ | **Invalid, Not enough track to be cleared.**"); 41 | 42 | return interaction.reply({ embeds: [cembed], ephemeral: true }); 43 | } 44 | 45 | player.queue.clear(); 46 | 47 | let clearEmbed = new MessageEmbed() 48 | .setColor(client.config.embedColor) 49 | .setDescription(`✅ | **Cleared the queue!**`); 50 | 51 | return interaction.reply({ embeds: [clearEmbed] }); 52 | }); 53 | 54 | module.exports = command; -------------------------------------------------------------------------------- /commands/slash/filters.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require("discord.js"); 2 | const SlashCommand = require("../../lib/SlashCommand"); 3 | 4 | const command = new SlashCommand() 5 | .setName("filters") 6 | .setDescription("add or remove filters") 7 | .addStringOption((option) => 8 | option 9 | .setName("preset") 10 | .setDescription("the preset to add") 11 | .setRequired(true) 12 | .addChoices( 13 | { name: "Nightcore", value: "nightcore" }, 14 | { name: "BassBoost", value: "bassboost" }, 15 | { name: "Vaporwave", value: "vaporwave" }, 16 | { name: "Pop", value: "pop" }, 17 | { name: "Soft", value: "soft" }, 18 | { name: "Treblebass", value: "treblebass" }, 19 | { name: "Eight Dimension", value: "eightD" }, 20 | { name: "Karaoke", value: "karaoke" }, 21 | { name: "Vibrato", value: "vibrato" }, 22 | { name: "Tremolo", value: "tremolo" }, 23 | { name: "Reset", value: "off" }, 24 | ), 25 | ) 26 | 27 | .setRun(async (client, interaction, options) => { 28 | const args = interaction.options.getString("preset"); 29 | 30 | let channel = await client.getChannel(client, interaction); 31 | if (!channel) { 32 | return; 33 | } 34 | 35 | let player; 36 | if (client.manager) { 37 | player = client.manager.players.get(interaction.guild.id); 38 | } else { 39 | return interaction.reply({ 40 | embeds: [ 41 | new MessageEmbed() 42 | .setColor("RED") 43 | .setDescription("Lavalink node is not connected"), 44 | ], 45 | }); 46 | } 47 | 48 | if (!player) { 49 | return interaction.reply({ 50 | embeds: [ 51 | new MessageEmbed() 52 | .setColor("RED") 53 | .setDescription("There's no music playing."), 54 | ], 55 | ephemeral: true, 56 | }); 57 | } 58 | 59 | // create a new embed 60 | let filtersEmbed = new MessageEmbed().setColor(client.config.embedColor); 61 | 62 | if (args == "nightcore") { 63 | filtersEmbed.setDescription("✅ | Nightcore filter is now active!"); 64 | player.nightcore = true; 65 | } else if (args == "bassboost") { 66 | filtersEmbed.setDescription("✅ | BassBoost filter is now on!"); 67 | player.bassboost = true; 68 | } else if (args == "vaporwave") { 69 | filtersEmbed.setDescription("✅ | Vaporwave filter is now on!"); 70 | player.vaporwave = true; 71 | } else if (args == "pop") { 72 | filtersEmbed.setDescription("✅ | Pop filter is now on!"); 73 | player.pop = true; 74 | } else if (args == "soft") { 75 | filtersEmbed.setDescription("✅ | Soft filter is now on!"); 76 | player.soft = true; 77 | } else if (args == "treblebass") { 78 | filtersEmbed.setDescription("✅ | Treblebass filter is now on!"); 79 | player.treblebass = true; 80 | } else if (args == "eightD") { 81 | filtersEmbed.setDescription("✅ | Eight Dimension filter is now on!"); 82 | player.eightD = true; 83 | } else if (args == "karaoke") { 84 | filtersEmbed.setDescription("✅ | Karaoke filter is now on!"); 85 | player.karaoke = true; 86 | } else if (args == "vibrato") { 87 | filtersEmbed.setDescription("✅ | Vibrato filter is now on!"); 88 | player.vibrato = true; 89 | } else if (args == "tremolo") { 90 | filtersEmbed.setDescription("✅ | Tremolo filter is now on!"); 91 | player.tremolo = true; 92 | } else if (args == "off") { 93 | filtersEmbed.setDescription("✅ | EQ has been cleared!"); 94 | player.reset(); 95 | } else { 96 | filtersEmbed.setDescription("❌ | Invalid filter!"); 97 | } 98 | 99 | return interaction.reply({ embeds: [filtersEmbed] }); 100 | }); 101 | 102 | module.exports = command; 103 | -------------------------------------------------------------------------------- /commands/slash/guildleave.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed, message } = require("discord.js"); 2 | const SlashCommand = require("../../lib/SlashCommand"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { forEach } = require("lodash"); 6 | 7 | const command = new SlashCommand() 8 | .setName("guildleave") 9 | .setDescription("leaves a guild") 10 | .addStringOption((option) => 11 | option 12 | .setName("id") 13 | .setDescription("Enter the guild id to leave (type `list` for guild ids)") 14 | .setRequired(true) 15 | ) 16 | .setRun(async (client, interaction, options) => { 17 | if (interaction.user.id === client.config.adminId) { 18 | try{ 19 | const id = interaction.options.getString('id'); 20 | 21 | if (id.toLowerCase() === 'list'){ 22 | client.guilds.cache.forEach((guild) => { 23 | console.log(`${guild.name} | ${guild.id}`); 24 | }); 25 | const guild = client.guilds.cache.map(guild => ` ${guild.name} | ${guild.id}`); 26 | try{ 27 | return interaction.reply({content:`Guilds:\n\`${guild}\``, ephemeral: true}); 28 | }catch{ 29 | return interaction.reply({content:`check console for list of guilds`, ephemeral: true}); 30 | } 31 | } 32 | 33 | const guild = client.guilds.cache.get(id); 34 | 35 | if(!guild){ 36 | return interaction.reply({content: `\`${id}\` is not a valid guild id`, ephemeral:true}); 37 | } 38 | 39 | await guild.leave().then(c => console.log(`left guild ${id}`)).catch((err) => {console.log(err)}); 40 | return interaction.reply({content:`left guild \`${id}\``, ephemeral: true}); 41 | }catch (error){ 42 | console.log(`there was an error trying to leave guild ${id}`, error); 43 | } 44 | }else { 45 | return interaction.reply({ 46 | embeds: [ 47 | new MessageEmbed() 48 | .setColor(client.config.embedColor) 49 | .setDescription("You are not authorized to use this command!"), 50 | ], 51 | ephemeral: true, 52 | }); 53 | } 54 | }); 55 | 56 | module.exports = command; 57 | -------------------------------------------------------------------------------- /commands/slash/help.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { 3 | Client, 4 | Interaction, 5 | MessageActionRow, 6 | MessageButton, 7 | MessageEmbed, 8 | } = require("discord.js"); 9 | const LoadCommands = require("../../util/loadCommands"); 10 | const { filter } = require("lodash"); 11 | 12 | const command = new SlashCommand() 13 | .setName("help") 14 | .setDescription("Shows this list") 15 | .setRun(async (client, interaction) => { 16 | await interaction.deferReply().catch((_) => {}); 17 | // map the commands name and description to the embed 18 | const commands = await LoadCommands().then((cmds) => { 19 | return [].concat(cmds.slash) /*.concat(cmds.context)*/; 20 | }); 21 | // from commands remove the ones that have "null" in the description 22 | const filteredCommands = commands.filter( 23 | (cmd) => cmd.description != "null" 24 | ); 25 | //console.log(filteredCommands); 26 | const totalCmds = filteredCommands.length; 27 | let maxPages = Math.ceil(totalCmds / client.config.helpCmdPerPage); 28 | 29 | // if git exists, then get commit hash 30 | let gitHash = ""; 31 | try { 32 | gitHash = require("child_process") 33 | .execSync("git rev-parse --short HEAD") 34 | .toString() 35 | .trim(); 36 | } catch (e) { 37 | // do nothing 38 | gitHash = "unknown"; 39 | } 40 | 41 | // default Page No. 42 | let pageNo = 0; 43 | 44 | const helpEmbed = new MessageEmbed() 45 | .setColor(client.config.embedColor) 46 | .setAuthor({ 47 | name: `Commands of ${client.user.username}`, 48 | iconURL: client.config.iconURL, 49 | }) 50 | .setTimestamp() 51 | .setFooter({ text: `Page ${pageNo + 1} / ${maxPages}` }); 52 | 53 | // initial temporary array 54 | var tempArray = filteredCommands.slice( 55 | pageNo * client.config.helpCmdPerPage, 56 | pageNo * client.config.helpCmdPerPage + client.config.helpCmdPerPage 57 | ); 58 | 59 | tempArray.forEach((cmd) => { 60 | helpEmbed.addFields({ name: cmd.name, value: cmd.description }); 61 | }); 62 | helpEmbed.addFields({ 63 | name: "Credits", 64 | value: 65 | `Discord Music Bot Version: v${ 66 | require("../../package.json").version 67 | }; Build: ${gitHash}` + 68 | "\n" + 69 | `[✨ Support Server](${client.config.supportServer}) | [Issues](${client.config.Issues}) | [Source](https://github.com/SudhanPlayz/Discord-MusicBot/tree/v5) | [Invite Me](https://discord.com/oauth2/authorize?client_id=${client.config.clientId}&permissions=${client.config.permissions}&scope=bot%20applications.commands)`, 70 | }); 71 | 72 | // Construction of the buttons for the embed 73 | const getButtons = (pageNo) => { 74 | return new MessageActionRow().addComponents( 75 | new MessageButton() 76 | .setCustomId("help_cmd_but_2_app") 77 | .setEmoji("◀️") 78 | .setStyle("PRIMARY") 79 | .setDisabled(pageNo == 0), 80 | new MessageButton() 81 | .setCustomId("help_cmd_but_1_app") 82 | .setEmoji("▶️") 83 | .setStyle("PRIMARY") 84 | .setDisabled(pageNo == maxPages - 1) 85 | ); 86 | }; 87 | 88 | const tempMsg = await interaction.editReply({ 89 | embeds: [helpEmbed], 90 | components: [getButtons(pageNo)], 91 | fetchReply: true, 92 | }); 93 | const collector = tempMsg.createMessageComponentCollector({ 94 | time: 600000, 95 | componentType: "BUTTON", 96 | }); 97 | 98 | collector.on("collect", async (iter) => { 99 | if (iter.customId === "help_cmd_but_1_app") { 100 | pageNo++; 101 | } else if (iter.customId === "help_cmd_but_2_app") { 102 | pageNo--; 103 | } 104 | 105 | helpEmbed.fields = []; 106 | 107 | var tempArray = filteredCommands.slice( 108 | pageNo * client.config.helpCmdPerPage, 109 | pageNo * client.config.helpCmdPerPage + client.config.helpCmdPerPage 110 | ); 111 | 112 | tempArray.forEach((cmd) => { 113 | //console.log(cmd); 114 | helpEmbed 115 | .addFields({ name: cmd.name, value: cmd.description }) 116 | .setFooter({ text: `Page ${pageNo + 1} / ${maxPages}` }); 117 | }); 118 | helpEmbed.addFields({ 119 | name: "Credits", 120 | value: 121 | `Discord Music Bot Version: v${ 122 | require("../../package.json").version 123 | }; Build: ${gitHash}` + 124 | "\n" + 125 | `[✨ Support Server](${client.config.supportServer}) | [Issues](${client.config.Issues}) | [Source](https://github.com/SudhanPlayz/Discord-MusicBot/tree/v5) | [Invite Me](https://discord.com/oauth2/authorize?client_id=${client.config.clientId}&permissions=${client.config.permissions}&scope=bot%20applications.commands)`, 126 | }); 127 | await iter.update({ 128 | embeds: [helpEmbed], 129 | components: [getButtons(pageNo)], 130 | fetchReply: true, 131 | }); 132 | }); 133 | }); 134 | 135 | module.exports = command; 136 | -------------------------------------------------------------------------------- /commands/slash/invite.js: -------------------------------------------------------------------------------- 1 | const { MessageActionRow, MessageButton, MessageEmbed } = require("discord.js"); 2 | const SlashCommand = require("../../lib/SlashCommand"); 3 | 4 | const command = new SlashCommand() 5 | .setName("invite") 6 | .setDescription("Invite me to your server") 7 | .setRun(async (client, interaction, options) => { 8 | return interaction.reply({ 9 | embeds: [ 10 | new MessageEmbed() 11 | .setColor(client.config.embedColor) 12 | .setTitle(`Invite me to your server!`), 13 | ], 14 | components: [ 15 | new MessageActionRow().addComponents( 16 | new MessageButton() 17 | .setLabel("Invite me") 18 | .setStyle("LINK") 19 | .setURL( 20 | `https://discord.com/oauth2/authorize?client_id=${ 21 | client.config.clientId 22 | }&permissions=${ 23 | client.config.permissions 24 | }&scope=${client.config.inviteScopes 25 | .toString() 26 | .replace(/,/g, "%20")}` 27 | ) 28 | ), 29 | ], 30 | }); 31 | }); 32 | module.exports = command; 33 | -------------------------------------------------------------------------------- /commands/slash/loop.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("loop") 6 | .setDescription("Loops the current song") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("Nothing is playing right now."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | if (player.setTrackRepeat(!player.trackRepeat)) { 38 | ; 39 | } 40 | const trackRepeat = player.trackRepeat? "enabled" : "disabled"; 41 | 42 | interaction.reply({ 43 | embeds: [ 44 | new MessageEmbed() 45 | .setColor(client.config.embedColor) 46 | .setDescription(`👍 | **Loop has been \`${ trackRepeat }\`**`), 47 | ], 48 | }); 49 | }); 50 | 51 | module.exports = command; 52 | -------------------------------------------------------------------------------- /commands/slash/loopq.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("loopq") 6 | .setDescription("Loop the current song queue") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("There is no music playing."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | if (player.setQueueRepeat(!player.queueRepeat)) { 38 | ; 39 | } 40 | const queueRepeat = player.queueRepeat? "enabled" : "disabled"; 41 | 42 | interaction.reply({ 43 | embeds: [ 44 | new MessageEmbed() 45 | .setColor(client.config.embedColor) 46 | .setDescription( 47 | `:thumbsup: | **Loop queue is now \`${ queueRepeat }\`**`, 48 | ), 49 | ], 50 | }); 51 | }); 52 | 53 | module.exports = command; 54 | -------------------------------------------------------------------------------- /commands/slash/lyrics.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { 3 | MessageActionRow, 4 | MessageSelectMenu, 5 | MessageButton, 6 | MessageEmbed 7 | } = require("discord.js"); 8 | const { Rlyrics } = require("rlyrics"); 9 | const lyricsApi = new Rlyrics(); 10 | 11 | const command = new SlashCommand() 12 | .setName("lyrics") 13 | .setDescription("Get the lyrics of a song") 14 | .addStringOption((option) => 15 | option 16 | .setName("song") 17 | .setDescription("The song to get lyrics for") 18 | .setRequired(false), 19 | ) 20 | .setRun(async (client, interaction, options) => { 21 | await interaction.reply({ 22 | embeds: [ 23 | new MessageEmbed() 24 | .setColor(client.config.embedColor) 25 | .setDescription("🔎 | **Searching...**"), 26 | ], 27 | }); 28 | 29 | let player; 30 | if (client.manager) { 31 | player = client.manager.players.get(interaction.guild.id); 32 | } else { 33 | return interaction.editReply({ 34 | embeds: [ 35 | new MessageEmbed() 36 | .setColor("RED") 37 | .setDescription("Lavalink node is not connected"), 38 | ], 39 | }); 40 | } 41 | 42 | const args = interaction.options.getString("song"); 43 | if (!args && !player) { 44 | return interaction.editReply({ 45 | embeds: [ 46 | new MessageEmbed() 47 | .setColor("RED") 48 | .setDescription("There's nothing playing"), 49 | ], 50 | }); 51 | } 52 | 53 | let currentTitle = ``; 54 | const phrasesToRemove = [ 55 | "Full Video", "Full Audio", "Official Music Video", "Lyrics", "Lyrical Video", 56 | "Feat.", "Ft.", "Official", "Audio", "Video", "HD", "4K", "Remix", "Lyric Video", "Lyrics Video", "8K", 57 | "High Quality", "Animation Video", "\\(Official Video\\. .*\\)", "\\(Music Video\\. .*\\)", "\\[NCS Release\\]", 58 | "Extended", "DJ Edit", "with Lyrics", "Lyrics", "Karaoke", 59 | "Instrumental", "Live", "Acoustic", "Cover", "\\(feat\\. .*\\)" 60 | ]; 61 | if (!args) { 62 | currentTitle = player.queue.current.title; 63 | currentTitle = currentTitle 64 | .replace(new RegExp(phrasesToRemove.join('|'), 'gi'), '') 65 | .replace(/\s*([\[\(].*?[\]\)])?\s*(\|.*)?\s*(\*.*)?$/, ''); 66 | } 67 | let query = args ? args : currentTitle; 68 | let lyricsResults = []; 69 | 70 | lyricsApi.search(query).then(async (lyricsData) => { 71 | if (lyricsData.length !== 0) { 72 | for (let i = 0; i < client.config.lyricsMaxResults; i++) { 73 | if (lyricsData[i]) { 74 | lyricsResults.push({ 75 | label: `${lyricsData[i].title}`, 76 | description: `${lyricsData[i].artist}`, 77 | value: i.toString() 78 | }); 79 | } else { break } 80 | } 81 | 82 | const menu = new MessageActionRow().addComponents( 83 | new MessageSelectMenu() 84 | .setCustomId("choose-lyrics") 85 | .setPlaceholder("Choose a song") 86 | .addOptions(lyricsResults), 87 | ); 88 | 89 | let selectedLyrics = await interaction.editReply({ 90 | embeds: [ 91 | new MessageEmbed() 92 | .setColor(client.config.embedColor) 93 | .setDescription( 94 | `Here are some of the results I found for \`${query}\`. Please choose a song to display lyrics within \`30 seconds\`.` 95 | ), 96 | ], components: [menu], 97 | }); 98 | 99 | const filter = (button) => button.user.id === interaction.user.id; 100 | 101 | const collector = selectedLyrics.createMessageComponentCollector({ 102 | filter, 103 | time: 30000, 104 | }); 105 | 106 | collector.on("collect", async (interaction) => { 107 | if (interaction.isSelectMenu()) { 108 | await interaction.deferUpdate(); 109 | const url = lyricsData[parseInt(interaction.values[0])].url; 110 | 111 | lyricsApi.find(url).then((lyrics) => { 112 | let lyricsText = lyrics.lyrics; 113 | 114 | const button = new MessageActionRow() 115 | .addComponents( 116 | new MessageButton() 117 | .setCustomId('tipsbutton') 118 | .setLabel('Tips') 119 | .setEmoji(`📌`) 120 | .setStyle('SECONDARY'), 121 | new MessageButton() 122 | .setLabel('Source') 123 | .setURL(url) 124 | .setStyle('LINK'), 125 | ); 126 | 127 | const musixmatch_icon = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e3/Musixmatch_logo_icon_only.svg/480px-Musixmatch_logo_icon_only.svg.png'; 128 | let lyricsEmbed = new MessageEmbed() 129 | .setColor(client.config.embedColor) 130 | .setTitle(`${lyrics.name}`) 131 | .setURL(url) 132 | .setThumbnail(lyrics.icon) 133 | .setFooter({ 134 | text: 'Lyrics provided by MusixMatch.', 135 | iconURL: musixmatch_icon 136 | }) 137 | .setDescription(lyricsText); 138 | 139 | if (lyricsText.length === 0) { 140 | lyricsEmbed 141 | .setDescription(`**Unfortunately we're not authorized to show these lyrics.**`) 142 | .setFooter({ 143 | text: 'Lyrics is restricted by MusixMatch.', 144 | iconURL: musixmatch_icon 145 | }) 146 | } 147 | 148 | if (lyricsText.length > 4096) { 149 | lyricsText = lyricsText.substring(0, 4050) + "\n\n[...]"; 150 | lyricsEmbed 151 | .setDescription(lyricsText + `\nTruncated, the lyrics were too long.`) 152 | } 153 | 154 | return interaction.editReply({ 155 | embeds: [lyricsEmbed], 156 | components: [button], 157 | }); 158 | 159 | }) 160 | } 161 | }); 162 | 163 | collector.on("end", async (i) => { 164 | if (i.size == 0) { 165 | selectedLyrics.edit({ 166 | content: null, 167 | embeds: [ 168 | new MessageEmbed() 169 | .setDescription( 170 | `No song is selected. You took too long to select a track.` 171 | ) 172 | .setColor(client.config.embedColor), 173 | ], components: [], 174 | }); 175 | } 176 | }); 177 | 178 | } else { 179 | const button = new MessageActionRow() 180 | .addComponents( 181 | new MessageButton() 182 | .setEmoji(`📌`) 183 | .setCustomId('tipsbutton') 184 | .setLabel('Tips') 185 | .setStyle('SECONDARY'), 186 | ); 187 | return interaction.editReply({ 188 | embeds: [ 189 | new MessageEmbed() 190 | .setColor("RED") 191 | .setDescription( 192 | `No results found for \`${query}\`!\nMake sure you typed in your search correctly.`, 193 | ), 194 | ], components: [button], 195 | }); 196 | } 197 | }).catch((err) => { 198 | console.error(err); 199 | return interaction.editReply({ 200 | embeds: [ 201 | new MessageEmbed() 202 | .setColor("RED") 203 | .setDescription( 204 | `An unknown error has occured, please check your console.`, 205 | ), 206 | ], 207 | }); 208 | }); 209 | 210 | const collector = interaction.channel.createMessageComponentCollector({ 211 | time: 1000 * 3600 212 | }); 213 | 214 | collector.on('collect', async interaction => { 215 | if (interaction.customId === 'tipsbutton') { 216 | await interaction.deferUpdate(); 217 | await interaction.followUp({ 218 | embeds: [ 219 | new MessageEmbed() 220 | .setTitle(`Lyrics Tips`) 221 | .setColor(client.config.embedColor) 222 | .setDescription( 223 | `Here is some tips to get your song lyrics correctly \n\n\ 224 | 1. Try to add the artist's name in front of the song name.\n\ 225 | 2. Try to search the lyrics manually by providing the song query using your keyboard.\n\ 226 | 3. Avoid searching lyrics in languages other than English.`, 227 | ), 228 | ], ephemeral: true, components: [] 229 | }); 230 | }; 231 | }); 232 | }); 233 | 234 | module.exports = command; 235 | -------------------------------------------------------------------------------- /commands/slash/move.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("move") 6 | .setDescription("Moves track to a different position") 7 | .addIntegerOption((option) => 8 | option 9 | .setName("track") 10 | .setDescription("The track number to move") 11 | .setRequired(true), 12 | ) 13 | .addIntegerOption((option) => 14 | option 15 | .setName("position") 16 | .setDescription("The position to move the track to") 17 | .setRequired(true), 18 | ) 19 | 20 | .setRun(async (client, interaction) => { 21 | const track = interaction.options.getInteger("track"); 22 | const position = interaction.options.getInteger("position"); 23 | 24 | let channel = await client.getChannel(client, interaction); 25 | if (!channel) { 26 | return; 27 | } 28 | 29 | let player; 30 | if (client.manager) { 31 | player = client.manager.players.get(interaction.guild.id); 32 | } else { 33 | return interaction.reply({ 34 | embeds: [ 35 | new MessageEmbed() 36 | .setColor("RED") 37 | .setDescription("Lavalink node is not connected"), 38 | ], 39 | }); 40 | } 41 | 42 | if (!player) { 43 | return interaction.reply({ 44 | embeds: [ 45 | new MessageEmbed() 46 | .setColor("RED") 47 | .setDescription("There's nothing playing."), 48 | ], 49 | ephemeral: true, 50 | }); 51 | } 52 | 53 | let trackNum = Number(track) - 1; 54 | if (trackNum < 0 || trackNum > player.queue.length - 1) { 55 | return interaction.reply(":x: | **Invalid track number**"); 56 | } 57 | 58 | let dest = Number(position) - 1; 59 | if (dest < 0 || dest > player.queue.length - 1) { 60 | return interaction.reply(":x: | **Invalid position number**"); 61 | } 62 | 63 | const thing = player.queue[trackNum]; 64 | player.queue.splice(trackNum, 1); 65 | player.queue.splice(dest, 0, thing); 66 | return interaction.reply({ 67 | embeds: [ 68 | new MessageEmbed() 69 | .setColor(client.config.embedColor) 70 | .setDescription(":white_check_mark: | **Moved track**"), 71 | ], 72 | }); 73 | }); 74 | 75 | module.exports = command; 76 | -------------------------------------------------------------------------------- /commands/slash/nowplaying.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require("discord.js"); 2 | const escapeMarkdown = require('discord.js').Util.escapeMarkdown; 3 | const SlashCommand = require("../../lib/SlashCommand"); 4 | const prettyMilliseconds = require("pretty-ms"); 5 | 6 | const command = new SlashCommand() 7 | .setName("nowplaying") 8 | .setDescription("Shows the song currently playing in the voice channel.") 9 | .setRun(async (client, interaction, options) => { 10 | let channel = await client.getChannel(client, interaction); 11 | if (!channel) { 12 | return; 13 | } 14 | 15 | let player; 16 | if (client.manager) { 17 | player = client.manager.players.get(interaction.guild.id); 18 | } else { 19 | return interaction.reply({ 20 | embeds: [ 21 | new MessageEmbed() 22 | .setColor("RED") 23 | .setDescription("Lavalink node is not connected"), 24 | ], 25 | }); 26 | } 27 | 28 | if (!player) { 29 | return interaction.reply({ 30 | embeds: [ 31 | new MessageEmbed() 32 | .setColor("RED") 33 | .setDescription("The bot isn't in a channel."), 34 | ], 35 | ephemeral: true, 36 | }); 37 | } 38 | 39 | if (!player.playing) { 40 | return interaction.reply({ 41 | embeds: [ 42 | new MessageEmbed() 43 | .setColor("RED") 44 | .setDescription("There's nothing playing."), 45 | ], 46 | ephemeral: true, 47 | }); 48 | } 49 | 50 | const song = player.queue.current; 51 | var title = escapeMarkdown(song.title) 52 | var title = title.replace(/\]/g,"") 53 | var title = title.replace(/\[/g,"") 54 | const embed = new MessageEmbed() 55 | .setColor(client.config.embedColor) 56 | .setAuthor({ name: "Now Playing", iconURL: client.config.iconURL }) 57 | // show who requested the song via setField, also show the duration of the song 58 | .setFields([ 59 | { 60 | name: "Requested by", 61 | value: `<@${ song.requester.id }>`, 62 | inline: true, 63 | }, 64 | // show duration, if live show live 65 | { 66 | name: "Duration", 67 | value: song.isStream 68 | ? `\`LIVE\`` 69 | : `\`${ prettyMilliseconds(player.position, { 70 | secondsDecimalDigits: 0, 71 | }) } / ${ prettyMilliseconds(song.duration, { 72 | secondsDecimalDigits: 0, 73 | }) }\``, 74 | inline: true, 75 | }, 76 | ]) 77 | // show the thumbnail of the song using displayThumbnail("maxresdefault") 78 | .setThumbnail(song.displayThumbnail("maxresdefault")) 79 | // show the title of the song and link to it 80 | .setDescription(`[${ title }](${ song.uri })`); 81 | return interaction.reply({ embeds: [embed] }); 82 | }); 83 | module.exports = command; 84 | -------------------------------------------------------------------------------- /commands/slash/pause.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("pause") 6 | .setDescription("Pauses the current playing track") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("Nothing is playing."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | if (player.paused) { 38 | return interaction.reply({ 39 | embeds: [ 40 | new MessageEmbed() 41 | .setColor("RED") 42 | .setDescription("Current playing track is already paused!"), 43 | ], 44 | ephemeral: true, 45 | }); 46 | } 47 | 48 | player.pause(true); 49 | return interaction.reply({ 50 | embeds: [ 51 | new MessageEmbed() 52 | .setColor(client.config.embedColor) 53 | .setDescription(`⏸ | **Paused!**`), 54 | ], 55 | }); 56 | }); 57 | 58 | module.exports = command; 59 | -------------------------------------------------------------------------------- /commands/slash/ping.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require("discord.js"); 2 | const SlashCommand = require("../../lib/SlashCommand"); 3 | 4 | const command = new SlashCommand() 5 | .setName("ping") 6 | .setDescription("View the bot's latency") 7 | .setRun(async (client, interaction, options) => { 8 | let msg = await interaction.channel.send({ 9 | embeds: [ 10 | new MessageEmbed() 11 | .setDescription("🏓 | Fetching ping...") 12 | .setColor("#6F8FAF"), 13 | ], 14 | }); 15 | 16 | let zap = "⚡"; 17 | let green = "🟢"; 18 | let red = "🔴"; 19 | let yellow = "🟡"; 20 | 21 | var botState = zap; 22 | var apiState = zap; 23 | 24 | let apiPing = client.ws.ping; 25 | let botPing = Math.floor(msg.createdAt - interaction.createdAt); 26 | 27 | if (apiPing >= 40 && apiPing < 200) { 28 | apiState = green; 29 | } else if (apiPing >= 200 && apiPing < 400) { 30 | apiState = yellow; 31 | } else if (apiPing >= 400) { 32 | apiState = red; 33 | } 34 | 35 | if (botPing >= 40 && botPing < 200) { 36 | botState = green; 37 | } else if (botPing >= 200 && botPing < 400) { 38 | botState = yellow; 39 | } else if (botPing >= 400) { 40 | botState = red; 41 | } 42 | 43 | msg.delete(); 44 | interaction.reply({ 45 | embeds: [ 46 | new MessageEmbed() 47 | .setTitle("🏓 | Pong!") 48 | .addFields( 49 | { 50 | name: "API Latency", 51 | value: `\`\`\`yml\n${apiState} | ${apiPing}ms\`\`\``, 52 | inline: true, 53 | }, 54 | { 55 | name: "Bot Latency", 56 | value: `\`\`\`yml\n${botState} | ${botPing}ms\`\`\``, 57 | inline: true, 58 | } 59 | ) 60 | .setColor(client.config.embedColor) 61 | .setFooter({ 62 | text: `Requested by ${interaction.user.tag}`, 63 | iconURL: interaction.user.avatarURL(), 64 | }), 65 | ], 66 | }); 67 | }); 68 | 69 | module.exports = command; 70 | -------------------------------------------------------------------------------- /commands/slash/play.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const escapeMarkdown = require("discord.js").Util.escapeMarkdown; 4 | 5 | const command = new SlashCommand() 6 | .setName("play") 7 | .setDescription( 8 | "Searches and plays the requested song \nSupports: \nYoutube, Spotify, Deezer, Apple Music" 9 | ) 10 | .addStringOption((option) => 11 | option 12 | .setName("query") 13 | .setDescription("What am I looking for?") 14 | .setAutocomplete(true) 15 | .setRequired(true) 16 | ) 17 | .setRun(async (client, interaction, options) => { 18 | let channel = await client.getChannel(client, interaction); 19 | if (!channel) { 20 | return; 21 | } 22 | 23 | let node = await client.getLavalink(client); 24 | if (!node) { 25 | return interaction.reply({ 26 | embeds: [client.ErrorEmbed("Lavalink node is not connected")], 27 | }); 28 | } 29 | 30 | let player = client.createPlayer(interaction.channel, channel); 31 | 32 | if (player.state !== "CONNECTED") { 33 | player.connect(); 34 | } 35 | 36 | if (channel.type == "GUILD_STAGE_VOICE") { 37 | setTimeout(() => { 38 | if (interaction.guild.members.me.voice.suppress == true) { 39 | try { 40 | interaction.guild.members.me.voice.setSuppressed(false); 41 | } catch (e) { 42 | interaction.guild.members.me.voice.setRequestToSpeak(true); 43 | } 44 | } 45 | }, 2000); // Need this because discord api is buggy asf, and without this the bot will not request to speak on a stage - Darren 46 | } 47 | 48 | const ret = await interaction.reply({ 49 | embeds: [ 50 | new MessageEmbed() 51 | .setColor(client.config.embedColor) 52 | .setDescription(":mag_right: **Searching...**"), 53 | ], 54 | fetchReply: true, 55 | }); 56 | 57 | let query = options.getString("query", true); 58 | let res = await player.search(query, interaction.user).catch((err) => { 59 | client.error(err); 60 | return { 61 | loadType: "LOAD_FAILED", 62 | }; 63 | }); 64 | 65 | if (res.loadType === "LOAD_FAILED") { 66 | if (!player.queue.current) { 67 | player.destroy(); 68 | } 69 | await interaction 70 | .editReply({ 71 | embeds: [ 72 | new MessageEmbed() 73 | .setColor("RED") 74 | .setDescription("There was an error while searching"), 75 | ], 76 | }) 77 | .catch(this.warn); 78 | } 79 | 80 | if (res.loadType === "NO_MATCHES") { 81 | if (!player.queue.current) { 82 | player.destroy(); 83 | } 84 | await interaction 85 | .editReply({ 86 | embeds: [ 87 | new MessageEmbed() 88 | .setColor("RED") 89 | .setDescription("No results were found"), 90 | ], 91 | }) 92 | .catch(this.warn); 93 | } 94 | 95 | if (res.loadType === "TRACK_LOADED" || res.loadType === "SEARCH_RESULT") { 96 | player.queue.add(res.tracks[0]); 97 | 98 | if (!player.playing && !player.paused && !player.queue.size) { 99 | player.play(); 100 | } 101 | var title = escapeMarkdown(res.tracks[0].title); 102 | var title = title.replace(/\]/g, ""); 103 | var title = title.replace(/\[/g, ""); 104 | let addQueueEmbed = new MessageEmbed() 105 | .setColor(client.config.embedColor) 106 | .setAuthor({ name: "Added to queue", iconURL: client.config.iconURL }) 107 | .setDescription(`[${title}](${res.tracks[0].uri})` || "No Title") 108 | .setURL(res.tracks[0].uri) 109 | .addFields( 110 | { 111 | name: "Added by", 112 | value: `<@${interaction.user.id}>`, 113 | inline: true, 114 | }, 115 | { 116 | name: "Duration", 117 | value: res.tracks[0].isStream 118 | ? `\`LIVE 🔴 \`` 119 | : `\`${client.ms(res.tracks[0].duration, { 120 | colonNotation: true, 121 | secondsDecimalDigits: 0, 122 | })}\``, 123 | inline: true, 124 | } 125 | ); 126 | 127 | try { 128 | addQueueEmbed.setThumbnail( 129 | res.tracks[0].displayThumbnail("maxresdefault") 130 | ); 131 | } catch (err) { 132 | addQueueEmbed.setThumbnail(res.tracks[0].thumbnail); 133 | } 134 | 135 | if (player.queue.totalSize > 1) { 136 | addQueueEmbed.addFields({ 137 | name: "Position in queue", 138 | value: `${player.queue.size}`, 139 | inline: true, 140 | }); 141 | } else { 142 | player.queue.previous = player.queue.current; 143 | } 144 | 145 | await interaction.editReply({ embeds: [addQueueEmbed] }).catch(this.warn); 146 | } 147 | 148 | if (res.loadType === "PLAYLIST_LOADED") { 149 | player.queue.add(res.tracks); 150 | 151 | if ( 152 | !player.playing && 153 | !player.paused && 154 | player.queue.totalSize === res.tracks.length 155 | ) { 156 | player.play(); 157 | } 158 | 159 | let playlistEmbed = new MessageEmbed() 160 | .setColor(client.config.embedColor) 161 | .setAuthor({ 162 | name: "Playlist added to queue", 163 | iconURL: client.config.iconURL, 164 | }) 165 | .setThumbnail(res.tracks[0].thumbnail) 166 | .setDescription(`[${res.playlist.name}](${query})`) 167 | .addFields( 168 | { 169 | name: "Enqueued", 170 | value: `\`${res.tracks.length}\` songs`, 171 | inline: true, 172 | }, 173 | { 174 | name: "Playlist duration", 175 | value: `\`${client.ms(res.playlist.duration, { 176 | colonNotation: true, 177 | secondsDecimalDigits: 0, 178 | })}\``, 179 | inline: true, 180 | } 181 | ); 182 | 183 | await interaction.editReply({ embeds: [playlistEmbed] }).catch(this.warn); 184 | } 185 | 186 | if (ret) setTimeout(() => ret.delete().catch(this.warn), 20000); 187 | return ret; 188 | }); 189 | 190 | module.exports = command; 191 | -------------------------------------------------------------------------------- /commands/slash/previous.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("previous") 6 | .setDescription("Go back to the previous song.") 7 | .setRun(async (client, interaction) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("There are no previous songs for this session."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | const previousSong = player.queue.previous; 38 | const currentSong = player.queue.current; 39 | const nextSong = player.queue[0] 40 | 41 | if (!previousSong 42 | || previousSong === currentSong 43 | || previousSong === nextSong) { 44 | return interaction.reply({ 45 | embeds: [ 46 | new MessageEmbed() 47 | .setColor("RED") 48 | .setDescription("There is no previous song in the queue."), 49 | ], 50 | })} 51 | 52 | if (previousSong !== currentSong && previousSong !== nextSong) { 53 | player.queue.splice(0, 0, currentSong) 54 | player.play(previousSong); 55 | } 56 | interaction.reply({ 57 | embeds: [ 58 | new MessageEmbed() 59 | .setColor(client.config.embedColor) 60 | .setDescription( 61 | `⏮ | Previous song: **${ previousSong.title }**`, 62 | ), 63 | ], 64 | }); 65 | }); 66 | 67 | module.exports = command; 68 | -------------------------------------------------------------------------------- /commands/slash/reload.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed, message } = require("discord.js"); 2 | const SlashCommand = require("../../lib/SlashCommand"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | const command = new SlashCommand() 7 | .setName("reload") 8 | .setDescription("Reload all commands") 9 | .setRun(async (client, interaction, options) => { 10 | if (interaction.user.id === client.config.adminId) { 11 | try { 12 | let ContextCommandsDirectory = path.join(__dirname, "..", "context"); 13 | fs.readdir(ContextCommandsDirectory, (err, files) => { 14 | files.forEach((file) => { 15 | delete require.cache[ 16 | require.resolve(ContextCommandsDirectory + "/" + file) 17 | ]; 18 | let cmd = require(ContextCommandsDirectory + "/" + file); 19 | if (!cmd.command || !cmd.run) { 20 | return this.warn( 21 | "❌ Unable to load Command: " + 22 | file.split(".")[0] + 23 | ", File doesn't have either command/run", 24 | ); 25 | } 26 | client.contextCommands.set(file.split(".")[0].toLowerCase(), cmd); 27 | }); 28 | }); 29 | 30 | let SlashCommandsDirectory = path.join(__dirname, "..", "slash"); 31 | fs.readdir(SlashCommandsDirectory, (err, files) => { 32 | files.forEach((file) => { 33 | delete require.cache[ 34 | require.resolve(SlashCommandsDirectory + "/" + file) 35 | ]; 36 | let cmd = require(SlashCommandsDirectory + "/" + file); 37 | 38 | if (!cmd || !cmd.run) { 39 | return client.warn( 40 | "❌ Unable to load Command: " + 41 | file.split(".")[0] + 42 | ", File doesn't have a valid command with run function", 43 | ); 44 | } 45 | client.slashCommands.set(file.split(".")[0].toLowerCase(), cmd); 46 | }); 47 | }); 48 | 49 | const totalCmds = 50 | client.slashCommands.size + client.contextCommands.size; 51 | client.log(`Reloaded ${ totalCmds } commands!`); 52 | return interaction.reply({ 53 | embeds: [ 54 | new MessageEmbed() 55 | .setColor(client.config.embedColor) 56 | .setDescription(`Sucessfully Reloaded \`${ totalCmds }\` Commands!`) 57 | .setFooter({ 58 | text: `${ client.user.username } was reloaded by ${ interaction.user.username }`, 59 | }) 60 | .setTimestamp(), 61 | ], 62 | ephemeral: true, 63 | }); 64 | } catch (err) { 65 | console.log(err); 66 | return interaction.reply({ 67 | embeds: [ 68 | new MessageEmbed() 69 | .setColor(client.config.embedColor) 70 | .setDescription( 71 | "An error has occured. For more details please check console.", 72 | ), 73 | ], 74 | ephemeral: true, 75 | }); 76 | } 77 | } else { 78 | return interaction.reply({ 79 | embeds: [ 80 | new MessageEmbed() 81 | .setColor(client.config.embedColor) 82 | .setDescription("You are not authorized to use this command!"), 83 | ], 84 | ephemeral: true, 85 | }); 86 | } 87 | }); 88 | 89 | module.exports = command; 90 | -------------------------------------------------------------------------------- /commands/slash/remove.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("remove") 6 | .setDescription("Remove track you don't want from queue") 7 | .addNumberOption((option) => 8 | option 9 | .setName("number") 10 | .setDescription("Enter track number.") 11 | .setRequired(true), 12 | ) 13 | 14 | .setRun(async (client, interaction) => { 15 | const args = interaction.options.getNumber("number"); 16 | 17 | let channel = await client.getChannel(client, interaction); 18 | if (!channel) { 19 | return; 20 | } 21 | 22 | let player; 23 | if (client.manager) { 24 | player = client.manager.players.get(interaction.guild.id); 25 | } else { 26 | return interaction.reply({ 27 | embeds: [ 28 | new MessageEmbed() 29 | .setColor("RED") 30 | .setDescription("Lavalink node is not connected"), 31 | ], 32 | }); 33 | } 34 | 35 | if (!player) { 36 | return interaction.reply({ 37 | embeds: [ 38 | new MessageEmbed() 39 | .setColor("RED") 40 | .setDescription("There are no songs to remove."), 41 | ], 42 | ephemeral: true, 43 | }); 44 | } 45 | 46 | await interaction.deferReply(); 47 | 48 | const position = Number(args) - 1; 49 | if (position > player.queue.size) { 50 | let thing = new MessageEmbed() 51 | .setColor(client.config.embedColor) 52 | .setDescription( 53 | `Current queue has only **${ player.queue.size }** track`, 54 | ); 55 | return interaction.editReply({ embeds: [thing] }); 56 | } 57 | 58 | const song = player.queue[position]; 59 | player.queue.remove(position); 60 | 61 | const number = position + 1; 62 | let removeEmbed = new MessageEmbed() 63 | .setColor(client.config.embedColor) 64 | .setDescription(`Removed track number **${ number }** from queue`); 65 | return interaction.editReply({ embeds: [removeEmbed] }); 66 | }); 67 | 68 | module.exports = command; 69 | -------------------------------------------------------------------------------- /commands/slash/replay.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("replay") 6 | .setDescription("Replay current playing track") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("I'm not playing anything."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | await interaction.deferReply(); 38 | 39 | player.seek(0); 40 | 41 | let song = player.queue.current; 42 | return interaction.editReply({ 43 | embeds: [ 44 | new MessageEmbed() 45 | .setColor(client.config.embedColor) 46 | .setDescription(`Replay [${ song.title }](${ song.uri })`), 47 | ], 48 | }); 49 | }); 50 | 51 | module.exports = command; 52 | -------------------------------------------------------------------------------- /commands/slash/resume.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("resume") 6 | .setDescription("Resume current track") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("There is no song playing right now."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | if (!player.paused) { 38 | return interaction.reply({ 39 | embeds: [ 40 | new MessageEmbed() 41 | .setColor("RED") 42 | .setDescription("Current track is already resumed"), 43 | ], 44 | ephemeral: true, 45 | }); 46 | } 47 | player.pause(false); 48 | return interaction.reply({ 49 | embeds: [ 50 | new MessageEmbed() 51 | .setColor(client.config.embedColor) 52 | .setDescription(`⏯ **Resumed!**`), 53 | ], 54 | }); 55 | }); 56 | 57 | module.exports = command; 58 | -------------------------------------------------------------------------------- /commands/slash/save.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const prettyMilliseconds = require("pretty-ms"); 4 | 5 | const command = new SlashCommand() 6 | .setName("save") 7 | .setDescription("Saves current song to your DM's") 8 | .setRun(async (client, interaction) => { 9 | let channel = await client.getChannel(client, interaction); 10 | if (!channel) { 11 | return; 12 | } 13 | 14 | let player; 15 | if (client.manager) { 16 | player = client.manager.players.get(interaction.guild.id); 17 | } else { 18 | return interaction.reply({ 19 | embeds: [ 20 | new MessageEmbed() 21 | .setColor("RED") 22 | .setDescription("Lavalink node is not connected"), 23 | ], 24 | }); 25 | } 26 | 27 | if (!player) { 28 | return interaction.reply({ 29 | embeds: [ 30 | new MessageEmbed() 31 | .setColor("RED") 32 | .setDescription("There is no music playing right now."), 33 | ], 34 | ephemeral: true, 35 | }); 36 | } 37 | 38 | const sendtoDmEmbed = new MessageEmbed() 39 | .setColor(client.config.embedColor) 40 | .setAuthor({ 41 | name: "Saved track", 42 | iconURL: `${ interaction.user.displayAvatarURL({ dynamic: true }) }`, 43 | }) 44 | .setDescription( 45 | `**Saved [${ player.queue.current.title }](${ player.queue.current.uri }) to your DM**`, 46 | ) 47 | .addFields( 48 | { 49 | name: "Track Duration", 50 | value: `\`${ prettyMilliseconds(player.queue.current.duration, { 51 | colonNotation: true, 52 | }) }\``, 53 | inline: true, 54 | }, 55 | { 56 | name: "Track Author", 57 | value: `\`${ player.queue.current.author }\``, 58 | inline: true, 59 | }, 60 | { 61 | name: "Requested Guild", 62 | value: `\`${ interaction.guild }\``, 63 | inline: true, 64 | }, 65 | ); 66 | 67 | interaction.user.send({ embeds: [sendtoDmEmbed] }); 68 | 69 | return interaction.reply({ 70 | embeds: [ 71 | new MessageEmbed() 72 | .setColor(client.config.embedColor) 73 | .setDescription( 74 | "Please check your **DMs**. If you didn't receive any message from me please make sure your **DMs** are open", 75 | ), 76 | ], 77 | ephemeral: true, 78 | }); 79 | }); 80 | 81 | module.exports = command; 82 | -------------------------------------------------------------------------------- /commands/slash/search.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const prettyMilliseconds = require("pretty-ms"); 3 | const { 4 | MessageEmbed, 5 | MessageActionRow, 6 | MessageSelectMenu, 7 | } = require("discord.js"); 8 | 9 | const command = new SlashCommand() 10 | .setName("search") 11 | .setDescription("Search for a song") 12 | .addStringOption((option) => 13 | option 14 | .setName("query") 15 | .setDescription("The song to search for") 16 | .setRequired(true) 17 | ) 18 | .setRun(async (client, interaction, options) => { 19 | let channel = await client.getChannel(client, interaction); 20 | if (!channel) { 21 | return; 22 | } 23 | 24 | let player; 25 | if (client.manager) { 26 | player = client.createPlayer(interaction.channel, channel); 27 | } else { 28 | return interaction.reply({ 29 | embeds: [ 30 | new MessageEmbed() 31 | .setColor("RED") 32 | .setDescription("Lavalink node is not connected"), 33 | ], 34 | }); 35 | } 36 | await interaction.deferReply().catch((_) => {}); 37 | 38 | if (player.state !== "CONNECTED") { 39 | player.connect(); 40 | } 41 | 42 | const search = interaction.options.getString("query"); 43 | let res; 44 | 45 | try { 46 | res = await player.search(search, interaction.user); 47 | if (res.loadType === "LOAD_FAILED") { 48 | return interaction.reply({ 49 | embeds: [ 50 | new MessageEmbed() 51 | .setDescription("An error occured while searching for the song") 52 | .setColor("RED"), 53 | ], 54 | ephemeral: true, 55 | }); 56 | } 57 | } catch (err) { 58 | return interaction.reply({ 59 | embeds: [ 60 | new MessageEmbed() 61 | .setAuthor({ 62 | name: "An error occured while searching for the song", 63 | }) 64 | .setColor("RED"), 65 | ], 66 | ephemeral: true, 67 | }); 68 | } 69 | 70 | if (res.loadType == "NO_MATCHES") { 71 | return interaction.reply({ 72 | embeds: [ 73 | new MessageEmbed() 74 | .setDescription(`No results found for \`${search}\``) 75 | .setColor("RED"), 76 | ], 77 | ephemeral: true, 78 | }); 79 | } else { 80 | let max = 10; 81 | if (res.tracks.length < max) { 82 | max = res.tracks.length; 83 | } 84 | 85 | let resultFromSearch = []; 86 | 87 | res.tracks.slice(0, max).map((track) => { 88 | resultFromSearch.push({ 89 | label: `${track.title}`, 90 | value: `${track.uri}`, 91 | description: track.isStream 92 | ? `LIVE` 93 | : `${prettyMilliseconds(track.duration, { 94 | secondsDecimalDigits: 0, 95 | })} - ${track.author}`, 96 | }); 97 | }); 98 | 99 | const menus = new MessageActionRow().addComponents( 100 | new MessageSelectMenu() 101 | .setCustomId("select") 102 | .setPlaceholder("Select a song") 103 | .addOptions(resultFromSearch) 104 | ); 105 | 106 | let choosenTracks = await interaction.editReply({ 107 | embeds: [ 108 | new MessageEmbed() 109 | .setColor(client.config.embedColor) 110 | .setDescription( 111 | `Here are some of the results I found for \`${search}\`. Please select track within \`30 seconds\`` 112 | ), 113 | ], 114 | components: [menus], 115 | }); 116 | const filter = (button) => button.user.id === interaction.user.id; 117 | 118 | const tracksCollector = choosenTracks.createMessageComponentCollector({ 119 | filter, 120 | time: 30000, 121 | }); 122 | tracksCollector.on("collect", async (i) => { 123 | if (i.isSelectMenu()) { 124 | await i.deferUpdate(); 125 | let uriFromCollector = i.values[0]; 126 | let trackForPlay; 127 | 128 | trackForPlay = await player?.search( 129 | uriFromCollector, 130 | interaction.user 131 | ); 132 | player?.queue?.add(trackForPlay.tracks[0]); 133 | if (!player?.playing && !player?.paused && !player?.queue?.size) { 134 | player?.play(); 135 | } 136 | i.editReply({ 137 | content: null, 138 | embeds: [ 139 | new MessageEmbed() 140 | .setAuthor({ 141 | name: "Added to queue", 142 | iconURL: client.config.iconURL, 143 | }) 144 | .setURL(res.tracks[0].uri) 145 | .setThumbnail(res.tracks[0].displayThumbnail("maxresdefault")) 146 | .setDescription( 147 | `[${trackForPlay?.tracks[0]?.title}](${trackForPlay?.tracks[0].uri})` || 148 | "No Title" 149 | ) 150 | .addFields( 151 | { 152 | name: "Added by", 153 | value: `<@${interaction.user.id}>`, 154 | inline: true, 155 | }, 156 | { 157 | name: "Duration", 158 | value: res.tracks[0].isStream 159 | ? `\`LIVE :red_circle:\`` 160 | : `\`${client.ms(res.tracks[0].duration, { 161 | colonNotation: true, 162 | })}\``, 163 | inline: true, 164 | } 165 | ) 166 | .setColor(client.config.embedColor), 167 | ], 168 | components: [], 169 | }); 170 | } 171 | }); 172 | tracksCollector.on("end", async (i) => { 173 | if (i.size == 0) { 174 | choosenTracks.edit({ 175 | content: null, 176 | embeds: [ 177 | new MessageEmbed() 178 | .setDescription( 179 | `No track selected. You took too long to select a track.` 180 | ) 181 | .setColor(client.config.embedColor), 182 | ], 183 | components: [], 184 | }); 185 | } 186 | }); 187 | } 188 | }); 189 | 190 | module.exports = command; 191 | -------------------------------------------------------------------------------- /commands/slash/seek.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const ms = require("ms"); 4 | 5 | const command = new SlashCommand() 6 | .setName("seek") 7 | .setDescription("Seek to a specific time in the current song.") 8 | .addStringOption((option) => 9 | option 10 | .setName("time") 11 | .setDescription("Seek to time you want. Ex 1h 30m | 2h | 80m | 53s") 12 | .setRequired(true), 13 | ) 14 | .setRun(async (client, interaction, options) => { 15 | let channel = await client.getChannel(client, interaction); 16 | if (!channel) { 17 | return; 18 | } 19 | 20 | let player; 21 | if (client.manager) { 22 | player = client.manager.players.get(interaction.guild.id); 23 | } else { 24 | return interaction.reply({ 25 | embeds: [ 26 | new MessageEmbed() 27 | .setColor("RED") 28 | .setDescription("Lavalink node is not connected"), 29 | ], 30 | }); 31 | } 32 | 33 | if (!player) { 34 | return interaction.reply({ 35 | embeds: [ 36 | new MessageEmbed() 37 | .setColor("RED") 38 | .setDescription("There is no music playing."), 39 | ], 40 | ephemeral: true, 41 | }); 42 | } 43 | 44 | await interaction.deferReply(); 45 | 46 | const rawArgs = interaction.options.getString("time"); 47 | const args = rawArgs.split(' '); 48 | var rawTime = []; 49 | for (i = 0; i < args.length; i++){ 50 | rawTime.push(ms(args[i])); 51 | } 52 | const time = rawTime.reduce((a,b) => a + b, 0); 53 | const position = player.position; 54 | const duration = player.queue.current.duration; 55 | 56 | if (time <= duration) { 57 | player.seek(time); 58 | return interaction.editReply({ 59 | embeds: [ 60 | new MessageEmbed() 61 | .setColor(client.config.embedColor) 62 | .setDescription( 63 | `⏩ | **${ player.queue.current.title }** has been ${ 64 | time < position? "rewound" : "seeked" 65 | } to **${ ms(time) }**`, 66 | ), 67 | ], 68 | }); 69 | } else { 70 | return interaction.editReply({ 71 | embeds: [ 72 | new MessageEmbed() 73 | .setColor(client.config.embedColor) 74 | .setDescription( 75 | `Unable to seek current playing track. This may be due to exceeding track duration or an incorrect time format. Please check and try again`, 76 | ), 77 | ], 78 | }); 79 | } 80 | }); 81 | 82 | module.exports = command; 83 | -------------------------------------------------------------------------------- /commands/slash/shuffle.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("shuffle") 6 | .setDescription("Randomizes the queue") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("There is no music playing."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | 37 | if (!player.queue || !player.queue.length || player.queue.length === 0) { 38 | return interaction.reply({ 39 | embeds: [ 40 | new MessageEmbed() 41 | .setColor("RED") 42 | .setDescription("There are not enough songs in the queue."), 43 | ], 44 | ephemeral: true, 45 | }); 46 | } 47 | 48 | // if the queue is not empty, shuffle the entire queue 49 | player.queue.shuffle(); 50 | return interaction.reply({ 51 | embeds: [ 52 | new MessageEmbed() 53 | .setColor(client.config.embedColor) 54 | .setDescription("🔀 | **Successfully shuffled the queue.**"), 55 | ], 56 | }); 57 | }); 58 | 59 | module.exports = command; 60 | -------------------------------------------------------------------------------- /commands/slash/skip.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("skip") 6 | .setDescription("Skip the current song") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!channel) { 10 | return; 11 | } 12 | 13 | let player; 14 | if (client.manager) { 15 | player = client.manager.players.get(interaction.guild.id); 16 | } else { 17 | return interaction.reply({ 18 | embeds: [ 19 | new MessageEmbed() 20 | .setColor("RED") 21 | .setDescription("Lavalink node is not connected"), 22 | ], 23 | }); 24 | } 25 | 26 | if (!player) { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("There is nothing to skip."), 32 | ], 33 | ephemeral: true, 34 | }); 35 | } 36 | const song = player.queue.current; 37 | const autoQueue = player.get("autoQueue"); 38 | if (player.queue[0] == undefined && (!autoQueue || autoQueue === false)) { 39 | return interaction.reply({ 40 | embeds: [ 41 | new MessageEmbed() 42 | .setColor("RED") 43 | .setDescription(`There is nothing after [${ song.title }](${ song.uri }) in the queue.`), 44 | ], 45 | })} 46 | 47 | player.queue.previous = player.queue.current; 48 | player.stop(); 49 | 50 | interaction.reply({ 51 | embeds: [ 52 | new MessageEmbed() 53 | .setColor(client.config.embedColor) 54 | .setDescription("✅ | **Skipped!**"), 55 | ], 56 | }); 57 | }); 58 | 59 | module.exports = command; 60 | -------------------------------------------------------------------------------- /commands/slash/skipto.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("skipto") 6 | .setDescription("skip to a specific song in the queue") 7 | .addNumberOption((option) => 8 | option 9 | .setName("number") 10 | .setDescription("The number of tracks to skipto") 11 | .setRequired(true), 12 | ) 13 | 14 | .setRun(async (client, interaction, options) => { 15 | const args = interaction.options.getNumber("number"); 16 | //const duration = player.queue.current.duration 17 | 18 | let channel = await client.getChannel(client, interaction); 19 | if (!channel) { 20 | return; 21 | } 22 | 23 | let player; 24 | if (client.manager) { 25 | player = client.manager.players.get(interaction.guild.id); 26 | } else { 27 | return interaction.reply({ 28 | embeds: [ 29 | new MessageEmbed() 30 | .setColor("RED") 31 | .setDescription("Lavalink node is not connected"), 32 | ], 33 | }); 34 | } 35 | 36 | if (!player) { 37 | return interaction.reply({ 38 | embeds: [ 39 | new MessageEmbed() 40 | .setColor("RED") 41 | .setDescription("I'm not in a channel."), 42 | ], 43 | ephemeral: true, 44 | }); 45 | } 46 | 47 | await interaction.deferReply(); 48 | 49 | const position = Number(args); 50 | 51 | try { 52 | if (!position || position < 0 || position > player.queue.size) { 53 | let thing = new MessageEmbed() 54 | .setColor(client.config.embedColor) 55 | .setDescription("❌ | Invalid position!"); 56 | return interaction.editReply({ embeds: [thing] }); 57 | } 58 | 59 | player.queue.remove(0, position - 1); 60 | player.stop(); 61 | 62 | let thing = new MessageEmbed() 63 | .setColor(client.config.embedColor) 64 | .setDescription("✅ | Skipped to position " + position); 65 | 66 | return interaction.editReply({ embeds: [thing] }); 67 | } catch { 68 | if (position === 1) { 69 | player.stop(); 70 | } 71 | return interaction.editReply({ 72 | embeds: [ 73 | new MessageEmbed() 74 | .setColor(client.config.embedColor) 75 | .setDescription("✅ | Skipped to position " + position), 76 | ], 77 | }); 78 | } 79 | }); 80 | 81 | module.exports = command; 82 | -------------------------------------------------------------------------------- /commands/slash/stats.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const moment = require("moment"); 3 | require("moment-duration-format"); 4 | const { MessageEmbed } = require("discord.js"); 5 | const os = require("os"); 6 | 7 | const command = new SlashCommand() 8 | .setName("stats") 9 | .setDescription("Get information about the bot") 10 | .setRun(async (client, interaction) => { 11 | // get OS info 12 | const osver = os.platform() + " " + os.release(); 13 | 14 | // Get nodejs version 15 | const nodeVersion = process.version; 16 | 17 | // get the uptime in a human readable format 18 | const runtime = moment 19 | .duration(client.uptime) 20 | .format("d[ Days]・h[ Hrs]・m[ Mins]・s[ Secs]"); 21 | // show lavalink uptime in a nice format 22 | const lavauptime = moment 23 | .duration(client.manager.nodes.values().next().value.stats.uptime) 24 | .format(" D[d], H[h], m[m]"); 25 | // show lavalink memory usage in a nice format 26 | const lavaram = ( 27 | client.manager.nodes.values().next().value.stats.memory.used / 28 | 1024 / 29 | 1024 30 | ).toFixed(2); 31 | // sow lavalink memory alocated in a nice format 32 | const lavamemalocated = ( 33 | client.manager.nodes.values().next().value.stats.memory.allocated / 34 | 1024 / 35 | 1024 36 | ).toFixed(2); 37 | // show system uptime 38 | var sysuptime = moment 39 | .duration(os.uptime() * 1000) 40 | .format("d[ Days]・h[ Hrs]・m[ Mins]・s[ Secs]"); 41 | 42 | // get commit hash and date 43 | let gitHash = "unknown"; 44 | try { 45 | gitHash = require("child_process") 46 | .execSync("git rev-parse HEAD") 47 | .toString() 48 | .trim(); 49 | } catch (e) { 50 | // do nothing 51 | gitHash = "unknown"; 52 | } 53 | 54 | const statsEmbed = new MessageEmbed() 55 | .setTitle(`${ client.user.username } Information`) 56 | .setColor(client.config.embedColor) 57 | .setDescription( 58 | `\`\`\`yml\nName: ${ client.user.username }#${ client.user.discriminator } [${ client.user.id }]\nAPI: ${ client.ws.ping }ms\nRuntime: ${ runtime }\`\`\``, 59 | ) 60 | .setFields([ 61 | { 62 | name: `Lavalink stats`, 63 | value: `\`\`\`yml\nUptime: ${ lavauptime }\nRAM: ${ lavaram } MB\nPlaying: ${ 64 | client.manager.nodes.values().next().value.stats.playingPlayers 65 | } out of ${ 66 | client.manager.nodes.values().next().value.stats.players 67 | }\`\`\``, 68 | inline: true, 69 | }, 70 | { 71 | name: "Bot stats", 72 | value: `\`\`\`yml\nGuilds: ${ 73 | client.guilds.cache.size 74 | } \nNodeJS: ${ nodeVersion }\nDiscordMusicBot: v${ 75 | require("../../package.json").version 76 | } \`\`\``, 77 | inline: true, 78 | }, 79 | { 80 | name: "System stats", 81 | value: `\`\`\`yml\nOS: ${ osver }\nUptime: ${ sysuptime }\n\`\`\``, 82 | inline: false, 83 | }, 84 | ]) 85 | .setFooter({ text: `Build: ${ gitHash }` }); 86 | return interaction.reply({ embeds: [statsEmbed], ephemeral: false }); 87 | }); 88 | 89 | module.exports = command; 90 | -------------------------------------------------------------------------------- /commands/slash/stop.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("stop") 6 | .setDescription("Stops whatever the bot is playing and leaves the voice channel\n(This command will clear the queue)") 7 | 8 | .setRun(async (client, interaction, options) => { 9 | let channel = await client.getChannel(client, interaction); 10 | if (!channel) { 11 | return; 12 | } 13 | 14 | let player; 15 | if (client.manager) { 16 | player = client.manager.players.get(interaction.guild.id); 17 | } else { 18 | return interaction.reply({ 19 | embeds: [ 20 | new MessageEmbed() 21 | .setColor("RED") 22 | .setDescription("Lavalink node is not connected"), 23 | ], 24 | }); 25 | } 26 | 27 | if (!player) { 28 | return interaction.reply({ 29 | embeds: [ 30 | new MessageEmbed() 31 | .setColor("RED") 32 | .setDescription("I'm not in a channel."), 33 | ], 34 | ephemeral: true, 35 | }); 36 | } 37 | 38 | if (player.twentyFourSeven) { 39 | player.queue.clear(); 40 | player.stop(); 41 | player.set("autoQueue", false); 42 | } else { 43 | player.destroy(); 44 | } 45 | 46 | interaction.reply({ 47 | embeds: [ 48 | new MessageEmbed() 49 | .setColor(client.config.embedColor) 50 | .setDescription(`:wave: | **Bye Bye!**`), 51 | ], 52 | }); 53 | }); 54 | 55 | module.exports = command; 56 | -------------------------------------------------------------------------------- /commands/slash/summon.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("summon") 6 | .setDescription("Summons the bot to the channel.") 7 | .setRun(async (client, interaction, options) => { 8 | let channel = await client.getChannel(client, interaction); 9 | if (!interaction.member.voice.channel) { 10 | const joinEmbed = new MessageEmbed() 11 | .setColor(client.config.embedColor) 12 | .setDescription( 13 | "❌ | **You must be in a voice channel to use this command.**", 14 | ); 15 | return interaction.reply({ embeds: [joinEmbed], ephemeral: true }); 16 | } 17 | 18 | let player = client.manager.players.get(interaction.guild.id); 19 | if (!player) { 20 | player = client.createPlayer(interaction.channel, channel); 21 | player.connect(true); 22 | } 23 | 24 | if (channel.id !== player.voiceChannel) { 25 | player.setVoiceChannel(channel.id); 26 | player.connect(); 27 | } 28 | 29 | interaction.reply({ 30 | embeds: [ 31 | client.Embed(`:thumbsup: | **Successfully joined <#${ channel.id }>!**`), 32 | ], 33 | }); 34 | }); 35 | 36 | module.exports = command; 37 | -------------------------------------------------------------------------------- /commands/slash/volume.js: -------------------------------------------------------------------------------- 1 | const SlashCommand = require("../../lib/SlashCommand"); 2 | const { MessageEmbed } = require("discord.js"); 3 | 4 | const command = new SlashCommand() 5 | .setName("volume") 6 | .setDescription("Change the volume of the current song.") 7 | .addNumberOption((option) => 8 | option 9 | .setName("amount") 10 | .setDescription("Amount of volume you want to change. Ex: 10") 11 | .setRequired(false), 12 | ) 13 | .setRun(async (client, interaction) => { 14 | let channel = await client.getChannel(client, interaction); 15 | if (!channel) { 16 | return; 17 | } 18 | 19 | let player; 20 | if (client.manager) { 21 | player = client.manager.players.get(interaction.guild.id); 22 | } else { 23 | return interaction.reply({ 24 | embeds: [ 25 | new MessageEmbed() 26 | .setColor("RED") 27 | .setDescription("Lavalink node is not connected"), 28 | ], 29 | }); 30 | } 31 | 32 | if (!player) { 33 | return interaction.reply({ 34 | embeds: [ 35 | new MessageEmbed() 36 | .setColor("RED") 37 | .setDescription("There is no music playing."), 38 | ], 39 | ephemeral: true, 40 | }); 41 | } 42 | 43 | let vol = interaction.options.getNumber("amount"); 44 | if (!vol || vol < 1 || vol > 125) { 45 | return interaction.reply({ 46 | embeds: [ 47 | new MessageEmbed() 48 | .setColor(client.config.embedColor) 49 | .setDescription( 50 | `:loud_sound: | Current volume **${ player.volume }**`, 51 | ), 52 | ], 53 | }); 54 | } 55 | 56 | player.setVolume(vol); 57 | return interaction.reply({ 58 | embeds: [ 59 | new MessageEmbed() 60 | .setColor(client.config.embedColor) 61 | .setDescription( 62 | `:loud_sound: | Successfully set volume to **${ player.volume }**`, 63 | ), 64 | ], 65 | }); 66 | }); 67 | 68 | module.exports = command; 69 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | helpCmdPerPage: 10, //- Number of commands per page of help command 3 | lyricsMaxResults: 5, //- Number of results for lyrics command (Do not touch this value if you don't know what you are doing) 4 | adminId: "UserId", //- Replace UserId with the Discord ID of the admin of the bot 5 | token: process.env.token || "", //- Bot's Token 6 | clientId: process.env.clientId || "", //- ID of the bot 7 | clientSecret: process.env.clientSecret || "", //- Client Secret of the bot 8 | port: 4200, //- Port of the API and Dashboard 9 | scopes: ["identify", "guilds", "applications.commands"], //- Discord OAuth2 Scopes 10 | inviteScopes: ["bot", "applications.commands"], // Invite link scopes 11 | serverDeafen: true, //- If you want bot to stay deafened 12 | defaultVolume: 100, //- Sets the default volume of the bot, You can change this number anywhere from 1 to 100 13 | supportServer: "https://discord.gg/sbySMS7m3v", //- Support Server Link 14 | Issues: "https://github.com/SudhanPlayz/Discord-MusicBot/issues", //- Bug Report Link 15 | permissions: 277083450689, //- Bot Inviting Permissions 16 | disconnectTime: 30000, //- How long should the bot wait before disconnecting from the voice channel (in miliseconds). Set to 1 for instant disconnect. 17 | twentyFourSeven: false, //- When set to true, the bot will never disconnect from the voice channel 18 | autoQueue: false, //- When set to true, related songs will automatically be added to the queue 19 | autoPause: true, //- When set to true, music will automatically be paused if everyone leaves the voice channel 20 | autoLeave: false, //- When set to true, the bot will automatically leave when no one is in the voice channel (can be combined with 24/7 to always be in voice channel until everyone leaves; if 24/7 is on disconnectTime will add a disconnect delay after everyone leaves.) 21 | debug: false, //- Debug mode 22 | cookieSecret: "CodingWithSudhan is epic", //- Cookie Secret 23 | website: "http://localhost:4200", //- without the / at the end 24 | // You need a lavalink server for this bot to work!!!! 25 | // Lavalink server; public lavalink -> https://lavalink-list.darrennathanael.com/; create one yourself -> https://darrennathanael.com/post/how-to-lavalink 26 | nodes: [ 27 | { 28 | identifier: "Main Node", //- Used for indentifier in stats commands. 29 | host: "", //- The host name or IP of the lavalink server. 30 | port: 80, // The port that lavalink is listening to. This must be a number! 31 | password: "", //- The password of the lavalink server. 32 | retryAmount: 200, //- The amount of times to retry connecting to the node if connection got dropped. 33 | retryDelay: 40, //- Delay between reconnect attempts if connection is lost. 34 | secure: false, //- Can be either true or false. Only use true if ssl is enabled! 35 | }, 36 | ], 37 | embedColor: "#2f3136", //- Color of the embeds, hex supported 38 | presence: { 39 | // PresenceData object | https://discord.js.org/#/docs/main/stable/typedef/PresenceData 40 | status: "online", //- You can have online, idle, dnd and invisible (Note: invisible makes people think the bot is offline) 41 | activities: [ 42 | { 43 | name: "Music", //- Status Text 44 | type: "LISTENING", //- PLAYING, WATCHING, LISTENING, STREAMING 45 | }, 46 | ], 47 | }, 48 | iconURL: "https://cdn.darrennathanael.com/icons/spinning_disk.gif", //- This icon will be in every embed's author field 49 | }; 50 | -------------------------------------------------------------------------------- /dashboard/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | .pnpm-debug.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | -------------------------------------------------------------------------------- /dashboard/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped 2 | with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 3 | 4 | ## Getting Started 5 | 6 | First, run the development server: 7 | 8 | ```bash 9 | npm run dev 10 | # or 11 | yarn dev 12 | ``` 13 | 14 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 15 | 16 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 17 | 18 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed 19 | on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited 20 | in `pages/api/hello.js`. 21 | 22 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated 23 | as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions 33 | are welcome! 34 | 35 | ## Deploy on Vercel 36 | 37 | The easiest way to deploy your Next.js app is to use 38 | the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) 39 | from the creators of Next.js. 40 | 41 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 42 | -------------------------------------------------------------------------------- /dashboard/components/StatCard.tsx: -------------------------------------------------------------------------------- 1 | import {Card, Text} from "@nextui-org/react"; 2 | import {ReactNode} from "react"; 3 | 4 | export default function StatCard(props: { 5 | title: string; 6 | amount: number | string; 7 | icon: ReactNode; 8 | }) { 9 | return ( 10 | <Card variant="flat" isHoverable css={ {margin: '10px', width: '200px', padding: '15px'} }> 11 | <Card.Body css={ {display: 'flex', padding: '0', alignItems: 'center', flexDirection: 'row'} }> 12 | <div style={ {marginRight: 'auto'} }> 13 | <Text h4 css={ {color: 'GrayText'} }>{ props.title }</Text> 14 | <Text h3>{ props.amount }</Text> 15 | </div> 16 | { props.icon } 17 | </Card.Body> 18 | </Card> 19 | ) 20 | } -------------------------------------------------------------------------------- /dashboard/components/content.tsx: -------------------------------------------------------------------------------- 1 | import {PropsWithChildren} from "react"; 2 | import Navbar from "./navbar"; 3 | 4 | export default function Content(props: PropsWithChildren) { 5 | return <div style={ { 6 | width: "100vw", 7 | height: "100vh", 8 | display: "flex", 9 | } }> 10 | <Navbar/> 11 | <div style={ { 12 | marginTop: '30px' 13 | } }> 14 | { props.children } 15 | </div> 16 | </div> 17 | } -------------------------------------------------------------------------------- /dashboard/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import {Button, Link, Spacer} from "@nextui-org/react"; 2 | import { useRouter } from "next/router"; 3 | 4 | export default function Navbar() { 5 | const router = useRouter(); 6 | 7 | return <div style={ { 8 | height: '100%', 9 | width: '250px', 10 | backgroundColor: '#16181A', 11 | display: 'flex', 12 | alignItems: 'center', 13 | flexDirection: 'column', 14 | paddingTop: '50px', 15 | marginRight: '50px', 16 | } }> 17 | <Link css={ { 18 | fontSize: '$xl2', 19 | fontWeight: 'bold', 20 | marginBottom: '30px', 21 | color: '#fff', 22 | } } href='/'>Discord Music Bot</Link> 23 | <Button css={ {background: router.pathname == '/dashboard' ? '$primary' : '$gray100'} } 24 | onClick={ () => window.location.pathname = '/dashboard' } style={ {marginBottom: '10px'} }>Dashboard</Button> 25 | <Button css={ {background: router.pathname == '/servers' ? '$primary' : '$gray100'} } color='default' 26 | onClick={ () => window.location.pathname = '/servers' } style={ {marginBottom: '10px'} }>Servers</Button> 27 | <Spacer/> 28 | <Button color='error' flat onClick={ () => window.location.pathname = '/logout' } 29 | style={ {marginBottom: '10px'} }>Logout</Button> 30 | </div> 31 | } 32 | -------------------------------------------------------------------------------- /dashboard/components/server.tsx: -------------------------------------------------------------------------------- 1 | import {Avatar, Tooltip} from "@nextui-org/react"; 2 | import Link from "next/link"; 3 | 4 | interface IProps { 5 | icon: string; 6 | name: string; 7 | id: string; 8 | } 9 | 10 | const getColor = () => { 11 | let c = ["gradient", "primary", "secondary", "error", "warning"] 12 | return c[Math.floor(Math.random() * c.length)]; 13 | } 14 | 15 | export default function Server(props: IProps) { 16 | return <div key={ props.id } style={ { 17 | margin: "10px" 18 | } }> 19 | <Link href={ "/servers/" + props.id }><a> 20 | <Tooltip content={ props.name } color="secondary"> 21 | <Avatar 22 | src={ props.icon } 23 | size="xl" 24 | //@ts-ignore 25 | color={ getColor() } 26 | bordered 27 | pointer 28 | /> 29 | </Tooltip> 30 | </a></Link> 31 | </div> 32 | } -------------------------------------------------------------------------------- /dashboard/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="next" /> 2 | /// <reference types="next/image-types/global" /> 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /dashboard/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /dashboard/out/_next/static/chunks/pages/_error-a4ba2246ff8fb532.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[820],{1981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(9185)}])}},function(n){n.O(0,[774,888,179],(function(){return _=1981,n(n.s=_);var _}));var _=n.O();_N_E=_}]); -------------------------------------------------------------------------------- /dashboard/out/_next/static/chunks/pages/dashboard-8fe77e1aeec6ff87.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[26],{528:function(n,e,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/dashboard",function(){return t(1659)}])},6303:function(n,e,t){"use strict";t.d(e,{Z:function(){return p}});var r=t(5893),i=t(5208),s=t(1160),o=t(7294),a=t(88),c=t(6772);const l=(0,t(6212).zo)("span",{size:"1px",variants:{inline:{true:{display:"inline-block"},false:{display:"block"}}},defaultVariants:{inline:!1}}),d=({x:n,y:e,inline:t,css:i,...s})=>{const o=(0,c.m)(n),a=(0,c.m)(e);return(0,r.jsx)(l,{css:{marginLeft:`${o} !important`,marginTop:`${a} !important`,...i},"aria-hidden":"true",...s})};d.toString=()=>".nextui-spacer";const u=o.memo(d);var h=(0,a.Z)(u,{x:1,y:1}),x=t(1163);function f(){var n=(0,x.useRouter)();return(0,r.jsxs)("div",{style:{height:"100%",width:"250px",backgroundColor:"#16181A",display:"flex",alignItems:"center",flexDirection:"column",paddingTop:"50px",marginRight:"50px"},children:[(0,r.jsx)(i.ZP,{css:{fontSize:"$xl2",fontWeight:"bold",marginBottom:"30px",color:"#fff"},href:"/",children:"Discord Music Bot"}),(0,r.jsx)(s.ZP,{css:{background:"/dashboard"==n.pathname?"$primary":"$gray100"},onClick:function(){return window.location.pathname="/dashboard"},style:{marginBottom:"10px"},children:"Dashboard"}),(0,r.jsx)(s.ZP,{css:{background:"/servers"==n.pathname?"$primary":"$gray100"},color:"default",onClick:function(){return window.location.pathname="/servers"},style:{marginBottom:"10px"},children:"Servers"}),(0,r.jsx)(h,{}),(0,r.jsx)(s.ZP,{color:"error",flat:!0,onClick:function(){return window.location.pathname="/logout"},style:{marginBottom:"10px"},children:"Logout"})]})}function p(n){return(0,r.jsxs)("div",{style:{width:"100vw",height:"100vh",display:"flex"},children:[(0,r.jsx)(f,{}),(0,r.jsx)("div",{style:{marginTop:"30px"},children:n.children})]})}},1659:function(n,e,t){"use strict";t.r(e),t.d(e,{default:function(){return v}});var r=t(5893),i=t(9008),s=t.n(i),o=t(5784),a=(0,o.Z)((0,r.jsx)("path",{d:"M9.19 6.35c-2.04 2.29-3.44 5.58-3.57 5.89l-2.26-.97c-.65-.28-.81-1.13-.31-1.63l3.01-3.01c.47-.47 1.15-.68 1.81-.55l1.32.27zm1.49 10.16c.3.3.74.38 1.12.2 1.16-.54 3.65-1.81 5.26-3.42 4.59-4.59 4.63-8.33 4.36-9.93-.07-.4-.39-.72-.79-.79-1.6-.27-5.34-.23-9.93 4.36-1.61 1.61-2.87 4.1-3.42 5.26-.18.38-.09.83.2 1.12l3.2 3.2zm6.97-1.7c-2.29 2.04-5.58 3.44-5.89 3.57l.97 2.26c.28.65 1.13.81 1.63.31l3.01-3.01c.47-.47.68-1.15.55-1.81l-.27-1.32zm-8.71 2.6c.2 1.06-.15 2.04-.82 2.71-.77.77-3.16 1.34-4.71 1.64-.69.13-1.3-.48-1.17-1.17.3-1.55.86-3.94 1.64-4.71.67-.67 1.65-1.02 2.71-.82 1.17.22 2.13 1.18 2.35 2.35zM13 9c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2z"}),"RocketLaunchRounded"),c=(0,o.Z)((0,r.jsx)("path",{d:"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v1c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-1c0-2.66-5.33-4-8-4z"}),"PersonRounded"),l=(0,o.Z)((0,r.jsx)("path",{d:"M19 13H5c-1.1 0-2 .9-2 2v4c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-4c0-1.1-.9-2-2-2zM7 19c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM19 3H5c-1.1 0-2 .9-2 2v4c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM7 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"}),"DnsRounded"),d=t(5446),u=t(6303),h=t(7370),x=t(6979);function f(n){return(0,r.jsx)(h.ZP,{variant:"flat",isHoverable:!0,css:{margin:"10px",width:"200px",padding:"15px"},children:(0,r.jsxs)(h.ZP.Body,{css:{display:"flex",padding:"0",alignItems:"center",flexDirection:"row"},children:[(0,r.jsxs)("div",{style:{marginRight:"auto"},children:[(0,r.jsx)(x.Z,{h4:!0,css:{color:"GrayText"},children:n.title}),(0,r.jsx)(x.Z,{h3:!0,children:n.amount})]}),n.icon]})})}var p=t(7294),m=t(7568),g=t(4051),j=t.n(g),v=function(n){var e=(0,p.useState)(null),t=e[0],i=e[1];return(0,p.useEffect)((function(){new Promise(function(){var n=(0,m.Z)(j().mark((function n(e,t){var r,i;return j().wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.next=2,fetch("/api/dashboard",{method:"GET",credentials:"same-origin"});case 2:return r=n.sent,n.next=5,r.json();case 5:i=n.sent,e(i);case 7:case"end":return n.stop()}}),n)})));return function(e,t){return n.apply(this,arguments)}}()).then(i)}),[]),(0,r.jsxs)(u.Z,{children:[(0,r.jsx)(s(),{children:(0,r.jsx)("title",{children:"Dashboard | Discord Music Bot"})}),(0,r.jsx)("h1",{children:"Dashboard"}),(0,r.jsxs)("div",{style:{display:"flex"},children:[(0,r.jsx)(f,{title:"Commands Ran",amount:t?t.commandsRan:"Loading",icon:(0,r.jsx)(a,{fontSize:"large"})}),(0,r.jsx)(f,{title:"Users",amount:t?t.users:"Loading",icon:(0,r.jsx)(c,{fontSize:"large"})}),(0,r.jsx)(f,{title:"Servers",amount:t?t.servers:"Loading",icon:(0,r.jsx)(l,{fontSize:"large"})}),(0,r.jsx)(f,{title:"Songs Played",amount:t?t.songsPlayed:"Loading",icon:(0,r.jsx)(d.Z,{fontSize:"large"})})]})]})}},1163:function(n,e,t){n.exports=t(387)}},function(n){n.O(0,[123,732,774,888,179],(function(){return e=528,n(n.s=e);var e}));var e=n.O();_N_E=e}]); -------------------------------------------------------------------------------- /dashboard/out/_next/static/chunks/pages/index-0494ad302e38da35.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[405],{8312:function(e,n,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/",function(){return i(4636)}])},4636:function(e,n,i){"use strict";i.r(n),i.d(n,{default:function(){return b}});var t=i(5893),s=i(7294),r=i(5784),o=(0,r.Z)((0,t.jsx)("path",{d:"M10 15l5.19-3L10 9v6m11.56-7.83c.13.47.22 1.1.28 1.9.07.8.1 1.49.1 2.09L22 12c0 2.19-.16 3.8-.44 4.83-.25.9-.83 1.48-1.73 1.73-.47.13-1.33.22-2.65.28-1.3.07-2.49.1-3.59.1L12 19c-4.19 0-6.8-.16-7.83-.44-.9-.25-1.48-.83-1.73-1.73-.13-.47-.22-1.1-.28-1.9-.07-.8-.1-1.49-.1-2.09L2 12c0-2.19.16-3.8.44-4.83.25-.9.83-1.48 1.73-1.73.47-.13 1.33-.22 2.65-.28 1.3-.07 2.49-.1 3.59-.1L12 5c4.19 0 6.8.16 7.83.44.9.25 1.48.83 1.73 1.73z"}),"YouTube"),l=i(5446),c=(0,r.Z)((0,t.jsx)("path",{d:"M19.5 12c0-.23-.01-.45-.03-.68l1.86-1.41c.4-.3.51-.86.26-1.3l-1.87-3.23c-.25-.44-.79-.62-1.25-.42l-2.15.91c-.37-.26-.76-.49-1.17-.68l-.29-2.31c-.06-.5-.49-.88-.99-.88h-3.73c-.51 0-.94.38-1 .88l-.29 2.31c-.41.19-.8.42-1.17.68l-2.15-.91c-.46-.2-1-.02-1.25.42L2.41 8.62c-.25.44-.14.99.26 1.3l1.86 1.41c-.02.22-.03.44-.03.67s.01.45.03.68l-1.86 1.41c-.4.3-.51.86-.26 1.3l1.87 3.23c.25.44.79.62 1.25.42l2.15-.91c.37.26.76.49 1.17.68l.29 2.31c.06.5.49.88.99.88h3.73c.5 0 .93-.38.99-.88l.29-2.31c.41-.19.8-.42 1.17-.68l2.15.91c.46.2 1 .02 1.25-.42l1.87-3.23c.25-.44.14-.99-.26-1.3l-1.86-1.41c.03-.23.04-.45.04-.68zm-7.46 3.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"}),"SettingsRounded");var a=(0,i(6212).zo)("div",{w:"100%",mr:"auto",ml:"auto",variants:{fluid:{true:{maxWidth:"100%"}},responsive:{true:{"@xs":{maxWidth:"$breakpoints$xs"},"@sm":{maxWidth:"$breakpoints$sm"},"@md":{maxWidth:"$breakpoints$md"},"@lg":{maxWidth:"$breakpoints$lg"},"@xl":{maxWidth:"$breakpoints$xl"}}}},defaultVariants:{fluid:!1,responsive:!0}});const d=({xs:e,sm:n,md:i,lg:r,xl:o,wrap:l,gap:c,as:d,display:u,justify:x,direction:p,alignItems:h,alignContent:f,children:m,responsive:g,fluid:y,css:j,...$})=>{const b=(0,s.useMemo)((()=>`calc(${c} * $space$sm)`),[c]);return(0,t.jsx)(a,{css:{px:b,maxWidth:e?"$breakpoints$xs":n?"$breakpoints$sm":i?"$breakpoints$md":r?"$breakpoints$lg":o?"$breakpoints$xl":"",alignItems:h,alignContent:f,flexWrap:l,display:u,justifyContent:x,flexDirection:p,...j},responsive:g,fluid:y,as:d,...$,children:m})};d.toString=()=>".nextui-container",d.defaultProps={gap:2,xs:!1,sm:!1,md:!1,lg:!1,xl:!1,responsive:!0,fluid:!1,wrap:"wrap",as:"div",display:"block"};var u=s.memo(d),x=i(5208),p=i(1160),h=i(6979),f=i(7370),m=i(9008),g=i.n(m),y=i(7568),j=i(4051),$=i.n(j),b=function(e){var n=(0,s.useState)(null),i=n[0],r=n[1];return(0,s.useEffect)((function(){new Promise(function(){var e=(0,y.Z)($().mark((function e(n,i){var t;return $().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,fetch("/api/data",{method:"GET"});case 2:return t=e.sent,e.t0=n,e.next=6,t.json();case 6:e.t1=e.sent,(0,e.t0)(e.t1);case 8:case"end":return e.stop()}}),e)})));return function(n,i){return e.apply(this,arguments)}}()).then((function(e){var n;r(e),(null===(n=e.redirect)||void 0===n?void 0:n.length)&&(window.location.href=e.redirect)}))}),[]),(0,t.jsxs)(u,{children:[(0,t.jsx)(g(),{children:(0,t.jsx)("title",{children:"Discord Music Bot"})}),(0,t.jsxs)(u,{css:{display:"flex",alignItems:"center",background:"$gray50",position:"fixed",padding:"20px",minWidth:"100%",left:"0",top:"0",zIndex:"$5"},children:[(0,t.jsx)(x.ZP,{css:{fontSize:"$xl",fontWeight:"$semibold"},href:"/",children:i?i.name:"Discord Music Bot"}),(0,t.jsx)(x.ZP,{color:"text",css:{fontSize:"$lg",fontWeight:"$medium",marginLeft:"20px"},href:"#",children:"Home"}),(0,t.jsx)(x.ZP,{color:"text",css:{fontSize:"$lg",fontWeight:"$medium",marginLeft:"20px"},href:"#features",children:"Features"}),(0,t.jsx)(p.ZP,{onClick:function(){return window.location.pathname="/dashboard"},css:{marginLeft:"auto"},auto:!0,shadow:!0,children:"Dashboard"})]}),(0,t.jsxs)(u,{style:{textAlign:"center",marginTop:"1rem",display:"flex",height:"100vh",justifyContent:"center",alignItems:"center",flexDirection:"column"},children:[(0,t.jsx)(h.Z,{h1:!0,css:{textGradient:"180deg, $blue600 -20%, $blue800 100%"},children:"Discord Music Bot"}),(0,t.jsx)(h.Z,{h3:!0,css:{color:"$gray800"},children:"An advanced discord music bot, supports Spotify, SoundCloud, YouTube with Shuffling, Volume Control and Web Dashboard!"}),(0,t.jsxs)(u,{css:{display:"flex",alignItems:"center",justifyContent:"center"},children:[(0,t.jsx)(p.ZP,{color:"primary",onClick:function(){return window.location.pathname="/login"},shadow:!0,style:{marginTop:"1rem"},children:"Login"}),(0,t.jsx)(p.ZP,{color:"primary",flat:!0,onClick:function(){return window.open("https://github.com/SudhanPlayz/Discord-MusicBot")},style:{marginTop:"1rem",marginLeft:"20px"},children:"Github"})]})]}),(0,t.jsxs)(u,{css:{display:"flex",flexDirection:"column",alignItems:"center",minHeight:"60vh"},children:[(0,t.jsx)(h.Z,{h2:!0,children:"Features"}),(0,t.jsxs)(u,{css:{display:"flex",justifyContent:"center",flexWrap:"wrap"},children:[(0,t.jsxs)(f.ZP,{isHoverable:!0,css:{display:"flex",flexDirection:"column",alignItems:"center",margin:"10px",width:"300px",padding:"20px",textAlign:"center"},children:[(0,t.jsx)(o,{style:{fontSize:"150px",color:"#3694FF"}}),(0,t.jsx)(h.Z,{h3:!0,children:"Spotify, Soundcloud, and Youtube support"}),(0,t.jsx)(h.Z,{css:{color:"$gray800"},children:"Use your spotify playlist, youtube videos, youtube playlists and much more using this bot"})]}),(0,t.jsxs)(f.ZP,{isHoverable:!0,css:{display:"flex",flexDirection:"column",alignItems:"center",margin:"10px",width:"300px",padding:"20px",textAlign:"center"},children:[(0,t.jsx)(l.Z,{style:{fontSize:"150px",color:"#3694FF"}}),(0,t.jsx)(h.Z,{h3:!0,children:"Lag-free Music"}),(0,t.jsx)(h.Z,{css:{color:"$gray800"},children:"This bot will never lag when playing any song in a voice channel"})]}),(0,t.jsxs)(f.ZP,{isHoverable:!0,css:{display:"flex",flexDirection:"column",alignItems:"center",margin:"10px",width:"300px",padding:"20px",textAlign:"center"},children:[(0,t.jsx)(c,{style:{fontSize:"150px",color:"#3694FF"}}),(0,t.jsx)(h.Z,{h3:!0,children:"Server Settings"}),(0,t.jsx)(h.Z,{css:{color:"$gray800"},children:"Control your song looping song or queue, play or pause your song easily, or stop the bot completely."})]})]})]}),(0,t.jsxs)(u,{css:{marginTop:"30px",display:"flex",flexDirection:"column",alignItems:"center",minHeight:"60vh"},children:[(0,t.jsx)(h.Z,{h2:!0,children:"What are you waiting for?"}),(0,t.jsx)(p.ZP,{shadow:!0,size:"md",css:{marginTop:"5em"},children:"Start Now"})]})]})}}},function(e){e.O(0,[123,732,774,888,179],(function(){return n=8312,e(e.s=n);var n}));var n=e.O();_N_E=n}]); -------------------------------------------------------------------------------- /dashboard/out/_next/static/chunks/pages/login-8481030b110c33c9.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[459],{3236:function(n,i,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/login",function(){return t(9729)}])},9729:function(n,i,t){"use strict";t.r(i);var o=t(5893),e=t(9008),c=t.n(e),r=t(7294);i.default=function(n){return(0,r.useEffect)((function(){window.location.href="/api/login"})),(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(c(),{children:(0,o.jsx)("title",{children:"Logging In | Discord Music Bot"})}),(0,o.jsx)("p",{children:"Redirecting you to login..."})]})}},9008:function(n,i,t){n.exports=t(5443)}},function(n){n.O(0,[774,888,179],(function(){return i=3236,n(n.s=i);var i}));var i=n.O();_N_E=i}]); -------------------------------------------------------------------------------- /dashboard/out/_next/static/chunks/pages/logout-7167c9506bd9bdd3.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[765],{1527:function(n,t,o){(window.__NEXT_P=window.__NEXT_P||[]).push(["/logout",function(){return o(585)}])},585:function(n,t,o){"use strict";o.r(t),o.d(t,{default:function(){return c}});var u=o(5893),i=o(9008),e=o.n(i),r=o(7294);function c(n){return(0,r.useEffect)((function(){window.location.href="/api/logout"}),[]),(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(e(),{children:(0,u.jsx)("title",{children:"Logging Out | Discord Music Bot"})}),(0,u.jsx)("p",{children:"Redirecting you to logout..."})]})}},9008:function(n,t,o){n.exports=o(5443)}},function(n){n.O(0,[774,888,179],(function(){return t=1527,n(n.s=t);var t}));var t=n.O();_N_E=t}]); -------------------------------------------------------------------------------- /dashboard/out/_next/static/chunks/pages/servers/[id]-9f8c48f5bf25bd78.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[341],{8974:function(n,r,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/servers/[id]",function(){return i(1111)}])},6303:function(n,r,i){"use strict";i.d(r,{Z:function(){return x}});var t=i(5893),e=i(5208),o=i(1160),s=i(7294),a=i(88),c=i(6772);const l=(0,i(6212).zo)("span",{size:"1px",variants:{inline:{true:{display:"inline-block"},false:{display:"block"}}},defaultVariants:{inline:!1}}),u=({x:n,y:r,inline:i,css:e,...o})=>{const s=(0,c.m)(n),a=(0,c.m)(r);return(0,t.jsx)(l,{css:{marginLeft:`${s} !important`,marginTop:`${a} !important`,...e},"aria-hidden":"true",...o})};u.toString=()=>".nextui-spacer";const d=s.memo(u);var h=(0,a.Z)(d,{x:1,y:1}),p=i(1163);function f(){var n=(0,p.useRouter)();return(0,t.jsxs)("div",{style:{height:"100%",width:"250px",backgroundColor:"#16181A",display:"flex",alignItems:"center",flexDirection:"column",paddingTop:"50px",marginRight:"50px"},children:[(0,t.jsx)(e.ZP,{css:{fontSize:"$xl2",fontWeight:"bold",marginBottom:"30px",color:"#fff"},href:"/",children:"Discord Music Bot"}),(0,t.jsx)(o.ZP,{css:{background:"/dashboard"==n.pathname?"$primary":"$gray100"},onClick:function(){return window.location.pathname="/dashboard"},style:{marginBottom:"10px"},children:"Dashboard"}),(0,t.jsx)(o.ZP,{css:{background:"/servers"==n.pathname?"$primary":"$gray100"},color:"default",onClick:function(){return window.location.pathname="/servers"},style:{marginBottom:"10px"},children:"Servers"}),(0,t.jsx)(h,{}),(0,t.jsx)(o.ZP,{color:"error",flat:!0,onClick:function(){return window.location.pathname="/logout"},style:{marginBottom:"10px"},children:"Logout"})]})}function x(n){return(0,t.jsxs)("div",{style:{width:"100vw",height:"100vh",display:"flex"},children:[(0,t.jsx)(f,{}),(0,t.jsx)("div",{style:{marginTop:"30px"},children:n.children})]})}},1111:function(n,r,i){"use strict";i.r(r),i.d(r,{default:function(){return a}});var t=i(5893),e=i(9008),o=i.n(e),s=i(6303);function a(n){var r="Amongus";return(0,t.jsxs)(s.Z,{children:[(0,t.jsx)(o(),{children:(0,t.jsx)("title",{children:r})}),(0,t.jsx)("h1",{children:r})]})}},1163:function(n,r,i){n.exports=i(387)}},function(n){n.O(0,[123,774,888,179],(function(){return r=8974,n(n.s=r);var r}));var r=n.O();_N_E=r}]); -------------------------------------------------------------------------------- /dashboard/out/_next/static/chunks/webpack-fd1bc4a65a80e5c8.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var e={},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var u=t[r]={exports:{}},f=!0;try{e[r](u,u.exports,n),f=!1}finally{f&&delete t[r]}return u.exports}n.m=e,function(){var e=[];n.O=function(t,r,o,u){if(!r){var f=1/0;for(l=0;l<e.length;l++){r=e[l][0],o=e[l][1],u=e[l][2];for(var i=!0,c=0;c<r.length;c++)(!1&u||f>=u)&&Object.keys(n.O).every((function(e){return n.O[e](r[c])}))?r.splice(c--,1):(i=!1,u<f&&(f=u));if(i){e.splice(l--,1);var a=o();void 0!==a&&(t=a)}}return t}u=u||0;for(var l=e.length;l>0&&e[l-1][2]>u;l--)e[l]=e[l-1];e[l]=[r,o,u]}}(),n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},function(){var e,t=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__};n.t=function(r,o){if(1&o&&(r=this(r)),8&o)return r;if("object"===typeof r&&r){if(4&o&&r.__esModule)return r;if(16&o&&"function"===typeof r.then)return r}var u=Object.create(null);n.r(u);var f={};e=e||[null,t({}),t([]),t(t)];for(var i=2&o&&r;"object"==typeof i&&!~e.indexOf(i);i=t(i))Object.getOwnPropertyNames(i).forEach((function(e){f[e]=function(){return r[e]}}));return f.default=function(){return r},n.d(u,f),u}}(),n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="/_next/",function(){var e={272:0};n.O.j=function(t){return 0===e[t]};var t=function(t,r){var o,u,f=r[0],i=r[1],c=r[2],a=0;if(f.some((function(t){return 0!==e[t]}))){for(o in i)n.o(i,o)&&(n.m[o]=i[o]);if(c)var l=c(n)}for(t&&t(r);a<f.length;a++)u=f[a],n.o(e,u)&&e[u]&&e[u][0](),e[u]=0;return n.O(l)},r=self.webpackChunk_N_E=self.webpackChunk_N_E||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))}()}(); -------------------------------------------------------------------------------- /dashboard/out/_next/static/wV3SzfWusZ8UapJ--_pvH/_buildManifest.js: -------------------------------------------------------------------------------- 1 | self.__BUILD_MANIFEST=function(s,e){return{__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":[s,e,"static/chunks/pages/index-0494ad302e38da35.js"],"/_error":["static/chunks/pages/_error-a4ba2246ff8fb532.js"],"/dashboard":[s,e,"static/chunks/pages/dashboard-8fe77e1aeec6ff87.js"],"/login":["static/chunks/pages/login-8481030b110c33c9.js"],"/logout":["static/chunks/pages/logout-7167c9506bd9bdd3.js"],"/servers":[s,"static/chunks/pages/servers-b957468847859725.js"],"/servers/[id]":[s,"static/chunks/pages/servers/[id]-9f8c48f5bf25bd78.js"],sortedPages:["/","/_app","/_error","/dashboard","/login","/logout","/servers","/servers/[id]"]}}("static/chunks/123-d3ffcfb4730480c6.js","static/chunks/732-e52c1d2253f458fa.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); -------------------------------------------------------------------------------- /dashboard/out/_next/static/wV3SzfWusZ8UapJ--_pvH/_ssgManifest.js: -------------------------------------------------------------------------------- 1 | self.__SSG_MANIFEST=new Set,self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB(); -------------------------------------------------------------------------------- /dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "export": "next export" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.9.3", 14 | "@emotion/styled": "^11.9.3", 15 | "@mui/icons-material": "^5.8.4", 16 | "@mui/material": "^5.8.4", 17 | "@nextui-org/react": "1.0.0-beta.9", 18 | "next": "12.2.4", 19 | "react": "18.2.0", 20 | "react-dom": "18.2.0" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "17.0.41", 24 | "@types/react": "18.0.16", 25 | "eslint": "8.19.0", 26 | "eslint-config-next": "12.2.4", 27 | "typescript": "4.7.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dashboard/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import {createTheme, NextUIProvider} from '@nextui-org/react'; 2 | 3 | function MyApp({Component, pageProps}) { 4 | return ( 5 | <NextUIProvider theme={ createTheme({ 6 | type: "dark" 7 | }) }> 8 | <Component { ...pageProps } /> 9 | </NextUIProvider> 10 | ); 11 | } 12 | 13 | export default MyApp; 14 | -------------------------------------------------------------------------------- /dashboard/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Document, {Head, Html, Main, NextScript} from 'next/document'; 3 | import {CssBaseline} from '@nextui-org/react'; 4 | 5 | class MyDocument extends Document { 6 | static async getInitialProps(ctx) { 7 | const initialProps = await Document.getInitialProps(ctx); 8 | return { 9 | ...initialProps, 10 | styles: React.Children.toArray([initialProps.styles]) 11 | }; 12 | } 13 | 14 | render() { 15 | return ( 16 | <Html lang="en"> 17 | <Head> 18 | { CssBaseline.flush() } 19 | <link rel="shortcut icon" 20 | href="https://github.com/SudhanPlayz/Discord-MusicBot/blob/v5/assets/logo.gif" 21 | type="image/png"/> 22 | </Head> 23 | <body> 24 | <Main/> 25 | <NextScript/> 26 | </body> 27 | </Html> 28 | ); 29 | } 30 | } 31 | 32 | export default MyDocument; 33 | -------------------------------------------------------------------------------- /dashboard/pages/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import {AudiotrackRounded, DnsRounded, PersonRounded, RocketLaunchRounded} from "@mui/icons-material" 3 | import Content from "../components/content"; 4 | import StatCard from "../components/StatCard"; 5 | import {useEffect, useState} from "react"; 6 | import {getDashboard, IDashboard} from "../utils/dashboard"; 7 | 8 | const Dashboard = (_props: any) => { 9 | const [data, setData] = useState<IDashboard | null>(null) 10 | 11 | useEffect(() => { 12 | getDashboard().then(setData); 13 | }, []); 14 | 15 | return (<Content> 16 | <Head> 17 | <title>Dashboard | Discord Music Bot</title> 18 | </Head> 19 | 20 | <h1>Dashboard</h1> 21 | <div style={ { 22 | display: 'flex', 23 | } }> 24 | <StatCard title='Commands Ran' amount={ data ? data.commandsRan : "Loading" } icon={ 25 | <RocketLaunchRounded fontSize="large"/> } 26 | /> 27 | <StatCard title='Users' amount={ data ? data.users : "Loading" } icon={ 28 | <PersonRounded fontSize="large"/> } 29 | /> 30 | <StatCard title='Servers' amount={ data ? data.servers : "Loading" } icon={ 31 | <DnsRounded fontSize="large"/> } 32 | /> 33 | 34 | <StatCard title='Songs Played' amount={ data ? data.songsPlayed : "Loading" } icon={ 35 | <AudiotrackRounded fontSize="large"/> } 36 | /> 37 | </div> 38 | </Content>) 39 | } 40 | 41 | export default Dashboard 42 | -------------------------------------------------------------------------------- /dashboard/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import {AudiotrackRounded, SettingsRounded, YouTube} from '@mui/icons-material' 2 | import {Button, Card, Container, Link, Text} from '@nextui-org/react' 3 | import Head from 'next/head' 4 | import {useEffect, useState} from 'react' 5 | import {getData, IData} from '../utils/data' 6 | 7 | const Home = (_props: any) => { 8 | const [data, setData] = useState<IData | null>(null) 9 | 10 | useEffect(() => { 11 | getData().then(res => { 12 | setData(res); 13 | if (res.redirect?.length) window.location.href = res.redirect; 14 | }) 15 | }, []) 16 | 17 | return ( 18 | <Container> 19 | <Head> 20 | <title>Discord Music Bot</title> 21 | </Head> 22 | <Container css={ { 23 | display: 'flex', 24 | alignItems: 'center', 25 | background: '$gray50', 26 | position: 'fixed', 27 | padding: '20px', 28 | minWidth: '100%', 29 | left: '0', 30 | top: '0', 31 | zIndex: '$5' 32 | } }> 33 | <Link css={ {fontSize: '$xl', fontWeight: '$semibold'} } href='/'> 34 | { data ? data.name : "Discord Music Bot" } 35 | </Link> 36 | <Link color='text' css={ {fontSize: '$lg', fontWeight: '$medium', marginLeft: '20px'} } href='#'> 37 | Home 38 | </Link> 39 | <Link color='text' css={ {fontSize: '$lg', fontWeight: '$medium', marginLeft: '20px'} } 40 | href='#features'> 41 | Features 42 | </Link> 43 | <Button onClick={ () => window.location.pathname = '/dashboard' } css={ {marginLeft: 'auto'} } auto shadow> 44 | Dashboard 45 | </Button> 46 | </Container> 47 | <Container style={ { 48 | textAlign: 'center', 49 | marginTop: '1rem', 50 | display: 'flex', 51 | height: '100vh', 52 | justifyContent: 'center', 53 | alignItems: 'center', 54 | flexDirection: 'column', 55 | } }> 56 | <Text h1 css={ {textGradient: "180deg, $blue600 -20%, $blue800 100%",} }>Discord Music Bot</Text> 57 | <Text h3 css={ {color: '$gray800'} }>An advanced discord music bot, supports Spotify, SoundCloud, 58 | YouTube with Shuffling, Volume Control and Web Dashboard!</Text> 59 | <Container css={ {display: 'flex', alignItems: 'center', justifyContent: 'center'} }> 60 | <Button color="primary" onClick={ () => window.location.pathname = '/login' } shadow style={ { 61 | marginTop: '1rem' 62 | } }>Login 63 | </Button> 64 | <Button color="primary" flat 65 | onClick={ () => window.open('https://github.com/SudhanPlayz/Discord-MusicBot') } style={ { 66 | marginTop: '1rem', 67 | marginLeft: '20px' 68 | } }>Github 69 | </Button> 70 | </Container> 71 | </Container> 72 | <Container css={ {display: 'flex', flexDirection: 'column', alignItems: 'center', minHeight: '60vh'} }> 73 | <Text h2>Features</Text> 74 | <Container css={ {display: 'flex', justifyContent: 'center', flexWrap: 'wrap'} }> 75 | <Card isHoverable css={ { 76 | display: 'flex', 77 | flexDirection: 'column', 78 | alignItems: 'center', 79 | margin: '10px', 80 | width: '300px', 81 | padding: '20px', 82 | textAlign: 'center' 83 | } }> 84 | <YouTube style={ {fontSize: '150px', color: '#3694FF'} }/> 85 | <Text h3>Spotify, Soundcloud, and Youtube support</Text> 86 | <Text css={ {color: '$gray800'} }> 87 | Use your spotify playlist, youtube videos, youtube playlists 88 | and much more using this bot 89 | </Text> 90 | </Card> 91 | <Card isHoverable css={ { 92 | display: 'flex', 93 | flexDirection: 'column', 94 | alignItems: 'center', 95 | margin: '10px', 96 | width: '300px', 97 | padding: '20px', 98 | textAlign: 'center' 99 | } }> 100 | <AudiotrackRounded style={ {fontSize: '150px', color: '#3694FF'} }/> 101 | <Text h3>Lag-free Music</Text> 102 | <Text css={ {color: '$gray800'} }> 103 | This bot will never lag when playing any song in a voice channel 104 | </Text> 105 | </Card> 106 | <Card isHoverable css={ { 107 | display: 'flex', 108 | flexDirection: 'column', 109 | alignItems: 'center', 110 | margin: '10px', 111 | width: '300px', 112 | padding: '20px', 113 | textAlign: 'center' 114 | } }> 115 | <SettingsRounded style={ {fontSize: '150px', color: '#3694FF'} }/> 116 | <Text h3>Server Settings</Text> 117 | <Text css={ {color: '$gray800'} }> 118 | Control your song looping song or queue, play or pause your song easily, or stop the bot 119 | completely. 120 | </Text> 121 | </Card> 122 | </Container> 123 | </Container> 124 | <Container css={ { 125 | marginTop: '30px', 126 | display: 'flex', 127 | flexDirection: 'column', 128 | alignItems: 'center', 129 | minHeight: '60vh' 130 | } }> 131 | <Text h2>What are you waiting for?</Text> 132 | <Button shadow size={ 'md' } css={ {marginTop: '5em'} }>Start Now</Button> 133 | </Container> 134 | </Container> 135 | ) 136 | } 137 | 138 | 139 | export default Home 140 | -------------------------------------------------------------------------------- /dashboard/pages/login.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import {useEffect} from "react"; 3 | 4 | const Login = (_props: any) => { 5 | useEffect(() => { 6 | window.location.href = "/api/login" 7 | }); 8 | 9 | return <> 10 | <Head> 11 | <title>Logging In | Discord Music Bot</title> 12 | </Head> 13 | <p>Redirecting you to login...</p> 14 | </> 15 | } 16 | 17 | 18 | export default Login 19 | -------------------------------------------------------------------------------- /dashboard/pages/logout.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useEffect } from "react"; 3 | 4 | export default function Logout(_props: any) { 5 | useEffect(() => { 6 | window.location.href = "/api/logout" 7 | }, []); 8 | 9 | return <> 10 | <Head> 11 | <title>Logging Out | Discord Music Bot</title> 12 | </Head> 13 | <p>Redirecting you to logout...</p> 14 | </> 15 | } 16 | -------------------------------------------------------------------------------- /dashboard/pages/servers.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Content from "../components/content"; 3 | import Server from "../components/server"; 4 | 5 | export default function Servers(_props: any) { 6 | return <Content> 7 | <Head> 8 | <title>Servers | Discord Music Bot</title> 9 | </Head> 10 | <h1>Select a server</h1> 11 | <div style={ { 12 | display: 'flex', 13 | } }> 14 | <Server 15 | icon="https://cdn.discordapp.com/icons/855346696258060338/93317b7b5c163ecaa21ed16db455066f.png?size=4096" 16 | name="Coding with amogus" id=";-;"/> 17 | <Server 18 | icon="https://cdn.discordapp.com/icons/855346696258060338/93317b7b5c163ecaa21ed16db455066f.png?size=4096" 19 | name="Coding with amogus" id=";-;"/> 20 | <Server 21 | icon="https://cdn.discordapp.com/icons/855346696258060338/93317b7b5c163ecaa21ed16db455066f.png?size=4096" 22 | name="Coding with amogus" id=";-;"/> 23 | </div> 24 | </Content> 25 | } -------------------------------------------------------------------------------- /dashboard/pages/servers/[id].tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Content from "../../components/content"; 3 | 4 | export default function Server(_props: any) { 5 | let server = { 6 | name: "Amongus", 7 | id: "137984839", 8 | icon: "https://cdn.discordapp.com/icons/855346696258060338/93317b7b5c163ecaa21ed16db455066f.png?size=4096", 9 | loop: { 10 | song: true, 11 | queue: false 12 | }, 13 | queue: [], 14 | playing: { 15 | title: "nice song", 16 | duration: 4000000000, 17 | currentTime: 3000 18 | } 19 | } 20 | 21 | return ( 22 | <Content> 23 | <Head> 24 | <title>{ server.name }</title> 25 | </Head> 26 | <h1>{ server.name }</h1> 27 | </Content> 28 | ); 29 | } -------------------------------------------------------------------------------- /dashboard/svgs/AudiotrackRounded.svg: -------------------------------------------------------------------------------- 1 | <svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-1shn170" 2 | focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="AudiotrackRoundedIcon" tabindex="-1" 3 | title="AudiotrackRounded"> 4 | <path d="M12 5v8.55c-.94-.54-2.1-.75-3.33-.32-1.34.48-2.37 1.67-2.61 3.07-.46 2.74 1.86 5.08 4.59 4.65 1.96-.31 3.35-2.11 3.35-4.1V7h2c1.1 0 2-.9 2-2s-.9-2-2-2h-2c-1.1 0-2 .9-2 2z"></path> 5 | </svg> -------------------------------------------------------------------------------- /dashboard/svgs/DnsRounded.svg: -------------------------------------------------------------------------------- 1 | <svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-1shn170" 2 | focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="DnsRoundedIcon" tabindex="-1" 3 | title="DnsRounded"> 4 | <path d="M19 13H5c-1.1 0-2 .9-2 2v4c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-4c0-1.1-.9-2-2-2zM7 19c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM19 3H5c-1.1 0-2 .9-2 2v4c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM7 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path> 5 | </svg> -------------------------------------------------------------------------------- /dashboard/svgs/PersonRounded.svg: -------------------------------------------------------------------------------- 1 | <svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-1shn170" 2 | focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="PersonRoundedIcon" tabindex="-1" 3 | title="PersonRounded"> 4 | <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v1c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-1c0-2.66-5.33-4-8-4z"></path> 5 | </svg> -------------------------------------------------------------------------------- /dashboard/svgs/RocketLaunchRounded.svg: -------------------------------------------------------------------------------- 1 | <svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-1shn170" 2 | focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="RocketLaunchRoundedIcon" tabindex="-1" 3 | title="RocketLaunchRounded"> 4 | <path d="M9.19 6.35c-2.04 2.29-3.44 5.58-3.57 5.89l-2.26-.97c-.65-.28-.81-1.13-.31-1.63l3.01-3.01c.47-.47 1.15-.68 1.81-.55l1.32.27zm1.49 10.16c.3.3.74.38 1.12.2 1.16-.54 3.65-1.81 5.26-3.42 4.59-4.59 4.63-8.33 4.36-9.93-.07-.4-.39-.72-.79-.79-1.6-.27-5.34-.23-9.93 4.36-1.61 1.61-2.87 4.1-3.42 5.26-.18.38-.09.83.2 1.12l3.2 3.2zm6.97-1.7c-2.29 2.04-5.58 3.44-5.89 3.57l.97 2.26c.28.65 1.13.81 1.63.31l3.01-3.01c.47-.47.68-1.15.55-1.81l-.27-1.32zm-8.71 2.6c.2 1.06-.15 2.04-.82 2.71-.77.77-3.16 1.34-4.71 1.64-.69.13-1.3-.48-1.17-1.17.3-1.55.86-3.94 1.64-4.71.67-.67 1.65-1.02 2.71-.82 1.17.22 2.13 1.18 2.35 2.35zM13 9c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2z"></path> 5 | </svg> -------------------------------------------------------------------------------- /dashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "incremental": true, 15 | "esModuleInterop": true, 16 | "module": "esnext", 17 | "moduleResolution": "Node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /dashboard/utils/dashboard.ts: -------------------------------------------------------------------------------- 1 | export interface IDashboard { 2 | commandsRan: number; 3 | users: number; 4 | servers: number; 5 | songsPlayed: number; 6 | } 7 | 8 | export const getDashboard: () => Promise<IDashboard> = () => { 9 | return new Promise(async (resolve, _reject) => { 10 | let data = await fetch("/api/dashboard", { 11 | method: "GET", 12 | credentials: "same-origin", 13 | }); 14 | let json = await data.json(); 15 | resolve(json); 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /dashboard/utils/data.ts: -------------------------------------------------------------------------------- 1 | export interface ICommand { 2 | name: string; 3 | description: string; 4 | } 5 | 6 | export interface IData { 7 | name: string; 8 | version: string; 9 | commands: ICommand[]; 10 | inviteURL: string; 11 | loggedIn: boolean | null; 12 | redirect: string | null; 13 | } 14 | 15 | export const getData: () => Promise<IData> = () => { 16 | return new Promise(async (resolve, _reject) => { 17 | let data = await fetch("/api/data", { 18 | method: "GET" 19 | }) 20 | resolve(await data.json()) 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /deploy/deployGlobal.js: -------------------------------------------------------------------------------- 1 | const { REST } = require("@discordjs/rest"); 2 | const { Routes } = require("discord-api-types/v9"); 3 | const getConfig = require("../util/getConfig"); 4 | const LoadCommands = require("../util/loadCommands"); 5 | 6 | (async () => { 7 | const config = await getConfig(); 8 | const rest = new REST({ version: "9" }).setToken(config.token); 9 | const commands = await LoadCommands().then((cmds) => { 10 | return [].concat(cmds.slash).concat(cmds.context); 11 | }); 12 | 13 | console.log("Deploying commands to global..."); 14 | await rest 15 | .put(Routes.applicationCommands(config.clientId), { 16 | body: commands, 17 | }) 18 | .catch(console.log); 19 | console.log("Successfully deployed commands!"); 20 | })(); 21 | -------------------------------------------------------------------------------- /deploy/deployGuild.js: -------------------------------------------------------------------------------- 1 | const readline = require("readline"); 2 | const { REST } = require("@discordjs/rest"); 3 | const { Routes } = require("discord-api-types/v9"); 4 | const getConfig = require("../util/getConfig"); 5 | const LoadCommands = require("../util/loadCommands"); 6 | 7 | const rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout, 10 | }); 11 | 12 | (async () => { 13 | const config = await getConfig(); 14 | const rest = new REST({ version: "9" }).setToken(config.token); 15 | const commands = await LoadCommands().then((cmds) => { 16 | return [].concat(cmds.slash).concat(cmds.context); 17 | }); 18 | 19 | rl.question( 20 | "Enter the guild id you wanted to deploy commands: ", 21 | async (guild) => { 22 | console.log("Deploying commands to guild..."); 23 | await rest 24 | .put(Routes.applicationGuildCommands(config.clientId, guild), { 25 | body: commands, 26 | }) 27 | .catch(console.log); 28 | console.log("Successfully deployed commands!"); 29 | rl.close(); 30 | }, 31 | ); 32 | })(); 33 | -------------------------------------------------------------------------------- /deploy/destroyGlobal.js: -------------------------------------------------------------------------------- 1 | const { Client, Intents } = require("discord.js"); 2 | const client = new Client({ 3 | intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES], 4 | }); 5 | const config = require("../config"); 6 | 7 | client.login(config.token); 8 | 9 | client.on("ready", async () => { 10 | const commands = await client.application.commands.fetch(); 11 | 12 | if (commands.size === 0) { 13 | console.log("Could not find any global commands."); 14 | process.exit(); 15 | } 16 | 17 | let deletedCount = 0; 18 | 19 | commands.forEach(async (command) => { 20 | await client.application.commands.delete(command.id); 21 | console.log(`Slash Command with ID ${command.id} has been deleted.`); 22 | deletedCount++; 23 | 24 | if (deletedCount === commands.size) { 25 | console.log(`Successfully deleted all global slash commands.`); 26 | process.exit(); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /deploy/destroyGuild.js: -------------------------------------------------------------------------------- 1 | //Deletes every commands from every server yikes!!1!!11!! 2 | const readline = require("readline"); 3 | const { REST } = require("@discordjs/rest"); 4 | const { Routes } = require("discord-api-types/v9"); 5 | const getConfig = require("../util/getConfig"); 6 | 7 | const rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout, 10 | }); 11 | 12 | (async () => { 13 | const config = await getConfig(); 14 | const rest = new REST({ version: "9" }).setToken(config.token); 15 | 16 | if (!process.argv.includes("--global")) { 17 | rl.question( 18 | "Enter the guild id you wanted to delete commands: ", 19 | async (guild) => { 20 | console.log("Evil bot has been started to delete commands..."); 21 | await rest 22 | .put(Routes.applicationGuildCommands(config.clientId, guild), { 23 | body: [], 24 | }) 25 | .catch(console.log); 26 | console.log("Evil bot has done the deed, exiting..."); 27 | rl.close(); 28 | }, 29 | ); 30 | } else { 31 | console.log("Evil bot has been started to delete global commands..."); 32 | await rest 33 | .put(Routes.applicationCommands(config.clientId), { 34 | body: [], 35 | }) 36 | .catch(console.log); 37 | console.log("Evil bot has done the deed, exiting..."); 38 | process.exit(); 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | discord-musicbot: 5 | build: . 6 | image: discord-musicbot:latest 7 | container_name: discord-musicbot 8 | restart: unless-stopped 9 | networks: 10 | - lavalink-net 11 | depends_on: 12 | - lavalink 13 | volumes: 14 | - ./config.js:/usr/src/app/config.js:ro 15 | 16 | lavalink: 17 | image: fredboat/lavalink:3.7.12 18 | container_name: music-lavalink 19 | hostname: lavalink 20 | restart: unless-stopped 21 | networks: 22 | - lavalink-net 23 | volumes: 24 | - ./docker/application.yml:/opt/Lavalink/application.yml:ro 25 | 26 | networks: 27 | lavalink-net: 28 | -------------------------------------------------------------------------------- /docker/application.yml: -------------------------------------------------------------------------------- 1 | server: # REST and WS server 2 | port: 2333 3 | address: 0.0.0.0 4 | plugins: 5 | youtube: 6 | enabled: true 7 | allowSearch: true 8 | allowDirectVideoIds: true 9 | allowDirectPlaylistIds: true 10 | clients: ["MUSIC", "ANDROID", "WEB"] 11 | # name: # Name of the plugin 12 | # some_key: some_value # Some key-value pair for the plugin 13 | # another_key: another_value 14 | lavalink: 15 | plugins: 16 | - dependency: "dev.lavalink.youtube:youtube-plugin:1.3.0" 17 | repository: "https://maven.lavalink.dev/releases" 18 | pluginsDir: "./plugins" 19 | server: 20 | password: "youshallnotpass" 21 | sources: 22 | youtube: false 23 | bandcamp: true 24 | soundcloud: true 25 | twitch: true 26 | vimeo: true 27 | http: true 28 | local: false 29 | filters: # All filters are enabled by default 30 | volume: true 31 | equalizer: true 32 | karaoke: true 33 | timescale: true 34 | tremolo: true 35 | vibrato: true 36 | distortion: true 37 | rotation: true 38 | channelMix: true 39 | lowPass: true 40 | bufferDurationMs: 400 # The duration of the NAS buffer. Higher values fare better against longer GC pauses. Duration <= 0 to disable JDA-NAS. Minimum of 40ms, lower values may introduce pauses. 41 | frameBufferDurationMs: 5000 # How many milliseconds of audio to keep buffered 42 | opusEncodingQuality: 10 # Opus encoder quality. Valid values range from 0 to 10, where 10 is best quality but is the most expensive on the CPU. 43 | resamplingQuality: LOW # Quality of resampling operations. Valid values are LOW, MEDIUM and HIGH, where HIGH uses the most CPU. 44 | trackStuckThresholdMs: 10000 # The threshold for how long a track can be stuck. A track is stuck if does not return any audio data. 45 | useSeekGhosting: true # Seek ghosting is the effect where whilst a seek is in progress, the audio buffer is read from until empty, or until seek is ready. 46 | youtubePlaylistLoadLimit: 6 # Number of pages at 100 each 47 | playerUpdateInterval: 5 # How frequently to send player updates to clients, in seconds 48 | youtubeSearchEnabled: true 49 | soundcloudSearchEnabled: true 50 | gc-warnings: true 51 | #ratelimit: 52 | #ipBlocks: ["1.0.0.0/8", "..."] # list of ip blocks 53 | #excludedIps: ["...", "..."] # ips which should be explicit excluded from usage by lavalink 54 | #strategy: "RotateOnBan" # RotateOnBan | LoadBalance | NanoSwitch | RotatingNanoSwitch 55 | #searchTriggersFail: true # Whether a search 429 should trigger marking the ip as failing 56 | #retryLimit: -1 # -1 = use default lavaplayer value | 0 = infinity | >0 = retry will happen this numbers times 57 | #youtubeConfig: # Required for avoiding all age restrictions by YouTube, some restricted videos still can be played without. 58 | #email: "" # Email of Google account 59 | #password: "" # Password of Google account 60 | #httpConfig: # Useful for blocking bad-actors from ip-grabbing your music node and attacking it, this way only the http proxy will be attacked 61 | #proxyHost: "localhost" # Hostname of the proxy, (ip or domain) 62 | #proxyPort: 3128 # Proxy port, 3128 is the default for squidProxy 63 | #proxyUser: "" # Optional user for basic authentication fields, leave blank if you don't use basic auth 64 | #proxyPassword: "" # Password for basic authentication 65 | 66 | metrics: 67 | prometheus: 68 | enabled: false 69 | endpoint: /metrics 70 | 71 | sentry: 72 | dsn: "" 73 | environment: "" 74 | # tags: 75 | # some_key: some_value 76 | # another_key: another_value 77 | 78 | logging: 79 | file: 80 | path: ./logs/ 81 | 82 | level: 83 | root: INFO 84 | lavalink: INFO 85 | 86 | request: 87 | enabled: true 88 | includeClientInfo: true 89 | includeHeaders: false 90 | includeQueryString: true 91 | includePayload: true 92 | maxPayloadLength: 10000 93 | 94 | 95 | logback: 96 | rollingpolicy: 97 | max-file-size: 1GB 98 | max-history: 30 99 | -------------------------------------------------------------------------------- /events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const Controller = require("../util/Controller"); 2 | const yt = require("youtube-sr").default; 3 | 4 | /** 5 | * 6 | * @param {import("../lib/DiscordMusicBot")} client 7 | * @param {import("discord.js").Interaction}interaction 8 | */ 9 | module.exports = async (client, interaction) => { 10 | if (interaction.isCommand()) { 11 | let command = client.slashCommands.find( 12 | (x) => x.name == interaction.commandName, 13 | ); 14 | if (!command || !command.run) { 15 | return interaction.reply( 16 | "Sorry the command you used doesn't have any run function", 17 | ); 18 | } 19 | client.commandsRan++; 20 | command.run(client, interaction, interaction.options); 21 | return; 22 | } 23 | 24 | if (interaction.isContextMenu()) { 25 | let command = client.contextCommands.find( 26 | (x) => x.command.name == interaction.commandName, 27 | ); 28 | if (!command || !command.run) { 29 | return interaction.reply( 30 | "Sorry the command you used doesn't have any run function", 31 | ); 32 | } 33 | client.commandsRan++; 34 | command.run(client, interaction, interaction.options); 35 | return; 36 | } 37 | 38 | if (interaction.isButton()) { 39 | if (interaction.customId.startsWith("controller")) { 40 | Controller(client, interaction); 41 | } 42 | } 43 | 44 | if (interaction.isAutocomplete()) { 45 | const url = interaction.options.getString("query") 46 | if (url === "") return; 47 | 48 | const match = [ 49 | /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/, 50 | /^(?:spotify:|https:\/\/[a-z]+\.spotify\.com\/(track\/|user\/(.*)\/playlist\/|playlist\/))(.*)$/, 51 | /^https?:\/\/(?:www\.)?deezer\.com\/[a-z]+\/(track|album|playlist)\/(\d+)$/, 52 | /^(?:(https?):\/\/)?(?:(?:www|m)\.)?(soundcloud\.com|snd\.sc)\/(.*)$/, 53 | /(?:https:\/\/music\.apple\.com\/)(?:.+)?(artist|album|music-video|playlist)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)/ 54 | ].some(function (match) { 55 | return match.test(url) == true; 56 | }); 57 | 58 | async function checkRegex() { 59 | if (match == true) { 60 | let choice = [] 61 | choice.push({ name: url, value: url }) 62 | await interaction.respond(choice).catch(() => { }); 63 | } 64 | } 65 | 66 | const Random = "ytsearch"[Math.floor(Math.random() * "ytsearch".length)]; 67 | 68 | if (interaction.commandName == "play") { 69 | checkRegex() 70 | let choice = [] 71 | await yt.search(url || Random, { safeSearch: false, limit: 25 }).then(result => { 72 | result.forEach(x => { choice.push({ name: x.title, value: x.url }) }) 73 | }); 74 | return await interaction.respond(choice).catch(() => { }); 75 | } else if (result.loadType === "LOAD_FAILED" || "NO_MATCHES") 76 | return; 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /events/messageCreate.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed, MessageButton, MessageActionRow } = require("discord.js"); 2 | const { get } = require("../util/db"); 3 | const { platform, arch } = require("os"); 4 | 5 | module.exports = async (client, message) => { 6 | const refront = `^<@!?${client.user.id}>`; 7 | const mention = new RegExp(refront + "quot;); 8 | const debugIdMention = new RegExp(refront + " debug-id ([^\\s]+)"); 9 | const invite = `https://discord.com/oauth2/authorize?client_id=${ 10 | client.config.clientId 11 | }&permissions=${client.config.inviteScopes.toString().replace(/,/g, "%20")}`; 12 | 13 | const buttons = new MessageActionRow().addComponents( 14 | new MessageButton().setStyle("LINK").setLabel("Invite me").setURL(invite), 15 | new MessageButton() 16 | .setStyle("LINK") 17 | .setLabel("Support server") 18 | .setURL(`${client.config.supportServer}`) 19 | ); 20 | 21 | if (message.content.match(mention)) { 22 | const mentionEmbed = new MessageEmbed() 23 | .setColor(client.config.embedColor) 24 | .setDescription( 25 | `My prefix on this server is \`/\` (Slash Command).\nTo get started you can type \`/help\` to see all my commands.\nIf you can't see it, Please [re-invite](invite) me with the correct permissions.` 26 | ); 27 | 28 | message.channel.send({ 29 | embeds: [mentionEmbed], 30 | components: [buttons], 31 | }); 32 | } 33 | 34 | if (["750335181285490760"].includes(message.author.id)) { 35 | const m = message.content?.match(debugIdMention); 36 | const r = m[1]?.length ? get("global")?.[m[1]] : null; 37 | message.channel.send(r?.length ? r : platform() + " " + arch()); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /events/messageDelete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * On messageDelete events, adds the message to a WeakSet within the bot's client 3 | * allowing for the bot to easily see which messages have been deleted asynchronously 4 | * during the run time of the bot 5 | * @param {Client} client 6 | * @param {Message} message 7 | */ 8 | 9 | module.exports = async (client, message) => { 10 | if (!client.isMessageDeleted(message)) { 11 | client.markMessageAsDeleted(message); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /events/raw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {import("../lib/DiscordMusicBot")} client 4 | * @param {*} data 5 | */ 6 | module.exports = (client, data) => { 7 | client.manager.updateVoiceState(data); 8 | }; 9 | -------------------------------------------------------------------------------- /events/ready.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {import("../lib/DiscordMusicBot")} client 4 | */ 5 | module.exports = (client) => { 6 | client.manager.init(client.user.id); 7 | client.user.setPresence(client.config.presence); 8 | client.log("Successfully Logged in as " + client.user.tag); 9 | }; 10 | -------------------------------------------------------------------------------- /events/voiceStateUpdate.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require("discord.js"); 2 | 3 | /** 4 | * 5 | * @param {import("../lib/DiscordMusicBot")} client 6 | * @param {import("discord.js").VoiceState} oldState 7 | * @param {import("discord.js").VoiceState} newState 8 | * @returns {Promise<void>} 9 | */ 10 | module.exports = async (client, oldState, newState) => { 11 | // get guild and player 12 | let guildId = newState.guild.id; 13 | const player = client.manager.get(guildId); 14 | 15 | // check if the bot is active (playing, paused or empty does not matter (return otherwise) 16 | if (!player || player.state !== "CONNECTED") { 17 | return; 18 | } 19 | 20 | // prepreoces the data 21 | const stateChange = {}; 22 | // get the state change 23 | if (oldState.channel === null && newState.channel !== null) { 24 | stateChange.type = "JOIN"; 25 | } 26 | if (oldState.channel !== null && newState.channel === null) { 27 | stateChange.type = "LEAVE"; 28 | } 29 | if (oldState.channel !== null && newState.channel !== null) { 30 | stateChange.type = "MOVE"; 31 | } 32 | if (oldState.channel === null && newState.channel === null) { 33 | return; 34 | } // you never know, right 35 | if ( 36 | newState.serverMute == true && 37 | oldState.serverMute == false && 38 | newState.id === client.config.clientId 39 | ) { 40 | return player.pause(true); 41 | } 42 | if ( 43 | newState.serverMute == false && 44 | oldState.serverMute == true && 45 | newState.id === client.config.clientId 46 | ) { 47 | return player.pause(false); 48 | } 49 | // move check first as it changes type 50 | if (stateChange.type === "MOVE") { 51 | if (oldState.channel.id === player.voiceChannel) { 52 | stateChange.type = "LEAVE"; 53 | } 54 | if (newState.channel.id === player.voiceChannel) { 55 | stateChange.type = "JOIN"; 56 | } 57 | } 58 | // double triggered on purpose for MOVE events 59 | if (stateChange.type === "JOIN") { 60 | stateChange.channel = newState.channel; 61 | } 62 | if (stateChange.type === "LEAVE") { 63 | stateChange.channel = oldState.channel; 64 | } 65 | 66 | // check if the bot's voice channel is involved (return otherwise) 67 | if (!stateChange.channel || stateChange.channel.id !== player.voiceChannel) { 68 | return; 69 | } 70 | player.prevMembers = player.members 71 | player.members = stateChange.channel.members.filter(member => !member.user.bot).size; 72 | switch (stateChange.type) { 73 | case "JOIN": 74 | if (player.get("autoPause") === true) { 75 | var members = stateChange.channel.members.filter(member => !member.user.bot).size 76 | if (members === 1 && player.paused && members !== player.prevMembers){ 77 | player.pause(false); 78 | let playerResumed = new MessageEmbed() 79 | .setColor(client.config.embedColor) 80 | .setTitle(`Resumed!`, client.config.iconURL) 81 | .setDescription( 82 | `Playing [${ player.queue.current.title }](${ player.queue.current.uri })`, 83 | ) 84 | .setFooter({ text: `The current song has been resumed.` }); 85 | 86 | let resumeMessage = await client.channels.cache 87 | .get(player.textChannel) 88 | .send({ embeds: [playerResumed] }); 89 | player.setResumeMessage(client, resumeMessage); 90 | 91 | setTimeout(() => { 92 | if (!client.isMessageDeleted(resumeMessage)) { 93 | resumeMessage.delete(); 94 | client.markMessageAsDeleted(resumeMessage); 95 | } 96 | }, 5000); 97 | } 98 | } 99 | break; 100 | case "LEAVE": 101 | var members = stateChange.channel.members.filter(member => !member.user.bot).size 102 | const twentyFourSeven = player.get("twentyFourSeven"); 103 | if (player.get("autoPause") === true && player.get("autoLeave") === false) { 104 | if (members === 0 && !player.paused && player.playing) { 105 | player.pause(true); 106 | 107 | let playerPaused = new MessageEmbed() 108 | .setColor(client.config.embedColor) 109 | .setTitle(`Paused!`, client.config.iconURL) 110 | .setFooter({ 111 | text: `The current song has been paused because theres no one in the voice channel.`, 112 | }); 113 | 114 | let pausedMessage = await client.channels.cache 115 | .get(player.textChannel) 116 | .send({ embeds: [playerPaused] }); 117 | player.setPausedMessage(client, pausedMessage); 118 | } 119 | }else if (player.get("autoLeave") === true && player.get("autoPause") === false) { 120 | if (members === 0) { 121 | if (twentyFourSeven){ 122 | setTimeout(async () => { 123 | var members = stateChange.channel.members.filter(member => !member.user.bot).size 124 | if (members === 0 && player.state !== 'DISCONNECTED'){ 125 | let leftEmbed = new MessageEmbed() 126 | .setColor(client.config.embedColor) 127 | .setAuthor({ 128 | name: "Disconnected!", 129 | iconURL: client.config.iconURL, 130 | }) 131 | .setFooter({ text: "Left because there is no one left in the voice channel." }) 132 | .setTimestamp(); 133 | let Disconnected = await client.channels.cache 134 | .get(player.textChannel) 135 | .send({ embeds: [leftEmbed] }); 136 | setTimeout(() => Disconnected.delete(true), 5000); 137 | player.queue.clear(); 138 | player.destroy(); 139 | player.set("autoQueue", false); 140 | } 141 | }, client.config.disconnectTime); 142 | } else{ 143 | let leftEmbed = new MessageEmbed() 144 | .setColor(client.config.embedColor) 145 | .setAuthor({ 146 | name: "Disconnected!", 147 | iconURL: client.config.iconURL, 148 | }) 149 | .setFooter({ text: "Left because there is no one left in the voice channel." }) 150 | .setTimestamp(); 151 | let Disconnected = await client.channels.cache 152 | .get(player.textChannel) 153 | .send({ embeds: [leftEmbed] }); 154 | setTimeout(() => Disconnected.delete(true), 5000); 155 | player.destroy(); 156 | } 157 | 158 | } 159 | }else if (player.get("autoLeave") === true && player.get("autoPause") === true){ 160 | if (members === 0 && !player.paused && player.playing && twentyFourSeven) { 161 | player.pause(true); 162 | 163 | let playerPaused = new MessageEmbed() 164 | .setColor(client.config.embedColor) 165 | .setTitle(`Paused!`, client.config.iconURL) 166 | .setFooter({ 167 | text: `The current song has been paused because theres no one in the voice channel.`, 168 | }); 169 | 170 | let pausedMessage = await client.channels.cache 171 | .get(player.textChannel) 172 | .send({ embeds: [playerPaused] }); 173 | player.setPausedMessage(client, pausedMessage); 174 | setTimeout(async () => { 175 | var members = stateChange.channel.members.filter(member => !member.user.bot).size 176 | if (members === 0 && player.state !== 'DISCONNECTED'){ 177 | let leftEmbed = new MessageEmbed() 178 | .setColor(client.config.embedColor) 179 | .setAuthor({ 180 | name: "Disconnected!", 181 | iconURL: client.config.iconURL, 182 | }) 183 | .setFooter({ text: "Left because there is no one left in the voice channel." }) 184 | .setTimestamp(); 185 | let Disconnected = await client.channels.cache 186 | .get(player.textChannel) 187 | .send({ embeds: [leftEmbed] }); 188 | setTimeout(() => Disconnected.delete(true), 5000); 189 | pausedMessage.delete(true); 190 | player.queue.clear(); 191 | player.destroy(); 192 | player.set("autoQueue", false); 193 | } 194 | }, client.config.disconnectTime); 195 | }else{ 196 | if (members === 0 && player.state !== 'DISCONNECTED'){ 197 | let leftEmbed = new MessageEmbed() 198 | .setColor(client.config.embedColor) 199 | .setAuthor({ 200 | name: "Disconnected!", 201 | iconURL: client.config.iconURL, 202 | }) 203 | .setFooter({ text: "Left because there is no one left in the voice channel." }) 204 | .setTimestamp(); 205 | let Disconnected = await client.channels.cache 206 | .get(player.textChannel) 207 | .send({ embeds: [leftEmbed] }); 208 | setTimeout(() => Disconnected.delete(true), 5000); 209 | player.destroy(); 210 | } 211 | } 212 | } 213 | break; 214 | } 215 | }; 216 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | //JotaroKujo0525 note, this is a deed that i should've done a long time ago 2 | require('dotenv').config() 3 | 4 | const DiscordMusicBot = require("./lib/DiscordMusicBot"); 5 | const { exec } = require("child_process"); 6 | 7 | if (process.env.REPL_ID) { 8 | console.log("Replit system detected, initiating special `unhandledRejection` event listener.") 9 | process.on('unhandledRejection', (reason, promise) => { 10 | promise.catch((err) => { 11 | if (err.status === 429) { 12 | console.log("something went wrong whilst trying to connect to discord gateway, resetting..."); 13 | exec("kill 1"); 14 | } 15 | }); 16 | }); 17 | } 18 | 19 | const client = new DiscordMusicBot(); 20 | 21 | console.log("Make sure to fill in the config.js before starting the bot."); 22 | 23 | const getClient = () => client; 24 | 25 | module.exports = { 26 | getClient, 27 | }; 28 | -------------------------------------------------------------------------------- /kickstartReplit.sh: -------------------------------------------------------------------------------- 1 | echo Kickstarting replit 2 | echo Please make sure to fill config.js before running this script 3 | npm i 4 | echo Do you want me to deploy slash commands for you? y/n 5 | 6 | read slashanswer 7 | 8 | if [ "$slashanswer" == "y" ]; then 9 | echo Deploying slash commands 10 | npm run deploy 11 | fi 12 | 13 | node index.js 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/EpicPlayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "discord.js"; 2 | import { Player } from "erela.js"; 3 | import DiscordMusicBot from "./DiscordMusicBot"; 4 | 5 | declare class EpicPlayer extends Player { 6 | private resumeMessage: Message<boolean>; 7 | private pausedMessage: Message<boolean>; 8 | private nowPlayingMessage: Message<boolean>; 9 | 10 | // add more undefined member and method types here 11 | 12 | public setResumeMessage(client: DiscordMusicBot, message: Message): Message<boolean>; 13 | public setPausedMessage(client: DiscordMusicBot, message: Message): Message<boolean>; 14 | public setNowplayingMessage(client: DiscordMusicBot, message: Message): Message<boolean>; 15 | } 16 | 17 | export default EpicPlayer; 18 | -------------------------------------------------------------------------------- /lib/EpicPlayer.js: -------------------------------------------------------------------------------- 1 | const { Message } = require("discord.js"); 2 | const { Structure } = require("erela.js"); 3 | const Client = require("./DiscordMusicBot"); 4 | 5 | Structure.extend( 6 | "Player", 7 | (Player) => 8 | class extends Player { 9 | constructor(...props) { 10 | super(...props); 11 | this.twentyFourSeven = false; 12 | } 13 | 14 | /** 15 | * Set's (maps) the client's resume message so it can be deleted afterwards 16 | * @param {Client} client 17 | * @param {Message} message 18 | * @returns the Set Message 19 | */ 20 | setResumeMessage(client, message) { 21 | if (this.pausedMessage && !client.isMessageDeleted(this.pausedMessage)) { 22 | this.pausedMessage.delete(); 23 | client.markMessageAsDeleted(this.pausedMessage); 24 | } 25 | return (this.resumeMessage = message); 26 | } 27 | 28 | /** 29 | * Set's (maps) the client's paused message so it can be deleted afterwards 30 | * @param {Client} client 31 | * @param {Message} message 32 | * @returns 33 | */ 34 | setPausedMessage(client, message) { 35 | if (this.resumeMessage && !client.isMessageDeleted(this.resumeMessage)) { 36 | this.resumeMessage.delete(); 37 | client.markMessageAsDeleted(this.resumeMessage); 38 | } 39 | return (this.pausedMessage = message); 40 | } 41 | 42 | /** 43 | * Set's (maps) the client's now playing message so it can be deleted afterwards 44 | * @param {Client} client 45 | * @param {Message} message 46 | * @returns 47 | */ 48 | setNowplayingMessage(client, message) { 49 | if (this.nowPlayingMessage && !client.isMessageDeleted(this.nowPlayingMessage)) { 50 | this.nowPlayingMessage.delete(); 51 | client.markMessageAsDeleted(this.nowPlayingMessage); 52 | } 53 | return (this.nowPlayingMessage = message); 54 | } 55 | }, 56 | ); 57 | -------------------------------------------------------------------------------- /lib/Logger.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const colors = require("colors"); 3 | 4 | class Logger { 5 | constructor(file) { 6 | this.logger = winston.createLogger({ 7 | transports: [new winston.transports.File({ filename: file })], 8 | }); 9 | } 10 | 11 | log(Text) { 12 | let d = new Date(); 13 | this.logger.log({ 14 | level: "info", 15 | message: "info: " + Text, 16 | }); 17 | console.log( 18 | colors.gray( 19 | `[${ d.getDate() }:${ d.getMonth() }:${ d.getFullYear() } - ${ d.getHours() }:${ d.getMinutes() }]`, 20 | ) + colors.green(" | " + Text), 21 | ); 22 | } 23 | 24 | warn(Text) { 25 | let d = new Date(); 26 | this.logger.log({ 27 | level: "warn", 28 | message: "warn: " + Text, 29 | }); 30 | console.log( 31 | colors.gray( 32 | `[${ d.getDate() }:${ d.getMonth() }:${ d.getFullYear() } - ${ d.getHours() }:${ d.getMinutes() }]`, 33 | ) + colors.yellow(" | " + Text), 34 | ); 35 | } 36 | 37 | error(Text) { 38 | let d = new Date(); 39 | this.logger.log({ 40 | level: "error", 41 | message: "error: " + Text, 42 | }); 43 | console.log( 44 | colors.gray( 45 | `[${ d.getDate() }:${ d.getMonth() }:${ d.getFullYear() } - ${ d.getHours() }:${ d.getMinutes() }]`, 46 | ) + colors.red(" | " + Text), 47 | ); 48 | } 49 | } 50 | 51 | module.exports = Logger; 52 | -------------------------------------------------------------------------------- /lib/SlashCommand.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require("@discordjs/builders"); 2 | const { 3 | CommandInteraction, 4 | CommandInteractionOptionResolver, 5 | } = require("discord.js"); 6 | const DiscordMusicBot = require("./DiscordMusicBot"); 7 | 8 | class SlashCommand extends SlashCommandBuilder { 9 | constructor() { 10 | super(); 11 | this.type = 1; 12 | return this; 13 | } 14 | 15 | /** 16 | * Set Execution of the command 17 | * @param {(client: DiscordMusicBot, interaction: CommandInteraction, options: CommandInteractionOptionResolver) => Promise<any>} callback 18 | */ 19 | setRun(callback) { 20 | this.run = callback; 21 | return this; 22 | } 23 | } 24 | 25 | module.exports = SlashCommand; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-musicbot", 3 | "version": "5.0.0-beta", 4 | "description": "Very simple discord music bot with the discord.js with Song Name playing. It can able to play music with the song name", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index", 8 | "rm-logs": "rm logs.log", 9 | "lint": "prettier --check .", 10 | "deploy": "node deploy/deployGlobal", 11 | "guild": "node deploy/deployGuild", 12 | "destroy-guild": "node deploy/destroyGuild", 13 | "destroy-global": "node deploy/destroyGlobal", 14 | "ddev": "cd dashboard && npm run dev", 15 | "dbuild": "cd dashboard && npm run build && npm run export", 16 | "dstart": "cd dashboard && npm run start" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/SudhanPlayz/Discord-MusicBot.git" 21 | }, 22 | "engines": { 23 | "node": ">=16.x <=16.16" 24 | }, 25 | "keywords": [ 26 | "discord", 27 | "discord-bot", 28 | "discord-musicbot", 29 | "music", 30 | "discord-music" 31 | ], 32 | "authors": [ 33 | "SudhanPlayz", 34 | "DarrenOfficial" 35 | ], 36 | "license": "Apache-2.0", 37 | "bugs": { 38 | "url": "https://github.com/SudhanPlayz/Discord-MusicBot/issues" 39 | }, 40 | "homepage": "https://github.com/SudhanPlayz/Discord-MusicBot#readme", 41 | "dependencies": { 42 | "@discordjs/builders": "^1.4.0", 43 | "@discordjs/rest": "^1.5.0", 44 | "axios": "^0.27.0", 45 | "better-erela.js-apple": "^0.1.0", 46 | "better-erela.js-spotify": "1.3.9", 47 | "colors": "1.3.3", 48 | "discord-api-types": "0.37.1", 49 | "discord-together": "^1.3.25", 50 | "discord.js": "^13.14.0", 51 | "dotenv": "^16.0.1", 52 | "ejs": "^3.1.6", 53 | "erela.js": "^2.3.3", 54 | "erela.js-deezer": "^1.0.7", 55 | "erela.js-facebook": "^1.0.4", 56 | "erela.js-filters": "^1.2.6", 57 | "express": "^4.17.1", 58 | "express-rate-limit": "^6.2.0", 59 | "express-session": "^1.17.3", 60 | "express-ws": "^5.0.2", 61 | "js-yaml": "^4.1.0", 62 | "jsoning": "^0.13.0", 63 | "lodash": "^4.17.21", 64 | "moment": "^2.29.1", 65 | "moment-duration-format": "^2.3.2", 66 | "node-fetch": "2.6.7", 67 | "os": "^0.1.2", 68 | "passport": "^0.6.0", 69 | "passport-discord": "^0.1.4", 70 | "pretty-ms": "^7.0.1", 71 | "rlyrics": "^2.0.1", 72 | "systeminformation": "^5.9.12", 73 | "winston": "^3.3.3", 74 | "youtube-sr": "^4.3.4" 75 | }, 76 | "devDependencies": { 77 | "prettier": "2.6.2" 78 | }, 79 | "volta": { 80 | "node": "16.15.1", 81 | "yarn": "3.3.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /replit.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: { 2 | deps = [ 3 | pkgs.nodejs-18_x 4 | 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /util/Controller.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require("discord.js"); 2 | /** 3 | * 4 | * @param {import("../lib/DiscordMusicBot")} client 5 | * @param {import("discord.js").ButtonInteraction} interaction 6 | */ 7 | module.exports = async (client, interaction) => { 8 | let guild = client.guilds.cache.get(interaction.customId.split(":")[1]); 9 | let property = interaction.customId.split(":")[2]; 10 | let player = client.manager.get(guild.id); 11 | 12 | if (!player) { 13 | await interaction.reply({ 14 | embeds: [ 15 | client.Embed("❌ | **There is no player to control in this server.**"), 16 | ], 17 | }); 18 | setTimeout(() => { 19 | interaction.deleteReply(); 20 | }, 5000); 21 | return; 22 | } 23 | if (!interaction.member.voice.channel) { 24 | const joinEmbed = new MessageEmbed() 25 | .setColor(client.config.embedColor) 26 | .setDescription( 27 | "❌ | **You must be in a voice channel to use this action!**", 28 | ); 29 | return interaction.reply({ embeds: [joinEmbed], ephemeral: true }); 30 | } 31 | 32 | if ( 33 | interaction.guild.members.me.voice.channel && 34 | !interaction.guild.members.me.voice.channel.equals(interaction.member.voice.channel) 35 | ) { 36 | const sameEmbed = new MessageEmbed() 37 | .setColor(client.config.embedColor) 38 | .setDescription( 39 | "❌ | **You must be in the same voice channel as me to use this action!**", 40 | ); 41 | return await interaction.reply({ embeds: [sameEmbed], ephemeral: true }); 42 | } 43 | 44 | if (property === "Stop") { 45 | player.queue.clear(); 46 | player.stop(); 47 | player.set("autoQueue", false); 48 | client.warn(`Player: ${ player.options.guild } | Successfully stopped the player`); 49 | const msg = await interaction.channel.send({ 50 | embeds: [ 51 | client.Embed( 52 | "⏹️ | **Successfully stopped the player**", 53 | ), 54 | ], 55 | }); 56 | setTimeout(() => { 57 | msg.delete(); 58 | }, 5000); 59 | 60 | interaction.update({ 61 | components: [client.createController(player.options.guild, player)], 62 | }); 63 | return; 64 | } 65 | 66 | // if theres no previous song, return an error. 67 | if (property === "Replay") { 68 | const previousSong = player.queue.previous; 69 | const currentSong = player.queue.current; 70 | const nextSong = player.queue[0] 71 | if (!player.queue.previous || 72 | player.queue.previous === player.queue.current || 73 | player.queue.previous === player.queue[0]) { 74 | 75 | return interaction.reply({ 76 | ephemeral: true, 77 | embeds: [ 78 | new MessageEmbed() 79 | .setColor("RED") 80 | .setDescription(`There is no previous song played.`), 81 | ], 82 | }); 83 | } 84 | if (previousSong !== currentSong && previousSong !== nextSong) { 85 | player.queue.splice(0, 0, currentSong) 86 | player.play(previousSong); 87 | return interaction.deferUpdate(); 88 | } 89 | } 90 | 91 | if (property === "PlayAndPause") { 92 | if (!player || (!player.playing && player.queue.totalSize === 0)) { 93 | const msg = await interaction.channel.send({ 94 | ephemeral: true, 95 | embeds: [ 96 | new MessageEmbed() 97 | .setColor("RED") 98 | .setDescription("There is no song playing right now."), 99 | ], 100 | }); 101 | setTimeout(() => { 102 | msg.delete(); 103 | }, 5000); 104 | return interaction.deferUpdate(); 105 | } else { 106 | 107 | if (player.paused) { 108 | player.pause(false); 109 | } else { 110 | player.pause(true); 111 | } 112 | client.warn(`Player: ${ player.options.guild } | Successfully ${ player.paused? "paused" : "resumed" } the player`); 113 | 114 | return interaction.update({ 115 | components: [client.createController(player.options.guild, player)], 116 | }); 117 | } 118 | } 119 | 120 | if (property === "Next") { 121 | const song = player.queue.current; 122 | const autoQueue = player.get("autoQueue"); 123 | if (player.queue[0] == undefined && (!autoQueue || autoQueue === false)) { 124 | return interaction.reply({ 125 | ephemeral: true, 126 | embeds: [ 127 | new MessageEmbed() 128 | .setColor("RED") 129 | .setDescription(`There is nothing after [${ song.title }](${ song.uri }) in the queue.`), 130 | ], 131 | })} else player.stop(); 132 | return interaction.deferUpdate 133 | } 134 | 135 | if (property === "Loop") { 136 | if (player.trackRepeat) { 137 | player.setTrackRepeat(false); 138 | player.setQueueRepeat(true); 139 | } else if (player.queueRepeat) { 140 | player.setQueueRepeat(false); 141 | } else { 142 | player.setTrackRepeat(true); 143 | } 144 | client.warn(`Player: ${player.options.guild} | Successfully toggled loop ${player.trackRepeat ? "on" : player.queueRepeat ? "queue on" : "off"} the player`); 145 | 146 | interaction.update({ 147 | components: [client.createController(player.options.guild, player)], 148 | }); 149 | return; 150 | } 151 | 152 | return interaction.reply({ 153 | ephemeral: true, 154 | content: "❌ | **Unknown controller option**", 155 | }); 156 | }; 157 | -------------------------------------------------------------------------------- /util/db.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This only implement global database. 3 | * Bugs are expected, contact Shasha on discord or fix it yourself. 4 | * TODO: Implement guild database, store each guild data on its own json file. 5 | * 6 | * CAUTION: Beware on providing path, all path to json file must be absolute path!!! 7 | */ 8 | 9 | "use strict"; 10 | 11 | const { writeFileSync, readdirSync, unlinkSync } = require("fs"); 12 | const { join } = require("path"); 13 | 14 | const _dbName = "db.json"; 15 | const _dbListName = "dbList.json" 16 | const _globalDbDir = join(__dirname, ".."); 17 | const _globalDbPath = join(_globalDbDir, _dbName); 18 | const _dbListPath = join(_globalDbDir, _dbListName); 19 | 20 | let _hasGlobalDb = false; 21 | let _hasDbList = false; 22 | 23 | try { 24 | const l = readdirSync(_globalDbDir); 25 | _hasGlobalDb = l.includes(_dbName); 26 | _hasDbList = l.includes(_dbListName); 27 | } catch (e) { 28 | console.warn("[DB] No global database exist"); 29 | } 30 | 31 | /** 32 | * @typedef {object} DbList 33 | * @property {string} name - Database name 34 | * @property {path} path - Absolute path to json 35 | */ 36 | 37 | /** 38 | * @typedef {object} DbData 39 | * @property {string} path - Absolute path to json 40 | * @property {object} data - Json to write 41 | */ 42 | 43 | /** 44 | * @type {DbList[]} 45 | */ 46 | const _dbList = _hasDbList ? require(_dbListPath) || [] : []; 47 | 48 | /** 49 | * @type {Map<string, DbData>} 50 | */ 51 | const _dbs = new Map([ 52 | [ 53 | "global", 54 | { 55 | path: _globalDbPath, 56 | data: _hasGlobalDb ? require(_globalDbPath) || {} : {} 57 | } 58 | ], 59 | ]); 60 | 61 | for (const v of _dbList) { 62 | try { 63 | _dbs.set(v.name, { 64 | path: v.path, 65 | data: require(v.path) || {} 66 | }); 67 | } catch (e) { 68 | console.error("[DB] Can't load database '" + v.name + "' in '" + v.path + "'"); 69 | } 70 | } 71 | 72 | /** 73 | * Idiot checker 74 | * @param {string} path 75 | */ 76 | const _validateDbPath = (path) => { 77 | if (typeof path !== "string") throw TypeError("path isn't string"); 78 | if (!path.endsWith(".json")) throw TypeError("path doesn't point to json file"); 79 | } 80 | 81 | /** 82 | * Write to global db. 83 | * 84 | * @param {string} path - Absolute path to json 85 | * @param {object} data - Json to write 86 | * 87 | * @returns {boolean} 88 | */ 89 | const _write = (path, data) => { 90 | _validateDbPath(path); 91 | try { 92 | // you don't need it to be in human readable format, it will still be valid json 93 | writeFileSync(path, JSON.stringify(data)); 94 | return true; 95 | } catch (e) { 96 | console.error("[DB] Can't write to '" + path + "', data is lost"); 97 | return false; 98 | } 99 | }; 100 | 101 | /** 102 | * Delete database of path 103 | * @param {string} path - Absolute path to delete 104 | * @returns {boolean} 105 | */ 106 | const _delete = (path) => { 107 | _validateDbPath(path); 108 | try { 109 | unlinkSync(path); 110 | return true; 111 | } catch (e) { 112 | console.error("[DB] Can't delete '" + path + "'"); 113 | return false; 114 | } 115 | } 116 | 117 | /** 118 | * Write queue 119 | * @type {DbData[]} 120 | */ 121 | const _wQueue = []; 122 | 123 | /** 124 | * Delete queue 125 | * @type {string[]} 126 | */ 127 | const _dQueue = []; 128 | 129 | // queue loop 130 | const _run = async () => { 131 | while (true) { 132 | await new Promise((r, j) => setTimeout(r, 100)); 133 | 134 | while (_wQueue.length) { 135 | const q = _wQueue.shift(); 136 | if (!q) continue; 137 | _write(q.path, q.data); 138 | } 139 | 140 | while (_dQueue.length) { 141 | const q = _dQueue.shift(); 142 | if (!q?.length) continue; 143 | _delete(q); 144 | } 145 | } 146 | } 147 | 148 | _run(); 149 | 150 | const _addDbList = (name, path) => { 151 | if (!name?.length) throw new TypeError("name undefined"); 152 | _validateDbPath(path); 153 | _dbList.push({ name, path }); 154 | _wQueue.push({ path: _dbListPath, data: _dbList }); 155 | } 156 | 157 | const _removeDbList = (name) => { 158 | if (!name?.length) throw new TypeError("name undefined"); 159 | 160 | for (let i = 0; i < _dbList.length;) { 161 | if (_dbList[i] && _dbList[i].name === name) { 162 | _dbList.splice(i, 1); 163 | } 164 | else i++; 165 | } 166 | _wQueue.push({ path: _dbListPath, data: _dbList }); 167 | } 168 | 169 | /** 170 | * Get database data 171 | * @param {string} name - Database name 172 | * @returns {object} Data 173 | */ 174 | const get = (name) => { 175 | if (!name?.length) throw new TypeError("name undefined"); 176 | const d = _dbs.get(name); 177 | if (!d) throw new RangeError("No database with name " + name); 178 | return d.data; 179 | } 180 | 181 | /** 182 | * Create new database with name 183 | * @param {string} name - Database name 184 | * @param {string} path - Absolute path to json file 185 | * @param {object} initialData - Initial value 186 | * 187 | * @returns {boolean} 188 | */ 189 | const create = (name, path, initialData = {}) => { 190 | if (!name?.length) throw new TypeError("name undefined"); 191 | _validateDbPath(path); 192 | if (typeof initialData !== "object") throw new TypeError("initialData is not object"); 193 | 194 | for (const [n,d] of _dbs) { 195 | if (n === name) 196 | throw new Error("Database '" + name + "' already exist"); 197 | if (d.path === path) { 198 | throw new Error("Database in path '" + path + "' already exist with name '" + n + "'"); 199 | } 200 | } 201 | 202 | const d = { 203 | path: path, 204 | data: initialData 205 | }; 206 | 207 | _wQueue.push(d); 208 | _addDbList(name, path); 209 | return !!_dbs.set(name, d); 210 | } 211 | 212 | /** 213 | * Set data to associated database name 214 | * @param {string} name - Database name 215 | * @param {object} data - Data to set, must be plain javascript object 216 | * 217 | * @returns {boolean} 218 | */ 219 | const set = (name, data) => { 220 | if (!name?.length) throw new TypeError("name undefined"); 221 | if (!data) throw new TypeError("data undefined"); 222 | if (typeof data !== "object") throw new TypeError("data is not object"); 223 | 224 | const d = _dbs.get(name); 225 | if (!d) throw new RangeError("No database with name " + name); 226 | d.data = data; 227 | 228 | _wQueue.push(d); 229 | return !!_dbs.set(name, d); 230 | } 231 | 232 | /** 233 | * Delete database 234 | * @param {string} name - Database name 235 | * @returns {boolean} 236 | */ 237 | const remove = (name) => { 238 | if (!name?.length) throw new TypeError("name undefined"); 239 | 240 | const d = _dbs.get(name); 241 | if (!d) throw new RangeError("No database with name " + name); 242 | 243 | _dQueue.push(d.path); 244 | _removeDbList(name); 245 | return _dbs.delete(name); 246 | } 247 | 248 | module.exports = { 249 | get, 250 | create, 251 | set, 252 | remove, 253 | } 254 | -------------------------------------------------------------------------------- /util/getChannel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {import("../lib/DiscordMusicBot")} client 4 | * @param {import("discord.js").GuildCommandInteraction} interaction 5 | * @returns 6 | */ 7 | module.exports = async (client, interaction) => { 8 | return new Promise(async (resolve) => { 9 | if (!interaction.member.voice.channel) { 10 | await interaction.reply({ 11 | embeds: [ 12 | client.ErrorEmbed( 13 | "You must be in a voice channel to use this command!", 14 | ), 15 | ], 16 | }); 17 | return resolve(false); 18 | } 19 | if ( 20 | interaction.guild.members.me.voice.channel && 21 | interaction.member.voice.channel.id !== 22 | interaction.guild.members.me.voice.channel.id 23 | ) { 24 | await interaction.reply({ 25 | embeds: [ 26 | client.ErrorEmbed( 27 | "You must be in the same voice channel as me to use this command!", 28 | ), 29 | ], 30 | }); 31 | return resolve(false); 32 | } 33 | if (!interaction.member.voice.channel.joinable) { 34 | await interaction.reply({ 35 | embeds: [ 36 | client.ErrorEmbed( 37 | "I don't have enough permission to join your voice channel!", 38 | ), 39 | ], 40 | }); 41 | return resolve(false); 42 | } 43 | 44 | resolve(interaction.member.voice.channel); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /util/getConfig.js: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv").config(); 2 | 3 | module.exports = () => { 4 | return new Promise((res, rej) => { 5 | try { 6 | const config = require("../dev-config"); 7 | res(config); 8 | } catch { 9 | try { 10 | const config = require("../config"); 11 | res(config); 12 | } catch { 13 | rej("No config file found."); 14 | } 15 | } 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /util/getLavalink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {import("../lib/DiscordMusicBot")} client 4 | * @returns {import("erela.js").Node | undefined} 5 | */ 6 | module.exports = async (client) => { 7 | return new Promise((resolve) => { 8 | for (let i = 0; i < client.manager.nodes.size; i++) { 9 | client.manager.nodes.forEach((node) => { 10 | if (node.connected) resolve(node); 11 | }); 12 | } 13 | resolve(undefined); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /util/guildDb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ============================ MODULE SAFETY USE ============================ 3 | * 4 | * Function should only be `getter` and `setter` for each guild data: 5 | * 6 | * getter: Call _getOrCreateGuildDb() and return a specific property value if 7 | * exist (not undefined or null), else return a default value. 8 | * 9 | * setter: Call _getOrCreateGuildDb() and modify a specific property of the 10 | * returned value and call set() to save. 11 | * 12 | * For space management we only need one function to delete the json file 13 | * based on database name, the deleteGuildDatabase(). 14 | * 15 | * We don't need a public createGuildDatabase function as the `getter` and 16 | * `setter` should create it if it doesn't exist. 17 | */ 18 | 19 | "use strict"; 20 | 21 | const { readdirSync, mkdirSync } = require("fs"); 22 | const { join } = require("path"); 23 | const { get, set, create, remove } = require("./db"); 24 | 25 | const _guildDbDir = join(__dirname,"..",".guild_dbs"); 26 | 27 | try { 28 | readdirSync(_guildDbDir); 29 | } catch (e) { 30 | try { 31 | mkdirSync(_guildDbDir); 32 | } catch (e) { 33 | console.error("[ERROR] Can't create guild database folder, module might won't work properly."); 34 | } 35 | } 36 | 37 | const _getGuildDbName = (guild_id) => { 38 | return "guild-"+guild_id; 39 | } 40 | 41 | const _getGuildDbPath = (guild_id) => { 42 | return join(_guildDbDir, _getGuildDbName(guild_id)+".json"); 43 | } 44 | 45 | const _getOrCreateGuildDb = (guild_id) => { 46 | const dbName = _getGuildDbName(guild_id); 47 | 48 | let d; 49 | try { 50 | d = get(dbName); 51 | } catch (e) { 52 | if (e.message.startsWith("No database with name ")) { 53 | create(dbName,_getGuildDbPath(guild_id)); 54 | d = {}; 55 | } else { 56 | console.error("[ERROR] Unexpected error:"); 57 | console.error(e); 58 | } 59 | } 60 | return d; 61 | } 62 | 63 | const _deleteGuildDb = (guild_id) => { 64 | const dbName = _getGuildDbName(guild_id); 65 | return remove(dbName); 66 | } 67 | 68 | /** 69 | * Wipe and delete all guild's data 70 | * @param {string} guild_id 71 | * @returns {boolean} 72 | */ 73 | const deleteGuildDatabase = (guild_id) => { 74 | return _deleteGuildDb(guild_id); 75 | } 76 | 77 | /** 78 | * Set guild dj-only mode 79 | * @param {string} guild_id 80 | * @param {boolean} djOnly 81 | */ 82 | const setDjOnly = (guild_id, djOnly) => { 83 | const d = _getOrCreateGuildDb(guild_id); 84 | d.djOnly = djOnly; 85 | 86 | set(_getGuildDbName(guild_id), d); 87 | } 88 | 89 | /** 90 | * Get guild dj-only mode 91 | * @param {string} guild_id 92 | * @returns {boolean} 93 | */ 94 | const getDjOnly = (guild_id) => { 95 | return !!_getOrCreateGuildDb(guild_id)?.djOnly; 96 | } 97 | 98 | // TODO: Write more stuff here 99 | 100 | 101 | 102 | module.exports = { 103 | setDjOnly, 104 | getDjOnly, 105 | deleteGuildDatabase, 106 | // new export here 107 | } 108 | -------------------------------------------------------------------------------- /util/loadCommands.js: -------------------------------------------------------------------------------- 1 | const { join } = require("path"); 2 | const fs = require("fs"); 3 | 4 | const LoadCommands = () => { 5 | return new Promise(async (resolve) => { 6 | let slash = await LoadDirectory("slash"); 7 | let context = await LoadDirectory("context"); 8 | 9 | resolve({ slash, context }); 10 | }); 11 | }; 12 | 13 | const LoadDirectory = (dir) => { 14 | return new Promise((resolve) => { 15 | let commands = []; 16 | let CommandsDir = join(__dirname, "..", "commands", dir); 17 | 18 | fs.readdir(CommandsDir, (err, files) => { 19 | if (err) { 20 | throw err; 21 | } 22 | 23 | for (const file of files) { 24 | let cmd = require(CommandsDir + "/" + file); 25 | if (!cmd || (dir == "context" && !cmd.command)) { 26 | return console.log( 27 | "Unable to load Command: " + 28 | file.split(".")[0] + 29 | ", File doesn't have either command", 30 | ); 31 | } 32 | if (dir == "context") { 33 | commands.push(cmd.command); 34 | } else { 35 | commands.push(cmd); 36 | } 37 | } 38 | ; 39 | resolve(commands); 40 | }); 41 | }); 42 | }; 43 | 44 | module.exports = LoadCommands; 45 | --------------------------------------------------------------------------------