Compare commits

...
284 Commits
Author SHA1 Message Date
Christian F. Brinkmann 9fd246ac25 Fix JDA-Chewtils dependency groupId and update version to jda-6.1.1-SNAPSHOT 2025-11-15 12:28:38 +01:00
Christian F. Brinkmann 6a15bbb0df Add MESSAGE_CONTENT and additional intents to JMusicBot 2025-11-15 12:28:23 +01:00
Christian F. Brinkmann 16bc176685 Bump version to 0.5.1 and enable additional intents for JDA 2025-11-15 10:56:43 +01:00
Christian F. Brinkmann 50e8bff831 Bump version from 0.4.12 to 0.5.0 in pom.xml 2025-11-14 21:49:41 +01:00
Christian F. Brinkmann 9c012eb169 Update README to reflect JDA 6 upgrade and MESSAGE_CONTENT intent requirements 2025-11-14 21:49:33 +01:00
Christian F. Brinkmann 87771df1fb Update Lavaplayer and Lavalink dependencies to latest versions 2025-11-14 21:49:04 +01:00
Christian F. Brinkmann 5d35f5bb4c Enable MESSAGE_CONTENT intent for JDA
Appears to be a requirement for JDA 5 or later
2025-11-14 21:13:10 +01:00
Christian F. Brinkmann ecd8cdf77b Update logback-classic dependency to version 1.5.21 2025-11-14 20:29:33 +01:00
Christian F. Brinkmann 39e5bf7786 Upgrade JDA from 4.4.1_353 to 6.1.1
- Updated JDA dependency to 6.1.1
- Switched to Chew's JDA-Chewtils fork (fix/components-v2 branch) for JDA v6 support
- Fixed breaking API changes:
  - Channel types moved to channel.concrete package
  - MessageBuilder → MessageCreateBuilder/MessageCreateData
  - Events moved to events.session package
  - GuildMessageDeleteEvent → MessageDeleteEvent
  - Permissions: MESSAGE_READ → VIEW_CHANNEL, MESSAGE_WRITE → MESSAGE_SEND
  - Methods: inVoiceChannel() → inAudioChannel()
  - File uploads: sendFile() → sendFiles() with FileUpload
  - Removed CacheFlag.EMOTE
  - Removed LoginException handling (no longer thrown by JDABuilder)

