Compare commits

...
250 Commits
Author SHA1 Message Date
John Grosh 6a5a9c7a75 Change playlist page count (#1542)
* change playlist page count

* make the value configurable

* small refactor
2024-05-10 17:43:49 -04:00
Michaili K 8557f7ab8e Log track exceptions in the audio handler (#1558) 2024-05-10 15:36:43 -04:00
Michaili K 48e62f1a50 Revert "Always self-deafen (#1491)" (#1551)
This reverts commit a7807b96e2.
2024-05-10 15:32:30 -04:00
John Grosh 6f12c33781 custom eval engine (#1530) 2024-05-10 15:23:30 -04:00
Michaili K 0afb3db269 Switch to lavalink's new YouTube audio source manager (#1552)
* Switch to new lavalink's new YouTube audio source manager

* Add back setPlaylistPageCount call for the youtube audio source manager

* Manually add all the individual audio sources EXCEPT for old youtube

* Remove unused import

* Upgrade lavaplayer-youtube-source to 1.0.3

* Upgrade lavaplayer-youtube-source to 1.0.4
2024-05-10 14:19:11 -04:00
Michaili KandWhew., Inc 81322ef993 Add seek command (supersedes #344) (#674)
* Add seek command

* Combine nested if statements

* Put the seek command in correct place to keep alphabetical order

* Add license header

* Brackets on next line

* Restructure if-statements

* Check for permissions with DJCommand#checkDJPermission

* Make regex slightly smaller

* Optimize imports

* Output length of current track if requested seek time is invalid

* Restate seeked point when seeked successfully

* Add empty newline at end of file to keep consistency

* Create TimeUtil class for parsing and formatting time

* Move FormatUtil#formatTime to TimeUtil, and refactor

* Apply requested changes (Pass 2)

* Seek based on current position in track

* Apply requested changes (Pass 3)

* Add javadoc param

* Apply requested changes (Pass 4)

* Fix merge

* Avoid reassigning parameter (Codacy)

* Rework timestamp parsing

* Refactor timestamp parsing

* Apply requested changes (Pass 5)

* Add examples in help

* Apply requested changes (Pass 6)

* Fix missing import

* Keep track of start timestamp & add "unit" times

* Fix my abdominal merge with QueuedTrack

* Use RequestMetadata to store start timestamp

* Store request info in request metadata

* Add regex to try getting a timestamp from the url

* Require RequestMetadata for QueuedTracks

* Add some unit tests for unit seeking

* Add docs & examples

---------

Co-authored-by: Whew., Inc <22574706+Whew-Inc@users.noreply.github.com>
2024-05-10 14:14:28 -04:00
Michaili KandJohn Grosh b3ffbdba7d Add log level configuration (#1156)
* Add loglevel config

* Change order of fields

---------

Co-authored-by: John Grosh <john.a.grosh@gmail.com>
2024-03-30 15:34:46 -04:00
John Grosh 801b3769a1 Fix startup message type and add URL to message (#1521)
* fix

* add URL to error message
2024-03-30 15:18:52 -04:00
Michaili Kandunknown 49c3ec7960 Switch to lavalink's lavaplayer fork (#1490)
* Switch to lavalink's lavaplayer fork

* Create logback turbofilter to suppress YouTube auth tokens warning

* added license header

---------

Co-authored-by: unknown <john.a.grosh@gmail.com>
2024-03-04 19:38:04 -05:00
John Grosh 6752695b43 rename github action file and update readme (#1494) 2024-03-04 19:35:32 -05:00
John Grosh bb17c984a2 Codefactor Fixes (#1493)
* codefactor fixes

* anotha one
2024-03-04 19:35:17 -05:00
Michaili K a7807b96e2 Always self-deafen (#1491) 2024-03-04 19:08:27 -05:00
2e9dd5df49 add LinearQueue (#1194)
* add LinearQueue

* rework queueType to be server-specific

* rework QueueType enum and command

* add QueueType supplier

* fix queue type formatting

* add QueueSupplier util

* fix code issues

* Fix unit tests

* add suggested changes on PR

---------

Co-authored-by: Michaili K <mysteriouscursor+git@protonmail.com>
Co-authored-by: Michail <git@michaili.dev>
2024-03-04 18:49:01 -05:00
Maoyue 貓月andunknown 1ad16181d0 Support new Discord username (#1397)
* Support new username

* Support new username, again

* Make a separate username formatting function

* Add some missing usage

* switch away from using static imports, add shortcuts for jda User and metadata UserInfo, add null check

---------

Co-authored-by: unknown <john.a.grosh@gmail.com>
2024-03-02 19:39:16 -05:00
John Grosh bb53bad1ff Update test.yml 2024-03-01 20:11:30 -05:00
Kevin S 8a1066cc87 Allow configurable default skip ratio (#1140)
* Allow configurable default skip ratio

Revert "Allow configurable default skip ratio"

This reverts commit 10e38b4132ee2b28fb583603d69e6228da211ecd.

* FIX: Use -1 to denote a guild has not set a skip ratio, and opted to use the bot default.
2024-03-01 19:42:39 -05:00
Michaili Kandunknown 23c2dfa78e Make JMusicBot error & shut down if ran in an unsupported manner (#1486)
* Make JMusicBot error & shut down if ran in an unsupported manner

* add info about message content intent

* five second rule

---------

Co-authored-by: unknown <john.a.grosh@gmail.com>
2024-03-01 19:26:58 -05:00
Michaili Kandunknown 18bf6ac0f6 Remove channel topic info (#1485)
* Remove channel topic info

As Discord has severely increased the ratelimits for updating the channel topic, updating the channel topic has become not viable for bots.

* fix compilation error

---------

Co-authored-by: unknown <john.a.grosh@gmail.com>
2024-03-01 19:19:18 -05:00
John Grosh fd6de60fc7 Merge pull request #1489 from jagrosh/add-more-sources
Add more sources and check more nulls
2024-03-01 19:00:50 -05:00
unknown 4c8ff655d8 update github actions to jdk 11 2024-03-01 18:26:10 -05:00
John Grosh ac8661c904 Merge pull request #1487 from MichailiK/fix/server-settings-logging
Don't warn about failing to load server settings when the file isn't found
2024-03-01 18:24:09 -05:00
unknown 4f926baf76 update circleci settings 2024-03-01 18:08:46 -05:00
unknown c5ecf89852 switch to java 11 2024-03-01 18:04:26 -05:00
unknown a52103620a update version 2024-03-01 18:00:02 -05:00
unknown fa5e33e2a5 fix formatting and nulls in metadata 2024-03-01 17:53:08 -05:00
unknown 781a94ea91 Merge branch 'master' of https://github.com/jagrosh/MusicBot 2024-03-01 17:50:39 -05:00
Michail ff1ede9d12 Log "loaded config" message again after successfully initializing the GUI 2024-02-28 21:33:45 +01:00
Michail 90d9a1631a Create a new serversettings.json file if one doesn't exist 2024-02-28 21:33:10 +01:00
John Grosh c4502bbe16 Merge pull request #1460 from jagrosh/dependabot/maven/ch.qos.logback-logback-classic-1.2.13
Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13
2024-02-28 15:13:45 -05:00
Michail 2d4f2671c9 Don't warn about failing to load server settings when the file isn't found 2024-02-28 21:01:08 +01:00
John Grosh e595de2033 Merge pull request #1479 from MichailiK/fix/eval-shenanigans
Fix eval command failing in DMs
2024-02-10 17:28:33 -05:00
Michail 53875f2791 Properly fix eval command failing in DMs 2024-02-10 23:25:11 +01:00
Michail cd7e643228 Possible workaround for Eval command failing 2024-02-10 23:10:07 +01:00
dependabot[bot] 0e1f0d3bfa Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.2.3 to 1.2.13.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.2.3...v_1.2.13)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 22:17:47 +00:00
John Grosh 2c76164f6d Merge pull request #1304 from LachlanPond/master
Catch JDA exception when there is no internet connection
2023-03-06 16:31:28 -05:00
Lachlan Pond 788cd40f2c Added import for ErrorResponseException 2023-03-04 13:59:42 +10:00
Michaili K 1e12e4e01d Bump JDA to 4.4.1_353 2023-03-01 20:52:53 +01:00
Lachlan Pond c4cbfe2a11 Catch JDA exception when there is no internet connection 2023-02-27 21:52:22 +10:00
John Grosh bbed9e8990 Merge pull request #1294 from lrbn86/master
Sanitize the playlist name
2023-02-14 12:22:51 -05:00
lrbn86 354a0b3def Sanitize the playlist name 2023-02-13 18:31:14 -05:00
John Grosh 2faef67b30 Merge branch 'master' of https://github.com/jagrosh/MusicBot 2022-09-02 21:56:23 -04:00
John Grosh aee076c854 add more sources 2022-09-02 21:56:19 -04:00
John Grosh b76f6fa07c Merge pull request #1141 from t3rminus/fix-warnings-deprecations
Fix warnings and deprecations
2022-09-02 21:55:14 -04:00
John Grosh ec4e9e624c Merge pull request #1209 from jagrosh/dependabot/maven/org.jsoup-jsoup-1.15.3
Bump jsoup from 1.14.2 to 1.15.3
2022-09-02 21:53:47 -04:00
dependabot[bot] a7be2c4602 Bump jsoup from 1.14.2 to 1.15.3
Bumps [jsoup](https://github.com/jhy/jsoup) from 1.14.2 to 1.15.3.
- [Release notes](https://github.com/jhy/jsoup/releases)
- [Changelog](https://github.com/jhy/jsoup/blob/master/CHANGES)
- [Commits](https://github.com/jhy/jsoup/compare/jsoup-1.14.2...jsoup-1.15.3)

---
updated-dependencies:
- dependency-name: org.jsoup:jsoup
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 23:43:19 +00:00
John Grosh 6007d3e99b Merge pull request #1192 from yo-louk/queue-links
Queue entry formatting fix
2022-07-07 16:00:25 -04:00
yo-louk a17b2f20ee check uri type before formatting queue entry 2022-07-07 21:26:18 +02:00
John Grosh 4375d4caa8 Merge pull request #1155 from MichailiK/pr/ci/simplify-release
Update generated description in release workflow
2022-04-09 15:01:04 -04:00
Michaili K c22807bcbb Update make-release.yml 2022-04-09 18:25:01 +02:00
John Grosh baf629a910 Merge branch 'master' of https://github.com/jagrosh/MusicBot 2022-04-06 18:28:07 -04:00
John Grosh c4e11d682e update deps, minor code cleanup 2022-04-06 18:27:59 -04:00
Xiao Quan 全笑andMichaili K 041ab27b94 Fixes #514 Require inputing name for playlist make <name> command (#1046)
* Fixes #514 Require inputing name for `playlist make <name>` command

playlist make command without a name will now not make an empty playlist without a playlist name.

* Update src/main/java/com/jagrosh/jmusicbot/commands/owner/PlaylistCmd.java

Co-authored-by: Michaili K <mysteriouscursor+git@protonmail.com>

* Rid excessive hints

Co-authored-by: Michaili K <mysteriouscursor+git@protonmail.com>
2022-04-06 12:44:52 -04:00
John Grosh 85aebe8a89 Update make-release.yml 2022-04-05 19:06:50 -04:00
John Grosh 3c306a8d7a Update make-release.yml 2022-04-05 19:04:50 -04:00
John Grosh cadaf94cf7 Update make-release.yml 2022-04-05 18:55:05 -04:00
John Grosh 15a47a7b4d Update make-release.yml 2022-04-05 18:51:36 -04:00
John Grosh 92fca22059 Update make-release.yml 2022-04-05 18:42:44 -04:00
John Grosh 0cdc0d2d34 Update make-release.yml 2022-04-05 18:29:06 -04:00
John Grosh 1d1dfe103e Create make-release.yml 2022-04-05 18:25:29 -04:00
John Grosh ab39d2cd48 Merge branch 'master' of https://github.com/jagrosh/MusicBot 2022-04-05 17:47:09 -04:00
John Grosh 32d165d1b3 add natives to base repo because I don't think any other repo is opening up soon 2022-04-05 17:46:59 -04:00
Kevin S 8457f944cd Fix warnings and deprecations 2022-03-31 18:30:19 -07:00
John Grosh c404e92932 Create test.yml 2022-01-06 16:10:02 -05:00
John Grosh 6af86cae5d Update bug-report.yml 2022-01-06 10:24:11 -05:00
John Grosh 9421730229 Delete feature-request.md 2022-01-06 10:18:58 -05:00
John Grosh b76e31c7e2 Delete bug-report.md 2022-01-06 10:18:48 -05:00
John Grosh 6056ff2c13 Merge pull request #1032 from yo-louk/queue-links
Queue links
2021-11-13 14:14:21 -05:00
yo-louk 8c92a7a75a changed QueuedTrack class instead 2021-11-12 00:55:16 +01:00
yo-louk e942f3ef21 changed list format 2021-11-12 00:31:31 +01:00
John Grosh 157da6c0da Update config.yml 2021-10-04 17:36:01 -04:00
John Grosh 03b909a3f5 whoops 2021-10-04 08:09:12 -04:00
Louk e08c1491c2 Voice channel mention (#932)
* Voice channel mention

I recently noticed voice channels can be mentioned just like text channels. A small change to the code to implement this feature.

* Voice channel mention

* vc.getName() changed to vc.getAsMention()
2021-10-02 15:17:00 -04:00
John Grosh 95de05ff2d Merge pull request #940 from MichailiK/feature/use-lyrics-anytime
Allow usage of LyricsCmd without playing music
2021-10-02 15:15:31 -04:00
Michaili K e242fa9557 Allow usage of LyricsCmd without playing music 2021-10-01 17:47:36 +02:00
John Grosh b83028fb97 use lavaplayer fork, fix update alerts, fix special case repeat mode parse 2021-09-27 14:19:48 -04:00
Michaili K 7273b5bf42 Add single repeat (#359)
* Add single repeat mode

* Toggle between NONE and REPEAT modes when no argument has been given

* Allow single as repeat mode

* Add one to the arguments

* Add backwards comparability for old versions of MusicBot.

* Use JSONObject#getEnum for parsing repeat mode

* Rename repeat modes & fix repeat one not repeating properly

* Add copyright notice & author to RepeatMode

* Fix spacing in SettingsManager

* Rename REPEAT_ONE to One in RepeatMode

* Rename repeat to repeat_mode

* Use variable on track end in AudioHandler & update RepeatMode & its usages

* Use user friendly name in RepeatCmd

* Remove unused constants

* Fix spacing in SettingsManager

* Rename repeat modes to off/all/single respectively
2021-09-27 19:51:57 +02:00
John Grosh 6f6064521b Create feature-request.yml 2021-09-21 22:08:51 -04:00
John Grosh 48263ec60a Merge pull request #825 from MichailiK/fix/path-loading-bug
Fix Windows path loading bug
2021-09-18 16:23:30 -04:00
Michaili K c6e5b8cbda Add "clear" alias to stop command (#847)
* Add "clear" alias to stop command

* Use "leave" as an alias to stop instead of "clear"
2021-09-18 16:21:55 -04:00
John Grosh 3b8892d2c0 fix queuedtrack string 2021-09-17 18:09:46 -04:00
John Grosh 54cb757dc5 Merge branch 'master' of https://github.com/jagrosh/MusicBot 2021-09-17 17:06:18 -04:00
John Grosh c4de816c7c added skip ratio and more request metadata beyond user id 2021-09-17 17:06:09 -04:00
John Grosh ccb421e110 Update bug-report.yml 2021-09-17 13:49:19 -04:00
John Grosh 23334e4253 Update bug-report.yml 2021-09-17 13:48:57 -04:00
John Grosh 826aec2492 Create bug-report.yml 2021-09-17 13:46:23 -04:00
John Grosh c397afcf8d Merge branch 'master' of https://github.com/jagrosh/MusicBot 2021-09-16 20:49:58 -04:00
John Grosh 95c07eab8e updated pom with working repos, and add comments with what we need to fix 2021-09-16 20:48:49 -04:00
John Grosh 58e0e5737d update pom with lowercase for circleci 2021-09-16 18:50:50 -04:00
John Grosh 84bc57e3cf Merge branch 'master' of https://github.com/jagrosh/MusicBot 2021-09-16 18:45:04 -04:00
John Grosh 67f6169e61 update pom 2021-09-16 18:45:02 -04:00
John Grosh 6f7c47757b Create FUNDING.yml 2021-09-16 18:42:46 -04:00
John Grosh 0a353e0fa3 Update README.md 2021-09-16 18:36:14 -04:00
Michaili K ddf95da19f Fix Windows path loading bug 2021-09-16 20:30:17 +02:00
John Grosh d5bb16d143 Update config.yml 2021-09-16 09:31:49 -04:00
John Grosh d47cfee8bf Create CNAME 2021-09-15 18:09:07 -04:00
John Grosh 1ee089e7ad Delete CNAME 2021-09-15 17:42:24 -04:00
John Grosh 7255e4b6db Create CNAME 2021-09-15 17:42:18 -04:00
John Grosh 4e314da86d Update README.md 2021-09-14 17:32:39 -04:00
John Grosh ec06cf10e9 update reference.conf 2021-08-30 21:09:48 -04:00
John Grosh ad972185d4 update deps
add transformative audio source
fix intents
2021-08-30 02:17:34 -04:00
John Grosh bc64b45d5c whoops 2021-04-02 14:21:39 -04:00
John Grosh 699c2a0e6e update deps and add noprompt mode 2021-04-02 14:02:16 -04:00
John Grosh ebf6e43084 Merge pull request #583 from MichailiK/feature/auto-leave-when-alone
Add "alone time until stop"
2021-02-09 09:55:50 -05:00
Michaili K 6527afb88c Ignore all bots when checking if the bot is alone 2021-02-09 15:52:41 +01:00
Michaili K 18e142ed88 Store guild ID instead of the guild object in AloneInVoiceHandler 2021-02-09 15:09:59 +01:00
John Grosh 797d306a4e Update README.md 2021-02-08 17:10:24 -05:00
John Grosh 267bf71d47 Update feature-request.md 2021-02-08 10:03:08 -05:00
John Grosh 88a89f2178 Update config.yml 2021-02-08 09:37:16 -05:00
John Grosh 8d2f60eba5 Merge origin/master 2021-02-06 19:53:03 -05:00
John Grosh b4c9a125e5 deps update and ref cleanup 2021-02-06 19:52:48 -05:00
Michaili K 36c53cfd41 Add "alone time until stop" feature & config 2021-01-13 01:58:21 +01:00
John Grosh d1223e9795 Update config.yml 2020-12-03 12:33:45 -05:00
John Grosh cebdbc3aa5 initial version of automation script 2020-11-04 18:56:10 -05:00
John Grosh be4f235071 Merge origin/master 2020-11-04 08:07:20 -04:00
John Grosh e8df9e9523 fix aboutcommand error 2020-11-04 08:07:00 -04:00
John Grosh 9a79308065 Update and rename feature_request.md to feature-request.md 2020-11-02 15:52:57 -05:00
John Grosh 7a55e76a35 Update bug-report.md 2020-11-02 15:51:15 -05:00
John Grosh c92c55ca52 Create config.yml 2020-11-02 15:46:36 -05:00
John Grosh 2bb43b57ba return to main lavaplayer 2020-10-28 07:10:56 -04:00
John Grosh 403a43530f deps 2020-10-27 16:44:58 -04:00
John Grosh 51f9024498 first iteration of handling new gateway 2020-10-27 05:57:05 -04:00
John Grosh 9f7ad33707 Merge pull request #512 from jagrosh/dependabot/maven/junit-junit-4.13.1
Bump junit from 4.12 to 4.13.1
2020-10-21 23:09:23 -04:00
dependabot[bot] fa5241490f Bump junit from 4.12 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 10:57:44 +00:00
John Grosh 27fd75aa67 deps update and small code cleanup 2020-07-04 17:02:19 -04:00
John Grosh c5a583b96f update deps and deal with discords new ratelimits 2020-05-30 13:26:06 -04:00
John Grosh f1e301b48e fix windows path loading bug
fix empty game bug
2020-04-09 16:13:48 -04:00
John Grosh 61d4c51276 update deps 2020-03-23 17:05:02 -04:00
John Grosh cbdbe7bf4b update lavaplayer 2020-02-02 20:42:00 -05:00
John Grosh baa3a60b05 Merge origin/master
Conflicts:
	pom.xml
2020-01-28 21:02:36 -05:00
John Grosh 338dafca0c deps update 2020-01-28 21:00:57 -05:00
John Grosh b1dddb649b Merge pull request #342 from SplitPixl/patch-1
Add the letter "s"
2020-01-17 10:13:39 -05:00
SplitPixl 3eb284da55 Add the letter "s"
As of January 2020, JCenter rejects all non-https connections with a 403 Forbidden. 

https://jfrog.com/blog/secure-jcenter-with-https/
2020-01-16 20:02:26 -08:00
John Grosh 5fb969283d Update bug-report.md 2019-11-10 00:54:29 -05:00
John Grosh ae8b5cfe90 first pass of per-server prefixes 2019-11-09 20:22:53 -05:00
John Grosh b6fa7de7e2 Merge origin/master 2019-11-09 18:16:13 -05:00
John Grosh a4f13ca2de Merge pull request #308 from MichailiK/pr/feature/config/aliases
Add configurable aliases for all commands
2019-11-09 18:15:38 -05:00
John Grosh 5172deb12d dep update and fix playlist shuffling 2019-11-07 22:07:31 -05:00
MichailiK 179f447a70 Catch ConfigException.Missing in BotConfig#getAliases(string) 2019-10-26 09:39:56 +02:00
MichailiK f4a937cc6b Remove unused import from SCSearchCmd 2019-10-25 20:03:45 +02:00
MichailiK 0b4c6c9af1 Use Bot#getConfig() instead of using a public static config & Add missing newline 2019-10-25 19:48:41 +02:00
MichailiK f17058a344 Put aliases inside an "aliases"-block 2019-10-25 19:25:05 +02:00
MichailiK 4fcf86a5d6 Fix alias of volume 2019-10-25 18:14:23 +02:00
MichailiK 94362d640b Change Example 2 2019-10-25 17:56:55 +02:00
MichailiK 7192d55732 Add configurable aliases for all commands 2019-10-25 17:51:56 +02:00
John Grosh 11c9b00510 fix DJ permissions in PlayCmd
general cleanup
2019-10-21 01:02:48 -04:00
John Grosh 4579d6ca09 update deps and bump version 2019-10-21 00:27:19 -04:00
John Grosh c3a9937c90 first pass of debug command 2019-10-21 00:25:45 -04:00
MichailiandM R S E 8650f82210 Fix bot joining in AFK voice channels (#292)
* Fix bot joining in AFK voice channels

Since Discord automatically mutes users in AFK channels, bots shouldn't
play music inside AFK channels. This commit also fixes #147.

* Apply requested changes

* Fix potential NPE

Co-Authored-By: M R S E <Maurice.Craft@t-online.de>

* Fix indention
2019-10-19 23:02:28 -04:00
John Grosh 3ba13d2daa Merge pull request #296 from MichailiK/rp/fix/nowplayinghandler/refreshes-even-with-npimages-enabled
Fix nowplaying being updated even when NPImages is true.
2019-10-16 18:34:29 -04:00
MichailiK dace101c30 Fix nowplaying being updated even when NPImages is true. 2019-10-17 00:27:07 +02:00
John Grosh 05e8ec6814 Merge pull request #290 from MichailiK/pr/fix/pausebug
Fix player being paused when it is paused, then the queue being cleared
2019-10-15 13:19:26 -04:00
John Grosh 646ed6e9b1 Merge pull request #294 from MichailiK/pr/feature/command/queue/wrap-page-ends
Add wrapping of page ends in queue command
2019-10-15 13:15:41 -04:00
MichailiK a501b00ab7 Add wrapping of page ends in queue command
This feature wraps both page ends, meaning that users can jump from the
first page to the last page, using the previous page reaction,
while using the next page button on the last page brings you to the
first page.

Resolves #233.
2019-10-15 18:33:32 +02:00
MichailiK 6ae683b749 Fix player being paused when it is paused, then the queue being cleared
This commit fixes #75, where the player doesn't start playing when
it's been previously paused.
This may happen when a user pauses the player, clears the queue or skips
all songs and another user tries to use the player later.
2019-10-15 10:48:04 +02:00
John Grosh 235af8c8c6 Merge pull request #287 from MichailiK/pr-forceremovecmd-copyright
Add copyright and author to ForceRemoveCmd.java
2019-10-12 16:44:02 -04:00
MichailiK 33e4e8e5ce Add copyright and author to ForceRemoveCmd.java 2019-10-12 22:33:53 +02:00
John Grosh 20b23a3b0d Merge pull request #243 from MichailiK/pr-forceremovecmd
Added ForceRemoveCmd
2019-10-07 09:52:23 -04:00
MichailiK d616c1e386 Apply requested changes (Pass 3) 2019-10-05 19:37:19 +02:00
MichailiK e6f2b23ca9 Fix minor oversight 2019-10-05 10:30:59 +02:00
MichailiK fbfea7ab1f Apply requested changes (Pass 2) 2019-10-04 17:19:11 +02:00
MichailiK bc85fb9597 Apply requested changes 2019-09-12 14:44:13 +02:00
John Grosh a34f06669d removed build files 2019-09-11 19:31:15 -04:00
John Grosh 6508c2b62c dependency and version bump 2019-09-11 19:04:13 -04:00
John Grosh fb53b9e8ea deps updates 2019-07-31 23:35:50 -04:00
Michaili K 75abab2832 Removed redundant code. 2019-06-30 19:09:43 +02:00
Michaili K 13d9fa87d8 Added ForceRemoveCmd 2019-06-30 14:54:42 +02:00
John Grosh c0d40b2594 Merge origin/master 2019-06-10 18:35:49 -04:00
John Grosh 327cfe7d21 Update README.md 2019-06-07 14:48:26 -04:00
John Grosh b4e83fd3f0 Merge origin/master
also minor edits
Conflicts:
	pom.xml
2019-05-29 22:26:51 -04:00
John Grosh c902c1eace Merge pull request #225 from Sanduhr32/patch-1
Fixing news/store channels issue
2019-05-22 14:05:55 -04:00
M R S E 39a787c3c9 Dont go to insane on JDAs version
Also bump version
2019-05-05 16:55:18 +02:00
M R S E cdc20ce500 Fixing news/store channels issue 2019-05-05 16:52:51 +02:00
John Grosh 7dcd7badb3 Merge pull request #218 from SlapGaming/pullreqs
Move track command
2019-04-09 15:34:59 -04:00
Rick Fontein 1a0a73ac7c PR feedback pass 2 2019-04-09 20:56:16 +02:00
Rick Fontein eb1fb776a5 PR feedback pass 2019-04-08 20:38:59 +02:00
Rick Fontein 82349d380b Move track command
Adds support for moving a track in queue.
2019-04-07 00:12:21 +02:00
John Grosh 3c5deca55e improved config flow and autowrite full config 2019-03-16 16:29:09 -05:00
John Grosh e1b9e0165a Move pull req template 2019-01-30 11:58:55 -05:00
John Grosh 2e701d0d23 Move code of conduct 2019-01-30 11:58:21 -05:00
John Grosh 259e3d716b Update README.md 2019-01-13 15:53:19 -05:00
John Grosh 43398449c7 more cleanup and update readme 2019-01-13 15:51:26 -05:00
John Grosh e7f00fe088 Merge origin/master 2019-01-13 15:06:49 -05:00
John Grosh 1a9364acae code cleanup and deps update 2019-01-13 15:06:26 -05:00
John Grosh 19234998f1 Merge pull request #193 from jagrosh/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2018-12-07 10:46:41 -05:00
John Grosh 31037775d1 Create CODE_OF_CONDUCT.md 2018-12-07 10:42:34 -05:00
John Grosh e0b1bb0d06 Create PULL_REQUEST_TEMPLATE.md 2018-12-07 10:34:42 -05:00
John Grosh 1f810aa36f Delete ISSUE_TEMPLATE.md 2018-12-07 10:22:20 -05:00
John Grosh e7a8cec0b4 Update issue templates 2018-12-07 10:22:04 -05:00
John Grosh ae07ac2598 lyrics second pass 2018-12-05 02:16:39 -05:00
John Grosh d75098fdd9 Update config.yml 2018-12-05 00:47:25 -05:00
John Grosh ac87cc13d7 fix pom 2018-12-05 00:43:13 -05:00
John Grosh 34bffe0148 first pass of lyrics command
version bump
more code cleanup
2018-12-05 00:30:28 -05:00
John Grosh 15f3cf07a8 Merge origin/master 2018-12-04 23:53:24 -05:00
John Grosh c0f0d45138 code cleanup 2018-12-04 23:37:45 -05:00
John Grosh 2eddaccb86 Update README.md 2018-11-27 15:04:21 -05:00
John Grosh 87b2f4f93a Update README.md 2018-11-27 15:02:41 -05:00
John Grosh 88970314ad Update README.md 2018-11-27 10:01:04 -05:00
John Grosh 0de4e2afcc Update .appveyor.yml 2018-11-27 09:58:09 -05:00
John Grosh 2671ac18c8 Update .appveyor.yml 2018-11-27 09:50:50 -05:00
John Grosh 68a992c79e Create .appveyor.yml 2018-11-27 09:40:46 -05:00
John Grosh 9bf233ec79 Update README.md 2018-11-27 09:25:50 -05:00
John Grosh b41dcded98 Update README.md 2018-11-27 09:25:30 -05:00
John Grosh b6239ed608 Update .travis.yml 2018-11-27 09:25:14 -05:00
John Grosh 9dfe613921 Update .travis.yml 2018-11-27 09:21:10 -05:00
John Grosh 865119e2ae Create .travis.settings.xml 2018-11-27 09:20:53 -05:00
John Grosh c616f9c92a Create .travis.yml 2018-11-27 09:01:09 -05:00
John Grosh 2737a85a61 Create config.yml 2018-11-27 08:41:38 -05:00
John Grosh c88979a6dd fixed npe during autoplaylists
added playnext command (simple version)
setting dj role to everyone works now
fixed massively wide joptionpanes
version number bump
2018-11-21 23:34:18 -05:00
John Grosh a6f7c9b3ef dependency update 2018-11-21 19:07:04 -05:00
John Grosh 2aac68b412 formatting 2018-11-21 18:43:27 -05:00
John Grosh e1b07868fd update alerts to owner DMs 2018-11-21 17:46:51 -05:00
John Grosh aaec886b81 massive organization overhaul
dependency updates
config overhaul
2018-11-21 05:31:03 -05:00
John Grosh a7a4b55011 allow owner commands (set avatar, game, and name) to be used in DM 2018-04-02 17:16:18 -05:00
John Grosh bcb7e03aac get version number programmatically and compare to latest version 2018-03-19 17:09:50 -05:00
John Grosh c49a559e34 play command accepts file uploads 2018-03-15 18:09:29 -05:00
John Grosh af98eb5e01 more minor updates 2018-03-13 05:46:31 -05:00
John Grosh e47c3efc58 dependency updates 2018-02-16 21:14:58 -05:00
John Grosh 058c3e18d2 added issue template 2018-02-07 17:00:36 -05:00
John Grosh 38ae19cbec little bugfixes 2018-02-07 16:33:26 -05:00
John Grosh 2eeffb374f dependency updates
more emojis
2018-02-07 11:07:37 -05:00
John Grosh e478fa43c8 perm and id fix 2017-12-18 11:48:30 -05:00
John Grosh 1093f92d67 Merge origin/master 2017-12-18 10:33:26 -05:00
John Grosh 6148dc6a89 updated deps
updated logger
added repeat mode
added loading playlist from video
added more game types
stop command improved
2017-12-18 10:33:08 -05:00
John Grosh a20ade9016 Update README.md 2017-10-25 18:26:33 -04:00
John Grosh fbbb7bd3a7 Update README.md 2017-10-18 23:54:55 -04:00
John Grosh 0c44160023 Update README.md 2017-10-12 23:41:16 -04:00
John Grosh 13427cdab7 Update README.md 2017-10-12 23:39:15 -04:00
John Grosh d0ffc0308c Update README.md 2017-10-12 23:35:35 -04:00
John Grosh fe62e03367 updated lib versions
fixed config loading format
filtered more output
longer yt playlists supported
2017-09-10 23:38:59 -04:00
John Grosh 5deb41c651 updated libs
fixed internal bugs
lots of other things (see 0.0.9 release)
2017-09-02 16:39:08 -04:00
John Grosh 6b454c299d fixed NONE for game 2017-08-18 16:42:32 -04:00
John Grosh dddf4c8e38 added altprefix, songinstatus, and setstatus features
slight change to internals to reduce some bugs
2017-08-18 15:43:20 -04:00
John Grosh 0912de74e9 Merge origin/master 2017-07-04 02:58:24 -04:00
John Grosh 3752e5b9e0 update pom 2017-07-04 02:57:56 -04:00
John Grosh ae62d5771f Update README.md 2017-07-04 02:32:26 -04:00
John Grosh 434749a250 fixed playlist shuffling 2017-07-04 02:19:08 -04:00
John Grosh cb9644d694 playlist load faster and queue command shows np 2017-07-04 01:41:42 -04:00
John Grosh 8f7e53a37c updated config for staying in voice 2017-06-22 02:47:54 -04:00
John Grosh b158e70495 added pause command
use embed for nowplaying
2017-06-22 01:45:11 -04:00
John Grosh ebed4f972a shuffle playlists when a single commented line contains 'shuffle'
fixed role bug
2017-06-22 00:54:51 -04:00
John Grosh f8b15d9591 updated readme
bumped license year
removed unnecessary import
2017-05-21 10:17:44 -04:00
John Grosh ab1e86d4ca Merge origin/master 2017-05-18 19:59:57 -04:00
John Grosh 9da577aa60 updated versions
local file support
less topic spam for the audit log conscious
2017-05-18 19:59:25 -04:00
John Grosh d7f690bd9a Update README.md 2017-04-26 12:42:39 -04:00
John Grosh 0f52bbd170 updated package name
fixed soundcloud search url bug
fixed playlist permission bug
added info to playlists command
bumped versions
2017-04-24 23:49:57 -04:00
John Grosh 61c62032f2 graphics checks 2017-03-26 04:00:27 -04:00
John Grosh eb6be6b589 lib updates
basic playlist support
config updates
2017-03-25 21:35:00 -04:00
John Grosh 25f262c6e4 added pom 2017-03-19 17:54:39 -04:00
John Grosh f1cca0b305 Merge origin/master 2017-03-19 17:51:19 -04:00
John Grosh 6c9a497725 removed guildlist 2017-03-19 17:50:49 -04:00
111 changed files with 8324 additions and 3203 deletions

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
# Java Maven CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: cimg/openjdk:11.0
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/postgres:9.4
working_directory: ~/repo
environment:
# Customize the JVM maximum heap limit
MAVEN_OPTS: -Xmx3200m
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: mvn dependency:go-offline
- save_cache:
paths:
- ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }}
# run tests!
- run: mvn integration-test
workflows:
version: 2
build:
jobs:
- build:
filters:
branches:
ignore:
- gh-pages
- gh-pages-generated
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at j@gro.sh. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: jagrosh
patreon: jagrosh
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+57
View File
@@ -0,0 +1,57 @@
name: Bug Report
description: Report a bug or unexpected behavior
title: "[Bug Report] Title Here "
labels: []
assignees: []
body:
- type: textarea
id: description
attributes:
label: Bug Description
description: Please provide a clear and concise description of what the bug is.
placeholder: The bug is...
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to Reproduce
description: Provide steps you can take to reliably reproduce this bug
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Result
description: What did you expect to happen when this bug occurred?
placeholder: I expected...
validations:
required: true
- type: textarea
id: debug
attributes:
label: Debug Output
description: Please run the `debug` command and paste it here or upload it as an attachment. If you cannot run the command, please provide information about your system such as operating system version, JMusicBot version, etc.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Info
description: Any additional info that you think might be useful or relevant to this bug
- type: checkboxes
id: checks
attributes:
label: Checklist
description: Before submitting your issue, please affirm the following
options:
- label: I have looked for information about this [within the documentation](https://jmusicbot.com/)
required: true
- label: I have searched for similar issues [on the issues page](https://github.com/jagrosh/MusicBot/issues?q=is%3Aissue)
required: true
- label: "I am running the latest version of the bot: ![Release](https://img.shields.io/github/release/jagrosh/MusicBot.svg)"
required: true
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://github.com/jagrosh/MusicBot/discussions/categories/questions
about: For general questions, please check the Discussions
- name: Troubleshooting
url: https://github.com/jagrosh/MusicBot/discussions/categories/troubleshooting
about: For troubleshooting help, please check the Discussions
@@ -0,0 +1,52 @@
name: Feature Request
description: Suggest an idea for this project
title: "[Feature Request] Title Here"
labels: []
assignees: []
body:
- type: textarea
id: problem
attributes:
label: Is your feature request related to a problem? Please describe.
description: Please provide a clear and concise description of what the problem is.
placeholder: A common problem users face is...
validations:
required: true
- type: textarea
id: solution
attributes:
label: What is your ideal solution to the problem?
description: Describe a clear and concise description of what you want to happen.
placeholder: My solution to the above problem is...
validations:
required: true
- type: textarea
id: procedure
attributes:
label: How would this feature be used?
description: Describe the basic flow/steps of using this feature.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Info
description: Any additional info that you think might be useful or relevant to this feature request
- type: checkboxes
id: checks
attributes:
label: Checklist
description: Before submitting your issue, please affirm the following
options:
- label: I have checked [the documentation](https://jmusicbot.com/) to make sure this feature doesn't already exist
required: true
- label: I have searched for [similar feature requests](https://github.com/jagrosh/MusicBot/labels/enhancement)
required: true
- label: I have confirmed that my requests is **not** on the [list of things that will not be added](https://jmusicbot.com/things-that-wont-be-added)
required: true
- label: "I am running the latest version of the bot: ![Release](https://img.shields.io/github/release/jagrosh/MusicBot.svg)"
required: true
+14
View File
@@ -0,0 +1,14 @@
### This pull request...
- [ ] Fixes a bug
- [ ] Introduces a new feature
- [ ] Improves an existing feature
- [ ] Boosts code quality or performance
### Description
Replace this with a description of the pull request
### Purpose
Replace this with a description of the problem or use-case
### Relevant Issue(s)
This PR closes issue #...
+22
View File
@@ -0,0 +1,22 @@
name: Build and Test
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
cache: maven
- name: Build and Test with Maven
run: mvn --batch-mode --update-snapshots verify
+70
View File
@@ -0,0 +1,70 @@
name: Make Release
on:
workflow_dispatch:
inputs:
version_number:
description: 'Version Number'
required: true
type: string
info:
description: 'Description of this Release'
required: true
type: string
jobs:
build_jar:
name: Build Jar
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
cache: maven
- name: Set Version
uses: datamonsters/replace-action@v2
with:
files: 'pom.xml'
replacements: 'Snapshot=${{ github.event.inputs.version_number }}'
- name: Build with Maven
run: mvn --batch-mode --update-snapshots verify
- name: Rename jar
run: mv target/*-All.jar JMusicBot-${{ github.event.inputs.version_number }}.jar
- name: Upload jar
uses: actions/upload-artifact@v3
with:
name: jar
path: JMusicBot-${{ github.event.inputs.version_number }}.jar
if-no-files-found: error
create_release:
name: Create Release
runs-on: ubuntu-latest
needs: build_jar
steps:
- name: Download a Build Artifact
uses: actions/download-artifact@v3.0.0
with:
name: jar
path: .
- name: Show Artifacts
run: ls -R
- name: Create Release
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.event.inputs.version_number }}
name: Version ${{ github.event.inputs.version_number }}
draft: true
prerelease: false
artifacts: "*.jar"
body: |
${{ github.event.inputs.info }}
---
### Setup
https://jmusicbot.com/setup
https://jmusicbot.com/config
# Download: [JMusicBot-${{ github.event.inputs.version_number }}.jar](https://github.com/jagrosh/MusicBot/releases/download/${{ github.event.inputs.version_number }}/JMusicBot-${{ github.event.inputs.version_number }}.jar)
+7
View File
@@ -0,0 +1,7 @@
/nbproject/
/target/
/Playlists/
/test/
*.json
*.txt
nb*.xml
+1
View File
@@ -0,0 +1 @@
jmusicbot.com
+201 -201
View File
@@ -1,201 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 John Grosh
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+55 -17
View File
@@ -1,22 +1,60 @@
<img align="right" src="https://i.imgur.com/zrE80HY.png" height="200" width="200">
# JMusicBot
A Discord music bot with a clean interface, and that is easy to set up and run yourself
# Features
* Easy to run (just make sure Java is installed, and run!)
* Fast loading of songs
* No external keys needed (besides a Discord Bot token)
* Smooth playback
* Server-specific setup for the "DJ" role that can moderate the music
* Clean and beautiful menus
* Channel-topic playback bar
* Supports many sites, including Youtube, Soundcloud, and more
* Playlist support
[![Downloads](https://img.shields.io/github/downloads/jagrosh/MusicBot/total.svg)](https://github.com/jagrosh/MusicBot/releases/latest)
[![Stars](https://img.shields.io/github/stars/jagrosh/MusicBot.svg)](https://github.com/jagrosh/MusicBot/stargazers)
[![Release](https://img.shields.io/github/release/jagrosh/MusicBot.svg)](https://github.com/jagrosh/MusicBot/releases/latest)
[![License](https://img.shields.io/github/license/jagrosh/MusicBot.svg)](https://github.com/jagrosh/MusicBot/blob/master/LICENSE)
[![Discord](https://discordapp.com/api/guilds/147698382092238848/widget.png)](https://discord.gg/0p9LSGoRLu6Pet0k)<br>
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/jagrosh/MusicBot/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/jagrosh/MusicBot/tree/master)
[![Build and Test](https://github.com/jagrosh/MusicBot/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/jagrosh/MusicBot/actions/workflows/build-and-test.yml)
[![CodeFactor](https://www.codefactor.io/repository/github/jagrosh/musicbot/badge)](https://www.codefactor.io/repository/github/jagrosh/musicbot)
# Setup
Please see the [Setup Page](https://github.com/jagrosh/MusicBot/wiki/Setup) in the wiki to run this bot yourself!
A cross-platform Discord music bot with a clean interface, and that is easy to set up and run yourself!
# Questions/Suggestions/Bug Reports
If you'd like to suggest changes to how the bot functions, recommend more customization options, or report bugs, feel free to either open an [Issue](https://github.com/jagrosh/MusicBot/issues) on this repository, or join [my Discord server](https://discord.gg/0p9LSGoRLu6Pet0k). If you like this bot, be sure to add a star to the libraries that make this possible: [**JDA**](https://github.com/DV8FromTheWorld/JDA) and [**lavaplayer**](https://github.com/sedmelluq/lavaplayer)
[![Setup](http://i.imgur.com/VvXYp5j.png)](https://jmusicbot.com/setup)
# Example
![Example](http://i.imgur.com/gxDdlRL.png)
## Features
* Easy to run (just make sure Java is installed, and run!)
* Fast loading of songs
* No external keys needed (besides a Discord Bot token)
* Smooth playback
* Server-specific setup for the "DJ" role that can moderate the music
* Clean and beautiful menus
* Supports many sites, including Youtube, Soundcloud, and more
* Supports many online radio/streams
* Supports local files
* Playlist support (both web/youtube, and local)
## Supported sources and formats
JMusicBot supports all sources and formats supported by [lavaplayer](https://github.com/sedmelluq/lavaplayer#supported-formats):
### Sources
* YouTube
* SoundCloud
* Bandcamp
* Vimeo
* Twitch streams
* Local files
* HTTP URLs
### Formats
* MP3
* FLAC
* WAV
* Matroska/WebM (AAC, Opus or Vorbis codecs)
* MP4/M4A (AAC codec)
* OGG streams (Opus, Vorbis and FLAC codecs)
* AAC streams
* Stream playlists (M3U and PLS)
## Example
![Loading Example...](https://i.imgur.com/kVtTKvS.gif)
## Setup
Please see the [Setup Page](https://jmusicbot.com/setup) to run this bot yourself!
## Questions/Suggestions/Bug Reports
**Please read the [Issues List](https://github.com/jagrosh/MusicBot/issues) before suggesting a feature**. If you have a question, need troubleshooting help, or want to brainstorm a new feature, please start a [Discussion](https://github.com/jagrosh/MusicBot/discussions). If you'd like to suggest a feature or report a reproducible bug, please open an [Issue](https://github.com/jagrosh/MusicBot/issues) on this repository. If you like this bot, be sure to add a star to the libraries that make this possible: [**JDA**](https://github.com/DV8FromTheWorld/JDA) and [**lavaplayer**](https://github.com/sedmelluq/lavaplayer)!
## Editing
This bot (and the source code here) might not be easy to edit for inexperienced programmers. The main purpose of having the source public is to show the capabilities of the libraries, to allow others to understand how the bot works, and to allow those knowledgeable about java, JDA, and Discord bot development to contribute. There are many requirements and dependencies required to edit and compile it, and there will not be support provided for people looking to make changes on their own. Instead, consider making a feature request (see the above section). If you choose to make edits, please do so in accordance with the Apache 2.0 License.
+155
View File
@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jagrosh</groupId>
<artifactId>JMusicBot</artifactId>
<version>Snapshot</version>
<packaging>jar</packaging>
<repositories>
<repository>
<id>dv8tion</id>
<name>m2-dv8tion</name>
<url>https://m2.dv8tion.net/releases</url>
</repository>
<repository>
<id>central</id>
<name>bintray</name>
<url>https://jcenter.bintray.com</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
<repository>
<id>m2.duncte123.dev</id>
<name>m2-duncte123</name>
<url>https://m2.duncte123.dev/releases</url>
</repository>
<repository>
<id>arbjergDev-snapshots</id>
<name>Lavalink Repository</name>
<url>https://maven.lavalink.dev/snapshots</url>
</repository>
</repositories>
<dependencies>
<!-- Discord Dependencies -->
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>4.4.1_353</version>
</dependency>
<dependency>
<groupId>com.jagrosh</groupId>
<artifactId>jda-utilities</artifactId>
<version>3.0.5</version>
<type>pom</type>
</dependency>
<!-- Music Dependencies -->
<dependency>
<groupId>dev.arbjerg</groupId>
<artifactId>lavaplayer</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.github.lavalink-devs</groupId>
<artifactId>lavaplayer-youtube-source</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>com.github.jagrosh</groupId>
<artifactId>JLyrics</artifactId>
<version>master-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.dunctebot</groupId>
<artifactId>sourcemanagers</artifactId>
<version>1.9.0</version>
</dependency>
<!-- Misc Internal Dependencies -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<!-- Testing Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>All</shadedClassifierName>
<artifactSet>
<includes>
<include>*:*</include>
</includes>
</artifactSet>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>reference.conf</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>com.jagrosh.jmusicbot.JMusicBot</Main-Class>
<Specification-Title>${project.artifactId}</Specification-Title>
<Specification-Version>${project.version}</Specification-Version>
<Implementation-Title>${project.artifactId}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
<Implementation-Vendor-Id>${project.groupId}</Implementation-Vendor-Id>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
# This will have this script check for a new version of JMusicBot every
# startup (and download it if the latest version isn't currently downloaded)
DOWNLOAD=true
# This will cause the script to run in a loop so that the bot auto-restarts
# when you use the shutdown command
LOOP=true
download() {
if [ $DOWNLOAD == true ]; then
URL=$(curl -s https://api.github.com/repos/jagrosh/MusicBot/releases/latest \
| grep -i "browser_download_url.*\.jar" \
| sed 's/.*\(http.*\)"/\1/')
FILENAME=$(echo $URL | sed 's/.*\/\([^\/]*\)/\1/')
if [ -f $FILENAME ]; then
echo "Latest version already downloaded (${FILENAME})"
else
curl -L $URL -o $FILENAME
fi
fi
}
run() {
java -Dnogui=true -jar $(ls -t JMusicBot* | head -1)
}
while
download
run
$LOOP
do
continue
done
@@ -0,0 +1,160 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import com.jagrosh.jmusicbot.audio.AloneInVoiceHandler;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.NowplayingHandler;
import com.jagrosh.jmusicbot.audio.PlayerManager;
import com.jagrosh.jmusicbot.gui.GUI;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader;
import com.jagrosh.jmusicbot.settings.SettingsManager;
import java.util.Objects;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class Bot
{
private final EventWaiter waiter;
private final ScheduledExecutorService threadpool;
private final BotConfig config;
private final SettingsManager settings;
private final PlayerManager players;
private final PlaylistLoader playlists;
private final NowplayingHandler nowplaying;
private final AloneInVoiceHandler aloneInVoiceHandler;
private boolean shuttingDown = false;
private JDA jda;
private GUI gui;
public Bot(EventWaiter waiter, BotConfig config, SettingsManager settings)
{
this.waiter = waiter;
this.config = config;
this.settings = settings;
this.playlists = new PlaylistLoader(config);
this.threadpool = Executors.newSingleThreadScheduledExecutor();
this.players = new PlayerManager(this);
this.players.init();
this.nowplaying = new NowplayingHandler(this);
this.nowplaying.init();
this.aloneInVoiceHandler = new AloneInVoiceHandler(this);
this.aloneInVoiceHandler.init();
}
public BotConfig getConfig()
{
return config;
}
public SettingsManager getSettingsManager()
{
return settings;
}
public EventWaiter getWaiter()
{
return waiter;
}
public ScheduledExecutorService getThreadpool()
{
return threadpool;
}
public PlayerManager getPlayerManager()
{
return players;
}
public PlaylistLoader getPlaylistLoader()
{
return playlists;
}
public NowplayingHandler getNowplayingHandler()
{
return nowplaying;
}
public AloneInVoiceHandler getAloneInVoiceHandler()
{
return aloneInVoiceHandler;
}
public JDA getJDA()
{
return jda;
}
public void closeAudioConnection(long guildId)
{
Guild guild = jda.getGuildById(guildId);
if(guild!=null)
threadpool.submit(() -> guild.getAudioManager().closeAudioConnection());
}
public void resetGame()
{
Activity game = config.getGame()==null || config.getGame().getName().equalsIgnoreCase("none") ? null : config.getGame();
if(!Objects.equals(jda.getPresence().getActivity(), game))
jda.getPresence().setActivity(game);
}
public void shutdown()
{
if(shuttingDown)
return;
shuttingDown = true;
threadpool.shutdownNow();
if(jda.getStatus()!=JDA.Status.SHUTTING_DOWN)
{
jda.getGuilds().stream().forEach(g ->
{
g.getAudioManager().closeAudioConnection();
AudioHandler ah = (AudioHandler)g.getAudioManager().getSendingHandler();
if(ah!=null)
{
ah.stopAndClear();
ah.getPlayer().destroy();
}
});
jda.shutdown();
}
if(gui!=null)
gui.dispose();
System.exit(0);
}
public void setJDA(JDA jda)
{
this.jda = jda;
}
public void setGUI(GUI gui)
{
this.gui = gui;
}
}
@@ -0,0 +1,384 @@
/*
* Copyright 2018 John Grosh (jagrosh)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot;
import com.jagrosh.jmusicbot.entities.Prompt;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.typesafe.config.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
/**
*
*
* @author John Grosh (jagrosh)
*/
public class BotConfig
{
private final Prompt prompt;
private final static String CONTEXT = "Config";
private final static String START_TOKEN = "/// START OF JMUSICBOT CONFIG ///";
private final static String END_TOKEN = "/// END OF JMUSICBOT CONFIG ///";
private Path path = null;
private String token, prefix, altprefix, helpWord, playlistsFolder, logLevel,
successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji,
evalEngine;
private boolean stayInChannel, songInGame, npImages, updatealerts, useEval, dbots;
private long owner, maxSeconds, aloneTimeUntilStop;
private int maxYTPlaylistPages;
private double skipratio;
private OnlineStatus status;
private Activity game;
private Config aliases, transforms;
private boolean valid = false;
public BotConfig(Prompt prompt)
{
this.prompt = prompt;
}
public void load()
{
valid = false;
// read config from file
try
{
// get the path to the config, default config.txt
path = getConfigPath();
// load in the config file, plus the default values
//Config config = ConfigFactory.parseFile(path.toFile()).withFallback(ConfigFactory.load());
Config config = ConfigFactory.load();
// set values
token = config.getString("token");
prefix = config.getString("prefix");
altprefix = config.getString("altprefix");
helpWord = config.getString("help");
owner = config.getLong("owner");
successEmoji = config.getString("success");
warningEmoji = config.getString("warning");
errorEmoji = config.getString("error");
loadingEmoji = config.getString("loading");
searchingEmoji = config.getString("searching");
game = OtherUtil.parseGame(config.getString("game"));
status = OtherUtil.parseStatus(config.getString("status"));
stayInChannel = config.getBoolean("stayinchannel");
songInGame = config.getBoolean("songinstatus");
npImages = config.getBoolean("npimages");
updatealerts = config.getBoolean("updatealerts");
logLevel = config.getString("loglevel");
useEval = config.getBoolean("eval");
evalEngine = config.getString("evalengine");
maxSeconds = config.getLong("maxtime");
maxYTPlaylistPages = config.getInt("maxytplaylistpages");
aloneTimeUntilStop = config.getLong("alonetimeuntilstop");
playlistsFolder = config.getString("playlistsfolder");
aliases = config.getConfig("aliases");
transforms = config.getConfig("transforms");
skipratio = config.getDouble("skipratio");
dbots = owner == 113156185389092864L;
// we may need to write a new config file
boolean write = false;
// validate bot token
if(token==null || token.isEmpty() || token.equalsIgnoreCase("BOT_TOKEN_HERE"))
{
token = prompt.prompt("Please provide a bot token."
+ "\nInstructions for obtaining a token can be found here:"
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Getting-a-Bot-Token."
+ "\nBot Token: ");
if(token==null)
{
prompt.alert(Prompt.Level.WARNING, CONTEXT, "No token provided! Exiting.\n\nConfig Location: " + path.toAbsolutePath().toString());
return;
}
else
{
write = true;
}
}
// validate bot owner
if(owner<=0)
{
try
{
owner = Long.parseLong(prompt.prompt("Owner ID was missing, or the provided owner ID is not valid."
+ "\nPlease provide the User ID of the bot's owner."
+ "\nInstructions for obtaining your User ID can be found here:"
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Finding-Your-User-ID"
+ "\nOwner User ID: "));
}
catch(NumberFormatException | NullPointerException ex)
{
owner = 0;
}
if(owner<=0)
{
prompt.alert(Prompt.Level.ERROR, CONTEXT, "Invalid User ID! Exiting.\n\nConfig Location: " + path.toAbsolutePath().toString());
return;
}
else
{
write = true;
}
}
if(write)
writeToFile();
// if we get through the whole config, it's good to go
valid = true;
}
catch (ConfigException ex)
{
prompt.alert(Prompt.Level.ERROR, CONTEXT, ex + ": " + ex.getMessage() + "\n\nConfig Location: " + path.toAbsolutePath().toString());
}
}
private void writeToFile()
{
byte[] bytes = loadDefaultConfig().replace("BOT_TOKEN_HERE", token)
.replace("0 // OWNER ID", Long.toString(owner))
.trim().getBytes();
try
{
Files.write(path, bytes);
}
catch(IOException ex)
{
prompt.alert(Prompt.Level.WARNING, CONTEXT, "Failed to write new config options to config.txt: "+ex
+ "\nPlease make sure that the files are not on your desktop or some other restricted area.\n\nConfig Location: "
+ path.toAbsolutePath().toString());
}
}
private static String loadDefaultConfig()
{
String original = OtherUtil.loadResource(new JMusicBot(), "/reference.conf");
return original==null
? "token = BOT_TOKEN_HERE\r\nowner = 0 // OWNER ID"
: original.substring(original.indexOf(START_TOKEN)+START_TOKEN.length(), original.indexOf(END_TOKEN)).trim();
}
private static Path getConfigPath()
{
Path path = OtherUtil.getPath(System.getProperty("config.file", System.getProperty("config", "config.txt")));
if(path.toFile().exists())
{
if(System.getProperty("config.file") == null)
System.setProperty("config.file", System.getProperty("config", path.toAbsolutePath().toString()));
ConfigFactory.invalidateCaches();
}
return path;
}
public static void writeDefaultConfig()
{
Prompt prompt = new Prompt(null, null, true, true);
prompt.alert(Prompt.Level.INFO, "JMusicBot Config", "Generating default config file");
Path path = BotConfig.getConfigPath();
byte[] bytes = BotConfig.loadDefaultConfig().getBytes();
try
{
prompt.alert(Prompt.Level.INFO, "JMusicBot Config", "Writing default config file to " + path.toAbsolutePath().toString());
Files.write(path, bytes);
}
catch(Exception ex)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot Config", "An error occurred writing the default config file: " + ex.getMessage());
}
}
public boolean isValid()
{
return valid;
}
public String getConfigLocation()
{
return path.toFile().getAbsolutePath();
}
public String getPrefix()
{
return prefix;
}
public String getAltPrefix()
{
return "NONE".equalsIgnoreCase(altprefix) ? null : altprefix;
}
public String getToken()
{
return token;
}
public double getSkipRatio()
{
return skipratio;
}
public long getOwnerId()
{
return owner;
}
public String getSuccess()
{
return successEmoji;
}
public String getWarning()
{
return warningEmoji;
}
public String getError()
{
return errorEmoji;
}
public String getLoading()
{
return loadingEmoji;
}
public String getSearching()
{
return searchingEmoji;
}
public Activity getGame()
{
return game;
}
public boolean isGameNone()
{
return game != null && game.getName().equalsIgnoreCase("none");
}
public OnlineStatus getStatus()
{
return status;
}
public String getHelp()
{
return helpWord;
}
public boolean getStay()
{
return stayInChannel;
}
public boolean getSongInStatus()
{
return songInGame;
}
public String getPlaylistsFolder()
{
return playlistsFolder;
}
public boolean getDBots()
{
return dbots;
}
public boolean useUpdateAlerts()
{
return updatealerts;
}
public String getLogLevel()
{
return logLevel;
}
public boolean useEval()
{
return useEval;
}
public String getEvalEngine()
{
return evalEngine;
}
public boolean useNPImages()
{
return npImages;
}
public long getMaxSeconds()
{
return maxSeconds;
}
public int getMaxYTPlaylistPages()
{
return maxYTPlaylistPages;
}
public String getMaxTime()
{
return TimeUtil.formatTime(maxSeconds * 1000);
}
public long getAloneTimeUntilStop()
{
return aloneTimeUntilStop;
}
public boolean isTooLong(AudioTrack track)
{
if(maxSeconds<=0)
return false;
return Math.round(track.getDuration()/1000.0) > maxSeconds;
}
public String[] getAliases(String command)
{
try
{
return aliases.getStringList(command).toArray(new String[0]);
}
catch(NullPointerException | ConfigException.Missing e)
{
return new String[0];
}
}
public Config getTransforms()
{
return transforms;
}
}
@@ -0,0 +1,251 @@
/*
* Copyright 2016 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot;
import com.jagrosh.jdautilities.command.CommandClient;
import com.jagrosh.jdautilities.command.CommandClientBuilder;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import com.jagrosh.jdautilities.examples.command.*;
import com.jagrosh.jmusicbot.commands.admin.*;
import com.jagrosh.jmusicbot.commands.dj.*;
import com.jagrosh.jmusicbot.commands.general.*;
import com.jagrosh.jmusicbot.commands.music.*;
import com.jagrosh.jmusicbot.commands.owner.*;
import com.jagrosh.jmusicbot.entities.Prompt;
import com.jagrosh.jmusicbot.gui.GUI;
import com.jagrosh.jmusicbot.settings.SettingsManager;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import java.awt.Color;
import java.util.Arrays;
import javax.security.auth.login.LoginException;
import net.dv8tion.jda.api.*;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
/**
*
* @author John Grosh (jagrosh)
*/
public class JMusicBot
{
public final static Logger LOG = LoggerFactory.getLogger(JMusicBot.class);
public final static Permission[] RECOMMENDED_PERMS = {Permission.MESSAGE_READ, Permission.MESSAGE_WRITE, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI,
Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE};
public final static GatewayIntent[] INTENTS = {GatewayIntent.DIRECT_MESSAGES, GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGE_REACTIONS, GatewayIntent.GUILD_VOICE_STATES};
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
if(args.length > 0)
switch(args[0].toLowerCase())
{
case "generate-config":
BotConfig.writeDefaultConfig();
return;
default:
}
startBot();
}
private static void startBot()
{
// create prompt to handle startup
Prompt prompt = new Prompt("JMusicBot");
// startup checks
OtherUtil.checkVersion(prompt);
OtherUtil.checkJavaVersion(prompt);
// load config
BotConfig config = new BotConfig(prompt);
config.load();
if(!config.isValid())
return;
LOG.info("Loaded config from " + config.getConfigLocation());
// set log level from config
((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(
Level.toLevel(config.getLogLevel(), Level.INFO));
// set up the listener
EventWaiter waiter = new EventWaiter();
SettingsManager settings = new SettingsManager();
Bot bot = new Bot(waiter, config, settings);
CommandClient client = createCommandClient(config, settings, bot);
if(!prompt.isNoGUI())
{
try
{
GUI gui = new GUI(bot);
bot.setGUI(gui);
gui.init();
LOG.info("Loaded config from " + config.getConfigLocation());
}
catch(Exception e)
{
LOG.error("Could not start GUI. If you are "
+ "running on a server or in a location where you cannot display a "
+ "window, please run in nogui mode using the -Dnogui=true flag.");
}
}
// attempt to log in and start
try
{
JDA jda = JDABuilder.create(config.getToken(), Arrays.asList(INTENTS))
.enableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE)
.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.EMOTE, CacheFlag.ONLINE_STATUS)
.setActivity(config.isGameNone() ? null : Activity.playing("loading..."))
.setStatus(config.getStatus()==OnlineStatus.INVISIBLE || config.getStatus()==OnlineStatus.OFFLINE
? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB)
.addEventListeners(client, waiter, new Listener(bot))
.setBulkDeleteSplittingEnabled(true)
.build();
bot.setJDA(jda);
// check if something about the current startup is not supported
String unsupportedReason = OtherUtil.getUnsupportedBotReason(jda);
if (unsupportedReason != null)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "JMusicBot cannot be run on this Discord bot: " + unsupportedReason);
try{ Thread.sleep(5000);}catch(InterruptedException ignored){} // this is awful but until we have a better way...
jda.shutdown();
System.exit(1);
}
// other check that will just be a warning now but may be required in the future
// check if the user has changed the prefix and provide info about the
// message content intent
if(!"@mention".equals(config.getPrefix()))
{
LOG.info("JMusicBot", "You currently have a custom prefix set. "
+ "If your prefix is not working, make sure that the 'MESSAGE CONTENT INTENT' is Enabled "
+ "on https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot");
}
}
catch (LoginException ex)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nPlease make sure you are "
+ "editing the correct config.txt file, and that you have used the "
+ "correct token (not the 'secret'!)\nConfig Location: " + config.getConfigLocation());
System.exit(1);
}
catch(IllegalArgumentException ex)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "Some aspect of the configuration is "
+ "invalid: " + ex + "\nConfig Location: " + config.getConfigLocation());
System.exit(1);
}
catch(ErrorResponseException ex)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nInvalid reponse returned when "
+ "attempting to connect, please make sure you're connected to the internet");
System.exit(1);
}
}
private static CommandClient createCommandClient(BotConfig config, SettingsManager settings, Bot bot)
{
// instantiate about command
AboutCommand aboutCommand = new AboutCommand(Color.BLUE.brighter(),
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot) (v" + OtherUtil.getCurrentVersion() + ")",
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
RECOMMENDED_PERMS);
aboutCommand.setIsAuthor(false);
aboutCommand.setReplacementCharacter("\uD83C\uDFB6"); // 🎶
// set up the command client
CommandClientBuilder cb = new CommandClientBuilder()
.setPrefix(config.getPrefix())
.setAlternativePrefix(config.getAltPrefix())
.setOwnerId(Long.toString(config.getOwnerId()))
.setEmojis(config.getSuccess(), config.getWarning(), config.getError())
.setHelpWord(config.getHelp())
.setLinkedCacheSize(200)
.setGuildSettingsManager(settings)
.addCommands(aboutCommand,
new PingCommand(),
new SettingsCmd(bot),
new LyricsCmd(bot),
new NowplayingCmd(bot),
new PlayCmd(bot),
new PlaylistsCmd(bot),
new QueueCmd(bot),
new RemoveCmd(bot),
new SearchCmd(bot),
new SCSearchCmd(bot),
new SeekCmd(bot),
new ShuffleCmd(bot),
new SkipCmd(bot),
new ForceRemoveCmd(bot),
new ForceskipCmd(bot),
new MoveTrackCmd(bot),
new PauseCmd(bot),
new PlaynextCmd(bot),
new RepeatCmd(bot),
new SkiptoCmd(bot),
new StopCmd(bot),
new VolumeCmd(bot),
new PrefixCmd(bot),
new QueueTypeCmd(bot),
new SetdjCmd(bot),
new SkipratioCmd(bot),
new SettcCmd(bot),
new SetvcCmd(bot),
new AutoplaylistCmd(bot),
new DebugCmd(bot),
new PlaylistCmd(bot),
new SetavatarCmd(bot),
new SetgameCmd(bot),
new SetnameCmd(bot),
new SetstatusCmd(bot),
new ShutdownCmd(bot)
);
// enable eval if applicable
if(config.useEval())
cb.addCommand(new EvalCmd(bot));
// set status if set in config
if(config.getStatus() != OnlineStatus.UNKNOWN)
cb.setStatus(config.getStatus());
// set game
if(config.getGame() == null)
cb.useDefaultGame();
else if(config.isGameNone())
cb.setActivity(null);
else
cb.setActivity(config.getGame());
return cb.build();
}
}
@@ -0,0 +1,126 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import java.util.concurrent.TimeUnit;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.events.ReadyEvent;
import net.dv8tion.jda.api.events.ShutdownEvent;
import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class Listener extends ListenerAdapter
{
private final Bot bot;
public Listener(Bot bot)
{
this.bot = bot;
}
@Override
public void onReady(ReadyEvent event)
{
if(event.getJDA().getGuildCache().isEmpty())
{
Logger log = LoggerFactory.getLogger("MusicBot");
log.warn("This bot is not on any guilds! Use the following link to add the bot to your guilds!");
log.warn(event.getJDA().getInviteUrl(JMusicBot.RECOMMENDED_PERMS));
}
credit(event.getJDA());
event.getJDA().getGuilds().forEach((guild) ->
{
try
{
String defpl = bot.getSettingsManager().getSettings(guild).getDefaultPlaylist();
VoiceChannel vc = bot.getSettingsManager().getSettings(guild).getVoiceChannel(guild);
if(defpl!=null && vc!=null && bot.getPlayerManager().setUpHandler(guild).playFromDefault())
{
guild.getAudioManager().openAudioConnection(vc);
}
}
catch(Exception ignore) {}
});
if(bot.getConfig().useUpdateAlerts())
{
bot.getThreadpool().scheduleWithFixedDelay(() ->
{
try
{
User owner = bot.getJDA().retrieveUserById(bot.getConfig().getOwnerId()).complete();
String currentVersion = OtherUtil.getCurrentVersion();
String latestVersion = OtherUtil.getLatestVersion();
if(latestVersion!=null && !currentVersion.equalsIgnoreCase(latestVersion))
{
String msg = String.format(OtherUtil.NEW_VERSION_AVAILABLE, currentVersion, latestVersion);
owner.openPrivateChannel().queue(pc -> pc.sendMessage(msg).queue());
}
}
catch(Exception ignored) {} // ignored
}, 0, 24, TimeUnit.HOURS);
}
}
@Override
public void onGuildMessageDelete(GuildMessageDeleteEvent event)
{
bot.getNowplayingHandler().onMessageDelete(event.getGuild(), event.getMessageIdLong());
}
@Override
public void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event)
{
bot.getAloneInVoiceHandler().onVoiceUpdate(event);
}
@Override
public void onShutdown(ShutdownEvent event)
{
bot.shutdown();
}
@Override
public void onGuildJoin(GuildJoinEvent event)
{
credit(event.getJDA());
}
// make sure people aren't adding clones to dbots
private void credit(JDA jda)
{
Guild dbots = jda.getGuildById(110373943822540800L);
if(dbots==null)
return;
if(bot.getConfig().getDBots())
return;
jda.getTextChannelById(119222314964353025L)
.sendMessage("This account is running JMusicBot. Please do not list bot clones on this server, <@"+bot.getConfig().getOwnerId()+">.").complete();
dbots.leave().queue();
}
}
@@ -0,0 +1,98 @@
/*
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.jagrosh.jmusicbot.Bot;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
import java.time.Instant;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
*
* @author Michaili K (mysteriouscursor+git@protonmail.com)
*/
public class AloneInVoiceHandler
{
private final Bot bot;
private final HashMap<Long, Instant> aloneSince = new HashMap<>();
private long aloneTimeUntilStop = 0;
public AloneInVoiceHandler(Bot bot)
{
this.bot = bot;
}
public void init()
{
aloneTimeUntilStop = bot.getConfig().getAloneTimeUntilStop();
if(aloneTimeUntilStop > 0)
bot.getThreadpool().scheduleWithFixedDelay(() -> check(), 0, 5, TimeUnit.SECONDS);
}
private void check()
{
Set<Long> toRemove = new HashSet<>();
for(Map.Entry<Long, Instant> entrySet: aloneSince.entrySet())
{
if(entrySet.getValue().getEpochSecond() > Instant.now().getEpochSecond() - aloneTimeUntilStop) continue;
Guild guild = bot.getJDA().getGuildById(entrySet.getKey());
if(guild == null)
{
toRemove.add(entrySet.getKey());
continue;
}
((AudioHandler) guild.getAudioManager().getSendingHandler()).stopAndClear();
guild.getAudioManager().closeAudioConnection();
toRemove.add(entrySet.getKey());
}
toRemove.forEach(id -> aloneSince.remove(id));
}
public void onVoiceUpdate(GuildVoiceUpdateEvent event)
{
if(aloneTimeUntilStop <= 0) return;
Guild guild = event.getEntity().getGuild();
if(!bot.getPlayerManager().hasHandler(guild)) return;
boolean alone = isAlone(guild);
boolean inList = aloneSince.containsKey(guild.getIdLong());
if(!alone && inList)
aloneSince.remove(guild.getIdLong());
else if(alone && !inList)
aloneSince.put(guild.getIdLong(), Instant.now());
}
private boolean isAlone(Guild guild)
{
if(guild.getAudioManager().getConnectedChannel() == null) return false;
return guild.getAudioManager().getConnectedChannel().getMembers().stream()
.noneMatch(x ->
!x.getVoiceState().isDeafened()
&& !x.getUser().isBot());
}
}
@@ -0,0 +1,331 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
import com.jagrosh.jmusicbot.queue.AbstractQueue;
import com.jagrosh.jmusicbot.settings.QueueType;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack;
import java.nio.ByteBuffer;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.audio.AudioSendHandler;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
{
public final static String PLAY_EMOJI = "\u25B6"; // ▶
public final static String PAUSE_EMOJI = "\u23F8"; // ⏸
public final static String STOP_EMOJI = "\u23F9"; // ⏹
private final List<AudioTrack> defaultQueue = new LinkedList<>();
private final Set<String> votes = new HashSet<>();
private final PlayerManager manager;
private final AudioPlayer audioPlayer;
private final long guildId;
private AudioFrame lastFrame;
private AbstractQueue<QueuedTrack> queue;
protected AudioHandler(PlayerManager manager, Guild guild, AudioPlayer player)
{
this.manager = manager;
this.audioPlayer = player;
this.guildId = guild.getIdLong();
this.setQueueType(manager.getBot().getSettingsManager().getSettings(guildId).getQueueType());
}
public void setQueueType(QueueType type)
{
queue = type.createInstance(queue);
}
public int addTrackToFront(QueuedTrack qtrack)
{
if(audioPlayer.getPlayingTrack()==null)
{
audioPlayer.playTrack(qtrack.getTrack());
return -1;
}
else
{
queue.addAt(0, qtrack);
return 0;
}
}
public int addTrack(QueuedTrack qtrack)
{
if(audioPlayer.getPlayingTrack()==null)
{
audioPlayer.playTrack(qtrack.getTrack());
return -1;
}
else
return queue.add(qtrack);
}
public AbstractQueue<QueuedTrack> getQueue()
{
return queue;
}
public void stopAndClear()
{
queue.clear();
defaultQueue.clear();
audioPlayer.stopTrack();
//current = null;
}
public boolean isMusicPlaying(JDA jda)
{
return guild(jda).getSelfMember().getVoiceState().inVoiceChannel() && audioPlayer.getPlayingTrack()!=null;
}
public Set<String> getVotes()
{
return votes;
}
public AudioPlayer getPlayer()
{
return audioPlayer;
}
public RequestMetadata getRequestMetadata()
{
if(audioPlayer.getPlayingTrack() == null)
return RequestMetadata.EMPTY;
RequestMetadata rm = audioPlayer.getPlayingTrack().getUserData(RequestMetadata.class);
return rm == null ? RequestMetadata.EMPTY : rm;
}
public boolean playFromDefault()
{
if(!defaultQueue.isEmpty())
{
audioPlayer.playTrack(defaultQueue.remove(0));
return true;
}
Settings settings = manager.getBot().getSettingsManager().getSettings(guildId);
if(settings==null || settings.getDefaultPlaylist()==null)
return false;
Playlist pl = manager.getBot().getPlaylistLoader().getPlaylist(settings.getDefaultPlaylist());
if(pl==null || pl.getItems().isEmpty())
return false;
pl.loadTracks(manager, (at) ->
{
if(audioPlayer.getPlayingTrack()==null)
audioPlayer.playTrack(at);
else
defaultQueue.add(at);
}, () ->
{
if(pl.getTracks().isEmpty() && !manager.getBot().getConfig().getStay())
manager.getBot().closeAudioConnection(guildId);
});
return true;
}
// Audio Events
@Override
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason)
{
RepeatMode repeatMode = manager.getBot().getSettingsManager().getSettings(guildId).getRepeatMode();
// if the track ended normally, and we're in repeat mode, re-add it to the queue
if(endReason==AudioTrackEndReason.FINISHED && repeatMode != RepeatMode.OFF)
{
QueuedTrack clone = new QueuedTrack(track.makeClone(), track.getUserData(RequestMetadata.class));
if(repeatMode == RepeatMode.ALL)
queue.add(clone);
else
queue.addAt(0, clone);
}
if(queue.isEmpty())
{
if(!playFromDefault())
{
manager.getBot().getNowplayingHandler().onTrackUpdate(null);
if(!manager.getBot().getConfig().getStay())
manager.getBot().closeAudioConnection(guildId);
// unpause, in the case when the player was paused and the track has been skipped.
// this is to prevent the player being paused next time it's being used.
player.setPaused(false);
}
}
else
{
QueuedTrack qt = queue.pull();
player.playTrack(qt.getTrack());
}
}
@Override
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) {
LoggerFactory.getLogger("AudioHandler").error("Track " + track.getIdentifier() + " has failed to play", exception);
}
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track)
{
votes.clear();
manager.getBot().getNowplayingHandler().onTrackUpdate(track);
}
// Formatting
public Message getNowPlaying(JDA jda)
{
if(isMusicPlaying(jda))
{
Guild guild = guild(jda);
AudioTrack track = audioPlayer.getPlayingTrack();
MessageBuilder mb = new MessageBuilder();
mb.append(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing in "+guild.getSelfMember().getVoiceState().getChannel().getAsMention()+"...**"));
EmbedBuilder eb = new EmbedBuilder();
eb.setColor(guild.getSelfMember().getColor());
RequestMetadata rm = getRequestMetadata();
if(rm.getOwner() != 0L)
{
User u = guild.getJDA().getUserById(rm.user.id);
if(u==null)
eb.setAuthor(FormatUtil.formatUsername(rm.user), null, rm.user.avatar);
else
eb.setAuthor(FormatUtil.formatUsername(u), null, u.getEffectiveAvatarUrl());
}
try
{
eb.setTitle(track.getInfo().title, track.getInfo().uri);
}
catch(Exception e)
{
eb.setTitle(track.getInfo().title);
}
if(track instanceof YoutubeAudioTrack && manager.getBot().getConfig().useNPImages())
{
eb.setThumbnail("https://img.youtube.com/vi/"+track.getIdentifier()+"/mqdefault.jpg");
}
if(track.getInfo().author != null && !track.getInfo().author.isEmpty())
eb.setFooter("Source: " + track.getInfo().author, null);
double progress = (double)audioPlayer.getPlayingTrack().getPosition()/track.getDuration();
eb.setDescription(getStatusEmoji()
+ " "+FormatUtil.progressBar(progress)
+ " `[" + TimeUtil.formatTime(track.getPosition()) + "/" + TimeUtil.formatTime(track.getDuration()) + "]` "
+ FormatUtil.volumeIcon(audioPlayer.getVolume()));
return mb.setEmbeds(eb.build()).build();
}
else return null;
}
public Message getNoMusicPlaying(JDA jda)
{
Guild guild = guild(jda);
return new MessageBuilder()
.setContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing...**"))
.setEmbeds(new EmbedBuilder()
.setTitle("No music playing")
.setDescription(STOP_EMOJI+" "+FormatUtil.progressBar(-1)+" "+FormatUtil.volumeIcon(audioPlayer.getVolume()))
.setColor(guild.getSelfMember().getColor())
.build()).build();
}
public String getStatusEmoji()
{
return audioPlayer.isPaused() ? PAUSE_EMOJI : PLAY_EMOJI;
}
// Audio Send Handler methods
/*@Override
public boolean canProvide()
{
if (lastFrame == null)
lastFrame = audioPlayer.provide();
return lastFrame != null;
}
@Override
public byte[] provide20MsAudio()
{
if (lastFrame == null)
lastFrame = audioPlayer.provide();
byte[] data = lastFrame != null ? lastFrame.getData() : null;
lastFrame = null;
return data;
}*/
@Override
public boolean canProvide()
{
lastFrame = audioPlayer.provide();
return lastFrame != null;
}
@Override
public ByteBuffer provide20MsAudio()
{
return ByteBuffer.wrap(lastFrame.getData());
}
@Override
public boolean isOpus()
{
return true;
}
// Private methods
private Guild guild(JDA jda)
{
return jda.getGuildById(guildId);
}
}
@@ -0,0 +1,123 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.entities.Pair;
import com.jagrosh.jmusicbot.settings.Settings;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.exceptions.PermissionException;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class NowplayingHandler
{
private final Bot bot;
private final HashMap<Long,Pair<Long,Long>> lastNP; // guild -> channel,message
public NowplayingHandler(Bot bot)
{
this.bot = bot;
this.lastNP = new HashMap<>();
}
public void init()
{
if(!bot.getConfig().useNPImages())
bot.getThreadpool().scheduleWithFixedDelay(() -> updateAll(), 0, 5, TimeUnit.SECONDS);
}
public void setLastNPMessage(Message m)
{
lastNP.put(m.getGuild().getIdLong(), new Pair<>(m.getTextChannel().getIdLong(), m.getIdLong()));
}
public void clearLastNPMessage(Guild guild)
{
lastNP.remove(guild.getIdLong());
}
private void updateAll()
{
Set<Long> toRemove = new HashSet<>();
for(long guildId: lastNP.keySet())
{
Guild guild = bot.getJDA().getGuildById(guildId);
if(guild==null)
{
toRemove.add(guildId);
continue;
}
Pair<Long,Long> pair = lastNP.get(guildId);
TextChannel tc = guild.getTextChannelById(pair.getKey());
if(tc==null)
{
toRemove.add(guildId);
continue;
}
AudioHandler handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
Message msg = handler.getNowPlaying(bot.getJDA());
if(msg==null)
{
msg = handler.getNoMusicPlaying(bot.getJDA());
toRemove.add(guildId);
}
try
{
tc.editMessageById(pair.getValue(), msg).queue(m->{}, t -> lastNP.remove(guildId));
}
catch(Exception e)
{
toRemove.add(guildId);
}
}
toRemove.forEach(id -> lastNP.remove(id));
}
// "event"-based methods
public void onTrackUpdate(AudioTrack track)
{
// update bot status if applicable
if(bot.getConfig().getSongInStatus())
{
if(track!=null && bot.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()<=1)
bot.getJDA().getPresence().setActivity(Activity.listening(track.getInfo().title));
else
bot.resetGame();
}
}
public void onMessageDelete(Guild guild, long messageId)
{
Pair<Long,Long> pair = lastNP.get(guild.getIdLong());
if(pair==null)
return;
if(pair.getValue() == messageId)
lastNP.remove(guild.getIdLong());
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.dunctebot.sourcemanagers.DuncteBotSources;
import com.jagrosh.jmusicbot.Bot;
import com.sedmelluq.discord.lavaplayer.container.MediaContainerRegistry;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
import com.sedmelluq.discord.lavaplayer.source.bandcamp.BandcampAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.beam.BeamAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.getyarn.GetyarnAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.nico.NicoAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager;
import dev.lavalink.youtube.YoutubeAudioSourceManager;
import net.dv8tion.jda.api.entities.Guild;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PlayerManager extends DefaultAudioPlayerManager
{
private final Bot bot;
public PlayerManager(Bot bot)
{
this.bot = bot;
}
public void init()
{
TransformativeAudioSourceManager.createTransforms(bot.getConfig().getTransforms()).forEach(t -> registerSourceManager(t));
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
registerSourceManager(yt);
registerSourceManager(SoundCloudAudioSourceManager.createDefault());
registerSourceManager(new BandcampAudioSourceManager());
registerSourceManager(new VimeoAudioSourceManager());
registerSourceManager(new TwitchStreamAudioSourceManager());
registerSourceManager(new BeamAudioSourceManager());
registerSourceManager(new GetyarnAudioSourceManager());
registerSourceManager(new NicoAudioSourceManager());
registerSourceManager(new HttpAudioSourceManager(MediaContainerRegistry.DEFAULT_REGISTRY));
AudioSourceManagers.registerLocalSource(this);
DuncteBotSources.registerAll(this, "en-US");
}
public Bot getBot()
{
return bot;
}
public boolean hasHandler(Guild guild)
{
return guild.getAudioManager().getSendingHandler()!=null;
}
public AudioHandler setUpHandler(Guild guild)
{
AudioHandler handler;
if(guild.getAudioManager().getSendingHandler()==null)
{
AudioPlayer player = createPlayer();
player.setVolume(bot.getSettingsManager().getSettings(guild).getVolume());
handler = new AudioHandler(this, guild, player);
player.addListener(handler);
guild.getAudioManager().setSendingHandler(handler);
}
else
handler = (AudioHandler) guild.getAudioManager().getSendingHandler();
return handler;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import com.jagrosh.jmusicbot.queue.Queueable;
import net.dv8tion.jda.api.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueuedTrack implements Queueable
{
private final AudioTrack track;
private final RequestMetadata requestMetadata;
public QueuedTrack(AudioTrack track, RequestMetadata rm)
{
this.track = track;
this.track.setUserData(rm == null ? RequestMetadata.EMPTY : rm);
this.requestMetadata = rm;
if (this.track.isSeekable() && rm != null)
track.setPosition(rm.requestInfo.startTimestamp);
}
@Override
public long getIdentifier()
{
return requestMetadata.getOwner();
}
public AudioTrack getTrack()
{
return track;
}
public RequestMetadata getRequestMetadata()
{
return requestMetadata;
}
@Override
public String toString()
{
String entry = "`[" + TimeUtil.formatTime(track.getDuration()) + "]` ";
AudioTrackInfo trackInfo = track.getInfo();
entry = entry + (trackInfo.uri.startsWith("http") ? "[**" + trackInfo.title + "**]("+trackInfo.uri+")" : "**" + trackInfo.title + "**");
return entry + " - <@" + track.getUserData(RequestMetadata.class).getOwner() + ">";
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import net.dv8tion.jda.api.entities.User;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class RequestMetadata
{
public static final RequestMetadata EMPTY = new RequestMetadata(null, null);
public final UserInfo user;
public final RequestInfo requestInfo;
public RequestMetadata(User user, RequestInfo requestInfo)
{
this.user = user == null ? null : new UserInfo(user.getIdLong(), user.getName(), user.getDiscriminator(), user.getEffectiveAvatarUrl());
this.requestInfo = requestInfo;
}
public long getOwner()
{
return user == null ? 0L : user.id;
}
public static RequestMetadata fromResultHandler(AudioTrack track, CommandEvent event)
{
return new RequestMetadata(event.getAuthor(), new RequestInfo(event.getArgs(), track.getInfo().uri));
}
public static class RequestInfo
{
public final String query, url;
public final long startTimestamp;
public RequestInfo(String query, String url)
{
this(query, url, tryGetTimestamp(query));
}
private RequestInfo(String query, String url, long startTimestamp)
{
this.url = url;
this.query = query;
this.startTimestamp = startTimestamp;
}
private static final Pattern youtubeTimestampPattern = Pattern.compile("youtu(?:\\.be|be\\..+)/.*\\?.*(?!.*list=)t=([\\dhms]+)");
private static long tryGetTimestamp(String url)
{
Matcher matcher = youtubeTimestampPattern.matcher(url);
return matcher.find() ? TimeUtil.parseUnitTime(matcher.group(1)) : 0;
}
}
public static class UserInfo
{
public final long id;
public final String username, discrim, avatar;
private UserInfo(long id, String username, String discrim, String avatar)
{
this.id = id;
this.username = username;
this.discrim = discrim;
this.avatar = avatar;
}
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.track.AudioItem;
import com.sedmelluq.discord.lavaplayer.track.AudioReference;
import com.typesafe.config.Config;
import dev.lavalink.youtube.YoutubeAudioSourceManager;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class TransformativeAudioSourceManager extends YoutubeAudioSourceManager
{
private final static Logger log = LoggerFactory.getLogger(TransformativeAudioSourceManager.class);
private final String name, regex, replacement, selector, format;
public TransformativeAudioSourceManager(String name, Config object)
{
this(name, object.getString("regex"), object.getString("replacement"), object.getString("selector"), object.getString("format"));
}
public TransformativeAudioSourceManager(String name, String regex, String replacement, String selector, String format)
{
this.name = name;
this.regex = regex;
this.replacement = replacement;
this.selector = selector;
this.format = format;
}
@Override
public String getSourceName()
{
return name;
}
@Override
public AudioItem loadItem(AudioPlayerManager apm, AudioReference ar)
{
if(ar.identifier == null || !ar.identifier.matches(regex))
return null;
try
{
String url = ar.identifier.replaceAll(regex, replacement);
Document doc = Jsoup.connect(url).get();
String value = doc.selectFirst(selector).ownText();
String formattedValue = String.format(format, value);
return super.loadItem(apm, new AudioReference(formattedValue, null));
}
catch (PatternSyntaxException ex)
{
log.info(String.format("Invalid pattern syntax '%s' in source '%s'", regex, name));
}
catch (IOException ex)
{
log.warn(String.format("Failed to resolve URL in source '%s': ", name), ex);
}
catch (Exception ex)
{
log.warn(String.format("Exception in source '%s'", name), ex);
}
return null;
}
public static List<TransformativeAudioSourceManager> createTransforms(Config transforms)
{
try
{
return transforms.root().entrySet().stream()
.map(e -> new TransformativeAudioSourceManager(e.getKey(), transforms.getConfig(e.getKey())))
.collect(Collectors.toList());
}
catch (Exception ex)
{
log.warn("Invalid transform ", ex);
return Collections.emptyList();
}
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.command.Command;
import net.dv8tion.jda.api.Permission;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public abstract class AdminCommand extends Command
{
public AdminCommand()
{
this.category = new Category("Admin", event ->
{
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
return true;
if(event.getGuild()==null)
return true;
return event.getMember().hasPermission(Permission.MANAGE_SERVER);
});
this.guildOnly = true;
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.settings.Settings;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Role;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public abstract class DJCommand extends MusicCommand
{
public DJCommand(Bot bot)
{
super(bot);
this.category = new Category("DJ", event -> checkDJPermission(event));
}
public static boolean checkDJPermission(CommandEvent event)
{
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
return true;
if(event.getGuild()==null)
return true;
if(event.getMember().hasPermission(Permission.MANAGE_SERVER))
return true;
Settings settings = event.getClient().getSettingsFor(event.getGuild());
Role dj = settings.getRole(event.getGuild());
return dj!=null && (event.getMember().getRoles().contains(dj) || dj.getIdLong()==event.getGuild().getIdLong());
}
}
@@ -0,0 +1,102 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public abstract class MusicCommand extends Command
{
protected final Bot bot;
protected boolean bePlaying;
protected boolean beListening;
public MusicCommand(Bot bot)
{
this.bot = bot;
this.guildOnly = true;
this.category = new Category("Music");
}
@Override
protected void execute(CommandEvent event)
{
Settings settings = event.getClient().getSettingsFor(event.getGuild());
TextChannel tchannel = settings.getTextChannel(event.getGuild());
if(tchannel!=null && !event.getTextChannel().equals(tchannel))
{
try
{
event.getMessage().delete().queue();
} catch(PermissionException ignore){}
event.replyInDm(event.getClient().getError()+" You can only use that command in "+tchannel.getAsMention()+"!");
return;
}
bot.getPlayerManager().setUpHandler(event.getGuild()); // no point constantly checking for this later
if(bePlaying && !((AudioHandler)event.getGuild().getAudioManager().getSendingHandler()).isMusicPlaying(event.getJDA()))
{
event.reply(event.getClient().getError()+" There must be music playing to use that!");
return;
}
if(beListening)
{
VoiceChannel current = event.getGuild().getSelfMember().getVoiceState().getChannel();
if(current==null)
current = settings.getVoiceChannel(event.getGuild());
GuildVoiceState userState = event.getMember().getVoiceState();
if(!userState.inVoiceChannel() || userState.isDeafened() || (current!=null && !userState.getChannel().equals(current)))
{
event.replyError("You must be listening in "+(current==null ? "a voice channel" : current.getAsMention())+" to use that!");
return;
}
VoiceChannel afkChannel = userState.getGuild().getAfkChannel();
if(afkChannel != null && afkChannel.equals(userState.getChannel()))
{
event.replyError("You cannot use that command in an AFK channel!");
return;
}
if(!event.getGuild().getSelfMember().getVoiceState().inVoiceChannel())
{
try
{
event.getGuild().getAudioManager().openAudioConnection(userState.getChannel());
}
catch(PermissionException ex)
{
event.reply(event.getClient().getError()+" I am unable to connect to "+userState.getChannel().getAsMention()+"!");
return;
}
}
}
doCommand(event);
}
public abstract void doCommand(CommandEvent event);
}
@@ -0,0 +1,31 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.command.Command;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public abstract class OwnerCommand extends Command
{
public OwnerCommand()
{
this.category = new Category("Owner");
this.ownerCommand = true;
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.admin;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PrefixCmd extends AdminCommand
{
public PrefixCmd(Bot bot)
{
this.name = "prefix";
this.help = "sets a server-specific prefix";
this.arguments = "<prefix|NONE>";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.replyError("Please include a prefix or NONE");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
s.setPrefix(null);
event.replySuccess("Prefix cleared.");
}
else
{
s.setPrefix(event.getArgs());
event.replySuccess("Custom prefix set to `" + event.getArgs() + "` on *" + event.getGuild().getName() + "*");
}
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.admin;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.QueueType;
import com.jagrosh.jmusicbot.settings.Settings;
/**
*
* @author Wolfgang Schwendtbauer
*/
public class QueueTypeCmd extends AdminCommand
{
public QueueTypeCmd(Bot bot)
{
super();
this.name = "queuetype";
this.help = "changes the queue type";
this.arguments = "[" + String.join("|", QueueType.getNames()) + "]";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
protected void execute(CommandEvent event)
{
String args = event.getArgs();
QueueType value;
Settings settings = event.getClient().getSettingsFor(event.getGuild());
if (args.isEmpty())
{
QueueType currentType = settings.getQueueType();
event.reply(currentType.getEmoji() + " Current queue type is: `" + currentType.getUserFriendlyName() + "`.");
return;
}
try
{
value = QueueType.valueOf(args.toUpperCase());
}
catch (IllegalArgumentException e)
{
event.replyError("Invalid queue type. Valid types are: [" + String.join("|", QueueType.getNames()) + "]");
return;
}
if (settings.getQueueType() != value)
{
settings.setQueueType(value);
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
if (handler != null)
handler.setQueueType(value);
}
event.reply(value.getEmoji() + " Queue type was set to `" + value.getUserFriendlyName() + "`.");
}
}
@@ -1,69 +1,70 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import java.util.List;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.utils.FinderUtil;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.Role;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetdjCmd extends Command {
private final Bot bot;
public SetdjCmd(Bot bot)
{
this.bot = bot;
this.name = "setdj";
this.help = "sets the DJ role for certain music commands";
this.arguments = "<rolename|NONE>";
this.guildOnly = true;
this.category = bot.ADMIN;
}
@Override
protected void execute(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a role name or NONE");
}
else if(event.getArgs().equalsIgnoreCase("none"))
{
bot.clearTextChannel(event.getGuild());
event.reply(event.getClient().getSuccess()+" DJ role cleared; Only Admins can use the DJ commands.");
}
else
{
List<Role> list = FinderUtil.findRole(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Roles found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfRoles(list, event.getArgs()));
else
{
bot.setRole(list.get(0));
event.reply(event.getClient().getSuccess()+" DJ commands can now be used by users with the **"+list.get(0).getName()+"** role.");
}
}
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.admin;
import java.util.List;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.entities.Role;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetdjCmd extends AdminCommand
{
public SetdjCmd(Bot bot)
{
this.name = "setdj";
this.help = "sets the DJ role for certain music commands";
this.arguments = "<rolename|NONE>";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a role name or NONE");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
s.setDJRole(null);
event.reply(event.getClient().getSuccess()+" DJ role cleared; Only Admins can use the DJ commands.");
}
else
{
List<Role> list = FinderUtil.findRoles(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Roles found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfRoles(list, event.getArgs()));
else
{
s.setDJRole(list.get(0));
event.reply(event.getClient().getSuccess()+" DJ commands can now be used by users with the **"+list.get(0).getName()+"** role.");
}
}
}
}
@@ -1,69 +1,70 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import java.util.List;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.utils.FinderUtil;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.TextChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SettcCmd extends Command {
private final Bot bot;
public SettcCmd(Bot bot)
{
this.bot = bot;
this.name = "settc";
this.help = "sets the text channel for music commands";
this.arguments = "<channel|NONE>";
this.guildOnly = true;
this.category = bot.ADMIN;
}
@Override
protected void execute(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a text channel or NONE");
}
else if(event.getArgs().equalsIgnoreCase("none"))
{
bot.clearTextChannel(event.getGuild());
event.reply(event.getClient().getSuccess()+" Music commands can now be used in any channel");
}
else
{
List<TextChannel> list = FinderUtil.findTextChannel(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Text Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfTChannels(list, event.getArgs()));
else
{
bot.setTextChannel(list.get(0));
event.reply(event.getClient().getSuccess()+" Music commands can now only be used in <#"+list.get(0).getId()+">");
}
}
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.admin;
import java.util.List;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.entities.TextChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SettcCmd extends AdminCommand
{
public SettcCmd(Bot bot)
{
this.name = "settc";
this.help = "sets the text channel for music commands";
this.arguments = "<channel|NONE>";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a text channel or NONE");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
s.setTextChannel(null);
event.reply(event.getClient().getSuccess()+" Music commands can now be used in any channel");
}
else
{
List<TextChannel> list = FinderUtil.findTextChannels(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Text Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfTChannels(list, event.getArgs()));
else
{
s.setTextChannel(list.get(0));
event.reply(event.getClient().getSuccess()+" Music commands can now only be used in <#"+list.get(0).getId()+">");
}
}
}
}
@@ -1,69 +1,69 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import java.util.List;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.utils.FinderUtil;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetvcCmd extends Command {
private final Bot bot;
public SetvcCmd(Bot bot)
{
this.bot = bot;
this.name = "setvc";
this.help = "sets the voice channel for playing music";
this.arguments = "<channel|NONE>";
this.guildOnly = true;
this.category = bot.ADMIN;
}
@Override
protected void execute(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a voice channel or NONE");
}
else if(event.getArgs().equalsIgnoreCase("none"))
{
bot.clearVoiceChannel(event.getGuild());
event.reply(event.getClient().getSuccess()+" Music can now be played in any channel");
}
else
{
List<VoiceChannel> list = FinderUtil.findVoiceChannel(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Voice Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfVChannels(list, event.getArgs()));
else
{
bot.setVoiceChannel(list.get(0));
event.reply(event.getClient().getSuccess()+" Music can now only be played in **"+list.get(0).getName()+"**");
}
}
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.admin;
import java.util.List;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.entities.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetvcCmd extends AdminCommand
{
public SetvcCmd(Bot bot)
{
this.name = "setvc";
this.help = "sets the voice channel for playing music";
this.arguments = "<channel|NONE>";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a voice channel or NONE");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
s.setVoiceChannel(null);
event.reply(event.getClient().getSuccess()+" Music can now be played in any channel");
}
else
{
List<VoiceChannel> list = FinderUtil.findVoiceChannels(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Voice Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfVChannels(list, event.getArgs()));
else
{
s.setVoiceChannel(list.get(0));
event.reply(event.getClient().getSuccess()+" Music can now only be played in "+list.get(0).getAsMention());
}
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.admin;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class SkipratioCmd extends AdminCommand
{
public SkipratioCmd(Bot bot)
{
this.name = "setskip";
this.help = "sets a server-specific skip percentage";
this.arguments = "<0 - 100>";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
protected void execute(CommandEvent event)
{
try
{
int val = Integer.parseInt(event.getArgs().endsWith("%") ? event.getArgs().substring(0,event.getArgs().length()-1) : event.getArgs());
if( val < 0 || val > 100)
{
event.replyError("The provided value must be between 0 and 100!");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
s.setSkipRatio(val / 100.0);
event.replySuccess("Skip percentage has been set to `" + val + "%` of listeners on *" + event.getGuild().getName() + "*");
}
catch(NumberFormatException ex)
{
event.replyError("Please include an integer between 0 and 100 (default is 55). This number is the percentage of listening users that must vote to skip a song.");
}
}
}
@@ -0,0 +1,120 @@
/*
* Copyright 2019 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
import com.jagrosh.jdautilities.menu.OrderedMenu;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
*
* @author Michaili K.
*/
public class ForceRemoveCmd extends DJCommand
{
public ForceRemoveCmd(Bot bot)
{
super(bot);
this.name = "forceremove";
this.help = "removes all entries by a user from the queue";
this.arguments = "<user>";
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = false;
this.bePlaying = true;
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
}
@Override
public void doCommand(CommandEvent event)
{
if (event.getArgs().isEmpty())
{
event.replyError("You need to mention a user!");
return;
}
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
if (handler.getQueue().isEmpty())
{
event.replyError("There is nothing in the queue!");
return;
}
User target;
List<Member> found = FinderUtil.findMembers(event.getArgs(), event.getGuild());
if(found.isEmpty())
{
event.replyError("Unable to find the user!");
return;
}
else if(found.size()>1)
{
OrderedMenu.Builder builder = new OrderedMenu.Builder();
for(int i=0; i<found.size() && i<4; i++)
{
Member member = found.get(i);
builder.addChoice("**"+member.getUser().getName()+"**#"+member.getUser().getDiscriminator());
}
builder
.setSelection((msg, i) -> removeAllEntries(found.get(i-1).getUser(), event))
.setText("Found multiple users:")
.setColor(event.getSelfMember().getColor())
.useNumbers()
.setUsers(event.getAuthor())
.useCancelButton(true)
.setCancel((msg) -> {})
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES)
.build().display(event.getChannel());
return;
}
else
{
target = found.get(0).getUser();
}
removeAllEntries(target, event);
}
private void removeAllEntries(User target, CommandEvent event)
{
int count = ((AudioHandler) event.getGuild().getAudioManager().getSendingHandler()).getQueue().removeAll(target.getIdLong());
if (count == 0)
{
event.replyWarning("**"+target.getName()+"** doesn't have any songs in the queue!");
}
else
{
event.replySuccess("Successfully removed `"+count+"` entries from "+FormatUtil.formatUsername(target)+".");
}
}
}
@@ -1,48 +1,49 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ForceskipCmd extends MusicCommand {
public ForceskipCmd(Bot bot)
{
super(bot);
this.name = "forceskip";
this.help = "skips the current song";
this.aliases = new String[]{"modskip"};
this.bePlaying = true;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
User u = event.getJDA().getUserById(handler.getCurrentTrack().getIdentifier());
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getCurrentTrack().getTrack().getInfo().title
+"** (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
handler.getPlayer().stopTrack();
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.RequestMetadata;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ForceskipCmd extends DJCommand
{
public ForceskipCmd(Bot bot)
{
super(bot);
this.name = "forceskip";
this.help = "skips the current song";
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
RequestMetadata rm = handler.getRequestMetadata();
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title
+"** "+(rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + FormatUtil.formatUsername(rm.user) + "**)"));
handler.getPlayer().stopTrack();
}
}
@@ -0,0 +1,85 @@
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.queue.AbstractQueue;
/**
* Command that provides users the ability to move a track in the playlist.
*/
public class MoveTrackCmd extends DJCommand
{
public MoveTrackCmd(Bot bot)
{
super(bot);
this.name = "movetrack";
this.help = "move a track in the current queue to a different position";
this.arguments = "<from> <to>";
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
int from;
int to;
String[] parts = event.getArgs().split("\\s+", 2);
if(parts.length < 2)
{
event.replyError("Please include two valid indexes.");
return;
}
try
{
// Validate the args
from = Integer.parseInt(parts[0]);
to = Integer.parseInt(parts[1]);
}
catch (NumberFormatException e)
{
event.replyError("Please provide two valid indexes.");
return;
}
if (from == to)
{
event.replyError("Can't move a track to the same position.");
return;
}
// Validate that from and to are available
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
AbstractQueue<QueuedTrack> queue = handler.getQueue();
if (isUnavailablePosition(queue, from))
{
String reply = String.format("`%d` is not a valid position in the queue!", from);
event.replyError(reply);
return;
}
if (isUnavailablePosition(queue, to))
{
String reply = String.format("`%d` is not a valid position in the queue!", to);
event.replyError(reply);
return;
}
// Move the track
QueuedTrack track = queue.moveItem(from - 1, to - 1);
String trackTitle = track.getTrack().getInfo().title;
String reply = String.format("Moved **%s** from position `%d` to `%d`.", trackTitle, from, to);
event.replySuccess(reply);
}
private static boolean isUnavailablePosition(AbstractQueue<QueuedTrack> queue, int position)
{
return (position < 1 || position > queue.size());
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PauseCmd extends DJCommand
{
public PauseCmd(Bot bot)
{
super(bot);
this.name = "pause";
this.help = "pauses the current song";
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getPlayer().isPaused())
{
event.replyWarning("The player is already paused! Use `"+event.getClient().getPrefix()+"play` to unpause!");
return;
}
handler.getPlayer().setPaused(true);
event.replySuccess("Paused **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**. Type `"+event.getClient().getPrefix()+"play` to unpause!");
}
}
@@ -0,0 +1,131 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.audio.RequestMetadata;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import net.dv8tion.jda.api.entities.Message;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PlaynextCmd extends DJCommand
{
private final String loadingEmoji;
public PlaynextCmd(Bot bot)
{
super(bot);
this.loadingEmoji = bot.getConfig().getLoading();
this.name = "playnext";
this.arguments = "<title|URL>";
this.help = "plays a single song next";
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = false;
}
@Override
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty())
{
event.replyWarning("Please include a song title or URL!");
return;
}
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
? event.getArgs().substring(1,event.getArgs().length()-1)
: event.getArgs().isEmpty() ? event.getMessage().getAttachments().get(0).getUrl() : event.getArgs();
event.reply(loadingEmoji+" Loading... `["+args+"]`", m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false)));
}
private class ResultHandler implements AudioLoadResultHandler
{
private final Message m;
private final CommandEvent event;
private final boolean ytsearch;
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
{
this.m = m;
this.event = event;
this.ytsearch = ytsearch;
}
private void loadSingle(AudioTrack track)
{
if(bot.getConfig().isTooLong(track))
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+ TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrackToFront(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
m.editMessage(addMsg).queue();
}
@Override
public void trackLoaded(AudioTrack track)
{
loadSingle(track);
}
@Override
public void playlistLoaded(AudioPlaylist playlist)
{
AudioTrack single;
if(playlist.getTracks().size()==1 || playlist.isSearchResult())
single = playlist.getSelectedTrack()==null ? playlist.getTracks().get(0) : playlist.getSelectedTrack();
else if (playlist.getSelectedTrack()!=null)
single = playlist.getSelectedTrack();
else
single = playlist.getTracks().get(0);
loadSingle(single);
}
@Override
public void noMatches()
{
if(ytsearch)
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
else
bot.getPlayerManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event,true));
}
@Override
public void loadFailed(FriendlyException throwable)
{
if(throwable.severity==FriendlyException.Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.jagrosh.jmusicbot.settings.Settings;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class RepeatCmd extends DJCommand
{
public RepeatCmd(Bot bot)
{
super(bot);
this.name = "repeat";
this.help = "re-adds music to the queue when finished";
this.arguments = "[off|all|single]";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = true;
}
// override musiccommand's execute because we don't actually care where this is used
@Override
protected void execute(CommandEvent event)
{
String args = event.getArgs();
RepeatMode value;
Settings settings = event.getClient().getSettingsFor(event.getGuild());
if(args.isEmpty())
{
if(settings.getRepeatMode() == RepeatMode.OFF)
value = RepeatMode.ALL;
else
value = RepeatMode.OFF;
}
else if(args.equalsIgnoreCase("false") || args.equalsIgnoreCase("off"))
{
value = RepeatMode.OFF;
}
else if(args.equalsIgnoreCase("true") || args.equalsIgnoreCase("on") || args.equalsIgnoreCase("all"))
{
value = RepeatMode.ALL;
}
else if(args.equalsIgnoreCase("one") || args.equalsIgnoreCase("single"))
{
value = RepeatMode.SINGLE;
}
else
{
event.replyError("Valid options are `off`, `all` or `single` (or leave empty to toggle between `off` and `all`)");
return;
}
settings.setRepeatMode(value);
event.replySuccess("Repeat mode is now `"+value.getUserFriendlyName()+"`");
}
@Override
public void doCommand(CommandEvent event) { /* Intentionally Empty */ }
}
@@ -1,63 +1,62 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SkiptoCmd extends MusicCommand {
public SkiptoCmd(Bot bot)
{
super(bot);
this.name = "skipto";
this.help = "skips to the current song";
this.arguments = "<position>";
this.aliases = new String[]{"jumpto"};
this.bePlaying = true;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
int index = 0;
try
{
index = Integer.parseInt(event.getArgs());
}
catch(NumberFormatException e)
{
event.reply(event.getClient().getError()+" `"+event.getArgs()+"` is not a valid integer!");
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(index<1 || index>handler.getQueue().size())
{
event.reply(event.getClient().getError()+" Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
return;
}
handler.getQueue().skip(index-1);
event.reply(event.getClient().getSuccess()+" Skipped to **"+handler.getQueue().get(0).getTrack().getInfo().title+"**");
handler.getPlayer().stopTrack();
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SkiptoCmd extends DJCommand
{
public SkiptoCmd(Bot bot)
{
super(bot);
this.name = "skipto";
this.help = "skips to the specified song";
this.arguments = "<position>";
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
int index = 0;
try
{
index = Integer.parseInt(event.getArgs());
}
catch(NumberFormatException e)
{
event.reply(event.getClient().getError()+" `"+event.getArgs()+"` is not a valid integer!");
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(index<1 || index>handler.getQueue().size())
{
event.reply(event.getClient().getError()+" Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
return;
}
handler.getQueue().skip(index-1);
event.reply(event.getClient().getSuccess()+" Skipped to **"+handler.getQueue().get(0).getTrack().getInfo().title+"**");
handler.getPlayer().stopTrack();
}
}
@@ -1,46 +1,46 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class StopCmd extends MusicCommand {
public StopCmd(Bot bot)
{
super(bot);
this.name = "stop";
this.help = "stops the current song and clears the queue";
this.bePlaying = true;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
handler.getQueue().clear();
handler.getPlayer().stopTrack();
event.getGuild().getAudioManager().closeAudioConnection();
event.reply(event.getClient().getSuccess()+" The player has stopped and the queue has been cleared.");
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class StopCmd extends DJCommand
{
public StopCmd(Bot bot)
{
super(bot);
this.name = "stop";
this.help = "stops the current song and clears the queue";
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = false;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
handler.stopAndClear();
event.getGuild().getAudioManager().closeAudioConnection();
event.reply(event.getClient().getSuccess()+" The player has stopped and the queue has been cleared.");
}
}
@@ -1,64 +1,69 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import me.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class VolumeCmd extends MusicCommand {
public VolumeCmd(Bot bot)
{
super(bot);
this.name = "volume";
this.help = "sets or shows volume";
this.arguments = "[0-150]";
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int volume = handler==null ? 100 : handler.getPlayer().getVolume();
if(event.getArgs().isEmpty())
{
event.reply(FormatUtil.volumeIcon(volume)+" Current volume is `"+volume+"`");
}
else
{
int nvolume;
try{
nvolume = Integer.parseInt(event.getArgs());
}catch(NumberFormatException e){
nvolume = -1;
}
if(nvolume<0 || nvolume>150)
event.reply(event.getClient().getError()+" Volume must be a valid integer between 0 and 150!");
else
{
bot.setUpHandler(event).getPlayer().setVolume(nvolume);
event.reply(FormatUtil.volumeIcon(nvolume)+" Volume changed from `"+volume+"` to `"+nvolume+"`");
}
}
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class VolumeCmd extends DJCommand
{
public VolumeCmd(Bot bot)
{
super(bot);
this.name = "volume";
this.aliases = bot.getConfig().getAliases(this.name);
this.help = "sets or shows volume";
this.arguments = "[0-150]";
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
Settings settings = event.getClient().getSettingsFor(event.getGuild());
int volume = handler.getPlayer().getVolume();
if(event.getArgs().isEmpty())
{
event.reply(FormatUtil.volumeIcon(volume)+" Current volume is `"+volume+"`");
}
else
{
int nvolume;
try{
nvolume = Integer.parseInt(event.getArgs());
}catch(NumberFormatException e){
nvolume = -1;
}
if(nvolume<0 || nvolume>150)
event.reply(event.getClient().getError()+" Volume must be a valid integer between 0 and 150!");
else
{
handler.getPlayer().setVolume(nvolume);
settings.setVolume(nvolume);
event.reply(FormatUtil.volumeIcon(nvolume)+" Volume changed from `"+volume+"` to `"+nvolume+"`");
}
}
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.general;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.settings.QueueType;
import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SettingsCmd extends Command
{
private final static String EMOJI = "\uD83C\uDFA7"; // 🎧
public SettingsCmd(Bot bot)
{
this.name = "settings";
this.help = "shows the bots settings";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = true;
}
@Override
protected void execute(CommandEvent event)
{
Settings s = event.getClient().getSettingsFor(event.getGuild());
MessageBuilder builder = new MessageBuilder()
.append(EMOJI + " **")
.append(FormatUtil.filter(event.getSelfUser().getName()))
.append("** settings:");
TextChannel tchan = s.getTextChannel(event.getGuild());
VoiceChannel vchan = s.getVoiceChannel(event.getGuild());
Role role = s.getRole(event.getGuild());
EmbedBuilder ebuilder = new EmbedBuilder()
.setColor(event.getSelfMember().getColor())
.setDescription("Text Channel: " + (tchan == null ? "Any" : "**#" + tchan.getName() + "**")
+ "\nVoice Channel: " + (vchan == null ? "Any" : vchan.getAsMention())
+ "\nDJ Role: " + (role == null ? "None" : "**" + role.getName() + "**")
+ "\nCustom Prefix: " + (s.getPrefix() == null ? "None" : "`" + s.getPrefix() + "`")
+ "\nRepeat Mode: " + (s.getRepeatMode() == RepeatMode.OFF
? s.getRepeatMode().getUserFriendlyName()
: "**"+s.getRepeatMode().getUserFriendlyName()+"**")
+ "\nQueue Type: " + (s.getQueueType() == QueueType.FAIR
? s.getQueueType().getUserFriendlyName()
: "**"+s.getQueueType().getUserFriendlyName()+"**")
+ "\nDefault Playlist: " + (s.getDefaultPlaylist() == null ? "None" : "**" + s.getDefaultPlaylist() + "**")
)
.setFooter(event.getJDA().getGuilds().size() + " servers | "
+ event.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()
+ " audio connections", null);
event.getChannel().sendMessage(builder.setEmbeds(ebuilder.build()).build()).queue();
}
}
@@ -0,0 +1,100 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jlyrics.LyricsClient;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class LyricsCmd extends MusicCommand
{
private final LyricsClient client = new LyricsClient();
public LyricsCmd(Bot bot)
{
super(bot);
this.name = "lyrics";
this.arguments = "[song name]";
this.help = "shows the lyrics of a song";
this.aliases = bot.getConfig().getAliases(this.name);
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
}
@Override
public void doCommand(CommandEvent event)
{
String title;
if(event.getArgs().isEmpty())
{
AudioHandler sendingHandler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
if (sendingHandler.isMusicPlaying(event.getJDA()))
title = sendingHandler.getPlayer().getPlayingTrack().getInfo().title;
else
{
event.replyError("There must be music playing to use that!");
return;
}
}
else
title = event.getArgs();
event.getChannel().sendTyping().queue();
client.getLyrics(title).thenAccept(lyrics ->
{
if(lyrics == null)
{
event.replyError("Lyrics for `" + title + "` could not be found!" + (event.getArgs().isEmpty() ? " Try entering the song name manually (`lyrics [song name]`)" : ""));
return;
}
EmbedBuilder eb = new EmbedBuilder()
.setAuthor(lyrics.getAuthor())
.setColor(event.getSelfMember().getColor())
.setTitle(lyrics.getTitle(), lyrics.getURL());
if(lyrics.getContent().length()>15000)
{
event.replyWarning("Lyrics for `" + title + "` found but likely not correct: " + lyrics.getURL());
}
else if(lyrics.getContent().length()>2000)
{
String content = lyrics.getContent().trim();
while(content.length() > 2000)
{
int index = content.lastIndexOf("\n\n", 2000);
if(index == -1)
index = content.lastIndexOf("\n", 2000);
if(index == -1)
index = content.lastIndexOf(" ", 2000);
if(index == -1)
index = 2000;
event.reply(eb.setDescription(content.substring(0, index).trim()).build());
content = content.substring(index).trim();
eb.setAuthor(null).setTitle(null, null);
}
event.reply(eb.setDescription(content).build());
}
else
event.reply(eb.setDescription(lyrics.getContent()).build());
});
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class NowplayingCmd extends MusicCommand
{
public NowplayingCmd(Bot bot)
{
super(bot);
this.name = "nowplaying";
this.help = "shows the song that is currently playing";
this.aliases = bot.getConfig().getAliases(this.name);
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
Message m = handler.getNowPlaying(event.getJDA());
if(m==null)
{
event.reply(handler.getNoMusicPlaying(event.getJDA()));
bot.getNowplayingHandler().clearLastNPMessage(event.getGuild());
}
else
{
event.reply(m, msg -> bot.getNowplayingHandler().setLastNPMessage(msg));
}
}
}
@@ -0,0 +1,263 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jmusicbot.audio.RequestMetadata;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.menu.ButtonMenu;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import java.util.concurrent.TimeUnit;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PlayCmd extends MusicCommand
{
private final static String LOAD = "\uD83D\uDCE5"; // 📥
private final static String CANCEL = "\uD83D\uDEAB"; // 🚫
private final String loadingEmoji;
public PlayCmd(Bot bot)
{
super(bot);
this.loadingEmoji = bot.getConfig().getLoading();
this.name = "play";
this.arguments = "<title|URL|subcommand>";
this.help = "plays the provided song";
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = false;
this.children = new Command[]{new PlaylistCmd(bot)};
}
@Override
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty())
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getPlayer().getPlayingTrack()!=null && handler.getPlayer().isPaused())
{
if(DJCommand.checkDJPermission(event))
{
handler.getPlayer().setPaused(false);
event.replySuccess("Resumed **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**.");
}
else
event.replyError("Only DJs can unpause the player!");
return;
}
StringBuilder builder = new StringBuilder(event.getClient().getWarning()+" Play Commands:\n");
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" <song title>` - plays the first result from Youtube");
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" <URL>` - plays the provided song, playlist, or stream");
for(Command cmd: children)
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" ").append(cmd.getName()).append(" ").append(cmd.getArguments()).append("` - ").append(cmd.getHelp());
event.reply(builder.toString());
return;
}
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
? event.getArgs().substring(1,event.getArgs().length()-1)
: event.getArgs().isEmpty() ? event.getMessage().getAttachments().get(0).getUrl() : event.getArgs();
event.reply(loadingEmoji+" Loading... `["+args+"]`", m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false)));
}
private class ResultHandler implements AudioLoadResultHandler
{
private final Message m;
private final CommandEvent event;
private final boolean ytsearch;
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
{
this.m = m;
this.event = event;
this.ytsearch = ytsearch;
}
private void loadSingle(AudioTrack track, AudioPlaylist playlist)
{
if(bot.getConfig().isTooLong(track))
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+ TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
if(playlist==null || !event.getSelfMember().hasPermission(event.getTextChannel(), Permission.MESSAGE_ADD_REACTION))
m.editMessage(addMsg).queue();
else
{
new ButtonMenu.Builder()
.setText(addMsg+"\n"+event.getClient().getWarning()+" This track has a playlist of **"+playlist.getTracks().size()+"** tracks attached. Select "+LOAD+" to load playlist.")
.setChoices(LOAD, CANCEL)
.setEventWaiter(bot.getWaiter())
.setTimeout(30, TimeUnit.SECONDS)
.setAction(re ->
{
if(re.getName().equals(LOAD))
m.editMessage(addMsg+"\n"+event.getClient().getSuccess()+" Loaded **"+loadPlaylist(playlist, track)+"** additional tracks!").queue();
else
m.editMessage(addMsg).queue();
}).setFinalAction(m ->
{
try{ m.clearReactions().queue(); }catch(PermissionException ignore) {}
}).build().display(m);
}
}
private int loadPlaylist(AudioPlaylist playlist, AudioTrack exclude)
{
int[] count = {0};
playlist.getTracks().stream().forEach((track) -> {
if(!bot.getConfig().isTooLong(track) && !track.equals(exclude))
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)));
count[0]++;
}
});
return count[0];
}
@Override
public void trackLoaded(AudioTrack track)
{
loadSingle(track, null);
}
@Override
public void playlistLoaded(AudioPlaylist playlist)
{
if(playlist.getTracks().size()==1 || playlist.isSearchResult())
{
AudioTrack single = playlist.getSelectedTrack()==null ? playlist.getTracks().get(0) : playlist.getSelectedTrack();
loadSingle(single, null);
}
else if (playlist.getSelectedTrack()!=null)
{
AudioTrack single = playlist.getSelectedTrack();
loadSingle(single, playlist);
}
else
{
int count = loadPlaylist(playlist, null);
if(playlist.getTracks().size() == 0)
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" The playlist "+(playlist.getName()==null ? "" : "(**"+playlist.getName()
+"**) ")+" could not be loaded or contained 0 entries")).queue();
}
else if(count==0)
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" All entries in this playlist "+(playlist.getName()==null ? "" : "(**"+playlist.getName()
+"**) ")+"were longer than the allowed maximum (`"+bot.getConfig().getMaxTime()+"`)")).queue();
}
else
{
m.editMessage(FormatUtil.filter(event.getClient().getSuccess()+" Found "
+(playlist.getName()==null?"a playlist":"playlist **"+playlist.getName()+"**")+" with `"
+ playlist.getTracks().size()+"` entries; added to the queue!"
+ (count<playlist.getTracks().size() ? "\n"+event.getClient().getWarning()+" Tracks longer than the allowed maximum (`"
+ bot.getConfig().getMaxTime()+"`) have been omitted." : ""))).queue();
}
}
}
@Override
public void noMatches()
{
if(ytsearch)
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
else
bot.getPlayerManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event,true));
}
@Override
public void loadFailed(FriendlyException throwable)
{
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
public class PlaylistCmd extends MusicCommand
{
public PlaylistCmd(Bot bot)
{
super(bot);
this.name = "playlist";
this.aliases = new String[]{"pl"};
this.arguments = "<name>";
this.help = "plays the provided playlist";
this.beListening = true;
this.bePlaying = false;
}
@Override
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name.");
return;
}
Playlist playlist = bot.getPlaylistLoader().getPlaylist(event.getArgs());
if(playlist==null)
{
event.replyError("I could not find `"+event.getArgs()+".txt` in the Playlists folder.");
return;
}
event.getChannel().sendMessage(loadingEmoji+" Loading playlist **"+event.getArgs()+"**... ("+playlist.getItems().size()+" items)").queue(m ->
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
playlist.loadTracks(bot.getPlayerManager(), (at)->handler.addTrack(new QueuedTrack(at, RequestMetadata.fromResultHandler(at, event))), () -> {
StringBuilder builder = new StringBuilder(playlist.getTracks().isEmpty()
? event.getClient().getWarning()+" No tracks were loaded!"
: event.getClient().getSuccess()+" Loaded **"+playlist.getTracks().size()+"** tracks!");
if(!playlist.getErrors().isEmpty())
builder.append("\nThe following tracks failed to load:");
playlist.getErrors().forEach(err -> builder.append("\n`[").append(err.getIndex()+1).append("]` **").append(err.getItem()).append("**: ").append(err.getReason()));
String str = builder.toString();
if(str.length()>2000)
str = str.substring(0,1994)+" (...)";
m.editMessage(FormatUtil.filter(str)).queue();
});
});
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import java.util.List;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.MusicCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PlaylistsCmd extends MusicCommand
{
public PlaylistsCmd(Bot bot)
{
super(bot);
this.name = "playlists";
this.help = "shows the available playlists";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = true;
this.beListening = false;
this.beListening = false;
}
@Override
public void doCommand(CommandEvent event)
{
if(!bot.getPlaylistLoader().folderExists())
bot.getPlaylistLoader().createFolder();
if(!bot.getPlaylistLoader().folderExists())
{
event.reply(event.getClient().getWarning()+" Playlists folder does not exist and could not be created!");
return;
}
List<String> list = bot.getPlaylistLoader().getPlaylistNames();
if(list==null)
event.reply(event.getClient().getError()+" Failed to load available playlists!");
else if(list.isEmpty())
event.reply(event.getClient().getWarning()+" There are no playlists in the Playlists folder!");
else
{
StringBuilder builder = new StringBuilder(event.getClient().getSuccess()+" Available playlists:\n");
list.forEach(str -> builder.append("`").append(str).append("` "));
builder.append("\nType `").append(event.getClient().getTextualPrefix()).append("play playlist <name>` to play a playlist");
event.reply(builder.toString());
}
}
}
@@ -0,0 +1,120 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.menu.Paginator;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.settings.QueueType;
import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueueCmd extends MusicCommand
{
private final Paginator.Builder builder;
public QueueCmd(Bot bot)
{
super(bot);
this.name = "queue";
this.help = "shows the current queue";
this.arguments = "[pagenum]";
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = true;
this.botPermissions = new Permission[]{Permission.MESSAGE_ADD_REACTION,Permission.MESSAGE_EMBED_LINKS};
builder = new Paginator.Builder()
.setColumns(1)
.setFinalAction(m -> {try{m.clearReactions().queue();}catch(PermissionException ignore){}})
.setItemsPerPage(10)
.waitOnSinglePage(false)
.useNumberedItems(true)
.showPageNumbers(true)
.wrapPageEnds(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES);
}
@Override
public void doCommand(CommandEvent event)
{
int pagenum = 1;
try
{
pagenum = Integer.parseInt(event.getArgs());
}
catch(NumberFormatException ignore){}
AudioHandler ah = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
List<QueuedTrack> list = ah.getQueue().getList();
if(list.isEmpty())
{
Message nowp = ah.getNowPlaying(event.getJDA());
Message nonowp = ah.getNoMusicPlaying(event.getJDA());
Message built = new MessageBuilder()
.setContent(event.getClient().getWarning() + " There is no music in the queue!")
.setEmbeds((nowp==null ? nonowp : nowp).getEmbeds().get(0)).build();
event.reply(built, m ->
{
if(nowp!=null)
bot.getNowplayingHandler().setLastNPMessage(m);
});
return;
}
String[] songs = new String[list.size()];
long total = 0;
for(int i=0; i<list.size(); i++)
{
total += list.get(i).getTrack().getDuration();
songs[i] = list.get(i).toString();
}
Settings settings = event.getClient().getSettingsFor(event.getGuild());
long fintotal = total;
builder.setText((i1,i2) -> getQueueTitle(ah, event.getClient().getSuccess(), songs.length, fintotal, settings.getRepeatMode(), settings.getQueueType()))
.setItems(songs)
.setUsers(event.getAuthor())
.setColor(event.getSelfMember().getColor())
;
builder.build().paginate(event.getChannel(), pagenum);
}
private String getQueueTitle(AudioHandler ah, String success, int songslength, long total, RepeatMode repeatmode, QueueType queueType)
{
StringBuilder sb = new StringBuilder();
if(ah.getPlayer().getPlayingTrack()!=null)
{
sb.append(ah.getStatusEmoji()).append(" **")
.append(ah.getPlayer().getPlayingTrack().getInfo().title).append("**\n");
}
return FormatUtil.filter(sb.append(success).append(" Current Queue | ").append(songslength)
.append(" entries | `").append(TimeUtil.formatTime(total)).append("` ")
.append("| ").append(queueType.getEmoji()).append(" `").append(queueType.getUserFriendlyName()).append('`')
.append(repeatmode.getEmoji() != null ? " | "+repeatmode.getEmoji() : "").toString());
}
}
@@ -0,0 +1,100 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class RemoveCmd extends MusicCommand
{
public RemoveCmd(Bot bot)
{
super(bot);
this.name = "remove";
this.help = "removes a song from the queue";
this.arguments = "<position|ALL>";
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getQueue().isEmpty())
{
event.replyError("There is nothing in the queue!");
return;
}
if(event.getArgs().equalsIgnoreCase("all"))
{
int count = handler.getQueue().removeAll(event.getAuthor().getIdLong());
if(count==0)
event.replyWarning("You don't have any songs in the queue!");
else
event.replySuccess("Successfully removed your "+count+" entries.");
return;
}
int pos;
try {
pos = Integer.parseInt(event.getArgs());
} catch(NumberFormatException e) {
pos = 0;
}
if(pos<1 || pos>handler.getQueue().size())
{
event.replyError("Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
return;
}
Settings settings = event.getClient().getSettingsFor(event.getGuild());
boolean isDJ = event.getMember().hasPermission(Permission.MANAGE_SERVER);
if(!isDJ)
isDJ = event.getMember().getRoles().contains(settings.getRole(event.getGuild()));
QueuedTrack qt = handler.getQueue().get(pos-1);
if(qt.getIdentifier()==event.getAuthor().getIdLong())
{
handler.getQueue().remove(pos-1);
event.replySuccess("Removed **"+qt.getTrack().getInfo().title+"** from the queue");
}
else if(isDJ)
{
handler.getQueue().remove(pos-1);
User u;
try {
u = event.getJDA().getUserById(qt.getIdentifier());
} catch(Exception e) {
u = null;
}
event.replySuccess("Removed **"+qt.getTrack().getInfo().title
+"** from the queue (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
}
else
{
event.replyError("You cannot remove **"+qt.getTrack().getInfo().title+"** because you didn't add it!");
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jmusicbot.Bot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SCSearchCmd extends SearchCmd
{
public SCSearchCmd(Bot bot)
{
super(bot);
this.searchPrefix = "scsearch:";
this.name = "scsearch";
this.help = "searches Soundcloud for a provided query";
this.aliases = bot.getConfig().getAliases(this.name);
}
}
@@ -0,0 +1,150 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jmusicbot.audio.RequestMetadata;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.util.concurrent.TimeUnit;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.menu.OrderedMenu;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SearchCmd extends MusicCommand
{
protected String searchPrefix = "ytsearch:";
private final OrderedMenu.Builder builder;
private final String searchingEmoji;
public SearchCmd(Bot bot)
{
super(bot);
this.searchingEmoji = bot.getConfig().getSearching();
this.name = "search";
this.aliases = bot.getConfig().getAliases(this.name);
this.arguments = "<query>";
this.help = "searches Youtube for a provided query";
this.beListening = true;
this.bePlaying = false;
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
builder = new OrderedMenu.Builder()
.allowTextInput(true)
.useNumbers()
.useCancelButton(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES);
}
@Override
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.replyError("Please include a query.");
return;
}
event.reply(searchingEmoji+" Searching... `["+event.getArgs()+"]`",
m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), searchPrefix + event.getArgs(), new ResultHandler(m,event)));
}
private class ResultHandler implements AudioLoadResultHandler
{
private final Message m;
private final CommandEvent event;
private ResultHandler(Message m, CommandEvent event)
{
this.m = m;
this.event = event;
}
@Override
public void trackLoaded(AudioTrack track)
{
if(bot.getConfig().isTooLong(track))
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue();
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
m.editMessage(FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos))).queue();
}
@Override
public void playlistLoaded(AudioPlaylist playlist)
{
builder.setColor(event.getSelfMember().getColor())
.setText(FormatUtil.filter(event.getClient().getSuccess()+" Search results for `"+event.getArgs()+"`:"))
.setChoices(new String[0])
.setSelection((msg,i) ->
{
AudioTrack track = playlist.getTracks().get(i-1);
if(bot.getConfig().isTooLong(track))
{
event.replyWarning("This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`");
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
event.replySuccess("Added **" + FormatUtil.filter(track.getInfo().title)
+ "** (`" + TimeUtil.formatTime(track.getDuration()) + "`) " + (pos==0 ? "to begin playing"
: " to the queue at position "+pos));
})
.setCancel((msg) -> {})
.setUsers(event.getAuthor())
;
for(int i=0; i<4 && i<playlist.getTracks().size(); i++)
{
AudioTrack track = playlist.getTracks().get(i);
builder.addChoices("`["+ TimeUtil.formatTime(track.getDuration())+"]` [**"+track.getInfo().title+"**]("+track.getInfo().uri+")");
}
builder.build().display(m);
}
@Override
public void noMatches()
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
}
@Override
public void loadFailed(FriendlyException throwable)
{
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
/**
* @author Whew., Inc.
*/
public class SeekCmd extends MusicCommand
{
public SeekCmd(Bot bot)
{
super(bot);
this.name = "seek";
this.help = "seeks the current song";
this.arguments = "[+ | -] <HH:MM:SS | MM:SS | SS>|<0h0m0s | 0m0s | 0s>";
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
AudioTrack playingTrack = handler.getPlayer().getPlayingTrack();
if (!playingTrack.isSeekable())
{
event.replyError("This track is not seekable.");
return;
}
if (!DJCommand.checkDJPermission(event) && playingTrack.getUserData(Long.class) != event.getAuthor().getIdLong())
{
event.replyError("You cannot seek **" + playingTrack.getInfo().title + "** because you didn't add it!");
return;
}
String args = event.getArgs();
TimeUtil.SeekTime seekTime = TimeUtil.parseTime(args);
if (seekTime == null)
{
event.replyError("Invalid seek! Expected format: " + arguments + "\nExamples: `1:02:23` `+1:10` `-90`, `1h10m`, `+90s`");
return;
}
long currentPosition = playingTrack.getPosition();
long trackDuration = playingTrack.getDuration();
long seekMilliseconds = seekTime.relative ? currentPosition + seekTime.milliseconds : seekTime.milliseconds;
if (seekMilliseconds > trackDuration)
{
event.replyError("Cannot seek to `" + TimeUtil.formatTime(seekMilliseconds) + "` because the current track is `" + TimeUtil.formatTime(trackDuration) + "` long!");
}
else
{
try
{
playingTrack.setPosition(seekMilliseconds);
}
catch (Exception e)
{
event.replyError("An error occurred while trying to seek!");
e.printStackTrace();
return;
}
}
event.replySuccess("Successfully seeked to `" + TimeUtil.formatTime(playingTrack.getPosition()) + "/" + TimeUtil.formatTime(playingTrack.getDuration()) + "`!");
}
}
@@ -1,54 +1,58 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ShuffleCmd extends MusicCommand {
public ShuffleCmd(Bot bot)
{
super(bot);
this.name = "shuffle";
this.help = "shuffles songs you have added";
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int s = handler.getQueue().shuffle(event.getAuthor().getId());
switch (s) {
case 0:
event.reply(event.getClient().getError()+" You don't have any music in the queue to shuffle!");
break;
case 1:
event.reply(event.getClient().getWarning()+" You only have one song in the queue!");
break;
default:
event.reply(event.getClient().getSuccess()+" You successfully shuffled your "+s+" entries.");
break;
}
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.MusicCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ShuffleCmd extends MusicCommand
{
public ShuffleCmd(Bot bot)
{
super(bot);
this.name = "shuffle";
this.help = "shuffles songs you have added";
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int s = handler.getQueue().shuffle(event.getAuthor().getIdLong());
switch (s)
{
case 0:
event.replyError("You don't have any music in the queue to shuffle!");
break;
case 1:
event.replyWarning("You only have one song in the queue!");
break;
default:
event.replySuccess("You successfully shuffled your "+s+" entries.");
break;
}
}
}
@@ -1,75 +1,81 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SkipCmd extends MusicCommand {
public SkipCmd(Bot bot)
{
super(bot);
this.name = "skip";
this.help = "votes to skip the current song";
this.aliases = new String[]{"voteskip"};
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(event.getAuthor().getId().equals(handler.getCurrentTrack().getIdentifier()))
{
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getCurrentTrack().getTrack().getInfo().title
+"**");
handler.getPlayer().stopTrack();
}
else
{
int listeners = (int)event.getSelfMember().getVoiceState().getChannel().getMembers().stream()
.filter(m -> !m.getUser().isBot() && !m.getVoiceState().isDeafened()).count();
String msg;
if(handler.getVotes().contains(event.getAuthor().getId()))
msg = event.getClient().getWarning()+" You already voted to skip this song `[";
else
{
msg = event.getClient().getSuccess()+" You voted to skip the song `[";
handler.getVotes().add(event.getAuthor().getId());
}
int skippers = (int)event.getSelfMember().getVoiceState().getChannel().getMembers().stream()
.filter(m -> handler.getVotes().contains(m.getUser().getId())).count();
int required = (int)Math.ceil(listeners * .55);
msg+= skippers+" votes, "+required+"/"+listeners+" needed]`";
if(skippers>=required)
{
User u = event.getJDA().getUserById(handler.getCurrentTrack().getIdentifier());
msg+="\n"+event.getClient().getSuccess()+" Skipped **"+handler.getCurrentTrack().getTrack().getInfo().title
+"** (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")";
handler.getPlayer().stopTrack();
}
event.reply(msg);
}
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.RequestMetadata;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SkipCmd extends MusicCommand
{
public SkipCmd(Bot bot)
{
super(bot);
this.name = "skip";
this.help = "votes to skip the current song";
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
RequestMetadata rm = handler.getRequestMetadata();
double skipRatio = bot.getSettingsManager().getSettings(event.getGuild()).getSkipRatio();
if(skipRatio == -1) {
skipRatio = bot.getConfig().getSkipRatio();
}
if(event.getAuthor().getIdLong() == rm.getOwner() || skipRatio == 0)
{
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**");
handler.getPlayer().stopTrack();
}
else
{
int listeners = (int)event.getSelfMember().getVoiceState().getChannel().getMembers().stream()
.filter(m -> !m.getUser().isBot() && !m.getVoiceState().isDeafened()).count();
String msg;
if(handler.getVotes().contains(event.getAuthor().getId()))
msg = event.getClient().getWarning()+" You already voted to skip this song `[";
else
{
msg = event.getClient().getSuccess()+" You voted to skip the song `[";
handler.getVotes().add(event.getAuthor().getId());
}
int skippers = (int)event.getSelfMember().getVoiceState().getChannel().getMembers().stream()
.filter(m -> handler.getVotes().contains(m.getUser().getId())).count();
int required = (int)Math.ceil(listeners * skipRatio);
msg += skippers + " votes, " + required + "/" + listeners + " needed]`";
if(skippers>=required)
{
msg += "\n" + event.getClient().getSuccess() + " Skipped **" + handler.getPlayer().getPlayingTrack().getInfo().title
+ "** " + (rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + FormatUtil.formatUsername(rm.user) + "**)");
handler.getPlayer().stopTrack();
}
event.reply(msg);
}
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import com.jagrosh.jmusicbot.settings.Settings;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class AutoplaylistCmd extends OwnerCommand
{
private final Bot bot;
public AutoplaylistCmd(Bot bot)
{
this.bot = bot;
this.guildOnly = true;
this.name = "autoplaylist";
this.arguments = "<name|NONE>";
this.help = "sets the default playlist for the server";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
public void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name or NONE");
return;
}
if(event.getArgs().equalsIgnoreCase("none"))
{
Settings settings = event.getClient().getSettingsFor(event.getGuild());
settings.setDefaultPlaylist(null);
event.reply(event.getClient().getSuccess()+" Cleared the default playlist for **"+event.getGuild().getName()+"**");
return;
}
String pname = event.getArgs().replaceAll("\\s+", "_");
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
{
event.reply(event.getClient().getError()+" Could not find `"+pname+".txt`!");
}
else
{
Settings settings = event.getClient().getSettingsFor(event.getGuild());
settings.setDefaultPlaylist(pname);
event.reply(event.getClient().getSuccess()+" The default playlist for **"+event.getGuild().getName()+"** is now `"+pname+"`");
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.commons.JDAUtilitiesInfo;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.sedmelluq.discord.lavaplayer.tools.PlayerLibrary;
import net.dv8tion.jda.api.JDAInfo;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.ChannelType;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class DebugCmd extends OwnerCommand
{
private final static String[] PROPERTIES = {"java.version", "java.vm.name", "java.vm.specification.version",
"java.runtime.name", "java.runtime.version", "java.specification.version", "os.arch", "os.name"};
private final Bot bot;
public DebugCmd(Bot bot)
{
this.bot = bot;
this.name = "debug";
this.help = "shows debug info";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
StringBuilder sb = new StringBuilder();
sb.append("```\nSystem Properties:");
for(String key: PROPERTIES)
sb.append("\n ").append(key).append(" = ").append(System.getProperty(key));
sb.append("\n\nJMusicBot Information:")
.append("\n Version = ").append(OtherUtil.getCurrentVersion())
.append("\n Owner = ").append(bot.getConfig().getOwnerId())
.append("\n Prefix = ").append(bot.getConfig().getPrefix())
.append("\n AltPrefix = ").append(bot.getConfig().getAltPrefix())
.append("\n MaxSeconds = ").append(bot.getConfig().getMaxSeconds())
.append("\n NPImages = ").append(bot.getConfig().useNPImages())
.append("\n SongInStatus = ").append(bot.getConfig().getSongInStatus())
.append("\n StayInChannel = ").append(bot.getConfig().getStay())
.append("\n UseEval = ").append(bot.getConfig().useEval())
.append("\n UpdateAlerts = ").append(bot.getConfig().useUpdateAlerts());
sb.append("\n\nDependency Information:")
.append("\n JDA Version = ").append(JDAInfo.VERSION)
.append("\n JDA-Utilities Version = ").append(JDAUtilitiesInfo.VERSION)
.append("\n Lavaplayer Version = ").append(PlayerLibrary.VERSION);
long total = Runtime.getRuntime().totalMemory() / 1024 / 1024;
long used = total - (Runtime.getRuntime().freeMemory() / 1024 / 1024);
sb.append("\n\nRuntime Information:")
.append("\n Total Memory = ").append(total)
.append("\n Used Memory = ").append(used);
sb.append("\n\nDiscord Information:")
.append("\n ID = ").append(event.getJDA().getSelfUser().getId())
.append("\n Guilds = ").append(event.getJDA().getGuildCache().size())
.append("\n Users = ").append(event.getJDA().getUserCache().size());
sb.append("\n```");
if(event.isFromType(ChannelType.PRIVATE)
|| event.getSelfMember().hasPermission(event.getTextChannel(), Permission.MESSAGE_ATTACH_FILES))
event.getChannel().sendFile(sb.toString().getBytes(), "debug_information.txt").queue();
else
event.reply("Debug Information: " + sb.toString());
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2016 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import net.dv8tion.jda.api.entities.ChannelType;
/**
*
* @author John Grosh (jagrosh)
*/
public class EvalCmd extends OwnerCommand
{
private final Bot bot;
private final String engine;
public EvalCmd(Bot bot)
{
this.bot = bot;
this.name = "eval";
this.help = "evaluates nashorn code";
this.aliases = bot.getConfig().getAliases(this.name);
this.engine = bot.getConfig().getEvalEngine();
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
ScriptEngine se = new ScriptEngineManager().getEngineByName(engine);
if(se == null)
{
event.replyError("The eval engine provided in the config (`"+engine+"`) doesn't exist. This could be due to an invalid "
+ "engine name, or the engine not existing in your version of java (`"+System.getProperty("java.version")+"`).");
return;
}
se.put("bot", bot);
se.put("event", event);
se.put("jda", event.getJDA());
if (event.getChannelType() != ChannelType.PRIVATE) {
se.put("guild", event.getGuild());
se.put("channel", event.getChannel());
}
try
{
event.reply(event.getClient().getSuccess()+" Evaluated Successfully:\n```\n"+se.eval(event.getArgs())+" ```");
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" An exception was thrown:\n```\n"+e+" ```");
}
}
}
@@ -0,0 +1,223 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import java.io.IOException;
import java.util.List;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PlaylistCmd extends OwnerCommand
{
private final Bot bot;
public PlaylistCmd(Bot bot)
{
this.bot = bot;
this.guildOnly = false;
this.name = "playlist";
this.arguments = "<append|delete|make|setdefault>";
this.help = "playlist management";
this.aliases = bot.getConfig().getAliases(this.name);
this.children = new OwnerCommand[]{
new ListCmd(),
new AppendlistCmd(),
new DeletelistCmd(),
new MakelistCmd(),
new DefaultlistCmd(bot)
};
}
@Override
public void execute(CommandEvent event)
{
StringBuilder builder = new StringBuilder(event.getClient().getWarning()+" Playlist Management Commands:\n");
for(Command cmd: this.children)
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" ").append(cmd.getName())
.append(" ").append(cmd.getArguments()==null ? "" : cmd.getArguments()).append("` - ").append(cmd.getHelp());
event.reply(builder.toString());
}
public class MakelistCmd extends OwnerCommand
{
public MakelistCmd()
{
this.name = "make";
this.aliases = new String[]{"create"};
this.help = "makes a new playlist";
this.arguments = "<name>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
String pname = event.getArgs().replaceAll("\\s+", "_");
pname = pname.replaceAll("[*?|\\/\":<>]", "");
if(pname == null || pname.isEmpty())
{
event.replyError("Please provide a name for the playlist!");
}
else if(bot.getPlaylistLoader().getPlaylist(pname) == null)
{
try
{
bot.getPlaylistLoader().createPlaylist(pname);
event.reply(event.getClient().getSuccess()+" Successfully created playlist `"+pname+"`!");
}
catch(IOException e)
{
event.reply(event.getClient().getError()+" I was unable to create the playlist: "+e.getLocalizedMessage());
}
}
else
event.reply(event.getClient().getError()+" Playlist `"+pname+"` already exists!");
}
}
public class DeletelistCmd extends OwnerCommand
{
public DeletelistCmd()
{
this.name = "delete";
this.aliases = new String[]{"remove"};
this.help = "deletes an existing playlist";
this.arguments = "<name>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
String pname = event.getArgs().replaceAll("\\s+", "_");
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
event.reply(event.getClient().getError()+" Playlist `"+pname+"` doesn't exist!");
else
{
try
{
bot.getPlaylistLoader().deletePlaylist(pname);
event.reply(event.getClient().getSuccess()+" Successfully deleted playlist `"+pname+"`!");
}
catch(IOException e)
{
event.reply(event.getClient().getError()+" I was unable to delete the playlist: "+e.getLocalizedMessage());
}
}
}
}
public class AppendlistCmd extends OwnerCommand
{
public AppendlistCmd()
{
this.name = "append";
this.aliases = new String[]{"add"};
this.help = "appends songs to an existing playlist";
this.arguments = "<name> <URL> | <URL> | ...";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
String[] parts = event.getArgs().split("\\s+", 2);
if(parts.length<2)
{
event.reply(event.getClient().getError()+" Please include a playlist name and URLs to add!");
return;
}
String pname = parts[0];
Playlist playlist = bot.getPlaylistLoader().getPlaylist(pname);
if(playlist==null)
event.reply(event.getClient().getError()+" Playlist `"+pname+"` doesn't exist!");
else
{
StringBuilder builder = new StringBuilder();
playlist.getItems().forEach(item -> builder.append("\r\n").append(item));
String[] urls = parts[1].split("\\|");
for(String url: urls)
{
String u = url.trim();
if(u.startsWith("<") && u.endsWith(">"))
u = u.substring(1, u.length()-1);
builder.append("\r\n").append(u);
}
try
{
bot.getPlaylistLoader().writePlaylist(pname, builder.toString());
event.reply(event.getClient().getSuccess()+" Successfully added "+urls.length+" items to playlist `"+pname+"`!");
}
catch(IOException e)
{
event.reply(event.getClient().getError()+" I was unable to append to the playlist: "+e.getLocalizedMessage());
}
}
}
}
public class DefaultlistCmd extends AutoplaylistCmd
{
public DefaultlistCmd(Bot bot)
{
super(bot);
this.name = "setdefault";
this.aliases = new String[]{"default"};
this.arguments = "<playlistname|NONE>";
this.guildOnly = true;
}
}
public class ListCmd extends OwnerCommand
{
public ListCmd()
{
this.name = "all";
this.aliases = new String[]{"available","list"};
this.help = "lists all available playlists";
this.guildOnly = true;
}
@Override
protected void execute(CommandEvent event)
{
if(!bot.getPlaylistLoader().folderExists())
bot.getPlaylistLoader().createFolder();
if(!bot.getPlaylistLoader().folderExists())
{
event.reply(event.getClient().getWarning()+" Playlists folder does not exist and could not be created!");
return;
}
List<String> list = bot.getPlaylistLoader().getPlaylistNames();
if(list==null)
event.reply(event.getClient().getError()+" Failed to load available playlists!");
else if(list.isEmpty())
event.reply(event.getClient().getWarning()+" There are no playlists in the Playlists folder!");
else
{
StringBuilder builder = new StringBuilder(event.getClient().getSuccess()+" Available playlists:\n");
list.forEach(str -> builder.append("`").append(str).append("` "));
event.reply(builder.toString());
}
}
}
}
@@ -1,70 +1,68 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.utils.OtherUtil;
import net.dv8tion.jda.core.entities.Icon;
import net.dv8tion.jda.core.exceptions.RateLimitedException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetavatarCmd extends Command {
public SetavatarCmd(Bot bot)
{
this.name = "setavatar";
this.help = "sets the name of the bot";
this.arguments = "<url>";
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
String url;
if(event.getArgs().isEmpty())
if(!event.getMessage().getAttachments().isEmpty() && event.getMessage().getAttachments().get(0).isImage())
url = event.getMessage().getAttachments().get(0).getUrl();
else
url = null;
else
url = event.getArgs();
InputStream s = OtherUtil.imageFromUrl(url);
if(s==null)
{
event.reply(event.getClient().getError()+" Invalid or missing URL");
}
else
{
try {
event.getSelfUser().getManager().setAvatar(Icon.from(s)).queue(
v -> event.reply(event.getClient().getSuccess()+" Successfully changed avatar."),
t -> event.reply(event.getClient().getError()+" Failed to set avatar."));
} catch(IOException e) {
event.reply(event.getClient().getError()+" Could not load from provided URL.");
}
}
}
}
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import java.io.IOException;
import java.io.InputStream;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import net.dv8tion.jda.api.entities.Icon;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetavatarCmd extends OwnerCommand
{
public SetavatarCmd(Bot bot)
{
this.name = "setavatar";
this.help = "sets the avatar of the bot";
this.arguments = "<url>";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
String url;
if(event.getArgs().isEmpty())
if(!event.getMessage().getAttachments().isEmpty() && event.getMessage().getAttachments().get(0).isImage())
url = event.getMessage().getAttachments().get(0).getUrl();
else
url = null;
else
url = event.getArgs();
InputStream s = OtherUtil.imageFromUrl(url);
if(s==null)
{
event.reply(event.getClient().getError()+" Invalid or missing URL");
}
else
{
try {
event.getSelfUser().getManager().setAvatar(Icon.from(s)).queue(
v -> event.reply(event.getClient().getSuccess()+" Successfully changed avatar."),
t -> event.reply(event.getClient().getError()+" Failed to set avatar."));
} catch(IOException e) {
event.reply(event.getClient().getError()+" Could not load from provided URL.");
}
}
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import net.dv8tion.jda.api.entities.Activity;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetgameCmd extends OwnerCommand
{
public SetgameCmd(Bot bot)
{
this.name = "setgame";
this.help = "sets the game the bot is playing";
this.arguments = "[action] [game]";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
this.children = new OwnerCommand[]{
new SetlistenCmd(),
new SetstreamCmd(),
new SetwatchCmd()
};
}
@Override
protected void execute(CommandEvent event)
{
String title = event.getArgs().toLowerCase().startsWith("playing") ? event.getArgs().substring(7).trim() : event.getArgs();
try
{
event.getJDA().getPresence().setActivity(title.isEmpty() ? null : Activity.playing(title));
event.reply(event.getClient().getSuccess()+" **"+event.getSelfUser().getName()
+"** is "+(title.isEmpty() ? "no longer playing anything." : "now playing `"+title+"`"));
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
private class SetstreamCmd extends OwnerCommand
{
private SetstreamCmd()
{
this.name = "stream";
this.aliases = new String[]{"twitch","streaming"};
this.help = "sets the game the bot is playing to a stream";
this.arguments = "<username> <game>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
String[] parts = event.getArgs().split("\\s+", 2);
if(parts.length<2)
{
event.replyError("Please include a twitch username and the name of the game to 'stream'");
return;
}
try
{
event.getJDA().getPresence().setActivity(Activity.streaming(parts[1], "https://twitch.tv/"+parts[0]));
event.replySuccess("**"+event.getSelfUser().getName()
+"** is now streaming `"+parts[1]+"`");
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
}
private class SetlistenCmd extends OwnerCommand
{
private SetlistenCmd()
{
this.name = "listen";
this.aliases = new String[]{"listening"};
this.help = "sets the game the bot is listening to";
this.arguments = "<title>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.replyError("Please include a title to listen to!");
return;
}
String title = event.getArgs().toLowerCase().startsWith("to") ? event.getArgs().substring(2).trim() : event.getArgs();
try
{
event.getJDA().getPresence().setActivity(Activity.listening(title));
event.replySuccess("**"+event.getSelfUser().getName()+"** is now listening to `"+title+"`");
} catch(Exception e) {
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
}
private class SetwatchCmd extends OwnerCommand
{
private SetwatchCmd()
{
this.name = "watch";
this.aliases = new String[]{"watching"};
this.help = "sets the game the bot is watching";
this.arguments = "<title>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.replyError("Please include a title to watch!");
return;
}
String title = event.getArgs();
try
{
event.getJDA().getPresence().setActivity(Activity.watching(title));
event.replySuccess("**"+event.getSelfUser().getName()+"** is now watching `"+title+"`");
} catch(Exception e) {
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
}
}
@@ -1,51 +1,56 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import net.dv8tion.jda.core.exceptions.RateLimitedException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetnameCmd extends Command {
public SetnameCmd(Bot bot)
{
this.name = "setname";
this.help = "sets the name of the bot";
this.arguments = "<name>";
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
try {
String oldname = event.getSelfUser().getName();
event.getSelfUser().getManager().setName(event.getArgs()).complete(false);
event.reply(event.getClient().getSuccess()+" Name changed from `"+oldname+"` to `"+event.getArgs()+"`");
} catch(RateLimitedException e) {
event.reply(event.getClient().getError()+" Name can only be changed twice per hour!");
} catch(Exception e) {
event.reply(event.getClient().getError()+" That name is not valid!");
}
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetnameCmd extends OwnerCommand
{
public SetnameCmd(Bot bot)
{
this.name = "setname";
this.help = "sets the name of the bot";
this.arguments = "<name>";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
try
{
String oldname = event.getSelfUser().getName();
event.getSelfUser().getManager().setName(event.getArgs()).complete(false);
event.reply(event.getClient().getSuccess()+" Name changed from `"+oldname+"` to `"+event.getArgs()+"`");
}
catch(RateLimitedException e)
{
event.reply(event.getClient().getError()+" Name can only be changed twice per hour!");
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" That name is not valid!");
}
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import net.dv8tion.jda.api.OnlineStatus;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetstatusCmd extends OwnerCommand
{
public SetstatusCmd(Bot bot)
{
this.name = "setstatus";
this.help = "sets the status the bot displays";
this.arguments = "<status>";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
try {
OnlineStatus status = OnlineStatus.fromKey(event.getArgs());
if(status==OnlineStatus.UNKNOWN)
{
event.replyError("Please include one of the following statuses: `ONLINE`, `IDLE`, `DND`, `INVISIBLE`");
}
else
{
event.getJDA().getPresence().setStatus(status);
event.replySuccess("Set the status to `"+status.getKey().toUpperCase()+"`");
}
} catch(Exception e) {
event.reply(event.getClient().getError()+" The status could not be set!");
}
}
}
@@ -1,44 +1,45 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ShutdownCmd extends Command {
private final Bot bot;
public ShutdownCmd(Bot bot)
{
this.bot = bot;
this.name = "shutdown";
this.help = "safely shuts down";
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
event.reply(event.getClient().getWarning()+" Shutting down...");
bot.shutdown();
}
}
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ShutdownCmd extends OwnerCommand
{
private final Bot bot;
public ShutdownCmd(Bot bot)
{
this.bot = bot;
this.name = "shutdown";
this.help = "safely shuts down";
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
event.replyWarning("Shutting down...");
bot.shutdown();
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.entities;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
* @param <K>
* @param <V>
*/
public class Pair<K,V>
{
private final K key;
private final V value;
public Pair(K key, V value)
{
this.key = key;
this.value = value;
}
public K getKey()
{
return key;
}
public V getValue()
{
return value;
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 2018 John Grosh (jagrosh)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.entities;
import java.util.Scanner;
import javax.swing.JOptionPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class Prompt
{
private final String title;
private final String noguiMessage;
private boolean nogui;
private boolean noprompt;
private Scanner scanner;
public Prompt(String title)
{
this(title, null);
}
public Prompt(String title, String noguiMessage)
{
this(title, noguiMessage, "true".equalsIgnoreCase(System.getProperty("nogui")), "true".equalsIgnoreCase(System.getProperty("noprompt")));
}
public Prompt(String title, String noguiMessage, boolean nogui, boolean noprompt)
{
this.title = title;
this.noguiMessage = noguiMessage == null ? "Switching to nogui mode. You can manually start in nogui mode by including the -Dnogui=true flag." : noguiMessage;
this.nogui = nogui;
this.noprompt = noprompt;
}
public boolean isNoGUI()
{
return nogui;
}
public void alert(Level level, String context, String message)
{
if(nogui)
{
Logger log = LoggerFactory.getLogger(context);
switch(level)
{
case INFO:
log.info(message);
break;
case WARNING:
log.warn(message);
break;
case ERROR:
log.error(message);
break;
default:
log.info(message);
break;
}
}
else
{
try
{
int option = 0;
switch(level)
{
case INFO:
option = JOptionPane.INFORMATION_MESSAGE;
break;
case WARNING:
option = JOptionPane.WARNING_MESSAGE;
break;
case ERROR:
option = JOptionPane.ERROR_MESSAGE;
break;
default:
option = JOptionPane.PLAIN_MESSAGE;
break;
}
JOptionPane.showMessageDialog(null, "<html><body><p style='width: 400px;'>"+message, title, option);
}
catch(Exception e)
{
nogui = true;
alert(Level.WARNING, context, noguiMessage);
alert(level, context, message);
}
}
}
public String prompt(String content)
{
if(noprompt)
return null;
if(nogui)
{
if(scanner==null)
scanner = new Scanner(System.in);
try
{
System.out.println(content);
if(scanner.hasNextLine())
return scanner.nextLine();
return null;
}
catch(Exception e)
{
alert(Level.ERROR, title, "Unable to read input from command line.");
e.printStackTrace();
return null;
}
}
else
{
try
{
return JOptionPane.showInputDialog(null, content, title, JOptionPane.QUESTION_MESSAGE);
}
catch(Exception e)
{
nogui = true;
alert(Level.WARNING, title, noguiMessage);
return prompt(content);
}
}
}
public static enum Level
{
INFO, WARNING, ERROR;
}
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.gui;
package com.jagrosh.jmusicbot.gui;
import java.awt.Dimension;
import java.awt.GridLayout;
@@ -1,64 +1,73 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.gui;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JFrame;
import javax.swing.JTabbedPane;
import javax.swing.WindowConstants;
import me.jagrosh.jmusicbot.Bot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class GUI extends JFrame {
private final ConsolePanel console;
private final GuildsPanel guilds;
private final Bot bot;
public GUI(Bot bot) {
super();
this.bot = bot;
console = new ConsolePanel();
guilds = new GuildsPanel(bot);
}
public void init()
{
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setTitle("JMusicBot");
JTabbedPane tabs = new JTabbedPane();
//tabs.add("Guilds", guilds);
tabs.add("Console", console);
getContentPane().add(tabs);
pack();
setLocationRelativeTo(null);
setVisible(true);
addWindowListener(new WindowListener() {
@Override public void windowOpened(WindowEvent e) {}
@Override public void windowClosing(WindowEvent e) {bot.shutdown();}
@Override public void windowClosed(WindowEvent e) {}
@Override public void windowIconified(WindowEvent e) {}
@Override public void windowDeiconified(WindowEvent e) {}
@Override public void windowActivated(WindowEvent e) {}
@Override public void windowDeactivated(WindowEvent e) {}
});
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.gui;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JFrame;
import javax.swing.JTabbedPane;
import javax.swing.WindowConstants;
import com.jagrosh.jmusicbot.Bot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class GUI extends JFrame
{
private final ConsolePanel console;
private final Bot bot;
public GUI(Bot bot)
{
super();
this.bot = bot;
console = new ConsolePanel();
}
public void init()
{
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setTitle("JMusicBot");
JTabbedPane tabs = new JTabbedPane();
tabs.add("Console", console);
getContentPane().add(tabs);
pack();
setLocationRelativeTo(null);
setVisible(true);
addWindowListener(new WindowListener()
{
@Override public void windowOpened(WindowEvent e) { /* unused */ }
@Override public void windowClosing(WindowEvent e)
{
try
{
bot.shutdown();
}
catch(Exception ex)
{
System.exit(0);
}
}
@Override public void windowClosed(WindowEvent e) { /* unused */ }
@Override public void windowIconified(WindowEvent e) { /* unused */ }
@Override public void windowDeiconified(WindowEvent e) { /* unused */ }
@Override public void windowActivated(WindowEvent e) { /* unused */ }
@Override public void windowDeactivated(WindowEvent e) { /* unused */ }
});
}
}
@@ -1,147 +1,157 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.gui;
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
/**
*
* @author Lawrence Dol
*/
public class TextAreaOutputStream extends OutputStream {
// *************************************************************************************************
// INSTANCE MEMBERS
// *************************************************************************************************
private byte[] oneByte; // array for write(int val);
private Appender appender; // most recent action
public TextAreaOutputStream(JTextArea txtara) {
this(txtara,1000);
}
public TextAreaOutputStream(JTextArea txtara, int maxlin) {
if(maxlin<1) { throw new IllegalArgumentException("TextAreaOutputStream maximum lines must be positive (value="+maxlin+")"); }
oneByte=new byte[1];
appender=new Appender(txtara,maxlin);
}
/** Clear the current console text area. */
public synchronized void clear() {
if(appender!=null) { appender.clear(); }
}
@Override
public synchronized void close() {
appender=null;
}
@Override
public synchronized void flush() {
}
@Override
public synchronized void write(int val) {
oneByte[0]=(byte)val;
write(oneByte,0,1);
}
@Override
public synchronized void write(byte[] ba) {
write(ba,0,ba.length);
}
@Override
public synchronized void write(byte[] ba,int str,int len) {
if(appender!=null) { appender.append(bytesToString(ba,str,len)); }
}
//@edu.umd.cs.findbugs.annotations.SuppressWarnings("DM_DEFAULT_ENCODING")
static private String bytesToString(byte[] ba, int str, int len) {
try { return new String(ba,str,len,"UTF-8"); } catch(UnsupportedEncodingException thr) { return new String(ba,str,len); } // all JVMs are required to support UTF-8
}
// *************************************************************************************************
// STATIC MEMBERS
// *************************************************************************************************
static class Appender
implements Runnable
{
private final JTextArea textArea;
private final int maxLines; // maximum lines allowed in text area
private final LinkedList<Integer> lengths; // length of lines within text area
private final List<String> values; // values waiting to be appended
private int curLength; // length of current line
private boolean clear;
private boolean queue;
Appender(JTextArea txtara, int maxlin) {
textArea =txtara;
maxLines =maxlin;
lengths =new LinkedList<>();
values =new ArrayList<>();
curLength=0;
clear =false;
queue =true;
}
synchronized void append(String val) {
values.add(val);
if(queue) { queue=false; EventQueue.invokeLater(this); }
}
synchronized void clear() {
clear=true;
curLength=0;
lengths.clear();
values.clear();
if(queue) { queue=false; EventQueue.invokeLater(this); }
}
// MUST BE THE ONLY METHOD THAT TOUCHES textArea!
@Override
public synchronized void run() {
if(clear) { textArea.setText(""); }
values.stream().map((val) -> {
curLength+=val.length();
return val;
}).map((val) -> {
if(val.endsWith(EOL1) || val.endsWith(EOL2)) {
if(lengths.size()>=maxLines) { textArea.replaceRange("",0,lengths.removeFirst()); }
lengths.addLast(curLength);
curLength=0;
}
return val;
}).forEach((val) -> {
textArea.append(val);
});
values.clear();
clear =false;
queue =true;
}
static private final String EOL1="\n";
static private final String EOL2=System.getProperty("line.separator",EOL1);
}
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.gui;
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
/**
*
* @author Lawrence Dol
*/
public class TextAreaOutputStream extends OutputStream {
// *************************************************************************************************
// INSTANCE MEMBERS
// *************************************************************************************************
private byte[] oneByte; // array for write(int val);
private Appender appender; // most recent action
public TextAreaOutputStream(JTextArea txtara) {
this(txtara,1000);
}
public TextAreaOutputStream(JTextArea txtara, int maxlin) {
if(maxlin<1) { throw new IllegalArgumentException("TextAreaOutputStream maximum lines must be positive (value="+maxlin+")"); }
oneByte=new byte[1];
appender=new Appender(txtara,maxlin);
}
/** Clear the current console text area. */
public synchronized void clear() {
if(appender!=null) { appender.clear(); }
}
@Override
public synchronized void close() {
appender=null;
}
@Override
public synchronized void flush() {
/* empty */
}
@Override
public synchronized void write(int val) {
oneByte[0]=(byte)val;
write(oneByte,0,1);
}
@Override
public synchronized void write(byte[] ba) {
write(ba,0,ba.length);
}
@Override
public synchronized void write(byte[] ba,int str,int len) {
if(appender!=null) { appender.append(bytesToString(ba,str,len)); }
}
//@edu.umd.cs.findbugs.annotations.SuppressWarnings("DM_DEFAULT_ENCODING")
static private String bytesToString(byte[] ba, int str, int len) {
try {
return new String(ba,str,len,"UTF-8");
} catch(UnsupportedEncodingException thr) {
return new String(ba,str,len);
} // all JVMs are required to support UTF-8
}
// *************************************************************************************************
// STATIC MEMBERS
// *************************************************************************************************
static class Appender
implements Runnable
{
static private final String EOL1="\n";
static private final String EOL2=System.getProperty("line.separator",EOL1);
private final JTextArea textArea;
private final int maxLines; // maximum lines allowed in text area
private final LinkedList<Integer> lengths; // length of lines within text area
private final List<String> values; // values waiting to be appended
private int curLength; // length of current line
private boolean clear;
private boolean queue;
Appender(JTextArea txtara, int maxlin) {
textArea =txtara;
maxLines =maxlin;
lengths =new LinkedList<>();
values =new ArrayList<>();
curLength=0;
clear =false;
queue =true;
}
private synchronized void append(String val) {
values.add(val);
if(queue) {
queue=false;
EventQueue.invokeLater(this);
}
}
private synchronized void clear() {
clear=true;
curLength=0;
lengths.clear();
values.clear();
if(queue) {
queue=false;
EventQueue.invokeLater(this);
}
}
// MUST BE THE ONLY METHOD THAT TOUCHES textArea!
@Override
public synchronized void run() {
if(clear) { textArea.setText(""); }
values.stream().map((val) -> {
curLength+=val.length();
return val;
}).map((val) -> {
if(val.endsWith(EOL1) || val.endsWith(EOL2)) {
if(lengths.size()>=maxLines) { textArea.replaceRange("",0,lengths.removeFirst()); }
lengths.addLast(curLength);
curLength=0;
}
return val;
}).forEach((val) -> {
textArea.append(val);
});
values.clear();
clear =false;
queue =true;
}
}
} /* END PUBLIC CLASS */
@@ -0,0 +1,295 @@
/*
* Copyright 2018 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.playlist;
import com.jagrosh.jmusicbot.BotConfig;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PlaylistLoader
{
private final BotConfig config;
public PlaylistLoader(BotConfig config)
{
this.config = config;
}
public List<String> getPlaylistNames()
{
if(folderExists())
{
File folder = new File(OtherUtil.getPath(config.getPlaylistsFolder()).toString());
return Arrays.asList(folder.listFiles((pathname) -> pathname.getName().endsWith(".txt")))
.stream().map(f -> f.getName().substring(0,f.getName().length()-4)).collect(Collectors.toList());
}
else
{
createFolder();
return Collections.emptyList();
}
}
public void createFolder()
{
try
{
Files.createDirectory(OtherUtil.getPath(config.getPlaylistsFolder()));
}
catch (IOException ignore) {}
}
public boolean folderExists()
{
return Files.exists(OtherUtil.getPath(config.getPlaylistsFolder()));
}
public void createPlaylist(String name) throws IOException
{
Files.createFile(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt"));
}
public void deletePlaylist(String name) throws IOException
{
Files.delete(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt"));
}
public void writePlaylist(String name, String text) throws IOException
{
Files.write(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt"), text.trim().getBytes());
}
public Playlist getPlaylist(String name)
{
if(!getPlaylistNames().contains(name))
return null;
try
{
if(folderExists())
{
boolean[] shuffle = {false};
List<String> list = new ArrayList<>();
Files.readAllLines(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt")).forEach(str ->
{
String s = str.trim();
if(s.isEmpty())
return;
if(s.startsWith("#") || s.startsWith("//"))
{
s = s.replaceAll("\\s+", "");
if(s.equalsIgnoreCase("#shuffle") || s.equalsIgnoreCase("//shuffle"))
shuffle[0]=true;
}
else
list.add(s);
});
if(shuffle[0])
shuffle(list);
return new Playlist(name, list, shuffle[0]);
}
else
{
createFolder();
return null;
}
}
catch(IOException e)
{
return null;
}
}
private static <T> void shuffle(List<T> list)
{
for(int first =0; first<list.size(); first++)
{
int second = (int)(Math.random()*list.size());
T tmp = list.get(first);
list.set(first, list.get(second));
list.set(second, tmp);
}
}
public class Playlist
{
private final String name;
private final List<String> items;
private final boolean shuffle;
private final List<AudioTrack> tracks = new LinkedList<>();
private final List<PlaylistLoadError> errors = new LinkedList<>();
private boolean loaded = false;
private Playlist(String name, List<String> items, boolean shuffle)
{
this.name = name;
this.items = items;
this.shuffle = shuffle;
}
public void loadTracks(AudioPlayerManager manager, Consumer<AudioTrack> consumer, Runnable callback)
{
if(loaded)
return;
loaded = true;
for(int i=0; i<items.size(); i++)
{
boolean last = i+1 == items.size();
int index = i;
manager.loadItemOrdered(name, items.get(i), new AudioLoadResultHandler()
{
private void done()
{
if(last)
{
if(shuffle)
shuffleTracks();
if(callback != null)
callback.run();
}
}
@Override
public void trackLoaded(AudioTrack at)
{
if(config.isTooLong(at))
errors.add(new PlaylistLoadError(index, items.get(index), "This track is longer than the allowed maximum"));
else
{
at.setUserData(0L);
tracks.add(at);
consumer.accept(at);
}
done();
}
@Override
public void playlistLoaded(AudioPlaylist ap)
{
if(ap.isSearchResult())
{
trackLoaded(ap.getTracks().get(0));
}
else if(ap.getSelectedTrack()!=null)
{
trackLoaded(ap.getSelectedTrack());
}
else
{
List<AudioTrack> loaded = new ArrayList<>(ap.getTracks());
if(shuffle)
for(int first =0; first<loaded.size(); first++)
{
int second = (int)(Math.random()*loaded.size());
AudioTrack tmp = loaded.get(first);
loaded.set(first, loaded.get(second));
loaded.set(second, tmp);
}
loaded.removeIf(track -> config.isTooLong(track));
loaded.forEach(at -> at.setUserData(0L));
tracks.addAll(loaded);
loaded.forEach(at -> consumer.accept(at));
}
done();
}
@Override
public void noMatches()
{
errors.add(new PlaylistLoadError(index, items.get(index), "No matches found."));
done();
}
@Override
public void loadFailed(FriendlyException fe)
{
errors.add(new PlaylistLoadError(index, items.get(index), "Failed to load track: "+fe.getLocalizedMessage()));
done();
}
});
}
}
public void shuffleTracks()
{
shuffle(tracks);
}
public String getName()
{
return name;
}
public List<String> getItems()
{
return items;
}
public List<AudioTrack> getTracks()
{
return tracks;
}
public List<PlaylistLoadError> getErrors()
{
return errors;
}
}
public class PlaylistLoadError
{
private final int number;
private final String item;
private final String reason;
private PlaylistLoadError(int number, String item, String reason)
{
this.number = number;
this.item = item;
this.reason = reason;
}
public int getIndex()
{
return number;
}
public String getItem()
{
return item;
}
public String getReason()
{
return reason;
}
}
}
@@ -1,108 +1,130 @@
/*
* Copyright 2016 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.queue;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*
* @author John Grosh (jagrosh)
*/
public class FairQueue<T extends Queueable> {
private final List<T> list = new ArrayList<>();
private final Set<String> set = new HashSet<>();
public int add(T item)
{
int lastIndex;
for(lastIndex=list.size()-1; lastIndex>-1; lastIndex--)
if(list.get(lastIndex).getIdentifier().equals(item.getIdentifier()))
break;
lastIndex++;
set.clear();
for(; lastIndex<list.size(); lastIndex++)
{
if(set.contains(list.get(lastIndex).getIdentifier()))
break;
set.add(list.get(lastIndex).getIdentifier());
}
list.add(lastIndex, item);
return lastIndex;
}
public int size()
{
return list.size();
}
public T pull()
{
return list.remove(0);
}
public boolean isEmpty()
{
return list.isEmpty();
}
public List<T> getList()
{
return list;
}
public T get(int index)
{
return list.get(index);
}
public T remove(int index)
{
return list.remove(index);
}
public void clear()
{
list.clear();
}
public int shuffle(String identifier)
{
List<Integer> iset = new ArrayList<>();
for(int i=0; i<list.size(); i++)
{
if(list.get(i).getIdentifier().equals(identifier))
iset.add(i);
}
for(int j=0; j<iset.size(); j++)
{
int first = iset.get(j);
int second = iset.get((int)(Math.random()*iset.size()));
T temp = list.get(first);
list.set(first, list.get(second));
list.set(second, temp);
}
return iset.size();
}
public void skip(int number)
{
for(int i=0; i<number; i++)
list.remove(0);
}
}
/*
* Copyright 2022 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.queue;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
*
* @author Wolfgang Schwendtbauer
* @param <T>
*/
public abstract class AbstractQueue<T extends Queueable>
{
protected AbstractQueue(AbstractQueue<T> queue)
{
this.list = queue != null ? queue.getList() : new LinkedList<>();
}
protected final List<T> list;
public abstract int add(T item);
public void addAt(int index, T item)
{
if(index >= list.size())
list.add(item);
else
list.add(index, item);
}
public int size() {
return list.size();
}
public T pull() {
return list.remove(0);
}
public boolean isEmpty()
{
return list.isEmpty();
}
public List<T> getList()
{
return list;
}
public T get(int index) {
return list.get(index);
}
public T remove(int index)
{
return list.remove(index);
}
public int removeAll(long identifier)
{
int count = 0;
for(int i=list.size()-1; i>=0; i--)
{
if(list.get(i).getIdentifier()==identifier)
{
list.remove(i);
count++;
}
}
return count;
}
public void clear()
{
list.clear();
}
public int shuffle(long identifier)
{
List<Integer> iset = new ArrayList<>();
for(int i=0; i<list.size(); i++)
{
if(list.get(i).getIdentifier()==identifier)
iset.add(i);
}
for(int j=0; j<iset.size(); j++)
{
int first = iset.get(j);
int second = iset.get((int)(Math.random()*iset.size()));
T temp = list.get(first);
list.set(first, list.get(second));
list.set(second, temp);
}
return iset.size();
}
public void skip(int number)
{
if (number > 0) {
list.subList(0, number).clear();
}
}
/**
* Move an item to a different position in the list
* @param from The position of the item
* @param to The new position of the item
* @return the moved item
*/
public T moveItem(int from, int to)
{
T item = list.remove(from);
list.add(to, item);
return item;
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2016 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.queue;
import java.util.HashSet;
import java.util.Set;
/**
*
* @author John Grosh (jagrosh)
* @param <T>
*/
public class FairQueue<T extends Queueable> extends AbstractQueue<T>
{
public FairQueue(AbstractQueue<T> queue)
{
super(queue);
}
protected final Set<Long> set = new HashSet<>();
@Override
public int add(T item)
{
int lastIndex;
for(lastIndex=list.size()-1; lastIndex>-1; lastIndex--)
if(list.get(lastIndex).getIdentifier() == item.getIdentifier())
break;
lastIndex++;
set.clear();
for(; lastIndex<list.size(); lastIndex++)
{
if(set.contains(list.get(lastIndex).getIdentifier()))
break;
set.add(list.get(lastIndex).getIdentifier());
}
list.add(lastIndex, item);
return lastIndex;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2022 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.queue;
/**
*
* @author Wolfgang Schwendtbauer
* @param <T>
*/
public class LinearQueue<T extends Queueable> extends AbstractQueue<T>
{
public LinearQueue(AbstractQueue<T> queue)
{
super(queue);
}
@Override
public int add(T item)
{
list.add(item);
return list.size() - 1;
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.queue;
/**
*
* @author Wolfgang Schwendtbauer
*/
@FunctionalInterface
public interface QueueSupplier
{
<T extends Queueable> AbstractQueue<T> apply(AbstractQueue<T> queue);
}
@@ -1,25 +1,25 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.queue;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public interface Queueable {
public String getIdentifier();
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.queue;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public interface Queueable {
public long getIdentifier();
}
@@ -0,0 +1,69 @@
/*
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.settings;
import com.jagrosh.jmusicbot.queue.AbstractQueue;
import com.jagrosh.jmusicbot.queue.FairQueue;
import com.jagrosh.jmusicbot.queue.LinearQueue;
import com.jagrosh.jmusicbot.queue.Queueable;
import com.jagrosh.jmusicbot.queue.QueueSupplier;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* @author Wolfgang Schwendtbauer
*/
public enum QueueType
{
LINEAR("\u23E9", "Linear", LinearQueue::new), // ⏩
FAIR("\uD83D\uDD22", "Fair", FairQueue::new); // 🔢
private final String userFriendlyName;
private final String emoji;
private final QueueSupplier supplier;
QueueType(final String emoji, final String userFriendlyName, QueueSupplier supplier)
{
this.userFriendlyName = userFriendlyName;
this.emoji = emoji;
this.supplier = supplier;
}
public static List<String> getNames()
{
return Arrays.stream(QueueType.values())
.map(type -> type.name().toLowerCase())
.collect(Collectors.toList());
}
public <T extends Queueable> AbstractQueue<T> createInstance(AbstractQueue<T> previous)
{
return supplier.apply(previous);
}
public String getUserFriendlyName()
{
return userFriendlyName;
}
public String getEmoji()
{
return emoji;
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.settings;
/**
*
* @author Michaili K
*/
public enum RepeatMode
{
OFF(null, "Off"),
ALL("\uD83D\uDD01", "All"), // 🔁
SINGLE("\uD83D\uDD02", "Single"); // 🔂
private final String emoji;
private final String userFriendlyName;
private RepeatMode(String emoji, String userFriendlyName)
{
this.emoji = emoji;
this.userFriendlyName = userFriendlyName;
}
public String getEmoji()
{
return emoji;
}
public String getUserFriendlyName()
{
return userFriendlyName;
}
}
@@ -0,0 +1,198 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.settings;
import com.jagrosh.jdautilities.command.GuildSettingsProvider;
import java.util.Collection;
import java.util.Collections;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class Settings implements GuildSettingsProvider
{
private final SettingsManager manager;
protected long textId;
protected long voiceId;
protected long roleId;
private int volume;
private String defaultPlaylist;
private RepeatMode repeatMode;
private QueueType queueType;
private String prefix;
private double skipRatio;
public Settings(SettingsManager manager, String textId, String voiceId, String roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio, QueueType queueType)
{
this.manager = manager;
try
{
this.textId = Long.parseLong(textId);
}
catch(NumberFormatException e)
{
this.textId = 0;
}
try
{
this.voiceId = Long.parseLong(voiceId);
}
catch(NumberFormatException e)
{
this.voiceId = 0;
}
try
{
this.roleId = Long.parseLong(roleId);
}
catch(NumberFormatException e)
{
this.roleId = 0;
}
this.volume = volume;
this.defaultPlaylist = defaultPlaylist;
this.repeatMode = repeatMode;
this.prefix = prefix;
this.skipRatio = skipRatio;
this.queueType = queueType;
}
public Settings(SettingsManager manager, long textId, long voiceId, long roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio, QueueType queueType)
{
this.manager = manager;
this.textId = textId;
this.voiceId = voiceId;
this.roleId = roleId;
this.volume = volume;
this.defaultPlaylist = defaultPlaylist;
this.repeatMode = repeatMode;
this.prefix = prefix;
this.skipRatio = skipRatio;
this.queueType = queueType;
}
// Getters
public TextChannel getTextChannel(Guild guild)
{
return guild == null ? null : guild.getTextChannelById(textId);
}
public VoiceChannel getVoiceChannel(Guild guild)
{
return guild == null ? null : guild.getVoiceChannelById(voiceId);
}
public Role getRole(Guild guild)
{
return guild == null ? null : guild.getRoleById(roleId);
}
public int getVolume()
{
return volume;
}
public String getDefaultPlaylist()
{
return defaultPlaylist;
}
public RepeatMode getRepeatMode()
{
return repeatMode;
}
public String getPrefix()
{
return prefix;
}
public double getSkipRatio()
{
return skipRatio;
}
public QueueType getQueueType()
{
return queueType;
}
@Override
public Collection<String> getPrefixes()
{
return prefix == null ? Collections.emptySet() : Collections.singleton(prefix);
}
// Setters
public void setTextChannel(TextChannel tc)
{
this.textId = tc == null ? 0 : tc.getIdLong();
this.manager.writeSettings();
}
public void setVoiceChannel(VoiceChannel vc)
{
this.voiceId = vc == null ? 0 : vc.getIdLong();
this.manager.writeSettings();
}
public void setDJRole(Role role)
{
this.roleId = role == null ? 0 : role.getIdLong();
this.manager.writeSettings();
}
public void setVolume(int volume)
{
this.volume = volume;
this.manager.writeSettings();
}
public void setDefaultPlaylist(String defaultPlaylist)
{
this.defaultPlaylist = defaultPlaylist;
this.manager.writeSettings();
}
public void setRepeatMode(RepeatMode mode)
{
this.repeatMode = mode;
this.manager.writeSettings();
}
public void setPrefix(String prefix)
{
this.prefix = prefix;
this.manager.writeSettings();
}
public void setSkipRatio(double skipRatio)
{
this.skipRatio = skipRatio;
this.manager.writeSettings();
}
public void setQueueType(QueueType queueType)
{
this.queueType = queueType;
this.manager.writeSettings();
}
}
@@ -0,0 +1,133 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.settings;
import com.jagrosh.jdautilities.command.GuildSettingsManager;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.HashMap;
import net.dv8tion.jda.api.entities.Guild;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class SettingsManager implements GuildSettingsManager<Settings>
{
private final static String SETTINGS_FILE = "serversettings.json";
private final HashMap<Long,Settings> settings;
public SettingsManager()
{
this.settings = new HashMap<>();
try {
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(OtherUtil.getPath(SETTINGS_FILE))));
loadedSettings.keySet().forEach((id) -> {
JSONObject o = loadedSettings.getJSONObject(id);
// Legacy version support: On versions 0.3.3 and older, the repeat mode was represented as a boolean.
if (!o.has("repeat_mode") && o.has("repeat") && o.getBoolean("repeat"))
o.put("repeat_mode", RepeatMode.ALL);
settings.put(Long.parseLong(id), new Settings(this,
o.has("text_channel_id") ? o.getString("text_channel_id") : null,
o.has("voice_channel_id")? o.getString("voice_channel_id") : null,
o.has("dj_role_id") ? o.getString("dj_role_id") : null,
o.has("volume") ? o.getInt("volume") : 100,
o.has("default_playlist")? o.getString("default_playlist") : null,
o.has("repeat_mode") ? o.getEnum(RepeatMode.class, "repeat_mode"): RepeatMode.OFF,
o.has("prefix") ? o.getString("prefix") : null,
o.has("skip_ratio") ? o.getDouble("skip_ratio") : -1,
o.has("queue_type") ? o.getEnum(QueueType.class, "queue_type") : QueueType.FAIR));
});
} catch (NoSuchFileException e) {
// create an empty json file
try {
LoggerFactory.getLogger("Settings").info("serversettings.json will be created in " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
Files.write(OtherUtil.getPath("serversettings.json"), new JSONObject().toString(4).getBytes());
} catch(IOException ex) {
LoggerFactory.getLogger("Settings").warn("Failed to create new settings file: "+ex);
}
return;
} catch(IOException | JSONException e) {
LoggerFactory.getLogger("Settings").warn("Failed to load server settings: "+e);
}
LoggerFactory.getLogger("Settings").info("serversettings.json loaded from " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
}
/**
* Gets non-null settings for a Guild
*
* @param guild the guild to get settings for
* @return the existing settings, or new settings for that guild
*/
@Override
public Settings getSettings(Guild guild)
{
return getSettings(guild.getIdLong());
}
public Settings getSettings(long guildId)
{
return settings.computeIfAbsent(guildId, id -> createDefaultSettings());
}
private Settings createDefaultSettings()
{
return new Settings(this, 0, 0, 0, 100, null, RepeatMode.OFF, null, -1, QueueType.FAIR);
}
protected void writeSettings()
{
JSONObject obj = new JSONObject();
settings.keySet().stream().forEach(key -> {
JSONObject o = new JSONObject();
Settings s = settings.get(key);
if(s.textId!=0)
o.put("text_channel_id", Long.toString(s.textId));
if(s.voiceId!=0)
o.put("voice_channel_id", Long.toString(s.voiceId));
if(s.roleId!=0)
o.put("dj_role_id", Long.toString(s.roleId));
if(s.getVolume()!=100)
o.put("volume",s.getVolume());
if(s.getDefaultPlaylist() != null)
o.put("default_playlist", s.getDefaultPlaylist());
if(s.getRepeatMode()!=RepeatMode.OFF)
o.put("repeat_mode", s.getRepeatMode());
if(s.getPrefix() != null)
o.put("prefix", s.getPrefix());
if(s.getSkipRatio() != -1)
o.put("skip_ratio", s.getSkipRatio());
if(s.getQueueType() != QueueType.FAIR)
o.put("queue_type", s.getQueueType().name());
obj.put(Long.toString(key), o);
});
try {
Files.write(OtherUtil.getPath(SETTINGS_FILE), obj.toString(4).getBytes());
} catch(IOException ex){
LoggerFactory.getLogger("Settings").warn("Failed to write to file: "+ex);
}
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.utils;
import com.jagrosh.jmusicbot.audio.RequestMetadata.UserInfo;
import java.util.List;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class FormatUtil {
public static String formatUsername(String username, String discrim)
{
if(discrim == null || discrim.equals("0000"))
{
return username;
}
else
{
return username + "#" + discrim;
}
}
public static String formatUsername(UserInfo userinfo)
{
return formatUsername(userinfo.username, userinfo.discrim);
}
public static String formatUsername(User user)
{
return formatUsername(user.getName(), user.getDiscriminator());
}
public static String progressBar(double percent)
{
String str = "";
for(int i=0; i<12; i++)
if(i == (int)(percent*12))
str+="\uD83D\uDD18"; // 🔘
else
str+="▬";
return str;
}
public static String volumeIcon(int volume)
{
if(volume == 0)
return "\uD83D\uDD07"; // 🔇
if(volume < 30)
return "\uD83D\uDD08"; // 🔈
if(volume < 70)
return "\uD83D\uDD09"; // 🔉
return "\uD83D\uDD0A"; // 🔊
}
public static String listOfTChannels(List<TextChannel> list, String query)
{
String out = " Multiple text channels found matching \""+query+"\":";
for(int i=0; i<6 && i<list.size(); i++)
out+="\n - "+list.get(i).getName()+" (<#"+list.get(i).getId()+">)";
if(list.size()>6)
out+="\n**And "+(list.size()-6)+" more...**";
return out;
}
public static String listOfVChannels(List<VoiceChannel> list, String query)
{
String out = " Multiple voice channels found matching \""+query+"\":";
for(int i=0; i<6 && i<list.size(); i++)
out+="\n - "+list.get(i).getAsMention()+" (ID:"+list.get(i).getId()+")";
if(list.size()>6)
out+="\n**And "+(list.size()-6)+" more...**";
return out;
}
public static String listOfRoles(List<Role> list, String query)
{
String out = " Multiple text channels found matching \""+query+"\":";
for(int i=0; i<6 && i<list.size(); i++)
out+="\n - "+list.get(i).getName()+" (ID:"+list.get(i).getId()+")";
if(list.size()>6)
out+="\n**And "+(list.size()-6)+" more...**";
return out;
}
public static String filter(String input)
{
return input.replace("\u202E","")
.replace("@everyone", "@\u0435veryone") // cyrillic letter e
.replace("@here", "@h\u0435re") // cyrillic letter e
.trim();
}
}
@@ -0,0 +1,231 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.utils;
import com.jagrosh.jmusicbot.JMusicBot;
import com.jagrosh.jmusicbot.entities.Prompt;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Path;
import java.nio.file.Paths;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.ApplicationInfo;
import net.dv8tion.jda.api.entities.User;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class OtherUtil
{
public final static String NEW_VERSION_AVAILABLE = "There is a new version of JMusicBot available!\n"
+ "Current version: %s\n"
+ "New Version: %s\n\n"
+ "Please visit https://github.com/jagrosh/MusicBot/releases/latest to get the latest release.";
private final static String WINDOWS_INVALID_PATH = "c:\\windows\\system32\\";
/**
* gets a Path from a String
* also fixes the windows tendency to try to start in system32
* any time the bot tries to access this path, it will instead start in the location of the jar file
*
* @param path the string path
* @return the Path object
*/
public static Path getPath(String path)
{
Path result = Paths.get(path);
// special logic to prevent trying to access system32
if(result.toAbsolutePath().toString().toLowerCase().startsWith(WINDOWS_INVALID_PATH))
{
try
{
result = Paths.get(new File(JMusicBot.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath() + File.separator + path);
}
catch(URISyntaxException ignored) {}
}
return result;
}
/**
* Loads a resource from the jar as a string
*
* @param clazz class base object
* @param name name of resource
* @return string containing the contents of the resource
*/
public static String loadResource(Object clazz, String name)
{
try(BufferedReader reader = new BufferedReader(new InputStreamReader(clazz.getClass().getResourceAsStream(name))))
{
StringBuilder sb = new StringBuilder();
reader.lines().forEach(line -> sb.append("\r\n").append(line));
return sb.toString().trim();
}
catch(IOException ignored)
{
return null;
}
}
/**
* Loads image data from a URL
*
* @param url url of image
* @return inputstream of url
*/
public static InputStream imageFromUrl(String url)
{
if(url==null)
return null;
try
{
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
urlConnection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");
return urlConnection.getInputStream();
}
catch(IOException | IllegalArgumentException ignore) {}
return null;
}
/**
* Parses an activity from a string
*
* @param game the game, including the action such as 'playing' or 'watching'
* @return the parsed activity
*/
public static Activity parseGame(String game)
{
if(game==null || game.trim().isEmpty() || game.trim().equalsIgnoreCase("default"))
return null;
String lower = game.toLowerCase();
if(lower.startsWith("playing"))
return Activity.playing(makeNonEmpty(game.substring(7).trim()));
if(lower.startsWith("listening to"))
return Activity.listening(makeNonEmpty(game.substring(12).trim()));
if(lower.startsWith("listening"))
return Activity.listening(makeNonEmpty(game.substring(9).trim()));
if(lower.startsWith("watching"))
return Activity.watching(makeNonEmpty(game.substring(8).trim()));
if(lower.startsWith("streaming"))
{
String[] parts = game.substring(9).trim().split("\\s+", 2);
if(parts.length == 2)
{
return Activity.streaming(makeNonEmpty(parts[1]), "https://twitch.tv/"+parts[0]);
}
}
return Activity.playing(game);
}
public static String makeNonEmpty(String str)
{
return str == null || str.isEmpty() ? "\u200B" : str;
}
public static OnlineStatus parseStatus(String status)
{
if(status==null || status.trim().isEmpty())
return OnlineStatus.ONLINE;
OnlineStatus st = OnlineStatus.fromKey(status);
return st == null ? OnlineStatus.ONLINE : st;
}
public static void checkJavaVersion(Prompt prompt)
{
if(!System.getProperty("java.vm.name").contains("64"))
prompt.alert(Prompt.Level.WARNING, "Java Version",
"It appears that you may not be using a supported Java version. Please use 64-bit java.");
}
public static void checkVersion(Prompt prompt)
{
// Get current version number
String version = getCurrentVersion();
// Check for new version
String latestVersion = getLatestVersion();
if(latestVersion!=null && !latestVersion.equals(version))
{
prompt.alert(Prompt.Level.WARNING, "JMusicBot Version", String.format(NEW_VERSION_AVAILABLE, version, latestVersion));
}
}
public static String getCurrentVersion()
{
if(JMusicBot.class.getPackage()!=null && JMusicBot.class.getPackage().getImplementationVersion()!=null)
return JMusicBot.class.getPackage().getImplementationVersion();
else
return "UNKNOWN";
}
public static String getLatestVersion()
{
try
{
Response response = new OkHttpClient.Builder().build()
.newCall(new Request.Builder().get().url("https://api.github.com/repos/jagrosh/MusicBot/releases/latest").build())
.execute();
ResponseBody body = response.body();
if(body != null)
{
try(Reader reader = body.charStream())
{
JSONObject obj = new JSONObject(new JSONTokener(reader));
return obj.getString("tag_name");
}
finally
{
response.close();
}
}
else
return null;
}
catch(IOException | JSONException | NullPointerException ex)
{
return null;
}
}
/**
* Checks if the bot JMusicBot is being run on is supported & returns the reason if it is not.
* @return A string with the reason, or null if it is supported.
*/
public static String getUnsupportedBotReason(JDA jda)
{
if (jda.getSelfUser().getFlags().contains(User.UserFlag.VERIFIED_BOT))
return "The bot is verified. Using JMusicBot in a verified bot is not supported.";
ApplicationInfo info = jda.retrieveApplicationInfo().complete();
if (info.isBotPublic())
return "\"Public Bot\" is enabled. Using JMusicBot as a public bot is not supported. Please disable it in the "
+ "Developer Dashboard at https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot.";
return null;
}
}
@@ -0,0 +1,138 @@
/*
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.utils;
public class TimeUtil
{
public static String formatTime(long duration)
{
if(duration == Long.MAX_VALUE)
return "LIVE";
long seconds = Math.round(duration/1000.0);
long hours = seconds/(60*60);
seconds %= 60*60;
long minutes = seconds/60;
seconds %= 60;
return (hours>0 ? hours+":" : "") + (minutes<10 ? "0"+minutes : minutes) + ":" + (seconds<10 ? "0"+seconds : seconds);
}
/**
* Parses a seek time string into milliseconds and determines if it's relative.
* Supports "colon time" (HH:MM:SS) or "unit time" (1h20m)
* @param args time string
* @return SeekTime object, or null if the string could not be parsed
*/
public static SeekTime parseTime(String args)
{
if (args.length() == 0) return null;
String timestamp = args;
boolean relative = false; // seek forward or backward
boolean isSeekingBackwards = false;
char first = timestamp.charAt(0);
if (first == '+' || first == '-')
{
relative = true;
isSeekingBackwards = first == '-';
timestamp = timestamp.substring(1);
}
long milliseconds = parseColonTime(timestamp);
if(milliseconds == -1) milliseconds = parseUnitTime(timestamp);
if(milliseconds == -1) return null;
milliseconds *= isSeekingBackwards ? -1 : 1;
return new SeekTime(milliseconds, relative);
}
/**
* @param timestamp timestamp formatted as: [+ | -] &lt;HH:MM:SS | MM:SS | SS&gt;
* @return Time in milliseconds
*/
public static long parseColonTime(String timestamp)
{
String[] timestampSplitArray = timestamp.split(":+");
if(timestampSplitArray.length > 3 )
return -1;
double[] timeUnitArray = new double[3]; // hours, minutes, seconds
for(int index = 0; index < timestampSplitArray.length; index++)
{
String unit = timestampSplitArray[index];
if (unit.startsWith("+") || unit.startsWith("-")) return -1;
unit = unit.replace(",", ".");
try
{
timeUnitArray[index + 3 - timestampSplitArray.length] = Double.parseDouble(unit);
}
catch (NumberFormatException e)
{
return -1;
}
}
return Math.round(timeUnitArray[0] * 3600000 + timeUnitArray[1] * 60000 + timeUnitArray[2] * 1000);
}
/**
*
* @param timestr time string formatted as a unit time, e.g. 20m10, 1d5h20m14s or 1h and 20m
* @return Time in milliseconds
*/
public static long parseUnitTime(String timestr)
{
timestr = timestr.replaceAll("(?i)(\\s|,|and)","")
.replaceAll("(?is)(-?\\d+|[a-z]+)", "$1 ")
.trim();
String[] vals = timestr.split("\\s+");
int time = 0;
try
{
for(int j=0; j<vals.length; j+=2)
{
int num = Integer.parseInt(vals[j]);
if(vals.length > j+1)
{
if(vals[j+1].toLowerCase().startsWith("m"))
num*=60;
else if(vals[j+1].toLowerCase().startsWith("h"))
num*=60*60;
else if(vals[j+1].toLowerCase().startsWith("d"))
num*=60*60*24;
}
time+=num*1000;
}
}
catch(Exception ex)
{
return -1;
}
return time;
}
public static class SeekTime
{
public final long milliseconds;
public final boolean relative;
private SeekTime(long milliseconds, boolean relative)
{
this.milliseconds = milliseconds;
this.relative = relative;
}
}
}
-332
View File
@@ -1,332 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import me.jagrosh.jdautilities.commandclient.Command.Category;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jdautilities.waiter.EventWaiter;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import me.jagrosh.jmusicbot.gui.GUI;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.JDA;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.Role;
import net.dv8tion.jda.core.entities.TextChannel;
import net.dv8tion.jda.core.entities.VoiceChannel;
import net.dv8tion.jda.core.events.ReadyEvent;
import net.dv8tion.jda.core.events.ShutdownEvent;
import net.dv8tion.jda.core.hooks.ListenerAdapter;
import net.dv8tion.jda.core.utils.PermissionUtil;
import net.dv8tion.jda.core.utils.SimpleLog;
import org.json.JSONException;
import org.json.JSONObject;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class Bot extends ListenerAdapter {
private final HashMap<String,Settings> settings;
private final AudioPlayerManager manager;
private final EventWaiter waiter;
private final ScheduledExecutorService threadpool;
private JDA jda;
private GUI gui;
//private GuildsPanel panel;
public final Category MUSIC = new Category("Music");
public final Category DJ = new Category("DJ", event ->
{
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
return true;
if(event.getGuild()==null)
return true;
if(PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER))
return true;
Role dj = event.getGuild().getRoleById(getSettings(event.getGuild()).getRoleId());
return event.getMember().getRoles().contains(dj);
});
public final Category ADMIN = new Category("Admin", event ->
{
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
return true;
if(event.getGuild()==null)
return true;
return PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER);
});
public final Category OWNER = new Category("Owner");
public Bot(EventWaiter waiter)
{
this.waiter = waiter;
this.settings = new HashMap<>();
manager = new DefaultAudioPlayerManager();
threadpool = Executors.newSingleThreadScheduledExecutor();
AudioSourceManagers.registerRemoteSources(manager);
try {
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(Paths.get("serversettings.json"))));
for(String id: loadedSettings.keySet())
{
JSONObject o = loadedSettings.getJSONObject(id);
settings.put(id, new Settings(o.has("text_channel_id")?o.getString("text_channel_id"):null,
o.has("voice_channel_id")?o.getString("voice_channel_id"):null,
o.has("dj_role_id")?o.getString("dj_role_id"):null));
}
} catch(IOException | JSONException e) {
SimpleLog.getLog("Settings").warn("Failed to load server settings: "+e);
}
}
public EventWaiter getWaiter()
{
return waiter;
}
public AudioPlayerManager getAudioManager()
{
return manager;
}
public int queueTrack(CommandEvent event, AudioTrack track)
{
return setUpHandler(event).addTrack(track, event.getAuthor());
}
public AudioHandler setUpHandler(CommandEvent event)
{
AudioHandler handler;
if(event.getGuild().getAudioManager().getSendingHandler()==null)
{
AudioPlayer player = manager.createPlayer();
handler = new AudioHandler(player, event.getGuild());
player.addListener(handler);
event.getGuild().getAudioManager().setSendingHandler(handler);
threadpool.scheduleWithFixedDelay(() -> updateTopic(event.getGuild(),handler), 0, 5, TimeUnit.SECONDS);
}
else
{
handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
}
return handler;
}
private void updateTopic(Guild guild, AudioHandler handler)
{
TextChannel tchan = guild.getTextChannelById(getSettings(guild).getTextId());
if(tchan!=null && PermissionUtil.checkPermission(tchan, guild.getSelfMember(), Permission.MANAGE_CHANNEL))
{
String otherText;
if(tchan.getTopic()==null || tchan.getTopic().isEmpty())
otherText = "";
else if(tchan.getTopic().contains("\u200B"))
otherText = tchan.getTopic().substring(tchan.getTopic().indexOf("\u200B"));
else
otherText = "\u200B\n "+tchan.getTopic();
String text = FormatUtil.formattedAudio(handler, guild.getJDA(), true)+otherText;
if(!text.equals(tchan.getTopic()))
tchan.getManager().setTopic(text).queue();
}
}
public void shutdown(){
manager.shutdown();
threadpool.shutdownNow();
jda.getGuilds().stream().forEach(g -> {
g.getAudioManager().closeAudioConnection();
AudioHandler ah = (AudioHandler)g.getAudioManager().getSendingHandler();
if(ah!=null)
{
ah.getQueue().clear();
ah.getPlayer().destroy();
updateTopic(g, ah);
}
});
jda.shutdown();
}
public void setGUI(GUI gui)
{
this.gui = gui;
}
@Override
public void onShutdown(ShutdownEvent event) {
if(gui!=null)
gui.dispose();
}
@Override
public void onReady(ReadyEvent event) {
this.jda = event.getJDA();
//if(panel!=null)
// panel.updateList(event.getJDA().getGuilds());
}
// settings
public Settings getSettings(Guild guild)
{
return settings.getOrDefault(guild.getId(), Settings.DEFAULT_SETTINGS);
}
public void setTextChannel(TextChannel channel)
{
Settings s = settings.get(channel.getGuild().getId());
if(s==null)
{
settings.put(channel.getGuild().getId(), new Settings(channel.getId(),null,null));
}
else
{
s.setTextId(channel.getId());
}
writeSettings();
}
public void setVoiceChannel(VoiceChannel channel)
{
Settings s = settings.get(channel.getGuild().getId());
if(s==null)
{
settings.put(channel.getGuild().getId(), new Settings(null,channel.getId(),null));
}
else
{
s.setVoiceId(channel.getId());
}
writeSettings();
}
public void setRole(Role role)
{
Settings s = settings.get(role.getGuild().getId());
if(s==null)
{
settings.put(role.getGuild().getId(), new Settings(null,null,role.getId()));
}
else
{
s.setRoleId(role.getId());
}
writeSettings();
}
public void clearTextChannel(Guild guild)
{
Settings s = getSettings(guild);
if(s!=null)
{
if(s.getVoiceId()==null && s.getRoleId()==null)
settings.remove(guild.getId());
else
s.setTextId(null);
writeSettings();
}
}
public void clearVoiceChannel(Guild guild)
{
Settings s = getSettings(guild);
if(s!=null)
{
if(s.getTextId()==null && s.getRoleId()==null)
settings.remove(guild.getId());
else
s.setVoiceId(null);
writeSettings();
}
}
public void clearRole(Guild guild)
{
Settings s = getSettings(guild);
if(s!=null)
{
if(s.getVoiceId()==null && s.getTextId()==null)
settings.remove(guild.getId());
else
s.setRoleId(null);
writeSettings();
}
}
private void writeSettings()
{
JSONObject obj = new JSONObject();
settings.keySet().stream().forEach(key -> {
JSONObject o = new JSONObject();
Settings s = settings.get(key);
if(s.getTextId()!=null)
o.put("text_channel_id", s.getTextId());
if(s.getVoiceId()!=null)
o.put("voice_channel_id", s.getVoiceId());
if(s.getRoleId()!=null)
o.put("dj_role_id", s.getRoleId());
obj.put(key, o);
});
try {
Files.write(Paths.get("serversettings.json"), obj.toString(4).getBytes());
} catch(IOException ex){
SimpleLog.getLog("Settings").warn("Failed to write to file: "+ex);
}
}
//gui stuff
/*public void registerPanel(GuildsPanel panel)
{
this.panel = panel;
threadpool.scheduleWithFixedDelay(() -> updatePanel(), 0, 5, TimeUnit.SECONDS);
}
public void updatePanel()
{
System.out.println("updating...");
Guild guild = jda.getGuilds().get(panel.getIndex());
panel.updatePanel((AudioHandler)guild.getAudioManager().getSendingHandler());
}
@Override
public void onGuildJoin(GuildJoinEvent event) {
if(panel!=null)
panel.updateList(event.getJDA().getGuilds());
}
@Override
public void onGuildLeave(GuildLeaveEvent event) {
if(panel!=null)
panel.updateList(event.getJDA().getGuilds());
}
@Override
public void onShutdown(ShutdownEvent event) {
((GUI)panel.getTopLevelAncestor()).dispose();
}*/
}
@@ -1,177 +0,0 @@
/*
* Copyright 2016 John Grosh (jagrosh)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import javax.swing.JOptionPane;
/**
*
* @author John Grosh (jagrosh)
*/
public class Config {
private final boolean nogui;
private String prefix;
private String token;
private String owner;
private String success;
private String warning;
private String error;
public Config(boolean nogui)
{
this.nogui = nogui;
List<String> lines;
try {
lines = Files.readAllLines(Paths.get("config.txt"));
for(String line: lines)
{
String[] parts = line.split("=",2);
String key = parts[0].trim().toLowerCase();
String value = parts.length>1 ? parts[1].trim() : null;
switch(key)
{
case "token":
token = value;
break;
case "prefix":
prefix = value;
break;
case "owner":
owner = value;
break;
case "success":
success = value;
break;
case "warning":
warning = value;
break;
case "error":
error = value;
break;
}
}
} catch (IOException ex) {
alert("'config.txt' was not found!");
lines = new LinkedList<>();
}
boolean write = false;
if(token==null || token.isEmpty())
{
token = prompt("Please provide a bot token."
+ "\nInstructions for obtaining a token can be found here:"
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Getting-a-Bot-Token."
+ "\nBot Token: ");
if(token==null)
{
alert("No token provided! Exiting.");
System.exit(0);
}
else
{
lines.add("token="+token);
write = true;
}
}
if(owner==null || !owner.matches("\\d{17,20}"))
{
owner = prompt("Owner ID was missing, or the provided owner ID is not valid."
+ "\nPlease provide the User ID of the bot's owner."
+ "\nInstructions for obtaining your User ID can be found here:"
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Finding-Your-User-ID"
+ "\nOwner User ID: ");
if(owner==null || !owner.matches("\\d{17,20}"))
{
alert("Invalid User ID! Exiting.");
System.exit(0);
}
else
{
lines.add("owner="+owner);
write = true;
}
}
if(write)
{
StringBuilder builder = new StringBuilder();
lines.stream().forEach(s -> builder.append(s).append("\r\n"));
try {
Files.write(Paths.get("config.txt"), builder.toString().trim().getBytes());
} catch(IOException ex) {
alert("Failed to write new config options to config.txt: "+ex);
}
}
}
public String getPrefix()
{
return prefix;
}
public String getToken()
{
return token;
}
public String getOwnerId()
{
return owner;
}
public String getSuccess()
{
return success==null ? "\uD83C\uDFB6" : success;
}
public String getWarning()
{
return warning==null ? "\uD83D\uDCA1" : warning;
}
public String getError()
{
return error==null ? "\uD83D\uDEAB" : error;
}
private void alert(String message)
{
if(nogui)
System.out.println("[WARNING] "+message);
else
{
JOptionPane.showMessageDialog(null, message, "JMusicBot", JOptionPane.WARNING_MESSAGE);
}
}
private String prompt(String content)
{
if(nogui)
{
Scanner scanner = new Scanner(System.in);
System.out.println(content);
return scanner.next();
}
else
{
return JOptionPane.showInputDialog(null, content, "JMusicBot", JOptionPane.WARNING_MESSAGE);
}
}
}
@@ -1,121 +0,0 @@
/*
* Copyright 2016 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot;
import java.awt.Color;
import javax.security.auth.login.LoginException;
import me.jagrosh.jdautilities.commandclient.CommandClient;
import me.jagrosh.jdautilities.commandclient.CommandClientBuilder;
import me.jagrosh.jdautilities.commandclient.examples.*;
import me.jagrosh.jdautilities.waiter.EventWaiter;
import me.jagrosh.jmusicbot.commands.*;
import me.jagrosh.jmusicbot.gui.GUI;
import net.dv8tion.jda.core.AccountType;
import net.dv8tion.jda.core.JDABuilder;
import net.dv8tion.jda.core.OnlineStatus;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Game;
import net.dv8tion.jda.core.exceptions.RateLimitedException;
import net.dv8tion.jda.core.utils.SimpleLog;
/**
*
* @author John Grosh (jagrosh)
*/
public class JMusicBot {
/**
* @param args the command line arguments
*/
public static void main(String[] args){
// check run mode(s)
boolean nogui = false;
for(String arg: args)
if("-nogui".equalsIgnoreCase(arg))
nogui = true;
// load config
Config config = new Config(nogui);
// set up the listener
EventWaiter waiter = new EventWaiter();
Bot bot = new Bot(waiter);
AboutCommand.IS_AUTHOR = false;
// set up the command client
CommandClient client = new CommandClientBuilder()
.setPrefix(config.getPrefix())
.setOwnerId(config.getOwnerId())
.setEmojis(config.getSuccess(), config.getWarning(), config.getError())
.addCommands(
new AboutCommand(Color.BLUE.brighter(),
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot)",
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
Permission.MESSAGE_READ, Permission.MESSAGE_WRITE, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI,
Permission.MANAGE_CHANNEL, Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE),
new PingCommand(),
new SettingsCmd(bot),
new NowplayingCmd(bot),
new PlayCmd(bot),
new QueueCmd(bot),
new RemoveCmd(bot),
new SearchCmd(bot),
new ShuffleCmd(bot),
new SkipCmd(bot),
new ForceskipCmd(bot),
new SkiptoCmd(bot),
new StopCmd(bot),
new VolumeCmd(bot),
new SetdjCmd(bot),
new SettcCmd(bot),
new SetvcCmd(bot),
new GuildlistCommand(waiter),
new SetavatarCmd(bot),
new SetgameCmd(bot),
new SetnameCmd(bot),
new ShutdownCmd(bot)
).build();
if(!nogui)
{
GUI gui = new GUI(bot);
bot.setGUI(gui);
gui.init();
}
// attempt to log in and start
try {
new JDABuilder(AccountType.BOT)
.setToken(config.getToken())
.setAudioEnabled(true)
.setGame(Game.of("loading..."))
.setStatus(OnlineStatus.DO_NOT_DISTURB)
.addListener(client)
.addListener(waiter)
.addListener(bot)
.buildAsync();
} catch (LoginException | IllegalArgumentException | RateLimitedException ex) {
SimpleLog.getLog("Login").fatal(ex);
}
}
}
@@ -1,65 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class Settings {
public final static Settings DEFAULT_SETTINGS = new Settings(null, null, null);
private String textId;
private String voiceId;
private String roleId;
public Settings(String textId, String voiceId, String roleId)
{
this.textId = textId;
this.voiceId = voiceId;
this.roleId = roleId;
}
public String getTextId()
{
return textId;
}
public String getVoiceId()
{
return voiceId;
}
public String getRoleId()
{
return roleId;
}
public void setTextId(String id)
{
this.textId = id;
}
public void setVoiceId(String id)
{
this.voiceId = id;
}
public void setRoleId(String id)
{
this.roleId = id;
}
}
@@ -1,118 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.audio;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
import java.util.HashSet;
import java.util.Set;
import me.jagrosh.jmusicbot.queue.FairQueue;
import net.dv8tion.jda.core.audio.AudioSendHandler;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler {
private final AudioPlayer audioPlayer;
private final Guild guild;
private final FairQueue<QueuedTrack> queue;
private final Set<String> votes;
private AudioFrame lastFrame;
private QueuedTrack current;
public AudioHandler(AudioPlayer audioPlayer, Guild guild) {
this.audioPlayer = audioPlayer;
this.guild = guild;
queue = new FairQueue<>();
votes = new HashSet<>();
}
public int addTrack(AudioTrack track, User user)
{
QueuedTrack qt = new QueuedTrack(track, user.getId());
if(current==null)
{
current = qt;
audioPlayer.playTrack(track);
return -1;
}
else
{
return queue.add(qt);
}
}
public QueuedTrack getCurrentTrack()
{
return current;
}
public FairQueue<QueuedTrack> getQueue()
{
return queue;
}
public Set<String> getVotes()
{
return votes;
}
public AudioPlayer getPlayer()
{
return audioPlayer;
}
@Override
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
if(queue.isEmpty())
{
current = null;
guild.getAudioManager().closeAudioConnection();
}
else
{
current = queue.pull();
player.playTrack(current.getTrack());
}
}
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track) {
votes.clear();
}
@Override
public boolean canProvide() {
lastFrame = audioPlayer.provide();
return lastFrame != null;
}
@Override
public byte[] provide20MsAudio() {
return lastFrame.data;
}
@Override
public boolean isOpus() {
return true;
}
}
@@ -1,52 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.audio;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import me.jagrosh.jmusicbot.queue.Queueable;
import me.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueuedTrack implements Queueable {
private final AudioTrack track;
private final String owner;
public QueuedTrack(AudioTrack track, String owner)
{
this.track = track;
this.owner = owner;
}
@Override
public String getIdentifier() {
return owner;
}
public AudioTrack getTrack()
{
return track;
}
@Override
public String toString() {
return "`["+FormatUtil.formatTime(track.getDuration())+"]` **" + track.getInfo().title +"** - <@"+owner+">";
}
}
@@ -1,84 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.Settings;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.GuildVoiceState;
import net.dv8tion.jda.core.entities.TextChannel;
import net.dv8tion.jda.core.entities.VoiceChannel;
import net.dv8tion.jda.core.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public abstract class MusicCommand extends Command {
protected final Bot bot;
protected boolean bePlaying;
protected boolean beListening;
public MusicCommand(Bot bot)
{
this.bot = bot;
this.guildOnly = true;
this.category = bot.MUSIC;
}
@Override
protected void execute(CommandEvent event) {
Settings settings = bot.getSettings(event.getGuild());
TextChannel tchannel = event.getGuild().getTextChannelById(settings.getTextId());
if(tchannel!=null && !event.getTextChannel().equals(tchannel))
{
try {
event.getMessage().deleteMessage().queue();
} catch(PermissionException e){}
event.replyInDM(event.getClient().getError()+" You can only use that command in <#"+settings.getTextId()+">!");
return;
}
if(bePlaying
&& (event.getGuild().getAudioManager().getSendingHandler()==null
|| ((AudioHandler)event.getGuild().getAudioManager().getSendingHandler()).getCurrentTrack()==null))
{
event.reply(event.getClient().getError()+" There must be music playing to use that!");
return;
}
if(beListening)
{
VoiceChannel current = event.getGuild().getSelfMember().getVoiceState().getChannel();
if(current==null)
current = event.getGuild().getVoiceChannelById(settings.getVoiceId());
GuildVoiceState userState = event.getMember().getVoiceState();
if(!userState.inVoiceChannel() || userState.isDeafened() || (current!=null && !userState.getChannel().equals(current)))
{
event.reply(event.getClient().getError()
+" You must be listening in "+(current==null ? "a voice channel" : "**"+current.getName()+"**")
+" to use that!");
return;
}
if(!event.getGuild().getSelfMember().getVoiceState().inVoiceChannel())
event.getGuild().getAudioManager().openAudioConnection(userState.getChannel());
}
doCommand(event);
}
public abstract void doCommand(CommandEvent event);
}
@@ -1,42 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import me.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class NowplayingCmd extends MusicCommand {
public NowplayingCmd(Bot bot)
{
super(bot);
this.name = "nowplaying";
this.help = "shows the song that is currently playing";
this.aliases = new String[]{"np","current"};
}
@Override
public void doCommand(CommandEvent event) {
event.reply(FormatUtil.formattedAudio((AudioHandler)event.getGuild().getAudioManager().getSendingHandler(), event.getJDA(), false));
}
}
@@ -1,115 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.Message;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PlayCmd extends MusicCommand {
public PlayCmd(Bot bot)
{
super(bot);
this.name = "play";
this.arguments = "<title|URL>";
this.help = "plays the provided song";
this.beListening = true;
this.bePlaying = false;
}
@Override
public void doCommand(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a url or song title.");
return;
}
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
? event.getArgs().substring(1,event.getArgs().length()-1)
: event.getArgs();
event.getChannel().sendMessage("\u231A Loading... `["+args+"]`").queue(m -> {
bot.getAudioManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false));
});
}
private class ResultHandler implements AudioLoadResultHandler {
final Message m;
final CommandEvent event;
final boolean ytsearch;
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
{
this.m = m;
this.event = event;
this.ytsearch = ytsearch;
}
@Override
public void trackLoaded(AudioTrack track) {
int pos = bot.queueTrack(event, track)+1;
m.editMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
if(playlist.getTracks().size()==1 || playlist.isSearchResult() || playlist.getSelectedTrack()!=null)
{
AudioTrack single = playlist.getSelectedTrack()==null?playlist.getTracks().get(0):playlist.getSelectedTrack();
int pos = bot.queueTrack(event, single)+1;
m.editMessage(event.getClient().getSuccess()+" Added **"+single.getInfo().title
+"** (`"+FormatUtil.formatTime(single.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
}
else
{
m.editMessage(event.getClient().getSuccess()+" Found "
+(playlist.getName()==null?"a playlist":"playlist **"+playlist.getName()+"**")+" with `"
+playlist.getTracks().size()+"` entries; adding to the queue.").queue();
playlist.getTracks().stream().forEach((track) -> {
bot.queueTrack(event, track);
});
}
}
@Override
public void noMatches() {
if(ytsearch)
m.editMessage(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.").queue();
else
bot.getAudioManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event,true));
}
@Override
public void loadFailed(FriendlyException throwable) {
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
}
@@ -1,84 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import java.util.List;
import java.util.concurrent.TimeUnit;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jdautilities.menu.pagination.PaginatorBuilder;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import me.jagrosh.jmusicbot.audio.QueuedTrack;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueueCmd extends MusicCommand {
private final PaginatorBuilder builder;
public QueueCmd(Bot bot)
{
super(bot);
this.name = "queue";
this.help = "shows the current queue";
this.arguments = "[pagenum]";
this.aliases = new String[]{"list"};
this.bePlaying = true;
this.botPermissions = new Permission[]{Permission.MESSAGE_ADD_REACTION,Permission.MESSAGE_EMBED_LINKS};
builder = new PaginatorBuilder()
.setColumns(1)
.setFinalAction(m -> {try{m.clearReactions().queue();}catch(PermissionException e){}})
.setItemsPerPage(10)
.waitOnSinglePage(false)
.useNumberedItems(true)
.showPageNumbers(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES)
;
}
@Override
public void doCommand(CommandEvent event) {
int pagenum = 1;
try{
pagenum = Integer.parseInt(event.getArgs());
}catch(NumberFormatException e){}
List<QueuedTrack> list = ((AudioHandler)event.getGuild().getAudioManager().getSendingHandler()).getQueue().getList();
if(list.isEmpty())
{
event.reply(event.getClient().getWarning()+" There is no music in the queue!");
return;
}
String[] songs = new String[list.size()];
long total = 0;
for(int i=0; i<list.size(); i++)
{
total += list.get(i).getTrack().getDuration();
songs[i] = list.get(i).toString();
}
builder.setText(event.getClient().getSuccess()+" Current Queue | "+songs.length+" entries | `"+FormatUtil.formatTime(total)+"` ")
.setItems(songs)
.setUsers(event.getAuthor())
.setColor(event.getSelfMember().getColor())
;
builder.build().paginate(event.getChannel(), pagenum);
}
}
@@ -1,85 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import me.jagrosh.jmusicbot.audio.QueuedTrack;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Role;
import net.dv8tion.jda.core.entities.User;
import net.dv8tion.jda.core.utils.PermissionUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class RemoveCmd extends MusicCommand {
public RemoveCmd(Bot bot)
{
super(bot);
this.name = "remove";
this.help = "removes a song from the queue";
this.arguments = "<position>";
this.aliases = new String[]{"delete"};
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getQueue().isEmpty())
{
event.reply(event.getClient().getError()+" There is nothing in the queue!");
return;
}
int pos;
try {
pos = Integer.parseInt(event.getArgs());
} catch(NumberFormatException e) {
pos = 0;
}
if(pos<1 || pos>handler.getQueue().size())
{
event.reply(event.getClient().getError()+" Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
return;
}
boolean isDJ = PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER);
if(!isDJ)
isDJ = event.getMember().getRoles().contains(event.getGuild().getRoleById(bot.getSettings(event.getGuild()).getRoleId()));
QueuedTrack qt = handler.getQueue().get(pos-1);
if(qt.getIdentifier().equals(event.getAuthor().getId()))
{
handler.getQueue().remove(pos-1);
event.reply(event.getClient().getSuccess()+" Removed **"+qt.getTrack().getInfo().title+"** from the queue");
}
else if(isDJ)
{
handler.getQueue().remove(pos-1);
User u = event.getJDA().getUserById(qt.getIdentifier());
event.reply(event.getClient().getSuccess()+" Removed **"+qt.getTrack().getInfo().title
+"** from the queue (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
}
else
{
event.reply(event.getClient().getError()+" You cannot remove **"+qt.getTrack().getInfo().title+"** because you didn't add it!");
}
}
}
@@ -1,121 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.util.concurrent.TimeUnit;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jdautilities.menu.orderedmenu.OrderedMenuBuilder;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Message;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SearchCmd extends MusicCommand {
private final OrderedMenuBuilder builder;
public SearchCmd(Bot bot)
{
super(bot);
this.name = "search";
this.arguments = "<query>";
this.help = "searches Youtube for a provided query";
this.beListening = true;
this.bePlaying = false;
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
builder = new OrderedMenuBuilder()
.allowTextInput(true)
.useNumbers()
.useCancelButton(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES)
;
}
@Override
public void doCommand(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a query.");
return;
}
event.getChannel().sendMessage("\uD83D\uDD0E Searching... `["+event.getArgs()+"]`").queue(m -> {
bot.getAudioManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event));
});
}
private class ResultHandler implements AudioLoadResultHandler {
final Message m;
final CommandEvent event;
private ResultHandler(Message m, CommandEvent event)
{
this.m = m;
this.event = event;
}
@Override
public void trackLoaded(AudioTrack track) {
int pos = bot.queueTrack(event, track)+1;
m.editMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
builder.setColor(event.getSelfMember().getColor())
.setText(event.getClient().getSuccess()+" Search results for `"+event.getArgs()+"`:")
.setChoices(new String[0])
.setAction(i -> {
AudioTrack track = playlist.getTracks().get(i-1);
int pos = bot.queueTrack(event, track)+1;
event.getChannel().sendMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
})
.setUsers(event.getAuthor())
;
for(int i=0; i<4&&i<playlist.getTracks().size(); i++)
{
AudioTrack track = playlist.getTracks().get(i);
builder.addChoices("`["+FormatUtil.formatTime(track.getDuration())+"]` [**"
+track.getInfo().title+"**](https://youtu.be/"+track.getIdentifier()+")");
}
builder.build().display(m);
}
@Override
public void noMatches() {
m.editMessage(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.").queue();
}
@Override
public void loadFailed(FriendlyException throwable) {
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
}
@@ -1,49 +0,0 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import net.dv8tion.jda.core.entities.Game;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetgameCmd extends Command {
public SetgameCmd(Bot bot)
{
this.name = "setgame";
this.help = "sets the game the bot is playing";
this.arguments = "[game]";
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
try {
event.getJDA().getPresence().setGame(event.getArgs().isEmpty() ? null : Game.of(event.getArgs()));
event.reply(event.getClient().getSuccess()+" **"+event.getSelfUser().getName()
+"** is "+(event.getArgs().isEmpty() ? "no longer playing anything." : "now playing `"+event.getArgs()+"`"));
} catch(Exception e) {
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
}
@@ -1,65 +0,0 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.commands;
import me.jagrosh.jdautilities.commandclient.Command;
import me.jagrosh.jdautilities.commandclient.CommandEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.Settings;
import net.dv8tion.jda.core.EmbedBuilder;
import net.dv8tion.jda.core.MessageBuilder;
import net.dv8tion.jda.core.entities.Role;
import net.dv8tion.jda.core.entities.TextChannel;
import net.dv8tion.jda.core.entities.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SettingsCmd extends Command {
private final Bot bot;
public SettingsCmd(Bot bot)
{
this.bot = bot;
this.name = "settings";
this.help = "shows the bots settings";
this.aliases = new String[]{"status"};
this.guildOnly = true;
}
@Override
protected void execute(CommandEvent event) {
Settings s = bot.getSettings(event.getGuild());
MessageBuilder builder = new MessageBuilder()
.append("\uD83C\uDFA7 **")
.append(event.getSelfUser().getName())
.append("** settings:");
TextChannel tchan = event.getGuild().getTextChannelById(s.getTextId());
VoiceChannel vchan = event.getGuild().getVoiceChannelById(s.getVoiceId());
Role role = event.getGuild().getRoleById(s.getRoleId());
EmbedBuilder ebuilder = new EmbedBuilder()
.setColor(event.getSelfMember().getColor())
.setDescription("Text Channel: "+(tchan==null ? "Any" : "**#"+tchan.getName()+"**")
+ "\nVoice Channel: "+(vchan==null ? "Any" : "**"+vchan.getName()+"**")
+ "\nDJ Role: "+(role==null ? "None" : "**"+role.getName()+"**"))
.setFooter(event.getJDA().getGuilds().size()+" servers | "
+event.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()
+" audio connections", null);
event.getChannel().sendMessage(builder.setEmbed(ebuilder.build()).build()).queue();
}
}
@@ -1,111 +0,0 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.gui;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import me.jagrosh.jmusicbot.Bot;
import me.jagrosh.jmusicbot.audio.AudioHandler;
import me.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.Guild;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class GuildsPanel extends JPanel {
private final Bot bot;
private final JList guildList;
private final JTextArea guildQueue;
private int index = -1;
public GuildsPanel(Bot bot)
{
super();
super.setLayout(new GridBagLayout());
this.bot = bot;
guildList = new JList();
guildQueue = new JTextArea();
guildList.setModel(new DefaultListModel());
guildList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
guildList.setFixedCellHeight(20);
guildList.setPreferredSize(new Dimension(100,300));
guildQueue.setPreferredSize(new Dimension(300,300));
guildQueue.setEditable(false);
JScrollPane pane = new JScrollPane();
JScrollPane pane2 = new JScrollPane();
pane.setViewportView(guildList);
pane2.setViewportView(guildQueue);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.anchor = GridBagConstraints.LINE_START;
c.gridx = 0;
c.gridwidth = 1;
super.add(pane, c);
c.gridx = 1;
c.gridwidth = 3;
super.add(pane2, c);
//bot.registerPanel(this);
guildList.addListSelectionListener((ListSelectionEvent e) -> {
index = guildList.getSelectedIndex();
//bot.updatePanel();
});
}
public void updateList(List<Guild> guilds)
{
String[] strs = new String[guilds.size()];
for(int i=0; i<guilds.size(); i++)
strs[i] = guilds.get(i).getName();
guildList.setListData(strs);
}
public int getIndex()
{
return guildList.getSelectedIndex();
}
public void updatePanel(AudioHandler handler) {
StringBuilder builder = new StringBuilder("Now Playing: ");
if(handler==null || handler.getCurrentTrack()==null)
{
builder.append("nothing");
}
else
{
builder.append(handler.getCurrentTrack().getTrack().getInfo().title)
.append(" [")
.append(FormatUtil.formatTime(handler.getCurrentTrack().getTrack().getDuration()))
.append("]\n");
for(int i=0; i<handler.getQueue().size(); i++)
builder.append("\n").append(i+1).append(". ").append(handler.getQueue().get(i).getTrack().getInfo().title);
}
guildQueue.setText(builder.toString());
guildQueue.updateUI();
}
}
@@ -1,140 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.jagrosh.jmusicbot.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.Role;
import net.dv8tion.jda.core.entities.TextChannel;
import net.dv8tion.jda.core.entities.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class FinderUtil {
public static List<TextChannel> findTextChannel(String query, Guild guild)
{
String id;
if(query.matches("<#\\d+>"))
{
id = query.replaceAll("<#(\\d+)>", "$1");
TextChannel tc = guild.getJDA().getTextChannelById(id);
if(tc!=null && tc.getGuild().equals(guild))
return Collections.singletonList(tc);
}
ArrayList<TextChannel> exact = new ArrayList<>();
ArrayList<TextChannel> wrongcase = new ArrayList<>();
ArrayList<TextChannel> startswith = new ArrayList<>();
ArrayList<TextChannel> contains = new ArrayList<>();
String lowerquery = query.toLowerCase();
guild.getTextChannels().stream().forEach((tc) -> {
if(tc.getName().equals(lowerquery))
exact.add(tc);
else if (tc.getName().equalsIgnoreCase(lowerquery) && exact.isEmpty())
wrongcase.add(tc);
else if (tc.getName().toLowerCase().startsWith(lowerquery) && wrongcase.isEmpty())
startswith.add(tc);
else if (tc.getName().toLowerCase().contains(lowerquery) && startswith.isEmpty())
contains.add(tc);
});
if(!exact.isEmpty())
return exact;
if(!wrongcase.isEmpty())
return wrongcase;
if(!startswith.isEmpty())
return startswith;
return contains;
}
public static List<VoiceChannel> findVoiceChannel(String query, Guild guild)
{
String id;
if(query.matches("<#\\d+>"))
{
id = query.replaceAll("<#(\\d+)>", "$1");
VoiceChannel tc = guild.getJDA().getVoiceChannelById(id);
if(tc!=null && tc.getGuild().equals(guild))
return Collections.singletonList(tc);
}
ArrayList<VoiceChannel> exact = new ArrayList<>();
ArrayList<VoiceChannel> wrongcase = new ArrayList<>();
ArrayList<VoiceChannel> startswith = new ArrayList<>();
ArrayList<VoiceChannel> contains = new ArrayList<>();
String lowerquery = query.toLowerCase();
guild.getVoiceChannels().stream().forEach((tc) -> {
if(tc.getName().equals(lowerquery))
exact.add(tc);
else if (tc.getName().equalsIgnoreCase(lowerquery) && exact.isEmpty())
wrongcase.add(tc);
else if (tc.getName().toLowerCase().startsWith(lowerquery) && wrongcase.isEmpty())
startswith.add(tc);
else if (tc.getName().toLowerCase().contains(lowerquery) && startswith.isEmpty())
contains.add(tc);
});
if(!exact.isEmpty())
return exact;
if(!wrongcase.isEmpty())
return wrongcase;
if(!startswith.isEmpty())
return startswith;
return contains;
}
public static List<Role> findRole(String query, Guild guild)
{
String id;
if(query.matches("<@&\\d+>"))
{
id = query.replaceAll("<@&(\\d+)>", "$1");
Role role = guild.getRoleById(id);
if(role!=null)
return Collections.singletonList(role);
}
if(query.matches("[Ii][Dd]\\s*:\\s*\\d+"))
{
id = query.replaceAll("[Ii][Dd]\\s*:\\s*(\\d+)", "$1");
for(Role role: guild.getRoles())
if(role.getId().equals(id))
return Collections.singletonList(role);
}
ArrayList<Role> exact = new ArrayList<>();
ArrayList<Role> wrongcase = new ArrayList<>();
ArrayList<Role> startswith = new ArrayList<>();
ArrayList<Role> contains = new ArrayList<>();
String lowerQuery = query.toLowerCase();
guild.getRoles().stream().forEach((role) -> {
if(role.getName().equals(query))
exact.add(role);
else if (role.getName().equalsIgnoreCase(query) && exact.isEmpty())
wrongcase.add(role);
else if (role.getName().toLowerCase().startsWith(lowerQuery) && wrongcase.isEmpty())
startswith.add(role);
else if (role.getName().toLowerCase().contains(lowerQuery) && startswith.isEmpty())
contains.add(role);
});
if(!exact.isEmpty())
return exact;
if(!wrongcase.isEmpty())
return wrongcase;
if(!startswith.isEmpty())
return startswith;
return contains;
}
}
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff. Show more