Compare commits

...
81 Commits
Author SHA1 Message Date
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
John Grosh c0d40b2594 Merge origin/master 2019-06-10 18:35:49 -04:00
John Grosh 327cfe7d21 Update README.md 2019-06-07 14:48:26 -04:00
John Grosh b4e83fd3f0 Merge origin/master
also minor edits
Conflicts:
	pom.xml
2019-05-29 22:26:51 -04:00
John Grosh c902c1eace Merge pull request #225 from Sanduhr32/patch-1
Fixing news/store channels issue
2019-05-22 14:05:55 -04:00
M R S E 39a787c3c9 Dont go to insane on JDAs version
Also bump version
2019-05-05 16:55:18 +02:00
M R S E cdc20ce500 Fixing news/store channels issue 2019-05-05 16:52:51 +02:00
John Grosh 7dcd7badb3 Merge pull request #218 from SlapGaming/pullreqs
Move track command
2019-04-09 15:34:59 -04:00
Rick Fontein 1a0a73ac7c PR feedback pass 2 2019-04-09 20:56:16 +02:00
Rick Fontein eb1fb776a5 PR feedback pass 2019-04-08 20:38:59 +02:00
Rick Fontein 82349d380b Move track command
Adds support for moving a track in queue.
2019-04-07 00:12:21 +02:00
John Grosh 3c5deca55e improved config flow and autowrite full config 2019-03-16 16:29:09 -05:00
John Grosh e1b9e0165a Move pull req template 2019-01-30 11:58:55 -05:00
John Grosh 2e701d0d23 Move code of conduct 2019-01-30 11:58:21 -05:00
John Grosh 259e3d716b Update README.md 2019-01-13 15:53:19 -05:00
John Grosh 43398449c7 more cleanup and update readme 2019-01-13 15:51:26 -05:00
John Grosh e7f00fe088 Merge origin/master 2019-01-13 15:06:49 -05:00
John Grosh 1a9364acae code cleanup and deps update 2019-01-13 15:06:26 -05:00
John Grosh 19234998f1 Merge pull request #193 from jagrosh/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2018-12-07 10:46:41 -05:00
John Grosh 31037775d1 Create CODE_OF_CONDUCT.md 2018-12-07 10:42:34 -05:00
John Grosh e0b1bb0d06 Create PULL_REQUEST_TEMPLATE.md 2018-12-07 10:34:42 -05:00
John Grosh 1f810aa36f Delete ISSUE_TEMPLATE.md 2018-12-07 10:22:20 -05:00
John Grosh e7a8cec0b4 Update issue templates 2018-12-07 10:22:04 -05:00
John Grosh ae07ac2598 lyrics second pass 2018-12-05 02:16:39 -05:00
John Grosh d75098fdd9 Update config.yml 2018-12-05 00:47:25 -05:00
John Grosh ac87cc13d7 fix pom 2018-12-05 00:43:13 -05:00
John Grosh 34bffe0148 first pass of lyrics command
version bump
more code cleanup
2018-12-05 00:30:28 -05:00
John Grosh 15f3cf07a8 Merge origin/master 2018-12-04 23:53:24 -05:00
John Grosh c0f0d45138 code cleanup 2018-12-04 23:37:45 -05:00
John Grosh 2eddaccb86 Update README.md 2018-11-27 15:04:21 -05:00
John Grosh 87b2f4f93a Update README.md 2018-11-27 15:02:41 -05:00
John Grosh 88970314ad Update README.md 2018-11-27 10:01:04 -05:00
John Grosh 0de4e2afcc Update .appveyor.yml 2018-11-27 09:58:09 -05:00
John Grosh 2671ac18c8 Update .appveyor.yml 2018-11-27 09:50:50 -05:00
John Grosh 68a992c79e Create .appveyor.yml 2018-11-27 09:40:46 -05:00
John Grosh 9bf233ec79 Update README.md 2018-11-27 09:25:50 -05:00
John Grosh b41dcded98 Update README.md 2018-11-27 09:25:30 -05:00
John Grosh b6239ed608 Update .travis.yml 2018-11-27 09:25:14 -05:00
John Grosh 9dfe613921 Update .travis.yml 2018-11-27 09:21:10 -05:00
John Grosh 865119e2ae Create .travis.settings.xml 2018-11-27 09:20:53 -05:00
John Grosh c616f9c92a Create .travis.yml 2018-11-27 09:01:09 -05:00
John Grosh 2737a85a61 Create config.yml 2018-11-27 08:41:38 -05:00
John Grosh c88979a6dd fixed npe during autoplaylists
added playnext command (simple version)
setting dj role to everyone works now
fixed massively wide joptionpanes
version number bump
2018-11-21 23:34:18 -05:00
John Grosh a6f7c9b3ef dependency update 2018-11-21 19:07:04 -05:00
John Grosh 2aac68b412 formatting 2018-11-21 18:43:27 -05:00
John Grosh e1b07868fd update alerts to owner DMs 2018-11-21 17:46:51 -05:00
John Grosh aaec886b81 massive organization overhaul
dependency updates
config overhaul
2018-11-21 05:31:03 -05:00
John Grosh a7a4b55011 allow owner commands (set avatar, game, and name) to be used in DM 2018-04-02 17:16:18 -05:00
John Grosh bcb7e03aac get version number programmatically and compare to latest version 2018-03-19 17:09:50 -05:00
John Grosh c49a559e34 play command accepts file uploads 2018-03-15 18:09:29 -05:00
John Grosh af98eb5e01 more minor updates 2018-03-13 05:46:31 -05:00
John Grosh e47c3efc58 dependency updates 2018-02-16 21:14:58 -05:00
John Grosh 058c3e18d2 added issue template 2018-02-07 17:00:36 -05:00
John Grosh 38ae19cbec little bugfixes 2018-02-07 16:33:26 -05:00
John Grosh 2eeffb374f dependency updates
more emojis
2018-02-07 11:07:37 -05:00
John Grosh e478fa43c8 perm and id fix 2017-12-18 11:48:30 -05:00
John Grosh 1093f92d67 Merge origin/master 2017-12-18 10:33:26 -05:00
John Grosh 6148dc6a89 updated deps
updated logger
added repeat mode
added loading playlist from video
added more game types
stop command improved
2017-12-18 10:33:08 -05:00
John Grosh a20ade9016 Update README.md 2017-10-25 18:26:33 -04:00
John Grosh fbbb7bd3a7 Update README.md 2017-10-18 23:54:55 -04:00
John Grosh 0c44160023 Update README.md 2017-10-12 23:41:16 -04:00
John Grosh 13427cdab7 Update README.md 2017-10-12 23:39:15 -04:00
John Grosh d0ffc0308c Update README.md 2017-10-12 23:35:35 -04:00
John Grosh fe62e03367 updated lib versions
fixed config loading format
filtered more output
longer yt playlists supported
2017-09-10 23:38:59 -04:00
John Grosh 5deb41c651 updated libs
fixed internal bugs
lots of other things (see 0.0.9 release)
2017-09-02 16:39:08 -04:00
John Grosh 6b454c299d fixed NONE for game 2017-08-18 16:42:32 -04:00
John Grosh dddf4c8e38 added altprefix, songinstatus, and setstatus features
slight change to internals to reduce some bugs
2017-08-18 15:43:20 -04:00
John Grosh 0912de74e9 Merge origin/master 2017-07-04 02:58:24 -04:00
John Grosh 3752e5b9e0 update pom 2017-07-04 02:57:56 -04:00
John Grosh ae62d5771f Update README.md 2017-07-04 02:32:26 -04:00
John Grosh 434749a250 fixed playlist shuffling 2017-07-04 02:19:08 -04:00
John Grosh cb9644d694 playlist load faster and queue command shows np 2017-07-04 01:41:42 -04:00
John Grosh 8f7e53a37c updated config for staying in voice 2017-06-22 02:47:54 -04:00
John Grosh b158e70495 added pause command
use embed for nowplaying
2017-06-22 01:45:11 -04:00
John Grosh ebed4f972a shuffle playlists when a single commented line contains 'shuffle'
fixed role bug
2017-06-22 00:54:51 -04:00
John Grosh f8b15d9591 updated readme
bumped license year
removed unnecessary import
2017-05-21 10:17:44 -04:00
John Grosh ab1e86d4ca Merge origin/master 2017-05-18 19:59:57 -04:00
John Grosh 9da577aa60 updated versions
local file support
less topic spam for the audit log conscious
2017-05-18 19:59:25 -04:00
John Grosh d7f690bd9a Update README.md 2017-04-26 12:42:39 -04:00
75 changed files with 5123 additions and 3022 deletions

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# 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: circleci/openjdk:8u171-jdk
# 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
+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
+30
View File
@@ -0,0 +1,30 @@
---
name: Bug Report
about: Report a bug or unexpected behavior
title: "[Bug Report] Title Here"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run with '...'
2. Do a command '....'
3. Some other step '....'
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version Info (please complete the following information):**
- Operating System: [e.g. Windows 64bit]
- JMusicBot Version: [e.g. 0.2.1]
**Additional context**
Add any other context about the problem here.
+27
View File
@@ -0,0 +1,27 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request] Title Here"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe the basic flow/steps of using this feature**
How this feature would be used:
1. Set something '...'
2. Run some command '...'
3. Other steps or related info '...'
4. ...
**Additional context**
Add any other context or screenshots about the feature request here.
**Have you confirmed that this idea is not already described here?**
https://github.com/jagrosh/MusicBot/projects/1
+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 #...
+6
View File
@@ -0,0 +1,6 @@
/nbproject/
/target/
/Playlists/
*.json
*.txt
nb*.xml
+201 -201
View File
@@ -1,201 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 John Grosh
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+35 -16
View File
@@ -1,22 +1,41 @@
<img align="right" src="https://i.imgur.com/zrE80HY.png" height="200" width="200">
# JMusicBot
A Discord music bot with a clean interface, and that is easy to set up and run yourself
# Features
* Easy to run (just make sure Java is installed, and run!)
* Fast loading of songs
* No external keys needed (besides a Discord Bot token)
* Smooth playback
* Server-specific setup for the "DJ" role that can moderate the music
* Clean and beautiful menus
* Channel-topic playback bar
* Supports many sites, including Youtube, Soundcloud, and more
* Playlist support (both web/youtube, and local)
[![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://img.shields.io/circleci/project/github/jagrosh/MusicBot/master.svg)](https://circleci.com/gh/jagrosh/MusicBot)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/gdu6nyte5psj6xfk/branch/master?svg=true)](https://ci.appveyor.com/project/jagrosh/musicbot/branch/master)
[![CodeFactor](https://www.codefactor.io/repository/github/jagrosh/musicbot/badge)](https://www.codefactor.io/repository/github/jagrosh/musicbot)
# Setup
A cross-platform 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)
## 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)
## Example
![Loading Example...](https://i.imgur.com/kVtTKvS.gif)
## Setup
Please see the [Setup Page](https://github.com/jagrosh/MusicBot/wiki/Setup) in the wiki to run this bot yourself!
# Questions/Suggestions/Bug Reports
If you'd like to suggest changes to how the bot functions, recommend more customization options, or report bugs, feel free to either open an [Issue](https://github.com/jagrosh/MusicBot/issues) on this repository, or join [my Discord server](https://discord.gg/0p9LSGoRLu6Pet0k). If you like this bot, be sure to add a star to the libraries that make this possible: [**JDA**](https://github.com/DV8FromTheWorld/JDA) and [**lavaplayer**](https://github.com/sedmelluq/lavaplayer)
## 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)
# Example
![Example](http://i.imgur.com/gxDdlRL.png)
## 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.
+71 -44
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.jagrosh</groupId>
<artifactId>JMusicBot</artifactId>
<version>0.0.4</version>
<version>0.2.5</version>
<packaging>jar</packaging>
<repositories>
@@ -15,64 +15,91 @@
</repositories>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>3.0.0_168</version>
<version>3.8.3_464</version>
</dependency>
<dependency>
<groupId>com.sedmelluq</groupId>
<artifactId>lavaplayer</artifactId>
<version>1.2.36</version>
<version>1.3.22</version>
</dependency>
<dependency>
<groupId>com.jagrosh</groupId>
<artifactId>JDA-Utilities</artifactId>
<version>1.1</version>
<artifactId>jda-utilities</artifactId>
<version>2.1.5</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160810</version>
<groupId>com.jagrosh</groupId>
<artifactId>JLyrics</artifactId>
<version>0.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</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>
@@ -80,4 +107,4 @@
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
</project>
+87 -323
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,96 +15,59 @@
*/
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.commandclient.Command.Category;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jdautilities.waiter.EventWaiter;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
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.jagrosh.jmusicbot.playlist.PlaylistLoader;
import com.jagrosh.jmusicbot.settings.SettingsManager;
import java.util.Objects;
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.Role;
import net.dv8tion.jda.core.entities.TextChannel;
import net.dv8tion.jda.core.entities.VoiceChannel;
import net.dv8tion.jda.core.events.ReadyEvent;
import net.dv8tion.jda.core.events.ShutdownEvent;
import net.dv8tion.jda.core.hooks.ListenerAdapter;
import net.dv8tion.jda.core.utils.PermissionUtil;
import net.dv8tion.jda.core.utils.SimpleLog;
import org.json.JSONException;
import org.json.JSONObject;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class Bot extends ListenerAdapter {
private final HashMap<String,Settings> settings;
private final AudioPlayerManager manager;
public class Bot
{
private final EventWaiter waiter;
private final ScheduledExecutorService threadpool;
private final BotConfig config;
private final SettingsManager settings;
private final PlayerManager players;
private final PlaylistLoader playlists;
private final NowplayingHandler nowplaying;
private 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(PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER))
return true;
Role dj = event.getGuild().getRoleById(getSettings(event.getGuild()).getRoleId());
return event.getMember().getRoles().contains(dj);
});
public final Category ADMIN = new Category("Admin", event ->
{
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
return true;
if(event.getGuild()==null)
return true;
return PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER);
});
public final Category OWNER = new Category("Owner");
public Bot(EventWaiter waiter)
public Bot(EventWaiter waiter, BotConfig config, SettingsManager settings)
{
this.waiter = waiter;
this.settings = new HashMap<>();
manager = new DefaultAudioPlayerManager();
threadpool = Executors.newSingleThreadScheduledExecutor();
AudioSourceManagers.registerRemoteSources(manager);
try {
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(Paths.get("serversettings.json"))));
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));
});
} catch(IOException | JSONException e) {
SimpleLog.getLog("Settings").warn("Failed to load server settings: "+e);
}
this.config = config;
this.settings = settings;
this.playlists = new PlaylistLoader(config);
this.threadpool = Executors.newSingleThreadScheduledExecutor();
this.players = new PlayerManager(this);
this.players.init();
this.nowplaying = new NowplayingHandler(this);
this.nowplaying.init();
}
public BotConfig getConfig()
{
return config;
}
public SettingsManager getSettingsManager()
{
return settings;
}
public EventWaiter getWaiter()
@@ -112,277 +75,78 @@ public class Bot extends ListenerAdapter {
return waiter;
}
public AudioPlayerManager getAudioManager()
public ScheduledExecutorService getThreadpool()
{
return manager;
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);
threadpool.scheduleWithFixedDelay(() -> updateTopic(guild,handler), 0, 5, TimeUnit.SECONDS);
}
else
handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
return handler;
return nowplaying;
}
private void updateTopic(Guild guild, AudioHandler handler)
public JDA getJDA()
{
TextChannel tchan = guild.getTextChannelById(getSettings(guild).getTextId());
if(tchan!=null && PermissionUtil.checkPermission(tchan, guild.getSelfMember(), Permission.MANAGE_CHANNEL))
{
String otherText;
if(tchan.getTopic()==null || tchan.getTopic().isEmpty())
otherText = "\u200B";
else if(tchan.getTopic().contains("\u200B"))
otherText = tchan.getTopic().substring(tchan.getTopic().indexOf("\u200B"));
else
otherText = "\u200B\n "+tchan.getTopic();
String text = FormatUtil.formattedAudio(handler, guild.getJDA(), true)+otherText;
if(!text.equals(tchan.getTopic()))
tchan.getManager().setTopic(text).queue();
}
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 shutdown(){
manager.shutdown();
public void shutdown()
{
if(shuttingDown)
return;
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, ah);
}
});
jda.shutdown();
g.getAudioManager().closeAudioConnection();
AudioHandler ah = (AudioHandler)g.getAudioManager().getSendingHandler();
if(ah!=null)
{
ah.stopAndClear();
ah.getPlayer().destroy();
nowplaying.updateTopic(g.getIdLong(), ah, true);
}
});
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())
{
SimpleLog.getLog("MusicBot").warn("This bot is not on any guilds! Use the following link to add the bot to your guilds!");
SimpleLog.getLog("MusicBot").warn(event.getJDA().asBot().getInviteUrl(JMusicBot.RECOMMENDED_PERMS));
}
jda.getGuilds().forEach((guild) -> {
try
{
String defpl = getSettings(guild).getDefaultPlaylist();
if(defpl!=null)
{
if(setUpHandler(guild).playFromDefault())
guild.getAudioManager().openAudioConnection(guild.getVoiceChannelById(getSettings(guild).getVoiceId()));
}
}
catch(Exception ex) {System.err.println(ex);}
});
}
// 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));
}
else
{
s.setTextId(channel.getId());
}
writeSettings();
}
public void setVoiceChannel(VoiceChannel channel)
{
Settings s = settings.get(channel.getGuild().getId());
if(s==null)
{
settings.put(channel.getGuild().getId(), new Settings(null,channel.getId(),null,100,null));
}
else
{
s.setVoiceId(channel.getId());
}
writeSettings();
}
public void setRole(Role role)
{
Settings s = settings.get(role.getGuild().getId());
if(s==null)
{
settings.put(role.getGuild().getId(), new Settings(null,null,role.getId(),100,null));
}
else
{
s.setRoleId(role.getId());
}
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));
}
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));
}
else
{
s.setVolume(volume);
}
writeSettings();
}
public void clearTextChannel(Guild guild)
{
Settings s = getSettings(guild);
if(s!=Settings.DEFAULT_SETTINGS)
{
if(s.getVoiceId()==null && s.getRoleId()==null)
settings.remove(guild.getId());
else
s.setTextId(null);
writeSettings();
}
}
public void clearVoiceChannel(Guild guild)
{
Settings s = getSettings(guild);
if(s!=Settings.DEFAULT_SETTINGS)
{
if(s.getTextId()==null && s.getRoleId()==null)
settings.remove(guild.getId());
else
s.setVoiceId(null);
writeSettings();
}
}
public void clearRole(Guild guild)
{
Settings s = getSettings(guild);
if(s!=Settings.DEFAULT_SETTINGS)
{
if(s.getVoiceId()==null && s.getTextId()==null)
settings.remove(guild.getId());
else
s.setRoleId(null);
writeSettings();
}
}
private void writeSettings()
{
JSONObject obj = new JSONObject();
settings.keySet().stream().forEach(key -> {
JSONObject o = new JSONObject();
Settings s = settings.get(key);
if(s.getTextId()!=null)
o.put("text_channel_id", s.getTextId());
if(s.getVoiceId()!=null)
o.put("voice_channel_id", s.getVoiceId());
if(s.getRoleId()!=null)
o.put("dj_role_id", s.getRoleId());
if(s.getVolume()!=100)
o.put("volume",s.getVolume());
if(s.getDefaultPlaylist()!=null)
o.put("default_playlist", s.getDefaultPlaylist());
obj.put(key, o);
});
try {
Files.write(Paths.get("serversettings.json"), obj.toString(4).getBytes());
} catch(IOException ex){
SimpleLog.getLog("Settings").warn("Failed to write to file: "+ex);
}
}
//gui stuff
/*public void registerPanel(GuildsPanel panel)
{
this.panel = panel;
threadpool.scheduleWithFixedDelay(() -> updatePanel(), 0, 5, TimeUnit.SECONDS);
}
public void updatePanel()
{
System.out.println("updating...");
Guild guild = jda.getGuilds().get(panel.getIndex());
panel.updatePanel((AudioHandler)guild.getAudioManager().getSendingHandler());
}
@Override
public void onGuildJoin(GuildJoinEvent event) {
if(panel!=null)
panel.updateList(event.getJDA().getGuilds());
}
@Override
public void onGuildLeave(GuildLeaveEvent event) {
if(panel!=null)
panel.updateList(event.getJDA().getGuilds());
}
@Override
public void onShutdown(ShutdownEvent event) {
((GUI)panel.getTopLevelAncestor()).dispose();
}*/
}
@@ -0,0 +1,303 @@
/*
* 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.FormatUtil;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.typesafe.config.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import net.dv8tion.jda.core.OnlineStatus;
import net.dv8tion.jda.core.entities.Game;
/**
*
*
* @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,
successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji;
private boolean stayInChannel, songInGame, npImages, updatealerts, useEval, dbots;
private long owner, maxSeconds;
private OnlineStatus status;
private Game game;
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 = Paths.get(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", "config.txt"));
ConfigFactory.invalidateCaches();
}
// load in the config file, plus the default values
//Config config = ConfigFactory.parseFile(path.toFile()).withFallback(ConfigFactory.load());
Config config = ConfigFactory.load();
// set values
token = config.getString("token");
prefix = config.getString("prefix");
altprefix = config.getString("altprefix");
helpWord = config.getString("help");
owner = config.getLong("owner");
successEmoji = config.getString("success");
warningEmoji = config.getString("warning");
errorEmoji = config.getString("error");
loadingEmoji = config.getString("loading");
searchingEmoji = config.getString("searching");
game = OtherUtil.parseGame(config.getString("game"));
status = OtherUtil.parseStatus(config.getString("status"));
stayInChannel = config.getBoolean("stayinchannel");
songInGame = config.getBoolean("songinstatus");
npImages = config.getBoolean("npimages");
updatealerts = config.getBoolean("updatealerts");
useEval = config.getBoolean("eval");
maxSeconds = config.getLong("maxtime");
playlistsFolder = config.getString("playlistsfolder");
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());
System.exit(0);
}
else
{
write = true;
}
}
if(write)
{
String original = OtherUtil.loadResource(this, "/reference.conf");
byte[] bytes;
if(original==null)
{
bytes = ("token = "+token+"\r\nowner = "+owner).getBytes();
}
else
{
bytes = original.substring(original.indexOf(START_TOKEN)+START_TOKEN.length(), original.indexOf(END_TOKEN))
.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());
}
}
// 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());
}
}
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 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 Game getGame()
{
return game;
}
public OnlineStatus getStatus()
{
return status;
}
public String getHelp()
{
return helpWord;
}
public boolean getStay()
{
return stayInChannel;
}
public boolean getSongInStatus()
{
return songInGame;
}
public String getPlaylistsFolder()
{
return playlistsFolder;
}
public boolean getDBots()
{
return dbots;
}
public boolean useUpdateAlerts()
{
return updatealerts;
}
public boolean useEval()
{
return useEval;
}
public boolean useNPImages()
{
return npImages;
}
public long getMaxSeconds()
{
return maxSeconds;
}
public String getMaxTime()
{
return FormatUtil.formatTime(maxSeconds * 1000);
}
public boolean isTooLong(AudioTrack track)
{
if(maxSeconds<=0)
return false;
return Math.round(track.getDuration()/1000.0) > maxSeconds;
}
}
@@ -1,213 +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.file.Files;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import javax.swing.JOptionPane;
/**
*
* @author John Grosh (jagrosh)
*/
public class Config {
private boolean nogui;
private String prefix;
private String token;
private String owner;
private String success;
private String warning;
private String error;
private String game;
private String help;
public Config(boolean nogui)
{
this.nogui = nogui;
List<String> lines;
try {
lines = Files.readAllLines(Paths.get("config.txt"));
for(String line: lines)
{
String[] parts = line.split("=",2);
String key = parts[0].trim().toLowerCase();
String value = parts.length>1 ? parts[1].trim() : null;
switch(key)
{
case "token":
token = value;
break;
case "prefix":
prefix = value;
break;
case "owner":
owner = value;
break;
case "success":
success = value;
break;
case "warning":
warning = value;
break;
case "error":
error = value;
break;
case "game":
game = value;
break;
case "help":
help = 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 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 getGame()
{
return game;
}
public String getHelp()
{
return help==null ? "help" : help;
}
public boolean getNoGui()
{
return nogui;
}
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,123 +15,184 @@
*/
package com.jagrosh.jmusicbot;
import com.jagrosh.jdautilities.command.CommandClient;
import com.jagrosh.jdautilities.command.CommandClientBuilder;
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
import com.jagrosh.jdautilities.examples.command.*;
import com.jagrosh.jmusicbot.commands.admin.*;
import com.jagrosh.jmusicbot.commands.dj.*;
import com.jagrosh.jmusicbot.commands.general.*;
import com.jagrosh.jmusicbot.commands.music.*;
import com.jagrosh.jmusicbot.commands.owner.*;
import com.jagrosh.jmusicbot.entities.Prompt;
import com.jagrosh.jmusicbot.gui.GUI;
import com.jagrosh.jmusicbot.settings.SettingsManager;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import java.awt.Color;
import javax.security.auth.login.LoginException;
import com.jagrosh.jdautilities.commandclient.CommandClient;
import com.jagrosh.jdautilities.commandclient.CommandClientBuilder;
import com.jagrosh.jdautilities.commandclient.examples.*;
import com.jagrosh.jdautilities.waiter.EventWaiter;
import com.jagrosh.jmusicbot.commands.*;
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.*;
import net.dv8tion.jda.core.entities.Game;
import net.dv8tion.jda.core.exceptions.RateLimitedException;
import net.dv8tion.jda.core.utils.SimpleLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @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 String PLAY_EMOJI = "\u25B6"; // ▶
public final static String PAUSE_EMOJI = "\u23F8"; // ⏸
public final static String STOP_EMOJI = "\u23F9"; // ⏹
public final static Permission[] RECOMMENDED_PERMS = new Permission[]{Permission.MESSAGE_READ, Permission.MESSAGE_WRITE, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI,
Permission.MANAGE_CHANNEL, Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE};
/**
* @param args the command line arguments
*/
public static void main(String[] args){
// check run mode(s)
boolean nogui = false;
public static void main(String[] args)
{
// startup log
Logger log = LoggerFactory.getLogger("Startup");
// create prompt to handle startup
Prompt prompt = new Prompt("JMusicBot", "Switching to nogui mode. You can manually start in nogui mode by including the -Dnogui=true flag.",
"true".equalsIgnoreCase(System.getProperty("nogui", "false")));
// check deprecated nogui mode (new way of setting it is -Dnogui=true)
for(String arg: args)
if("-nogui".equalsIgnoreCase(arg))
nogui = true;
{
prompt.alert(Prompt.Level.WARNING, "GUI", "The -nogui flag has been deprecated. "
+ "Please use the -Dnogui=true flag before the name of the jar. Example: java -jar -Dnogui=true JMusicBot.jar");
break;
}
// get and check latest version
String version = OtherUtil.checkVersion(prompt);
// load config
Config config = new Config(nogui);
BotConfig config = new BotConfig(prompt);
config.load();
if(!config.isValid())
return;
// set up the listener
EventWaiter waiter = new EventWaiter();
Bot bot = new Bot(waiter);
SettingsManager settings = new SettingsManager();
Bot bot = new Bot(waiter, config, settings);
AboutCommand.IS_AUTHOR = false;
AboutCommand aboutCommand = new AboutCommand(Color.BLUE.brighter(),
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot) (v"+version+")",
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
RECOMMENDED_PERMS);
aboutCommand.setIsAuthor(false);
aboutCommand.setReplacementCharacter("\uD83C\uDFB6"); // 🎶
// set up the command client
CommandClientBuilder cb = new CommandClientBuilder()
.setPrefix(config.getPrefix())
.setOwnerId(config.getOwnerId())
.setAlternativePrefix(config.getAltPrefix())
.setOwnerId(Long.toString(config.getOwnerId()))
.setEmojis(config.getSuccess(), config.getWarning(), config.getError())
.setHelpWord(config.getHelp())
.addCommands(
new AboutCommand(Color.BLUE.brighter(),
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot)",
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
RECOMMENDED_PERMS),
.setLinkedCacheSize(200)
.setGuildSettingsManager(settings)
.addCommands(aboutCommand,
new PingCommand(),
new SettingsCmd(bot),
new SettingsCmd(),
new LyricsCmd(bot),
new NowplayingCmd(bot),
new PlayCmd(bot),
new PlayCmd(bot, config.getLoading()),
new PlaylistsCmd(bot),
new QueueCmd(bot),
new RemoveCmd(bot),
new SearchCmd(bot),
new SCSearchCmd(bot),
new SearchCmd(bot, config.getSearching()),
new SCSearchCmd(bot, config.getSearching()),
new ShuffleCmd(bot),
new SkipCmd(bot),
new ForceskipCmd(bot),
new MoveTrackCmd(bot),
new PauseCmd(bot),
new PlaynextCmd(bot, config.getLoading()),
new RepeatCmd(bot),
new SkiptoCmd(bot),
new StopCmd(bot),
new VolumeCmd(bot),
new SetdjCmd(bot),
new SettcCmd(bot),
new SetvcCmd(bot),
new SetdjCmd(),
new SettcCmd(),
new SetvcCmd(),
//new GuildlistCommand(waiter),
new AutoplaylistCmd(bot),
new PlaylistCmd(bot),
new SetavatarCmd(bot),
new SetgameCmd(bot),
new SetnameCmd(bot),
new SetavatarCmd(),
new SetgameCmd(),
new SetnameCmd(),
new SetstatusCmd(),
new ShutdownCmd(bot)
);
if(config.useEval())
cb.addCommand(new EvalCmd(bot));
boolean nogame = false;
if(config.getStatus()!=OnlineStatus.UNKNOWN)
cb.setStatus(config.getStatus());
if(config.getGame()==null)
cb.useDefaultGame();
else if(config.getGame().getName().equalsIgnoreCase("none"))
{
cb.setGame(null);
nogame = true;
}
else
cb.setGame(Game.of(config.getGame()));
cb.setGame(config.getGame());
CommandClient client = cb.build();
if(!config.getNoGui())
if(!prompt.isNoGUI())
{
try {
try
{
GUI gui = new GUI(bot);
bot.setGUI(gui);
gui.init();
} catch(Exception e) {
SimpleLog.getLog("Startup").fatal("Could not start GUI. If you are "
}
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 -nogui flag.");
+ "window, please run in nogui mode using the -Dnogui=true flag.");
}
}
log.info("Loaded config from "+config.getConfigLocation());
// attempt to log in and start
try {
new JDABuilder(AccountType.BOT)
try
{
JDA jda = new JDABuilder(AccountType.BOT)
.setToken(config.getToken())
.setAudioEnabled(true)
.setGame(Game.of("loading..."))
.setStatus(OnlineStatus.DO_NOT_DISTURB)
.addListener(client)
.addListener(waiter)
.addListener(bot)
.buildAsync();
} catch (LoginException | IllegalArgumentException | RateLimitedException ex) {
SimpleLog.getLog("Login").fatal(ex);
.setGame(nogame ? null : Game.playing("loading..."))
.setStatus(config.getStatus()==OnlineStatus.INVISIBLE||config.getStatus()==OnlineStatus.OFFLINE ? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB)
.addEventListener(client, waiter, new Listener(bot))
.setBulkDeleteSplittingEnabled(true)
.build();
bot.setJDA(jda);
}
catch (LoginException ex)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nPlease make sure you are "
+ "editing the correct config.txt file, and that you have used the "
+ "correct token (not the 'secret'!)\nConfig Location: " + config.getConfigLocation());
System.exit(1);
}
catch(IllegalArgumentException ex)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "Some aspect of the configuration is "
+ "invalid: " + ex + "\nConfig Location: " + config.getConfigLocation());
System.exit(1);
}
}
}
@@ -0,0 +1,117 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import java.util.concurrent.TimeUnit;
import net.dv8tion.jda.core.JDA;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.User;
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.guild.GuildMessageDeleteEvent;
import net.dv8tion.jda.core.hooks.ListenerAdapter;
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().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());
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(() ->
{
User owner = bot.getJDA().getUserById(bot.getConfig().getOwnerId());
if(owner!=null)
{
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());
}
}
}, 0, 24, TimeUnit.HOURS);
}
}
@Override
public void onGuildMessageDelete(GuildMessageDeleteEvent event)
{
bot.getNowplayingHandler().onMessageDelete(event.getGuild(), event.getMessageIdLong());
}
@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,91 +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(null, null, null, 100, null);
private String textId;
private String voiceId;
private String roleId;
private int volume;
private String defaultPlaylist;
public Settings(String textId, String voiceId, String roleId, int volume, String defaultPlaylist)
{
this.textId = textId;
this.voiceId = voiceId;
this.roleId = roleId;
this.volume = volume;
this.defaultPlaylist = defaultPlaylist;
}
public String getTextId()
{
return textId;
}
public String getVoiceId()
{
return voiceId;
}
public String getRoleId()
{
return roleId;
}
public int getVolume()
{
return volume;
}
public String getDefaultPlaylist()
{
return defaultPlaylist;
}
public void setTextId(String id)
{
this.textId = id;
}
public void setVoiceId(String id)
{
this.voiceId = id;
}
public void setRoleId(String id)
{
this.roleId = id;
}
public void setVolume(int volume)
{
this.volume = volume;
}
public void setDefaultPlaylist(String defaultPlaylist)
{
this.defaultPlaylist = defaultPlaylist;
}
}
@@ -15,6 +15,8 @@
*/
package com.jagrosh.jmusicbot.audio;
import com.jagrosh.jmusicbot.JMusicBot;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
@@ -24,54 +26,64 @@ 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 com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
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.audio.AudioSendHandler;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.entities.Message;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler {
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
{
private final FairQueue<QueuedTrack> queue = new FairQueue<>();
private final List<AudioTrack> defaultQueue = new LinkedList<>();
private final Set<String> votes = new HashSet<>();
private final PlayerManager manager;
private final AudioPlayer audioPlayer;
private final Guild guild;
private final FairQueue<QueuedTrack> queue;
private final Set<String> votes;
private final List<AudioTrack> defaultQueue;
private final Bot bot;
private final long guildId;
private AudioFrame lastFrame;
private QueuedTrack current;
public AudioHandler(AudioPlayer audioPlayer, Guild guild, Bot bot) {
this.audioPlayer = audioPlayer;
this.guild = guild;
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();
}
public int addTrack(AudioTrack track, User user)
public int addTrackToFront(QueuedTrack qtrack)
{
QueuedTrack qt = new QueuedTrack(track, user.getId());
if(current==null)
if(audioPlayer.getPlayingTrack()==null)
{
current = qt;
audioPlayer.playTrack(track);
audioPlayer.playTrack(qtrack.getTrack());
return -1;
}
else
{
return queue.add(qt);
queue.addAt(0, qtrack);
return 0;
}
}
public QueuedTrack getCurrentTrack()
public int addTrack(QueuedTrack qtrack)
{
return current;
if(audioPlayer.getPlayingTrack()==null)
{
audioPlayer.playTrack(qtrack.getTrack());
return -1;
}
else
return queue.add(qtrack);
}
public FairQueue<QueuedTrack> getQueue()
@@ -79,6 +91,19 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
return queue;
}
public void stopAndClear()
{
queue.clear();
defaultQueue.clear();
audioPlayer.stopTrack();
//current = null;
}
public boolean isMusicPlaying(JDA jda)
{
return guild(jda).getSelfMember().getVoiceState().inVoiceChannel() && audioPlayer.getPlayingTrack()!=null;
}
public Set<String> getVotes()
{
return votes;
@@ -89,69 +114,184 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
return audioPlayer;
}
public long getRequester()
{
if(audioPlayer.getPlayingTrack()==null || audioPlayer.getPlayingTrack().getUserData(Long.class)==null)
return 0;
return audioPlayer.getPlayingTrack().getUserData(Long.class);
}
public boolean playFromDefault()
{
if(!defaultQueue.isEmpty())
{
current = new QueuedTrack(defaultQueue.remove(0), null);
audioPlayer.playTrack(current.getTrack());
audioPlayer.playTrack(defaultQueue.remove(0));
return true;
}
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(), () -> {
if(pl.getTracks().isEmpty())
{
current = null;
guild.getAudioManager().closeAudioConnection();
}
pl.loadTracks(manager, (at) ->
{
if(audioPlayer.getPlayingTrack()==null)
audioPlayer.playTrack(at);
else
{
defaultQueue.addAll(pl.getTracks());
playFromDefault();
}
defaultQueue.add(at);
}, () ->
{
if(pl.getTracks().isEmpty() && !manager.getBot().getConfig().getStay())
manager.getBot().closeAudioConnection(guildId);
});
return true;
}
// Audio Events
@Override
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason)
{
// if the track ended normally, and we're in repeat mode, re-add it to the queue
if(endReason==AudioTrackEndReason.FINISHED && manager.getBot().getSettingsManager().getSettings(guildId).getRepeatMode())
{
queue.add(new QueuedTrack(track.makeClone(), track.getUserData(Long.class)==null ? 0L : track.getUserData(Long.class)));
}
if(queue.isEmpty())
{
if(!playFromDefault())
{
current = null;
guild.getAudioManager().closeAudioConnection();
manager.getBot().getNowplayingHandler().onTrackUpdate(guildId, null, this);
if(!manager.getBot().getConfig().getStay())
manager.getBot().closeAudioConnection(guildId);
}
}
else
{
current = queue.pull();
player.playTrack(current.getTrack());
QueuedTrack qt = queue.pull();
player.playTrack(qt.getTrack());
}
}
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track) {
public void onTrackStart(AudioPlayer player, AudioTrack track)
{
votes.clear();
manager.getBot().getNowplayingHandler().onTrackUpdate(guildId, track, this);
}
// Formatting
public Message getNowPlaying(JDA jda)
{
if(isMusicPlaying(jda))
{
Guild guild = guild(jda);
AudioTrack track = audioPlayer.getPlayingTrack();
MessageBuilder mb = new MessageBuilder();
mb.append(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing in "+guild.getSelfMember().getVoiceState().getChannel().getName()+"...**"));
EmbedBuilder eb = new EmbedBuilder();
eb.setColor(guild.getSelfMember().getColor());
if(getRequester() != 0)
{
User u = guild.getJDA().getUserById(getRequester());
if(u==null)
eb.setAuthor("Unknown (ID:"+getRequester()+")", null, null);
else
eb.setAuthor(u.getName()+"#"+u.getDiscriminator(), 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((audioPlayer.isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI)
+ " "+FormatUtil.progressBar(progress)
+ " `[" + FormatUtil.formatTime(track.getPosition()) + "/" + FormatUtil.formatTime(track.getDuration()) + "]` "
+ FormatUtil.volumeIcon(audioPlayer.getVolume()));
return mb.setEmbed(eb.build()).build();
}
else return null;
}
public Message getNoMusicPlaying(JDA jda)
{
Guild guild = guild(jda);
return new MessageBuilder()
.setContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing...**"))
.setEmbed(new EmbedBuilder()
.setTitle("No music playing")
.setDescription(JMusicBot.STOP_EMOJI+" "+FormatUtil.progressBar(-1)+" "+FormatUtil.volumeIcon(audioPlayer.getVolume()))
.setColor(guild.getSelfMember().getColor())
.build()).build();
}
public String getTopicFormat(JDA jda)
{
if(isMusicPlaying(jda))
{
long userid = getRequester();
AudioTrack track = audioPlayer.getPlayingTrack();
String title = track.getInfo().title;
if(title==null || title.equals("Unknown Title"))
title = track.getInfo().uri;
return "**"+title+"** ["+(userid==0 ? "autoplay" : "<@"+userid+">")+"]"
+ "\n" + (audioPlayer.isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI) + " "
+ "[" + FormatUtil.formatTime(track.getDuration()) + "] "
+ FormatUtil.volumeIcon(audioPlayer.getVolume());
}
else return "No music playing " + JMusicBot.STOP_EMOJI + " " + FormatUtil.volumeIcon(audioPlayer.getVolume());
}
// Audio Send Handler methods
@Override
public boolean canProvide() {
lastFrame = audioPlayer.provide();
return lastFrame != null;
public boolean canProvide()
{
if (lastFrame == null)
lastFrame = audioPlayer.provide();
return lastFrame != null;
}
@Override
public byte[] provide20MsAudio() {
return lastFrame.data;
public byte[] provide20MsAudio()
{
if (lastFrame == null)
lastFrame = audioPlayer.provide();
byte[] data = lastFrame != null ? lastFrame.getData() : null;
lastFrame = null;
return data;
}
@Override
public boolean isOpus() {
return true;
public boolean isOpus()
{
return true;
}
// Private methods
private Guild guild(JDA jda)
{
return jda.getGuildById(guildId);
}
}
@@ -0,0 +1,155 @@
/*
* 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.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.TextChannel;
import net.dv8tion.jda.core.exceptions.PermissionException;
/**
*
* @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()
{
bot.getThreadpool().scheduleWithFixedDelay(() -> updateAll(), 0, 5, TimeUnit.SECONDS);
}
public void setLastNPMessage(Message m)
{
lastNP.put(m.getGuild().getIdLong(), new Pair<>(m.getTextChannel().getIdLong(), m.getIdLong()));
}
public void clearLastNPMessage(Guild guild)
{
lastNP.remove(guild.getIdLong());
}
private void updateAll()
{
Set<Long> toRemove = new HashSet<>();
for(long guildId: lastNP.keySet())
{
Guild guild = bot.getJDA().getGuildById(guildId);
if(guild==null)
{
toRemove.add(guildId);
continue;
}
Pair<Long,Long> pair = lastNP.get(guildId);
TextChannel tc = guild.getTextChannelById(pair.getKey());
if(tc==null)
{
toRemove.add(guildId);
continue;
}
AudioHandler handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
Message msg = handler.getNowPlaying(bot.getJDA());
if(msg==null)
{
msg = handler.getNoMusicPlaying(bot.getJDA());
toRemove.add(guildId);
}
try
{
tc.editMessageById(pair.getValue(), msg).queue(m->{}, t -> lastNP.remove(guildId));
}
catch(Exception e)
{
toRemove.add(guildId);
}
}
toRemove.forEach(id -> lastNP.remove(id));
}
public void updateTopic(long guildId, AudioHandler handler, boolean wait)
{
Guild guild = bot.getJDA().getGuildById(guildId);
if(guild==null)
return;
Settings settings = bot.getSettingsManager().getSettings(guildId);
TextChannel tchan = settings.getTextChannel(guild);
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 = handler.getTopicFormat(bot.getJDA()) + otherText;
if(!text.equals(tchan.getTopic()))
{
try
{
if(wait)
tchan.getManager().setTopic(text).complete();
else
tchan.getManager().setTopic(text).queue();
}
catch(PermissionException ignore) {}
}
}
}
// "event"-based methods
public void onTrackUpdate(long guildId, AudioTrack track, AudioHandler handler)
{
// update bot status if applicable
if(bot.getConfig().getSongInStatus())
{
if(track!=null && bot.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()<=1)
bot.getJDA().getPresence().setGame(Game.listening(track.getInfo().title));
else
bot.resetGame();
}
// update channel topic if applicable
updateTopic(guildId, handler, false);
}
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,70 @@
/*
* 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.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.youtube.YoutubeAudioSourceManager;
import net.dv8tion.jda.core.entities.Guild;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PlayerManager extends DefaultAudioPlayerManager
{
private final Bot bot;
public PlayerManager(Bot bot)
{
this.bot = bot;
}
public void init()
{
AudioSourceManagers.registerRemoteSources(this);
AudioSourceManagers.registerLocalSource(this);
source(YoutubeAudioSourceManager.class).setPlaylistPageCount(10);
}
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,52 +1,58 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.jagrosh.jmusicbot.queue.Queueable;
import com.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueuedTrack implements Queueable {
private final AudioTrack track;
private final String owner;
public QueuedTrack(AudioTrack track, String owner)
{
this.track = track;
this.owner = owner;
}
@Override
public String getIdentifier() {
return owner;
}
public AudioTrack getTrack()
{
return track;
}
@Override
public String toString() {
return "`["+FormatUtil.formatTime(track.getDuration())+"]` **" + track.getInfo().title +"** - <@"+owner+">";
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.audio;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.jagrosh.jmusicbot.queue.Queueable;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueuedTrack implements Queueable
{
private final AudioTrack track;
public QueuedTrack(AudioTrack track, User owner)
{
this(track, owner.getIdLong());
}
public QueuedTrack(AudioTrack track, long owner)
{
this.track = track;
this.track.setUserData(owner);
}
@Override
public long getIdentifier()
{
return track.getUserData(Long.class);
}
public AudioTrack getTrack()
{
return track;
}
@Override
public String toString()
{
return "`[" + FormatUtil.formatTime(track.getDuration()) + "]` **" + track.getInfo().title + "** - <@" + track.getUserData(Long.class) + ">";
}
}
@@ -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.core.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,45 @@
/*
* 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.jmusicbot.Bot;
import com.jagrosh.jmusicbot.settings.Settings;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.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 ->
{
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.
@@ -15,10 +15,10 @@
*/
package com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
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;
@@ -29,8 +29,8 @@ import net.dv8tion.jda.core.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,24 +39,25 @@ 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()).getCurrentTrack()==null))
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;
@@ -65,23 +66,27 @@ public abstract class MusicCommand extends Command {
{
VoiceChannel current = 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)))
{
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.getName()+"**")+" to use that!");
return;
}
if(!event.getGuild().getSelfMember().getVoiceState().inVoiceChannel())
try {
{
try
{
event.getGuild().getAudioManager().openAudioConnection(userState.getChannel());
}catch(PermissionException ex) {
}
catch(PermissionException ex)
{
event.reply(event.getClient().getError()+" I am unable to connect to **"+userState.getChannel().getName()+"**!");
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,167 +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 com.jagrosh.jdautilities.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.playlist.Playlist;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.Message;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PlayCmd extends MusicCommand {
public PlayCmd(Bot bot)
{
super(bot);
this.name = "play";
this.arguments = "<title|URL|subcommand>";
this.help = "plays the provided song";
this.beListening = true;
this.bePlaying = false;
this.children = new Command[]{new PlaylistCmd(bot)};
}
@Override
public void doCommand(CommandEvent event) {
if(event.getArgs().isEmpty())
{
StringBuilder builder = new StringBuilder(event.getClient().getWarning()+" Play Commands:\n");
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" <song title>` - plays the first result from Youtube");
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" <URL>` - plays the provided song, playlist, or stream");
for(Command cmd: children)
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" ").append(cmd.getName()).append(" ").append(cmd.getArguments()).append("` - ").append(cmd.getHelp());
event.reply(builder.toString());
return;
}
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
? event.getArgs().substring(1,event.getArgs().length()-1)
: event.getArgs();
event.getChannel().sendMessage("\u231A Loading... `["+args+"]`").queue(m -> {
bot.getAudioManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false));
});
}
private class ResultHandler implements AudioLoadResultHandler {
final Message m;
final CommandEvent event;
final boolean ytsearch;
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
{
this.m = m;
this.event = event;
this.ytsearch = ytsearch;
}
@Override
public void trackLoaded(AudioTrack track) {
int pos = bot.queueTrack(event, track)+1;
m.editMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
if(playlist.getTracks().size()==1 || playlist.isSearchResult() || playlist.getSelectedTrack()!=null)
{
AudioTrack single = playlist.getSelectedTrack()==null?playlist.getTracks().get(0):playlist.getSelectedTrack();
int pos = bot.queueTrack(event, single)+1;
m.editMessage(event.getClient().getSuccess()+" Added **"+single.getInfo().title
+"** (`"+FormatUtil.formatTime(single.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
}
else
{
m.editMessage(event.getClient().getSuccess()+" Found "
+(playlist.getName()==null?"a playlist":"playlist **"+playlist.getName()+"**")+" with `"
+playlist.getTracks().size()+"` entries; added to the queue!").queue();
playlist.getTracks().stream().forEach((track) -> {
bot.queueTrack(event, track);
});
}
}
@Override
public void noMatches() {
if(ytsearch)
m.editMessage(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.").queue();
else
bot.getAudioManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event,true));
}
@Override
public void loadFailed(FriendlyException throwable) {
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
public class PlaylistCmd extends MusicCommand {
public PlaylistCmd(Bot bot)
{
super(bot);
this.name = "playlist";
this.aliases = new String[]{"pl"};
this.arguments = "<name>";
this.help = "plays the provided playlist";
this.beListening = true;
this.bePlaying = false;
}
@Override
public void doCommand(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name.");
return;
}
Playlist playlist = Playlist.loadPlaylist(event.getArgs());
if(playlist==null)
{
event.reply(event.getClient().getError()+" I could not find `"+event.getArgs()+".txt` in the Playlists folder.");
return;
}
event.getChannel().sendMessage("\u231A Loading playlist **"+event.getArgs()+"**...").queue(m -> {
playlist.loadTracks(bot.getAudioManager(), () -> {
StringBuilder builder = new StringBuilder(playlist.getTracks().isEmpty()
? event.getClient().getWarning()+" No tracks were loaded!"
: event.getClient().getSuccess()+" Loaded **"+playlist.getTracks().size()+"** tracks!");
if(!playlist.getErrors().isEmpty())
builder.append("\nThe following tracks failed to load:");
playlist.getErrors().forEach(err -> builder.append("\n`[").append(err.getIndex()+1).append("]` **").append(err.getItem()).append("**: ").append(err.getReason()));
String str = builder.toString();
if(str.length()>2000)
str = str.substring(0,1994)+" (...)";
m.editMessage(str).queue();
playlist.getTracks().forEach(track -> bot.queueTrack(event, track));
});
});
}
}
}
@@ -1,84 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jdautilities.menu.pagination.PaginatorBuilder;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueueCmd extends MusicCommand {
private final PaginatorBuilder builder;
public QueueCmd(Bot bot)
{
super(bot);
this.name = "queue";
this.help = "shows the current queue";
this.arguments = "[pagenum]";
this.aliases = new String[]{"list"};
this.bePlaying = true;
this.botPermissions = new Permission[]{Permission.MESSAGE_ADD_REACTION,Permission.MESSAGE_EMBED_LINKS};
builder = new PaginatorBuilder()
.setColumns(1)
.setFinalAction(m -> {try{m.clearReactions().queue();}catch(PermissionException e){}})
.setItemsPerPage(10)
.waitOnSinglePage(false)
.useNumberedItems(true)
.showPageNumbers(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES)
;
}
@Override
public void doCommand(CommandEvent event) {
int pagenum = 1;
try{
pagenum = Integer.parseInt(event.getArgs());
}catch(NumberFormatException e){}
List<QueuedTrack> list = ((AudioHandler)event.getGuild().getAudioManager().getSendingHandler()).getQueue().getList();
if(list.isEmpty())
{
event.reply(event.getClient().getWarning()+" There is no music in the queue!");
return;
}
String[] songs = new String[list.size()];
long total = 0;
for(int i=0; i<list.size(); i++)
{
total += list.get(i).getTrack().getDuration();
songs[i] = list.get(i).toString();
}
builder.setText(event.getClient().getSuccess()+" Current Queue | "+songs.length+" entries | `"+FormatUtil.formatTime(total)+"` ")
.setItems(songs)
.setUsers(event.getAuthor())
.setColor(event.getSelfMember().getColor())
;
builder.build().paginate(event.getChannel(), pagenum);
}
}
@@ -1,121 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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.commandclient.CommandEvent;
import com.jagrosh.jdautilities.menu.orderedmenu.OrderedMenuBuilder;
import com.jagrosh.jmusicbot.Bot;
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 OrderedMenuBuilder builder;
public SCSearchCmd(Bot bot)
{
super(bot);
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 OrderedMenuBuilder()
.allowTextInput(true)
.useNumbers()
.useCancelButton(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES)
;
}
@Override
public void doCommand(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a query.");
return;
}
event.getChannel().sendMessage("\uD83D\uDD0E Searching... `["+event.getArgs()+"]`").queue(m -> {
bot.getAudioManager().loadItemOrdered(event.getGuild(), "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) {
int pos = bot.queueTrack(event, track)+1;
m.editMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
builder.setColor(event.getSelfMember().getColor())
.setText(event.getClient().getSuccess()+" Search results for `"+event.getArgs()+"`:")
.setChoices(new String[0])
.setAction(i -> {
AudioTrack track = playlist.getTracks().get(i-1);
int pos = bot.queueTrack(event, track)+1;
event.getChannel().sendMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
})
.setCancel(() -> m.delete().queue())
.setUsers(event.getAuthor())
;
for(int i=0; i<4&&i<playlist.getTracks().size(); i++)
{
AudioTrack track = playlist.getTracks().get(i);
builder.addChoices("`["+FormatUtil.formatTime(track.getDuration())+"]` [**"
+track.getInfo().title+"**]("+track.getInfo().uri+")");
}
builder.build().display(m);
}
@Override
public void noMatches() {
m.editMessage(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.").queue();
}
@Override
public void loadFailed(FriendlyException throwable) {
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
}
@@ -1,122 +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.commandclient.CommandEvent;
import com.jagrosh.jdautilities.menu.orderedmenu.OrderedMenuBuilder;
import com.jagrosh.jmusicbot.Bot;
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 SearchCmd extends MusicCommand {
private final OrderedMenuBuilder builder;
public SearchCmd(Bot bot)
{
super(bot);
this.name = "search";
this.aliases = new String[]{"ytsearch"};
this.arguments = "<query>";
this.help = "searches Youtube for a provided query";
this.beListening = true;
this.bePlaying = false;
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
builder = new OrderedMenuBuilder()
.allowTextInput(true)
.useNumbers()
.useCancelButton(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES)
;
}
@Override
public void doCommand(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a query.");
return;
}
event.getChannel().sendMessage("\uD83D\uDD0E Searching... `["+event.getArgs()+"]`").queue(m -> {
bot.getAudioManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event));
});
}
private class ResultHandler implements AudioLoadResultHandler {
final Message m;
final CommandEvent event;
private ResultHandler(Message m, CommandEvent event)
{
this.m = m;
this.event = event;
}
@Override
public void trackLoaded(AudioTrack track) {
int pos = bot.queueTrack(event, track)+1;
m.editMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
builder.setColor(event.getSelfMember().getColor())
.setText(event.getClient().getSuccess()+" Search results for `"+event.getArgs()+"`:")
.setChoices(new String[0])
.setAction(i -> {
AudioTrack track = playlist.getTracks().get(i-1);
int pos = bot.queueTrack(event, track)+1;
event.getChannel().sendMessage(event.getClient().getSuccess()+" Added **"+track.getInfo().title
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
: " to the queue at position "+pos)).queue();
})
.setCancel(() -> m.delete().queue())
.setUsers(event.getAuthor())
;
for(int i=0; i<4&&i<playlist.getTracks().size(); i++)
{
AudioTrack track = playlist.getTracks().get(i);
builder.addChoices("`["+FormatUtil.formatTime(track.getDuration())+"]` [**"
+track.getInfo().title+"**](https://youtu.be/"+track.getIdentifier()+")");
}
builder.build().display(m);
}
@Override
public void noMatches() {
m.editMessage(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.").queue();
}
@Override
public void loadFailed(FriendlyException throwable) {
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
}
@@ -1,49 +0,0 @@
/*
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import net.dv8tion.jda.core.entities.Game;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetgameCmd extends Command {
public SetgameCmd(Bot bot)
{
this.name = "setgame";
this.help = "sets the game the bot is playing";
this.arguments = "[game]";
this.ownerCommand = true;
this.category = bot.OWNER;
}
@Override
protected void execute(CommandEvent event) {
try {
event.getJDA().getPresence().setGame(event.getArgs().isEmpty() ? null : Game.of(event.getArgs()));
event.reply(event.getClient().getSuccess()+" **"+event.getSelfUser().getName()
+"** is "+(event.getArgs().isEmpty() ? "no longer playing anything." : "now playing `"+event.getArgs()+"`"));
} catch(Exception e) {
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
}
@@ -1,69 +1,68 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import java.util.List;
import com.jagrosh.jdautilities.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.utils.FinderUtil;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.Role;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetdjCmd extends Command {
private final Bot bot;
public SetdjCmd(Bot bot)
{
this.bot = bot;
this.name = "setdj";
this.help = "sets the DJ role for certain music commands";
this.arguments = "<rolename|NONE>";
this.guildOnly = true;
this.category = bot.ADMIN;
}
@Override
protected void execute(CommandEvent event) {
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a role name or NONE");
}
else if(event.getArgs().equalsIgnoreCase("none"))
{
bot.clearTextChannel(event.getGuild());
event.reply(event.getClient().getSuccess()+" DJ role cleared; Only Admins can use the DJ commands.");
}
else
{
List<Role> list = FinderUtil.findRole(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Roles found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfRoles(list, event.getArgs()));
else
{
bot.setRole(list.get(0));
event.reply(event.getClient().getSuccess()+" DJ commands can now be used by users with the **"+list.get(0).getName()+"** role.");
}
}
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.admin;
import java.util.List;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.entities.Role;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetdjCmd extends AdminCommand
{
public SetdjCmd()
{
this.name = "setdj";
this.help = "sets the DJ role for certain music commands";
this.arguments = "<rolename|NONE>";
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a role name or NONE");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
s.setDJRole(null);
event.reply(event.getClient().getSuccess()+" DJ role cleared; Only Admins can use the DJ commands.");
}
else
{
List<Role> list = FinderUtil.findRoles(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Roles found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfRoles(list, event.getArgs()));
else
{
s.setDJRole(list.get(0));
event.reply(event.getClient().getSuccess()+" DJ commands can now be used by users with the **"+list.get(0).getName()+"** role.");
}
}
}
}
@@ -1,69 +1,68 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import java.util.List;
import com.jagrosh.jdautilities.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.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.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
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 AdminCommand
{
public SettcCmd()
{
this.name = "settc";
this.help = "sets the text channel for music commands";
this.arguments = "<channel|NONE>";
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a text channel or NONE");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
s.setTextChannel(null);
event.reply(event.getClient().getSuccess()+" Music commands can now be used in any channel");
}
else
{
List<TextChannel> list = FinderUtil.findTextChannels(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Text Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfTChannels(list, event.getArgs()));
else
{
s.setTextChannel(list.get(0));
event.reply(event.getClient().getSuccess()+" Music commands can now only be used in <#"+list.get(0).getId()+">");
}
}
}
}
@@ -1,69 +1,67 @@
/*
* 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.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.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.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
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 AdminCommand
{
public SetvcCmd()
{
this.name = "setvc";
this.help = "sets the voice channel for playing music";
this.arguments = "<channel|NONE>";
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a voice channel or NONE");
return;
}
Settings s = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().equalsIgnoreCase("none"))
{
s.setVoiceChannel(null);
event.reply(event.getClient().getSuccess()+" Music can now be played in any channel");
}
else
{
List<VoiceChannel> list = FinderUtil.findVoiceChannels(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Voice Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
event.reply(event.getClient().getWarning()+FormatUtil.listOfVChannels(list, event.getArgs()));
else
{
s.setVoiceChannel(list.get(0));
event.reply(event.getClient().getSuccess()+" Music can now only be played in **"+list.get(0).getName()+"**");
}
}
}
}
@@ -1,48 +1,48 @@
/*
* 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.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ForceskipCmd extends MusicCommand {
public ForceskipCmd(Bot bot)
{
super(bot);
this.name = "forceskip";
this.help = "skips the current song";
this.aliases = new String[]{"modskip"};
this.bePlaying = true;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
User u = event.getJDA().getUserById(handler.getCurrentTrack().getIdentifier());
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getCurrentTrack().getTrack().getInfo().title
+"** (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
handler.getPlayer().stopTrack();
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
import net.dv8tion.jda.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ForceskipCmd extends DJCommand
{
public ForceskipCmd(Bot bot)
{
super(bot);
this.name = "forceskip";
this.help = "skips the current song";
this.aliases = new String[]{"modskip"};
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
User u = event.getJDA().getUserById(handler.getRequester());
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title
+"** (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
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.FairQueue;
/**
* 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 = new String[]{"move"};
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();
FairQueue<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(FairQueue<QueuedTrack> queue, int position)
{
return (position < 1 || position > queue.size());
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PauseCmd extends DJCommand
{
public PauseCmd(Bot bot)
{
super(bot);
this.name = "pause";
this.help = "pauses the current song";
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getPlayer().isPaused())
{
event.replyWarning("The player is already paused! Use `"+event.getClient().getPrefix()+"play` to unpause!");
return;
}
handler.getPlayer().setPaused(true);
event.replySuccess("Paused **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**. Type `"+event.getClient().getPrefix()+"play` to unpause!");
}
}
@@ -0,0 +1,128 @@
/*
* 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.commands.DJCommand;
import com.jagrosh.jmusicbot.utils.FormatUtil;
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.core.entities.Message;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PlaynextCmd extends DJCommand
{
private final String loadingEmoji;
public PlaynextCmd(Bot bot, String loadingEmoji)
{
super(bot);
this.loadingEmoji = loadingEmoji;
this.name = "playnext";
this.arguments = "<title|URL>";
this.help = "plays a single song next";
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: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrackToFront(new QueuedTrack(track, event.getAuthor()))+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));
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,67 @@
/*
* 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.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 = "[on|off]";
this.guildOnly = true;
}
// override musiccommand's execute because we don't actually care where this is used
@Override
protected void execute(CommandEvent event)
{
boolean value;
Settings settings = event.getClient().getSettingsFor(event.getGuild());
if(event.getArgs().isEmpty())
{
value = !settings.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;
}
settings.setRepeatMode(value);
event.replySuccess("Repeat mode is now `"+(value ? "ON" : "OFF")+"`");
}
@Override
public void doCommand(CommandEvent event) { /* Intentionally Empty */ }
}
@@ -13,19 +13,19 @@
* 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.commandclient.CommandEvent;
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);
@@ -34,11 +34,11 @@ public class SkiptoCmd extends MusicCommand {
this.arguments = "<position>";
this.aliases = new String[]{"jumpto"};
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,46 +1,45 @@
/*
* 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.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class StopCmd extends MusicCommand {
public StopCmd(Bot bot)
{
super(bot);
this.name = "stop";
this.help = "stops the current song and clears the queue";
this.bePlaying = true;
this.category = bot.DJ;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
handler.getQueue().clear();
handler.getPlayer().stopTrack();
event.getGuild().getAudioManager().closeAudioConnection();
event.reply(event.getClient().getSuccess()+" The player has stopped and the queue has been cleared.");
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.dj;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.DJCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class StopCmd extends DJCommand
{
public StopCmd(Bot bot)
{
super(bot);
this.name = "stop";
this.help = "stops the current song and clears the queue";
this.bePlaying = false;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
handler.stopAndClear();
event.getGuild().getAudioManager().closeAudioConnection();
event.reply(event.getClient().getSuccess()+" The player has stopped and the queue has been cleared.");
}
}
@@ -13,19 +13,21 @@
* 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.commandclient.CommandEvent;
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);
@@ -33,13 +35,14 @@ public class VolumeCmd extends MusicCommand {
this.aliases = new String[]{"vol"};
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+"`");
}
}
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
package com.jagrosh.jmusicbot.commands.general;
import com.jagrosh.jdautilities.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.Settings;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.settings.Settings;
import net.dv8tion.jda.core.EmbedBuilder;
import net.dv8tion.jda.core.MessageBuilder;
import net.dv8tion.jda.core.entities.Role;
@@ -29,12 +28,12 @@ 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)
public class SettingsCmd extends Command
{
private final static String EMOJI = "\uD83C\uDFA7"; // 🎧
public SettingsCmd()
{
this.bot = bot;
this.name = "settings";
this.help = "shows the bots settings";
this.aliases = new String[]{"status"};
@@ -42,20 +41,22 @@ public class SettingsCmd extends Command {
}
@Override
protected void execute(CommandEvent event) {
Settings s = bot.getSettings(event.getGuild());
protected void execute(CommandEvent event)
{
Settings s = event.getClient().getSettingsFor(event.getGuild());
MessageBuilder builder = new MessageBuilder()
.append("\uD83C\uDFA7 **")
.append(EMOJI + " **")
.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());
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.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 | "
@@ -0,0 +1,93 @@
/*
* 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.Lyrics;
import com.jagrosh.jlyrics.LyricsClient;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import java.util.concurrent.ExecutionException;
import net.dv8tion.jda.core.EmbedBuilder;
import net.dv8tion.jda.core.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 to the currently-playing song";
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
event.getChannel().sendTyping().queue();
String title;
if(event.getArgs().isEmpty())
title = ((AudioHandler)event.getGuild().getAudioManager().getSendingHandler()).getPlayer().getPlayingTrack().getInfo().title;
else
title = event.getArgs();
client.getLyrics(title).thenAccept(lyrics ->
{
if(lyrics == null)
{
event.replyError("Lyrics for `" + title + "` could not be found!");
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.core.Permission;
import net.dv8tion.jda.core.entities.Message;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class NowplayingCmd extends MusicCommand
{
public NowplayingCmd(Bot bot)
{
super(bot);
this.name = "nowplaying";
this.help = "shows the song that is currently playing";
this.aliases = new String[]{"np","current"};
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
Message m = handler.getNowPlaying(event.getJDA());
if(m==null)
{
event.reply(handler.getNoMusicPlaying(event.getJDA()));
bot.getNowplayingHandler().clearLastNPMessage(event.getGuild());
}
else
{
event.reply(m, msg -> bot.getNowplayingHandler().setLastNPMessage(msg));
}
}
}
@@ -0,0 +1,263 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.jagrosh.jdautilities.command.Command;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.menu.ButtonMenu;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
import com.jagrosh.jmusicbot.settings.Settings;
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.entities.Role;
import net.dv8tion.jda.core.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class PlayCmd extends MusicCommand
{
private final static String LOAD = "\uD83D\uDCE5"; // 📥
private final static String CANCEL = "\uD83D\uDEAB"; // 🚫
private final String loadingEmoji;
public PlayCmd(Bot bot, String loadingEmoji)
{
super(bot);
this.loadingEmoji = loadingEmoji;
this.name = "play";
this.arguments = "<title|URL|subcommand>";
this.help = "plays the provided song";
this.beListening = true;
this.bePlaying = false;
this.children = new Command[]{new PlaylistCmd(bot)};
}
@Override
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty())
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getPlayer().getPlayingTrack()!=null && handler.getPlayer().isPaused())
{
boolean isDJ = event.getMember().hasPermission(Permission.MANAGE_SERVER);
if(!isDJ)
isDJ = event.isOwner();
Settings settings = event.getClient().getSettingsFor(event.getGuild());
Role dj = settings.getRole(event.getGuild());
if(!isDJ && dj!=null)
isDJ = event.getMember().getRoles().contains(dj);
if(!isDJ)
event.replyError("Only DJs can unpause the player!");
else
{
handler.getPlayer().setPaused(false);
event.replySuccess("Resumed **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**.");
}
return;
}
StringBuilder builder = new StringBuilder(event.getClient().getWarning()+" Play Commands:\n");
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" <song title>` - plays the first result from Youtube");
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" <URL>` - plays the provided song, playlist, or stream");
for(Command cmd: children)
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" ").append(cmd.getName()).append(" ").append(cmd.getArguments()).append("` - ").append(cmd.getHelp());
event.reply(builder.toString());
return;
}
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
? event.getArgs().substring(1,event.getArgs().length()-1)
: event.getArgs().isEmpty() ? event.getMessage().getAttachments().get(0).getUrl() : event.getArgs();
event.reply(loadingEmoji+" Loading... `["+args+"]`", m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false)));
}
private class ResultHandler implements AudioLoadResultHandler
{
private final Message m;
private final CommandEvent event;
private final boolean ytsearch;
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
{
this.m = m;
this.event = event;
this.ytsearch = ytsearch;
}
private void loadSingle(AudioTrack track, AudioPlaylist playlist)
{
if(bot.getConfig().isTooLong(track))
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+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));
if(playlist==null || !event.getSelfMember().hasPermission(event.getTextChannel(), Permission.MESSAGE_ADD_REACTION))
m.editMessage(addMsg).queue();
else
{
new ButtonMenu.Builder()
.setText(addMsg+"\n"+event.getClient().getWarning()+" This track has a playlist of **"+playlist.getTracks().size()+"** tracks attached. Select "+LOAD+" to load playlist.")
.setChoices(LOAD, CANCEL)
.setEventWaiter(bot.getWaiter())
.setTimeout(30, TimeUnit.SECONDS)
.setAction(re ->
{
if(re.getName().equals(LOAD))
m.editMessage(addMsg+"\n"+event.getClient().getSuccess()+" Loaded **"+loadPlaylist(playlist, track)+"** additional tracks!").queue();
else
m.editMessage(addMsg).queue();
}).setFinalAction(m ->
{
try{ m.clearReactions().queue(); }catch(PermissionException ignore) {}
}).build().display(m);
}
}
private int loadPlaylist(AudioPlaylist playlist, AudioTrack exclude)
{
int[] count = {0};
playlist.getTracks().stream().forEach((track) -> {
if(!bot.getConfig().isTooLong(track) && !track.equals(exclude))
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
handler.addTrack(new QueuedTrack(track, event.getAuthor()));
count[0]++;
}
});
return count[0];
}
@Override
public void trackLoaded(AudioTrack track)
{
loadSingle(track, null);
}
@Override
public void playlistLoaded(AudioPlaylist playlist)
{
if(playlist.getTracks().size()==1 || playlist.isSearchResult())
{
AudioTrack single = playlist.getSelectedTrack()==null ? playlist.getTracks().get(0) : playlist.getSelectedTrack();
loadSingle(single, null);
}
else if (playlist.getSelectedTrack()!=null)
{
AudioTrack single = playlist.getSelectedTrack();
loadSingle(single, playlist);
}
else
{
int count = loadPlaylist(playlist, null);
if(count==0)
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" All entries in this playlist "+(playlist.getName()==null ? "" : "(**"+playlist.getName()
+"**) ")+"were longer than the allowed maximum (`"+bot.getConfig().getMaxTime()+"`)")).queue();
}
else
{
m.editMessage(FormatUtil.filter(event.getClient().getSuccess()+" Found "
+(playlist.getName()==null?"a playlist":"playlist **"+playlist.getName()+"**")+" with `"
+ playlist.getTracks().size()+"` entries; added to the queue!"
+ (count<playlist.getTracks().size() ? "\n"+event.getClient().getWarning()+" Tracks longer than the allowed maximum (`"
+ bot.getConfig().getMaxTime()+"`) have been omitted." : ""))).queue();
}
}
}
@Override
public void noMatches()
{
if(ytsearch)
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
else
bot.getPlayerManager().loadItemOrdered(event.getGuild(), "ytsearch:"+event.getArgs(), new ResultHandler(m,event,true));
}
@Override
public void loadFailed(FriendlyException throwable)
{
if(throwable.severity==Severity.COMMON)
m.editMessage(event.getClient().getError()+" Error loading: "+throwable.getMessage()).queue();
else
m.editMessage(event.getClient().getError()+" Error loading track.").queue();
}
}
public class PlaylistCmd extends MusicCommand
{
public PlaylistCmd(Bot bot)
{
super(bot);
this.name = "playlist";
this.aliases = new String[]{"pl"};
this.arguments = "<name>";
this.help = "plays the provided playlist";
this.beListening = true;
this.bePlaying = false;
}
@Override
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name.");
return;
}
Playlist playlist = bot.getPlaylistLoader().getPlaylist(event.getArgs());
if(playlist==null)
{
event.replyError("I could not find `"+event.getArgs()+".txt` in the Playlists folder.");
return;
}
event.getChannel().sendMessage(loadingEmoji+" Loading playlist **"+event.getArgs()+"**... ("+playlist.getItems().size()+" items)").queue(m ->
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
playlist.loadTracks(bot.getPlayerManager(), (at)->handler.addTrack(new QueuedTrack(at, event.getAuthor())), () -> {
StringBuilder builder = new StringBuilder(playlist.getTracks().isEmpty()
? event.getClient().getWarning()+" No tracks were loaded!"
: event.getClient().getSuccess()+" Loaded **"+playlist.getTracks().size()+"** tracks!");
if(!playlist.getErrors().isEmpty())
builder.append("\nThe following tracks failed to load:");
playlist.getErrors().forEach(err -> builder.append("\n`[").append(err.getIndex()+1).append("]` **").append(err.getItem()).append("**: ").append(err.getReason()));
String str = builder.toString();
if(str.length()>2000)
str = str.substring(0,1994)+" (...)";
m.editMessage(FormatUtil.filter(str)).queue();
});
});
}
}
}
@@ -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,19 +13,19 @@
* 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.commandclient.CommandEvent;
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);
@@ -38,15 +38,16 @@ public class PlaylistsCmd extends MusicCommand {
}
@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());
}
}
}
@@ -0,0 +1,118 @@
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.menu.Paginator;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.JMusicBot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.MessageBuilder;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Message;
import net.dv8tion.jda.core.exceptions.PermissionException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class QueueCmd extends MusicCommand
{
private final static String REPEAT = "\uD83D\uDD01"; // 🔁
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.bePlaying = true;
this.botPermissions = new Permission[]{Permission.MESSAGE_ADD_REACTION,Permission.MESSAGE_EMBED_LINKS};
builder = new Paginator.Builder()
.setColumns(1)
.setFinalAction(m -> {try{m.clearReactions().queue();}catch(PermissionException ignore){}})
.setItemsPerPage(10)
.waitOnSinglePage(false)
.useNumberedItems(true)
.showPageNumbers(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES);
}
@Override
public void doCommand(CommandEvent event)
{
int pagenum = 1;
try
{
pagenum = Integer.parseInt(event.getArgs());
}
catch(NumberFormatException ignore){}
AudioHandler ah = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
List<QueuedTrack> list = ah.getQueue().getList();
if(list.isEmpty())
{
Message nowp = ah.getNowPlaying(event.getJDA());
Message nonowp = ah.getNoMusicPlaying(event.getJDA());
Message built = new MessageBuilder()
.setContent(event.getClient().getWarning() + " There is no music in the queue!")
.setEmbed((nowp==null ? nonowp : nowp).getEmbeds().get(0)).build();
event.reply(built, m ->
{
if(nowp!=null)
bot.getNowplayingHandler().setLastNPMessage(m);
});
return;
}
String[] songs = new String[list.size()];
long total = 0;
for(int i=0; i<list.size(); i++)
{
total += list.get(i).getTrack().getDuration();
songs[i] = list.get(i).toString();
}
Settings settings = event.getClient().getSettingsFor(event.getGuild());
long fintotal = total;
builder.setText((i1,i2) -> getQueueTitle(ah, event.getClient().getSuccess(), songs.length, fintotal, settings.getRepeatMode()))
.setItems(songs)
.setUsers(event.getAuthor())
.setColor(event.getSelfMember().getColor())
;
builder.build().paginate(event.getChannel(), pagenum);
}
private String getQueueTitle(AudioHandler ah, String success, int songslength, long total, boolean repeatmode)
{
StringBuilder sb = new StringBuilder();
if(ah.getPlayer().getPlayingTrack()!=null)
{
sb.append(ah.getPlayer().isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI).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 ? "| " + REPEAT : "").toString());
}
}
@@ -13,22 +13,23 @@
* 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.commandclient.CommandEvent;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.User;
import net.dv8tion.jda.core.utils.PermissionUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class RemoveCmd extends MusicCommand {
public class RemoveCmd extends MusicCommand
{
public RemoveCmd(Bot bot)
{
super(bot);
@@ -41,20 +42,21 @@ public class RemoveCmd extends MusicCommand {
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(handler.getQueue().isEmpty())
{
event.reply(event.getClient().getError()+" There is nothing in the queue!");
event.replyError("There is nothing in the queue!");
return;
}
if(event.getArgs().equalsIgnoreCase("all"))
{
int count = handler.getQueue().removeAll(event.getAuthor().getId());
int count = handler.getQueue().removeAll(event.getAuthor().getIdLong());
if(count==0)
event.reply(event.getClient().getWarning()+" You don't have any songs in the queue!");
event.replyWarning("You don't have any songs in the queue!");
else
event.reply(event.getClient().getSuccess()+" Successfully removed your "+count+" entries.");
event.replySuccess("Successfully removed your "+count+" entries.");
return;
}
int pos;
@@ -65,29 +67,34 @@ public class RemoveCmd extends MusicCommand {
}
if(pos<1 || pos>handler.getQueue().size())
{
event.reply(event.getClient().getError()+" Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
event.replyError("Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
return;
}
boolean isDJ = PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER);
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().equals(event.getAuthor().getId()))
if(qt.getIdentifier()==event.getAuthor().getIdLong())
{
handler.getQueue().remove(pos-1);
event.reply(event.getClient().getSuccess()+" Removed **"+qt.getTrack().getInfo().title+"** from the queue");
event.replySuccess("Removed **"+qt.getTrack().getInfo().title+"** from the queue");
}
else if(isDJ)
{
handler.getQueue().remove(pos-1);
User u = event.getJDA().getUserById(qt.getIdentifier());
event.reply(event.getClient().getSuccess()+" Removed **"+qt.getTrack().getInfo().title
User u;
try {
u = event.getJDA().getUserById(qt.getIdentifier());
} catch(Exception e) {
u = null;
}
event.replySuccess("Removed **"+qt.getTrack().getInfo().title
+"** from the queue (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
}
else
{
event.reply(event.getClient().getError()+" You cannot remove **"+qt.getTrack().getInfo().title+"** because you didn't add it!");
event.replyError("You cannot remove **"+qt.getTrack().getInfo().title+"** because you didn't add it!");
}
}
}
@@ -1,42 +1,34 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.utils.FormatUtil;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class NowplayingCmd extends MusicCommand {
public NowplayingCmd(Bot bot)
{
super(bot);
this.name = "nowplaying";
this.help = "shows the song that is currently playing";
this.aliases = new String[]{"np","current"};
}
@Override
public void doCommand(CommandEvent event) {
event.reply(FormatUtil.formattedAudio((AudioHandler)event.getGuild().getAudioManager().getSendingHandler(), event.getJDA(), false));
}
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jmusicbot.Bot;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SCSearchCmd extends SearchCmd
{
public SCSearchCmd(Bot bot, String searchingEmoji)
{
super(bot, searchingEmoji);
this.searchPrefix = "scsearch:";
this.name = "scsearch";
this.help = "searches Soundcloud for a provided query";
this.aliases = new String[]{};
}
}
@@ -0,0 +1,148 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.util.concurrent.TimeUnit;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jdautilities.menu.OrderedMenu;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.QueuedTrack;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Message;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SearchCmd extends MusicCommand
{
protected String searchPrefix = "ytsearch:";
private final OrderedMenu.Builder builder;
private final String searchingEmoji;
public SearchCmd(Bot bot, String searchingEmoji)
{
super(bot);
this.searchingEmoji = searchingEmoji;
this.name = "search";
this.aliases = new String[]{"ytsearch"};
this.arguments = "<query>";
this.help = "searches Youtube for a provided query";
this.beListening = true;
this.bePlaying = false;
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
builder = new OrderedMenu.Builder()
.allowTextInput(true)
.useNumbers()
.useCancelButton(true)
.setEventWaiter(bot.getWaiter())
.setTimeout(1, TimeUnit.MINUTES);
}
@Override
public void doCommand(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.replyError("Please include a query.");
return;
}
event.reply(searchingEmoji+" Searching... `["+event.getArgs()+"]`",
m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), searchPrefix + event.getArgs(), new ResultHandler(m,event)));
}
private class ResultHandler implements AudioLoadResultHandler
{
private final Message m;
private final CommandEvent event;
private ResultHandler(Message m, CommandEvent event)
{
this.m = m;
this.event = event;
}
@Override
public void trackLoaded(AudioTrack track)
{
if(bot.getConfig().isTooLong(track))
{
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue();
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+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(bot.getConfig().isTooLong(track))
{
event.replyWarning("This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
+FormatUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`");
return;
}
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+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,54 +1,57 @@
/*
* 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.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ShuffleCmd extends MusicCommand {
public ShuffleCmd(Bot bot)
{
super(bot);
this.name = "shuffle";
this.help = "shuffles songs you have added";
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event) {
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int s = handler.getQueue().shuffle(event.getAuthor().getId());
switch (s) {
case 0:
event.reply(event.getClient().getError()+" You don't have any music in the queue to shuffle!");
break;
case 1:
event.reply(event.getClient().getWarning()+" You only have one song in the queue!");
break;
default:
event.reply(event.getClient().getSuccess()+" You successfully shuffled your "+s+" entries.");
break;
}
}
}
/*
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.MusicCommand;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class ShuffleCmd extends MusicCommand
{
public ShuffleCmd(Bot bot)
{
super(bot);
this.name = "shuffle";
this.help = "shuffles songs you have added";
this.beListening = true;
this.bePlaying = true;
}
@Override
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
int s = handler.getQueue().shuffle(event.getAuthor().getIdLong());
switch (s)
{
case 0:
event.replyError("You don't have any music in the queue to shuffle!");
break;
case 1:
event.replyWarning("You only have one song in the queue!");
break;
default:
event.replySuccess("You successfully shuffled your "+s+" entries.");
break;
}
}
}
@@ -13,19 +13,20 @@
* 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.commandclient.CommandEvent;
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.core.entities.User;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SkipCmd extends MusicCommand {
public class SkipCmd extends MusicCommand
{
public SkipCmd(Bot bot)
{
super(bot);
@@ -37,12 +38,12 @@ public class SkipCmd extends MusicCommand {
}
@Override
public void doCommand(CommandEvent event) {
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
if(event.getAuthor().getId().equals(handler.getCurrentTrack().getIdentifier()))
if(event.getAuthor().getIdLong()==handler.getRequester())
{
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getCurrentTrack().getTrack().getInfo().title
+"**");
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**");
handler.getPlayer().stopTrack();
}
else
@@ -63,9 +64,9 @@ public class SkipCmd extends MusicCommand {
msg+= skippers+" votes, "+required+"/"+listeners+" needed]`";
if(skippers>=required)
{
User u = event.getJDA().getUserById(handler.getCurrentTrack().getIdentifier());
msg+="\n"+event.getClient().getSuccess()+" Skipped **"+handler.getCurrentTrack().getTrack().getInfo().title
+"**"+(handler.getCurrentTrack().getIdentifier()==null ? "" : " (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
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()+"**")+")");
handler.getPlayer().stopTrack();
}
event.reply(msg);
@@ -0,0 +1,67 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import com.jagrosh.jmusicbot.settings.Settings;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class AutoplaylistCmd extends OwnerCommand
{
private final Bot bot;
public AutoplaylistCmd(Bot bot)
{
this.bot = bot;
this.guildOnly = true;
this.name = "autoplaylist";
this.arguments = "<name|NONE>";
this.help = "sets the default playlist for the server";
}
@Override
public void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.reply(event.getClient().getError()+" Please include a playlist name or NONE");
return;
}
if(event.getArgs().equalsIgnoreCase("none"))
{
Settings settings = event.getClient().getSettingsFor(event.getGuild());
settings.setDefaultPlaylist(null);
event.reply(event.getClient().getSuccess()+" Cleared the default playlist for **"+event.getGuild().getName()+"**");
return;
}
String pname = event.getArgs().replaceAll("\\s+", "_");
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
{
event.reply(event.getClient().getError()+" Could not find `"+pname+".txt`!");
}
else
{
Settings settings = event.getClient().getSettingsFor(event.getGuild());
settings.setDefaultPlaylist(pname);
event.reply(event.getClient().getSuccess()+" The default playlist for **"+event.getGuild().getName()+"** is now `"+pname+"`");
}
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2016 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.commands.owner;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
/**
*
* @author John Grosh (jagrosh)
*/
public class EvalCmd extends OwnerCommand
{
private final Bot bot;
public EvalCmd(Bot bot)
{
this.bot = bot;
this.name = "eval";
this.help = "evaluates nashorn code";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
ScriptEngine se = new ScriptEngineManager().getEngineByName("Nashorn");
se.put("bot", bot);
se.put("event", event);
se.put("jda", event.getJDA());
se.put("guild", event.getGuild());
se.put("channel", event.getChannel());
try
{
event.reply(event.getClient().getSuccess()+" Evaluated Successfully:\n```\n"+se.eval(event.getArgs())+" ```");
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" An exception was thrown:\n```\n"+e+" ```");
}
}
}
@@ -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.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
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.commands.owner.AutoplaylistCmd;
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.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,26 @@ 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)
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 +89,8 @@ public class PlaylistCmd extends Command {
}
}
public class DeletelistCmd extends Command {
public class DeletelistCmd extends OwnerCommand
{
public DeletelistCmd()
{
this.name = "delete";
@@ -101,20 +98,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 +121,8 @@ public class PlaylistCmd extends Command {
}
}
public class AppendlistCmd extends Command {
public class AppendlistCmd extends OwnerCommand
{
public AppendlistCmd()
{
this.name = "append";
@@ -134,12 +130,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 +142,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 +159,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 +170,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,14 +13,12 @@
* 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.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import net.dv8tion.jda.core.entities.Icon;
@@ -28,19 +26,19 @@ import net.dv8tion.jda.core.entities.Icon;
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetavatarCmd extends Command {
public SetavatarCmd(Bot bot)
public class SetavatarCmd extends OwnerCommand
{
public SetavatarCmd()
{
this.name = "setavatar";
this.help = "sets the avatar of the bot";
this.arguments = "<url>";
this.ownerCommand = true;
this.category = bot.OWNER;
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 +63,4 @@ public class SetavatarCmd extends Command {
}
}
}
}
@@ -0,0 +1,149 @@
/*
* 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.commands.OwnerCommand;
import net.dv8tion.jda.core.entities.Game;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetgameCmd extends OwnerCommand
{
public SetgameCmd()
{
this.name = "setgame";
this.help = "sets the game the bot is playing";
this.arguments = "[action] [game]";
this.guildOnly = false;
this.children = new OwnerCommand[]{
new SetlistenCmd(),
new SetstreamCmd(),
new SetwatchCmd()
};
}
@Override
protected void execute(CommandEvent event)
{
String title = event.getArgs().toLowerCase().startsWith("playing") ? event.getArgs().substring(7).trim() : event.getArgs();
try
{
event.getJDA().getPresence().setGame(title.isEmpty() ? null : Game.playing(title));
event.reply(event.getClient().getSuccess()+" **"+event.getSelfUser().getName()
+"** is "+(title.isEmpty() ? "no longer playing anything." : "now playing `"+title+"`"));
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
private class SetstreamCmd extends OwnerCommand
{
private SetstreamCmd()
{
this.name = "stream";
this.aliases = new String[]{"twitch","streaming"};
this.help = "sets the game the bot is playing to a stream";
this.arguments = "<username> <game>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
String[] parts = event.getArgs().split("\\s+", 2);
if(parts.length<2)
{
event.replyError("Please include a twitch username and the name of the game to 'stream'");
return;
}
try
{
event.getJDA().getPresence().setGame(Game.streaming(parts[1], "https://twitch.tv/"+parts[0]));
event.replySuccess("**"+event.getSelfUser().getName()
+"** is now streaming `"+parts[1]+"`");
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
}
private class SetlistenCmd extends OwnerCommand
{
private SetlistenCmd()
{
this.name = "listen";
this.aliases = new String[]{"listening"};
this.help = "sets the game the bot is listening to";
this.arguments = "<title>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.replyError("Please include a title to listen to!");
return;
}
String title = event.getArgs().toLowerCase().startsWith("to") ? event.getArgs().substring(2).trim() : event.getArgs();
try
{
event.getJDA().getPresence().setGame(Game.listening(title));
event.replySuccess("**"+event.getSelfUser().getName()+"** is now listening to `"+title+"`");
} catch(Exception e) {
event.reply(event.getClient().getError()+" The game could not be set!");
}
}
}
private class SetwatchCmd extends OwnerCommand
{
private SetwatchCmd()
{
this.name = "watch";
this.aliases = new String[]{"watching"};
this.help = "sets the game the bot is watching";
this.arguments = "<title>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
if(event.getArgs().isEmpty())
{
event.replyError("Please include a title to watch!");
return;
}
String title = event.getArgs();
try
{
event.getJDA().getPresence().setGame(Game.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,55 @@
/*
* 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.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.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.core.exceptions.RateLimitedException;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetnameCmd extends OwnerCommand
{
public SetnameCmd()
{
this.name = "setname";
this.help = "sets the name of the bot";
this.arguments = "<name>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
try
{
String oldname = event.getSelfUser().getName();
event.getSelfUser().getManager().setName(event.getArgs()).complete(false);
event.reply(event.getClient().getSuccess()+" Name changed from `"+oldname+"` to `"+event.getArgs()+"`");
}
catch(RateLimitedException e)
{
event.reply(event.getClient().getError()+" Name can only be changed twice per hour!");
}
catch(Exception e)
{
event.reply(event.getClient().getError()+" That name is not valid!");
}
}
}
@@ -0,0 +1,54 @@
/*
* 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.commands.OwnerCommand;
import net.dv8tion.jda.core.OnlineStatus;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class SetstatusCmd extends OwnerCommand
{
public SetstatusCmd()
{
this.name = "setstatus";
this.help = "sets the status the bot displays";
this.arguments = "<status>";
this.guildOnly = false;
}
@Override
protected void execute(CommandEvent event)
{
try {
OnlineStatus status = OnlineStatus.fromKey(event.getArgs());
if(status==OnlineStatus.UNKNOWN)
{
event.replyError("Please include one of the following statuses: `ONLINE`, `IDLE`, `DND`, `INVISIBLE`");
}
else
{
event.getJDA().getPresence().setStatus(status);
event.replySuccess("Set the status to `"+status.getKey().toUpperCase()+"`");
}
} catch(Exception e) {
event.reply(event.getClient().getError()+" The status could not be set!");
}
}
}
@@ -1,44 +1,44 @@
/*
* 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.commandclient.Command;
import com.jagrosh.jdautilities.commandclient.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.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,148 @@
/*
* 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 Scanner scanner;
public Prompt(String title)
{
this(title, null);
}
public Prompt(String title, String noguiMessage)
{
this(title, noguiMessage, "true".equalsIgnoreCase(System.getProperty("nogui")));
}
public Prompt(String title, String noguiMessage, boolean nogui)
{
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;
}
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(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,186 +0,0 @@
/*
* 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.
*/
package com.jagrosh.jmusicbot.playlist;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class Playlist {
private final String name;
private final List<String> items;
private List<AudioTrack> tracks;
private List<PlaylistLoadError> errors;
private Playlist(String name, List<String> items)
{
this.name = name;
this.items = items;
}
public void loadTracks(AudioPlayerManager manager, 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) {
tracks.add(at);
if(last && callback!=null)
callback.run();
}
@Override
public void playlistLoaded(AudioPlaylist ap) {
if(ap.isSearchResult())
tracks.add(ap.getTracks().get(0));
else if(ap.getSelectedTrack()!=null)
tracks.add(ap.getSelectedTrack());
else
tracks.addAll(ap.getTracks());
if(last && callback!=null)
callback.run();
}
@Override
public void noMatches() {
errors.add(new PlaylistLoadError(index, items.get(index), "No matches found."));
if(last && 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 && callback!=null)
callback.run();
}
});
}
}
}
public String getName()
{
return name;
}
public List<String> getItems()
{
return items;
}
public List<AudioTrack> getTracks()
{
return tracks;
}
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()
{
if(folderExists())
{
File folder = new File("Playlists");
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;
}
}
public static Playlist loadPlaylist(String name)
{
try
{
if(folderExists())
{
return new Playlist(name, Files.readAllLines(Paths.get("Playlists"+File.separator+name+".txt"))
.stream()
.map((str) -> str.trim())
.filter((s) -> (!s.isEmpty() && !s.startsWith("#") && !s.startsWith("//")))
.collect(Collectors.toList()));
}
else
{
createFolder();
return null;
}
}
catch(IOException e)
{
return null;
}
}
public class PlaylistLoadError {
private final int number;
private final String item;
private final String reason;
private PlaylistLoadError(int number, String item, String reason)
{
this.number = number;
this.item = item;
this.reason = reason;
}
public int getIndex()
{
return number;
}
public String getItem()
{
return item;
}
public String getReason()
{
return reason;
}
}
}
@@ -0,0 +1,306 @@
/*
* Copyright 2018 John Grosh (jagrosh).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.playlist;
import com.jagrosh.jmusicbot.BotConfig;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class PlaylistLoader
{
private final BotConfig config;
public PlaylistLoader(BotConfig config)
{
this.config = config;
}
public List<String> getPlaylistNames()
{
if(folderExists())
{
File folder = new File(config.getPlaylistsFolder());
return Arrays.asList(folder.listFiles((pathname) -> pathname.getName().endsWith(".txt")))
.stream().map(f -> f.getName().substring(0,f.getName().length()-4)).collect(Collectors.toList());
}
else
{
createFolder();
return Collections.EMPTY_LIST;
}
}
public void createFolder()
{
try
{
Files.createDirectory(Paths.get(config.getPlaylistsFolder()));
}
catch (IOException ignore) {}
}
public boolean folderExists()
{
return Files.exists(Paths.get(config.getPlaylistsFolder()));
}
public void createPlaylist(String name) throws IOException
{
Files.createFile(Paths.get(config.getPlaylistsFolder()+File.separator+name+".txt"));
}
public void deletePlaylist(String name) throws IOException
{
Files.delete(Paths.get(config.getPlaylistsFolder()+File.separator+name+".txt"));
}
public void writePlaylist(String name, String text) throws IOException
{
Files.write(Paths.get(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(config.getPlaylistsFolder()+File.separator+name+".txt")).forEach(str ->
{
String s = str.trim();
if(s.isEmpty())
return;
if(s.startsWith("#") || s.startsWith("//"))
{
s = s.replaceAll("\\s+", "");
if(s.equalsIgnoreCase("#shuffle") || s.equalsIgnoreCase("//shuffle"))
shuffle[0]=true;
}
else
list.add(s);
});
if(shuffle[0])
shuffle(list);
return new Playlist(name, list, shuffle[0]);
}
else
{
createFolder();
return null;
}
}
catch(IOException e)
{
return null;
}
}
private static <T> void shuffle(List<T> list)
{
for(int first =0; first<list.size(); first++)
{
int second = (int)(Math.random()*list.size());
T tmp = list.get(first);
list.set(first, list.get(second));
list.set(second, tmp);
}
}
public class Playlist
{
private final String name;
private final List<String> items;
private final boolean shuffle;
private final List<AudioTrack> tracks = new LinkedList<>();
private final List<PlaylistLoadError> errors = new LinkedList<>();
private boolean loaded = false;
private Playlist(String name, List<String> items, boolean shuffle)
{
this.name = name;
this.items = items;
this.shuffle = shuffle;
}
public void loadTracks(AudioPlayerManager manager, Consumer<AudioTrack> consumer, Runnable callback)
{
if(!loaded)
{
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()
{
@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);
}
if(last && callback!=null)
callback.run();
}
@Override
public void playlistLoaded(AudioPlaylist ap)
{
if(ap.isSearchResult())
{
if(config.isTooLong(ap.getTracks().get(0)))
errors.add(new PlaylistLoadError(index, items.get(index), "This track is longer than the allowed maximum"));
else
{
ap.getTracks().get(0).setUserData(0L);
tracks.add(ap.getTracks().get(0));
consumer.accept(ap.getTracks().get(0));
}
}
else if(ap.getSelectedTrack()!=null)
{
if(config.isTooLong(ap.getSelectedTrack()))
errors.add(new PlaylistLoadError(index, items.get(index), "This track is longer than the allowed maximum"));
else
{
ap.getSelectedTrack().setUserData(0L);
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 -> config.isTooLong(track));
loaded.forEach(at -> at.setUserData(0L));
tracks.addAll(loaded);
loaded.forEach(at -> consumer.accept(at));
}
if(last && callback!=null)
callback.run();
}
@Override
public void noMatches()
{
errors.add(new PlaylistLoadError(index, items.get(index), "No matches found."));
if(last && 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 && callback!=null)
callback.run();
}
});
}
}
if(shuffle)
shuffleTracks();
}
public void shuffleTracks()
{
if(tracks!=null)
shuffle(tracks);
}
public String getName()
{
return name;
}
public List<String> getItems()
{
return items;
}
public List<AudioTrack> getTracks()
{
return tracks;
}
public List<PlaylistLoadError> getErrors()
{
return errors;
}
}
public class PlaylistLoadError
{
private final int number;
private final String item;
private final String reason;
private PlaylistLoadError(int number, String item, String reason)
{
this.number = number;
this.item = item;
this.reason = reason;
}
public int getIndex()
{
return number;
}
public String getItem()
{
return item;
}
public String getReason()
{
return reason;
}
}
}
@@ -27,13 +27,13 @@ import java.util.Set;
*/
public class FairQueue<T extends Queueable> {
private final List<T> list = new ArrayList<>();
private final Set<String> set = new HashSet<>();
private final Set<Long> set = new HashSet<>();
public int add(T item)
{
int lastIndex;
for(lastIndex=list.size()-1; lastIndex>-1; lastIndex--)
if(list.get(lastIndex).getIdentifier().equals(item.getIdentifier()))
if(list.get(lastIndex).getIdentifier()==item.getIdentifier())
break;
lastIndex++;
set.clear();
@@ -47,6 +47,14 @@ public class FairQueue<T extends Queueable> {
return lastIndex;
}
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();
@@ -77,12 +85,12 @@ public class FairQueue<T extends Queueable> {
return list.remove(index);
}
public int removeAll(String identifier)
public int removeAll(long identifier)
{
int count = 0;
for(int i=list.size()-1; i>=0; i--)
{
if(list.get(i).getIdentifier().equals(identifier))
if(list.get(i).getIdentifier()==identifier)
{
list.remove(i);
count++;
@@ -96,12 +104,12 @@ public class FairQueue<T extends Queueable> {
list.clear();
}
public int shuffle(String identifier)
public int shuffle(long identifier)
{
List<Integer> iset = new ArrayList<>();
for(int i=0; i<list.size(); i++)
{
if(list.get(i).getIdentifier().equals(identifier))
if(list.get(i).getIdentifier()==identifier)
iset.add(i);
}
for(int j=0; j<iset.size(); j++)
@@ -120,4 +128,17 @@ public class FairQueue<T extends Queueable> {
for(int i=0; i<number; i++)
list.remove(0);
}
/**
* 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;
}
}
@@ -1,25 +1,25 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.queue;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public interface Queueable {
public String getIdentifier();
}
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.queue;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public interface Queueable {
public long getIdentifier();
}
@@ -0,0 +1,148 @@
/*
* 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 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 Settings implements GuildSettingsProvider
{
private final SettingsManager manager;
protected long textId;
protected long voiceId;
protected long roleId;
private int volume;
private String defaultPlaylist;
private boolean repeatMode;
public Settings(SettingsManager manager, String textId, String voiceId, String roleId, int volume, String defaultPlaylist, boolean repeatMode)
{
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;
}
public Settings(SettingsManager manager, long textId, long voiceId, long roleId, int volume, String defaultPlaylist, boolean repeatMode)
{
this.manager = manager;
this.textId = textId;
this.voiceId = voiceId;
this.roleId = roleId;
this.volume = volume;
this.defaultPlaylist = defaultPlaylist;
this.repeatMode = repeatMode;
}
// 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 boolean getRepeatMode()
{
return repeatMode;
}
// 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(boolean mode)
{
this.repeatMode = mode;
this.manager.writeSettings();
}
}
@@ -0,0 +1,104 @@
/*
* 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 java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import net.dv8tion.jda.core.entities.Guild;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.LoggerFactory;
/**
*
* @author John Grosh (john.a.grosh@gmail.com)
*/
public class SettingsManager implements GuildSettingsManager
{
private final HashMap<Long,Settings> settings;
public SettingsManager()
{
this.settings = new HashMap<>();
try {
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(Paths.get("serversettings.json"))));
loadedSettings.keySet().forEach((id) -> {
JSONObject o = loadedSettings.getJSONObject(id);
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") ? 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);
}
}
/**
* 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, false);
}
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())
o.put("repeat", true);
obj.put(Long.toString(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);
}
}
}
@@ -1,140 +0,0 @@
/*
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package 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 static List<TextChannel> findTextChannel(String query, Guild guild)
{
String id;
if(query.matches("<#\\d+>"))
{
id = query.replaceAll("<#(\\d+)>", "$1");
TextChannel tc = guild.getJDA().getTextChannelById(id);
if(tc!=null && tc.getGuild().equals(guild))
return Collections.singletonList(tc);
}
ArrayList<TextChannel> exact = new ArrayList<>();
ArrayList<TextChannel> wrongcase = new ArrayList<>();
ArrayList<TextChannel> startswith = new ArrayList<>();
ArrayList<TextChannel> contains = new ArrayList<>();
String lowerquery = query.toLowerCase();
guild.getTextChannels().stream().forEach((tc) -> {
if(tc.getName().equals(lowerquery))
exact.add(tc);
else if (tc.getName().equalsIgnoreCase(lowerquery) && exact.isEmpty())
wrongcase.add(tc);
else if (tc.getName().toLowerCase().startsWith(lowerquery) && wrongcase.isEmpty())
startswith.add(tc);
else if (tc.getName().toLowerCase().contains(lowerquery) && startswith.isEmpty())
contains.add(tc);
});
if(!exact.isEmpty())
return exact;
if(!wrongcase.isEmpty())
return wrongcase;
if(!startswith.isEmpty())
return startswith;
return contains;
}
public static List<VoiceChannel> findVoiceChannel(String query, Guild guild)
{
String id;
if(query.matches("<#\\d+>"))
{
id = query.replaceAll("<#(\\d+)>", "$1");
VoiceChannel tc = guild.getJDA().getVoiceChannelById(id);
if(tc!=null && tc.getGuild().equals(guild))
return Collections.singletonList(tc);
}
ArrayList<VoiceChannel> exact = new ArrayList<>();
ArrayList<VoiceChannel> wrongcase = new ArrayList<>();
ArrayList<VoiceChannel> startswith = new ArrayList<>();
ArrayList<VoiceChannel> contains = new ArrayList<>();
String lowerquery = query.toLowerCase();
guild.getVoiceChannels().stream().forEach((tc) -> {
if(tc.getName().equals(lowerquery))
exact.add(tc);
else if (tc.getName().equalsIgnoreCase(lowerquery) && exact.isEmpty())
wrongcase.add(tc);
else if (tc.getName().toLowerCase().startsWith(lowerquery) && wrongcase.isEmpty())
startswith.add(tc);
else if (tc.getName().toLowerCase().contains(lowerquery) && startswith.isEmpty())
contains.add(tc);
});
if(!exact.isEmpty())
return exact;
if(!wrongcase.isEmpty())
return wrongcase;
if(!startswith.isEmpty())
return startswith;
return contains;
}
public static List<Role> findRole(String query, Guild guild)
{
String id;
if(query.matches("<@&\\d+>"))
{
id = query.replaceAll("<@&(\\d+)>", "$1");
Role role = guild.getRoleById(id);
if(role!=null)
return Collections.singletonList(role);
}
if(query.matches("[Ii][Dd]\\s*:\\s*\\d+"))
{
id = query.replaceAll("[Ii][Dd]\\s*:\\s*(\\d+)", "$1");
for(Role role: guild.getRoles())
if(role.getId().equals(id))
return Collections.singletonList(role);
}
ArrayList<Role> exact = new ArrayList<>();
ArrayList<Role> wrongcase = new ArrayList<>();
ArrayList<Role> startswith = new ArrayList<>();
ArrayList<Role> contains = new ArrayList<>();
String lowerQuery = query.toLowerCase();
guild.getRoles().stream().forEach((role) -> {
if(role.getName().equals(query))
exact.add(role);
else if (role.getName().equalsIgnoreCase(query) && exact.isEmpty())
wrongcase.add(role);
else if (role.getName().toLowerCase().startsWith(lowerQuery) && wrongcase.isEmpty())
startswith.add(role);
else if (role.getName().toLowerCase().contains(lowerQuery) && startswith.isEmpty())
contains.add(role);
});
if(!exact.isEmpty())
return exact;
if(!wrongcase.isEmpty())
return wrongcase;
if(!startswith.isEmpty())
return startswith;
return contains;
}
}
@@ -15,13 +15,9 @@
*/
package com.jagrosh.jmusicbot.utils;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import java.util.List;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.core.JDA;
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;
/**
@@ -41,36 +37,13 @@ public class FormatUtil {
seconds %= 60;
return (hours>0 ? hours+":" : "") + (minutes<10 ? "0"+minutes : minutes) + ":" + (seconds<10 ? "0"+seconds : seconds);
}
public static String formattedAudio(AudioHandler handler, JDA jda, boolean inTopic)
{
if(handler==null)
return "No music playing\n\u23F9 "+progressBar(-1)+" "+volumeIcon(100);
else if (handler.getCurrentTrack()==null)
return "No music playing\n\u23F9 "+progressBar(-1)+" "+volumeIcon(handler.getPlayer().getVolume());
else
{
String userid = handler.getCurrentTrack().getIdentifier();
User user = jda.getUserById(userid);
AudioTrack track = handler.getCurrentTrack().getTrack();
String title = track.getInfo().title;
if(inTopic && title.length()>30)
title = title.substring(0,27)+"...";
double progress = (double)track.getPosition()/track.getDuration();
String str = "**"+title+"** ["+(user==null||inTopic ? (userid==null ? "autoplay" : "<@"+userid+">") : user.getName())+"]\n\u25B6 "+progressBar(progress)
+" "+(inTopic ? "" : "`")+"["+formatTime(track.getPosition()) + "/" + formatTime(track.getDuration())
+"]"+(inTopic ? "" : "`")+" " +volumeIcon(handler.getPlayer().getVolume())
+(inTopic ? "" : "\n**<"+track.getInfo().uri+">**");
return str;
}
}
private static String progressBar(double percent)
public static String progressBar(double percent)
{
String str = "";
for(int i=0; i<8; i++)
if(i == (int)(percent*8))
str+="\uD83D\uDD18";
for(int i=0; i<12; i++)
if(i == (int)(percent*12))
str+="\uD83D\uDD18"; // 🔘
else
str+="▬";
return str;
@@ -79,12 +52,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)
@@ -96,6 +69,7 @@ public class FormatUtil {
out+="\n**And "+(list.size()-6)+" more...**";
return out;
}
public static String listOfVChannels(List<VoiceChannel> list, String query)
{
String out = " Multiple voice channels found matching \""+query+"\":";
@@ -105,6 +79,7 @@ public class FormatUtil {
out+="\n**And "+(list.size()-6)+" more...**";
return out;
}
public static String listOfRoles(List<Role> list, String query)
{
String out = " Multiple text channels found matching \""+query+"\":";
@@ -114,4 +89,9 @@ public class FormatUtil {
out+="\n**And "+(list.size()-6)+" more...**";
return out;
}
public static String filter(String input)
{
return input.replace("@everyone", "@\u0435veryone").replace("@here", "@h\u0435re").trim(); // cyrillic letter e
}
}
@@ -1,42 +1,155 @@
/*
* 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.URL;
import java.net.URLConnection;
import net.dv8tion.jda.core.OnlineStatus;
import net.dv8tion.jda.core.entities.Game;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
/**
*
* @author John Grosh <john.a.grosh@gmail.com>
*/
public class OtherUtil
{
public final static String NEW_VERSION_AVAILABLE = "There is a new version of JMusicBot available!\n"
+ "Current version: %s\n"
+ "New Version: %s\n\n"
+ "Please visit https://github.com/jagrosh/MusicBot/releases/latest to get the latest release.";
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 ex)
{
return null;
}
}
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;
}
public static Game parseGame(String game)
{
if(game==null || game.trim().isEmpty() || game.trim().equalsIgnoreCase("default"))
return null;
String lower = game.toLowerCase();
if(lower.startsWith("playing"))
return Game.playing(game.substring(7).trim());
if(lower.startsWith("listening to"))
return Game.listening(game.substring(12).trim());
if(lower.startsWith("listening"))
return Game.listening(game.substring(9).trim());
if(lower.startsWith("watching"))
return Game.watching(game.substring(8).trim());
if(lower.startsWith("streaming"))
{
String[] parts = game.substring(9).trim().split("\\s+", 2);
if(parts.length == 2)
{
return Game.streaming(parts[1], "https://twitch.tv/"+parts[0]);
}
}
return Game.playing(game);
}
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 String 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, "Version", String.format(NEW_VERSION_AVAILABLE, version, latestVersion));
}
// Return the current version
return version;
}
public static String getCurrentVersion()
{
if(JMusicBot.class.getPackage()!=null && JMusicBot.class.getPackage().getImplementationVersion()!=null)
return JMusicBot.class.getPackage().getImplementationVersion();
else
return "UNKNOWN";
}
public static String getLatestVersion()
{
try
{
Response response = new OkHttpClient.Builder().build()
.newCall(new Request.Builder().get().url("https://api.github.com/repos/jagrosh/MusicBot/releases/latest").build())
.execute();
ResponseBody body = response.body();
if(body != null)
{
try(Reader reader = body.charStream())
{
JSONObject obj = new JSONObject(new JSONTokener(reader));
return obj.getString("tag_name");
}
finally
{
response.close();
}
}
else
return null;
}
catch(IOException | JSONException | NullPointerException ex)
{
return null;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
<!-- author: Kaidan Gustave -->
<configuration>
<appender name="Simple" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- Pattern -->
<pattern>
%nopex[%d{HH:mm:ss}] [%level] [%logger{0}]: %msg%n%ex
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="Simple"/>
</root>
</configuration>
+140
View File
@@ -0,0 +1,140 @@
/// 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)
// If you make this blank, the bot will not use a prefix
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 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 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"
// 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"
// 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
/// 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<>();
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<>();
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;
}
}
}