Note: LoginException removal means less helpful error messages for invalid tokens.
2025-11-14 14:12:21 +01:00
Christian F. Brinkmann 650a9ea996 bump version to 0.4.12 and update Lavalink dependency to 1.13.2 2025-06-08 21:01:45 +02:00
Christian F. Brinkmann 0e0c05acf9 Update version to 0.4.11-1 and add Lavalink snapshot repository 2025-05-01 09:24:57 +02:00
Christian F. Brinkmann 2e52ecac3b bump version to 0.4.11 and update lavalink to 1.13.0 2025-05-01 01:07:01 +02:00
Christian F. Brinkmann (cloud) 450ee7acb8 bump lavalink to 1.12.0, bump version to 4.10 2025-03-28 01:16:49 +01:00
Christian F. Brinkmann a7b56b67ab Update README.md to mention nssm for windows 2025-02-26 23:39:18 +01:00
Christian F. Brinkmann fedb97e1c7 Update README.md 2025-02-26 16:17:21 +01:00
Christian F. Brinkmann 68f951dc63 Merge remote-tracking branch 'origin/master' 2025-02-26 15:46:45 +01:00
Christian F. Brinkmann 01eee8813a bump project version to 0.4.9 2025-02-26 15:46:07 +01:00
Christian F. Brinkmann 3df09a4cd2 bump lavaplayer common dependency version to 1.11.5 2025-02-26 15:45:24 +01:00
Christian F. Brinkmann 0084ba7baf Update README.md
Add demo server discord invite
2025-02-19 11:16:26 +01:00
Christian F. Brinkmann 35e756df9b bump project version to 0.4.8 2025-02-13 14:20:38 +01:00
Christian F. Brinkmann 426e28ed8c bump lavaplayer dependencies to latest versions 2025-02-13 14:16:16 +01:00
Christian F. Brinkmann dd72baca0d Reflect recent changes in the readme 2025-01-11 13:45:31 +01:00
Christian F. Brinkmann ac4e09cbd9 bump project patch version 2025-01-11 13:41:38 +01:00
Christian F. Brinkmann fd01d94cf2 bump lavaplayer version 2025-01-11 13:40:48 +01:00
Christian F. Brinkmann 95f72937c8 Merge remote-tracking branch 'other-fork/fix-youtube' 2025-01-11 13:38:53 +01:00
Michaili K 792cb7a8b7 Remove whitespace 2025-01-03 00:02:27 +01:00
Michaili K 03ba83f06b Log the entire exception in debug for brevity 2025-01-03 00:00:58 +01:00
Michaili K 3b3a483b17 Use Selenium with Chrome to obtain PO token and visitordata 2025-01-02 23:25:44 +01:00
Christian F. Brinkmann 1874252ff6 change urls for update check logic 2024-12-14 13:26:33 +01:00
Christian F. Brinkmann aa8e430ff1 bump version to 0.4.6 2024-12-14 13:26:17 +01:00
Christian F. Brinkmann 204bb6f04a bump lavalink version in the hopes that it fixes some errors 2024-12-14 13:05:20 +01:00
Michaili K 9ec21bf329 Update youtube-source to 1.11.1 2024-12-10 14:16:24 +01:00
Christian F. Brinkmann b419191bad use actual version instead of snapshot in pom.xml 2024-12-06 18:17:37 +01:00
Christian F. Brinkmann aa89145ae8 update readme to reflect that i have now made miniscule dependency changes myself 2024-12-06 18:11:20 +01:00
Christian F. Brinkmann 293733f0e7 bump versions of lavaplayer and lavalink to most recent in the hopes that this fixes something 2024-12-06 17:55:44 +01:00
Michaili K 7be02b1fe2 Add quotes around po token & visitor data config 2024-11-21 04:12:18 +01:00
Michaili K 1ac46d9ec5 Implement IP rotation for YouTube 2024-11-21 03:47:12 +01:00
Michaili K 026ce87440 Implement PO Tokens for YouTube playback 2024-11-21 01:41:13 +01:00
Michaili K e5f34cc900 Update deps 2024-11-21 01:25:57 +01:00
Christian F. Brinkmann c9a2477f86 Update README.md 2024-10-25 13:26:06 +02:00
root 61a18a1db7 update README to give some info about the fork 2024-10-24 17:24:38 +02:00
root 7fb44e296b Merge branch 'pr-1670', use 1.8.3 instead of 1.8.2 2024-10-24 14:01:24 +02:00
Lythus 4ae9f05776 ignore dockerfile 2024-10-19 21:28:44 +08:00
Lythus 84facb8b82 updated lavalink version to 1.8.3 2024-10-19 20:36:53 +08:00
Michaili K 070938b326 Add anothen message to onTrackError handler 2024-10-15 10:54:27 +02:00
Michaili K 58a9e76edb Formatting changes 2024-10-11 23:42:06 +02:00
Michaili K 96ffcd8291 Handle specific track exceptions with a special message 2024-10-10 20:00:40 +02:00
Michaili K 844a1bb75a Change youtubeoauth2 option description 2024-10-10 19:11:29 +02:00
Michaili K 6094e49ab2 Return early when failing to read the token file 2024-10-10 19:09:05 +02:00
Michaili K 88ce3e1149 Stop logging the refresh token on startup 2024-10-10 19:08:49 +02:00
Michaili K 463bf75783 Prepend "Google" to "accounts" in OAuth2 DM for clarification 2024-10-06 04:56:00 +02:00
Michaili K 348fc3ceec DM the user when the YoutubeOauth2Handler is providing us a token 2024-10-02 21:39:29 +02:00
Lythus 30eafb9239 Updated JDA-Utilities 2024-09-30 15:22:32 +08:00
Michaili K c95f2248f3 Bump youtube-source to 1.8.2 2024-09-26 02:12:38 +02:00
Michail b80b0c8459 Remove accidental HTML tag :) 2024-08-24 14:01:15 +02:00
Michail 4a8392fdb9 Catch potential exceptions thrown by the useOAuth2 call 2024-08-24 13:38:52 +02:00
Michail 9d6584525c Implement YouTube OAuth2 login 2024-08-24 13:25:11 +02:00
Michail 909b655f5c Update YouTube source manager 2024-08-24 12:09:43 +02:00
Michaili K 859e5c5862 Update lavaplayer youtube source to 1.5.2 (#1655) 2024-08-12 13:28:38 -04:00
njezi 43b63f1ba8 fixed a mistake (#1648) 2024-08-06 15:59:19 -04:00
John Grosh eca2563abf fix npe and an import (#1567)
* fix npe and an import

* stop double posting

* switched stacktrace to log, changed some logs
2024-08-01 09:42:17 -04:00
kokofixcomputersandMichaili K e6bfd1841f fix error when playing (#1632)
* fix error when playing

* Update youtube source to 1.5.1

---------

Co-authored-by: Michaili K <git@michaili.dev>
2024-08-01 09:14:51 -04:00
Timothy Gillespie f140b41bf5 Use shell comparsion (#1627) 2024-08-01 09:10:04 -04:00
John Grosh 8733899aa0 Update bug-report.yml 2024-07-10 17:35:50 -04:00
Moritz Bender e5fd05e601 Update dependencies to fix youtube issues (#1609) 2024-07-10 13:19:13 +02:00
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
98 changed files with 6637 additions and 2873 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']
+64
View File
@@ -0,0 +1,64 @@
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 using an official release from [the releases page](https://github.com/jagrosh/MusicBot/releases)
required: true
- type: input
id: version
attributes:
label: JMusicBot Version
description: Which version of JMusicBot are you running?
validations:
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)
+8
View File
@@ -0,0 +1,8 @@
/nbproject/
/target/
/Playlists/
/test/
*.json
*.txt
nb*.xml
dockerfile
+1
View File
@@ -0,0 +1 @@
jmusicbot.com
+137 -26
View File
@@ -1,36 +1,147 @@
<img align="right" src="https://i.imgur.com/zrE80HY.png" height="200" width="200">
[![Downloads](https://img.shields.io/github/downloads/jagrosh/MusicBot/total.svg)](https://github.com/jagrosh/MusicBot/releases/latest)
[![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)
## Demo
If you only want to temporarily join the demo server, use this link: [https://discord.gg/4cp9CvjwaW](https://discord.gg/4cp9CvjwaW)
If you want to join the demo server permanently, use this link: [https://discord.gg/hK95396uB3](https://discord.gg/hK95396uB3)
# This fork
We had to update to JDA 6, switch to jda-chewtils to make the bot work again.
Also, we somewhat regularly bump [lavaplayer](https://mvnrepository.com/artifact/dev.arbjerg/lavaplayer)/[lavalink](https://maven.lavalink.dev/#/releases/dev/lavalink/youtube/common) to their new versions. I will try to update this automatically in the future.
Additionally, this fork includes the following PRs from other people:
### Support for logging into your (burner) youtube account:
[PR-1670](https://github.com/jagrosh/MusicBot/pull/1670)
### To allow building the project from source
[PR-1703](https://github.com/jagrosh/MusicBot/pull/1703)
### Add YouTube PO token & IP rotation
https://github.com/jagrosh/MusicBot/pull/1772
### For PO_Tokens: In your bot folder, edit "config.txt" and add
```
ytpotoken = "PO_TOKEN_HERE"
ytvisitordata = "VISITOR_DATA_HERE"
```
To get po_token and visitor_data, use [youtube-trusted-session-generator](https://github.com/iv-org/youtube-trusted-session-generator)
If you have problems with this fork, you could also check out [this other fork](https://github.com/SeVile/MusicBot).
## Requirements for 0.5+ and above
Since we updated JDA from version 4 to 6 to fix various issues, we had to enable the MESSAGE_CONTENT intent. If you let your bot join more than 100 servers(guilds), you now need to enable this intent in the [Discord Developer Portal](https://discord.com/developers/applications).
More information can be found in the [JDA documentation](https://jda.wiki/using-jda/gateway-intents-and-member-cache-policy/).
Note, however, that this is a privileged intent and will require a valid use-case for your bot to be verified in over 75 servers (it will only show up in the portal when on at least 76 servers according to the documentation).
## Easy deployment via docker
Additionally, i thought a corresponding docker image would make things better:
The image can be found on [dockerhub](https://hub.docker.com/repository/docker/chrisb09/jmusicbot/general)
Docker-compose:
```yaml
version: '2.2'
services:
jmusicbot:
image: chrisb09/jmusicbot:latest
container_name: jmusicbot
restart: unless-stopped
volumes:
- ./config:/jmb/config
```
although, obviously you should change `./config` to whatever path is right for your setup.
Source code for the docker image is also on [Github](https://github.com/chrisb09/jmb-container).
### Windows
If you want to run the bot on a windows host without docker, you obviously need to install java, and if you want to run it in the background something like [nssm](http://nssm.cc/) can be helpful.
## YouTube-Login
Obviously you still need to log in if you want to use youtube properly, hence you need to enable this in the config.
If you are using a pre-existing config, simply add:
```
/ If you set this to true, the bot will use a Google account to play YouTube tracks.
// This is only needed if you are experiencing issues with YouTube playback.
// Once enabled, instructions for login will be written to the console and DMs.
// DO NOT use your main Google account! Use an alternative account.
youtubeoauth2=true
```
If you start with a clean slate this line should already be present, albeit with `false` being the default.
As the author of the PR reiterates multiple times, do not use your main google account as using your account for a bot is most certainly against the ToS and thus you are endangering said account.
To add the account you simply have to follow the very basic instructions printed either in the console or sent to you from the bot via discord after you have started it, open the linked google page and paste the code.
# JMusicBot
A Discord music bot with a clean interface, and that is easy to set up and run yourself
[![Setup](http://i.imgur.com/VvXYp5j.png)](https://github.com/jagrosh/MusicBot/wiki/Setup)
[![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)
# 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
* Supports many online radio/streams
* Supports local files
* Playlist support (both web/youtube, and local)
A cross-platform Discord music bot with a clean interface, and that is easy to set up and run yourself!
# Setup
Please see the [Setup Page](https://github.com/jagrosh/MusicBot/wiki/Setup) in the wiki to run this bot yourself!
[![Setup](http://i.imgur.com/VvXYp5j.png)](https://jmusicbot.com/setup)
# Questions/Suggestions/Bug Reports
**Please read the [Suggested/Planned Features List](https://github.com/jagrosh/MusicBot/projects/1) before suggesting a feature**. 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). (Note: I will not accept any feature requests that will require additional API keys, nor any non-music features). 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)
## 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)
# Example
![Example](https://i.imgur.com/tevrtKt.png)
## 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)
# Editing
This bot (and the source code here) is not meant to be edited. The main purpose of having the source public is to show the capabilities of the libraries, and to allow others to understand how the bot works. 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 still choose to make edits, please do so in accordance with the Apache 2.0 License.
## 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.
+162 -53
View File
@@ -3,81 +3,190 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.jagrosh</groupId>
<artifactId>JMusicBot</artifactId>
<version>0.1.2</version>
<version>0.5.1</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>http://jcenter.bintray.com</url>
<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</id>
<name>Lavalink Repository</name>
<url>https://maven.lavalink.dev/releases</url>
</repository>
<repository>
<id>arbjergDevSnapshots</id>
<name>Lavalink Repository</name>
<url>https://maven.lavalink.dev/snapshots</url>
</repository>
</repositories>
<dependencies>
<!-- Discord Dependencies -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>6.1.1</version>
</dependency>
<dependency>
<groupId>com.github.chrisb09</groupId>
<artifactId>JDA-Chewtils</artifactId>
<!-- <version>fix~components-v2-SNAPSHOT</version> -->
<version>jda-6.1.1-SNAPSHOT</version>
</dependency>
<!-- Music Dependencies -->
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>3.5.0_335</version>
</dependency>
<dependency>
<groupId>com.sedmelluq</groupId>
<groupId>dev.arbjerg</groupId>
<artifactId>lavaplayer</artifactId>
<version>1.2.47</version>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>com.jagrosh</groupId>
<artifactId>JDA-Utilities</artifactId>
<version>2.0</version>
<groupId>dev.arbjerg</groupId>
<artifactId>lavaplayer-ext-youtube-rotator</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>dev.lavalink.youtube</groupId>
<artifactId>common</artifactId>
<version>1.16.0</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.3</version>
<version>1.5.21</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>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.27.0</version>
<!-- https://github.com/SeleniumHQ/selenium/issues/13791 -->
<exclusions>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-devtools-v85</artifactId>
</exclusion>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-devtools-v129</artifactId>
</exclusion>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-devtools-v130</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.9.2</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-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.jagrosh.jmusicbot.JMusicBot</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<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>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
</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
+96 -428
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -15,117 +15,67 @@
*/
package com.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 com.jagrosh.jdautilities.command.Command.Category;
import com.jagrosh.jdautilities.command.CommandEvent;
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.utils.FormatUtil;
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader;
import com.jagrosh.jmusicbot.settings.SettingsManager;
import java.util.Objects;
import javafx.util.Pair;
import net.dv8tion.jda.core.JDA;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Game;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.Message;
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.events.guild.GuildJoinEvent;
import net.dv8tion.jda.core.events.message.MessageDeleteEvent;
import net.dv8tion.jda.core.exceptions.PermissionException;
import net.dv8tion.jda.core.hooks.ListenerAdapter;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler;
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 extends ListenerAdapter {
private final HashMap<String,Settings> settings;
private final HashMap<Long,Pair<Long,Long>> lastNP; // guild -> channel,message
private final AudioPlayerManager manager;
public class Bot
{
private final EventWaiter waiter;
private final ScheduledExecutorService threadpool;
private final Config config;
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 final YoutubeOauth2TokenHandler youTubeOauth2TokenHandler;
private boolean shuttingDown = false;
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(event.getMember().hasPermission(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 ->
public Bot(EventWaiter waiter, BotConfig config, SettingsManager settings)
{
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
return true;
if(event.getGuild()==null)
return true;
return event.getMember().hasPermission(Permission.MANAGE_SERVER);
});
public final Category OWNER = new Category("Owner");
public Bot(EventWaiter waiter, Config config)
{
this.config = config;
this.waiter = waiter;
this.settings = new HashMap<>();
this.lastNP = new HashMap<>();
manager = new DefaultAudioPlayerManager();
threadpool = Executors.newSingleThreadScheduledExecutor();
AudioSourceManagers.registerRemoteSources(manager);
AudioSourceManagers.registerLocalSource(manager);
manager.source(YoutubeAudioSourceManager.class).setPlaylistPageCount(10);
try {
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(Paths.get("serversettings.json"))));
loadedSettings.keySet().forEach((id) -> {
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,
o.has("volume") ? o.getInt("volume") : 100,
o.has("default_playlist")? o.getString("default_playlist"): null,
o.has("repeat") ? o.getBoolean("repeat") : false));
});
} catch(IOException | JSONException e) {
LoggerFactory.getLogger("Settings").warn("Failed to load server settings (this is normal if no settings have been set yet): "+e);
}
this.config = config;
this.settings = settings;
this.playlists = new PlaylistLoader(config);
this.threadpool = Executors.newSingleThreadScheduledExecutor();
this.youTubeOauth2TokenHandler = new YoutubeOauth2TokenHandler();
this.youTubeOauth2TokenHandler.init();
this.players = new PlayerManager(this, config);
this.players.init();
this.nowplaying = new NowplayingHandler(this);
this.nowplaying.init();
this.aloneInVoiceHandler = new AloneInVoiceHandler(this);
this.aloneInVoiceHandler.init();
}
public JDA getJDA()
public BotConfig getConfig()
{
return jda;
return config;
}
public SettingsManager getSettingsManager()
{
return settings;
}
public EventWaiter getWaiter()
@@ -133,369 +83,87 @@ public class Bot extends ListenerAdapter {
return waiter;
}
public AudioPlayerManager getAudioManager()
{
return manager;
}
public ScheduledExecutorService getThreadpool()
{
return threadpool;
}
public int queueTrack(CommandEvent event, AudioTrack track)
public PlayerManager getPlayerManager()
{
return setUpHandler(event).addTrack(track, event.getAuthor());
return players;
}
public AudioHandler setUpHandler(CommandEvent event)
public PlaylistLoader getPlaylistLoader()
{
return setUpHandler(event.getGuild());
return playlists;
}
public AudioHandler setUpHandler(Guild guild)
public NowplayingHandler getNowplayingHandler()
{
AudioHandler handler;
if(guild.getAudioManager().getSendingHandler()==null)
{
AudioPlayer player = manager.createPlayer();
if(settings.containsKey(guild.getId()))
player.setVolume(settings.get(guild.getId()).getVolume());
handler = new AudioHandler(player, guild, this);
player.addListener(handler);
guild.getAudioManager().setSendingHandler(handler);
if(AudioHandler.USE_NP_REFRESH)
threadpool.scheduleWithFixedDelay(() -> updateLastNP(guild.getIdLong()), 0, 5, TimeUnit.SECONDS);
}
else
handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
return handler;
return nowplaying;
}
public AloneInVoiceHandler getAloneInVoiceHandler()
{
return aloneInVoiceHandler;
}
public YoutubeOauth2TokenHandler getYouTubeOauth2Handler()
{
return youTubeOauth2TokenHandler;
}
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()
{
Game game = config.getGame()==null || config.getGame().getName().equalsIgnoreCase("none") ? null : config.getGame();
if(!Objects.equals(jda.getPresence().getGame(), game))
jda.getPresence().setGame(game);
}
public void setLastNP(Message m)
{
lastNP.put(m.getGuild().getIdLong(), new Pair<>(m.getTextChannel().getIdLong(), m.getIdLong()));
Activity game = config.getGame()==null || config.getGame().getName().equalsIgnoreCase("none") ? null : config.getGame();
if(!Objects.equals(jda.getPresence().getActivity(), game))
jda.getPresence().setActivity(game);
}
@Override
public void onMessageDelete(MessageDeleteEvent event) {
if(lastNP.containsKey(event.getGuild().getIdLong()))
{
Pair<Long,Long> pair = lastNP.get(event.getGuild().getIdLong());
if(pair.getValue()==event.getMessageIdLong())
lastNP.remove(event.getGuild().getIdLong());
}
}
private void updateLastNP(long guildId)
public void shutdown()
{
Guild guild = jda.getGuildById(guildId);
if(guild==null)
if(shuttingDown)
return;
if(!lastNP.containsKey(guildId))
return;
Pair<Long,Long> pair = lastNP.get(guildId);
if(pair==null)
return;
TextChannel tc = guild.getTextChannelById(pair.getKey());
if(tc==null)
{
lastNP.remove(guildId);
return;
}
try {
tc.editMessageById(pair.getValue(), FormatUtil.nowPlayingMessage(guild, config.getSuccess())).queue(m->{}, t -> lastNP.remove(guildId));
} catch(Exception e) {
lastNP.remove(guildId);
}
}
public void updateTopic(long guildId, AudioHandler handler)
{
Guild guild = jda.getGuildById(guildId);
if(guild==null)
return;
TextChannel tchan = guild.getTextChannelById(getSettings(guild).getTextId());
if(tchan!=null && guild.getSelfMember().hasPermission(tchan, Permission.MANAGE_CHANNEL))
{
String otherText;
if(tchan.getTopic()==null || tchan.getTopic().isEmpty())
otherText = "\u200B";
else if(tchan.getTopic().contains("\u200B"))
otherText = tchan.getTopic().substring(tchan.getTopic().lastIndexOf("\u200B"));
else
otherText = "\u200B\n "+tchan.getTopic();
String text = FormatUtil.topicFormat(handler, guild.getJDA())+otherText;
if(!text.equals(tchan.getTopic()))
try {
tchan.getManager().setTopic(text).queue();
} catch(PermissionException e){}
}
}
public void shutdown(){
manager.shutdown();
shuttingDown = true;
threadpool.shutdownNow();
jda.getGuilds().stream().forEach(g -> {
g.getAudioManager().closeAudioConnection();
AudioHandler ah = (AudioHandler)g.getAudioManager().getSendingHandler();
if(ah!=null)
if(jda.getStatus()!=JDA.Status.SHUTTING_DOWN)
{
jda.getGuilds().stream().forEach(g ->
{
ah.getQueue().clear();
ah.getPlayer().destroy();
updateTopic(g.getIdLong(), ah);
}
});
jda.shutdown();
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;
}
@Override
public void onShutdown(ShutdownEvent event) {
if(gui!=null)
gui.dispose();
}
@Override
public void onReady(ReadyEvent event) {
this.jda = event.getJDA();
if(jda.getGuilds().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().asBot().getInviteUrl(JMusicBot.RECOMMENDED_PERMS));
}
credit(event.getJDA());
jda.getGuilds().forEach((guild) -> {
try
{
String defpl = getSettings(guild).getDefaultPlaylist();
VoiceChannel vc = guild.getVoiceChannelById(getSettings(guild).getVoiceId());
if(defpl!=null && vc!=null)
{
if(setUpHandler(guild).playFromDefault())
guild.getAudioManager().openAudioConnection(vc);
}
}
catch(Exception ex) {System.err.println(ex);}
});
}
@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(config.getDBots())
return;
jda.getTextChannelById(119222314964353025L)
.sendMessage("<@113156185389092864>: This account is running JMusicBot. Please do not list bot clones on this server, <@"+config.getOwnerId()+">.").complete();
dbots.leave().queue();
}
// 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,100,null,false));
}
else
{
s.setTextId(channel.getIdLong());
}
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,100,null,false));
}
else
{
s.setVoiceId(channel.getIdLong());
}
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(),100,null,false));
}
else
{
s.setRoleId(role.getIdLong());
}
writeSettings();
}
public void setDefaultPlaylist(Guild guild, String playlist)
{
Settings s = settings.get(guild.getId());
if(s==null)
{
settings.put(guild.getId(), new Settings(null,null,null,100,playlist,false));
}
else
{
s.setDefaultPlaylist(playlist);
}
writeSettings();
}
public void setVolume(Guild guild, int volume)
{
Settings s = settings.get(guild.getId());
if(s==null)
{
settings.put(guild.getId(), new Settings(null,null,null,volume,null,false));
}
else
{
s.setVolume(volume);
}
writeSettings();
}
public void setRepeatMode(Guild guild, boolean mode)
{
Settings s = settings.get(guild.getId());
if(s==null)
{
settings.put(guild.getId(), new Settings(null,null,null,100,null,mode));
}
else
{
s.setRepeatMode(mode);
}
writeSettings();
}
public void clearTextChannel(Guild guild)
{
Settings s = getSettings(guild);
if(s!=Settings.DEFAULT_SETTINGS)
{
if(s.getVoiceId()==0 && s.getRoleId()==0)
settings.remove(guild.getId());
else
s.setTextId(0);
writeSettings();
}
}
public void clearVoiceChannel(Guild guild)
{
Settings s = getSettings(guild);
if(s!=Settings.DEFAULT_SETTINGS)
{
if(s.getTextId()==0 && s.getRoleId()==0)
settings.remove(guild.getId());
else
s.setVoiceId(0);
writeSettings();
}
}
public void clearRole(Guild guild)
{
Settings s = getSettings(guild);
if(s!=Settings.DEFAULT_SETTINGS)
{
if(s.getVoiceId()==0 && s.getTextId()==0)
settings.remove(guild.getId());
else
s.setRoleId(0);
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()!=0)
o.put("text_channel_id", Long.toString(s.getTextId()));
if(s.getVoiceId()!=0)
o.put("voice_channel_id", Long.toString(s.getVoiceId()));
if(s.getRoleId()!=0)
o.put("dj_role_id", Long.toString(s.getRoleId()));
if(s.getVolume()!=100)
o.put("volume",s.getVolume());
if(s.getDefaultPlaylist()!=null)
o.put("default_playlist", s.getDefaultPlaylist());
if(s.getRepeatMode())
o.put("repeat", true);
obj.put(key, o);
});
try {
Files.write(Paths.get("serversettings.json"), obj.toString(4).getBytes());
} catch(IOException ex){
LoggerFactory.getLogger("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();
}*/
}
@@ -0,0 +1,427 @@
/*
* 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.jagrosh.jmusicbot.utils.YouTubeUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
import com.typesafe.config.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
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,
chromePath, chromeDriverPath, evalEngine;
private YouTubeUtil.RoutingPlanner ytRoutingPlanner;
private List<IpBlock> ytIpBlocks;
private boolean youtubeOauth2, stayInChannel, songInGame, npImages, updatealerts, chromeHeadless, 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"));
youtubeOauth2 = config.getBoolean("youtubeoauth2");
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");
chromePath = config.getString("chromepath");
chromeDriverPath = config.getString("chromedriverpath");
chromeHeadless = config.getBoolean("chromeheadless");
ytRoutingPlanner = config.getEnum(YouTubeUtil.RoutingPlanner.class, "ytroutingplanner");
ytIpBlocks = config.getStringList("ytipblocks").stream().map(YouTubeUtil::parseIpBlock).collect(Collectors.toList());
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 useYoutubeOauth2()
{
return youtubeOauth2;
}
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 String getChromePath()
{
return chromePath.equals("AUTO") ? null : chromePath;
}
public String getChromeDriverPath()
{
return chromeDriverPath.equals("AUTO") ? null : chromeDriverPath;
}
public boolean getChromeHeadless()
{
return chromeHeadless;
}
public YouTubeUtil.RoutingPlanner getYTRoutingPlanner()
{
return ytRoutingPlanner;
}
public List<IpBlock> getYTIpBlocks()
{
return ytIpBlocks;
}
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;
}
}
@@ -1,306 +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 com.jagrosh.jmusicbot;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
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;
import net.dv8tion.jda.core.OnlineStatus;
import net.dv8tion.jda.core.entities.Game;
/**
*
* @author John Grosh (jagrosh)
*/
public class Config {
private boolean nogui;
private String prefix, altprefix, token, owner, success, warning, error, game,
help, loadingEmoji, searchingEmoji;
private boolean stay, dbots, songingame, useEval, npimages;
private long maxSeconds;
private OnlineStatus status = OnlineStatus.UNKNOWN;
public Config(boolean nogui)
{
this.nogui = nogui;
List<String> lines;
try {
lines = Files.readAllLines(Paths.get("config.txt"), StandardCharsets.UTF_8);
System.out.println("[INFO] Loading config: "+Paths.get("config.txt").toFile().getAbsolutePath());
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 "altprefix":
altprefix = value;
break;
case "owner":
owner = value;
break;
case "success":
success = value;
break;
case "warning":
warning = value;
break;
case "error":
error = value;
break;
case "loading":
loadingEmoji = value;
break;
case "searching":
searchingEmoji = value;
break;
case "game":
game = value;
break;
case "help":
help = value;
break;
case "songinstatus":
songingame = "true".equalsIgnoreCase(value);
break;
case "stayinchannel":
stay = "true".equalsIgnoreCase(value);
break;
case "eval":
useEval = "true".equalsIgnoreCase(value);
break;
case "dbots":
dbots = "110373943822540800".equals(value);
break;
case "npimages":
npimages = "true".equalsIgnoreCase(value);
break;
case "maxtime":
try{
maxSeconds = Long.parseLong(value);
}catch(NumberFormatException e){}
break;
case "status":
status = OnlineStatus.fromKey(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
+ "\nPlease make sure that the files are not on your desktop or some other restricted area.");
}
}
}
public String getPrefix()
{
return prefix;
}
public String getAltPrefix()
{
return altprefix;
}
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;
}
public String getLoading()
{
return loadingEmoji==null ? "\u231A" : loadingEmoji;
}
public String getSearching()
{
return searchingEmoji==null ? "\uD83D\uDD0E" : searchingEmoji;
}
public Game getGame()
{
if(game==null || game.isEmpty())
return null;
if(game.toLowerCase().startsWith("playing"))
return Game.playing(game.substring(7).trim());
if(game.toLowerCase().startsWith("listening to"))
return Game.listening(game.substring(12).trim());
if(game.toLowerCase().startsWith("listening"))
return Game.listening(game.substring(9).trim());
if(game.toLowerCase().startsWith("watching"))
return Game.watching(game.substring(8).trim());
return Game.playing(game);
}
public String getHelp()
{
return help==null ? "help" : help;
}
public boolean getNoGui()
{
return nogui;
}
public boolean getStay()
{
return stay;
}
public boolean getSongInStatus()
{
return songingame;
}
public boolean getDBots()
{
return dbots;
}
public boolean useEval()
{
return useEval;
}
public boolean useNPImages()
{
return npimages;
}
public long getMaxSeconds()
{
return maxSeconds;
}
public OnlineStatus getStatus()
{
return status;
}
private void alert(String message)
{
if(nogui)
System.out.println("[WARNING] "+message);
else
{
try {
JOptionPane.showMessageDialog(null, message, "JMusicBot", JOptionPane.WARNING_MESSAGE);
} catch(Exception e) {
nogui = true;
alert("Switching to nogui mode. You can manually start in nogui mode by including the -nogui flag.");
alert(message);
}
}
}
private String prompt(String content)
{
if(nogui)
{
Scanner scanner = new Scanner(System.in);
System.out.println(content);
return scanner.next();
}
else
{
try {
return JOptionPane.showInputDialog(null, content, "JMusicBot", JOptionPane.WARNING_MESSAGE);
} catch(Exception e) {
nogui = true;
alert("Switching to nogui mode. You can manually start in nogui mode by including the -nogui flag.");
return prompt(content);
}
}
}
}
@@ -15,94 +15,210 @@
*/
package com.jagrosh.jmusicbot;
import java.awt.Color;
import javax.security.auth.login.LoginException;
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.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.*;
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 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 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;
import static com.jagrosh.jmusicbot.utils.YouTubeUtil.GetPoTokenAndVisitorData;
/**
*
* @author John Grosh (jagrosh)
*/
public class JMusicBot {
public static Permission[] RECOMMENDED_PERMS = new Permission[]{Permission.MESSAGE_READ, Permission.MESSAGE_WRITE, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
public class JMusicBot
{
public final static Logger LOG = LoggerFactory.getLogger(JMusicBot.class);
public final static Permission[] RECOMMENDED_PERMS = {Permission.VIEW_CHANNEL, Permission.MESSAGE_SEND, 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};
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, GatewayIntent.MESSAGE_CONTENT, GatewayIntent.GUILD_EXPRESSIONS, GatewayIntent.SCHEDULED_EVENTS};
/**
* @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;
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
Config config = new Config(nogui);
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();
Bot bot = new Bot(waiter, config);
SettingsManager settings = new SettingsManager();
Bot bot = new Bot(waiter, config, settings);
CommandClient client = createCommandClient(config, settings, bot);
AboutCommand ab = new AboutCommand(Color.BLUE.brighter(),
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot) (v0.1.2)",
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.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(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);
}
GetPoTokenAndVisitorData(config.getChromePath(), config.getChromeDriverPath(), config.getChromeHeadless());
}
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);
ab.setIsAuthor(false);
ab.setReplacementCharacter("\uD83C\uDFB6");
AudioHandler.STAY_IN_CHANNEL = config.getStay();
AudioHandler.SONG_IN_STATUS = config.getSongInStatus();
AudioHandler.MAX_SECONDS = config.getMaxSeconds();
AudioHandler.USE_NP_REFRESH = !config.useNPImages();
// set up the command client
aboutCommand.setIsAuthor(false);
aboutCommand.setReplacementCharacter("\uD83C\uDFB6"); // 🎶
// set up the command client
CommandClientBuilder cb = new CommandClientBuilder()
.setPrefix(config.getPrefix())
.setAlternativePrefix(config.getAltPrefix())
.setOwnerId(config.getOwnerId())
.setOwnerId(Long.toString(config.getOwnerId()))
.setEmojis(config.getSuccess(), config.getWarning(), config.getError())
.setHelpWord(config.getHelp())
.addCommands(
ab,
.setLinkedCacheSize(200)
.setGuildSettingsManager(settings)
.addCommands(aboutCommand,
new PingCommand(),
new SettingsCmd(bot),
new LyricsCmd(bot),
new NowplayingCmd(bot),
new PlayCmd(bot, config.getLoading()),
new PlayCmd(bot),
new PlaylistsCmd(bot),
new QueueCmd(bot),
new RemoveCmd(bot),
new SearchCmd(bot, config.getSearching()),
new SCSearchCmd(bot, config.getSearching()),
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 GuildlistCommand(waiter),
new AutoplaylistCmd(bot),
new DebugCmd(bot),
new PlaylistCmd(bot),
new SetavatarCmd(bot),
new SetgameCmd(bot),
@@ -110,55 +226,23 @@ public class JMusicBot {
new SetstatusCmd(bot),
new ShutdownCmd(bot)
);
// enable eval if applicable
if(config.useEval())
cb.addCommand(new EvalCmd(bot));
boolean nogame = false;
if(config.getStatus()!=OnlineStatus.UNKNOWN)
// set status if set in config
if(config.getStatus() != OnlineStatus.UNKNOWN)
cb.setStatus(config.getStatus());
if(config.getGame()==null)
// set game
if(config.getGame() == null)
cb.useDefaultGame();
else if(config.getGame().getName().equalsIgnoreCase("none"))
{
cb.setGame(null);
nogame = true;
}
else if(config.isGameNone())
cb.setActivity(null);
else
cb.setGame(config.getGame());
CommandClient client = cb.build();
cb.setActivity(config.getGame());
if(!config.getNoGui())
{
try {
GUI gui = new GUI(bot);
bot.setGUI(gui);
gui.init();
} catch(Exception e) {
LoggerFactory.getLogger("Startup").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 -nogui flag.");
}
}
// attempt to log in and start
try {
new JDABuilder(AccountType.BOT)
.setToken(config.getToken())
.setAudioEnabled(true)
.setGame(nogame ? null : Game.playing("loading..."))
.setStatus(config.getStatus()==OnlineStatus.INVISIBLE||config.getStatus()==OnlineStatus.OFFLINE ? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB)
.addEventListener(client)
.addEventListener(waiter)
.addEventListener(bot)
.buildAsync();
} catch (LoginException ex)
{
LoggerFactory.getLogger("Startup").error(ex+"\nPlease make sure you are "
+ "editing the correct config.txt file, and that you have used the "
+ "correct token (not the 'secret'!)");
}
catch(IllegalArgumentException ex)
{
LoggerFactory.getLogger("Startup").error(""+ex);
}
return cb.build();
}
}
@@ -0,0 +1,145 @@
/*
* 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 com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler;
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.channel.concrete.PrivateChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.events.session.ReadyEvent;
import net.dv8tion.jda.api.events.session.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.MessageDeleteEvent;
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);
}
if (bot.getConfig().useYoutubeOauth2())
{
YoutubeOauth2TokenHandler.Data data = bot.getYouTubeOauth2Handler().getData();
if (data != null)
{
PrivateChannel channel = bot.getJDA().openPrivateChannelById(bot.getConfig().getOwnerId()).complete();
channel
.sendMessage(
"# DO NOT AUTHORISE THIS WITH YOUR MAIN GOOGLE ACCOUNT!!!\n"
+ "## Create or use an alternative/burner Google account!\n"
+ "To give JMusicBot access to your Google account, go to "
+ data.getAuthorisationUrl()
+ " and enter the code **" + data.getCode() + "**")
.queue();
}
}
}
@Override
public void onMessageDelete(MessageDeleteEvent event)
{
if(event.isFromGuild())
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();
}
}
@@ -1,132 +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 com.jagrosh.jmusicbot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class Settings {
public final static Settings DEFAULT_SETTINGS = new Settings(0, 0, 0, 100, null, false);
private long textId;
private long voiceId;
private long roleId;
private int volume;
private String defaultPlaylist;
private boolean repeatMode;
public Settings(String textId, String voiceId, String roleId, int volume, String defaultPlaylist, boolean repeatMode)
{
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;
}
public Settings(long textId, long voiceId, long roleId, int volume, String defaultPlaylist, boolean repeatMode)
{
this.textId = textId;
this.voiceId = voiceId;
this.roleId = roleId;
this.volume = volume;
this.defaultPlaylist = defaultPlaylist;
this.repeatMode = repeatMode;
}
public long getTextId()
{
return textId;
}
public long getVoiceId()
{
return voiceId;
}
public long getRoleId()
{
return roleId;
}
public int getVolume()
{
return volume;
}
public String getDefaultPlaylist()
{
return defaultPlaylist;
}
public boolean getRepeatMode()
{
return repeatMode;
}
public void setTextId(long id)
{
this.textId = id;
}
public void setVoiceId(long id)
{
this.voiceId = id;
}
public void setRoleId(long id)
{
this.roleId = id;
}
public void setVolume(int volume)
{
this.volume = volume;
}
public void setDefaultPlaylist(String defaultPlaylist)
{
this.defaultPlaylist = defaultPlaylist;
}
public void setRepeatMode(boolean mode)
{
this.repeatMode = mode;
}
}
@@ -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());
}
}
@@ -15,8 +15,14 @@
*/
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.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;
@@ -24,54 +30,83 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.playlist.Playlist;
import com.jagrosh.jmusicbot.queue.FairQueue;
import net.dv8tion.jda.core.audio.AudioSendHandler;
import net.dv8tion.jda.core.entities.Game;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.User;
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.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
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.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler {
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 static Logger LOGGER = LoggerFactory.getLogger(AudioHandler.class);
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 final FairQueue<QueuedTrack> queue;
private final Set<String> votes;
private final List<AudioTrack> defaultQueue;
private final Bot bot;
private AudioFrame lastFrame;
private long requester;
public static boolean STAY_IN_CHANNEL;
public static boolean SONG_IN_STATUS;
public static long MAX_SECONDS = -1;
public static boolean USE_NP_REFRESH;
private AbstractQueue<QueuedTrack> queue;
public AudioHandler(AudioPlayer audioPlayer, Guild guild, Bot bot) {
this.audioPlayer = audioPlayer;
this.guildId = guild.getIdLong();
this.bot = bot;
queue = new FairQueue<>();
votes = new HashSet<>();
defaultQueue = new LinkedList<>();
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 int addTrack(AudioTrack track, User user)
public void setQueueType(QueueType type)
{
queue = type.createInstance(queue);
}
public int addTrackToFront(QueuedTrack qtrack)
{
if(audioPlayer.getPlayingTrack()==null)
{
requester = user.getIdLong();
audioPlayer.playTrack(track);
audioPlayer.playTrack(qtrack.getTrack());
return -1;
}
else
return queue.add(new QueuedTrack(track, user.getIdLong()));
{
queue.addAt(0, qtrack);
return 0;
}
}
public FairQueue<QueuedTrack> getQueue()
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;
}
@@ -84,9 +119,9 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
//current = null;
}
public boolean isMusicPlaying()
public boolean isMusicPlaying(JDA jda)
{
return bot.getJDA().getGuildById(guildId).getSelfMember().getVoiceState().inVoiceChannel() && audioPlayer.getPlayingTrack()!=null;
return guild(jda).getSelfMember().getVoiceState().inAudioChannel() && audioPlayer.getPlayingTrack()!=null;
}
public Set<String> getVotes()
@@ -99,9 +134,12 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
return audioPlayer;
}
public long getRequester()
public RequestMetadata getRequestMetadata()
{
return requester;
if(audioPlayer.getPlayingTrack() == null)
return RequestMetadata.EMPTY;
RequestMetadata rm = audioPlayer.getPlayingTrack().getUserData(RequestMetadata.class);
return rm == null ? RequestMetadata.EMPTY : rm;
}
public boolean playFromDefault()
@@ -111,83 +149,197 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
audioPlayer.playTrack(defaultQueue.remove(0));
return true;
}
Guild guild = bot.getJDA().getGuildById(guildId);
if(bot.getSettings(guild)==null || bot.getSettings(guild).getDefaultPlaylist()==null)
Settings settings = manager.getBot().getSettingsManager().getSettings(guildId);
if(settings==null || settings.getDefaultPlaylist()==null)
return false;
Playlist pl = Playlist.loadPlaylist(bot.getSettings(guild).getDefaultPlaylist());
Playlist pl = manager.getBot().getPlaylistLoader().getPlaylist(settings.getDefaultPlaylist());
if(pl==null || pl.getItems().isEmpty())
return false;
pl.loadTracks(bot.getAudioManager(), (at) -> {
pl.loadTracks(manager, (at) ->
{
if(audioPlayer.getPlayingTrack()==null)
audioPlayer.playTrack(at);
else
defaultQueue.add(at);
}, () -> {
if(pl.getTracks().isEmpty() && !STAY_IN_CHANNEL)
guild.getAudioManager().closeAudioConnection();
}, () ->
{
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) {
if(endReason==AudioTrackEndReason.FINISHED && bot.getSettings(bot.getJDA().getGuildById(guildId)).getRepeatMode())
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)
{
queue.add(new QueuedTrack(track.makeClone(), requester));
QueuedTrack clone = new QueuedTrack(track.makeClone(), track.getUserData(RequestMetadata.class));
if(repeatMode == RepeatMode.ALL)
queue.add(clone);
else
queue.addAt(0, clone);
}
requester = 0;
if(queue.isEmpty())
{
if(!playFromDefault())
{
if(SONG_IN_STATUS)
bot.resetGame();
if(!STAY_IN_CHANNEL)
bot.getThreadpool().submit(() -> bot.getJDA().getGuildById(guildId).getAudioManager().closeAudioConnection());
bot.updateTopic(guildId, this);
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();
requester = qt.getIdentifier();
player.playTrack(qt.getTrack());
}
}
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track) {
votes.clear();
if(SONG_IN_STATUS)
{
if(bot.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()<=1)
bot.getJDA().getPresence().setGame(Game.listening(track.getInfo().title));
else
bot.resetGame();
}
bot.updateTopic(guildId, this);
}
@Override
public boolean canProvide() {
lastFrame = audioPlayer.provide();
return lastFrame != null;
}
@Override
public byte[] provide20MsAudio() {
return lastFrame.data;
}
@Override
public boolean isOpus() {
return true;
}
public static boolean isTooLong(AudioTrack track)
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception)
{
if(MAX_SECONDS<=0)
return false;
return Math.round(track.getDuration()/1000.0) > MAX_SECONDS;
if (
exception.getMessage().equals("Sign in to confirm you're not a bot")
|| exception.getMessage().equals("Please sign in")
)
LOGGER.error(
"Track {} has failed to play: {}"
+ "You will need to sign in to Google to play YouTube tracks. More info: https://jmusicbot.com/youtube-oauth2",
track.getIdentifier(),
exception.getMessage()
);
else
LOGGER.error("Track {} has failed to play", track.getIdentifier(), exception);
}
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track)
{
votes.clear();
manager.getBot().getNowplayingHandler().onTrackUpdate(track);
}
// Formatting
public MessageCreateData getNowPlaying(JDA jda)
{
if(isMusicPlaying(jda))
{
Guild guild = guild(jda);
AudioTrack track = audioPlayer.getPlayingTrack();
MessageCreateBuilder mb = new MessageCreateBuilder();
mb.addContent(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 MessageCreateData getNoMusicPlaying(JDA jda)
{
Guild guild = guild(jda);
return new MessageCreateBuilder()
.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,125 @@
/*
* 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.channel.concrete.TextChannel;
import net.dv8tion.jda.api.exceptions.PermissionException;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
/**
*
* @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.getChannel().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();
MessageCreateData msg = handler.getNowPlaying(bot.getJDA());
if(msg==null)
{
msg = handler.getNoMusicPlaying(bot.getJDA());
toRemove.add(guildId);
}
try
{
tc.editMessageById(pair.getValue(), MessageEditData.fromCreateData(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().inAudioChannel()).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,159 @@
/*
* 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.jagrosh.jmusicbot.BotConfig;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.jagrosh.jmusicbot.utils.YouTubeUtil;
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 com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup;
import com.sedmelluq.lava.extensions.youtuberotator.planner.AbstractRoutePlanner;
import com.sedmelluq.lava.extensions.youtuberotator.planner.BalancingIpRoutePlanner;
import com.sedmelluq.lava.extensions.youtuberotator.planner.NanoIpRoutePlanner;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
import dev.lavalink.youtube.YoutubeAudioSourceManager;
import dev.lavalink.youtube.clients.Web;
import net.dv8tion.jda.api.entities.Guild;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PlayerManager extends DefaultAudioPlayerManager
{
private final static Logger LOGGER = LoggerFactory.getLogger(PlayerManager.class);
private final Bot bot;
private final BotConfig config;
public PlayerManager(Bot bot, BotConfig config)
{
this.bot = bot;
this.config = config;
}
public void init()
{
TransformativeAudioSourceManager.createTransforms(bot.getConfig().getTransforms()).forEach(t -> registerSourceManager(t));
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
if (config.getYTRoutingPlanner() != YouTubeUtil.RoutingPlanner.NONE)
{
AbstractRoutePlanner routePlanner = YouTubeUtil.createRouterPlanner(config.getYTRoutingPlanner(), config.getYTIpBlocks());
YoutubeIpRotatorSetup rotator = new YoutubeIpRotatorSetup(routePlanner);
rotator.forConfiguration(yt.getHttpInterfaceManager(), false)
.withMainDelegateFilter(yt.getContextFilter())
.setup();
}
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");
}
private YoutubeAudioSourceManager setupYoutubeAudioSourceManager()
{
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
// OAuth2 setup
if (bot.getConfig().useYoutubeOauth2())
{
String token = null;
try
{
token = Files.readString(OtherUtil.getPath("youtubetoken.txt"));
}
catch (NoSuchFileException e)
{
/* ignored */
}
catch (IOException e)
{
LOGGER.warn("Failed to read YouTube OAuth2 token file: {}", e.getMessage());
return yt;
}
LOGGER.debug("Read YouTube OAuth2 refresh token from youtubetoken.txt");
try
{
yt.useOauth2(token, false);
}
catch (Exception e)
{
LOGGER.warn("Failed to authorise with YouTube. If this issue persists, delete the youtubetoken.txt file to reauthorise.", e);
}
}
return yt;
}
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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -15,28 +15,35 @@
*/
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 com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueuedTrack implements Queueable {
public class QueuedTrack implements Queueable
{
private final AudioTrack track;
private final long owner;
public QueuedTrack(AudioTrack track, long owner)
private final RequestMetadata requestMetadata;
public QueuedTrack(AudioTrack track, RequestMetadata rm)
{
this.track = track;
this.owner = owner;
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 owner;
public long getIdentifier()
{
return requestMetadata.getOwner();
}
public AudioTrack getTrack()
@@ -44,9 +51,17 @@ public class QueuedTrack implements Queueable {
return track;
}
@Override
public String toString() {
return "`["+FormatUtil.formatTime(track.getDuration())+"]` **" + track.getInfo().title +"** - <@"+owner+">";
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());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -18,19 +18,19 @@ 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;
import com.jagrosh.jmusicbot.settings.Settings;
import com.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;
import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public abstract class MusicCommand extends Command {
public abstract class MusicCommand extends Command
{
protected final Bot bot;
protected boolean bePlaying;
protected boolean beListening;
@@ -39,49 +39,62 @@ public abstract class MusicCommand extends Command {
{
this.bot = bot;
this.guildOnly = true;
this.category = bot.MUSIC;
this.category = new Category("Music");
}
@Override
protected void execute(CommandEvent event) {
Settings settings = bot.getSettings(event.getGuild());
TextChannel tchannel = event.getGuild().getTextChannelById(settings.getTextId());
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 {
try
{
event.getMessage().delete().queue();
} catch(PermissionException e){}
event.replyInDm(event.getClient().getError()+" You can only use that command in <#"+settings.getTextId()+">!");
} catch(PermissionException ignore){}
event.replyInDm(event.getClient().getError()+" You can only use that command in "+tchannel.getAsMention()+"!");
return;
}
if(bePlaying
&& (event.getGuild().getAudioManager().getSendingHandler()==null
|| !((AudioHandler)event.getGuild().getAudioManager().getSendingHandler()).isMusicPlaying()))
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();
VoiceChannel current = (VoiceChannel) event.getGuild().getSelfMember().getVoiceState().getChannel();
if(current==null)
current = event.getGuild().getVoiceChannelById(settings.getVoiceId());
current = settings.getVoiceChannel(event.getGuild());
GuildVoiceState userState = event.getMember().getVoiceState();
if(!userState.inVoiceChannel() || userState.isDeafened() || (current!=null && !userState.getChannel().equals(current)))
if(!userState.inAudioChannel() || 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!");
event.replyError("You must be listening in "+(current==null ? "a voice channel" : current.getAsMention())+" to use that!");
return;
}
if(!event.getGuild().getSelfMember().getVoiceState().inVoiceChannel())
try {
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().inAudioChannel())
{
try
{
event.getGuild().getAudioManager().openAudioConnection(userState.getChannel());
}catch(PermissionException ex) {
event.reply(event.getClient().getError()+" I am unable to connect to **"+userState.getChannel().getName()+"**!");
}
catch(PermissionException ex)
{
event.reply(event.getClient().getError()+" I am unable to connect to "+userState.getChannel().getAsMention()+"!");
return;
}
}
}
doCommand(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;
}
}
@@ -1,63 +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 com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class RepeatCmd extends Command {
private final Bot bot;
public RepeatCmd(Bot bot)
{
this.bot = bot;
this.name = "repeat";
this.help = "re-adds music to the queue when finished";
this.arguments = "[on|off]";
this.guildOnly = true;
this.category = bot.DJ;
}
@Override
protected void execute(CommandEvent event) {
boolean value;
if(event.getArgs().isEmpty())
{
value = !bot.getSettings(event.getGuild()).getRepeatMode();
}
else if(event.getArgs().equalsIgnoreCase("true") || event.getArgs().equalsIgnoreCase("on"))
{
value = true;
}
else if(event.getArgs().equalsIgnoreCase("false") || event.getArgs().equalsIgnoreCase("off"))
{
value = false;
}
else
{
event.replyError("Valid options are `on` or `off` (or leave empty to toggle)");
return;
}
bot.setRepeatMode(event.getGuild(), value);
event.replySuccess("Repeat mode is now `"+(value ? "ON" : "OFF")+"`");
}
}
@@ -1,134 +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 com.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 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.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 SCSearchCmd extends MusicCommand {
private final OrderedMenu.Builder builder;
private final String searchingEmoji;
public SCSearchCmd(Bot bot, String searchingEmoji)
{
super(bot);
this.searchingEmoji = searchingEmoji;
this.name = "scsearch";
this.arguments = "<query>";
this.help = "searches Soundcloud 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.reply(event.getClient().getError()+" Please include a query.");
return;
}
event.reply(searchingEmoji+" Searching... `["+event.getArgs()+"]`",m -> bot.getAudioManager().loadItemOrdered(event.getGuild(), "scsearch:"+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) {
if(AudioHandler.isTooLong(track))
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(AudioHandler.MAX_SECONDS*1000)+"`")).queue();
return;
}
int pos = bot.queueTrack(event, track)+1;
m.editMessage(FormatUtil.filter(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(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(AudioHandler.isTooLong(track))
{
event.replyWarning("This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(AudioHandler.MAX_SECONDS*1000)+"`");
return;
}
int pos = bot.queueTrack(event, track)+1;
event.replySuccess("Added **"+track.getInfo().title
+"** (`"+FormatUtil.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("`["+FormatUtil.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();
}
}
}
@@ -1,68 +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 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;
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()+"**")
+ "\nRepeat Mode: **"+(s.getRepeatMode() ? "On" : "Off")+"**"
+ "\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.setEmbed(ebuilder.build()).build()).queue();
}
}
@@ -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,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -13,54 +13,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.admin;
import java.util.List;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.utils.FinderUtil;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.Role;
import net.dv8tion.jda.api.entities.Role;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetdjCmd extends Command {
private final Bot bot;
public class SetdjCmd extends AdminCommand
{
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;
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
protected void execute(CommandEvent event) {
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a role name or NONE");
return;
}
else if(event.getArgs().equalsIgnoreCase("none"))
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
bot.clearRole(event.getGuild());
s.setDJRole(null);
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());
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
{
bot.setRole(list.get(0));
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,71 @@
/*
* 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;
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.utils.FinderUtil;
import com.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.channel.concrete.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
{
@SuppressWarnings({"unchecked", "rawtypes"})
List<TextChannel> list = (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,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 com.jagrosh.jmusicbot.commands;
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.utils.FinderUtil;
import com.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.channel.concrete.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
{
@SuppressWarnings({"unchecked", "rawtypes"})
List<VoiceChannel> list = (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)+".");
}
}
}
@@ -13,36 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
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 MusicCommand {
public class ForceskipCmd extends DJCommand
{
public ForceskipCmd(Bot bot)
{
super(bot);
this.name = "forceskip";
this.help = "skips the current song";
this.aliases = new String[]{"modskip"};
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = true;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
User u = event.getJDA().getUserById(handler.getRequester());
RequestMetadata rm = handler.getRequestMetadata();
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title
+"** (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
+"** "+(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());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -13,30 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
import com.jagrosh.jmusicbot.commands.DJCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PauseCmd extends MusicCommand {
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;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getPlayer().isPaused())
{
@@ -46,5 +47,4 @@ public class PauseCmd extends MusicCommand {
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 */ }
}
@@ -13,32 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
import com.jagrosh.jmusicbot.commands.DJCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SkiptoCmd extends MusicCommand {
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 = new String[]{"jumpto"};
this.aliases = bot.getConfig().getAliases(this.name);
this.bePlaying = true;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
int index = 0;
try
{
@@ -59,5 +59,4 @@ public class SkiptoCmd extends MusicCommand {
event.reply(event.getClient().getSuccess()+" Skipped to **"+handler.getQueue().get(0).getTrack().getInfo().title+"**");
handler.getPlayer().stopTrack();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -13,34 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
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 MusicCommand {
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;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler!=null)
handler.stopAndClear();
handler.stopAndClear();
event.getGuild().getAudioManager().closeAudioConnection();
event.reply(event.getClient().getSuccess()+" The player has stopped and the queue has been cleared.");
}
}
@@ -13,33 +13,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
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 MusicCommand {
public class VolumeCmd extends DJCommand
{
public VolumeCmd(Bot bot)
{
super(bot);
this.name = "volume";
this.aliases = new String[]{"vol"};
this.aliases = bot.getConfig().getAliases(this.name);
this.help = "sets or shows volume";
this.arguments = "[0-150]";
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int volume = handler==null && bot.getSettings(event.getGuild())==null ? 100 : (handler==null ? bot.getSettings(event.getGuild()).getVolume() : handler.getPlayer().getVolume());
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+"`");
@@ -56,8 +59,8 @@ public class VolumeCmd extends MusicCommand {
event.reply(event.getClient().getError()+" Volume must be a valid integer between 0 and 150!");
else
{
bot.setUpHandler(event).getPlayer().setVolume(nvolume);
bot.setVolume(event.getGuild(), nvolume);
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.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.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());
MessageCreateBuilder builder = new MessageCreateBuilder()
.addContent(EMOJI + " **")
.addContent(FormatUtil.filter(event.getSelfUser().getName()))
.addContent("** 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().inAudioChannel()).count()
+ " audio connections", null);
event.getMessage().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.utils.messages.MessageCreateData;
/**
*
* @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();
MessageCreateData m = handler.getNowPlaying(event.getJDA());
if(m==null)
{
event.getMessage().getChannel().sendMessage(handler.getNoMusicPlaying(event.getJDA())).queue();
bot.getNowplayingHandler().clearLastNPMessage(event.getGuild());
}
else
{
event.getMessage().getChannel().sendMessage(m).queue(msg -> bot.getNowplayingHandler().setLastNPMessage(msg));
}
}
}
@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
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;
@@ -25,12 +27,15 @@ 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.playlist.Playlist;
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.core.Permission;
import net.dv8tion.jda.core.entities.Message;
import net.dv8tion.jda.core.exceptions.PermissionException;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.exceptions.PermissionException;
/**
*
@@ -38,41 +43,39 @@ import net.dv8tion.jda.core.exceptions.PermissionException;
*/
public class PlayCmd extends MusicCommand
{
public final static String LOAD = "\uD83D\uDCE5";
public final static String CANCEL = "\uD83D\uDEAB";
private final static String LOAD = "\uD83D\uDCE5"; // 📥
private final static String CANCEL = "\uD83D\uDEAB"; // 🚫
private final String loadingEmoji;
public PlayCmd(Bot bot, String loadingEmoji)
public PlayCmd(Bot bot)
{
super(bot);
this.loadingEmoji = loadingEmoji;
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())
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty())
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler!=null && handler.getPlayer().getPlayingTrack()!=null && handler.getPlayer().isPaused())
if(handler.getPlayer().getPlayingTrack()!=null && handler.getPlayer().isPaused())
{
boolean isDJ = event.getMember().hasPermission(Permission.MANAGE_SERVER);
if(!isDJ)
isDJ = event.isOwner();
if(!isDJ)
isDJ = event.getMember().getRoles().contains(event.getGuild().getRoleById(bot.getSettings(event.getGuild()).getRoleId()));
if(!isDJ)
event.replyError("Only DJs can unpause the player!");
else
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");
@@ -85,15 +88,15 @@ public class PlayCmd extends MusicCommand
}
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
? event.getArgs().substring(1,event.getArgs().length()-1)
: event.getArgs();
event.reply(loadingEmoji+" Loading... `["+args+"]`", m -> bot.getAudioManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false)));
: 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
{
final Message m;
final CommandEvent event;
final boolean ytsearch;
private final Message m;
private final CommandEvent event;
private final boolean ytsearch;
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
{
@@ -104,15 +107,16 @@ public class PlayCmd extends MusicCommand
private void loadSingle(AudioTrack track, AudioPlaylist playlist)
{
if(AudioHandler.isTooLong(track))
if(bot.getConfig().isTooLong(track))
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(AudioHandler.MAX_SECONDS*1000)+"`")).queue();
+ TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
return;
}
int pos = bot.queueTrack(event, track)+1;
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
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
+"** (`"+ 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
@@ -130,7 +134,7 @@ public class PlayCmd extends MusicCommand
m.editMessage(addMsg).queue();
}).setFinalAction(m ->
{
try{m.clearReactions().queue();}catch(PermissionException ex){}
try{ m.clearReactions().queue(); }catch(PermissionException ignore) {}
}).build().display(m);
}
}
@@ -139,9 +143,10 @@ public class PlayCmd extends MusicCommand
{
int[] count = {0};
playlist.getTracks().stream().forEach((track) -> {
if(!AudioHandler.isTooLong(track) && !track.equals(exclude))
if(!bot.getConfig().isTooLong(track) && !track.equals(exclude))
{
bot.queueTrack(event, track);
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)));
count[0]++;
}
});
@@ -170,10 +175,15 @@ public class PlayCmd extends MusicCommand
else
{
int count = loadPlaylist(playlist, null);
if(count==0)
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 (`"+FormatUtil.formatTime(AudioHandler.MAX_SECONDS*1000)+"`)")).queue();
+"**) ")+"were longer than the allowed maximum (`"+bot.getConfig().getMaxTime()+"`)")).queue();
}
else
{
@@ -181,7 +191,7 @@ public class PlayCmd extends MusicCommand
+(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 (`"
+ FormatUtil.formatTime(AudioHandler.MAX_SECONDS*1000)+"`) have been omitted." : ""))).queue();
+ bot.getConfig().getMaxTime()+"`) have been omitted." : ""))).queue();
}
}
}
@@ -192,7 +202,7 @@ public class PlayCmd extends MusicCommand
if(ytsearch)
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
else
bot.getAudioManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event,true));
bot.getPlayerManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event,true));
}
@Override
@@ -205,8 +215,8 @@ public class PlayCmd extends MusicCommand
}
}
public class PlaylistCmd extends MusicCommand {
public class PlaylistCmd extends MusicCommand
{
public PlaylistCmd(Bot bot)
{
super(bot);
@@ -219,20 +229,23 @@ public class PlayCmd extends MusicCommand
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name.");
return;
}
Playlist playlist = Playlist.loadPlaylist(event.getArgs());
Playlist playlist = bot.getPlaylistLoader().getPlaylist(event.getArgs());
if(playlist==null)
{
event.reply(event.getClient().getError()+" I could not find `"+event.getArgs()+".txt` in the Playlists folder.");
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 -> {
playlist.loadTracks(bot.getAudioManager(), (at)->bot.queueTrack(event, at), () -> {
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!");
@@ -1,5 +1,5 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -13,40 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
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.playlist.Playlist;
import com.jagrosh.jmusicbot.commands.MusicCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PlaylistsCmd extends MusicCommand {
public class PlaylistsCmd extends MusicCommand
{
public PlaylistsCmd(Bot bot)
{
super(bot);
this.name = "playlists";
this.help = "shows the available playlists";
this.aliases = new String[]{"pls"};
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = true;
this.beListening = false;
this.beListening = false;
}
@Override
public void doCommand(CommandEvent event) {
if(!Playlist.folderExists())
Playlist.createFolder();
if(!Playlist.folderExists())
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 = Playlist.getPlaylists();
List<String> list = bot.getPlaylistLoader().getPlaylistNames();
if(list==null)
event.reply(event.getClient().getError()+" Failed to load available playlists!");
else if(list.isEmpty())
@@ -59,5 +60,4 @@ public class PlaylistsCmd extends MusicCommand {
event.reply(builder.toString());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.music;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -22,33 +22,43 @@ 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 net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.exceptions.PermissionException;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
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 {
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 = new String[]{"list"};
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 e){}})
.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);
}
@@ -57,15 +67,25 @@ public class QueueCmd extends MusicCommand {
public void doCommand(CommandEvent event)
{
int pagenum = 1;
try{
try
{
pagenum = Integer.parseInt(event.getArgs());
}catch(NumberFormatException e){}
}
catch(NumberFormatException ignore){}
AudioHandler ah = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
List<QueuedTrack> list = ah.getQueue().getList();
if(list.isEmpty())
{
event.replyWarning("There is no music in the queue!"
+(!ah.isMusicPlaying() ? "" : " Now playing:\n\n**"+ah.getPlayer().getPlayingTrack().getInfo().title+"**\n"+FormatUtil.embedFormat(ah)));
MessageCreateData nowp = ah.getNowPlaying(event.getJDA());
MessageCreateData nonowp = ah.getNoMusicPlaying(event.getJDA());
MessageCreateData built = new MessageCreateBuilder()
.setContent(event.getClient().getWarning() + " There is no music in the queue!")
.setEmbeds((nowp==null ? nonowp : nowp).getEmbeds().get(0)).build();
event.getMessage().getChannel().sendMessage(built).queue(m ->
{
if(nowp!=null)
bot.getNowplayingHandler().setLastNPMessage(m);
});
return;
}
String[] songs = new String[list.size()];
@@ -75,8 +95,9 @@ public class QueueCmd extends MusicCommand {
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) -> event.getClient().getSuccess()+" "+getQueueTitle(ah, event.getClient().getSuccess(), songs.length, fintotal, bot.getSettings(event.getGuild()).getRepeatMode()))
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())
@@ -84,13 +105,17 @@ public class QueueCmd extends MusicCommand {
builder.build().paginate(event.getChannel(), pagenum);
}
private String getQueueTitle(AudioHandler ah, String success, int songslength, long total, boolean repeatmode)
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("**").append(ah.getPlayer().getPlayingTrack().getInfo().title).append("**\n").append(FormatUtil.embedFormat(ah)).append("\n\n");
{
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(FormatUtil.formatTime(total)).append("` ")
.append(repeatmode ? "| \uD83D\uDD01" : "").toString());
.append(" entries | `").append(TimeUtil.formatTime(total)).append("` ")
.append("| ").append(queueType.getEmoji()).append(" `").append(queueType.getUserFriendlyName()).append('`')
.append(repeatmode.getEmoji() != null ? " | "+repeatmode.getEmoji() : "").toString());
}
}
@@ -13,34 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
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 net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.User;
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 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 = new String[]{"delete"};
this.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getQueue().isEmpty())
{
@@ -67,9 +70,10 @@ public class RemoveCmd extends MusicCommand {
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(event.getGuild().getRoleById(bot.getSettings(event.getGuild()).getRoleId()));
isDJ = event.getMember().getRoles().contains(settings.getRole(event.getGuild()));
QueuedTrack qt = handler.getQueue().get(pos-1);
if(qt.getIdentifier()==event.getAuthor().getIdLong())
{
@@ -93,5 +97,4 @@ public class RemoveCmd extends MusicCommand {
event.replyError("You cannot remove **"+qt.getTrack().getInfo().title+"** because you didn't add it!");
}
}
}
@@ -13,31 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.Permission;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class NowplayingCmd extends MusicCommand {
public NowplayingCmd(Bot bot)
public class SCSearchCmd extends SearchCmd
{
public SCSearchCmd(Bot bot)
{
super(bot);
this.name = "nowplaying";
this.help = "shows the song that is currently playing";
this.aliases = new String[]{"np","current"};
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
this.searchPrefix = "scsearch:";
this.name = "scsearch";
this.help = "searches Soundcloud for a provided query";
this.aliases = bot.getConfig().getAliases(this.name);
}
@Override
public void doCommand(CommandEvent event) {
event.reply(FormatUtil.nowPlayingMessage(event.getGuild(), event.getClient().getSuccess()), m->bot.setLastNP(m));
}
}
@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
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;
@@ -25,24 +27,28 @@ 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.core.Permission;
import net.dv8tion.jda.core.entities.Message;
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 {
public class SearchCmd extends MusicCommand
{
protected String searchPrefix = "ytsearch:";
private final OrderedMenu.Builder builder;
private final String searchingEmoji;
public SearchCmd(Bot bot, String searchingEmoji)
public SearchCmd(Bot bot)
{
super(bot);
this.searchingEmoji = searchingEmoji;
this.searchingEmoji = bot.getConfig().getSearching();
this.name = "search";
this.aliases = new String[]{"ytsearch"};
this.aliases = bot.getConfig().getAliases(this.name);
this.arguments = "<query>";
this.help = "searches Youtube for a provided query";
this.beListening = true;
@@ -53,22 +59,25 @@ public class SearchCmd extends MusicCommand {
.useNumbers()
.useCancelButton(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES)
;
.setTimeout(1, TimeUnit.MINUTES);
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a query.");
event.replyError("Please include a query.");
return;
}
event.reply(searchingEmoji+" Searching... `["+event.getArgs()+"]`",m ->bot.getAudioManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event)));
event.reply(searchingEmoji+" Searching... `["+event.getArgs()+"]`",
m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), searchPrefix + event.getArgs(), new ResultHandler(m,event)));
}
private class ResultHandler implements AudioLoadResultHandler {
final Message m;
final CommandEvent event;
private class ResultHandler implements AudioLoadResultHandler
{
private final Message m;
private final CommandEvent event;
private ResultHandler(Message m, CommandEvent event)
{
this.m = m;
@@ -76,16 +85,18 @@ public class SearchCmd extends MusicCommand {
}
@Override
public void trackLoaded(AudioTrack track) {
if(AudioHandler.isTooLong(track))
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: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(AudioHandler.MAX_SECONDS*1000)+"`")).queue();
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue();
return;
}
int pos = bot.queueTrack(event, track)+1;
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
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos))).queue();
}
@@ -95,38 +106,41 @@ public class SearchCmd extends MusicCommand {
builder.setColor(event.getSelfMember().getColor())
.setText(FormatUtil.filter(event.getClient().getSuccess()+" Search results for `"+event.getArgs()+"`:"))
.setChoices(new String[0])
.setSelection((msg,i) -> {
.setSelection((msg,i) ->
{
AudioTrack track = playlist.getTracks().get(i-1);
if(AudioHandler.isTooLong(track))
if(bot.getConfig().isTooLong(track))
{
event.replyWarning("This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(AudioHandler.MAX_SECONDS*1000)+"`");
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`");
return;
}
int pos = bot.queueTrack(event, track)+1;
event.replySuccess("Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
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++)
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.addChoices("`["+ TimeUtil.formatTime(track.getDuration())+"]` [**"+track.getInfo().title+"**]("+track.getInfo().uri+")");
}
builder.build().display(m);
}
@Override
public void noMatches() {
public void noMatches()
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
}
@Override
public void loadFailed(FriendlyException throwable) {
public void loadFailed(FriendlyException throwable)
{
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
@@ -0,0 +1,96 @@
/*
* 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.audio.RequestMetadata;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Whew., Inc.
*/
public class SeekCmd extends MusicCommand
{
private final static Logger LOG = LoggerFactory.getLogger("Seeking");
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(RequestMetadata.class).getOwner() != 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!");
return;
}
try
{
playingTrack.setPosition(seekMilliseconds);
}
catch (Exception e)
{
event.replyError("An error occurred while trying to seek: " + e.getMessage());
LOG.warn("Failed to seek track " + playingTrack.getIdentifier(), e);
return;
}
event.replySuccess("Successfully seeked to `" + TimeUtil.formatTime(playingTrack.getPosition()) + "/" + TimeUtil.formatTime(playingTrack.getDuration()) + "`!");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
* 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.
@@ -13,40 +13,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
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 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) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int s = handler.getQueue().shuffle(event.getAuthor().getIdLong());
switch (s) {
switch (s)
{
case 0:
event.reply(event.getClient().getError()+" You don't have any music in the queue to shuffle!");
event.replyError("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!");
event.replyWarning("You only have one song in the queue!");
break;
default:
event.reply(event.getClient().getSuccess()+" You successfully shuffled your "+s+" entries.");
event.replySuccess("You successfully shuffled your "+s+" entries.");
break;
}
}
@@ -13,33 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
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 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.aliases = bot.getConfig().getAliases(this.name);
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(event.getAuthor().getIdLong()==handler.getRequester())
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();
@@ -58,13 +66,12 @@ public class SkipCmd extends MusicCommand {
}
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]`";
int required = (int)Math.ceil(listeners * skipRatio);
msg += skippers + " votes, " + required + "/" + listeners + " needed]`";
if(skippers>=required)
{
User u = event.getJDA().getUserById(handler.getRequester());
msg+="\n"+event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title
+"**"+(handler.getRequester()==0 ? "" : " (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
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);
@@ -13,33 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.playlist.Playlist;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import com.jagrosh.jmusicbot.settings.Settings;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class AutoplaylistCmd extends Command {
public class AutoplaylistCmd extends OwnerCommand
{
private final Bot bot;
public AutoplaylistCmd(Bot bot)
{
this.bot = bot;
this.category = bot.OWNER;
this.ownerCommand = true;
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) {
public void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name or NONE");
@@ -47,18 +48,20 @@ public class AutoplaylistCmd extends Command {
}
if(event.getArgs().equalsIgnoreCase("none"))
{
bot.setDefaultPlaylist(event.getGuild(), null);
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(Playlist.loadPlaylist(pname)==null)
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
{
event.reply(event.getClient().getError()+" Could not find `"+pname+".txt`!");
}
else
{
bot.setDefaultPlaylist(event.getGuild(), pname);
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,88 @@
/*
* 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.channel.ChannelType;
import net.dv8tion.jda.api.utils.FileUpload;
/**
*
* @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.getGuildChannel(), Permission.MESSAGE_ATTACH_FILES))
event.getChannel().sendFiles(FileUpload.fromData(sb.toString().getBytes(), "debug_information.txt")).queue();
else
event.reply("Debug Information: " + sb.toString());
}
}
@@ -13,36 +13,51 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.owner;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import net.dv8tion.jda.api.entities.channel.ChannelType;
/**
*
* @author John Grosh (jagrosh)
*/
public class EvalCmd extends Command {
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.ownerCommand = true;
this.aliases = bot.getConfig().getAliases(this.name);
this.engine = bot.getConfig().getEvalEngine();
this.guildOnly = false;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
ScriptEngine se = new ScriptEngineManager().getEngineByName("Nashorn");
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());
se.put("guild", event.getGuild());
se.put("channel", event.getChannel());
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())+" ```");
@@ -13,45 +13,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.owner;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
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.playlist.Playlist;
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 Command {
public class PlaylistCmd extends OwnerCommand
{
private final Bot bot;
public PlaylistCmd(Bot bot)
{
this.bot = bot;
this.category = bot.OWNER;
this.ownerCommand = true;
this.guildOnly = false;
this.name = "playlist";
this.arguments = "<append|delete|make|setdefault>";
this.help = "playlist management";
this.children = new Command[]{
this.aliases = bot.getConfig().getAliases(this.name);
this.children = new OwnerCommand[]{
new ListCmd(),
new AppendlistCmd(),
new DeletelistCmd(),
new MakelistCmd(),
new DefaultlistCmd()
new DefaultlistCmd(bot)
};
}
@Override
public void execute(CommandEvent event) {
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())
@@ -59,27 +57,31 @@ public class PlaylistCmd extends Command {
event.reply(builder.toString());
}
public class MakelistCmd extends Command {
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.category = bot.OWNER;
this.ownerCommand = true;
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event) {
protected void execute(CommandEvent event)
{
String pname = event.getArgs().replaceAll("\\s+", "_");
if(Playlist.loadPlaylist(pname)==null)
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
{
Files.createFile(Paths.get("Playlists"+File.separator+pname+".txt"));
bot.getPlaylistLoader().createPlaylist(pname);
event.reply(event.getClient().getSuccess()+" Successfully created playlist `"+pname+"`!");
}
catch(IOException e)
@@ -92,8 +94,8 @@ public class PlaylistCmd extends Command {
}
}
public class DeletelistCmd extends Command {
public class DeletelistCmd extends OwnerCommand
{
public DeletelistCmd()
{
this.name = "delete";
@@ -101,20 +103,19 @@ public class PlaylistCmd extends Command {
this.help = "deletes an existing playlist";
this.arguments = "<name>";
this.guildOnly = false;
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
protected void execute(CommandEvent event)
{
String pname = event.getArgs().replaceAll("\\s+", "_");
if(Playlist.loadPlaylist(pname)==null)
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
event.reply(event.getClient().getError()+" Playlist `"+pname+"` doesn't exist!");
else
{
try
{
Files.delete(Paths.get("Playlists"+File.separator+pname+".txt"));
bot.getPlaylistLoader().deletePlaylist(pname);
event.reply(event.getClient().getSuccess()+" Successfully deleted playlist `"+pname+"`!");
}
catch(IOException e)
@@ -125,8 +126,8 @@ public class PlaylistCmd extends Command {
}
}
public class AppendlistCmd extends Command {
public class AppendlistCmd extends OwnerCommand
{
public AppendlistCmd()
{
this.name = "append";
@@ -134,12 +135,11 @@ public class PlaylistCmd extends Command {
this.help = "appends songs to an existing playlist";
this.arguments = "<name> <URL> | <URL> | ...";
this.guildOnly = false;
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
protected void execute(CommandEvent event)
{
String[] parts = event.getArgs().split("\\s+", 2);
if(parts.length<2)
{
@@ -147,7 +147,7 @@ public class PlaylistCmd extends Command {
return;
}
String pname = parts[0];
Playlist playlist = Playlist.loadPlaylist(pname);
Playlist playlist = bot.getPlaylistLoader().getPlaylist(pname);
if(playlist==null)
event.reply(event.getClient().getError()+" Playlist `"+pname+"` doesn't exist!");
else
@@ -164,8 +164,8 @@ public class PlaylistCmd extends Command {
}
try
{
Files.write(Paths.get("Playlists"+File.separator+pname+".txt"), builder.toString().trim().getBytes());
event.reply(event.getClient().getSuccess()+" Successfully added "+urls.length+" songs to playlist `"+pname+"`!");
bot.getPlaylistLoader().writePlaylist(pname, builder.toString());
event.reply(event.getClient().getSuccess()+" Successfully added "+urls.length+" items to playlist `"+pname+"`!");
}
catch(IOException e)
{
@@ -175,66 +175,39 @@ public class PlaylistCmd extends Command {
}
}
public class DefaultlistCmd extends Command {
public DefaultlistCmd()
public class DefaultlistCmd extends AutoplaylistCmd
{
public DefaultlistCmd(Bot bot)
{
super(bot);
this.name = "setdefault";
this.aliases = new String[]{"default"};
this.help = "sets the default playlist for the server";
this.arguments = "<playlistname|NONE>";
this.guildOnly = true;
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name or NONE");
}
if(event.getArgs().equalsIgnoreCase("none"))
{
bot.setDefaultPlaylist(event.getGuild(), null);
event.reply(event.getClient().getSuccess()+" Cleared the default playlist for **"+event.getGuild().getName()+"**");
return;
}
String pname = event.getArgs().replaceAll("\\s+", "_");
if(Playlist.loadPlaylist(pname)==null)
{
event.reply(event.getClient().getError()+" Could not find `"+pname+".txt`!");
}
else
{
bot.setDefaultPlaylist(event.getGuild(), pname);
event.reply(event.getClient().getSuccess()+" The default playlist for **"+event.getGuild().getName()+"** is now `"+pname+"`");
}
}
}
public class ListCmd extends Command {
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;
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
if(!Playlist.folderExists())
Playlist.createFolder();
if(!Playlist.folderExists())
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 = Playlist.getPlaylists();
List<String> list = bot.getPlaylistLoader().getPlaylistNames();
if(list==null)
event.reply(event.getClient().getError()+" Failed to load available playlists!");
else if(list.isEmpty())
@@ -13,34 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.owner;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
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.utils.OtherUtil;
import net.dv8tion.jda.core.entities.Icon;
import net.dv8tion.jda.api.entities.Icon;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetavatarCmd extends Command {
public class SetavatarCmd extends OwnerCommand
{
public SetavatarCmd(Bot bot)
{
this.name = "setavatar";
this.help = "sets the avatar of the bot";
this.arguments = "<url>";
this.ownerCommand = true;
this.category = bot.OWNER;
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event) {
protected void execute(CommandEvent event)
{
String url;
if(event.getArgs().isEmpty())
if(!event.getMessage().getAttachments().isEmpty() && event.getMessage().getAttachments().get(0).isImage())
@@ -65,5 +65,4 @@ public class SetavatarCmd extends Command {
}
}
}
}
@@ -13,39 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import net.dv8tion.jda.core.entities.Game;
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 Command
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.ownerCommand = true;
this.category = bot.OWNER;
this.children = new Command[]{
new SetlistenCmd(bot),
new SetstreamCmd(bot),
new SetwatchCmd(bot)
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) {
protected void execute(CommandEvent event)
{
String title = event.getArgs().toLowerCase().startsWith("playing") ? event.getArgs().substring(7).trim() : event.getArgs();
try
{
event.getJDA().getPresence().setGame(title.isEmpty() ? null : Game.playing(title));
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+"`"));
}
@@ -55,16 +56,15 @@ public class SetgameCmd extends Command
}
}
private class SetstreamCmd extends Command
private class SetstreamCmd extends OwnerCommand
{
private SetstreamCmd(Bot bot)
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.ownerCommand = true;
this.category = bot.OWNER;
this.guildOnly = false;
}
@Override
@@ -78,7 +78,7 @@ public class SetgameCmd extends Command
}
try
{
event.getJDA().getPresence().setGame(Game.streaming(parts[1], "https://twitch.tv/"+parts[0]));
event.getJDA().getPresence().setActivity(Activity.streaming(parts[1], "https://twitch.tv/"+parts[0]));
event.replySuccess("**"+event.getSelfUser().getName()
+"** is now streaming `"+parts[1]+"`");
}
@@ -89,16 +89,15 @@ public class SetgameCmd extends Command
}
}
private class SetlistenCmd extends Command
private class SetlistenCmd extends OwnerCommand
{
private SetlistenCmd(Bot bot)
private SetlistenCmd()
{
this.name = "listen";
this.aliases = new String[]{"listening"};
this.help = "sets the game the bot is listening to";
this.arguments = "<title>";
this.ownerCommand = true;
this.category = bot.OWNER;
this.guildOnly = false;
}
@Override
@@ -112,7 +111,7 @@ public class SetgameCmd extends Command
String title = event.getArgs().toLowerCase().startsWith("to") ? event.getArgs().substring(2).trim() : event.getArgs();
try
{
event.getJDA().getPresence().setGame(Game.listening(title));
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!");
@@ -120,16 +119,15 @@ public class SetgameCmd extends Command
}
}
private class SetwatchCmd extends Command
private class SetwatchCmd extends OwnerCommand
{
private SetwatchCmd(Bot bot)
private SetwatchCmd()
{
this.name = "watch";
this.aliases = new String[]{"watching"};
this.help = "sets the game the bot is watching";
this.arguments = "<title>";
this.ownerCommand = true;
this.category = bot.OWNER;
this.guildOnly = false;
}
@Override
@@ -143,7 +141,7 @@ public class SetgameCmd extends Command
String title = event.getArgs();
try
{
event.getJDA().getPresence().setGame(Game.watching(title));
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 com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.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!");
}
}
}
@@ -13,30 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import net.dv8tion.jda.core.OnlineStatus;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import net.dv8tion.jda.api.OnlineStatus;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetstatusCmd extends Command {
public class SetstatusCmd extends OwnerCommand
{
public SetstatusCmd(Bot bot)
{
this.name = "setstatus";
this.help = "sets the status the bot displays";
this.arguments = "<status>";
this.ownerCommand = true;
this.category = bot.OWNER;
this.aliases = bot.getConfig().getAliases(this.name);
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event) {
protected void execute(CommandEvent event)
{
try {
OnlineStatus status = OnlineStatus.fromKey(event.getArgs());
if(status==OnlineStatus.UNKNOWN)
@@ -52,5 +53,4 @@ public class SetstatusCmd extends Command {
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 com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.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;
}
}
@@ -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 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 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,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 com.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 com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.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,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 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() {
}
@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 */
@@ -1,12 +1,22 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
* 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.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
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;
@@ -15,11 +25,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -27,187 +33,71 @@ import java.util.stream.Collectors;
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class Playlist {
public class PlaylistLoader
{
private final BotConfig config;
private final String name;
private final List<String> items;
private List<AudioTrack> tracks;
private List<PlaylistLoadError> errors;
private final boolean shuffle;
private Playlist(String name, List<String> items, boolean shuffle)
public PlaylistLoader(BotConfig config)
{
this.name = name;
this.items = items;
this.shuffle = shuffle;
this.config = config;
}
public void loadTracks(AudioPlayerManager manager, Consumer<AudioTrack> consumer, Runnable callback)
{
if(tracks==null)
{
tracks = new LinkedList<>();
errors = new LinkedList<>();
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() {
@Override
public void trackLoaded(AudioTrack at) {
if(AudioHandler.isTooLong(at))
errors.add(new PlaylistLoadError(index, items.get(index), "This track is longer than the allowed maximum"));
else
{
tracks.add(at);
consumer.accept(at);
}
if(last)
{
if(callback!=null)
callback.run();
}
}
@Override
public void playlistLoaded(AudioPlaylist ap) {
if(ap.isSearchResult())
{
if(AudioHandler.isTooLong(ap.getTracks().get(0)))
errors.add(new PlaylistLoadError(index, items.get(index), "This track is longer than the allowed maximum"));
else
{
tracks.add(ap.getTracks().get(0));
consumer.accept(ap.getTracks().get(0));
}
}
else if(ap.getSelectedTrack()!=null)
{
if(AudioHandler.isTooLong(ap.getSelectedTrack()))
errors.add(new PlaylistLoadError(index, items.get(index), "This track is longer than the allowed maximum"));
else
{
tracks.add(ap.getSelectedTrack());
consumer.accept(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 -> AudioHandler.isTooLong(track));
tracks.addAll(loaded);
loaded.forEach(at -> consumer.accept(at));
}
if(last)
{
if(callback!=null)
callback.run();
}
}
@Override
public void noMatches() {
errors.add(new PlaylistLoadError(index, items.get(index), "No matches found."));
if(last)
{
if(callback!=null)
callback.run();
}
}
@Override
public void loadFailed(FriendlyException fe) {
errors.add(new PlaylistLoadError(index, items.get(index), "Failed to load track: "+fe.getLocalizedMessage()));
if(last)
{
if(callback!=null)
callback.run();
}
}
});
}
}
}
public String getName()
{
return name;
}
public List<String> getItems()
{
return items;
}
public List<AudioTrack> getTracks()
{
return tracks;
}
public void shuffleTracks()
{
if(tracks!=null)
{
for(int first =0; first<tracks.size(); first++)
{
int second = (int)(Math.random()*tracks.size());
AudioTrack tmp = tracks.get(first);
tracks.set(first, tracks.get(second));
tracks.set(second, tmp);
}
}
}
public List<PlaylistLoadError> getErrors()
{
return errors;
}
public static void createFolder()
{
try
{
Files.createDirectory(Paths.get("Playlists"));
} catch (IOException ex)
{}
}
public static boolean folderExists()
{
return Files.exists(Paths.get("Playlists"));
}
public static List<String> getPlaylists()
public List<String> getPlaylistNames()
{
if(folderExists())
{
File folder = new File("Playlists");
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 null;
return Collections.emptyList();
}
}
public static Playlist loadPlaylist(String name)
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(Paths.get("Playlists"+File.separator+name+".txt")).forEach(str -> {
Files.readAllLines(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt")).forEach(str ->
{
String s = str.trim();
if(s.isEmpty())
return;
@@ -221,15 +111,7 @@ public class Playlist {
list.add(s);
});
if(shuffle[0])
{
for(int first =0; first<list.size(); first++)
{
int second = (int)(Math.random()*list.size());
String tmp = list.get(first);
list.set(first, list.get(second));
list.set(second, tmp);
}
}
shuffle(list);
return new Playlist(name, list, shuffle[0]);
}
else
@@ -244,7 +126,146 @@ public class Playlist {
}
}
public class PlaylistLoadError {
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;
@@ -0,0 +1,130 @@
/*
* 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;
}
}
@@ -15,9 +15,7 @@
*/
package com.jagrosh.jmusicbot.queue;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@@ -25,15 +23,21 @@ import java.util.Set;
* @author John Grosh (jagrosh)
* @param <T>
*/
public class FairQueue<T extends Queueable> {
private final List<T> list = new ArrayList<>();
private final Set<Long> set = new HashSet<>();
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())
if(list.get(lastIndex).getIdentifier() == item.getIdentifier())
break;
lastIndex++;
set.clear();
@@ -46,78 +50,5 @@ public class FairQueue<T extends Queueable> {
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 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)
{
for(int i=0; i<number; i++)
list.remove(0);
}
}
@@ -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);
}
@@ -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.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.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,135 @@
/*
* 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.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class SettingsManager implements GuildSettingsManager<Settings>
{
private final static Logger LOG = LoggerFactory.getLogger("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 {
LOG.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) {
LOG.warn("Failed to create new settings file: "+ex);
}
return;
} catch(IOException | JSONException e) {
LOG.warn("Failed to load server settings: "+e);
}
LOG.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){
LOG.warn("Failed to write to file: "+ex);
}
}
}
@@ -1,156 +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 com.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 final static String DISCORD_ID = "\\d{17,20}";
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);
}
else if(query.matches(DISCORD_ID))
{
id = query;
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 vc = guild.getJDA().getVoiceChannelById(id);
if(vc!=null && vc.getGuild().equals(guild))
return Collections.singletonList(vc);
}
else if(query.matches(DISCORD_ID))
{
id = query;
VoiceChannel vc = guild.getJDA().getVoiceChannelById(id);
if(vc!=null && vc.getGuild().equals(guild))
return Collections.singletonList(vc);
}
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((vc) -> {
if(vc.getName().equals(lowerquery))
exact.add(vc);
else if (vc.getName().equalsIgnoreCase(lowerquery) && exact.isEmpty())
wrongcase.add(vc);
else if (vc.getName().toLowerCase().startsWith(lowerquery) && wrongcase.isEmpty())
startswith.add(vc);
else if (vc.getName().toLowerCase().contains(lowerquery) && startswith.isEmpty())
contains.add(vc);
});
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 r = guild.getRoleById(id);
if(r!=null)
return Collections.singletonList(r);
}
else if(query.matches(DISCORD_ID))
{
id = query;
Role r = guild.getRoleById(id);
if(r!=null)
return Collections.singletonList(r);
}
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;
}
}
@@ -15,116 +15,47 @@
*/
package com.jagrosh.jmusicbot.utils;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.jagrosh.jmusicbot.audio.RequestMetadata.UserInfo;
import java.util.List;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack;
import net.dv8tion.jda.core.EmbedBuilder;
import net.dv8tion.jda.core.JDA;
import net.dv8tion.jda.core.MessageBuilder;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.Message;
import net.dv8tion.jda.core.entities.Role;
import net.dv8tion.jda.core.entities.TextChannel;
import net.dv8tion.jda.core.entities.User;
import net.dv8tion.jda.core.entities.VoiceChannel;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class FormatUtil {
public static String formatTime(long duration)
public static String formatUsername(String username, String discrim)
{
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);
}
public static Message nowPlayingMessage(Guild guild, String successEmoji)
{
MessageBuilder mb = new MessageBuilder();
mb.append(successEmoji+" **Now Playing...**");
EmbedBuilder eb = new EmbedBuilder();
AudioHandler ah = (AudioHandler)guild.getAudioManager().getSendingHandler();
eb.setColor(guild.getSelfMember().getColor());
if(ah==null || !ah.isMusicPlaying())
if(discrim == null || discrim.equals("0000"))
{
eb.setTitle("No music playing");
eb.setDescription("\u23F9 "+FormatUtil.progressBar(-1)+" "+FormatUtil.volumeIcon(ah==null?100:ah.getPlayer().getVolume()));
return username;
}
else
{
if(ah.getRequester()!=0)
{
User u = guild.getJDA().getUserById(ah.getRequester());
if(u==null)
eb.setAuthor("Unknown (ID:"+ah.getRequester()+")", null, null);
else
eb.setAuthor(u.getName()+"#"+u.getDiscriminator(), null, u.getEffectiveAvatarUrl());
}
try {
eb.setTitle(ah.getPlayer().getPlayingTrack().getInfo().title, ah.getPlayer().getPlayingTrack().getInfo().uri);
} catch(Exception e) {
eb.setTitle(ah.getPlayer().getPlayingTrack().getInfo().title);
}
if(!AudioHandler.USE_NP_REFRESH && ah.getPlayer().getPlayingTrack() instanceof YoutubeAudioTrack)
eb.setThumbnail("https://img.youtube.com/vi/"+ah.getPlayer().getPlayingTrack().getIdentifier()+"/mqdefault.jpg");
eb.setDescription(FormatUtil.embedFormat(ah));
return username + "#" + discrim;
}
return mb.setEmbed(eb.build()).build();
}
public static String topicFormat(AudioHandler handler, JDA jda)
public static String formatUsername(UserInfo userinfo)
{
if(handler==null)
return "No music playing\n\u23F9 "+progressBar(-1)+" "+volumeIcon(100);
else if (!handler.isMusicPlaying())
return "No music playing\n\u23F9 "+progressBar(-1)+" "+volumeIcon(handler.getPlayer().getVolume());
else
{
long userid = handler.getRequester();
AudioTrack track = handler.getPlayer().getPlayingTrack();
String title = track.getInfo().title;
return "**"+title+"** ["+(userid==0 ? "autoplay" : "<@"+userid+">")+"]"
+ "\n"+(handler.getPlayer().isPaused()?"\u23F8":"\u25B6")+" "
+"["+formatTime(track.getDuration())+"] "
+volumeIcon(handler.getPlayer().getVolume());
}
return formatUsername(userinfo.username, userinfo.discrim);
}
public static String embedFormat(AudioHandler handler)
public static String formatUsername(User user)
{
if(handler==null)
return "No music playing\n\u23F9 "+progressBar(-1)+" "+volumeIcon(100);
else if (!handler.isMusicPlaying())
return "No music playing\n\u23F9 "+progressBar(-1)+" "+volumeIcon(handler.getPlayer().getVolume());
else
{
AudioTrack track = handler.getPlayer().getPlayingTrack();
double progress = (double)track.getPosition()/track.getDuration();
return (handler.getPlayer().isPaused()?"\u23F8":"\u25B6")
+" "+progressBar(progress)
+" `["+formatTime(track.getPosition()) + "/" + formatTime(track.getDuration()) +"]` "
+volumeIcon(handler.getPlayer().getVolume());
}
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";
str+="\uD83D\uDD18"; // 🔘
else
str+="▬";
return str;
@@ -133,12 +64,12 @@ public class FormatUtil {
public static String volumeIcon(int volume)
{
if(volume == 0)
return "\uD83D\uDD07";
return "\uD83D\uDD07"; // 🔇
if(volume < 30)
return "\uD83D\uDD08";
return "\uD83D\uDD08"; // 🔈
if(volume < 70)
return "\uD83D\uDD09";
return "\uD83D\uDD0A";
return "\uD83D\uDD09"; // 🔉
return "\uD83D\uDD0A"; // 🔊
}
public static String listOfTChannels(List<TextChannel> list, String query)
@@ -155,7 +86,7 @@ public class FormatUtil {
{
String out = " Multiple voice 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()+")";
out+="\n - "+list.get(i).getAsMention()+" (ID:"+list.get(i).getId()+")";
if(list.size()>6)
out+="\n**And "+(list.size()-6)+" more...**";
return out;
@@ -163,7 +94,7 @@ public class FormatUtil {
public static String listOfRoles(List<Role> list, String query)
{
String out = " Multiple text channels found matching \""+query+"\":";
String out = " Multiple roles 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)
@@ -173,6 +104,9 @@ public class FormatUtil {
public static String filter(String input)
{
return input.replace("@everyone", "@\u0435veryone").replace("@here", "@h\u0435re").trim();
return input.replace("\u202E","")
.replace("@everyone", "@\u0435veryone") // cyrillic letter e
.replace("@here", "@h\u0435re") // cyrillic letter e
.trim();
}
}
@@ -1,42 +1,237 @@
/*
* 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.utils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class OtherUtil {
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 e) {
}
return null;
}
}
/*
* 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/chrisb09/MusicBot/releases to get the latest release."
+ "If you are using docker (or docker-compose), please pull the new image with:"
+ "Docker: docker pull chrisb09/jmusicbot"
+ "Docker-compose: docker-compose pull"+
" and don't forget to restart the container.";
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/chrisb09/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 ."
+ "You may also need to disable all Installation Contexts at https://discord.com/developers/applications/"
+ jda.getSelfUser().getId() + "/installation .";
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;
}
}
}
@@ -0,0 +1,193 @@
package com.jagrosh.jmusicbot.utils;
import com.sedmelluq.lava.extensions.youtuberotator.planner.*;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
import dev.lavalink.youtube.clients.Web;
import org.json.JSONObject;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.chromium.ChromiumDriverLogLevel;
import org.openqa.selenium.devtools.NetworkInterceptor;
import org.openqa.selenium.os.ExecutableFinder;
import org.openqa.selenium.remote.http.Contents;
import org.openqa.selenium.remote.http.Filter;
import org.openqa.selenium.remote.http.HttpMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import io.github.bonigarcia.wdm.WebDriverManager;
public class YouTubeUtil {
private final static Logger LOGGER = LoggerFactory.getLogger(YouTubeUtil.class);
public enum RoutingPlanner {
NONE,
ROTATE_ON_BAN,
LOAD_BALANCE,
NANO_SWITCH,
ROTATING_NANO_SWITCH
}
public static IpBlock parseIpBlock(String cidr) {
if (Ipv6Block.isIpv6CidrBlock(cidr))
return new Ipv6Block(cidr);
if (Ipv4Block.isIpv4CidrBlock(cidr))
return new Ipv4Block(cidr);
throw new IllegalArgumentException("Could not parse CIDR " + cidr);
}
public static AbstractRoutePlanner createRouterPlanner(RoutingPlanner routingPlanner, List<IpBlock> ipBlocks) {
switch (routingPlanner) {
case NONE:
return null;
case ROTATE_ON_BAN:
return new RotatingIpRoutePlanner(ipBlocks);
case LOAD_BALANCE:
return new BalancingIpRoutePlanner(ipBlocks);
case NANO_SWITCH:
return new NanoIpRoutePlanner(ipBlocks, true);
case ROTATING_NANO_SWITCH:
return new RotatingNanoIpRoutePlanner(ipBlocks);
default:
throw new IllegalArgumentException("Unknown RoutingPlanner value provided");
}
}
public static void GetPoTokenAndVisitorData(String userChromePath, String userChromeDriverPath, boolean headless)
{
//
// 1. Find a Chrome install & launch the WebDriver
//
WebDriverManager wdm = WebDriverManager.chromedriver();
if (userChromePath != null) {
wdm.browserVersionDetectionCommand(userChromePath + " --version");
}
else if (wdm.getBrowserPath().isEmpty())
{
// try finding a Chromium browser instead (Linux users usually install Chromium instead of Google Chrome)
wdm = WebDriverManager.chromiumdriver();
if (wdm.getBrowserPath().isEmpty()) {
LOGGER.error("Could not obtain a PO token for YouTube playback, because no Chrome browser could be found. Please install Google Chrome or Chromium!");
return;
}
}
if (userChromeDriverPath == null)
// Automatically download & set up ChromeDriver for the Chrome/Chromium version that the user has installed
wdm.setup();
else {
// Find & use the ChromeDriver executable the user provided
String path = new ExecutableFinder().find(userChromeDriverPath);
if (path == null) {
LOGGER.error("Could not obtain a PO token for YouTube playback, because the specified ChromeDriver could not be found. " +
"Please check your config's value of the chromedriverpath, or set it to \"AUTO\" to have JMusicBot automatically download ChromeDriver");
return;
}
LOGGER.info("Using ChromeDriver at {}", path);
System.setProperty("webdriver.chrome.driver", path);
}
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setBinary(userChromePath != null ? userChromePath : wdm.getBrowserPath().get().toString());
if (!headless)
chromeOptions.addArguments("--auto-open-devtools-for-tabs");
else
chromeOptions.addArguments("--headless=new");
ChromeDriverService.Builder chromeDriverBuilder = new ChromeDriverService.Builder();
if (LOGGER.isDebugEnabled())
chromeDriverBuilder.withLogLevel(ChromiumDriverLogLevel.DEBUG);
WebDriver driver = new ChromeDriver(chromeDriverBuilder.build(), chromeOptions);
//
// 2. Setup a network interceptor to intercept the player API request & obtain po token and visitor data
//
// as the network interceptor below runs on a different thread, we'll have it notify us on this/current thread
// whether retrieving the data failed (completes with the Exception) or succeeded (completes with null)
CompletableFuture<Exception> interceptorFuture = new CompletableFuture<>();
NetworkInterceptor interceptor = new NetworkInterceptor(driver,
(Filter) next -> req -> {
if (req.getMethod() != HttpMethod.POST || !req.getUri().contains("/youtubei/v1/player"))
return next.execute(req);
try {
String rawBody = Contents.string(req);
JSONObject body = new JSONObject(rawBody);
String visitorData = body
.getJSONObject("context")
.getJSONObject("client")
.getString("visitorData");
String poToken = body
.getJSONObject("serviceIntegrityDimensions")
.getString("poToken");
LOGGER.info("Successfully retrieved PO token & visitor data for YouTube playback.");
LOGGER.debug("PO token: {}", poToken);
LOGGER.debug("Visitor Data: {}", visitorData);
if (poToken.length() < 160)
LOGGER.warn("There is a high chance that the retrieved PO token will not work. Try on a different network.");
Web.setPoTokenAndVisitorData(poToken, visitorData);
interceptorFuture.complete(null);
} catch (Exception e) {
interceptorFuture.complete(e);
}
return next.execute(req);
});
//
// 3. Navigate to YouTube & start the player
//
try {
driver.get("https://www.youtube.com/embed/aqz-KE-bpKQ");
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(15));
WebElement moviePlayer = driver.findElement(By.cssSelector("#movie_player"));
moviePlayer.click();
try {
// Wait for the network interceptor to tell us if it got the data
Exception result = interceptorFuture.get(20, TimeUnit.SECONDS);
if (result != null)
throw new Exception("Failed to read YouTube player request body", result);
} catch (TimeoutException e) {
throw new Exception("Timed out waiting for YouTube player request");
}
} catch (Exception e) {
LOGGER.error("Failed to obtain PO token for YouTube playback: {}", e.getMessage());
LOGGER.debug("Exception:", e);
} finally {
interceptor.close();
// Only quit the browser when not in headless mode. Helps with troubleshooting.
if (headless)
driver.quit();
}
}
}
@@ -0,0 +1,91 @@
package com.jagrosh.jmusicbot.utils;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.spi.FilterReply;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import java.nio.file.Files;
/**
* A logback turbo filter, used retrieve the YouTube OAuth2 refresh token that gets logged once authorized with YouTube.
*
* @author Michaili K. <git@michaili.dev>
*/
public class YoutubeOauth2TokenHandler extends TurboFilter {
public final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(YoutubeOauth2TokenHandler.class);
private Data data;
public void init()
{
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.addTurboFilter(this);
}
public Data getData()
{
return data;
}
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t)
{
if (!logger.getName().equals("dev.lavalink.youtube.http.YoutubeOauth2Handler"))
return FilterReply.NEUTRAL;
if (format.equals("OAUTH INTEGRATION: To give youtube-source access to your account, go to {} and enter code {}"))
{
this.data = new Data((String) params[0], (String) params[1]);
return FilterReply.NEUTRAL;
}
if (format.equals("OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. ({})"))
{
LOGGER.info(
"Authorization successful & retrieved token! Storing the token in {}",
OtherUtil.getPath("youtubetoken.txt").toAbsolutePath()
);
try
{
Files.write(OtherUtil.getPath("youtubetoken.txt"), params[0].toString().getBytes());
}
catch (Exception e)
{
LOGGER.error(
"Failed to write the YouTube OAuth2 refresh token to storage! You will need to authorize again on the next reboot",
e
);
}
return FilterReply.DENY;
}
return FilterReply.NEUTRAL;
}
public static class Data
{
private final String authorisationUrl;
private final String code;
private Data(String authorisationUrl, String code)
{
this.authorisationUrl = authorisationUrl;
this.code = code;
}
public String getCode()
{
return code;
}
public String getAuthorisationUrl()
{
return authorisationUrl;
}
}
}
Binary file not shown.
Binary file not shown.
+281
View File
@@ -0,0 +1,281 @@
/// START OF JMUSICBOT CONFIG ///
/////////////////////////////////////////////////////////
// Config for the JMusicBot //
/////////////////////////////////////////////////////////
// Any line starting with // is ignored //
// You MUST set the token and owner //
// All other items have defaults if you don't set them //
// Open in Notepad++ for best results //
/////////////////////////////////////////////////////////
// This sets the token for the bot to log in with
// This MUST be a bot token (user tokens will not work)
// If you don't know how to get a bot token, please see the guide here:
// https://github.com/jagrosh/MusicBot/wiki/Getting-a-Bot-Token
token = BOT_TOKEN_HERE
// This sets the owner of the bot
// This needs to be the owner's ID (a 17-18 digit number)
// https://github.com/jagrosh/MusicBot/wiki/Finding-Your-User-ID
owner = 0 // OWNER ID
// This sets the prefix for the bot
// The prefix is used to control the commands
// If you use !!, the play command will be !!play
// If you do not set this, the prefix will be a mention of the bot (@Botname play)
prefix = "@mention"
// If you set this, it modifies the default game of the bot
// Set this to NONE to have no game
// Set this to DEFAULT to use the default game
// You can make the game "Playing X", "Listening to X", or "Watching X"
// where X is the title. If you don't include an action, it will use the
// default of "Playing"
game = "DEFAULT"
// If you set this, it will modify the default status of bot
// Valid values: ONLINE IDLE DND INVISIBLE
status = ONLINE
// If you set this to true, the bot will use a Google account to play YouTube tracks.
// This is only needed if you are experiencing issues with YouTube playback.
// Once enabled, instructions for login will be written to the console and DMs.
// DO NOT use your main Google account! Use an alternative account.
youtubeoauth2=false
// If you set this to true, the bot will list the title of the song it is currently playing in its
// "Playing" status. Note that this will ONLY work if the bot is playing music on ONE guild;
// if the bot is playing on multiple guilds, this will not work.
songinstatus=false
// If you set this, the bot will also use this prefix in addition to
// the one provided above
altprefix = "NONE"
// If you set these, it will change the various emojis
success = "🎶"
warning = "💡"
error = "🚫"
loading = "⌚"
searching = "🔎"
// If you set this, you change the word used to view the help.
// For example, if you set the prefix to !! and the help to cmds, you would type
// !!cmds to see the help text
help = help
// If you set this, the "nowplaying" command will show youtube thumbnails
// Note: If you set this to true, the nowplaying boxes will NOT refresh
// This is because refreshing the boxes causes the image to be reloaded
// every time it refreshes.
npimages = false
// If you set this, the bot will not leave a voice channel after it finishes a queue.
// Keep in mind that being connected to a voice channel uses additional bandwith,
// so this option is not recommended if bandwidth is a concern.
stayinchannel = false
// This sets the maximum amount of seconds any track loaded can be. If not set or set
// to any number less than or equal to zero, there is no maximum time length. This time
// restriction applies to songs loaded from any source.
maxtime = 0
// This sets the maximum number of pages of songs that can be loaded from a YouTube
// playlist. Each page can contain up to 100 tracks. Playing a playlist with more
// pages than the maximum will stop loading after the provided number of pages.
// For example, if the max was set to 15 and a playlist contained 1850 tracks,
// only the first 1500 tracks (15 pages) would be loaded. By default, this is
// set to 10 pages (1000 tracks).
maxytplaylistpages = 10
// This sets the ratio of users that must vote to skip the currently playing song.
// Guild owners can define their own skip ratios, but this will be used if a guild
// has not defined their own skip ratio.
skipratio = 0.55
// This sets the amount of seconds the bot will stay alone on a voice channel until it
// automatically leaves the voice channel and clears the queue. If not set or set
// to any number less than or equal to zero, the bot won't leave when alone.
alonetimeuntilstop = 0
// This sets an alternative folder to be used as the Playlists folder
// This can be a relative or absolute path
playlistsfolder = "Playlists"
// By default, the bot will DM the owner if the bot is running and a new version of the bot
// becomes available. Set this to false to disable this feature.
updatealerts=true
// Changing this changes the lyrics provider
// Currently available providers: "A-Z Lyrics", "Genius", "MusicMatch", "LyricsFreak"
// At the time of writing, I would recommend sticking with A-Z Lyrics or MusicMatch,
// as Genius tends to have a lot of non-song results and you might get something
// completely unrelated to what you want.
// If you are interested in contributing a provider, please see
// https://github.com/jagrosh/JLyrics
lyrics.default = "A-Z Lyrics"
// These settings allow you to configure custom aliases for all commands.
// Multiple aliases may be given, separated by commas.
//
// Example 1: Giving command "play" the alias "p":
// play = [ p ]
//
// Example 2: Giving command "search" the aliases "yts" and "find":
// search = [ yts, find ]
aliases {
// General commands
settings = [ status ]
// Music commands
lyrics = []
nowplaying = [ np, current ]
play = []
playlists = [ pls ]
queue = [ list ]
remove = [ delete ]
scsearch = []
search = [ ytsearch ]
shuffle = []
skip = [ voteskip ]
// Admin commands
prefix = [ setprefix ]
setdj = []
setskip = [ setskippercent, skippercent, setskipratio ]
settc = []
setvc = []
// DJ Commands
forceremove = [ forcedelete, modremove, moddelete, modelete ]
forceskip = [ modskip ]
movetrack = [ move ]
pause = []
playnext = []
queuetype = []
repeat = []
skipto = [ jumpto ]
stop = [ leave ]
volume = [ vol ]
}
// This sets the logging verbosity.
// Available levels: off, error, warn, info, debug, trace, all
//
// It is recommended to leave this at info. Debug log levels might help with troubleshooting,
// but can contain sensitive data.
loglevel = info
// This sets the executable path of where a Chrome or Chromium installation is located JMusicBot can use.
// JMusicBot may launch a web browser to get around anti-bot checks.
// Set this to "AUTO" to make JMusicBot attempt to find a Chrome/Chromium installation itself.
chromepath = "AUTO"
// This sets the executable path of where a ChromeDriver installation is located JMusicBot can use.
// Set this to "AUTO" to make JMusicBot automatically download the ChromeDriver version
// which matches the installed Chrome version.
chromedriverpath = "AUTO"
// This sets whether JMusicBot should launch Chrome/Chromium instances without a GUI.
// It is recommended to leave this set to true.
// If set to false, JMusicBot will not close the browser automatically (even when finished)
// and will automatically open DevTools for troubleshooting purposes.
chromeheadless = true
// This configures the routing planner to use for IP rotation on YouTube.
// When set, you need to also set an IP block below.
// The following routing strategies are available:
// NONE = Disable
// ROTATE_ON_BAN = Switch IP when currently used address gets banned.
// LOAD_BALANCE = Selects random IP for each track playback.
// NANO_SWITCH = Selects IPv6 address based on current nanosecond. Recommended for one /64 IPv6 blocks.
// ROTATING_NANO_SWITCH = Same as above, but uses the next /64 IPv6 block in the list in case of a ban.
ytroutingplanner = NONE
// The IPv4 and IPv6 blocks to use when using the above routing planner.
// This does nothing when ytroutingplanner is set to none.
// The list accepts a list of IPv4 or IPv6 CIDR blocks (not a mix of them however)
// IPv4 example: [ "192.0.2.183", "198.51.100.153/29" ]
// IPv6 example: [ "2a02:1234:5678:9abc::/64" ]
ytipblocks = [ ]
// Transforms are used to modify specific play inputs and convert them to different kinds of inputs
// These are quite complicated to use, and have limited use-cases, but in theory allow for rough
// whitelists or blacklists, roundabout loading from some sources, and customization of how things are
// requested.
//
// These are NOT EASY to set up, so if you want to use these, you'll need to look through the code
// for how they work and what fields are needed. Also, it's possible this feature might get entirely
// removed in the future if I find a better way to do this.
transforms = {}
// If you set this to true, it will enable the eval command for the bot owner. This command
// allows the bot owner to run arbitrary code from the bot's account.
//
// WARNING:
// This command can be extremely dangerous. If you don't know what you're doing, you could
// cause horrific problems on your Discord server or on whatever computer this bot is running
// on. Never run this command unless you are completely positive what you are running.
//
// DO NOT ENABLE THIS IF YOU DON'T KNOW WHAT THIS DOES OR HOW TO USE IT
// IF SOMEONE ASKS YOU TO ENABLE THIS, THERE IS AN 11/10 CHANCE THEY ARE TRYING TO SCAM YOU
eval=false
evalengine="Nashorn"
/// END OF JMUSICBOT CONFIG ///
@@ -0,0 +1,64 @@
/*
* 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 com.jagrosh.jmusicbot.queue.FairQueue;
import com.jagrosh.jmusicbot.queue.Queueable;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class FairQueueTest
{
@Test
public void differentIdentifierSize()
{
FairQueue<Q> queue = new FairQueue<>(null);
int size = 100;
for(int i=0; i<size; i++)
queue.add(new Q(i));
assertEquals(queue.size(), size);
}
@Test
public void sameIdentifierSize()
{
FairQueue<Q> queue = new FairQueue<>(null);
int size = 100;
for(int i=0; i<size; i++)
queue.add(new Q(0));
assertEquals(queue.size(), size);
}
private class Q implements Queueable
{
private final long identifier;
private Q(long identifier)
{
this.identifier = identifier;
}
@Override
public long getIdentifier()
{
return identifier;
}
}
}
@@ -0,0 +1,119 @@
/*
* 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;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Whew., Inc.
*/
public class TimeUtilTest
{
@Test
public void singleDigit()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("5");
assertNotNull(seek);
assertEquals(5000, seek.milliseconds);
}
@Test
public void multipleDigits()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("99:9:999");
assertNotNull(seek);
assertEquals(357939000, seek.milliseconds);
seek = TimeUtil.parseTime("99h9m999s");
assertNotNull(seek);
assertEquals(357939000, seek.milliseconds);
}
@Test
public void decimalDigits()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("99.5:9.0:999.777");
assertNotNull(seek);
assertEquals(359739777, seek.milliseconds);
}
@Test
public void seeking()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("5");
assertNotNull(seek);
assertFalse(seek.relative);
assertEquals(5000, seek.milliseconds);
}
@Test
public void relativeSeekingForward()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("+5");
assertNotNull(seek);
assertTrue(seek.relative);
assertEquals(5000, seek.milliseconds);
}
@Test
public void relativeSeekingBackward()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("-5");
assertNotNull(seek);
assertTrue(seek.relative);
assertEquals(-5000, seek.milliseconds);
}
@Test
public void parseTimeArgumentLength()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("");
assertNull(seek);
}
@Test
public void timestampTotalUnits()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("1:1:1:1");
assertNull(seek);
seek = TimeUtil.parseTime("1h2m3m4s5s");
assertNotNull(seek);
assertEquals(3909000, seek.milliseconds);
}
@Test
public void relativeSymbol()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("+-1:-+1:+-1");
assertNull(seek);
}
@Test
public void timestampNumberFormat()
{
TimeUtil.SeekTime seek = TimeUtil.parseTime("1:1:a");
assertNull(seek);
seek = TimeUtil.parseTime("1a2s");
assertNotNull(seek);
assertEquals(3000, seek.milliseconds);
}
}