mirror of
https://github.com/chrisb09/MusicBot.git
synced 2026-11-05 17:02:35 -05:00
Compare commits
327
Commits
0.0.2-patch
...
0.6.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ff7b2f28e | ||
|
|
28dfdcf680 | ||
|
|
c5073b8755 | ||
|
|
5501f02a97 | ||
|
|
66e207eabe | ||
|
|
db3a57d517 | ||
|
|
81fa3fed25 | ||
|
|
9484c373ff | ||
|
|
860822d520 | ||
|
|
a0c313047f | ||
|
|
db69ea6787 | ||
|
|
5dba2c355b | ||
|
|
9fd246ac25 | ||
|
|
6a15bbb0df | ||
|
|
16bc176685 | ||
|
|
50e8bff831 | ||
|
|
9c012eb169 | ||
|
|
87771df1fb | ||
|
|
5d35f5bb4c | ||
|
|
ecd8cdf77b | ||
|
|
39e5bf7786 | ||
|
|
650a9ea996 | ||
|
|
0e0c05acf9 | ||
|
|
2e52ecac3b | ||
|
|
450ee7acb8 | ||
|
|
a7b56b67ab | ||
|
|
fedb97e1c7 | ||
|
|
68f951dc63 | ||
|
|
01eee8813a | ||
|
|
3df09a4cd2 | ||
|
|
0084ba7baf | ||
|
|
35e756df9b | ||
|
|
426e28ed8c | ||
|
|
dd72baca0d | ||
|
|
ac4e09cbd9 | ||
|
|
fd01d94cf2 | ||
|
|
95f72937c8 | ||
|
|
792cb7a8b7 | ||
|
|
03ba83f06b | ||
|
|
3b3a483b17 | ||
|
|
1874252ff6 | ||
|
|
aa8e430ff1 | ||
|
|
204bb6f04a | ||
|
|
9ec21bf329 | ||
|
|
b419191bad | ||
|
|
aa89145ae8 | ||
|
|
293733f0e7 | ||
|
|
7be02b1fe2 | ||
|
|
1ac46d9ec5 | ||
|
|
026ce87440 | ||
|
|
e5f34cc900 | ||
|
|
c9a2477f86 | ||
|
|
61a18a1db7 | ||
|
|
7fb44e296b | ||
|
|
4ae9f05776 | ||
|
|
84facb8b82 | ||
|
|
070938b326 | ||
|
|
58a9e76edb | ||
|
|
96ffcd8291 | ||
|
|
844a1bb75a | ||
|
|
6094e49ab2 | ||
|
|
88ce3e1149 | ||
|
|
463bf75783 | ||
|
|
348fc3ceec | ||
|
|
30eafb9239 | ||
|
|
c95f2248f3 | ||
|
|
b80b0c8459 | ||
|
|
4a8392fdb9 | ||
|
|
9d6584525c | ||
|
|
909b655f5c | ||
|
|
859e5c5862 | ||
|
|
43b63f1ba8 | ||
|
|
eca2563abf | ||
|
|
e6bfd1841f | ||
|
|
f140b41bf5 | ||
|
|
8733899aa0 | ||
|
|
e5fd05e601 | ||
|
|
6a5a9c7a75 | ||
|
|
8557f7ab8e | ||
|
|
48e62f1a50 | ||
|
|
6f12c33781 | ||
|
|
0afb3db269 | ||
|
|
81322ef993 | ||
|
|
b3ffbdba7d | ||
|
|
801b3769a1 | ||
|
|
49c3ec7960 | ||
|
|
6752695b43 | ||
|
|
bb17c984a2 | ||
|
|
a7807b96e2 | ||
|
|
2e9dd5df49 | ||
|
|
1ad16181d0 | ||
|
|
bb53bad1ff | ||
|
|
8a1066cc87 | ||
|
|
23c2dfa78e | ||
|
|
18bf6ac0f6 | ||
|
|
fd6de60fc7 | ||
|
|
4c8ff655d8 | ||
|
|
ac8661c904 | ||
|
|
4f926baf76 | ||
|
|
c5ecf89852 | ||
|
|
a52103620a | ||
|
|
fa5e33e2a5 | ||
|
|
781a94ea91 | ||
|
|
ff1ede9d12 | ||
|
|
90d9a1631a | ||
|
|
c4502bbe16 | ||
|
|
2d4f2671c9 | ||
|
|
e595de2033 | ||
|
|
53875f2791 | ||
|
|
cd7e643228 | ||
|
|
0e1f0d3bfa | ||
|
|
2c76164f6d | ||
|
|
788cd40f2c | ||
|
|
1e12e4e01d | ||
|
|
c4cbfe2a11 | ||
|
|
bbed9e8990 | ||
|
|
354a0b3def | ||
|
|
2faef67b30 | ||
|
|
aee076c854 | ||
|
|
b76f6fa07c | ||
|
|
ec4e9e624c | ||
|
|
a7be2c4602 | ||
|
|
6007d3e99b | ||
|
|
a17b2f20ee | ||
|
|
4375d4caa8 | ||
|
|
c22807bcbb | ||
|
|
baf629a910 | ||
|
|
c4e11d682e | ||
|
|
041ab27b94 | ||
|
|
85aebe8a89 | ||
|
|
3c306a8d7a | ||
|
|
cadaf94cf7 | ||
|
|
15a47a7b4d | ||
|
|
92fca22059 | ||
|
|
0cdc0d2d34 | ||
|
|
1d1dfe103e | ||
|
|
ab39d2cd48 | ||
|
|
32d165d1b3 | ||
|
|
8457f944cd | ||
|
|
c404e92932 | ||
|
|
6af86cae5d | ||
|
|
9421730229 | ||
|
|
b76e31c7e2 | ||
|
|
6056ff2c13 | ||
|
|
8c92a7a75a | ||
|
|
e942f3ef21 | ||
|
|
157da6c0da | ||
|
|
03b909a3f5 | ||
|
|
e08c1491c2 | ||
|
|
95de05ff2d | ||
|
|
e242fa9557 | ||
|
|
b83028fb97 | ||
|
|
7273b5bf42 | ||
|
|
6f6064521b | ||
|
|
48263ec60a | ||
|
|
c6e5b8cbda | ||
|
|
3b8892d2c0 | ||
|
|
54cb757dc5 | ||
|
|
c4de816c7c | ||
|
|
ccb421e110 | ||
|
|
23334e4253 | ||
|
|
826aec2492 | ||
|
|
c397afcf8d | ||
|
|
95c07eab8e | ||
|
|
58e0e5737d | ||
|
|
84bc57e3cf | ||
|
|
67f6169e61 | ||
|
|
6f7c47757b | ||
|
|
0a353e0fa3 | ||
|
|
ddf95da19f | ||
|
|
d5bb16d143 | ||
|
|
d47cfee8bf | ||
|
|
1ee089e7ad | ||
|
|
7255e4b6db | ||
|
|
4e314da86d | ||
|
|
ec06cf10e9 | ||
|
|
ad972185d4 | ||
|
|
bc64b45d5c | ||
|
|
699c2a0e6e | ||
|
|
ebf6e43084 | ||
|
|
6527afb88c | ||
|
|
18e142ed88 | ||
|
|
797d306a4e | ||
|
|
267bf71d47 | ||
|
|
88a89f2178 | ||
|
|
8d2f60eba5 | ||
|
|
b4c9a125e5 | ||
|
|
36c53cfd41 | ||
|
|
d1223e9795 | ||
|
|
cebdbc3aa5 | ||
|
|
be4f235071 | ||
|
|
e8df9e9523 | ||
|
|
9a79308065 | ||
|
|
7a55e76a35 | ||
|
|
c92c55ca52 | ||
|
|
2bb43b57ba | ||
|
|
403a43530f | ||
|
|
51f9024498 | ||
|
|
9f7ad33707 | ||
|
|
fa5241490f | ||
|
|
27fd75aa67 | ||
|
|
c5a583b96f | ||
|
|
f1e301b48e | ||
|
|
61d4c51276 | ||
|
|
cbdbe7bf4b | ||
|
|
baa3a60b05 | ||
|
|
338dafca0c | ||
|
|
b1dddb649b | ||
|
|
3eb284da55 | ||
|
|
5fb969283d | ||
|
|
ae8b5cfe90 | ||
|
|
b6fa7de7e2 | ||
|
|
a4f13ca2de | ||
|
|
5172deb12d | ||
|
|
179f447a70 | ||
|
|
f4a937cc6b | ||
|
|
0b4c6c9af1 | ||
|
|
f17058a344 | ||
|
|
4fcf86a5d6 | ||
|
|
94362d640b | ||
|
|
7192d55732 | ||
|
|
11c9b00510 | ||
|
|
4579d6ca09 | ||
|
|
c3a9937c90 | ||
|
|
8650f82210 | ||
|
|
3ba13d2daa | ||
|
|
dace101c30 | ||
|
|
05e8ec6814 | ||
|
|
646ed6e9b1 | ||
|
|
a501b00ab7 | ||
|
|
6ae683b749 | ||
|
|
235af8c8c6 | ||
|
|
33e4e8e5ce | ||
|
|
20b23a3b0d | ||
|
|
d616c1e386 | ||
|
|
e6f2b23ca9 | ||
|
|
fbfea7ab1f | ||
|
|
bc85fb9597 | ||
|
|
a34f06669d | ||
|
|
6508c2b62c | ||
|
|
fb53b9e8ea | ||
|
|
75abab2832 | ||
|
|
13d9fa87d8 | ||
|
|
c0d40b2594 | ||
|
|
327cfe7d21 | ||
|
|
b4e83fd3f0 | ||
|
|
c902c1eace | ||
|
|
39a787c3c9 | ||
|
|
cdc20ce500 | ||
|
|
7dcd7badb3 | ||
|
|
1a0a73ac7c | ||
|
|
eb1fb776a5 | ||
|
|
82349d380b | ||
|
|
3c5deca55e | ||
|
|
e1b9e0165a | ||
|
|
2e701d0d23 | ||
|
|
259e3d716b | ||
|
|
43398449c7 | ||
|
|
e7f00fe088 | ||
|
|
1a9364acae | ||
|
|
19234998f1 | ||
|
|
31037775d1 | ||
|
|
e0b1bb0d06 | ||
|
|
1f810aa36f | ||
|
|
e7a8cec0b4 | ||
|
|
ae07ac2598 | ||
|
|
d75098fdd9 | ||
|
|
ac87cc13d7 | ||
|
|
34bffe0148 | ||
|
|
15f3cf07a8 | ||
|
|
c0f0d45138 | ||
|
|
2eddaccb86 | ||
|
|
87b2f4f93a | ||
|
|
88970314ad | ||
|
|
0de4e2afcc | ||
|
|
2671ac18c8 | ||
|
|
68a992c79e | ||
|
|
9bf233ec79 | ||
|
|
b41dcded98 | ||
|
|
b6239ed608 | ||
|
|
9dfe613921 | ||
|
|
865119e2ae | ||
|
|
c616f9c92a | ||
|
|
2737a85a61 | ||
|
|
c88979a6dd | ||
|
|
a6f7c9b3ef | ||
|
|
2aac68b412 | ||
|
|
e1b07868fd | ||
|
|
aaec886b81 | ||
|
|
a7a4b55011 | ||
|
|
bcb7e03aac | ||
|
|
c49a559e34 | ||
|
|
af98eb5e01 | ||
|
|
e47c3efc58 | ||
|
|
058c3e18d2 | ||
|
|
38ae19cbec | ||
|
|
2eeffb374f | ||
|
|
e478fa43c8 | ||
|
|
1093f92d67 | ||
|
|
6148dc6a89 | ||
|
|
a20ade9016 | ||
|
|
fbbb7bd3a7 | ||
|
|
0c44160023 | ||
|
|
13427cdab7 | ||
|
|
d0ffc0308c | ||
|
|
fe62e03367 | ||
|
|
5deb41c651 | ||
|
|
6b454c299d | ||
|
|
dddf4c8e38 | ||
|
|
0912de74e9 | ||
|
|
3752e5b9e0 | ||
|
|
ae62d5771f | ||
|
|
434749a250 | ||
|
|
cb9644d694 | ||
|
|
8f7e53a37c | ||
|
|
b158e70495 | ||
|
|
ebed4f972a | ||
|
|
f8b15d9591 | ||
|
|
ab1e86d4ca | ||
|
|
9da577aa60 | ||
|
|
d7f690bd9a | ||
|
|
0f52bbd170 | ||
|
|
61c62032f2 | ||
|
|
eb6be6b589 | ||
|
|
25f262c6e4 | ||
|
|
f1cca0b305 | ||
|
|
6c9a497725 |
No files matched your search
@@ -0,0 +1,52 @@
|
||||
# Java Maven CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-java/ for more details
|
||||
#
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: cimg/openjdk:11.0
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
# documented at https://circleci.com/docs/2.0/circleci-images/
|
||||
# - image: circleci/postgres:9.4
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
environment:
|
||||
# Customize the JVM maximum heap limit
|
||||
MAVEN_OPTS: -Xmx3200m
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "pom.xml" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
|
||||
- run: mvn dependency:go-offline
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: v1-dependencies-{{ checksum "pom.xml" }}
|
||||
|
||||
# run tests!
|
||||
- run: mvn integration-test
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- build:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- gh-pages
|
||||
- gh-pages-generated
|
||||
@@ -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
|
||||
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: jagrosh
|
||||
patreon: jagrosh
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Bug Report
|
||||
description: Report a bug or unexpected behavior
|
||||
title: "[Bug Report] Title Here "
|
||||
labels: []
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: Please provide a clear and concise description of what the bug is.
|
||||
placeholder: The bug is...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Provide steps you can take to reliably reproduce this bug
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Result
|
||||
description: What did you expect to happen when this bug occurred?
|
||||
placeholder: I expected...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: debug
|
||||
attributes:
|
||||
label: Debug Output
|
||||
description: Please run the `debug` command and paste it here or upload it as an attachment. If you cannot run the command, please provide information about your system such as operating system version, JMusicBot version, etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-info
|
||||
attributes:
|
||||
label: Additional Info
|
||||
description: Any additional info that you think might be useful or relevant to this bug
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Before submitting your issue, please affirm the following
|
||||
options:
|
||||
- label: I have looked for information about this [within the documentation](https://jmusicbot.com/)
|
||||
required: true
|
||||
- label: I have searched for similar issues [on the issues page](https://github.com/jagrosh/MusicBot/issues?q=is%3Aissue)
|
||||
required: true
|
||||
- label: I am using an official release from [the releases page](https://github.com/jagrosh/MusicBot/releases)
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: JMusicBot Version
|
||||
description: Which version of JMusicBot are you running?
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://github.com/jagrosh/MusicBot/discussions/categories/questions
|
||||
about: For general questions, please check the Discussions
|
||||
- name: Troubleshooting
|
||||
url: https://github.com/jagrosh/MusicBot/discussions/categories/troubleshooting
|
||||
about: For troubleshooting help, please check the Discussions
|
||||
@@ -0,0 +1,52 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature Request] Title Here"
|
||||
labels: []
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: Please provide a clear and concise description of what the problem is.
|
||||
placeholder: A common problem users face is...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: What is your ideal solution to the problem?
|
||||
description: Describe a clear and concise description of what you want to happen.
|
||||
placeholder: My solution to the above problem is...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: procedure
|
||||
attributes:
|
||||
label: How would this feature be used?
|
||||
description: Describe the basic flow/steps of using this feature.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-info
|
||||
attributes:
|
||||
label: Additional Info
|
||||
description: Any additional info that you think might be useful or relevant to this feature request
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Before submitting your issue, please affirm the following
|
||||
options:
|
||||
- label: I have checked [the documentation](https://jmusicbot.com/) to make sure this feature doesn't already exist
|
||||
required: true
|
||||
- label: I have searched for [similar feature requests](https://github.com/jagrosh/MusicBot/labels/enhancement)
|
||||
required: true
|
||||
- label: I have confirmed that my requests is **not** on the [list of things that will not be added](https://jmusicbot.com/things-that-wont-be-added)
|
||||
required: true
|
||||
- label: "I am running the latest version of the bot: "
|
||||
required: true
|
||||
@@ -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 #...
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Build and Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
- name: Build and Test with Maven
|
||||
run: mvn --batch-mode --update-snapshots verify
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Make Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_number:
|
||||
description: 'Version Number'
|
||||
required: true
|
||||
type: string
|
||||
info:
|
||||
description: 'Description of this Release'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build_jar:
|
||||
name: Build Jar
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
- name: Set Version
|
||||
uses: datamonsters/replace-action@v2
|
||||
with:
|
||||
files: 'pom.xml'
|
||||
replacements: 'Snapshot=${{ github.event.inputs.version_number }}'
|
||||
- name: Build with Maven
|
||||
run: mvn --batch-mode --update-snapshots verify
|
||||
- name: Rename jar
|
||||
run: mv target/*-All.jar JMusicBot-${{ github.event.inputs.version_number }}.jar
|
||||
- name: Upload jar
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: jar
|
||||
path: JMusicBot-${{ github.event.inputs.version_number }}.jar
|
||||
if-no-files-found: error
|
||||
create_release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: build_jar
|
||||
steps:
|
||||
- name: Download a Build Artifact
|
||||
uses: actions/download-artifact@v3.0.0
|
||||
with:
|
||||
name: jar
|
||||
path: .
|
||||
- name: Show Artifacts
|
||||
run: ls -R
|
||||
- name: Create Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.event.inputs.version_number }}
|
||||
name: Version ${{ github.event.inputs.version_number }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
artifacts: "*.jar"
|
||||
body: |
|
||||
${{ github.event.inputs.info }}
|
||||
|
||||
---
|
||||
### Setup
|
||||
https://jmusicbot.com/setup
|
||||
https://jmusicbot.com/config
|
||||
# Download: [JMusicBot-${{ github.event.inputs.version_number }}.jar](https://github.com/jagrosh/MusicBot/releases/download/${{ github.event.inputs.version_number }}/JMusicBot-${{ github.event.inputs.version_number }}.jar)
|
||||
@@ -0,0 +1,9 @@
|
||||
/nbproject/
|
||||
/target/
|
||||
/Playlists/
|
||||
/test/
|
||||
*.json
|
||||
*.txt
|
||||
nb*.xml
|
||||
dockerfile
|
||||
.*
|
||||
@@ -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.
|
||||
@@ -1,22 +1,122 @@
|
||||
# JMusicBot
|
||||
A Discord music bot with a clean interface, and that is easy to set up and run yourself
|
||||
<img align="right" src="https://i.imgur.com/zrE80HY.png" height="200" width="200">
|
||||
|
||||
# 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
|
||||
# JMusicBot Fork
|
||||
|
||||
# Setup
|
||||
Please see the [Setup Page](https://github.com/jagrosh/MusicBot/wiki/Setup) in the wiki to run this bot yourself!
|
||||
A self-hosted Discord music bot fork based on JMusicBot, updated to keep working on modern Discord and JDA versions.
|
||||
|
||||
# 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)
|
||||
This repository keeps the bot practical to run today, while also adding fork-specific features such as status messages and playback analytics logging. Possibly more.
|
||||
|
||||
# Example
|
||||

|
||||
## Demo
|
||||
|
||||
Temporary invite: [discord.gg/4cp9CvjwaW](https://discord.gg/4cp9CvjwaW)
|
||||
Permanent invite: [discord.gg/hK95396uB3](https://discord.gg/hK95396uB3)
|
||||
|
||||
## What This Fork Adds
|
||||
- Make it work again, through:
|
||||
- JDA 6 support and current dependency updates
|
||||
- DAVE voice encryption support for current Discord voice requirements
|
||||
- Optional YouTube account login support
|
||||
- Optional YouTube PO token and visitor-data support
|
||||
- New niche features, such as:
|
||||
- Optional per-channel playback status message
|
||||
- Optional JDBC/SQLite playback analytics logging
|
||||
- Basic QoL improvements, such as:
|
||||
- Docker image and container-friendly deployment path
|
||||
|
||||
## Quick Links
|
||||
|
||||
- Fork changes and version notes: [docs/fork-changelog.md](/git/MusicBot/docs/fork-changelog.md)
|
||||
- Setup guide: [docs/setup.md](/git/MusicBot/docs/setup.md)
|
||||
- Docker usage: [docs/docker.md](/git/MusicBot/docs/docker.md)
|
||||
|
||||
## Features
|
||||
|
||||
- Easy self-hosting
|
||||
- Fast song loading
|
||||
- Smooth playback
|
||||
- DJ role and server-specific settings
|
||||
- Queue management and playlist support
|
||||
- Support for many sources, streams, and local files
|
||||
|
||||
## Supported Sources And Formats
|
||||
|
||||
JMusicBot supports all sources and formats supported by [lavaplayer](https://github.com/sedmelluq/lavaplayer#supported-formats).
|
||||
|
||||
Sources:
|
||||
- YouTube
|
||||
- SoundCloud
|
||||
- Bandcamp
|
||||
- Vimeo
|
||||
- Twitch streams
|
||||
- Local files
|
||||
- HTTP URLs
|
||||
|
||||
Formats:
|
||||
- MP3
|
||||
- FLAC
|
||||
- WAV
|
||||
- Matroska/WebM
|
||||
- MP4/M4A
|
||||
- OGG streams
|
||||
- AAC streams
|
||||
- Stream playlists such as M3U and PLS
|
||||
|
||||
## Setup Notes
|
||||
|
||||
If you only need the essentials:
|
||||
|
||||
1. Create a Discord bot and get its token, see [here](https://jmusicbot.com/getting-a-bot-token/).
|
||||
2. Enable the `MESSAGE_CONTENT` intent in the Discord Developer Portal [here](https://discord.com/developers/applications) ([Wiki link](https://jda.wiki/using-jda/gateway-intents-and-member-cache-policy/)).
|
||||
3. Configure `config.txt`.
|
||||
4. Run the jar directly or use Docker.
|
||||
|
||||
For the detailed version, see [docs/setup.md](/git/MusicBot/docs/setup.md).
|
||||
|
||||
## Docker
|
||||
|
||||
A Docker image is published here: [docker.io/chrisb09/jmusicbot](https://hub.docker.com/r/chrisb09/jmusicbot)
|
||||
|
||||
For compose examples and deployment notes, see [docs/docker.md](/git/MusicBot/docs/docker.md).
|
||||
|
||||
## YouTube Login
|
||||
|
||||
This fork supports Google account login for YouTube playback workarounds.
|
||||
|
||||
Enable:
|
||||
|
||||
```conf
|
||||
youtubeoauth2=true
|
||||
```
|
||||
|
||||
Do not use your main Google account. Use a burner or otherwise disposable account instead.
|
||||
|
||||
More details are in [docs/setup.md](/git/MusicBot/docs/setup.md).
|
||||
|
||||
If you use PO tokens or visitor data for YouTube, those details are also covered in [docs/setup.md](/git/MusicBot/docs/setup.md).
|
||||
|
||||
## DAVE / Voice Encryption
|
||||
|
||||
Discord currently requires DAVE for voice on modern API versions. This fork configures `libdave-jvm` during startup and bundles native binaries for common Linux, Windows, and macOS targets.
|
||||
|
||||
If you deploy on an unsupported architecture, add the matching native dependency in [pom.xml](/git/MusicBot/pom.xml). You probably need to build the library from source for your architecture, so have fun with that.
|
||||
|
||||
## Upstream And Related Work
|
||||
|
||||
This fork incorporates ideas and patches related to:
|
||||
|
||||
- [PR-1670](https://github.com/jagrosh/MusicBot/pull/1670)
|
||||
- [PR-1703](https://github.com/jagrosh/MusicBot/pull/1703)
|
||||
- [PR-1772](https://github.com/jagrosh/MusicBot/pull/1772)
|
||||
|
||||
If this fork does not fit your needs, another fork you may want to inspect is [SeVile/MusicBot](https://github.com/SeVile/MusicBot).
|
||||
|
||||
## Questions And Contributions
|
||||
|
||||
If you run into issues, please include:
|
||||
|
||||
- the version you are running
|
||||
- whether you are using Docker or the jar directly
|
||||
- relevant config changes
|
||||
- relevant logs
|
||||
|
||||
Fork-specific version notes live in [docs/fork-changelog.md](/git/MusicBot/docs/fork-changelog.md), which should help with troubleshooting and migration as the fork evolves.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Docker Guide
|
||||
|
||||
This fork supports running the bot in Docker, which is usually the easiest deployment path for a server.
|
||||
|
||||
## Image
|
||||
|
||||
Published image:
|
||||
|
||||
- [docker.io/chrisb09/jmusicbot](https://hub.docker.com/r/chrisb09/jmusicbot)
|
||||
|
||||
Container build sources:
|
||||
|
||||
- [github.com/chrisb09/jmb-container](https://github.com/chrisb09/jmb-container)
|
||||
|
||||
## Minimal Compose Example
|
||||
|
||||
```yaml
|
||||
version: "2.2"
|
||||
|
||||
services:
|
||||
jmusicbot:
|
||||
image: chrisb09/jmusicbot:latest
|
||||
container_name: jmusicbot
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config:/jmb/config
|
||||
```
|
||||
|
||||
Replace `./config` with the host path you actually want to use.
|
||||
|
||||
## Config Files
|
||||
|
||||
Your persistent bot config should live in the mounted config directory. That lets you redeploy containers without losing your bot settings.
|
||||
|
||||
Typical things to configure there:
|
||||
|
||||
- bot token
|
||||
- owner ID
|
||||
- prefix
|
||||
- YouTube login toggle
|
||||
- YouTube PO token and visitor-data values if needed
|
||||
- status message toggle
|
||||
- analytics logging settings
|
||||
|
||||
## Deploy Flow
|
||||
|
||||
A typical update flow is:
|
||||
|
||||
1. Pull or rebuild the image.
|
||||
2. Restart the container.
|
||||
3. Check logs for startup errors.
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
docker-compose logs -f jmusicbot
|
||||
```
|
||||
|
||||
## Alpine / musl Note
|
||||
|
||||
This fork bundles native support for Alpine-compatible musl environments as part of its DAVE setup, so Alpine-based container deployments are supported.
|
||||
|
||||
## SQLite Logging In Docker
|
||||
|
||||
If you enable analytics logging with SQLite:
|
||||
|
||||
```conf
|
||||
dblog.enabled = true
|
||||
dblog.jdbc_url = "jdbc:sqlite:musicbot.db"
|
||||
```
|
||||
|
||||
make sure the database file lives somewhere inside your mounted persistent config or data path if you want it to survive container recreation.
|
||||
|
||||
## YouTube Token Notes In Docker
|
||||
|
||||
If you use YouTube login or PO token based configuration, keep those values in your persistent mounted config rather than baking them into the image.
|
||||
|
||||
Relevant config values include:
|
||||
|
||||
```conf
|
||||
youtubeoauth2 = true
|
||||
ytpotoken = "PO_TOKEN_HERE"
|
||||
ytvisitordata = "VISITOR_DATA_HERE"
|
||||
```
|
||||
|
||||
## Recommendation
|
||||
|
||||
For production-style use, Docker is the cleanest path if you want:
|
||||
|
||||
- reproducible runtime behavior
|
||||
- simple upgrades
|
||||
- easier separation between bot code and persistent config
|
||||
@@ -0,0 +1,57 @@
|
||||
# Fork Changelog
|
||||
|
||||
This file tracks fork-specific changes that are useful to operators and contributors. It is not meant to replace commit history, but to summarize what changed at a higher level.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
- Added JDBC-backed playback analytics logging.
|
||||
- Added SQLite as the default logging backend.
|
||||
- Logged queue, playback, listener, search, skip, and command activity.
|
||||
- Added `dblog.*` config settings to enable and configure logging.
|
||||
|
||||
Config:
|
||||
|
||||
```conf
|
||||
dblog.enabled = true
|
||||
dblog.jdbc_url = "jdbc:sqlite:musicbot.db"
|
||||
dblog.user = ""
|
||||
dblog.password = ""
|
||||
```
|
||||
|
||||
By default, logging is disabled.
|
||||
|
||||
## 0.6.2
|
||||
|
||||
- Fixed several status-message race conditions and repost issues.
|
||||
- Added debug logging around status-message creation, editing, deletion, and repost flow.
|
||||
- Added a config toggle to disable the status-message feature entirely.
|
||||
|
||||
Config:
|
||||
|
||||
```conf
|
||||
statusmessages = true
|
||||
```
|
||||
|
||||
Set it to `false` to disable the feature.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
- Bumped the fork version after the initial status-message work had landed.
|
||||
|
||||
## 0.6.0
|
||||
|
||||
- Updated the bot for modern JDA and Discord requirements.
|
||||
- Added DAVE voice support required for current Discord voice playback.
|
||||
- Continued dependency refreshes for lavaplayer and related components.
|
||||
- Carried forward work related to YouTube login, PO token handling, and buildability on the newer stack.
|
||||
|
||||
Imported work included in this fork:
|
||||
|
||||
- PR-1670, support for logging into a YouTube account, is included from `0.4.5` onward.
|
||||
- PR-1703, changes to allow building the project from source more easily, is included from `0.4.5` onward.
|
||||
- `jagrosh#1772`, YouTube PO token and IP rotation support, is included from `0.4.7` onward.
|
||||
|
||||
## Notes
|
||||
|
||||
- This fork may move faster than upstream in areas related to Discord API compatibility and deployment practicality.
|
||||
- When reporting issues, mention the exact fork version because behavior around playback, status messages, and logging has changed across recent releases.
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# Setup Guide
|
||||
|
||||
This guide covers the normal non-Docker setup path and the main configuration points that matter on this fork.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Java 11 or newer
|
||||
- A Discord bot token
|
||||
- A Discord application with the bot invited to your server
|
||||
|
||||
## Discord Setup
|
||||
|
||||
1. Create a bot in the [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
2. Copy the bot token.
|
||||
3. Enable the `MESSAGE_CONTENT` intent.
|
||||
|
||||
This intent is required for command-based bots like this one.
|
||||
|
||||
## Privileged Intent Notes
|
||||
|
||||
This fork moved from JDA 4 to JDA 6, so `MESSAGE_CONTENT` is now an important operational requirement for the classic text-command flow.
|
||||
|
||||
Keep in mind:
|
||||
|
||||
- If the bot joins more than 100 servers, this intent must be enabled in the [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
- `MESSAGE_CONTENT` is a privileged intent.
|
||||
- Discord verification requirements become relevant as the bot grows.
|
||||
- The related portal controls historically only become visible once the application is already in a fairly large number of servers.
|
||||
|
||||
For the broader Discord/JDA behavior, see the [JDA gateway intents documentation](https://jda.wiki/using-jda/gateway-intents-and-member-cache-policy/).
|
||||
|
||||
## First Configuration
|
||||
|
||||
The bot reads from `config.txt` using the values defined in [reference.conf](/git/MusicBot/src/main/resources/reference.conf).
|
||||
|
||||
The minimum settings you should care about first are:
|
||||
|
||||
```conf
|
||||
token = "YOUR_BOT_TOKEN"
|
||||
owner = 123456789012345678
|
||||
prefix = "@mention"
|
||||
```
|
||||
|
||||
Useful fork-specific options:
|
||||
|
||||
```conf
|
||||
youtubeoauth2 = true
|
||||
statusmessages = true
|
||||
dblog.enabled = false
|
||||
```
|
||||
|
||||
You may also need these for YouTube PO token support:
|
||||
|
||||
```conf
|
||||
ytpotoken = "PO_TOKEN_HERE"
|
||||
ytvisitordata = "VISITOR_DATA_HERE"
|
||||
```
|
||||
|
||||
## Running The Bot
|
||||
|
||||
Build:
|
||||
|
||||
```bash
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
java -jar target/JMusicBot-*-All.jar
|
||||
```
|
||||
|
||||
This fork includes changes to keep source builds practical on the newer stack as well.
|
||||
|
||||
## YouTube Login
|
||||
|
||||
If YouTube playback is unreliable, enable:
|
||||
|
||||
```conf
|
||||
youtubeoauth2 = true
|
||||
```
|
||||
|
||||
After startup, the bot prints instructions and sends the owner a DM with the authorization flow.
|
||||
|
||||
Important:
|
||||
|
||||
- Do not use your main Google account.
|
||||
- Use a burner or otherwise disposable account.
|
||||
|
||||
If you are starting from a clean config, this option should already exist with a default of `false`.
|
||||
|
||||
## YouTube PO Tokens And Visitor Data
|
||||
|
||||
This fork also supports explicit YouTube PO token and visitor-data configuration.
|
||||
|
||||
Config:
|
||||
|
||||
```conf
|
||||
ytpotoken = "PO_TOKEN_HERE"
|
||||
ytvisitordata = "VISITOR_DATA_HERE"
|
||||
```
|
||||
|
||||
You can generate these values with:
|
||||
|
||||
- [iv-org/youtube-trusted-session-generator](https://github.com/iv-org/youtube-trusted-session-generator)
|
||||
|
||||
This is useful when normal YouTube playback is not enough and you need a more explicit trusted-session style setup.
|
||||
|
||||
## DAVE / Voice Support
|
||||
|
||||
This fork includes DAVE support for current Discord voice requirements. In normal supported environments, nothing extra is required beyond using the provided shaded jar.
|
||||
|
||||
If you run on an unsupported architecture, add the correct native `libdave-jvm` dependency in [pom.xml](/git/MusicBot/pom.xml).
|
||||
|
||||
Bundled native targets include:
|
||||
|
||||
- Linux glibc x86_64
|
||||
- Linux musl x86_64
|
||||
- Windows x86_64
|
||||
- macOS
|
||||
|
||||
## Status Messages
|
||||
|
||||
The bot can maintain a single playback status message in the active text channel.
|
||||
|
||||
Config:
|
||||
|
||||
```conf
|
||||
statusmessages = true
|
||||
```
|
||||
|
||||
Disable it with:
|
||||
|
||||
```conf
|
||||
statusmessages = false
|
||||
```
|
||||
|
||||
## Analytics Logging
|
||||
|
||||
Playback analytics logging is off by default.
|
||||
|
||||
Enable SQLite logging:
|
||||
|
||||
```conf
|
||||
dblog.enabled = true
|
||||
dblog.jdbc_url = "jdbc:sqlite:musicbot.db"
|
||||
```
|
||||
|
||||
This creates a local SQLite database file and records queue, playback, listener, and command activity.
|
||||
|
||||
## Windows Note
|
||||
|
||||
If you run the bot directly on Windows rather than in Docker, install Java first and consider a service wrapper such as [NSSM](http://nssm.cc/) if you want it to run in the background.
|
||||
|
||||
## Related Fork Context
|
||||
|
||||
This fork incorporates or builds on work related to:
|
||||
|
||||
- [PR-1670](https://github.com/jagrosh/MusicBot/pull/1670) for YouTube account login support
|
||||
- [PR-1703](https://github.com/jagrosh/MusicBot/pull/1703) for source builds
|
||||
- [PR-1772](https://github.com/jagrosh/MusicBot/pull/1772) for YouTube PO token and IP rotation work
|
||||
|
||||
If this fork does not fit your setup, another fork you may want to inspect is [SeVile/MusicBot](https://github.com/SeVile/MusicBot).
|
||||
@@ -0,0 +1,293 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.jagrosh</groupId>
|
||||
<artifactId>JMusicBot</artifactId>
|
||||
<version>0.6.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>m2-chew</id>
|
||||
<name>Maven Chewtral</name>
|
||||
<url>https://m2.chew.pro/releases</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>dv8tion</id>
|
||||
<name>m2-dv8tion</name>
|
||||
<url>https://m2.dv8tion.net/releases</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<name>Maven Central</name>
|
||||
<url>https://repo.maven.apache.org/maven2</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
<updatePolicy>always</updatePolicy>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>m2.duncte123.dev</id>
|
||||
<name>m2-duncte123</name>
|
||||
<url>https://m2.duncte123.dev/releases</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>arbjergDev</id>
|
||||
<name>Lavalink Repository</name>
|
||||
<url>https://maven.lavalink.dev/releases</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>arbjergDevSnapshots</id>
|
||||
<name>Lavalink Repository</name>
|
||||
<url>https://maven.lavalink.dev/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- Discord Dependencies -->
|
||||
<dependency>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
<version>6.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>moe.kyokobot.libdave</groupId>
|
||||
<artifactId>adapter-jda</artifactId>
|
||||
<version>0.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>moe.kyokobot.libdave</groupId>
|
||||
<artifactId>impl-jni</artifactId>
|
||||
<version>0.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>moe.kyokobot.libdave</groupId>
|
||||
<artifactId>natives-linux-x86-64</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>moe.kyokobot.libdave</groupId>
|
||||
<artifactId>natives-linux-musl-x86-64</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>moe.kyokobot.libdave</groupId>
|
||||
<artifactId>natives-win-x86-64</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>moe.kyokobot.libdave</groupId>
|
||||
<artifactId>natives-darwin</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>pw.chew</groupId>
|
||||
<artifactId>jda-chewtils-command</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>pw.chew</groupId>
|
||||
<artifactId>jda-chewtils-commons</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>pw.chew</groupId>
|
||||
<artifactId>jda-chewtils-examples</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>pw.chew</groupId>
|
||||
<artifactId>jda-chewtils-menu</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>com.github.chew.jda-chewtils</groupId>
|
||||
<artifactId>jda-chewtils-command</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.chew.jda-chewtils</groupId>
|
||||
<artifactId>jda-chewtils-menu</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.chew.jda-chewtils</groupId>
|
||||
<artifactId>jda-chewtils-commons</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency> -->
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>com.github.chew.jda-chewtils</groupId>
|
||||
<artifactId>jda-chewtils-command</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.chew.jda-chewtils</groupId>
|
||||
<artifactId>jda-chewtils-menu</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.chew.jda-chewtils</groupId>
|
||||
<artifactId>jda-chewtils-commons</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
-->
|
||||
<!-- Music Dependencies -->
|
||||
<dependency>
|
||||
<groupId>dev.arbjerg</groupId>
|
||||
<artifactId>lavaplayer</artifactId>
|
||||
<version>2.2.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.arbjerg</groupId>
|
||||
<artifactId>lavaplayer-ext-youtube-rotator</artifactId>
|
||||
<version>2.2.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.lavalink.youtube</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.16.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.jagrosh</groupId>
|
||||
<artifactId>JLyrics</artifactId>
|
||||
<version>master</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.dunctebot</groupId>
|
||||
<artifactId>sourcemanagers</artifactId>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Misc Internal Dependencies -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.21</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.46.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20240303</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.typesafe</groupId>
|
||||
<artifactId>config</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.14.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-java</artifactId>
|
||||
<version>4.27.0</version>
|
||||
<!-- https://github.com/SeleniumHQ/selenium/issues/13791 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-devtools-v85</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-devtools-v129</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-devtools-v130</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.bonigarcia</groupId>
|
||||
<artifactId>webdrivermanager</artifactId>
|
||||
<version>5.9.2</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<!-- Testing Dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<shadedClassifierName>All</shadedClassifierName>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>*:*</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>reference.conf</resource>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Main-Class>com.jagrosh.jmusicbot.JMusicBot</Main-Class>
|
||||
<Specification-Title>${project.artifactId}</Specification-Title>
|
||||
<Specification-Version>${project.version}</Specification-Version>
|
||||
<Implementation-Title>${project.artifactId}</Implementation-Title>
|
||||
<Implementation-Version>${project.version}</Implementation-Version>
|
||||
<Implementation-Vendor-Id>${project.groupId}</Implementation-Vendor-Id>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
</project>
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This will have this script check for a new version of JMusicBot every
|
||||
# startup (and download it if the latest version isn't currently downloaded)
|
||||
DOWNLOAD=true
|
||||
|
||||
# This will cause the script to run in a loop so that the bot auto-restarts
|
||||
# when you use the shutdown command
|
||||
LOOP=true
|
||||
|
||||
download() {
|
||||
if [ $DOWNLOAD = true ]; then
|
||||
URL=$(curl -s https://api.github.com/repos/jagrosh/MusicBot/releases/latest \
|
||||
| grep -i "browser_download_url.*\.jar" \
|
||||
| sed 's/.*\(http.*\)"/\1/')
|
||||
FILENAME=$(echo $URL | sed 's/.*\/\([^\/]*\)/\1/')
|
||||
if [ -f $FILENAME ]; then
|
||||
echo "Latest version already downloaded (${FILENAME})"
|
||||
else
|
||||
curl -L $URL -o $FILENAME
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
run() {
|
||||
java -Dnogui=true -jar $(ls -t JMusicBot* | head -1)
|
||||
}
|
||||
|
||||
while
|
||||
download
|
||||
run
|
||||
$LOOP
|
||||
do
|
||||
continue
|
||||
done
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
|
||||
import com.jagrosh.jmusicbot.audio.AloneInVoiceHandler;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.NowplayingHandler;
|
||||
import com.jagrosh.jmusicbot.audio.PlayerManager;
|
||||
import com.jagrosh.jmusicbot.audio.StatusMessageHandler;
|
||||
import com.jagrosh.jmusicbot.datalog.DataLogService;
|
||||
import com.jagrosh.jmusicbot.gui.GUI;
|
||||
import com.jagrosh.jmusicbot.playlist.PlaylistLoader;
|
||||
import com.jagrosh.jmusicbot.settings.SettingsManager;
|
||||
import java.util.Objects;
|
||||
import com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class Bot
|
||||
{
|
||||
private final EventWaiter waiter;
|
||||
private final ScheduledExecutorService threadpool;
|
||||
private final BotConfig config;
|
||||
private final SettingsManager settings;
|
||||
private final PlayerManager players;
|
||||
private final PlaylistLoader playlists;
|
||||
private final NowplayingHandler nowplaying;
|
||||
private final StatusMessageHandler statusMessageHandler;
|
||||
private final AloneInVoiceHandler aloneInVoiceHandler;
|
||||
private final DataLogService dataLogService;
|
||||
private final YoutubeOauth2TokenHandler youTubeOauth2TokenHandler;
|
||||
|
||||
private boolean shuttingDown = false;
|
||||
private JDA jda;
|
||||
private GUI gui;
|
||||
|
||||
public Bot(EventWaiter waiter, BotConfig config, SettingsManager settings)
|
||||
{
|
||||
this.waiter = waiter;
|
||||
this.config = config;
|
||||
this.settings = settings;
|
||||
this.playlists = new PlaylistLoader(config);
|
||||
this.threadpool = Executors.newSingleThreadScheduledExecutor();
|
||||
this.youTubeOauth2TokenHandler = new YoutubeOauth2TokenHandler();
|
||||
this.youTubeOauth2TokenHandler.init();
|
||||
this.players = new PlayerManager(this, config);
|
||||
this.players.init();
|
||||
this.nowplaying = new NowplayingHandler(this);
|
||||
this.nowplaying.init();
|
||||
this.statusMessageHandler = new StatusMessageHandler(this);
|
||||
this.statusMessageHandler.init();
|
||||
this.aloneInVoiceHandler = new AloneInVoiceHandler(this);
|
||||
this.aloneInVoiceHandler.init();
|
||||
DataLogService dls = null;
|
||||
if(config.getDataLogEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
dls = new DataLogService(this, config.getDataLogJdbcUrl(), config.getDataLogUser(), config.getDataLogPassword());
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
dls = null;
|
||||
}
|
||||
}
|
||||
this.dataLogService = dls;
|
||||
}
|
||||
|
||||
public BotConfig getConfig()
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
public SettingsManager getSettingsManager()
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
public EventWaiter getWaiter()
|
||||
{
|
||||
return waiter;
|
||||
}
|
||||
|
||||
public ScheduledExecutorService getThreadpool()
|
||||
{
|
||||
return threadpool;
|
||||
}
|
||||
|
||||
public PlayerManager getPlayerManager()
|
||||
{
|
||||
return players;
|
||||
}
|
||||
|
||||
public PlaylistLoader getPlaylistLoader()
|
||||
{
|
||||
return playlists;
|
||||
}
|
||||
|
||||
public NowplayingHandler getNowplayingHandler()
|
||||
{
|
||||
return nowplaying;
|
||||
}
|
||||
|
||||
public StatusMessageHandler getStatusMessageHandler()
|
||||
{
|
||||
return statusMessageHandler;
|
||||
}
|
||||
|
||||
public AloneInVoiceHandler getAloneInVoiceHandler()
|
||||
{
|
||||
return aloneInVoiceHandler;
|
||||
}
|
||||
|
||||
public DataLogService getDataLogService()
|
||||
{
|
||||
return dataLogService;
|
||||
}
|
||||
|
||||
public YoutubeOauth2TokenHandler getYouTubeOauth2Handler()
|
||||
{
|
||||
return youTubeOauth2TokenHandler;
|
||||
}
|
||||
|
||||
public JDA getJDA()
|
||||
{
|
||||
return jda;
|
||||
}
|
||||
|
||||
public void closeAudioConnection(long guildId)
|
||||
{
|
||||
Guild guild = jda.getGuildById(guildId);
|
||||
if(guild!=null)
|
||||
threadpool.submit(() -> guild.getAudioManager().closeAudioConnection());
|
||||
}
|
||||
|
||||
public void resetGame()
|
||||
{
|
||||
Activity game = config.getGame()==null || config.getGame().getName().equalsIgnoreCase("none") ? null : config.getGame();
|
||||
if(!Objects.equals(jda.getPresence().getActivity(), game))
|
||||
jda.getPresence().setActivity(game);
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
if(shuttingDown)
|
||||
return;
|
||||
shuttingDown = true;
|
||||
threadpool.shutdownNow();
|
||||
if(jda.getStatus()!=JDA.Status.SHUTTING_DOWN)
|
||||
{
|
||||
jda.getGuilds().stream().forEach(g ->
|
||||
{
|
||||
g.getAudioManager().closeAudioConnection();
|
||||
AudioHandler ah = (AudioHandler)g.getAudioManager().getSendingHandler();
|
||||
if(ah!=null)
|
||||
{
|
||||
ah.stopAndClear();
|
||||
ah.getPlayer().destroy();
|
||||
}
|
||||
});
|
||||
jda.shutdown();
|
||||
}
|
||||
if(gui!=null)
|
||||
gui.dispose();
|
||||
if(dataLogService != null)
|
||||
dataLogService.shutdown();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
public void setJDA(JDA jda)
|
||||
{
|
||||
this.jda = jda;
|
||||
}
|
||||
|
||||
public void setGUI(GUI gui)
|
||||
{
|
||||
this.gui = gui;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh (jagrosh)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot;
|
||||
|
||||
import com.jagrosh.jmusicbot.entities.Prompt;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.jagrosh.jmusicbot.utils.YouTubeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
|
||||
import com.typesafe.config.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author John Grosh (jagrosh)
|
||||
*/
|
||||
public class BotConfig
|
||||
{
|
||||
private final Prompt prompt;
|
||||
private final static String CONTEXT = "Config";
|
||||
private final static String START_TOKEN = "/// START OF JMUSICBOT CONFIG ///";
|
||||
private final static String END_TOKEN = "/// END OF JMUSICBOT CONFIG ///";
|
||||
|
||||
private Path path = null;
|
||||
private String token, prefix, altprefix, helpWord, playlistsFolder, logLevel,
|
||||
successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji,
|
||||
chromePath, chromeDriverPath, evalEngine;
|
||||
private YouTubeUtil.RoutingPlanner ytRoutingPlanner;
|
||||
private List<IpBlock> ytIpBlocks;
|
||||
private boolean youtubeOauth2, stayInChannel, songInGame, npImages, updatealerts, chromeHeadless, useEval, dbots;
|
||||
private boolean statusMessages;
|
||||
private boolean dataLogEnabled;
|
||||
private String dataLogJdbcUrl, dataLogUser, dataLogPassword;
|
||||
private long owner, maxSeconds, aloneTimeUntilStop;
|
||||
private int maxYTPlaylistPages;
|
||||
private double skipratio;
|
||||
private OnlineStatus status;
|
||||
private Activity game;
|
||||
private Config aliases, transforms;
|
||||
|
||||
private boolean valid = false;
|
||||
|
||||
public BotConfig(Prompt prompt)
|
||||
{
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public void load()
|
||||
{
|
||||
valid = false;
|
||||
|
||||
// read config from file
|
||||
try
|
||||
{
|
||||
// get the path to the config, default config.txt
|
||||
path = getConfigPath();
|
||||
|
||||
// load in the config file, plus the default values
|
||||
//Config config = ConfigFactory.parseFile(path.toFile()).withFallback(ConfigFactory.load());
|
||||
Config config = ConfigFactory.load();
|
||||
|
||||
// set values
|
||||
token = config.getString("token");
|
||||
prefix = config.getString("prefix");
|
||||
altprefix = config.getString("altprefix");
|
||||
helpWord = config.getString("help");
|
||||
owner = config.getLong("owner");
|
||||
successEmoji = config.getString("success");
|
||||
warningEmoji = config.getString("warning");
|
||||
errorEmoji = config.getString("error");
|
||||
loadingEmoji = config.getString("loading");
|
||||
searchingEmoji = config.getString("searching");
|
||||
game = OtherUtil.parseGame(config.getString("game"));
|
||||
status = OtherUtil.parseStatus(config.getString("status"));
|
||||
youtubeOauth2 = config.getBoolean("youtubeoauth2");
|
||||
stayInChannel = config.getBoolean("stayinchannel");
|
||||
songInGame = config.getBoolean("songinstatus");
|
||||
statusMessages = config.getBoolean("statusmessages");
|
||||
npImages = config.getBoolean("npimages");
|
||||
updatealerts = config.getBoolean("updatealerts");
|
||||
logLevel = config.getString("loglevel");
|
||||
useEval = config.getBoolean("eval");
|
||||
evalEngine = config.getString("evalengine");
|
||||
maxSeconds = config.getLong("maxtime");
|
||||
maxYTPlaylistPages = config.getInt("maxytplaylistpages");
|
||||
aloneTimeUntilStop = config.getLong("alonetimeuntilstop");
|
||||
playlistsFolder = config.getString("playlistsfolder");
|
||||
aliases = config.getConfig("aliases");
|
||||
chromePath = config.getString("chromepath");
|
||||
chromeDriverPath = config.getString("chromedriverpath");
|
||||
chromeHeadless = config.getBoolean("chromeheadless");
|
||||
ytRoutingPlanner = config.getEnum(YouTubeUtil.RoutingPlanner.class, "ytroutingplanner");
|
||||
ytIpBlocks = config.getStringList("ytipblocks").stream().map(YouTubeUtil::parseIpBlock).collect(Collectors.toList());
|
||||
transforms = config.getConfig("transforms");
|
||||
skipratio = config.getDouble("skipratio");
|
||||
dbots = owner == 113156185389092864L;
|
||||
dataLogEnabled = config.getBoolean("dblog.enabled");
|
||||
dataLogJdbcUrl = config.getString("dblog.jdbc_url");
|
||||
dataLogUser = config.getString("dblog.user");
|
||||
dataLogPassword = config.getString("dblog.password");
|
||||
|
||||
// we may need to write a new config file
|
||||
boolean write = false;
|
||||
|
||||
// validate bot token
|
||||
if(token==null || token.isEmpty() || token.equalsIgnoreCase("BOT_TOKEN_HERE"))
|
||||
{
|
||||
token = prompt.prompt("Please provide a bot token."
|
||||
+ "\nInstructions for obtaining a token can be found here:"
|
||||
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Getting-a-Bot-Token."
|
||||
+ "\nBot Token: ");
|
||||
if(token==null)
|
||||
{
|
||||
prompt.alert(Prompt.Level.WARNING, CONTEXT, "No token provided! Exiting.\n\nConfig Location: " + path.toAbsolutePath().toString());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
write = true;
|
||||
}
|
||||
}
|
||||
|
||||
// validate bot owner
|
||||
if(owner<=0)
|
||||
{
|
||||
try
|
||||
{
|
||||
owner = Long.parseLong(prompt.prompt("Owner ID was missing, or the provided owner ID is not valid."
|
||||
+ "\nPlease provide the User ID of the bot's owner."
|
||||
+ "\nInstructions for obtaining your User ID can be found here:"
|
||||
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Finding-Your-User-ID"
|
||||
+ "\nOwner User ID: "));
|
||||
}
|
||||
catch(NumberFormatException | NullPointerException ex)
|
||||
{
|
||||
owner = 0;
|
||||
}
|
||||
if(owner<=0)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, CONTEXT, "Invalid User ID! Exiting.\n\nConfig Location: " + path.toAbsolutePath().toString());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
write = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(write)
|
||||
writeToFile();
|
||||
|
||||
// if we get through the whole config, it's good to go
|
||||
valid = true;
|
||||
}
|
||||
catch (ConfigException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, CONTEXT, ex + ": " + ex.getMessage() + "\n\nConfig Location: " + path.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeToFile()
|
||||
{
|
||||
byte[] bytes = loadDefaultConfig().replace("BOT_TOKEN_HERE", token)
|
||||
.replace("0 // OWNER ID", Long.toString(owner))
|
||||
.trim().getBytes();
|
||||
try
|
||||
{
|
||||
Files.write(path, bytes);
|
||||
}
|
||||
catch(IOException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.WARNING, CONTEXT, "Failed to write new config options to config.txt: "+ex
|
||||
+ "\nPlease make sure that the files are not on your desktop or some other restricted area.\n\nConfig Location: "
|
||||
+ path.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static String loadDefaultConfig()
|
||||
{
|
||||
String original = OtherUtil.loadResource(new JMusicBot(), "/reference.conf");
|
||||
return original==null
|
||||
? "token = BOT_TOKEN_HERE\r\nowner = 0 // OWNER ID"
|
||||
: original.substring(original.indexOf(START_TOKEN)+START_TOKEN.length(), original.indexOf(END_TOKEN)).trim();
|
||||
}
|
||||
|
||||
private static Path getConfigPath()
|
||||
{
|
||||
Path path = OtherUtil.getPath(System.getProperty("config.file", System.getProperty("config", "config.txt")));
|
||||
if(path.toFile().exists())
|
||||
{
|
||||
if(System.getProperty("config.file") == null)
|
||||
System.setProperty("config.file", System.getProperty("config", path.toAbsolutePath().toString()));
|
||||
ConfigFactory.invalidateCaches();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void writeDefaultConfig()
|
||||
{
|
||||
Prompt prompt = new Prompt(null, null, true, true);
|
||||
prompt.alert(Prompt.Level.INFO, "JMusicBot Config", "Generating default config file");
|
||||
Path path = BotConfig.getConfigPath();
|
||||
byte[] bytes = BotConfig.loadDefaultConfig().getBytes();
|
||||
try
|
||||
{
|
||||
prompt.alert(Prompt.Level.INFO, "JMusicBot Config", "Writing default config file to " + path.toAbsolutePath().toString());
|
||||
Files.write(path, bytes);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot Config", "An error occurred writing the default config file: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
|
||||
public String getConfigLocation()
|
||||
{
|
||||
return path.toFile().getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getPrefix()
|
||||
{
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getAltPrefix()
|
||||
{
|
||||
return "NONE".equalsIgnoreCase(altprefix) ? null : altprefix;
|
||||
}
|
||||
|
||||
public String getToken()
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
public double getSkipRatio()
|
||||
{
|
||||
return skipratio;
|
||||
}
|
||||
|
||||
public long getOwnerId()
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
|
||||
public boolean getDataLogEnabled()
|
||||
{
|
||||
return dataLogEnabled;
|
||||
}
|
||||
|
||||
public String getDataLogJdbcUrl()
|
||||
{
|
||||
return dataLogJdbcUrl;
|
||||
}
|
||||
|
||||
public String getDataLogUser()
|
||||
{
|
||||
return dataLogUser;
|
||||
}
|
||||
|
||||
public String getDataLogPassword()
|
||||
{
|
||||
return dataLogPassword;
|
||||
}
|
||||
|
||||
public String getSuccess()
|
||||
{
|
||||
return successEmoji;
|
||||
}
|
||||
|
||||
public String getWarning()
|
||||
{
|
||||
return warningEmoji;
|
||||
}
|
||||
|
||||
public String getError()
|
||||
{
|
||||
return errorEmoji;
|
||||
}
|
||||
|
||||
public String getLoading()
|
||||
{
|
||||
return loadingEmoji;
|
||||
}
|
||||
|
||||
public String getSearching()
|
||||
{
|
||||
return searchingEmoji;
|
||||
}
|
||||
|
||||
public Activity getGame()
|
||||
{
|
||||
return game;
|
||||
}
|
||||
|
||||
public boolean isGameNone()
|
||||
{
|
||||
return game != null && game.getName().equalsIgnoreCase("none");
|
||||
}
|
||||
|
||||
public OnlineStatus getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getHelp()
|
||||
{
|
||||
return helpWord;
|
||||
}
|
||||
|
||||
public boolean useYoutubeOauth2()
|
||||
{
|
||||
return youtubeOauth2;
|
||||
}
|
||||
|
||||
public boolean getStay()
|
||||
{
|
||||
return stayInChannel;
|
||||
}
|
||||
|
||||
public boolean getSongInStatus()
|
||||
{
|
||||
return songInGame;
|
||||
}
|
||||
|
||||
public boolean useStatusMessages()
|
||||
{
|
||||
return statusMessages;
|
||||
}
|
||||
|
||||
public String getPlaylistsFolder()
|
||||
{
|
||||
return playlistsFolder;
|
||||
}
|
||||
|
||||
public boolean getDBots()
|
||||
{
|
||||
return dbots;
|
||||
}
|
||||
|
||||
public boolean useUpdateAlerts()
|
||||
{
|
||||
return updatealerts;
|
||||
}
|
||||
|
||||
public String getLogLevel()
|
||||
{
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public String getChromePath()
|
||||
{
|
||||
return chromePath.equals("AUTO") ? null : chromePath;
|
||||
}
|
||||
|
||||
public String getChromeDriverPath()
|
||||
{
|
||||
return chromeDriverPath.equals("AUTO") ? null : chromeDriverPath;
|
||||
}
|
||||
|
||||
public boolean getChromeHeadless()
|
||||
{
|
||||
return chromeHeadless;
|
||||
}
|
||||
|
||||
public YouTubeUtil.RoutingPlanner getYTRoutingPlanner()
|
||||
{
|
||||
return ytRoutingPlanner;
|
||||
}
|
||||
|
||||
public List<IpBlock> getYTIpBlocks()
|
||||
{
|
||||
return ytIpBlocks;
|
||||
}
|
||||
|
||||
public boolean useEval()
|
||||
{
|
||||
return useEval;
|
||||
}
|
||||
|
||||
public String getEvalEngine()
|
||||
{
|
||||
return evalEngine;
|
||||
}
|
||||
|
||||
public boolean useNPImages()
|
||||
{
|
||||
return npImages;
|
||||
}
|
||||
|
||||
public long getMaxSeconds()
|
||||
{
|
||||
return maxSeconds;
|
||||
}
|
||||
|
||||
public int getMaxYTPlaylistPages()
|
||||
{
|
||||
return maxYTPlaylistPages;
|
||||
}
|
||||
|
||||
public String getMaxTime()
|
||||
{
|
||||
return TimeUtil.formatTime(maxSeconds * 1000);
|
||||
}
|
||||
|
||||
public long getAloneTimeUntilStop()
|
||||
{
|
||||
return aloneTimeUntilStop;
|
||||
}
|
||||
|
||||
public boolean isTooLong(AudioTrack track)
|
||||
{
|
||||
if(maxSeconds<=0)
|
||||
return false;
|
||||
return Math.round(track.getDuration()/1000.0) > maxSeconds;
|
||||
}
|
||||
|
||||
public String[] getAliases(String command)
|
||||
{
|
||||
try
|
||||
{
|
||||
return aliases.getStringList(command).toArray(new String[0]);
|
||||
}
|
||||
catch(NullPointerException | ConfigException.Missing e)
|
||||
{
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
public Config getTransforms()
|
||||
{
|
||||
return transforms;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandClient;
|
||||
import com.jagrosh.jdautilities.command.CommandClientBuilder;
|
||||
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
|
||||
import com.jagrosh.jmusicbot.commands.general.LoggedAboutCmd;
|
||||
import com.jagrosh.jmusicbot.commands.general.LoggedPingCmd;
|
||||
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 moe.kyokobot.libdave.NativeDaveFactory;
|
||||
import moe.kyokobot.libdave.jda.LDJDADaveSessionFactory;
|
||||
import java.awt.Color;
|
||||
import java.util.Arrays;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import net.dv8tion.jda.api.*;
|
||||
import net.dv8tion.jda.api.audio.AudioModuleConfig;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
||||
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import ch.qos.logback.classic.Level;
|
||||
|
||||
import static com.jagrosh.jmusicbot.utils.YouTubeUtil.GetPoTokenAndVisitorData;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (jagrosh)
|
||||
*/
|
||||
public class JMusicBot
|
||||
{
|
||||
public final static Logger LOG = LoggerFactory.getLogger(JMusicBot.class);
|
||||
public final static Permission[] RECOMMENDED_PERMS = {Permission.VIEW_CHANNEL, Permission.MESSAGE_SEND, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
|
||||
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI,
|
||||
Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE};
|
||||
public final static GatewayIntent[] INTENTS = {GatewayIntent.DIRECT_MESSAGES, GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGE_REACTIONS, GatewayIntent.GUILD_VOICE_STATES, GatewayIntent.MESSAGE_CONTENT, GatewayIntent.GUILD_EXPRESSIONS, GatewayIntent.SCHEDULED_EVENTS};
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args)
|
||||
{
|
||||
if(args.length > 0)
|
||||
switch(args[0].toLowerCase())
|
||||
{
|
||||
case "generate-config":
|
||||
BotConfig.writeDefaultConfig();
|
||||
return;
|
||||
default:
|
||||
}
|
||||
startBot();
|
||||
}
|
||||
|
||||
private static void startBot()
|
||||
{
|
||||
// create prompt to handle startup
|
||||
Prompt prompt = new Prompt("JMusicBot");
|
||||
|
||||
// startup checks
|
||||
OtherUtil.checkVersion(prompt);
|
||||
OtherUtil.checkJavaVersion(prompt);
|
||||
|
||||
// load config
|
||||
BotConfig config = new BotConfig(prompt);
|
||||
config.load();
|
||||
if(!config.isValid())
|
||||
return;
|
||||
LOG.info("Loaded config from " + config.getConfigLocation());
|
||||
|
||||
// set log level from config
|
||||
((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(
|
||||
Level.toLevel(config.getLogLevel(), Level.INFO));
|
||||
|
||||
// set up the listener
|
||||
EventWaiter waiter = new EventWaiter();
|
||||
SettingsManager settings = new SettingsManager();
|
||||
Bot bot = new Bot(waiter, config, settings);
|
||||
CommandClient client = createCommandClient(config, settings, bot);
|
||||
|
||||
|
||||
if(!prompt.isNoGUI())
|
||||
{
|
||||
try
|
||||
{
|
||||
GUI gui = new GUI(bot);
|
||||
bot.setGUI(gui);
|
||||
gui.init();
|
||||
|
||||
LOG.info("Loaded config from " + config.getConfigLocation());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Could not start GUI. If you are "
|
||||
+ "running on a server or in a location where you cannot display a "
|
||||
+ "window, please run in nogui mode using the -Dnogui=true flag.");
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to log in and start
|
||||
try
|
||||
{
|
||||
JDA jda = JDABuilder.create(config.getToken(), Arrays.asList(INTENTS))
|
||||
.enableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE)
|
||||
.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.ONLINE_STATUS)
|
||||
.setActivity(config.isGameNone() ? null : Activity.playing("loading..."))
|
||||
.setStatus(config.getStatus()==OnlineStatus.INVISIBLE || config.getStatus()==OnlineStatus.OFFLINE
|
||||
? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB)
|
||||
.setAudioModuleConfig(createAudioModuleConfig())
|
||||
.addEventListeners(client, waiter, new Listener(bot))
|
||||
.setBulkDeleteSplittingEnabled(true)
|
||||
.build();
|
||||
bot.setJDA(jda);
|
||||
|
||||
// check if something about the current startup is not supported
|
||||
String unsupportedReason = OtherUtil.getUnsupportedBotReason(jda);
|
||||
if (unsupportedReason != null)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "JMusicBot cannot be run on this Discord bot: " + unsupportedReason);
|
||||
try{ Thread.sleep(5000);}catch(InterruptedException ignored){} // this is awful but until we have a better way...
|
||||
jda.shutdown();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// other check that will just be a warning now but may be required in the future
|
||||
// check if the user has changed the prefix and provide info about the
|
||||
// message content intent
|
||||
if(!"@mention".equals(config.getPrefix()))
|
||||
{
|
||||
LOG.info("JMusicBot", "You currently have a custom prefix set. "
|
||||
+ "If your prefix is not working, make sure that the 'MESSAGE CONTENT INTENT' is Enabled "
|
||||
+ "on https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot");
|
||||
}
|
||||
}
|
||||
catch(IllegalStateException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex.getMessage());
|
||||
LOG.error("Failed to initialize DAVE protocol support", ex);
|
||||
System.exit(1);
|
||||
}
|
||||
catch(IllegalArgumentException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "Some aspect of the configuration is "
|
||||
+ "invalid: " + ex + "\nConfig Location: " + config.getConfigLocation());
|
||||
System.exit(1);
|
||||
}
|
||||
catch(ErrorResponseException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nInvalid reponse returned when "
|
||||
+ "attempting to connect, please make sure you're connected to the internet");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
GetPoTokenAndVisitorData(config.getChromePath(), config.getChromeDriverPath(), config.getChromeHeadless());
|
||||
}
|
||||
|
||||
private static AudioModuleConfig createAudioModuleConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
NativeDaveFactory.ensureAvailable();
|
||||
LOG.info("Configured DAVE protocol support using libdave-jvm.");
|
||||
return new AudioModuleConfig()
|
||||
.withDaveSessionFactory(new LDJDADaveSessionFactory(new NativeDaveFactory()));
|
||||
}
|
||||
catch(Throwable ex)
|
||||
{
|
||||
throw new IllegalStateException("Unable to initialize DAVE protocol support via libdave-jvm. "
|
||||
+ "Verify native dependencies are present for this platform (Linux glibc/musl x86_64, Windows x86_64, or macOS).", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static CommandClient createCommandClient(BotConfig config, SettingsManager settings, Bot bot)
|
||||
{
|
||||
// instantiate about command
|
||||
LoggedAboutCmd aboutCommand = new LoggedAboutCmd(bot, Color.BLUE.brighter(),
|
||||
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot) (v" + OtherUtil.getCurrentVersion() + ")",
|
||||
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
|
||||
RECOMMENDED_PERMS);
|
||||
aboutCommand.setIsAuthor(false);
|
||||
aboutCommand.setReplacementCharacter("\uD83C\uDFB6"); // 🎶
|
||||
|
||||
// set up the command client
|
||||
CommandClientBuilder cb = new CommandClientBuilder()
|
||||
.setPrefix(config.getPrefix())
|
||||
.setAlternativePrefix(config.getAltPrefix())
|
||||
.setOwnerId(Long.toString(config.getOwnerId()))
|
||||
.setEmojis(config.getSuccess(), config.getWarning(), config.getError())
|
||||
.setHelpWord(config.getHelp())
|
||||
.setLinkedCacheSize(200)
|
||||
.setGuildSettingsManager(settings)
|
||||
.addCommands(aboutCommand,
|
||||
new LoggedPingCmd(bot),
|
||||
new SettingsCmd(bot),
|
||||
|
||||
new LyricsCmd(bot),
|
||||
new NowplayingCmd(bot),
|
||||
new PlayCmd(bot),
|
||||
new PlaylistsCmd(bot),
|
||||
new QueueCmd(bot),
|
||||
new RemoveCmd(bot),
|
||||
new SearchCmd(bot),
|
||||
new SCSearchCmd(bot),
|
||||
new SeekCmd(bot),
|
||||
new ShuffleCmd(bot),
|
||||
new SkipCmd(bot),
|
||||
|
||||
new ForceRemoveCmd(bot),
|
||||
new ForceskipCmd(bot),
|
||||
new MoveTrackCmd(bot),
|
||||
new PauseCmd(bot),
|
||||
new PlaynextCmd(bot),
|
||||
new RepeatCmd(bot),
|
||||
new SkiptoCmd(bot),
|
||||
new StopCmd(bot),
|
||||
new VolumeCmd(bot),
|
||||
|
||||
new PrefixCmd(bot),
|
||||
new QueueTypeCmd(bot),
|
||||
new SetdjCmd(bot),
|
||||
new SkipratioCmd(bot),
|
||||
new SettcCmd(bot),
|
||||
new SetvcCmd(bot),
|
||||
|
||||
new AutoplaylistCmd(bot),
|
||||
new DebugCmd(bot),
|
||||
new PlaylistCmd(bot),
|
||||
new SetavatarCmd(bot),
|
||||
new SetgameCmd(bot),
|
||||
new SetnameCmd(bot),
|
||||
new SetstatusCmd(bot),
|
||||
new ShutdownCmd(bot)
|
||||
);
|
||||
|
||||
// enable eval if applicable
|
||||
if(config.useEval())
|
||||
cb.addCommand(new EvalCmd(bot));
|
||||
|
||||
// set status if set in config
|
||||
if(config.getStatus() != OnlineStatus.UNKNOWN)
|
||||
cb.setStatus(config.getStatus());
|
||||
|
||||
// set game
|
||||
if(config.getGame() == null)
|
||||
cb.useDefaultGame();
|
||||
else if(config.isGameNone())
|
||||
cb.setActivity(null);
|
||||
else
|
||||
cb.setActivity(config.getGame());
|
||||
|
||||
return cb.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot;
|
||||
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import net.dv8tion.jda.api.events.session.ReadyEvent;
|
||||
import net.dv8tion.jda.api.events.session.ShutdownEvent;
|
||||
import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
|
||||
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class Listener extends ListenerAdapter
|
||||
{
|
||||
private final Bot bot;
|
||||
|
||||
public Listener(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(ReadyEvent event)
|
||||
{
|
||||
if(event.getJDA().getGuildCache().isEmpty())
|
||||
{
|
||||
Logger log = LoggerFactory.getLogger("MusicBot");
|
||||
log.warn("This bot is not on any guilds! Use the following link to add the bot to your guilds!");
|
||||
log.warn(event.getJDA().getInviteUrl(JMusicBot.RECOMMENDED_PERMS));
|
||||
}
|
||||
credit(event.getJDA());
|
||||
event.getJDA().getGuilds().forEach((guild) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
String defpl = bot.getSettingsManager().getSettings(guild).getDefaultPlaylist();
|
||||
VoiceChannel vc = bot.getSettingsManager().getSettings(guild).getVoiceChannel(guild);
|
||||
if(defpl!=null && vc!=null && bot.getPlayerManager().setUpHandler(guild).playFromDefault())
|
||||
{
|
||||
guild.getAudioManager().openAudioConnection(vc);
|
||||
}
|
||||
}
|
||||
catch(Exception ignore) {}
|
||||
});
|
||||
if(bot.getConfig().useUpdateAlerts())
|
||||
{
|
||||
bot.getThreadpool().scheduleWithFixedDelay(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
User owner = bot.getJDA().retrieveUserById(bot.getConfig().getOwnerId()).complete();
|
||||
String currentVersion = OtherUtil.getCurrentVersion();
|
||||
String latestVersion = OtherUtil.getLatestVersion();
|
||||
if(latestVersion!=null && !currentVersion.equalsIgnoreCase(latestVersion))
|
||||
{
|
||||
String msg = String.format(OtherUtil.NEW_VERSION_AVAILABLE, currentVersion, latestVersion);
|
||||
owner.openPrivateChannel().queue(pc -> pc.sendMessage(msg).queue());
|
||||
}
|
||||
}
|
||||
catch(Exception ignored) {} // ignored
|
||||
}, 0, 24, TimeUnit.HOURS);
|
||||
}
|
||||
if (bot.getConfig().useYoutubeOauth2())
|
||||
{
|
||||
YoutubeOauth2TokenHandler.Data data = bot.getYouTubeOauth2Handler().getData();
|
||||
if (data != null)
|
||||
{
|
||||
PrivateChannel channel = bot.getJDA().openPrivateChannelById(bot.getConfig().getOwnerId()).complete();
|
||||
channel
|
||||
.sendMessage(
|
||||
"# DO NOT AUTHORISE THIS WITH YOUR MAIN GOOGLE ACCOUNT!!!\n"
|
||||
+ "## Create or use an alternative/burner Google account!\n"
|
||||
+ "To give JMusicBot access to your Google account, go to "
|
||||
+ data.getAuthorisationUrl()
|
||||
+ " and enter the code **" + data.getCode() + "**")
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageDelete(MessageDeleteEvent event)
|
||||
{
|
||||
if(event.isFromGuild()) {
|
||||
bot.getNowplayingHandler().onMessageDelete(event.getGuild(), event.getMessageIdLong());
|
||||
bot.getStatusMessageHandler().onMessageDelete(event.getGuild(), event.getMessageIdLong());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(MessageReceivedEvent event)
|
||||
{
|
||||
bot.getStatusMessageHandler().onMessageReceived(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event)
|
||||
{
|
||||
bot.getAloneInVoiceHandler().onVoiceUpdate(event);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().onVoiceUpdate(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShutdown(ShutdownEvent event)
|
||||
{
|
||||
bot.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildJoin(GuildJoinEvent event)
|
||||
{
|
||||
credit(event.getJDA());
|
||||
}
|
||||
|
||||
// make sure people aren't adding clones to dbots
|
||||
private void credit(JDA jda)
|
||||
{
|
||||
Guild dbots = jda.getGuildById(110373943822540800L);
|
||||
if(dbots==null)
|
||||
return;
|
||||
if(bot.getConfig().getDBots())
|
||||
return;
|
||||
jda.getTextChannelById(119222314964353025L)
|
||||
.sendMessage("This account is running JMusicBot. Please do not list bot clones on this server, <@"+bot.getConfig().getOwnerId()+">.").complete();
|
||||
dbots.leave().queue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Michaili K (mysteriouscursor+git@protonmail.com)
|
||||
*/
|
||||
public class AloneInVoiceHandler
|
||||
{
|
||||
private final Bot bot;
|
||||
private final HashMap<Long, Instant> aloneSince = new HashMap<>();
|
||||
private long aloneTimeUntilStop = 0;
|
||||
|
||||
public AloneInVoiceHandler(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
aloneTimeUntilStop = bot.getConfig().getAloneTimeUntilStop();
|
||||
if(aloneTimeUntilStop > 0)
|
||||
bot.getThreadpool().scheduleWithFixedDelay(() -> check(), 0, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void check()
|
||||
{
|
||||
Set<Long> toRemove = new HashSet<>();
|
||||
for(Map.Entry<Long, Instant> entrySet: aloneSince.entrySet())
|
||||
{
|
||||
if(entrySet.getValue().getEpochSecond() > Instant.now().getEpochSecond() - aloneTimeUntilStop) continue;
|
||||
|
||||
Guild guild = bot.getJDA().getGuildById(entrySet.getKey());
|
||||
|
||||
if(guild == null)
|
||||
{
|
||||
toRemove.add(entrySet.getKey());
|
||||
continue;
|
||||
}
|
||||
|
||||
((AudioHandler) guild.getAudioManager().getSendingHandler()).stopAndClear();
|
||||
guild.getAudioManager().closeAudioConnection();
|
||||
|
||||
toRemove.add(entrySet.getKey());
|
||||
}
|
||||
toRemove.forEach(id -> aloneSince.remove(id));
|
||||
}
|
||||
|
||||
public void onVoiceUpdate(GuildVoiceUpdateEvent event)
|
||||
{
|
||||
if(aloneTimeUntilStop <= 0) return;
|
||||
|
||||
Guild guild = event.getEntity().getGuild();
|
||||
if(!bot.getPlayerManager().hasHandler(guild)) return;
|
||||
|
||||
boolean alone = isAlone(guild);
|
||||
boolean inList = aloneSince.containsKey(guild.getIdLong());
|
||||
|
||||
if(!alone && inList)
|
||||
aloneSince.remove(guild.getIdLong());
|
||||
else if(alone && !inList)
|
||||
aloneSince.put(guild.getIdLong(), Instant.now());
|
||||
}
|
||||
|
||||
private boolean isAlone(Guild guild)
|
||||
{
|
||||
if(guild.getAudioManager().getConnectedChannel() == null) return false;
|
||||
return guild.getAudioManager().getConnectedChannel().getMembers().stream()
|
||||
.noneMatch(x ->
|
||||
!x.getVoiceState().isDeafened()
|
||||
&& !x.getUser().isBot());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
|
||||
import com.jagrosh.jmusicbot.queue.AbstractQueue;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.jagrosh.jmusicbot.settings.RepeatMode;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
||||
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack;
|
||||
import java.nio.ByteBuffer;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
|
||||
import net.dv8tion.jda.api.audio.AudioSendHandler;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
{
|
||||
public final static String PLAY_EMOJI = "\u25B6"; // ▶
|
||||
public final static String PAUSE_EMOJI = "\u23F8"; // ⏸
|
||||
public final static String STOP_EMOJI = "\u23F9"; // ⏹
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(AudioHandler.class);
|
||||
|
||||
private final List<AudioTrack> defaultQueue = new LinkedList<>();
|
||||
private final Set<String> votes = new HashSet<>();
|
||||
|
||||
private final PlayerManager manager;
|
||||
private final AudioPlayer audioPlayer;
|
||||
private final long guildId;
|
||||
|
||||
private AudioFrame lastFrame;
|
||||
private AbstractQueue<QueuedTrack> queue;
|
||||
|
||||
protected AudioHandler(PlayerManager manager, Guild guild, AudioPlayer player)
|
||||
{
|
||||
this.manager = manager;
|
||||
this.audioPlayer = player;
|
||||
this.guildId = guild.getIdLong();
|
||||
|
||||
this.setQueueType(manager.getBot().getSettingsManager().getSettings(guildId).getQueueType());
|
||||
}
|
||||
|
||||
public void setQueueType(QueueType type)
|
||||
{
|
||||
queue = type.createInstance(queue);
|
||||
}
|
||||
|
||||
public int addTrackToFront(QueuedTrack qtrack)
|
||||
{
|
||||
if(audioPlayer.getPlayingTrack()==null)
|
||||
{
|
||||
audioPlayer.playTrack(qtrack.getTrack());
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
queue.addAt(0, qtrack);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int addTrack(QueuedTrack qtrack)
|
||||
{
|
||||
if(audioPlayer.getPlayingTrack()==null)
|
||||
{
|
||||
audioPlayer.playTrack(qtrack.getTrack());
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
return queue.add(qtrack);
|
||||
}
|
||||
|
||||
public AbstractQueue<QueuedTrack> getQueue()
|
||||
{
|
||||
return queue;
|
||||
}
|
||||
|
||||
public void stopAndClear()
|
||||
{
|
||||
queue.clear();
|
||||
defaultQueue.clear();
|
||||
audioPlayer.stopTrack();
|
||||
//current = null;
|
||||
}
|
||||
|
||||
public boolean isMusicPlaying(JDA jda)
|
||||
{
|
||||
return guild(jda).getSelfMember().getVoiceState().inAudioChannel() && audioPlayer.getPlayingTrack()!=null;
|
||||
}
|
||||
|
||||
public Set<String> getVotes()
|
||||
{
|
||||
return votes;
|
||||
}
|
||||
|
||||
public AudioPlayer getPlayer()
|
||||
{
|
||||
return audioPlayer;
|
||||
}
|
||||
|
||||
public RequestMetadata getRequestMetadata()
|
||||
{
|
||||
if(audioPlayer.getPlayingTrack() == null)
|
||||
return RequestMetadata.EMPTY;
|
||||
RequestMetadata rm = audioPlayer.getPlayingTrack().getUserData(RequestMetadata.class);
|
||||
return rm == null ? RequestMetadata.EMPTY : rm;
|
||||
}
|
||||
|
||||
public boolean playFromDefault()
|
||||
{
|
||||
if(!defaultQueue.isEmpty())
|
||||
{
|
||||
audioPlayer.playTrack(defaultQueue.remove(0));
|
||||
return true;
|
||||
}
|
||||
Settings settings = manager.getBot().getSettingsManager().getSettings(guildId);
|
||||
if(settings==null || settings.getDefaultPlaylist()==null)
|
||||
return false;
|
||||
|
||||
Playlist pl = manager.getBot().getPlaylistLoader().getPlaylist(settings.getDefaultPlaylist());
|
||||
if(pl==null || pl.getItems().isEmpty())
|
||||
return false;
|
||||
pl.loadTracks(manager, (at) ->
|
||||
{
|
||||
at.setUserData(RequestMetadata.fromDefaultPlaylist(settings.getDefaultPlaylist()));
|
||||
if(audioPlayer.getPlayingTrack()==null)
|
||||
audioPlayer.playTrack(at);
|
||||
else
|
||||
defaultQueue.add(at);
|
||||
if(manager.getBot().getDataLogService() != null)
|
||||
manager.getBot().getDataLogService().logQueueAdd(guild(manager.getBot().getJDA()), null, at, "DEFAULT_PLAYLIST", null, null, settings.getDefaultPlaylist());
|
||||
}, () ->
|
||||
{
|
||||
if(pl.getTracks().isEmpty() && !manager.getBot().getConfig().getStay())
|
||||
manager.getBot().closeAudioConnection(guildId);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Audio Events
|
||||
@Override
|
||||
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason)
|
||||
{
|
||||
if(manager.getBot().getDataLogService() != null)
|
||||
manager.getBot().getDataLogService().logPlayEnd(guild(manager.getBot().getJDA()), endReason.name());
|
||||
RepeatMode repeatMode = manager.getBot().getSettingsManager().getSettings(guildId).getRepeatMode();
|
||||
// if the track ended normally, and we're in repeat mode, re-add it to the queue
|
||||
if(endReason==AudioTrackEndReason.FINISHED && repeatMode != RepeatMode.OFF)
|
||||
{
|
||||
QueuedTrack clone = new QueuedTrack(track.makeClone(), track.getUserData(RequestMetadata.class));
|
||||
if(repeatMode == RepeatMode.ALL)
|
||||
queue.add(clone);
|
||||
else
|
||||
queue.addAt(0, clone);
|
||||
}
|
||||
|
||||
if(queue.isEmpty())
|
||||
{
|
||||
if(!playFromDefault())
|
||||
{
|
||||
manager.getBot().getNowplayingHandler().onTrackUpdate(null);
|
||||
manager.getBot().getStatusMessageHandler().onTrackUpdate(guildId);
|
||||
if(!manager.getBot().getConfig().getStay())
|
||||
manager.getBot().closeAudioConnection(guildId);
|
||||
// unpause, in the case when the player was paused and the track has been skipped.
|
||||
// this is to prevent the player being paused next time it's being used.
|
||||
player.setPaused(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QueuedTrack qt = queue.pull();
|
||||
player.playTrack(qt.getTrack());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception)
|
||||
{
|
||||
if (
|
||||
exception.getMessage().equals("Sign in to confirm you're not a bot")
|
||||
|| exception.getMessage().equals("Please sign in")
|
||||
)
|
||||
LOGGER.error(
|
||||
"Track {} has failed to play: {}"
|
||||
+ "You will need to sign in to Google to play YouTube tracks. More info: https://jmusicbot.com/youtube-oauth2",
|
||||
track.getIdentifier(),
|
||||
exception.getMessage()
|
||||
);
|
||||
else
|
||||
LOGGER.error("Track {} has failed to play", track.getIdentifier(), exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackStart(AudioPlayer player, AudioTrack track)
|
||||
{
|
||||
votes.clear();
|
||||
manager.getBot().getNowplayingHandler().onTrackUpdate(track);
|
||||
manager.getBot().getStatusMessageHandler().onTrackUpdate(guildId);
|
||||
if(manager.getBot().getDataLogService() != null)
|
||||
{
|
||||
RequestMetadata rm = getRequestMetadata();
|
||||
String source = rm.queueInfo == null ? null : rm.queueInfo.source;
|
||||
String searchQuery = rm.queueInfo == null ? null : rm.queueInfo.searchQuery;
|
||||
String playlistName = rm.queueInfo == null ? null : rm.queueInfo.playlistName;
|
||||
manager.getBot().getDataLogService().logPlayStart(guild(manager.getBot().getJDA()), track, rm, source, searchQuery, playlistName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Formatting
|
||||
public MessageCreateData getNowPlaying(JDA jda)
|
||||
{
|
||||
if(isMusicPlaying(jda))
|
||||
{
|
||||
Guild guild = guild(jda);
|
||||
AudioTrack track = audioPlayer.getPlayingTrack();
|
||||
MessageCreateBuilder mb = new MessageCreateBuilder();
|
||||
mb.addContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing in "+guild.getSelfMember().getVoiceState().getChannel().getAsMention()+"...**"));
|
||||
EmbedBuilder eb = new EmbedBuilder();
|
||||
eb.setColor(guild.getSelfMember().getColor());
|
||||
RequestMetadata rm = getRequestMetadata();
|
||||
if(rm.getOwner() != 0L)
|
||||
{
|
||||
User u = guild.getJDA().getUserById(rm.user.id);
|
||||
if(u==null)
|
||||
eb.setAuthor(FormatUtil.formatUsername(rm.user), null, rm.user.avatar);
|
||||
else
|
||||
eb.setAuthor(FormatUtil.formatUsername(u), null, u.getEffectiveAvatarUrl());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
eb.setTitle(track.getInfo().title, track.getInfo().uri);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
eb.setTitle(track.getInfo().title);
|
||||
}
|
||||
|
||||
if(track instanceof YoutubeAudioTrack && manager.getBot().getConfig().useNPImages())
|
||||
{
|
||||
eb.setThumbnail("https://img.youtube.com/vi/"+track.getIdentifier()+"/mqdefault.jpg");
|
||||
}
|
||||
|
||||
if(track.getInfo().author != null && !track.getInfo().author.isEmpty())
|
||||
eb.setFooter("Source: " + track.getInfo().author, null);
|
||||
|
||||
double progress = (double)audioPlayer.getPlayingTrack().getPosition()/track.getDuration();
|
||||
eb.setDescription(getStatusEmoji()
|
||||
+ " "+FormatUtil.progressBar(progress)
|
||||
+ " `[" + TimeUtil.formatTime(track.getPosition()) + "/" + TimeUtil.formatTime(track.getDuration()) + "]` "
|
||||
+ FormatUtil.volumeIcon(audioPlayer.getVolume()));
|
||||
|
||||
return mb.setEmbeds(eb.build()).build();
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
public MessageCreateData getNoMusicPlaying(JDA jda)
|
||||
{
|
||||
Guild guild = guild(jda);
|
||||
return new MessageCreateBuilder()
|
||||
.setContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing...**"))
|
||||
.setEmbeds(new EmbedBuilder()
|
||||
.setTitle("No music playing")
|
||||
.setDescription(STOP_EMOJI+" "+FormatUtil.progressBar(-1)+" "+FormatUtil.volumeIcon(audioPlayer.getVolume()))
|
||||
.setColor(guild.getSelfMember().getColor())
|
||||
.build()).build();
|
||||
}
|
||||
|
||||
public MessageCreateData getStatusMessage(JDA jda)
|
||||
{
|
||||
Guild guild = guild(jda);
|
||||
if(guild == null)
|
||||
return null;
|
||||
MessageCreateBuilder mb = new MessageCreateBuilder();
|
||||
mb.setContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing...**"));
|
||||
|
||||
EmbedBuilder eb = new EmbedBuilder();
|
||||
eb.setColor(guild.getSelfMember().getColor());
|
||||
|
||||
AudioTrack track = audioPlayer.getPlayingTrack();
|
||||
if(isMusicPlaying(jda) && track != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
eb.setTitle(track.getInfo().title, track.getInfo().uri);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
eb.setTitle(track.getInfo().title);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
eb.setTitle("No music playing");
|
||||
}
|
||||
|
||||
StringBuilder desc = new StringBuilder();
|
||||
if(isMusicPlaying(jda) && track != null)
|
||||
{
|
||||
double progress = (double)track.getPosition()/track.getDuration();
|
||||
desc.append(getStatusEmoji())
|
||||
.append(" ").append(FormatUtil.progressBar(progress))
|
||||
.append(" `[").append(TimeUtil.formatTime(track.getPosition()))
|
||||
.append("/").append(TimeUtil.formatTime(track.getDuration())).append("]` ")
|
||||
.append(FormatUtil.volumeIcon(audioPlayer.getVolume()));
|
||||
}
|
||||
else
|
||||
{
|
||||
desc.append(STOP_EMOJI)
|
||||
.append(" ").append(FormatUtil.progressBar(-1))
|
||||
.append(" ").append(FormatUtil.volumeIcon(audioPlayer.getVolume()));
|
||||
}
|
||||
|
||||
List<QueuedTrack> list = queue.getList();
|
||||
if(!list.isEmpty())
|
||||
{
|
||||
QueuedTrack next = list.get(0);
|
||||
String nextTitle = next.getTrack().getInfo().title;
|
||||
String requester = next.getRequestMetadata() == null || next.getRequestMetadata().user == null
|
||||
? null
|
||||
: FormatUtil.formatUsername(next.getRequestMetadata().user);
|
||||
desc.append("\n").append("Next: **").append(nextTitle).append("**");
|
||||
if(requester != null && !requester.isEmpty())
|
||||
desc.append(" (by ").append(requester).append(")");
|
||||
}
|
||||
|
||||
long total = 0L;
|
||||
for(QueuedTrack qt : list)
|
||||
total += qt.getTrack().getDuration();
|
||||
Settings settings = manager.getBot().getSettingsManager().getSettings(guildId);
|
||||
desc.append("\n")
|
||||
.append(manager.getBot().getConfig().getSuccess())
|
||||
.append(" Queue | ").append(list.size()).append(" entries | `")
|
||||
.append(TimeUtil.formatTime(total)).append("` | ")
|
||||
.append(settings.getQueueType().getEmoji()).append(" `")
|
||||
.append(settings.getQueueType().getUserFriendlyName()).append("`");
|
||||
if(settings.getRepeatMode().getEmoji() != null)
|
||||
desc.append(" | ").append(settings.getRepeatMode().getEmoji());
|
||||
|
||||
if(!guild.getSelfMember().getVoiceState().inAudioChannel())
|
||||
desc.append("\n").append(manager.getBot().getConfig().getWarning()).append(" Disconnected");
|
||||
|
||||
eb.setDescription(FormatUtil.filter(desc.toString()));
|
||||
return mb.setEmbeds(eb.build()).build();
|
||||
}
|
||||
|
||||
public String getStatusEmoji()
|
||||
{
|
||||
return audioPlayer.isPaused() ? PAUSE_EMOJI : PLAY_EMOJI;
|
||||
}
|
||||
|
||||
// Audio Send Handler methods
|
||||
/*@Override
|
||||
public boolean canProvide()
|
||||
{
|
||||
if (lastFrame == null)
|
||||
lastFrame = audioPlayer.provide();
|
||||
|
||||
return lastFrame != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] provide20MsAudio()
|
||||
{
|
||||
if (lastFrame == null)
|
||||
lastFrame = audioPlayer.provide();
|
||||
|
||||
byte[] data = lastFrame != null ? lastFrame.getData() : null;
|
||||
lastFrame = null;
|
||||
|
||||
return data;
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public boolean canProvide()
|
||||
{
|
||||
lastFrame = audioPlayer.provide();
|
||||
return lastFrame != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer provide20MsAudio()
|
||||
{
|
||||
return ByteBuffer.wrap(lastFrame.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpus()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Private methods
|
||||
private Guild guild(JDA jda)
|
||||
{
|
||||
return jda.getGuildById(guildId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.entities.Pair;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.exceptions.PermissionException;
|
||||
import net.dv8tion.jda.api.exceptions.RateLimitedException;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageEditData;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class NowplayingHandler
|
||||
{
|
||||
private final Bot bot;
|
||||
private final HashMap<Long,Pair<Long,Long>> lastNP; // guild -> channel,message
|
||||
|
||||
public NowplayingHandler(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
this.lastNP = new HashMap<>();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
if(!bot.getConfig().useNPImages())
|
||||
bot.getThreadpool().scheduleWithFixedDelay(() -> updateAll(), 0, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void setLastNPMessage(Message m)
|
||||
{
|
||||
lastNP.put(m.getGuild().getIdLong(), new Pair<>(m.getChannel().getIdLong(), m.getIdLong()));
|
||||
}
|
||||
|
||||
public void clearLastNPMessage(Guild guild)
|
||||
{
|
||||
lastNP.remove(guild.getIdLong());
|
||||
}
|
||||
|
||||
private void updateAll()
|
||||
{
|
||||
Set<Long> toRemove = new HashSet<>();
|
||||
for(long guildId: lastNP.keySet())
|
||||
{
|
||||
Guild guild = bot.getJDA().getGuildById(guildId);
|
||||
if(guild==null)
|
||||
{
|
||||
toRemove.add(guildId);
|
||||
continue;
|
||||
}
|
||||
Pair<Long,Long> pair = lastNP.get(guildId);
|
||||
TextChannel tc = guild.getTextChannelById(pair.getKey());
|
||||
if(tc==null)
|
||||
{
|
||||
toRemove.add(guildId);
|
||||
continue;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
|
||||
MessageCreateData msg = handler.getNowPlaying(bot.getJDA());
|
||||
if(msg==null)
|
||||
{
|
||||
msg = handler.getNoMusicPlaying(bot.getJDA());
|
||||
toRemove.add(guildId);
|
||||
}
|
||||
try
|
||||
{
|
||||
tc.editMessageById(pair.getValue(), MessageEditData.fromCreateData(msg)).queue(m->{}, t -> lastNP.remove(guildId));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
toRemove.add(guildId);
|
||||
}
|
||||
}
|
||||
toRemove.forEach(id -> lastNP.remove(id));
|
||||
}
|
||||
|
||||
// "event"-based methods
|
||||
public void onTrackUpdate(AudioTrack track)
|
||||
{
|
||||
// update bot status if applicable
|
||||
if(bot.getConfig().getSongInStatus())
|
||||
{
|
||||
if(track!=null && bot.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inAudioChannel()).count()<=1)
|
||||
bot.getJDA().getPresence().setActivity(Activity.listening(track.getInfo().title));
|
||||
else
|
||||
bot.resetGame();
|
||||
}
|
||||
}
|
||||
|
||||
public void onMessageDelete(Guild guild, long messageId)
|
||||
{
|
||||
Pair<Long,Long> pair = lastNP.get(guild.getIdLong());
|
||||
if(pair==null)
|
||||
return;
|
||||
if(pair.getValue() == messageId)
|
||||
lastNP.remove(guild.getIdLong());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.dunctebot.sourcemanagers.DuncteBotSources;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.BotConfig;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import com.jagrosh.jmusicbot.utils.YouTubeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.container.MediaContainerRegistry;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
|
||||
import com.sedmelluq.discord.lavaplayer.source.bandcamp.BandcampAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.beam.BeamAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.getyarn.GetyarnAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.nico.NicoAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.AbstractRoutePlanner;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.BalancingIpRoutePlanner;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.NanoIpRoutePlanner;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
|
||||
import dev.lavalink.youtube.YoutubeAudioSourceManager;
|
||||
import dev.lavalink.youtube.clients.Web;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class PlayerManager extends DefaultAudioPlayerManager
|
||||
{
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(PlayerManager.class);
|
||||
private final Bot bot;
|
||||
private final BotConfig config;
|
||||
|
||||
public PlayerManager(Bot bot, BotConfig config)
|
||||
{
|
||||
this.bot = bot;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
TransformativeAudioSourceManager.createTransforms(bot.getConfig().getTransforms()).forEach(t -> registerSourceManager(t));
|
||||
|
||||
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
|
||||
if (config.getYTRoutingPlanner() != YouTubeUtil.RoutingPlanner.NONE)
|
||||
{
|
||||
AbstractRoutePlanner routePlanner = YouTubeUtil.createRouterPlanner(config.getYTRoutingPlanner(), config.getYTIpBlocks());
|
||||
YoutubeIpRotatorSetup rotator = new YoutubeIpRotatorSetup(routePlanner);
|
||||
|
||||
rotator.forConfiguration(yt.getHttpInterfaceManager(), false)
|
||||
.withMainDelegateFilter(yt.getContextFilter())
|
||||
.setup();
|
||||
}
|
||||
|
||||
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
|
||||
registerSourceManager(yt);
|
||||
|
||||
registerSourceManager(SoundCloudAudioSourceManager.createDefault());
|
||||
registerSourceManager(new BandcampAudioSourceManager());
|
||||
registerSourceManager(new VimeoAudioSourceManager());
|
||||
registerSourceManager(new TwitchStreamAudioSourceManager());
|
||||
registerSourceManager(new BeamAudioSourceManager());
|
||||
registerSourceManager(new GetyarnAudioSourceManager());
|
||||
registerSourceManager(new NicoAudioSourceManager());
|
||||
registerSourceManager(new HttpAudioSourceManager(MediaContainerRegistry.DEFAULT_REGISTRY));
|
||||
|
||||
AudioSourceManagers.registerLocalSource(this);
|
||||
|
||||
DuncteBotSources.registerAll(this, "en-US");
|
||||
}
|
||||
|
||||
private YoutubeAudioSourceManager setupYoutubeAudioSourceManager()
|
||||
{
|
||||
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
|
||||
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
|
||||
|
||||
// OAuth2 setup
|
||||
if (bot.getConfig().useYoutubeOauth2())
|
||||
{
|
||||
String token = null;
|
||||
try
|
||||
{
|
||||
token = Files.readString(OtherUtil.getPath("youtubetoken.txt"));
|
||||
}
|
||||
catch (NoSuchFileException e)
|
||||
{
|
||||
/* ignored */
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOGGER.warn("Failed to read YouTube OAuth2 token file: {}", e.getMessage());
|
||||
return yt;
|
||||
}
|
||||
LOGGER.debug("Read YouTube OAuth2 refresh token from youtubetoken.txt");
|
||||
try
|
||||
{
|
||||
yt.useOauth2(token, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.warn("Failed to authorise with YouTube. If this issue persists, delete the youtubetoken.txt file to reauthorise.", e);
|
||||
}
|
||||
}
|
||||
return yt;
|
||||
}
|
||||
|
||||
public Bot getBot()
|
||||
{
|
||||
return bot;
|
||||
}
|
||||
|
||||
public boolean hasHandler(Guild guild)
|
||||
{
|
||||
return guild.getAudioManager().getSendingHandler()!=null;
|
||||
}
|
||||
|
||||
public AudioHandler setUpHandler(Guild guild)
|
||||
{
|
||||
AudioHandler handler;
|
||||
if(guild.getAudioManager().getSendingHandler()==null)
|
||||
{
|
||||
AudioPlayer player = createPlayer();
|
||||
player.setVolume(bot.getSettingsManager().getSettings(guild).getVolume());
|
||||
handler = new AudioHandler(this, guild, player);
|
||||
player.addListener(handler);
|
||||
guild.getAudioManager().setSendingHandler(handler);
|
||||
}
|
||||
else
|
||||
handler = (AudioHandler) guild.getAudioManager().getSendingHandler();
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
|
||||
import com.jagrosh.jmusicbot.queue.Queueable;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class QueuedTrack implements Queueable
|
||||
{
|
||||
private final AudioTrack track;
|
||||
private final RequestMetadata requestMetadata;
|
||||
|
||||
public QueuedTrack(AudioTrack track, RequestMetadata rm)
|
||||
{
|
||||
this.track = track;
|
||||
this.track.setUserData(rm == null ? RequestMetadata.EMPTY : rm);
|
||||
|
||||
this.requestMetadata = rm;
|
||||
if (this.track.isSeekable() && rm != null)
|
||||
track.setPosition(rm.requestInfo.startTimestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIdentifier()
|
||||
{
|
||||
return requestMetadata.getOwner();
|
||||
}
|
||||
|
||||
public AudioTrack getTrack()
|
||||
{
|
||||
return track;
|
||||
}
|
||||
|
||||
public RequestMetadata getRequestMetadata()
|
||||
{
|
||||
return requestMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String entry = "`[" + TimeUtil.formatTime(track.getDuration()) + "]` ";
|
||||
AudioTrackInfo trackInfo = track.getInfo();
|
||||
entry = entry + (trackInfo.uri.startsWith("http") ? "[**" + trackInfo.title + "**]("+trackInfo.uri+")" : "**" + trackInfo.title + "**");
|
||||
return entry + " - <@" + track.getUserData(RequestMetadata.class).getOwner() + ">";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class RequestMetadata
|
||||
{
|
||||
public static final RequestMetadata EMPTY = new RequestMetadata(null, null, null);
|
||||
|
||||
public final UserInfo user;
|
||||
public final RequestInfo requestInfo;
|
||||
public final QueueInfo queueInfo;
|
||||
|
||||
public RequestMetadata(User user, RequestInfo requestInfo, QueueInfo queueInfo)
|
||||
{
|
||||
this.user = user == null ? null : new UserInfo(user.getIdLong(), user.getName(), user.getDiscriminator(), user.getEffectiveAvatarUrl());
|
||||
this.requestInfo = requestInfo;
|
||||
this.queueInfo = queueInfo;
|
||||
}
|
||||
|
||||
public long getOwner()
|
||||
{
|
||||
return user == null ? 0L : user.id;
|
||||
}
|
||||
|
||||
public static RequestMetadata fromResultHandler(AudioTrack track, CommandEvent event)
|
||||
{
|
||||
return new RequestMetadata(event.getAuthor(), new RequestInfo(event.getArgs(), track.getInfo().uri),
|
||||
new QueueInfo("PLAY", null, event.getArgs(), System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
public static RequestMetadata fromResultHandler(AudioTrack track, CommandEvent event, String source, String playlistName, String searchQuery)
|
||||
{
|
||||
return new RequestMetadata(event.getAuthor(), new RequestInfo(event.getArgs(), track.getInfo().uri),
|
||||
new QueueInfo(source, playlistName, searchQuery, System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
public static RequestMetadata fromDefaultPlaylist(String playlistName)
|
||||
{
|
||||
return new RequestMetadata(null, new RequestInfo(playlistName, null),
|
||||
new QueueInfo("DEFAULT_PLAYLIST", playlistName, null, System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
public static class RequestInfo
|
||||
{
|
||||
public final String query, url;
|
||||
public final long startTimestamp;
|
||||
|
||||
public RequestInfo(String query, String url)
|
||||
{
|
||||
this(query, url, tryGetTimestamp(query));
|
||||
}
|
||||
|
||||
private RequestInfo(String query, String url, long startTimestamp)
|
||||
{
|
||||
this.url = url;
|
||||
this.query = query;
|
||||
this.startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
private static final Pattern youtubeTimestampPattern = Pattern.compile("youtu(?:\\.be|be\\..+)/.*\\?.*(?!.*list=)t=([\\dhms]+)");
|
||||
private static long tryGetTimestamp(String url)
|
||||
{
|
||||
if(url == null || url.isEmpty())
|
||||
return 0;
|
||||
Matcher matcher = youtubeTimestampPattern.matcher(url);
|
||||
return matcher.find() ? TimeUtil.parseUnitTime(matcher.group(1)) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class QueueInfo
|
||||
{
|
||||
public final String source;
|
||||
public final String playlistName;
|
||||
public final String searchQuery;
|
||||
public final long queuedAt;
|
||||
|
||||
public QueueInfo(String source, String playlistName, String searchQuery, long queuedAt)
|
||||
{
|
||||
this.source = source;
|
||||
this.playlistName = playlistName;
|
||||
this.searchQuery = searchQuery;
|
||||
this.queuedAt = queuedAt;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UserInfo
|
||||
{
|
||||
public final long id;
|
||||
public final String username, discrim, avatar;
|
||||
|
||||
private UserInfo(long id, String username, String discrim, String avatar)
|
||||
{
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.discrim = discrim;
|
||||
this.avatar = avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright 2026
|
||||
*
|
||||
* 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.settings.Settings;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
||||
import net.dv8tion.jda.api.requests.ErrorResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageEditData;
|
||||
|
||||
public class StatusMessageHandler
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger("StatusMessage");
|
||||
private final Bot bot;
|
||||
private final Map<Long, StatusState> states;
|
||||
|
||||
public StatusMessageHandler(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
this.states = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
if(!bot.getConfig().useStatusMessages())
|
||||
return;
|
||||
bot.getThreadpool().scheduleWithFixedDelay(() -> updateAll(), 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void onMessageReceived(MessageReceivedEvent event)
|
||||
{
|
||||
if(!bot.getConfig().useStatusMessages())
|
||||
return;
|
||||
if(!event.isFromGuild())
|
||||
return;
|
||||
Guild guild = event.getGuild();
|
||||
StatusState state = states.computeIfAbsent(guild.getIdLong(), id -> new StatusState());
|
||||
synchronized(state)
|
||||
{
|
||||
MessageChannelUnion channelUnion = event.getChannel();
|
||||
if(channelUnion == null || channelUnion.getType() != ChannelType.TEXT)
|
||||
return;
|
||||
TextChannel channel = channelUnion.asTextChannel();
|
||||
|
||||
boolean isCommand = isCommandMessage(event.getMessage(), guild);
|
||||
Settings settings = bot.getSettingsManager().getSettings(guild);
|
||||
if(isCommand && !event.getAuthor().isBot() && settings.getTextChannel(guild) == null)
|
||||
state.lastCommandChannelId = channel.getIdLong();
|
||||
|
||||
Long statusChannelId = resolveStatusChannelId(guild, state);
|
||||
if(statusChannelId == null || statusChannelId == 0L)
|
||||
return;
|
||||
if(isCommand && !event.getAuthor().isBot() && channel.getIdLong() == statusChannelId)
|
||||
state.pendingRepost = true;
|
||||
if(channel.getIdLong() != statusChannelId)
|
||||
return;
|
||||
|
||||
if(state.messageId != 0L && event.getMessageIdLong() == state.messageId)
|
||||
return;
|
||||
|
||||
if(state.pendingRepost)
|
||||
{
|
||||
long selfId = bot.getJDA().getSelfUser().getIdLong();
|
||||
if(event.getAuthor().getIdLong() == selfId)
|
||||
{
|
||||
debug(guild, state, "pending_repost_trigger", "author_is_self", event.getMessageIdLong());
|
||||
repostStatusMessage(guild, channel, state);
|
||||
state.pendingRepost = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid repost loops when the status message itself is created
|
||||
if(event.getAuthor().getIdLong() == bot.getJDA().getSelfUser().getIdLong() && state.createInFlight)
|
||||
return;
|
||||
|
||||
debug(guild, state, "repost_due_to_message", "message_author", event.getAuthor().getIdLong());
|
||||
repostStatusMessage(guild, channel, state);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMessageDelete(Guild guild, long messageId)
|
||||
{
|
||||
if(!bot.getConfig().useStatusMessages())
|
||||
return;
|
||||
StatusState state = states.get(guild.getIdLong());
|
||||
if(state == null)
|
||||
return;
|
||||
synchronized(state)
|
||||
{
|
||||
if(state.createInFlight)
|
||||
{
|
||||
debug(guild, state, "delete_ignored_in_flight", "message", messageId);
|
||||
return;
|
||||
}
|
||||
if(state.messageId == messageId)
|
||||
state.messageId = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
public void onTrackUpdate(long guildId)
|
||||
{
|
||||
if(!bot.getConfig().useStatusMessages())
|
||||
return;
|
||||
StatusState state = states.computeIfAbsent(guildId, id -> new StatusState());
|
||||
state.forceUpdate = true;
|
||||
}
|
||||
|
||||
private void updateAll()
|
||||
{
|
||||
if(!bot.getConfig().useStatusMessages())
|
||||
return;
|
||||
JDA jda = bot.getJDA();
|
||||
if(jda == null)
|
||||
return;
|
||||
for(Map.Entry<Long, StatusState> entry : states.entrySet())
|
||||
{
|
||||
long guildId = entry.getKey();
|
||||
StatusState state = entry.getValue();
|
||||
synchronized(state)
|
||||
{
|
||||
Guild guild = jda.getGuildById(guildId);
|
||||
if(guild == null)
|
||||
continue;
|
||||
TextChannel channel = resolveStatusChannel(guild, state);
|
||||
if(channel == null)
|
||||
continue;
|
||||
AudioHandler handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
|
||||
if(handler == null)
|
||||
continue;
|
||||
if(state.createInFlight)
|
||||
continue;
|
||||
|
||||
boolean isPlaying = handler.isMusicPlaying(jda);
|
||||
if(!isPlaying && !state.forceUpdate)
|
||||
continue;
|
||||
|
||||
if(state.pendingRepost && (state.messageId == 0L || state.channelId != channel.getIdLong()))
|
||||
continue;
|
||||
|
||||
MessageCreateData statusMessage = handler.getStatusMessage(jda);
|
||||
if(state.messageId == 0L || state.channelId != channel.getIdLong())
|
||||
{
|
||||
debug(guild, state, "repost_due_to_missing_or_channel", "channel", channel.getIdLong());
|
||||
repostStatusMessage(guild, channel, state, statusMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
final long editingId = state.messageId;
|
||||
channel.editMessageById(editingId, MessageEditData.fromCreateData(statusMessage))
|
||||
.queue(m -> {}, t ->
|
||||
{
|
||||
debug(guild, state, "edit_failed", t, editingId);
|
||||
if(state.messageId == editingId && shouldResetMessageId(t))
|
||||
state.messageId = 0L;
|
||||
});
|
||||
}
|
||||
state.forceUpdate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void repostStatusMessage(Guild guild, TextChannel channel, StatusState state)
|
||||
{
|
||||
MessageCreateData statusMessage = getStatusMessage(guild);
|
||||
if(statusMessage == null)
|
||||
return;
|
||||
repostStatusMessage(guild, channel, state, statusMessage);
|
||||
}
|
||||
|
||||
private void repostStatusMessage(Guild guild, TextChannel channel, StatusState state, MessageCreateData statusMessage)
|
||||
{
|
||||
if(state.createInFlight)
|
||||
return;
|
||||
state.createInFlight = true;
|
||||
debug(guild, state, "repost_start", "channel", channel.getIdLong());
|
||||
deleteOldStatusMessage(guild, state);
|
||||
channel.sendMessage(statusMessage).queue(m ->
|
||||
{
|
||||
state.channelId = channel.getIdLong();
|
||||
state.messageId = m.getIdLong();
|
||||
state.createInFlight = false;
|
||||
debug(guild, state, "repost_success", "message", m.getIdLong());
|
||||
}, t ->
|
||||
{
|
||||
state.createInFlight = false;
|
||||
debug(guild, state, "repost_failed", t, 0L);
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteOldStatusMessage(Guild guild, StatusState state)
|
||||
{
|
||||
if(state.messageId == 0L)
|
||||
return;
|
||||
TextChannel oldChannel = guild.getTextChannelById(state.channelId);
|
||||
if(oldChannel == null)
|
||||
return;
|
||||
oldChannel.deleteMessageById(state.messageId).queue(m -> {}, t -> debug(guild, state, "delete_failed", t, state.messageId));
|
||||
}
|
||||
|
||||
private boolean shouldResetMessageId(Throwable throwable)
|
||||
{
|
||||
if(throwable instanceof ErrorResponseException)
|
||||
{
|
||||
ErrorResponse response = ((ErrorResponseException)throwable).getErrorResponse();
|
||||
return response == ErrorResponse.UNKNOWN_MESSAGE || response == ErrorResponse.UNKNOWN_CHANNEL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void debug(Guild guild, StatusState state, String action, String key, long value)
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
LOG.debug("guild={} action={} {}={} msgId={} chanId={} pending={} inFlight={} force={}",
|
||||
guild.getId(), action, key, value, state.messageId, state.channelId, state.pendingRepost, state.createInFlight, state.forceUpdate);
|
||||
}
|
||||
|
||||
private void debug(Guild guild, StatusState state, String action, Throwable throwable, long messageId)
|
||||
{
|
||||
if(LOG.isDebugEnabled())
|
||||
{
|
||||
String err = throwable == null ? "null" : throwable.getClass().getSimpleName() + ": " + throwable.getMessage();
|
||||
LOG.debug("guild={} action={} error={} msgId={} chanId={} pending={} inFlight={} force={}",
|
||||
guild.getId(), action, err, messageId == 0L ? state.messageId : messageId,
|
||||
state.channelId, state.pendingRepost, state.createInFlight, state.forceUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageCreateData getStatusMessage(Guild guild)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
|
||||
if(handler == null)
|
||||
return null;
|
||||
return handler.getStatusMessage(bot.getJDA());
|
||||
}
|
||||
|
||||
private TextChannel resolveStatusChannel(Guild guild, StatusState state)
|
||||
{
|
||||
Long id = resolveStatusChannelId(guild, state);
|
||||
return id == null ? null : guild.getTextChannelById(id);
|
||||
}
|
||||
|
||||
private Long resolveStatusChannelId(Guild guild, StatusState state)
|
||||
{
|
||||
Settings settings = bot.getSettingsManager().getSettings(guild);
|
||||
if(settings == null)
|
||||
return null;
|
||||
if(settings.getTextChannel(guild) != null)
|
||||
return settings.getTextChannel(guild).getIdLong();
|
||||
return state.lastCommandChannelId == 0L ? null : state.lastCommandChannelId;
|
||||
}
|
||||
|
||||
private boolean isCommandMessage(Message message, Guild guild)
|
||||
{
|
||||
String content = message.getContentRaw();
|
||||
if(content == null || content.isEmpty())
|
||||
return false;
|
||||
Settings settings = bot.getSettingsManager().getSettings(guild);
|
||||
String prefix = settings.getPrefix() != null ? settings.getPrefix() : bot.getConfig().getPrefix();
|
||||
String altprefix = bot.getConfig().getAltPrefix();
|
||||
if(prefix != null && prefix.equalsIgnoreCase("@mention"))
|
||||
{
|
||||
long botId = bot.getJDA().getSelfUser().getIdLong();
|
||||
String mention = "<@" + botId + ">";
|
||||
String mentionNick = "<@!" + botId + ">";
|
||||
if(content.startsWith(mention) || content.startsWith(mentionNick))
|
||||
return true;
|
||||
}
|
||||
if(prefix != null && !prefix.equalsIgnoreCase("@mention") && content.startsWith(prefix))
|
||||
return true;
|
||||
return altprefix != null && !altprefix.isEmpty() && content.startsWith(altprefix);
|
||||
}
|
||||
|
||||
private static class StatusState
|
||||
{
|
||||
private long channelId = 0L;
|
||||
private long messageId = 0L;
|
||||
private long lastCommandChannelId = 0L;
|
||||
private boolean pendingRepost = false;
|
||||
private boolean forceUpdate = false;
|
||||
private boolean createInFlight = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2021 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioItem;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioReference;
|
||||
import com.typesafe.config.Config;
|
||||
import dev.lavalink.youtube.YoutubeAudioSourceManager;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class TransformativeAudioSourceManager extends YoutubeAudioSourceManager
|
||||
{
|
||||
private final static Logger log = LoggerFactory.getLogger(TransformativeAudioSourceManager.class);
|
||||
private final String name, regex, replacement, selector, format;
|
||||
|
||||
public TransformativeAudioSourceManager(String name, Config object)
|
||||
{
|
||||
this(name, object.getString("regex"), object.getString("replacement"), object.getString("selector"), object.getString("format"));
|
||||
}
|
||||
|
||||
public TransformativeAudioSourceManager(String name, String regex, String replacement, String selector, String format)
|
||||
{
|
||||
this.name = name;
|
||||
this.regex = regex;
|
||||
this.replacement = replacement;
|
||||
this.selector = selector;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioItem loadItem(AudioPlayerManager apm, AudioReference ar)
|
||||
{
|
||||
if(ar.identifier == null || !ar.identifier.matches(regex))
|
||||
return null;
|
||||
try
|
||||
{
|
||||
String url = ar.identifier.replaceAll(regex, replacement);
|
||||
Document doc = Jsoup.connect(url).get();
|
||||
String value = doc.selectFirst(selector).ownText();
|
||||
String formattedValue = String.format(format, value);
|
||||
return super.loadItem(apm, new AudioReference(formattedValue, null));
|
||||
}
|
||||
catch (PatternSyntaxException ex)
|
||||
{
|
||||
log.info(String.format("Invalid pattern syntax '%s' in source '%s'", regex, name));
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.warn(String.format("Failed to resolve URL in source '%s': ", name), ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.warn(String.format("Exception in source '%s'", name), ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<TransformativeAudioSourceManager> createTransforms(Config transforms)
|
||||
{
|
||||
try
|
||||
{
|
||||
return transforms.root().entrySet().stream()
|
||||
.map(e -> new TransformativeAudioSourceManager(e.getKey(), transforms.getConfig(e.getKey())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.warn("Invalid transform ", ex);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands;
|
||||
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public abstract class AdminCommand extends Command
|
||||
{
|
||||
protected final Bot bot;
|
||||
|
||||
public AdminCommand(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void execute(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.clear();
|
||||
try
|
||||
{
|
||||
doCommand(event);
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
if(entry != null && entry.manual)
|
||||
return;
|
||||
String result = entry == null || entry.result == null ? "OK" : entry.result;
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
logCommand(event, result, meta);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
if(meta == null)
|
||||
meta = new JSONObject();
|
||||
meta.put("error_reason", ex.getClass().getSimpleName());
|
||||
logCommand(event, "ERROR", meta);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void doCommand(CommandEvent event);
|
||||
|
||||
protected void logCommand(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public abstract class DJCommand extends MusicCommand
|
||||
{
|
||||
public DJCommand(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.category = new Category("DJ", event -> checkDJPermission(event));
|
||||
}
|
||||
|
||||
public static boolean checkDJPermission(CommandEvent event)
|
||||
{
|
||||
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
|
||||
return true;
|
||||
if(event.getGuild()==null)
|
||||
return true;
|
||||
if(event.getMember().hasPermission(Permission.MANAGE_SERVER))
|
||||
return true;
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
Role dj = settings.getRole(event.getGuild());
|
||||
return dj!=null && (event.getMember().getRoles().contains(dj) || dj.getIdLong()==event.getGuild().getIdLong());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2026
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands;
|
||||
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public abstract class LoggedCommand extends Command
|
||||
{
|
||||
protected final Bot bot;
|
||||
|
||||
public LoggedCommand(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void execute(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.clear();
|
||||
try
|
||||
{
|
||||
doCommand(event);
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
if(entry != null && entry.manual)
|
||||
return;
|
||||
String result = entry == null || entry.result == null ? "OK" : entry.result;
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
logCommand(event, result, meta);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
if(meta == null)
|
||||
meta = new JSONObject();
|
||||
meta.put("error_reason", ex.getClass().getSimpleName());
|
||||
logCommand(event, "ERROR", meta);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void doCommand(CommandEvent event);
|
||||
|
||||
protected void logCommand(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands;
|
||||
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
import net.dv8tion.jda.api.entities.GuildVoiceState;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import net.dv8tion.jda.api.exceptions.PermissionException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public abstract class MusicCommand extends Command
|
||||
{
|
||||
protected final Bot bot;
|
||||
protected boolean bePlaying;
|
||||
protected boolean beListening;
|
||||
|
||||
public MusicCommand(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
this.guildOnly = true;
|
||||
this.category = new Category("Music");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.clear();
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
TextChannel tchannel = settings.getTextChannel(event.getGuild());
|
||||
if(tchannel!=null && !event.getTextChannel().equals(tchannel))
|
||||
{
|
||||
try
|
||||
{
|
||||
event.getMessage().delete().queue();
|
||||
} catch(PermissionException ignore){}
|
||||
event.replyInDm(event.getClient().getError()+" You can only use that command in "+tchannel.getAsMention()+"!");
|
||||
logCommand(event, "ERROR", new JSONObject().put("error_reason", "wrong_text_channel"));
|
||||
return;
|
||||
}
|
||||
bot.getPlayerManager().setUpHandler(event.getGuild()); // no point constantly checking for this later
|
||||
if(bePlaying && !((AudioHandler)event.getGuild().getAudioManager().getSendingHandler()).isMusicPlaying(event.getJDA()))
|
||||
{
|
||||
event.reply(event.getClient().getError()+" There must be music playing to use that!");
|
||||
logCommand(event, "ERROR", new JSONObject().put("error_reason", "no_music_playing"));
|
||||
return;
|
||||
}
|
||||
if(beListening)
|
||||
{
|
||||
VoiceChannel current = (VoiceChannel) event.getGuild().getSelfMember().getVoiceState().getChannel();
|
||||
if(current==null)
|
||||
current = settings.getVoiceChannel(event.getGuild());
|
||||
GuildVoiceState userState = event.getMember().getVoiceState();
|
||||
if(!userState.inAudioChannel() || userState.isDeafened() || (current!=null && !userState.getChannel().equals(current)))
|
||||
{
|
||||
event.replyError("You must be listening in "+(current==null ? "a voice channel" : current.getAsMention())+" to use that!");
|
||||
logCommand(event, "ERROR", new JSONObject().put("error_reason", "not_listening"));
|
||||
return;
|
||||
}
|
||||
|
||||
VoiceChannel afkChannel = userState.getGuild().getAfkChannel();
|
||||
if(afkChannel != null && afkChannel.equals(userState.getChannel()))
|
||||
{
|
||||
event.replyError("You cannot use that command in an AFK channel!");
|
||||
logCommand(event, "ERROR", new JSONObject().put("error_reason", "afk_channel"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!event.getGuild().getSelfMember().getVoiceState().inAudioChannel())
|
||||
{
|
||||
try
|
||||
{
|
||||
event.getGuild().getAudioManager().openAudioConnection(userState.getChannel());
|
||||
}
|
||||
catch(PermissionException ex)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" I am unable to connect to "+userState.getChannel().getAsMention()+"!");
|
||||
logCommand(event, "ERROR", new JSONObject().put("error_reason", "cannot_connect"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
doCommand(event);
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
if(entry != null && entry.manual)
|
||||
return;
|
||||
String result = entry == null || entry.result == null ? "OK" : entry.result;
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
logCommand(event, result, meta);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
if(meta == null)
|
||||
meta = new JSONObject();
|
||||
meta.put("error_reason", ex.getClass().getSimpleName());
|
||||
logCommand(event, "ERROR", meta);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void doCommand(CommandEvent event);
|
||||
|
||||
protected void logCommand(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands;
|
||||
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public abstract class OwnerCommand extends Command
|
||||
{
|
||||
protected final Bot bot;
|
||||
|
||||
public OwnerCommand(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
this.category = new Category("Owner");
|
||||
this.ownerCommand = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void execute(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.clear();
|
||||
try
|
||||
{
|
||||
doCommand(event);
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
if(entry != null && entry.manual)
|
||||
return;
|
||||
String result = entry == null || entry.result == null ? "OK" : entry.result;
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
logCommand(event, result, meta);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
CommandLogContext.CommandLogEntry entry = CommandLogContext.consume();
|
||||
JSONObject meta = entry == null ? null : entry.meta;
|
||||
if(meta == null)
|
||||
meta = new JSONObject();
|
||||
meta.put("error_reason", ex.getClass().getSimpleName());
|
||||
logCommand(event, "ERROR", meta);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void doCommand(CommandEvent event);
|
||||
|
||||
protected void logCommand(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -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.admin;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.AdminCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class PrefixCmd extends AdminCommand
|
||||
{
|
||||
public PrefixCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "prefix";
|
||||
this.help = "sets a server-specific prefix";
|
||||
this.arguments = "<prefix|NONE>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.replyError("Please include a prefix or NONE");
|
||||
CommandLogContext.setError("missing_prefix");
|
||||
return;
|
||||
}
|
||||
|
||||
Settings s = event.getClient().getSettingsFor(event.getGuild());
|
||||
String before = s.getPrefix();
|
||||
if(event.getArgs().equalsIgnoreCase("none"))
|
||||
{
|
||||
s.setPrefix(null);
|
||||
event.replySuccess("Prefix cleared.");
|
||||
JSONObject meta = new JSONObject().put("before", before).put("after", JSONObject.NULL);
|
||||
CommandLogContext.setMeta(meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
s.setPrefix(event.getArgs());
|
||||
event.replySuccess("Custom prefix set to `" + event.getArgs() + "` on *" + event.getGuild().getName() + "*");
|
||||
JSONObject meta = new JSONObject().put("before", before).put("after", event.getArgs());
|
||||
CommandLogContext.setMeta(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.admin;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.AdminCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
*/
|
||||
public class QueueTypeCmd extends AdminCommand
|
||||
{
|
||||
public QueueTypeCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "queuetype";
|
||||
this.help = "changes the queue type";
|
||||
this.arguments = "[" + String.join("|", QueueType.getNames()) + "]";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
String args = event.getArgs();
|
||||
QueueType value;
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
QueueType before = settings.getQueueType();
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
QueueType currentType = settings.getQueueType();
|
||||
event.reply(currentType.getEmoji() + " Current queue type is: `" + currentType.getUserFriendlyName() + "`.");
|
||||
CommandLogContext.setMeta(new JSONObject().put("current", currentType.name()));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
value = QueueType.valueOf(args.toUpperCase());
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
event.replyError("Invalid queue type. Valid types are: [" + String.join("|", QueueType.getNames()) + "]");
|
||||
CommandLogContext.setError("invalid_queue_type");
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.getQueueType() != value)
|
||||
{
|
||||
settings.setQueueType(value);
|
||||
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
if (handler != null)
|
||||
handler.setQueueType(value);
|
||||
}
|
||||
|
||||
event.reply(value.getEmoji() + " Queue type was set to `" + value.getUserFriendlyName() + "`.");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before.name()).put("after", value.name()));
|
||||
}
|
||||
}
|
||||
+83
-69
@@ -1,69 +1,83 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.commands;
|
||||
|
||||
import java.util.List;
|
||||
import me.jagrosh.jdautilities.commandclient.Command;
|
||||
import me.jagrosh.jdautilities.commandclient.CommandEvent;
|
||||
import me.jagrosh.jmusicbot.Bot;
|
||||
import me.jagrosh.jmusicbot.utils.FinderUtil;
|
||||
import me.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.core.entities.Role;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetdjCmd extends Command {
|
||||
|
||||
private final Bot bot;
|
||||
public SetdjCmd(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
this.name = "setdj";
|
||||
this.help = "sets the DJ role for certain music commands";
|
||||
this.arguments = "<rolename|NONE>";
|
||||
this.guildOnly = true;
|
||||
this.category = bot.ADMIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event) {
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Please include a role name or NONE");
|
||||
}
|
||||
else if(event.getArgs().equalsIgnoreCase("none"))
|
||||
{
|
||||
bot.clearTextChannel(event.getGuild());
|
||||
event.reply(event.getClient().getSuccess()+" DJ role cleared; Only Admins can use the DJ commands.");
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Role> list = FinderUtil.findRole(event.getArgs(), event.getGuild());
|
||||
if(list.isEmpty())
|
||||
event.reply(event.getClient().getWarning()+" No Roles found matching \""+event.getArgs()+"\"");
|
||||
else if (list.size()>1)
|
||||
event.reply(event.getClient().getWarning()+FormatUtil.listOfRoles(list, event.getArgs()));
|
||||
else
|
||||
{
|
||||
bot.setRole(list.get(0));
|
||||
event.reply(event.getClient().getSuccess()+" DJ commands can now be used by users with the **"+list.get(0).getName()+"** role.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.admin;
|
||||
|
||||
import java.util.List;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.AdminCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetdjCmd extends AdminCommand
|
||||
{
|
||||
public SetdjCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setdj";
|
||||
this.help = "sets the DJ role for certain music commands";
|
||||
this.arguments = "<rolename|NONE>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Please include a role name or NONE");
|
||||
CommandLogContext.setError("missing_role");
|
||||
return;
|
||||
}
|
||||
Settings s = event.getClient().getSettingsFor(event.getGuild());
|
||||
String before = s.getRole(event.getGuild()) == null ? null : s.getRole(event.getGuild()).getId();
|
||||
if(event.getArgs().equalsIgnoreCase("none"))
|
||||
{
|
||||
s.setDJRole(null);
|
||||
event.reply(event.getClient().getSuccess()+" DJ role cleared; Only Admins can use the DJ commands.");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", JSONObject.NULL));
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Role> list = FinderUtil.findRoles(event.getArgs(), event.getGuild());
|
||||
if(list.isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+" No Roles found matching \""+event.getArgs()+"\"");
|
||||
CommandLogContext.setError("role_not_found");
|
||||
}
|
||||
else if (list.size()>1)
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+FormatUtil.listOfRoles(list, event.getArgs()));
|
||||
CommandLogContext.setError("role_ambiguous");
|
||||
}
|
||||
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.");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", list.get(0).getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.admin;
|
||||
|
||||
import java.util.List;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.AdminCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SettcCmd extends AdminCommand
|
||||
{
|
||||
public SettcCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "settc";
|
||||
this.help = "sets the text channel for music commands";
|
||||
this.arguments = "<channel|NONE>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Please include a text channel or NONE");
|
||||
CommandLogContext.setError("missing_text_channel");
|
||||
return;
|
||||
}
|
||||
Settings s = event.getClient().getSettingsFor(event.getGuild());
|
||||
String before = s.getTextChannel(event.getGuild()) == null ? null : s.getTextChannel(event.getGuild()).getId();
|
||||
if(event.getArgs().equalsIgnoreCase("none"))
|
||||
{
|
||||
s.setTextChannel(null);
|
||||
event.reply(event.getClient().getSuccess()+" Music commands can now be used in any channel");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", JSONObject.NULL));
|
||||
}
|
||||
else
|
||||
{
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
List<TextChannel> list = (List) FinderUtil.findTextChannels(event.getArgs(), event.getGuild());
|
||||
if(list.isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+" No Text Channels found matching \""+event.getArgs()+"\"");
|
||||
CommandLogContext.setError("text_channel_not_found");
|
||||
}
|
||||
else if (list.size()>1)
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+FormatUtil.listOfTChannels(list, event.getArgs()));
|
||||
CommandLogContext.setError("text_channel_ambiguous");
|
||||
}
|
||||
else
|
||||
{
|
||||
s.setTextChannel(list.get(0));
|
||||
event.reply(event.getClient().getSuccess()+" Music commands can now only be used in <#"+list.get(0).getId()+">");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", list.get(0).getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.admin;
|
||||
|
||||
import java.util.List;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.AdminCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetvcCmd extends AdminCommand
|
||||
{
|
||||
public SetvcCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setvc";
|
||||
this.help = "sets the voice channel for playing music";
|
||||
this.arguments = "<channel|NONE>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Please include a voice channel or NONE");
|
||||
CommandLogContext.setError("missing_voice_channel");
|
||||
return;
|
||||
}
|
||||
Settings s = event.getClient().getSettingsFor(event.getGuild());
|
||||
String before = s.getVoiceChannel(event.getGuild()) == null ? null : s.getVoiceChannel(event.getGuild()).getId();
|
||||
if(event.getArgs().equalsIgnoreCase("none"))
|
||||
{
|
||||
s.setVoiceChannel(null);
|
||||
event.reply(event.getClient().getSuccess()+" Music can now be played in any channel");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", JSONObject.NULL));
|
||||
}
|
||||
else
|
||||
{
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
List<VoiceChannel> list = (List) FinderUtil.findVoiceChannels(event.getArgs(), event.getGuild());
|
||||
if(list.isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+" No Voice Channels found matching \""+event.getArgs()+"\"");
|
||||
CommandLogContext.setError("voice_channel_not_found");
|
||||
}
|
||||
else if (list.size()>1)
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+FormatUtil.listOfVChannels(list, event.getArgs()));
|
||||
CommandLogContext.setError("voice_channel_ambiguous");
|
||||
}
|
||||
else
|
||||
{
|
||||
s.setVoiceChannel(list.get(0));
|
||||
event.reply(event.getClient().getSuccess()+" Music can now only be played in "+list.get(0).getAsMention());
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", list.get(0).getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.admin;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.AdminCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class SkipratioCmd extends AdminCommand
|
||||
{
|
||||
public SkipratioCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setskip";
|
||||
this.help = "sets a server-specific skip percentage";
|
||||
this.arguments = "<0 - 100>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
try
|
||||
{
|
||||
int val = Integer.parseInt(event.getArgs().endsWith("%") ? event.getArgs().substring(0,event.getArgs().length()-1) : event.getArgs());
|
||||
if( val < 0 || val > 100)
|
||||
{
|
||||
event.replyError("The provided value must be between 0 and 100!");
|
||||
CommandLogContext.setError("invalid_skip_ratio");
|
||||
return;
|
||||
}
|
||||
Settings s = event.getClient().getSettingsFor(event.getGuild());
|
||||
double before = s.getSkipRatio();
|
||||
s.setSkipRatio(val / 100.0);
|
||||
event.replySuccess("Skip percentage has been set to `" + val + "%` of listeners on *" + event.getGuild().getName() + "*");
|
||||
CommandLogContext.setMeta(new JSONObject()
|
||||
.put("before", before)
|
||||
.put("after", s.getSkipRatio()));
|
||||
}
|
||||
catch(NumberFormatException ex)
|
||||
{
|
||||
event.replyError("Please include an integer between 0 and 100 (default is 55). This number is the percentage of listening users that must vote to skip a song.");
|
||||
CommandLogContext.setError("invalid_skip_ratio");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2019 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.dj;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.commons.utils.FinderUtil;
|
||||
import com.jagrosh.jdautilities.menu.OrderedMenu;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Michaili K.
|
||||
*/
|
||||
public class ForceRemoveCmd extends DJCommand
|
||||
{
|
||||
public ForceRemoveCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "forceremove";
|
||||
this.help = "removes all entries by a user from the queue";
|
||||
this.arguments = "<user>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = false;
|
||||
this.bePlaying = true;
|
||||
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if (event.getArgs().isEmpty())
|
||||
{
|
||||
event.replyError("You need to mention a user!");
|
||||
CommandLogContext.setError("missing_user");
|
||||
return;
|
||||
}
|
||||
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
if (handler.getQueue().isEmpty())
|
||||
{
|
||||
event.replyError("There is nothing in the queue!");
|
||||
CommandLogContext.setError("empty_queue");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
User target;
|
||||
List<Member> found = FinderUtil.findMembers(event.getArgs(), event.getGuild());
|
||||
|
||||
if(found.isEmpty())
|
||||
{
|
||||
event.replyError("Unable to find the user!");
|
||||
CommandLogContext.setError("user_not_found");
|
||||
return;
|
||||
}
|
||||
else if(found.size()>1)
|
||||
{
|
||||
OrderedMenu.Builder builder = new OrderedMenu.Builder();
|
||||
for(int i=0; i<found.size() && i<4; i++)
|
||||
{
|
||||
Member member = found.get(i);
|
||||
builder.addChoice("**"+member.getUser().getName()+"**#"+member.getUser().getDiscriminator());
|
||||
}
|
||||
|
||||
builder
|
||||
.setSelection((msg, i) -> removeAllEntries(found.get(i-1).getUser(), event))
|
||||
.setText("Found multiple users:")
|
||||
.setColor(event.getSelfMember().getColor())
|
||||
.useNumbers()
|
||||
.setUsers(event.getAuthor())
|
||||
.useCancelButton(true)
|
||||
.setCancel((msg) -> {})
|
||||
.setEventWaiter(bot.getWaiter())
|
||||
.setTimeout(1, TimeUnit.MINUTES)
|
||||
|
||||
.build().display(event.getChannel());
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
target = found.get(0).getUser();
|
||||
}
|
||||
|
||||
removeAllEntries(target, event);
|
||||
|
||||
}
|
||||
|
||||
private void removeAllEntries(User target, CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
int sizeBefore = handler.getQueue().size();
|
||||
int count = handler.getQueue().removeAll(target.getIdLong());
|
||||
if (count == 0)
|
||||
{
|
||||
event.replyWarning("**"+target.getName()+"** doesn't have any songs in the queue!");
|
||||
CommandLogContext.setError("no_entries_for_user");
|
||||
}
|
||||
else
|
||||
{
|
||||
event.replySuccess("Successfully removed `"+count+"` entries from "+FormatUtil.formatUsername(target)+".");
|
||||
}
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("target_user_id", target.getIdLong())
|
||||
.put("removed_count", count)
|
||||
.put("queue_size_before", sizeBefore)
|
||||
.put("queue_size_after", handler.getQueue().size());
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), null, "REMOVE_ALL",
|
||||
"FORCE", null, null, null, meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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.commands.dj;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class ForceskipCmd extends DJCommand
|
||||
{
|
||||
public ForceskipCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "forceskip";
|
||||
this.help = "skips the current song";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = handler.getRequestMetadata();
|
||||
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title
|
||||
+"** "+(rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + FormatUtil.formatUsername(rm.user) + "**)"));
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
JSONObject meta = new JSONObject().put("reason", "FORCE");
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"SKIP_FORCE", null, null, null, null, meta.toString());
|
||||
CommandLogContext.setMeta(meta);
|
||||
}
|
||||
handler.getPlayer().stopTrack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
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.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.queue.AbstractQueue;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Command that provides users the ability to move a track in the playlist.
|
||||
*/
|
||||
public class MoveTrackCmd extends DJCommand
|
||||
{
|
||||
|
||||
public MoveTrackCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "movetrack";
|
||||
this.help = "move a track in the current queue to a different position";
|
||||
this.arguments = "<from> <to>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
int from;
|
||||
int to;
|
||||
|
||||
String[] parts = event.getArgs().split("\\s+", 2);
|
||||
if(parts.length < 2)
|
||||
{
|
||||
event.replyError("Please include two valid indexes.");
|
||||
CommandLogContext.setError("missing_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.");
|
||||
CommandLogContext.setError("invalid_indexes");
|
||||
return;
|
||||
}
|
||||
|
||||
if (from == to)
|
||||
{
|
||||
event.replyError("Can't move a track to the same position.");
|
||||
CommandLogContext.setError("same_position");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that from and to are available
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
AbstractQueue<QueuedTrack> queue = handler.getQueue();
|
||||
if (isUnavailablePosition(queue, from))
|
||||
{
|
||||
String reply = String.format("`%d` is not a valid position in the queue!", from);
|
||||
event.replyError(reply);
|
||||
CommandLogContext.setError("invalid_from_position");
|
||||
return;
|
||||
}
|
||||
if (isUnavailablePosition(queue, to))
|
||||
{
|
||||
String reply = String.format("`%d` is not a valid position in the queue!", to);
|
||||
event.replyError(reply);
|
||||
CommandLogContext.setError("invalid_to_position");
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the track
|
||||
int sizeBefore = queue.size();
|
||||
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);
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("position_from", from)
|
||||
.put("position_to", to)
|
||||
.put("queue_size_before", sizeBefore)
|
||||
.put("queue_size_after", queue.size());
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), track.getTrack(),
|
||||
"MOVE", null, to, null, null, meta.toString());
|
||||
}
|
||||
|
||||
private static boolean isUnavailablePosition(AbstractQueue<QueuedTrack> queue, int position)
|
||||
{
|
||||
return (position < 1 || position > queue.size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class PauseCmd extends DJCommand
|
||||
{
|
||||
public PauseCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "pause";
|
||||
this.help = "pauses the current song";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
if(handler.getPlayer().isPaused())
|
||||
{
|
||||
event.replyWarning("The player is already paused! Use `"+event.getClient().getPrefix()+"play` to unpause!");
|
||||
CommandLogContext.setError("already_paused");
|
||||
return;
|
||||
}
|
||||
handler.getPlayer().setPaused(true);
|
||||
event.replySuccess("Paused **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**. Type `"+event.getClient().getPrefix()+"play` to unpause!");
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("position_ms", handler.getPlayer().getPlayingTrack().getPosition());
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"PAUSE", null, null, null, null, meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.dj;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class PlaynextCmd extends DJCommand
|
||||
{
|
||||
private final String loadingEmoji;
|
||||
|
||||
public PlaynextCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.loadingEmoji = bot.getConfig().getLoading();
|
||||
this.name = "playnext";
|
||||
this.arguments = "<title|URL>";
|
||||
this.help = "plays a single song next";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = true;
|
||||
this.bePlaying = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
if(event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty())
|
||||
{
|
||||
event.replyWarning("Please include a song title or URL!");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "missing_query"));
|
||||
return;
|
||||
}
|
||||
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
|
||||
? event.getArgs().substring(1,event.getArgs().length()-1)
|
||||
: event.getArgs().isEmpty() ? event.getMessage().getAttachments().get(0).getUrl() : event.getArgs();
|
||||
event.reply(loadingEmoji+" Loading... `["+args+"]`", m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false)));
|
||||
}
|
||||
|
||||
private class ResultHandler implements AudioLoadResultHandler
|
||||
{
|
||||
private final Message m;
|
||||
private final CommandEvent event;
|
||||
private final boolean ytsearch;
|
||||
|
||||
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
|
||||
{
|
||||
this.m = m;
|
||||
this.event = event;
|
||||
this.ytsearch = ytsearch;
|
||||
}
|
||||
|
||||
private void loadSingle(AudioTrack track)
|
||||
{
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "track_too_long"));
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = RequestMetadata.fromResultHandler(track, event, "PLAYNEXT", null, event.getArgs());
|
||||
int pos = handler.addTrackToFront(new QueuedTrack(track, rm))+1;
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueAdd(event.getGuild(), event.getAuthor(), track, "PLAYNEXT", pos, event.getArgs(), null);
|
||||
String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
|
||||
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
|
||||
m.editMessage(addMsg).queue();
|
||||
logCommandEvent(event, "OK", new JSONObject().put("queue_position", pos));
|
||||
}
|
||||
|
||||
@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();
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "no_matches"));
|
||||
}
|
||||
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();
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", throwable.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void logCommandEvent(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.RepeatMode;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class RepeatCmd extends DJCommand
|
||||
{
|
||||
public RepeatCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "repeat";
|
||||
this.help = "re-adds music to the queue when finished";
|
||||
this.arguments = "[off|all|single]";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
String args = event.getArgs();
|
||||
RepeatMode value;
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
RepeatMode before = settings.getRepeatMode();
|
||||
if(args.isEmpty())
|
||||
{
|
||||
if(settings.getRepeatMode() == RepeatMode.OFF)
|
||||
value = RepeatMode.ALL;
|
||||
else
|
||||
value = RepeatMode.OFF;
|
||||
}
|
||||
else if(args.equalsIgnoreCase("false") || args.equalsIgnoreCase("off"))
|
||||
{
|
||||
value = RepeatMode.OFF;
|
||||
}
|
||||
else if(args.equalsIgnoreCase("true") || args.equalsIgnoreCase("on") || args.equalsIgnoreCase("all"))
|
||||
{
|
||||
value = RepeatMode.ALL;
|
||||
}
|
||||
else if(args.equalsIgnoreCase("one") || args.equalsIgnoreCase("single"))
|
||||
{
|
||||
value = RepeatMode.SINGLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
event.replyError("Valid options are `off`, `all` or `single` (or leave empty to toggle between `off` and `all`)");
|
||||
CommandLogContext.setError("invalid_repeat_mode");
|
||||
return;
|
||||
}
|
||||
settings.setRepeatMode(value);
|
||||
event.replySuccess("Repeat mode is now `"+value.getUserFriendlyName()+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before.name()).put("after", value.name()));
|
||||
}
|
||||
}
|
||||
+75
-63
@@ -1,63 +1,75 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.commands;
|
||||
|
||||
import me.jagrosh.jdautilities.commandclient.CommandEvent;
|
||||
import me.jagrosh.jmusicbot.Bot;
|
||||
import me.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import net.dv8tion.jda.core.entities.User;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SkiptoCmd extends MusicCommand {
|
||||
|
||||
public SkiptoCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "skipto";
|
||||
this.help = "skips to the current song";
|
||||
this.arguments = "<position>";
|
||||
this.aliases = new String[]{"jumpto"};
|
||||
this.bePlaying = true;
|
||||
this.category = bot.DJ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event) {
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
index = Integer.parseInt(event.getArgs());
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" `"+event.getArgs()+"` is not a valid integer!");
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
if(index<1 || index>handler.getQueue().size())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
|
||||
return;
|
||||
}
|
||||
handler.getQueue().skip(index-1);
|
||||
event.reply(event.getClient().getSuccess()+" Skipped to **"+handler.getQueue().get(0).getTrack().getInfo().title+"**");
|
||||
handler.getPlayer().stopTrack();
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.dj;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SkiptoCmd extends DJCommand
|
||||
{
|
||||
public SkiptoCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "skipto";
|
||||
this.help = "skips to the specified song";
|
||||
this.arguments = "<position>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
index = Integer.parseInt(event.getArgs());
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" `"+event.getArgs()+"` is not a valid integer!");
|
||||
CommandLogContext.setError("invalid_index");
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
if(index<1 || index>handler.getQueue().size())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
|
||||
CommandLogContext.setError("index_out_of_range");
|
||||
return;
|
||||
}
|
||||
int sizeBefore = handler.getQueue().size();
|
||||
handler.getQueue().skip(index-1);
|
||||
event.reply(event.getClient().getSuccess()+" Skipped to **"+handler.getQueue().get(0).getTrack().getInfo().title+"**");
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("position_to", index)
|
||||
.put("queue_size_before", sizeBefore)
|
||||
.put("queue_size_after", handler.getQueue().size());
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"SKIP_SKIPTO", null, index, null, null, meta.toString());
|
||||
handler.getPlayer().stopTrack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.dj;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class StopCmd extends DJCommand
|
||||
{
|
||||
public StopCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "stop";
|
||||
this.help = "stops the current song and clears the queue";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.bePlaying = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
int sizeBefore = handler.getQueue().size();
|
||||
if(handler.getPlayer().getPlayingTrack() != null && bot.getDataLogService() != null)
|
||||
{
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("queue_size_before", sizeBefore)
|
||||
.put("queue_size_after", 0);
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"STOP", null, null, null, null, meta.toString());
|
||||
CommandLogContext.setMeta(meta);
|
||||
}
|
||||
handler.stopAndClear();
|
||||
event.getGuild().getAudioManager().closeAudioConnection();
|
||||
event.reply(event.getClient().getSuccess()+" The player has stopped and the queue has been cleared.");
|
||||
}
|
||||
}
|
||||
+76
-64
@@ -1,64 +1,76 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.commands;
|
||||
|
||||
import me.jagrosh.jdautilities.commandclient.CommandEvent;
|
||||
import me.jagrosh.jmusicbot.Bot;
|
||||
import me.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import me.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class VolumeCmd extends MusicCommand {
|
||||
|
||||
public VolumeCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "volume";
|
||||
this.help = "sets or shows volume";
|
||||
this.arguments = "[0-150]";
|
||||
this.category = bot.DJ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event) {
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
int volume = handler==null ? 100 : handler.getPlayer().getVolume();
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(FormatUtil.volumeIcon(volume)+" Current volume is `"+volume+"`");
|
||||
}
|
||||
else
|
||||
{
|
||||
int nvolume;
|
||||
try{
|
||||
nvolume = Integer.parseInt(event.getArgs());
|
||||
}catch(NumberFormatException e){
|
||||
nvolume = -1;
|
||||
}
|
||||
if(nvolume<0 || nvolume>150)
|
||||
event.reply(event.getClient().getError()+" Volume must be a valid integer between 0 and 150!");
|
||||
else
|
||||
{
|
||||
bot.setUpHandler(event).getPlayer().setVolume(nvolume);
|
||||
event.reply(FormatUtil.volumeIcon(nvolume)+" Volume changed from `"+volume+"` to `"+nvolume+"`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.dj;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class VolumeCmd extends DJCommand
|
||||
{
|
||||
public VolumeCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "volume";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.help = "sets or shows volume";
|
||||
this.arguments = "[0-150]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
int volume = handler.getPlayer().getVolume();
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(FormatUtil.volumeIcon(volume)+" Current volume is `"+volume+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("current", volume));
|
||||
}
|
||||
else
|
||||
{
|
||||
int nvolume;
|
||||
try{
|
||||
nvolume = Integer.parseInt(event.getArgs());
|
||||
}catch(NumberFormatException e){
|
||||
nvolume = -1;
|
||||
}
|
||||
if(nvolume<0 || nvolume>150)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Volume must be a valid integer between 0 and 150!");
|
||||
CommandLogContext.setError("invalid_volume");
|
||||
}
|
||||
else
|
||||
{
|
||||
handler.getPlayer().setVolume(nvolume);
|
||||
settings.setVolume(nvolume);
|
||||
event.reply(FormatUtil.volumeIcon(nvolume)+" Volume changed from `"+volume+"` to `"+nvolume+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", volume).put("after", nvolume));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2026
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.general;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.examples.command.AboutCommand;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class LoggedAboutCmd extends AboutCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
|
||||
public LoggedAboutCmd(Bot bot, Color color, String description, String[] features, net.dv8tion.jda.api.Permission[] perms)
|
||||
{
|
||||
super(color, description, features, perms);
|
||||
this.bot = bot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
{
|
||||
super.execute(event);
|
||||
if(bot.getDataLogService() != null && event.getGuild() != null)
|
||||
{
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), "OK", new JSONObject().put("about", true).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2026
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.general;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.examples.command.PingCommand;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class LoggedPingCmd extends PingCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
|
||||
public LoggedPingCmd(Bot bot)
|
||||
{
|
||||
super();
|
||||
this.bot = bot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
{
|
||||
super.execute(event);
|
||||
if(bot.getDataLogService() != null && event.getGuild() != null)
|
||||
{
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), "OK", new JSONObject().put("ping", true).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.general;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.LoggedCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.settings.RepeatMode;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SettingsCmd extends LoggedCommand
|
||||
{
|
||||
private final static String EMOJI = "\uD83C\uDFA7"; // 🎧
|
||||
|
||||
public SettingsCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "settings";
|
||||
this.help = "shows the bots settings";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
Settings s = event.getClient().getSettingsFor(event.getGuild());
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("repeat_mode", s.getRepeatMode().name())
|
||||
.put("queue_type", s.getQueueType().name())
|
||||
.put("has_default_playlist", s.getDefaultPlaylist() != null)
|
||||
.put("has_custom_prefix", s.getPrefix() != null)
|
||||
.put("has_text_channel", s.getTextChannel(event.getGuild()) != null)
|
||||
.put("has_voice_channel", s.getVoiceChannel(event.getGuild()) != null)
|
||||
.put("has_dj_role", s.getRole(event.getGuild()) != null);
|
||||
CommandLogContext.setMeta(meta);
|
||||
MessageCreateBuilder builder = new MessageCreateBuilder()
|
||||
.addContent(EMOJI + " **")
|
||||
.addContent(FormatUtil.filter(event.getSelfUser().getName()))
|
||||
.addContent("** settings:");
|
||||
TextChannel tchan = s.getTextChannel(event.getGuild());
|
||||
VoiceChannel vchan = s.getVoiceChannel(event.getGuild());
|
||||
Role role = s.getRole(event.getGuild());
|
||||
EmbedBuilder ebuilder = new EmbedBuilder()
|
||||
.setColor(event.getSelfMember().getColor())
|
||||
.setDescription("Text Channel: " + (tchan == null ? "Any" : "**#" + tchan.getName() + "**")
|
||||
+ "\nVoice Channel: " + (vchan == null ? "Any" : vchan.getAsMention())
|
||||
+ "\nDJ Role: " + (role == null ? "None" : "**" + role.getName() + "**")
|
||||
+ "\nCustom Prefix: " + (s.getPrefix() == null ? "None" : "`" + s.getPrefix() + "`")
|
||||
+ "\nRepeat Mode: " + (s.getRepeatMode() == RepeatMode.OFF
|
||||
? s.getRepeatMode().getUserFriendlyName()
|
||||
: "**"+s.getRepeatMode().getUserFriendlyName()+"**")
|
||||
+ "\nQueue Type: " + (s.getQueueType() == QueueType.FAIR
|
||||
? s.getQueueType().getUserFriendlyName()
|
||||
: "**"+s.getQueueType().getUserFriendlyName()+"**")
|
||||
+ "\nDefault Playlist: " + (s.getDefaultPlaylist() == null ? "None" : "**" + s.getDefaultPlaylist() + "**")
|
||||
)
|
||||
.setFooter(event.getJDA().getGuilds().size() + " servers | "
|
||||
+ event.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inAudioChannel()).count()
|
||||
+ " audio connections", null);
|
||||
event.getMessage().getChannel().sendMessage(builder.setEmbeds(ebuilder.build()).build()).queue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jlyrics.LyricsClient;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class LyricsCmd extends MusicCommand
|
||||
{
|
||||
private final LyricsClient client = new LyricsClient();
|
||||
|
||||
public LyricsCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "lyrics";
|
||||
this.arguments = "[song name]";
|
||||
this.help = "shows the lyrics of a song";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
String title;
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
AudioHandler sendingHandler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
if (sendingHandler.isMusicPlaying(event.getJDA()))
|
||||
title = sendingHandler.getPlayer().getPlayingTrack().getInfo().title;
|
||||
else
|
||||
{
|
||||
event.replyError("There must be music playing to use that!");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "no_music_playing"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
title = event.getArgs();
|
||||
event.getChannel().sendTyping().queue();
|
||||
client.getLyrics(title).thenAccept(lyrics ->
|
||||
{
|
||||
if(lyrics == null)
|
||||
{
|
||||
event.replyError("Lyrics for `" + title + "` could not be found!" + (event.getArgs().isEmpty() ? " Try entering the song name manually (`lyrics [song name]`)" : ""));
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "lyrics_not_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());
|
||||
logCommandEvent(event, "OK", new JSONObject().put("title", lyrics.getTitle()));
|
||||
});
|
||||
}
|
||||
|
||||
private void logCommandEvent(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class NowplayingCmd extends MusicCommand
|
||||
{
|
||||
public NowplayingCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "nowplaying";
|
||||
this.help = "shows the song that is currently playing";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
MessageCreateData m = handler.getNowPlaying(event.getJDA());
|
||||
if(m==null)
|
||||
{
|
||||
event.getMessage().getChannel().sendMessage(handler.getNoMusicPlaying(event.getJDA())).queue();
|
||||
bot.getNowplayingHandler().clearLastNPMessage(event.getGuild());
|
||||
CommandLogContext.setMeta(new JSONObject().put("playing", false));
|
||||
}
|
||||
else
|
||||
{
|
||||
event.getMessage().getChannel().sendMessage(m).queue(msg -> bot.getNowplayingHandler().setLastNPMessage(msg));
|
||||
CommandLogContext.setMeta(new JSONObject()
|
||||
.put("playing", true)
|
||||
.put("title", handler.getPlayer().getPlayingTrack() == null ? JSONObject.NULL : handler.getPlayer().getPlayingTrack().getInfo().title));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.menu.ButtonMenu;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.exceptions.PermissionException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class PlayCmd extends MusicCommand
|
||||
{
|
||||
private final static String LOAD = "\uD83D\uDCE5"; // 📥
|
||||
private final static String CANCEL = "\uD83D\uDEAB"; // 🚫
|
||||
|
||||
private final String loadingEmoji;
|
||||
|
||||
public PlayCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.loadingEmoji = bot.getConfig().getLoading();
|
||||
this.name = "play";
|
||||
this.arguments = "<title|URL|subcommand>";
|
||||
this.help = "plays the provided song";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = true;
|
||||
this.bePlaying = false;
|
||||
this.children = new Command[]{new PlaylistCmd(bot)};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
if(event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty())
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
if(handler.getPlayer().getPlayingTrack()!=null && handler.getPlayer().isPaused())
|
||||
{
|
||||
if(DJCommand.checkDJPermission(event))
|
||||
{
|
||||
handler.getPlayer().setPaused(false);
|
||||
event.replySuccess("Resumed **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**.");
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("position_ms", handler.getPlayer().getPlayingTrack().getPosition());
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"RESUME", null, null, null, null, meta.toString());
|
||||
logCommandEvent(event, getName(), event.getArgs(), "OK", meta);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
event.replyError("Only DJs can unpause the player!");
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", "not_dj"));
|
||||
}
|
||||
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());
|
||||
logCommandEvent(event, getName(), event.getArgs(), "OK", new JSONObject().put("help", true));
|
||||
return;
|
||||
}
|
||||
String args = event.getArgs().startsWith("<") && event.getArgs().endsWith(">")
|
||||
? event.getArgs().substring(1,event.getArgs().length()-1)
|
||||
: event.getArgs().isEmpty() ? event.getMessage().getAttachments().get(0).getUrl() : event.getArgs();
|
||||
event.reply(loadingEmoji+" Loading... `["+args+"]`", m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), args, new ResultHandler(m,event,false)));
|
||||
}
|
||||
|
||||
private class ResultHandler implements AudioLoadResultHandler
|
||||
{
|
||||
private final Message m;
|
||||
private final CommandEvent event;
|
||||
private final boolean ytsearch;
|
||||
|
||||
private ResultHandler(Message m, CommandEvent event, boolean ytsearch)
|
||||
{
|
||||
this.m = m;
|
||||
this.event = event;
|
||||
this.ytsearch = ytsearch;
|
||||
}
|
||||
|
||||
private void loadSingle(AudioTrack track, AudioPlaylist playlist)
|
||||
{
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", "track_too_long"));
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = RequestMetadata.fromResultHandler(track, event, "PLAY", null, event.getArgs());
|
||||
int pos = handler.addTrack(new QueuedTrack(track, rm))+1;
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueAdd(event.getGuild(), event.getAuthor(), track, "PLAY", pos, event.getArgs(), null);
|
||||
String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
|
||||
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
|
||||
if(playlist==null || !event.getSelfMember().hasPermission(event.getTextChannel(), Permission.MESSAGE_ADD_REACTION))
|
||||
m.editMessage(addMsg).queue();
|
||||
else
|
||||
{
|
||||
new ButtonMenu.Builder()
|
||||
.setText(addMsg+"\n"+event.getClient().getWarning()+" This track has a playlist of **"+playlist.getTracks().size()+"** tracks attached. Select "+LOAD+" to load playlist.")
|
||||
.setChoices(LOAD, CANCEL)
|
||||
.setEventWaiter(bot.getWaiter())
|
||||
.setTimeout(30, TimeUnit.SECONDS)
|
||||
.setAction(re ->
|
||||
{
|
||||
if(re.getName().equals(LOAD))
|
||||
m.editMessage(addMsg+"\n"+event.getClient().getSuccess()+" Loaded **"+loadPlaylist(playlist, track)+"** additional tracks!").queue();
|
||||
else
|
||||
m.editMessage(addMsg).queue();
|
||||
}).setFinalAction(m ->
|
||||
{
|
||||
try{ m.clearReactions().queue(); }catch(PermissionException ignore) {}
|
||||
}).build().display(m);
|
||||
}
|
||||
logCommandEvent(event, getName(), event.getArgs(), "OK", new JSONObject().put("queue_position", pos));
|
||||
}
|
||||
|
||||
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();
|
||||
RequestMetadata rm = RequestMetadata.fromResultHandler(track, event, "PLAYLIST", playlist == null ? null : playlist.getName(), event.getArgs());
|
||||
int pos = handler.addTrack(new QueuedTrack(track, rm))+1;
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueAdd(event.getGuild(), event.getAuthor(), track, "PLAYLIST", pos, event.getArgs(), playlist == null ? null : playlist.getName());
|
||||
count[0]++;
|
||||
}
|
||||
});
|
||||
return count[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trackLoaded(AudioTrack track)
|
||||
{
|
||||
loadSingle(track, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playlistLoaded(AudioPlaylist playlist)
|
||||
{
|
||||
if(playlist.getTracks().size()==1 || playlist.isSearchResult())
|
||||
{
|
||||
AudioTrack single = playlist.getSelectedTrack()==null ? playlist.getTracks().get(0) : playlist.getSelectedTrack();
|
||||
loadSingle(single, null);
|
||||
}
|
||||
else if (playlist.getSelectedTrack()!=null)
|
||||
{
|
||||
AudioTrack single = playlist.getSelectedTrack();
|
||||
loadSingle(single, playlist);
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = loadPlaylist(playlist, null);
|
||||
if(playlist.getTracks().size() == 0)
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" The playlist "+(playlist.getName()==null ? "" : "(**"+playlist.getName()
|
||||
+"**) ")+" could not be loaded or contained 0 entries")).queue();
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", "playlist_empty"));
|
||||
}
|
||||
else if(count==0)
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" All entries in this playlist "+(playlist.getName()==null ? "" : "(**"+playlist.getName()
|
||||
+"**) ")+"were longer than the allowed maximum (`"+bot.getConfig().getMaxTime()+"`)")).queue();
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", "playlist_entries_too_long"));
|
||||
}
|
||||
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();
|
||||
logCommandEvent(event, getName(), event.getArgs(), "OK", new JSONObject().put("playlist_name", playlist.getName()).put("added_count", count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noMatches()
|
||||
{
|
||||
if(ytsearch)
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logSearchEventWithMeta(event.getGuild(), event.getAuthor(), event.getArgs(), null, null,
|
||||
new JSONObject().put("error_reason", "no_matches").toString());
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", "no_matches"));
|
||||
}
|
||||
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();
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logSearchEventWithMeta(event.getGuild(), event.getAuthor(), event.getArgs(), null, null,
|
||||
new JSONObject().put("error_reason", throwable.getMessage()).toString());
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", throwable.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Please include a playlist name.");
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", "missing_playlist"));
|
||||
return;
|
||||
}
|
||||
Playlist playlist = bot.getPlaylistLoader().getPlaylist(event.getArgs());
|
||||
if(playlist==null)
|
||||
{
|
||||
event.replyError("I could not find `"+event.getArgs()+".txt` in the Playlists folder.");
|
||||
logCommandEvent(event, getName(), event.getArgs(), "ERROR", new JSONObject().put("error_reason", "playlist_not_found"));
|
||||
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) ->
|
||||
{
|
||||
RequestMetadata rm = RequestMetadata.fromResultHandler(at, event, "PLAYLIST", event.getArgs(), event.getArgs());
|
||||
int pos = handler.addTrack(new QueuedTrack(at, rm))+1;
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueAdd(event.getGuild(), event.getAuthor(), at, "PLAYLIST", pos, event.getArgs(), event.getArgs());
|
||||
}, () -> {
|
||||
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();
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
playlist.getErrors().forEach(err ->
|
||||
bot.getDataLogService().logPlaylistError(event.getGuild(), event.getAuthor(), event.getArgs(), err.getItem(), err.getReason()));
|
||||
}
|
||||
logCommandEvent(event, getName(), event.getArgs(), "OK", new JSONObject()
|
||||
.put("playlist_name", event.getArgs())
|
||||
.put("loaded_count", playlist.getTracks().size())
|
||||
.put("error_count", playlist.getErrors().size()));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void logCommandEvent(CommandEvent event, String commandName, String args, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), commandName,
|
||||
args, result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import java.util.List;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class PlaylistsCmd extends MusicCommand
|
||||
{
|
||||
public PlaylistsCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "playlists";
|
||||
this.help = "shows the available playlists";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = true;
|
||||
this.beListening = false;
|
||||
this.beListening = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(!bot.getPlaylistLoader().folderExists())
|
||||
bot.getPlaylistLoader().createFolder();
|
||||
if(!bot.getPlaylistLoader().folderExists())
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+" Playlists folder does not exist and could not be created!");
|
||||
CommandLogContext.setError("playlist_folder_missing");
|
||||
return;
|
||||
}
|
||||
List<String> list = bot.getPlaylistLoader().getPlaylistNames();
|
||||
if(list==null)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Failed to load available playlists!");
|
||||
CommandLogContext.setError("playlist_list_failed");
|
||||
}
|
||||
else if(list.isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+" There are no playlists in the Playlists folder!");
|
||||
CommandLogContext.setMeta(new JSONObject().put("count", 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(event.getClient().getSuccess()+" Available playlists:\n");
|
||||
list.forEach(str -> builder.append("`").append(str).append("` "));
|
||||
builder.append("\nType `").append(event.getClient().getTextualPrefix()).append("play playlist <name>` to play a playlist");
|
||||
event.reply(builder.toString());
|
||||
CommandLogContext.setMeta(new JSONObject().put("count", list.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.menu.Paginator;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.settings.RepeatMode;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.exceptions.PermissionException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class QueueCmd extends MusicCommand
|
||||
{
|
||||
private final Paginator.Builder builder;
|
||||
|
||||
public QueueCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "queue";
|
||||
this.help = "shows the current queue";
|
||||
this.arguments = "[pagenum]";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.bePlaying = true;
|
||||
this.botPermissions = new Permission[]{Permission.MESSAGE_ADD_REACTION,Permission.MESSAGE_EMBED_LINKS};
|
||||
builder = new Paginator.Builder()
|
||||
.setColumns(1)
|
||||
.setFinalAction(m -> {try{m.clearReactions().queue();}catch(PermissionException ignore){}})
|
||||
.setItemsPerPage(10)
|
||||
.waitOnSinglePage(false)
|
||||
.useNumberedItems(true)
|
||||
.showPageNumbers(true)
|
||||
.wrapPageEnds(true)
|
||||
.setEventWaiter(bot.getWaiter())
|
||||
.setTimeout(1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
int pagenum = 1;
|
||||
try
|
||||
{
|
||||
pagenum = Integer.parseInt(event.getArgs());
|
||||
}
|
||||
catch(NumberFormatException ignore){}
|
||||
AudioHandler ah = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
List<QueuedTrack> list = ah.getQueue().getList();
|
||||
if(list.isEmpty())
|
||||
{
|
||||
MessageCreateData nowp = ah.getNowPlaying(event.getJDA());
|
||||
MessageCreateData nonowp = ah.getNoMusicPlaying(event.getJDA());
|
||||
MessageCreateData built = new MessageCreateBuilder()
|
||||
.setContent(event.getClient().getWarning() + " There is no music in the queue!")
|
||||
.setEmbeds((nowp==null ? nonowp : nowp).getEmbeds().get(0)).build();
|
||||
event.getMessage().getChannel().sendMessage(built).queue(m ->
|
||||
{
|
||||
if(nowp!=null)
|
||||
bot.getNowplayingHandler().setLastNPMessage(m);
|
||||
});
|
||||
CommandLogContext.setMeta(new JSONObject().put("queue_size", 0));
|
||||
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;
|
||||
CommandLogContext.setMeta(new JSONObject()
|
||||
.put("queue_size", list.size())
|
||||
.put("total_ms", total)
|
||||
.put("repeat_mode", settings.getRepeatMode().name())
|
||||
.put("queue_type", settings.getQueueType().name()));
|
||||
builder.setText((i1,i2) -> getQueueTitle(ah, event.getClient().getSuccess(), songs.length, fintotal, settings.getRepeatMode(), settings.getQueueType()))
|
||||
.setItems(songs)
|
||||
.setUsers(event.getAuthor())
|
||||
.setColor(event.getSelfMember().getColor())
|
||||
;
|
||||
builder.build().paginate(event.getChannel(), pagenum);
|
||||
}
|
||||
|
||||
private String getQueueTitle(AudioHandler ah, String success, int songslength, long total, RepeatMode repeatmode, QueueType queueType)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(ah.getPlayer().getPlayingTrack()!=null)
|
||||
{
|
||||
sb.append(ah.getStatusEmoji()).append(" **")
|
||||
.append(ah.getPlayer().getPlayingTrack().getInfo().title).append("**\n");
|
||||
}
|
||||
return FormatUtil.filter(sb.append(success).append(" Current Queue | ").append(songslength)
|
||||
.append(" entries | `").append(TimeUtil.formatTime(total)).append("` ")
|
||||
.append("| ").append(queueType.getEmoji()).append(" `").append(queueType.getUserFriendlyName()).append('`')
|
||||
.append(repeatmode.getEmoji() != null ? " | "+repeatmode.getEmoji() : "").toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class RemoveCmd extends MusicCommand
|
||||
{
|
||||
public RemoveCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "remove";
|
||||
this.help = "removes a song from the queue";
|
||||
this.arguments = "<position|ALL>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = true;
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
if(handler.getQueue().isEmpty())
|
||||
{
|
||||
event.replyError("There is nothing in the queue!");
|
||||
CommandLogContext.setError("empty_queue");
|
||||
return;
|
||||
}
|
||||
if(event.getArgs().equalsIgnoreCase("all"))
|
||||
{
|
||||
int sizeBefore = handler.getQueue().size();
|
||||
int count = handler.getQueue().removeAll(event.getAuthor().getIdLong());
|
||||
if(count==0)
|
||||
{
|
||||
event.replyWarning("You don't have any songs in the queue!");
|
||||
CommandLogContext.setError("no_entries_for_user");
|
||||
}
|
||||
else
|
||||
event.replySuccess("Successfully removed your "+count+" entries.");
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("removed_count", count)
|
||||
.put("queue_size_before", sizeBefore)
|
||||
.put("queue_size_after", handler.getQueue().size());
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), null,
|
||||
"REMOVE_ALL", "SELF", null, null, null, meta.toString());
|
||||
return;
|
||||
}
|
||||
int pos;
|
||||
try {
|
||||
pos = Integer.parseInt(event.getArgs());
|
||||
} catch(NumberFormatException e) {
|
||||
pos = 0;
|
||||
}
|
||||
if(pos<1 || pos>handler.getQueue().size())
|
||||
{
|
||||
event.replyError("Position must be a valid integer between 1 and "+handler.getQueue().size()+"!");
|
||||
CommandLogContext.setError("invalid_position");
|
||||
return;
|
||||
}
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
boolean isDJ = event.getMember().hasPermission(Permission.MANAGE_SERVER);
|
||||
if(!isDJ)
|
||||
isDJ = event.getMember().getRoles().contains(settings.getRole(event.getGuild()));
|
||||
QueuedTrack qt = handler.getQueue().get(pos-1);
|
||||
if(qt.getIdentifier()==event.getAuthor().getIdLong())
|
||||
{
|
||||
handler.getQueue().remove(pos-1);
|
||||
event.replySuccess("Removed **"+qt.getTrack().getInfo().title+"** from the queue");
|
||||
JSONObject meta = new JSONObject().put("position", pos);
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), qt.getTrack(),
|
||||
"REMOVE", "SELF", pos, null, null, meta.toString());
|
||||
}
|
||||
else if(isDJ)
|
||||
{
|
||||
handler.getQueue().remove(pos-1);
|
||||
User u;
|
||||
try {
|
||||
u = event.getJDA().getUserById(qt.getIdentifier());
|
||||
} catch(Exception e) {
|
||||
u = null;
|
||||
}
|
||||
event.replySuccess("Removed **"+qt.getTrack().getInfo().title
|
||||
+"** from the queue (requested by "+(u==null ? "someone" : "**"+u.getName()+"**")+")");
|
||||
JSONObject meta = new JSONObject().put("position", pos).put("removed_by_dj", true);
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), qt.getTrack(),
|
||||
"REMOVE", "DJ", pos, null, null, meta.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
event.replyError("You cannot remove **"+qt.getTrack().getInfo().title+"** because you didn't add it!");
|
||||
CommandLogContext.setError("not_owner");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SCSearchCmd extends SearchCmd
|
||||
{
|
||||
public SCSearchCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.searchPrefix = "scsearch:";
|
||||
this.name = "scsearch";
|
||||
this.help = "searches Soundcloud for a provided query";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.menu.OrderedMenu;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SearchCmd extends MusicCommand
|
||||
{
|
||||
protected String searchPrefix = "ytsearch:";
|
||||
private final OrderedMenu.Builder builder;
|
||||
private final String searchingEmoji;
|
||||
|
||||
public SearchCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.searchingEmoji = bot.getConfig().getSearching();
|
||||
this.name = "search";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.arguments = "<query>";
|
||||
this.help = "searches Youtube for a provided query";
|
||||
this.beListening = true;
|
||||
this.bePlaying = false;
|
||||
this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
|
||||
builder = new OrderedMenu.Builder()
|
||||
.allowTextInput(true)
|
||||
.useNumbers()
|
||||
.useCancelButton(true)
|
||||
.setEventWaiter(bot.getWaiter())
|
||||
.setTimeout(1, TimeUnit.MINUTES);
|
||||
}
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.replyError("Please include a query.");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "missing_query"));
|
||||
return;
|
||||
}
|
||||
event.reply(searchingEmoji+" Searching... `["+event.getArgs()+"]`",
|
||||
m -> bot.getPlayerManager().loadItemOrdered(event.getGuild(), searchPrefix + event.getArgs(), new ResultHandler(m,event)));
|
||||
}
|
||||
|
||||
private class ResultHandler implements AudioLoadResultHandler
|
||||
{
|
||||
private final Message m;
|
||||
private final CommandEvent event;
|
||||
|
||||
private ResultHandler(Message m, CommandEvent event)
|
||||
{
|
||||
this.m = m;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trackLoaded(AudioTrack track)
|
||||
{
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue();
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "track_too_long"));
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = RequestMetadata.fromResultHandler(track, event, "SEARCH", null, event.getArgs());
|
||||
int pos = handler.addTrack(new QueuedTrack(track, rm))+1;
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
bot.getDataLogService().logSearchSelection(event.getGuild(), event.getAuthor(), event.getArgs(), 1, track);
|
||||
bot.getDataLogService().logQueueAdd(event.getGuild(), event.getAuthor(), track, "SEARCH", pos, event.getArgs(), null);
|
||||
}
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
|
||||
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
|
||||
: " to the queue at position "+pos))).queue();
|
||||
logCommandEvent(event, "OK", new JSONObject().put("queue_position", pos).put("selected_index", 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playlistLoaded(AudioPlaylist playlist)
|
||||
{
|
||||
builder.setColor(event.getSelfMember().getColor())
|
||||
.setText(FormatUtil.filter(event.getClient().getSuccess()+" Search results for `"+event.getArgs()+"`:"))
|
||||
.setChoices(new String[0])
|
||||
.setSelection((msg,i) ->
|
||||
{
|
||||
AudioTrack track = playlist.getTracks().get(i-1);
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
event.replyWarning("This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "track_too_long"));
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = RequestMetadata.fromResultHandler(track, event, "SEARCH", null, event.getArgs());
|
||||
int pos = handler.addTrack(new QueuedTrack(track, rm))+1;
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
bot.getDataLogService().logSearchSelection(event.getGuild(), event.getAuthor(), event.getArgs(), i, track);
|
||||
bot.getDataLogService().logQueueAdd(event.getGuild(), event.getAuthor(), track, "SEARCH", pos, event.getArgs(), null);
|
||||
}
|
||||
event.replySuccess("Added **" + FormatUtil.filter(track.getInfo().title)
|
||||
+ "** (`" + TimeUtil.formatTime(track.getDuration()) + "`) " + (pos==0 ? "to begin playing"
|
||||
: " to the queue at position "+pos));
|
||||
})
|
||||
.setCancel((msg) -> {})
|
||||
.setUsers(event.getAuthor())
|
||||
;
|
||||
for(int i=0; i<4 && i<playlist.getTracks().size(); i++)
|
||||
{
|
||||
AudioTrack track = playlist.getTracks().get(i);
|
||||
builder.addChoices("`["+ TimeUtil.formatTime(track.getDuration())+"]` [**"+track.getInfo().title+"**]("+track.getInfo().uri+")");
|
||||
}
|
||||
builder.build().display(m);
|
||||
logCommandEvent(event, "OK", new JSONObject().put("result_count", playlist.getTracks().size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noMatches()
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" No results found for `"+event.getArgs()+"`.")).queue();
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logSearchEventWithMeta(event.getGuild(), event.getAuthor(), event.getArgs(), null, null,
|
||||
new JSONObject().put("error_reason", "no_matches").toString());
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "no_matches"));
|
||||
}
|
||||
|
||||
@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();
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logSearchEventWithMeta(event.getGuild(), event.getAuthor(), event.getArgs(), null, null,
|
||||
new JSONObject().put("error_reason", throwable.getMessage()).toString());
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", throwable.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void logCommandEvent(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
/**
|
||||
* @author Whew., Inc.
|
||||
*/
|
||||
public class SeekCmd extends MusicCommand
|
||||
{
|
||||
private final static Logger LOG = LoggerFactory.getLogger("Seeking");
|
||||
|
||||
public SeekCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "seek";
|
||||
this.help = "seeks the current song";
|
||||
this.arguments = "[+ | -] <HH:MM:SS | MM:SS | SS>|<0h0m0s | 0m0s | 0s>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = true;
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
AudioTrack playingTrack = handler.getPlayer().getPlayingTrack();
|
||||
if (!playingTrack.isSeekable())
|
||||
{
|
||||
event.replyError("This track is not seekable.");
|
||||
CommandLogContext.setError("not_seekable");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!DJCommand.checkDJPermission(event) && playingTrack.getUserData(RequestMetadata.class).getOwner() != event.getAuthor().getIdLong())
|
||||
{
|
||||
event.replyError("You cannot seek **" + playingTrack.getInfo().title + "** because you didn't add it!");
|
||||
CommandLogContext.setError("not_owner");
|
||||
return;
|
||||
}
|
||||
|
||||
String args = event.getArgs();
|
||||
TimeUtil.SeekTime seekTime = TimeUtil.parseTime(args);
|
||||
if (seekTime == null)
|
||||
{
|
||||
event.replyError("Invalid seek! Expected format: " + arguments + "\nExamples: `1:02:23` `+1:10` `-90`, `1h10m`, `+90s`");
|
||||
CommandLogContext.setError("invalid_seek");
|
||||
return;
|
||||
}
|
||||
|
||||
long currentPosition = playingTrack.getPosition();
|
||||
long trackDuration = playingTrack.getDuration();
|
||||
|
||||
long seekMilliseconds = seekTime.relative ? currentPosition + seekTime.milliseconds : seekTime.milliseconds;
|
||||
if (seekMilliseconds > trackDuration)
|
||||
{
|
||||
event.replyError("Cannot seek to `" + TimeUtil.formatTime(seekMilliseconds) + "` because the current track is `" + TimeUtil.formatTime(trackDuration) + "` long!");
|
||||
CommandLogContext.setError("seek_out_of_bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
playingTrack.setPosition(seekMilliseconds);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
event.replyError("An error occurred while trying to seek: " + e.getMessage());
|
||||
LOG.warn("Failed to seek track " + playingTrack.getIdentifier(), e);
|
||||
CommandLogContext.setError("seek_failed");
|
||||
return;
|
||||
}
|
||||
event.replySuccess("Successfully seeked to `" + TimeUtil.formatTime(playingTrack.getPosition()) + "/" + TimeUtil.formatTime(playingTrack.getDuration()) + "`!");
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("seek_from_ms", currentPosition)
|
||||
.put("seek_to_ms", playingTrack.getPosition());
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), playingTrack,
|
||||
"SEEK", null, null, null, null, meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class ShuffleCmd extends MusicCommand
|
||||
{
|
||||
public ShuffleCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "shuffle";
|
||||
this.help = "shuffles songs you have added";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = true;
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
int sizeBefore = handler.getQueue().size();
|
||||
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!");
|
||||
CommandLogContext.setError("no_entries_to_shuffle");
|
||||
break;
|
||||
case 1:
|
||||
event.replyWarning("You only have one song in the queue!");
|
||||
CommandLogContext.setMeta(new JSONObject().put("shuffled_count", 1));
|
||||
break;
|
||||
default:
|
||||
event.replySuccess("You successfully shuffled your "+s+" entries.");
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("shuffled_count", s)
|
||||
.put("queue_size_before", sizeBefore)
|
||||
.put("queue_size_after", handler.getQueue().size());
|
||||
CommandLogContext.setMeta(meta);
|
||||
if(bot.getDataLogService() != null)
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), null,
|
||||
"SHUFFLE", null, null, null, null, meta.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SkipCmd extends MusicCommand
|
||||
{
|
||||
public SkipCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "skip";
|
||||
this.help = "votes to skip the current song";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = true;
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = handler.getRequestMetadata();
|
||||
double skipRatio = bot.getSettingsManager().getSettings(event.getGuild()).getSkipRatio();
|
||||
if(skipRatio == -1) {
|
||||
skipRatio = bot.getConfig().getSkipRatio();
|
||||
}
|
||||
if(event.getAuthor().getIdLong() == rm.getOwner() || skipRatio == 0)
|
||||
{
|
||||
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**");
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
JSONObject meta = new JSONObject().put("reason", "REQUESTER");
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"SKIP_REQUESTER", null, null, null, null, meta.toString());
|
||||
CommandLogContext.setMeta(meta);
|
||||
}
|
||||
handler.getPlayer().stopTrack();
|
||||
}
|
||||
else
|
||||
{
|
||||
int listeners = (int)event.getSelfMember().getVoiceState().getChannel().getMembers().stream()
|
||||
.filter(m -> !m.getUser().isBot() && !m.getVoiceState().isDeafened()).count();
|
||||
String msg;
|
||||
if(handler.getVotes().contains(event.getAuthor().getId()))
|
||||
msg = event.getClient().getWarning()+" You already voted to skip this song `[";
|
||||
else
|
||||
{
|
||||
msg = event.getClient().getSuccess()+" You voted to skip the song `[";
|
||||
handler.getVotes().add(event.getAuthor().getId());
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
JSONObject meta = new JSONObject().put("vote", "added");
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"SKIP_VOTE", null, null, null, null, meta.toString());
|
||||
}
|
||||
}
|
||||
int skippers = (int)event.getSelfMember().getVoiceState().getChannel().getMembers().stream()
|
||||
.filter(m -> handler.getVotes().contains(m.getUser().getId())).count();
|
||||
int required = (int)Math.ceil(listeners * skipRatio);
|
||||
msg += skippers + " votes, " + required + "/" + listeners + " needed]`";
|
||||
CommandLogContext.setMeta(new JSONObject()
|
||||
.put("votes", skippers)
|
||||
.put("required", required)
|
||||
.put("listeners", listeners));
|
||||
if(skippers>=required)
|
||||
{
|
||||
msg += "\n" + event.getClient().getSuccess() + " Skipped **" + handler.getPlayer().getPlayingTrack().getInfo().title
|
||||
+ "** " + (rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + FormatUtil.formatUsername(rm.user) + "**)");
|
||||
if(bot.getDataLogService() != null)
|
||||
{
|
||||
JSONObject meta = new JSONObject()
|
||||
.put("reason", "VOTE")
|
||||
.put("votes", skippers)
|
||||
.put("required", required)
|
||||
.put("listeners", listeners);
|
||||
bot.getDataLogService().logQueueEventWithMeta(event.getGuild(), event.getAuthor(), handler.getPlayer().getPlayingTrack(),
|
||||
"SKIP_VOTE_ACTION", null, null, null, null, meta.toString());
|
||||
CommandLogContext.setMeta(meta);
|
||||
}
|
||||
handler.getPlayer().stopTrack();
|
||||
}
|
||||
event.reply(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class AutoplaylistCmd extends OwnerCommand
|
||||
{
|
||||
public AutoplaylistCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.guildOnly = true;
|
||||
this.name = "autoplaylist";
|
||||
this.arguments = "<name|NONE>";
|
||||
this.help = "sets the default playlist for the server";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Please include a playlist name or NONE");
|
||||
CommandLogContext.setError("missing_playlist");
|
||||
return;
|
||||
}
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
String before = settings.getDefaultPlaylist();
|
||||
if(event.getArgs().equalsIgnoreCase("none"))
|
||||
{
|
||||
settings.setDefaultPlaylist(null);
|
||||
event.reply(event.getClient().getSuccess()+" Cleared the default playlist for **"+event.getGuild().getName()+"**");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", JSONObject.NULL));
|
||||
return;
|
||||
}
|
||||
String pname = event.getArgs().replaceAll("\\s+", "_");
|
||||
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Could not find `"+pname+".txt`!");
|
||||
CommandLogContext.setError("playlist_not_found");
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.setDefaultPlaylist(pname);
|
||||
event.reply(event.getClient().getSuccess()+" The default playlist for **"+event.getGuild().getName()+"** is now `"+pname+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before).put("after", pname));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.commons.JDAUtilitiesInfo;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.PlayerLibrary;
|
||||
import net.dv8tion.jda.api.JDAInfo;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.utils.FileUpload;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class DebugCmd extends OwnerCommand
|
||||
{
|
||||
private final static String[] PROPERTIES = {"java.version", "java.vm.name", "java.vm.specification.version",
|
||||
"java.runtime.name", "java.runtime.version", "java.specification.version", "os.arch", "os.name"};
|
||||
|
||||
public DebugCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "debug";
|
||||
this.help = "shows debug info";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setMeta(new JSONObject().put("channel_type", event.getChannelType().name()));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("```\nSystem Properties:");
|
||||
for(String key: PROPERTIES)
|
||||
sb.append("\n ").append(key).append(" = ").append(System.getProperty(key));
|
||||
sb.append("\n\nJMusicBot Information:")
|
||||
.append("\n Version = ").append(OtherUtil.getCurrentVersion())
|
||||
.append("\n Owner = ").append(bot.getConfig().getOwnerId())
|
||||
.append("\n Prefix = ").append(bot.getConfig().getPrefix())
|
||||
.append("\n AltPrefix = ").append(bot.getConfig().getAltPrefix())
|
||||
.append("\n MaxSeconds = ").append(bot.getConfig().getMaxSeconds())
|
||||
.append("\n NPImages = ").append(bot.getConfig().useNPImages())
|
||||
.append("\n SongInStatus = ").append(bot.getConfig().getSongInStatus())
|
||||
.append("\n StayInChannel = ").append(bot.getConfig().getStay())
|
||||
.append("\n UseEval = ").append(bot.getConfig().useEval())
|
||||
.append("\n UpdateAlerts = ").append(bot.getConfig().useUpdateAlerts());
|
||||
sb.append("\n\nDependency Information:")
|
||||
.append("\n JDA Version = ").append(JDAInfo.VERSION)
|
||||
.append("\n JDA-Utilities Version = ").append(JDAUtilitiesInfo.VERSION)
|
||||
.append("\n Lavaplayer Version = ").append(PlayerLibrary.VERSION);
|
||||
long total = Runtime.getRuntime().totalMemory() / 1024 / 1024;
|
||||
long used = total - (Runtime.getRuntime().freeMemory() / 1024 / 1024);
|
||||
sb.append("\n\nRuntime Information:")
|
||||
.append("\n Total Memory = ").append(total)
|
||||
.append("\n Used Memory = ").append(used);
|
||||
sb.append("\n\nDiscord Information:")
|
||||
.append("\n ID = ").append(event.getJDA().getSelfUser().getId())
|
||||
.append("\n Guilds = ").append(event.getJDA().getGuildCache().size())
|
||||
.append("\n Users = ").append(event.getJDA().getUserCache().size());
|
||||
sb.append("\n```");
|
||||
|
||||
if(event.isFromType(ChannelType.PRIVATE)
|
||||
|| event.getSelfMember().hasPermission(event.getGuildChannel(), Permission.MESSAGE_ATTACH_FILES))
|
||||
event.getChannel().sendFiles(FileUpload.fromData(sb.toString().getBytes(), "debug_information.txt")).queue();
|
||||
else
|
||||
event.reply("Debug Information: " + sb.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (jagrosh)
|
||||
*/
|
||||
public class EvalCmd extends OwnerCommand
|
||||
{
|
||||
private final String engine;
|
||||
|
||||
public EvalCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "eval";
|
||||
this.help = "evaluates nashorn code";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.engine = bot.getConfig().getEvalEngine();
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
ScriptEngine se = new ScriptEngineManager().getEngineByName(engine);
|
||||
if(se == null)
|
||||
{
|
||||
event.replyError("The eval engine provided in the config (`"+engine+"`) doesn't exist. This could be due to an invalid "
|
||||
+ "engine name, or the engine not existing in your version of java (`"+System.getProperty("java.version")+"`).");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "engine_not_found"));
|
||||
return;
|
||||
}
|
||||
se.put("bot", bot);
|
||||
se.put("event", event);
|
||||
se.put("jda", event.getJDA());
|
||||
if (event.getChannelType() != ChannelType.PRIVATE) {
|
||||
se.put("guild", event.getGuild());
|
||||
se.put("channel", event.getChannel());
|
||||
}
|
||||
try
|
||||
{
|
||||
event.reply(event.getClient().getSuccess()+" Evaluated Successfully:\n```\n"+se.eval(event.getArgs())+" ```");
|
||||
logCommandEvent(event, "OK", new JSONObject().put("invoked", true));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" An exception was thrown:\n```\n"+e+" ```");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "eval_exception"));
|
||||
}
|
||||
}
|
||||
|
||||
private void logCommandEvent(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
null, result, meta == null ? null : meta.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class PlaylistCmd extends OwnerCommand
|
||||
{
|
||||
public PlaylistCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.guildOnly = false;
|
||||
this.name = "playlist";
|
||||
this.arguments = "<append|delete|make|setdefault>";
|
||||
this.help = "playlist management";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.children = new OwnerCommand[]{
|
||||
new ListCmd(),
|
||||
new AppendlistCmd(),
|
||||
new DeletelistCmd(),
|
||||
new MakelistCmd(),
|
||||
new DefaultlistCmd(bot)
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(event.getClient().getWarning()+" Playlist Management Commands:\n");
|
||||
for(com.jagrosh.jdautilities.command.Command cmd: this.children)
|
||||
builder.append("\n`").append(event.getClient().getPrefix()).append(name).append(" ").append(cmd.getName())
|
||||
.append(" ").append(cmd.getArguments()==null ? "" : cmd.getArguments()).append("` - ").append(cmd.getHelp());
|
||||
event.reply(builder.toString());
|
||||
CommandLogContext.setMeta(new JSONObject().put("subcommands", this.children.length));
|
||||
}
|
||||
|
||||
public class MakelistCmd extends OwnerCommand
|
||||
{
|
||||
public MakelistCmd()
|
||||
{
|
||||
super(PlaylistCmd.this.bot);
|
||||
this.name = "make";
|
||||
this.aliases = new String[]{"create"};
|
||||
this.help = "makes a new playlist";
|
||||
this.arguments = "<name>";
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
String pname = event.getArgs().replaceAll("\\s+", "_");
|
||||
pname = pname.replaceAll("[*?|\\/\":<>]", "");
|
||||
if(pname == null || pname.isEmpty())
|
||||
{
|
||||
event.replyError("Please provide a name for the playlist!");
|
||||
CommandLogContext.setError("missing_playlist_name");
|
||||
}
|
||||
else if(bot.getPlaylistLoader().getPlaylist(pname) == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
bot.getPlaylistLoader().createPlaylist(pname);
|
||||
event.reply(event.getClient().getSuccess()+" Successfully created playlist `"+pname+"`!");
|
||||
CommandLogContext.setMeta(new JSONObject().put("playlist_name", pname));
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" I was unable to create the playlist: "+e.getLocalizedMessage());
|
||||
CommandLogContext.setError("create_playlist_failed");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Playlist `"+pname+"` already exists!");
|
||||
CommandLogContext.setError("playlist_exists");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DeletelistCmd extends OwnerCommand
|
||||
{
|
||||
public DeletelistCmd()
|
||||
{
|
||||
super(PlaylistCmd.this.bot);
|
||||
this.name = "delete";
|
||||
this.aliases = new String[]{"remove"};
|
||||
this.help = "deletes an existing playlist";
|
||||
this.arguments = "<name>";
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
String pname = event.getArgs().replaceAll("\\s+", "_");
|
||||
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Playlist `"+pname+"` doesn't exist!");
|
||||
CommandLogContext.setError("playlist_not_found");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
bot.getPlaylistLoader().deletePlaylist(pname);
|
||||
event.reply(event.getClient().getSuccess()+" Successfully deleted playlist `"+pname+"`!");
|
||||
CommandLogContext.setMeta(new JSONObject().put("playlist_name", pname));
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" I was unable to delete the playlist: "+e.getLocalizedMessage());
|
||||
CommandLogContext.setError("delete_playlist_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AppendlistCmd extends OwnerCommand
|
||||
{
|
||||
public AppendlistCmd()
|
||||
{
|
||||
super(PlaylistCmd.this.bot);
|
||||
this.name = "append";
|
||||
this.aliases = new String[]{"add"};
|
||||
this.help = "appends songs to an existing playlist";
|
||||
this.arguments = "<name> <URL> | <URL> | ...";
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
String[] parts = event.getArgs().split("\\s+", 2);
|
||||
if(parts.length<2)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Please include a playlist name and URLs to add!");
|
||||
CommandLogContext.setError("missing_append_args");
|
||||
return;
|
||||
}
|
||||
String pname = parts[0];
|
||||
Playlist playlist = bot.getPlaylistLoader().getPlaylist(pname);
|
||||
if(playlist==null)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Playlist `"+pname+"` doesn't exist!");
|
||||
CommandLogContext.setError("playlist_not_found");
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
playlist.getItems().forEach(item -> builder.append("\r\n").append(item));
|
||||
String[] urls = parts[1].split("\\|");
|
||||
for(String url: urls)
|
||||
{
|
||||
String u = url.trim();
|
||||
if(u.startsWith("<") && u.endsWith(">"))
|
||||
u = u.substring(1, u.length()-1);
|
||||
builder.append("\r\n").append(u);
|
||||
}
|
||||
try
|
||||
{
|
||||
bot.getPlaylistLoader().writePlaylist(pname, builder.toString());
|
||||
event.reply(event.getClient().getSuccess()+" Successfully added "+urls.length+" items to playlist `"+pname+"`!");
|
||||
CommandLogContext.setMeta(new JSONObject().put("playlist_name", pname).put("added_count", urls.length));
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" I was unable to append to the playlist: "+e.getLocalizedMessage());
|
||||
CommandLogContext.setError("append_playlist_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultlistCmd extends AutoplaylistCmd
|
||||
{
|
||||
public DefaultlistCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setdefault";
|
||||
this.aliases = new String[]{"default"};
|
||||
this.arguments = "<playlistname|NONE>";
|
||||
this.guildOnly = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class ListCmd extends OwnerCommand
|
||||
{
|
||||
public ListCmd()
|
||||
{
|
||||
super(PlaylistCmd.this.bot);
|
||||
this.name = "all";
|
||||
this.aliases = new String[]{"available","list"};
|
||||
this.help = "lists all available playlists";
|
||||
this.guildOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(!bot.getPlaylistLoader().folderExists())
|
||||
bot.getPlaylistLoader().createFolder();
|
||||
if(!bot.getPlaylistLoader().folderExists())
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+" Playlists folder does not exist and could not be created!");
|
||||
CommandLogContext.setError("playlist_folder_missing");
|
||||
return;
|
||||
}
|
||||
List<String> list = bot.getPlaylistLoader().getPlaylistNames();
|
||||
if(list==null)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Failed to load available playlists!");
|
||||
CommandLogContext.setError("playlist_list_failed");
|
||||
}
|
||||
else if(list.isEmpty())
|
||||
{
|
||||
event.reply(event.getClient().getWarning()+" There are no playlists in the Playlists folder!");
|
||||
CommandLogContext.setMeta(new JSONObject().put("count", 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(event.getClient().getSuccess()+" Available playlists:\n");
|
||||
list.forEach(str -> builder.append("`").append(str).append("` "));
|
||||
event.reply(builder.toString());
|
||||
CommandLogContext.setMeta(new JSONObject().put("count", list.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import net.dv8tion.jda.api.entities.Icon;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetavatarCmd extends OwnerCommand
|
||||
{
|
||||
public SetavatarCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setavatar";
|
||||
this.help = "sets the avatar of the bot";
|
||||
this.arguments = "<url>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
String url;
|
||||
if(event.getArgs().isEmpty())
|
||||
if(!event.getMessage().getAttachments().isEmpty() && event.getMessage().getAttachments().get(0).isImage())
|
||||
url = event.getMessage().getAttachments().get(0).getUrl();
|
||||
else
|
||||
url = null;
|
||||
else
|
||||
url = event.getArgs();
|
||||
InputStream s = OtherUtil.imageFromUrl(url);
|
||||
if(s==null)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Invalid or missing URL");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "invalid_avatar_url"));
|
||||
}
|
||||
else
|
||||
{
|
||||
try {
|
||||
event.getSelfUser().getManager().setAvatar(Icon.from(s)).queue(
|
||||
v -> {
|
||||
event.reply(event.getClient().getSuccess()+" Successfully changed avatar.");
|
||||
logCommandEvent(event, "OK", new JSONObject()
|
||||
.put("source", event.getArgs().isEmpty() ? "attachment" : "url"));
|
||||
},
|
||||
t -> {
|
||||
event.reply(event.getClient().getError()+" Failed to set avatar.");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "avatar_set_failed"));
|
||||
});
|
||||
} catch(IOException e) {
|
||||
event.reply(event.getClient().getError()+" Could not load from provided URL.");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "avatar_load_failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logCommandEvent(CommandEvent event, String result, JSONObject meta)
|
||||
{
|
||||
if(bot.getDataLogService() == null || event == null || event.getGuild() == null)
|
||||
return;
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), result, meta == null ? null : meta.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetgameCmd extends OwnerCommand
|
||||
{
|
||||
public SetgameCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setgame";
|
||||
this.help = "sets the game the bot is playing";
|
||||
this.arguments = "[action] [game]";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = false;
|
||||
this.children = new OwnerCommand[]{
|
||||
new SetlistenCmd(),
|
||||
new SetstreamCmd(),
|
||||
new SetwatchCmd()
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
String title = event.getArgs().toLowerCase().startsWith("playing") ? event.getArgs().substring(7).trim() : event.getArgs();
|
||||
try
|
||||
{
|
||||
event.getJDA().getPresence().setActivity(title.isEmpty() ? null : Activity.playing(title));
|
||||
event.reply(event.getClient().getSuccess()+" **"+event.getSelfUser().getName()
|
||||
+"** is "+(title.isEmpty() ? "no longer playing anything." : "now playing `"+title+"`"));
|
||||
CommandLogContext.setMeta(new JSONObject()
|
||||
.put("activity_type", "PLAYING")
|
||||
.put("title", title.isEmpty() ? JSONObject.NULL : title));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" The game could not be set!");
|
||||
CommandLogContext.setError("set_game_failed");
|
||||
}
|
||||
}
|
||||
|
||||
private class SetstreamCmd extends OwnerCommand
|
||||
{
|
||||
private SetstreamCmd()
|
||||
{
|
||||
super(SetgameCmd.this.bot);
|
||||
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
|
||||
public void doCommand(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'");
|
||||
CommandLogContext.setError("missing_stream_args");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
event.getJDA().getPresence().setActivity(Activity.streaming(parts[1], "https://twitch.tv/"+parts[0]));
|
||||
event.replySuccess("**"+event.getSelfUser().getName()
|
||||
+"** is now streaming `"+parts[1]+"`");
|
||||
CommandLogContext.setMeta(new JSONObject()
|
||||
.put("activity_type", "STREAMING")
|
||||
.put("title", parts[1])
|
||||
.put("user", parts[0]));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" The game could not be set!");
|
||||
CommandLogContext.setError("set_game_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SetlistenCmd extends OwnerCommand
|
||||
{
|
||||
private SetlistenCmd()
|
||||
{
|
||||
super(SetgameCmd.this.bot);
|
||||
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
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.replyError("Please include a title to listen to!");
|
||||
CommandLogContext.setError("missing_listen_title");
|
||||
return;
|
||||
}
|
||||
String title = event.getArgs().toLowerCase().startsWith("to") ? event.getArgs().substring(2).trim() : event.getArgs();
|
||||
try
|
||||
{
|
||||
event.getJDA().getPresence().setActivity(Activity.listening(title));
|
||||
event.replySuccess("**"+event.getSelfUser().getName()+"** is now listening to `"+title+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("activity_type", "LISTENING").put("title", title));
|
||||
} catch(Exception e) {
|
||||
event.reply(event.getClient().getError()+" The game could not be set!");
|
||||
CommandLogContext.setError("set_game_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SetwatchCmd extends OwnerCommand
|
||||
{
|
||||
private SetwatchCmd()
|
||||
{
|
||||
super(SetgameCmd.this.bot);
|
||||
this.name = "watch";
|
||||
this.aliases = new String[]{"watching"};
|
||||
this.help = "sets the game the bot is watching";
|
||||
this.arguments = "<title>";
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.replyError("Please include a title to watch!");
|
||||
CommandLogContext.setError("missing_watch_title");
|
||||
return;
|
||||
}
|
||||
String title = event.getArgs();
|
||||
try
|
||||
{
|
||||
event.getJDA().getPresence().setActivity(Activity.watching(title));
|
||||
event.replySuccess("**"+event.getSelfUser().getName()+"** is now watching `"+title+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("activity_type", "WATCHING").put("title", title));
|
||||
} catch(Exception e) {
|
||||
event.reply(event.getClient().getError()+" The game could not be set!");
|
||||
CommandLogContext.setError("set_game_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
-51
@@ -1,51 +1,62 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.commands;
|
||||
|
||||
import me.jagrosh.jdautilities.commandclient.Command;
|
||||
import me.jagrosh.jdautilities.commandclient.CommandEvent;
|
||||
import me.jagrosh.jmusicbot.Bot;
|
||||
import net.dv8tion.jda.core.exceptions.RateLimitedException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetnameCmd extends Command {
|
||||
|
||||
public SetnameCmd(Bot bot)
|
||||
{
|
||||
this.name = "setname";
|
||||
this.help = "sets the name of the bot";
|
||||
this.arguments = "<name>";
|
||||
this.ownerCommand = true;
|
||||
this.category = bot.OWNER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event) {
|
||||
try {
|
||||
String oldname = event.getSelfUser().getName();
|
||||
event.getSelfUser().getManager().setName(event.getArgs()).complete(false);
|
||||
event.reply(event.getClient().getSuccess()+" Name changed from `"+oldname+"` to `"+event.getArgs()+"`");
|
||||
} catch(RateLimitedException e) {
|
||||
event.reply(event.getClient().getError()+" Name can only be changed twice per hour!");
|
||||
} catch(Exception e) {
|
||||
event.reply(event.getClient().getError()+" That name is not valid!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import net.dv8tion.jda.api.exceptions.RateLimitedException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetnameCmd extends OwnerCommand
|
||||
{
|
||||
public SetnameCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setname";
|
||||
this.help = "sets the name of the bot";
|
||||
this.arguments = "<name>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(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()+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", oldname).put("after", event.getArgs()));
|
||||
}
|
||||
catch(RateLimitedException e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" Name can only be changed twice per hour!");
|
||||
CommandLogContext.setError("rate_limited");
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
event.reply(event.getClient().getError()+" That name is not valid!");
|
||||
CommandLogContext.setError("invalid_name");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class SetstatusCmd extends OwnerCommand
|
||||
{
|
||||
public SetstatusCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "setstatus";
|
||||
this.help = "sets the status the bot displays";
|
||||
this.arguments = "<status>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(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`");
|
||||
CommandLogContext.setError("invalid_status");
|
||||
}
|
||||
else
|
||||
{
|
||||
OnlineStatus before = event.getJDA().getPresence().getStatus();
|
||||
event.getJDA().getPresence().setStatus(status);
|
||||
event.replySuccess("Set the status to `"+status.getKey().toUpperCase()+"`");
|
||||
CommandLogContext.setMeta(new JSONObject()
|
||||
.put("before", before == null ? JSONObject.NULL : before.getKey())
|
||||
.put("after", status.getKey()));
|
||||
}
|
||||
} catch(Exception e) {
|
||||
event.reply(event.getClient().getError()+" The status could not be set!");
|
||||
CommandLogContext.setError("set_status_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
-44
@@ -1,44 +1,46 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.commands;
|
||||
|
||||
import me.jagrosh.jdautilities.commandclient.Command;
|
||||
import me.jagrosh.jdautilities.commandclient.CommandEvent;
|
||||
import me.jagrosh.jmusicbot.Bot;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class ShutdownCmd extends Command {
|
||||
|
||||
private final Bot bot;
|
||||
public ShutdownCmd(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
this.name = "shutdown";
|
||||
this.help = "safely shuts down";
|
||||
this.ownerCommand = true;
|
||||
this.category = bot.OWNER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event) {
|
||||
event.reply(event.getClient().getWarning()+" Shutting down...");
|
||||
bot.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class ShutdownCmd extends OwnerCommand
|
||||
{
|
||||
public ShutdownCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "shutdown";
|
||||
this.help = "safely shuts down";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setMeta(new JSONObject().put("invoked", true));
|
||||
event.replyWarning("Shutting down...");
|
||||
bot.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2026
|
||||
*
|
||||
* 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.datalog;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public final class CommandLogContext
|
||||
{
|
||||
private static final ThreadLocal<CommandLogEntry> CONTEXT = new ThreadLocal<>();
|
||||
|
||||
private CommandLogContext()
|
||||
{
|
||||
}
|
||||
|
||||
public static void clear()
|
||||
{
|
||||
CONTEXT.remove();
|
||||
}
|
||||
|
||||
public static void setMeta(JSONObject meta)
|
||||
{
|
||||
getOrCreate().meta = meta;
|
||||
}
|
||||
|
||||
public static void setResult(String result)
|
||||
{
|
||||
getOrCreate().result = result;
|
||||
}
|
||||
|
||||
public static void setError(String reason)
|
||||
{
|
||||
CommandLogEntry entry = getOrCreate();
|
||||
entry.result = "ERROR";
|
||||
if(reason != null && !reason.isEmpty())
|
||||
{
|
||||
if(entry.meta == null)
|
||||
entry.meta = new JSONObject();
|
||||
entry.meta.put("error_reason", reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setManualLogging()
|
||||
{
|
||||
getOrCreate().manual = true;
|
||||
}
|
||||
|
||||
public static CommandLogEntry consume()
|
||||
{
|
||||
CommandLogEntry entry = CONTEXT.get();
|
||||
CONTEXT.remove();
|
||||
return entry;
|
||||
}
|
||||
|
||||
private static CommandLogEntry getOrCreate()
|
||||
{
|
||||
CommandLogEntry entry = CONTEXT.get();
|
||||
if(entry == null)
|
||||
{
|
||||
entry = new CommandLogEntry();
|
||||
CONTEXT.set(entry);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
public static final class CommandLogEntry
|
||||
{
|
||||
public JSONObject meta;
|
||||
public String result;
|
||||
public boolean manual;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* Copyright 2026
|
||||
*
|
||||
* 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.datalog;
|
||||
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
|
||||
import java.sql.*;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
|
||||
import net.dv8tion.jda.api.managers.AudioManager;
|
||||
import net.dv8tion.jda.api.entities.channel.unions.AudioChannelUnion;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DataLogService
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger("DataLog");
|
||||
|
||||
private final Bot bot;
|
||||
private final Connection connection;
|
||||
private final ExecutorService executor;
|
||||
private final Map<Long, Long> currentPlaySessionId;
|
||||
private final Map<Long, Long> currentPlayStartAt;
|
||||
private final Map<Long, Map<Long, Long>> activeListeners;
|
||||
|
||||
public DataLogService(Bot bot, String jdbcUrl, String user, String password) throws SQLException
|
||||
{
|
||||
this.bot = bot;
|
||||
this.executor = Executors.newSingleThreadExecutor();
|
||||
this.currentPlaySessionId = new ConcurrentHashMap<>();
|
||||
this.currentPlayStartAt = new ConcurrentHashMap<>();
|
||||
this.activeListeners = new ConcurrentHashMap<>();
|
||||
|
||||
if(user != null && !user.isEmpty())
|
||||
this.connection = DriverManager.getConnection(jdbcUrl, user, password == null ? "" : password);
|
||||
else
|
||||
this.connection = DriverManager.getConnection(jdbcUrl);
|
||||
|
||||
this.connection.setAutoCommit(true);
|
||||
initSchema();
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
executor.shutdownNow();
|
||||
try
|
||||
{
|
||||
connection.close();
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.warn("Failed to close DataLogService connection: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void initSchema()
|
||||
{
|
||||
submit(() ->
|
||||
{
|
||||
try(Statement st = connection.createStatement())
|
||||
{
|
||||
st.execute("CREATE TABLE IF NOT EXISTS guilds (guild_id INTEGER PRIMARY KEY, name TEXT, first_seen_at INTEGER)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS users (user_id INTEGER PRIMARY KEY)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS user_profiles (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, guild_id INTEGER, username TEXT, discrim TEXT, avatar_url TEXT, nickname TEXT, seen_at INTEGER)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS tracks (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT, identifier TEXT, uri TEXT, title TEXT, author TEXT, length_ms INTEGER, is_stream INTEGER)");
|
||||
st.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_tracks_source_identifier ON tracks(source, identifier)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS play_sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id INTEGER, voice_channel_id INTEGER, track_id INTEGER, requested_by INTEGER, queue_source TEXT, search_query TEXT, playlist_name TEXT, started_at INTEGER, ended_at INTEGER, end_reason TEXT, duration_ms INTEGER, listeners_at_start INTEGER)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS listener_sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, play_session_id INTEGER, user_id INTEGER, joined_at INTEGER, left_at INTEGER)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS queue_events (id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id INTEGER, user_id INTEGER, track_id INTEGER, event_type TEXT, source TEXT, position INTEGER, query TEXT, playlist_name TEXT, event_meta TEXT, created_at INTEGER)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS search_events (id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id INTEGER, user_id INTEGER, query TEXT, selected_index INTEGER, track_id INTEGER, event_meta TEXT, created_at INTEGER)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS command_events (id INTEGER PRIMARY KEY AUTOINCREMENT, guild_id INTEGER, user_id INTEGER, command TEXT, args TEXT, result TEXT, event_meta TEXT, created_at INTEGER)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS tags (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE)");
|
||||
st.execute("CREATE TABLE IF NOT EXISTS track_tags (track_id INTEGER, tag_id INTEGER, added_at INTEGER, added_by_user_id INTEGER)");
|
||||
|
||||
st.execute("CREATE INDEX IF NOT EXISTS idx_play_sessions_guild_started ON play_sessions(guild_id, started_at)");
|
||||
st.execute("CREATE INDEX IF NOT EXISTS idx_queue_events_guild_created ON queue_events(guild_id, created_at)");
|
||||
st.execute("CREATE INDEX IF NOT EXISTS idx_search_events_guild_created ON search_events(guild_id, created_at)");
|
||||
st.execute("CREATE INDEX IF NOT EXISTS idx_listener_sessions_play ON listener_sessions(play_session_id)");
|
||||
st.execute("CREATE INDEX IF NOT EXISTS idx_command_events_guild_created ON command_events(guild_id, created_at)");
|
||||
st.execute("CREATE INDEX IF NOT EXISTS idx_command_events_command_created ON command_events(command, created_at)");
|
||||
|
||||
ensureColumnExists("queue_events", "event_meta", "TEXT");
|
||||
ensureColumnExists("search_events", "event_meta", "TEXT");
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.warn("Failed to init datalog schema: {}", ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void logQueueAdd(Guild guild, User user, AudioTrack track, String source, Integer position, String query, String playlistName)
|
||||
{
|
||||
logQueueEventWithMeta(guild, user, track, "ADD", source, position, query, playlistName, null);
|
||||
}
|
||||
|
||||
public void logSearchSelection(Guild guild, User user, String query, int selectedIndex, AudioTrack track)
|
||||
{
|
||||
logSearchEventWithMeta(guild, user, query, selectedIndex, track, null);
|
||||
}
|
||||
|
||||
public void logPlayStart(Guild guild, AudioTrack track, RequestMetadata rm, String queueSource, String searchQuery, String playlistName)
|
||||
{
|
||||
if(guild == null || track == null)
|
||||
return;
|
||||
submit(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
ensureGuild(guild);
|
||||
long trackId = ensureTrack(track);
|
||||
long requestedBy = rm == null ? 0L : rm.getOwner();
|
||||
if(rm != null && rm.user != null)
|
||||
ensureUserAndProfile(guild, null, rm);
|
||||
|
||||
AudioManager am = guild.getAudioManager();
|
||||
AudioChannelUnion channel = am.getConnectedChannel();
|
||||
long channelId = channel == null ? 0L : channel.getIdLong();
|
||||
int listeners = channel == null ? 0 : (int)channel.getMembers().stream()
|
||||
.filter(m -> !m.getUser().isBot() && !m.getVoiceState().isDeafened())
|
||||
.count();
|
||||
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO play_sessions (guild_id, voice_channel_id, track_id, requested_by, queue_source, search_query, playlist_name, started_at, listeners_at_start) VALUES (?,?,?,?,?,?,?,?,?)",
|
||||
Statement.RETURN_GENERATED_KEYS))
|
||||
{
|
||||
ps.setLong(1, guild.getIdLong());
|
||||
if(channelId == 0L)
|
||||
ps.setNull(2, Types.BIGINT);
|
||||
else
|
||||
ps.setLong(2, channelId);
|
||||
ps.setLong(3, trackId);
|
||||
ps.setLong(4, requestedBy);
|
||||
ps.setString(5, queueSource);
|
||||
ps.setString(6, searchQuery);
|
||||
ps.setString(7, playlistName);
|
||||
long startedAt = now();
|
||||
ps.setLong(8, startedAt);
|
||||
ps.setInt(9, listeners);
|
||||
ps.executeUpdate();
|
||||
try(ResultSet rs = ps.getGeneratedKeys())
|
||||
{
|
||||
if(rs.next())
|
||||
{
|
||||
long sessionId = rs.getLong(1);
|
||||
currentPlaySessionId.put(guild.getIdLong(), sessionId);
|
||||
currentPlayStartAt.put(guild.getIdLong(), startedAt);
|
||||
activeListeners.put(guild.getIdLong(), new ConcurrentHashMap<>());
|
||||
if(channel != null)
|
||||
{
|
||||
channel.getMembers().forEach(m ->
|
||||
{
|
||||
if(m.getUser().isBot() || m.getVoiceState().isDeafened())
|
||||
return;
|
||||
startListenerSession(guild, sessionId, m.getUser().getIdLong());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("logPlayStart failed: {}", ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void logPlayEnd(Guild guild, String endReason)
|
||||
{
|
||||
if(guild == null)
|
||||
return;
|
||||
submit(() ->
|
||||
{
|
||||
Long sessionId = currentPlaySessionId.remove(guild.getIdLong());
|
||||
Long startedAt = currentPlayStartAt.remove(guild.getIdLong());
|
||||
if(sessionId == null || startedAt == null)
|
||||
return;
|
||||
long endedAt = now();
|
||||
long duration = Math.max(0L, endedAt - startedAt);
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"UPDATE play_sessions SET ended_at=?, end_reason=?, duration_ms=? WHERE id=?"))
|
||||
{
|
||||
ps.setLong(1, endedAt);
|
||||
ps.setString(2, endReason);
|
||||
ps.setLong(3, duration);
|
||||
ps.setLong(4, sessionId);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("logPlayEnd failed: {}", ex.getMessage());
|
||||
}
|
||||
closeAllListenerSessions(guild.getIdLong(), sessionId);
|
||||
});
|
||||
}
|
||||
|
||||
public void logSkipVote(Guild guild, User user, AudioTrack track)
|
||||
{
|
||||
logQueueEventWithMeta(guild, user, track, "SKIP_VOTE", null, null, null, null, null);
|
||||
}
|
||||
|
||||
public void logSkipAction(Guild guild, User user, AudioTrack track, String reason)
|
||||
{
|
||||
logQueueEventWithMeta(guild, user, track, "SKIP_"+reason, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public void logCommandEvent(Guild guild, User user, String command, String args, String result, String metaJson)
|
||||
{
|
||||
if(guild == null || command == null)
|
||||
return;
|
||||
submit(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
ensureGuild(guild);
|
||||
if(user != null)
|
||||
ensureUserAndProfile(guild, user, null);
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO command_events (guild_id, user_id, command, args, result, event_meta, created_at) VALUES (?,?,?,?,?,?,?)"))
|
||||
{
|
||||
ps.setLong(1, guild.getIdLong());
|
||||
ps.setLong(2, user == null ? 0L : user.getIdLong());
|
||||
ps.setString(3, command);
|
||||
ps.setString(4, args);
|
||||
ps.setString(5, result);
|
||||
ps.setString(6, metaJson);
|
||||
ps.setLong(7, now());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("logCommandEvent failed: {}", ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void logQueueEventWithMeta(Guild guild, User user, AudioTrack track, String eventType, String source, Integer position, String query, String playlistName, String metaJson)
|
||||
{
|
||||
if(guild == null || eventType == null)
|
||||
return;
|
||||
submit(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
ensureGuild(guild);
|
||||
if(user != null)
|
||||
ensureUserAndProfile(guild, user, null);
|
||||
Long trackId = null;
|
||||
if(track != null)
|
||||
trackId = ensureTrack(track);
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO queue_events (guild_id, user_id, track_id, event_type, source, position, query, playlist_name, event_meta, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)"))
|
||||
{
|
||||
ps.setLong(1, guild.getIdLong());
|
||||
ps.setLong(2, user == null ? 0L : user.getIdLong());
|
||||
if(trackId == null) ps.setNull(3, Types.INTEGER); else ps.setLong(3, trackId);
|
||||
ps.setString(4, eventType);
|
||||
ps.setString(5, source);
|
||||
if(position == null) ps.setNull(6, Types.INTEGER); else ps.setInt(6, position);
|
||||
ps.setString(7, query);
|
||||
ps.setString(8, playlistName);
|
||||
ps.setString(9, metaJson);
|
||||
ps.setLong(10, now());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("logQueueEvent failed: {}", ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void logSearchEventWithMeta(Guild guild, User user, String query, Integer selectedIndex, AudioTrack track, String metaJson)
|
||||
{
|
||||
if(guild == null)
|
||||
return;
|
||||
submit(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
ensureGuild(guild);
|
||||
if(user != null)
|
||||
ensureUserAndProfile(guild, user, null);
|
||||
Long trackId = null;
|
||||
if(track != null)
|
||||
trackId = ensureTrack(track);
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO search_events (guild_id, user_id, query, selected_index, track_id, event_meta, created_at) VALUES (?,?,?,?,?,?,?)"))
|
||||
{
|
||||
ps.setLong(1, guild.getIdLong());
|
||||
ps.setLong(2, user == null ? 0L : user.getIdLong());
|
||||
ps.setString(3, query);
|
||||
if(selectedIndex == null) ps.setNull(4, Types.INTEGER); else ps.setInt(4, selectedIndex);
|
||||
if(trackId == null) ps.setNull(5, Types.INTEGER); else ps.setLong(5, trackId);
|
||||
ps.setString(6, metaJson);
|
||||
ps.setLong(7, now());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("logSearchEvent failed: {}", ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void logPlaylistError(Guild guild, User user, String playlistName, String item, String reason)
|
||||
{
|
||||
JSONObject meta = new JSONObject();
|
||||
if(playlistName != null)
|
||||
meta.put("playlist_name", playlistName);
|
||||
if(item != null)
|
||||
meta.put("playlist_item", item);
|
||||
if(reason != null)
|
||||
meta.put("error_reason", reason);
|
||||
logQueueEventWithMeta(guild, user, null, "PLAYLIST_ERROR", "PLAYLIST", null, null, playlistName,
|
||||
meta.length() == 0 ? null : meta.toString());
|
||||
}
|
||||
|
||||
public void onVoiceUpdate(GuildVoiceUpdateEvent event)
|
||||
{
|
||||
if(event == null)
|
||||
return;
|
||||
Guild guild = event.getGuild();
|
||||
Long sessionId = currentPlaySessionId.get(guild.getIdLong());
|
||||
if(sessionId == null)
|
||||
return;
|
||||
Member member = event.getMember();
|
||||
if(member.getUser().isBot())
|
||||
return;
|
||||
AudioManager am = guild.getAudioManager();
|
||||
AudioChannelUnion channel = am.getConnectedChannel();
|
||||
if(channel == null)
|
||||
return;
|
||||
|
||||
boolean inChannel = member.getVoiceState().inAudioChannel() && channel.equals(member.getVoiceState().getChannel());
|
||||
boolean listening = inChannel && !member.getVoiceState().isDeafened();
|
||||
long userId = member.getUser().getIdLong();
|
||||
Map<Long, Long> listenerMap = activeListeners.computeIfAbsent(guild.getIdLong(), id -> new ConcurrentHashMap<>());
|
||||
|
||||
if(listening && !listenerMap.containsKey(userId))
|
||||
{
|
||||
submit(() -> startListenerSession(guild, sessionId, userId));
|
||||
}
|
||||
else if(!listening && listenerMap.containsKey(userId))
|
||||
{
|
||||
submit(() -> endListenerSession(guild.getIdLong(), sessionId, userId));
|
||||
}
|
||||
}
|
||||
|
||||
private void startListenerSession(Guild guild, long sessionId, long userId)
|
||||
{
|
||||
Map<Long, Long> listenerMap = activeListeners.computeIfAbsent(guild.getIdLong(), id -> new ConcurrentHashMap<>());
|
||||
if(listenerMap.containsKey(userId))
|
||||
return;
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"INSERT INTO listener_sessions (play_session_id, user_id, joined_at) VALUES (?,?,?)",
|
||||
Statement.RETURN_GENERATED_KEYS))
|
||||
{
|
||||
ps.setLong(1, sessionId);
|
||||
ps.setLong(2, userId);
|
||||
ps.setLong(3, now());
|
||||
ps.executeUpdate();
|
||||
try(ResultSet rs = ps.getGeneratedKeys())
|
||||
{
|
||||
if(rs.next())
|
||||
{
|
||||
listenerMap.put(userId, rs.getLong(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("startListenerSession failed: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void endListenerSession(long guildId, long sessionId, long userId)
|
||||
{
|
||||
Map<Long, Long> listenerMap = activeListeners.computeIfAbsent(guildId, id -> new ConcurrentHashMap<>());
|
||||
Long listenerSessionId = listenerMap.remove(userId);
|
||||
if(listenerSessionId == null)
|
||||
return;
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"UPDATE listener_sessions SET left_at=? WHERE id=?"))
|
||||
{
|
||||
ps.setLong(1, now());
|
||||
ps.setLong(2, listenerSessionId);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("endListenerSession failed: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAllListenerSessions(long guildId, long sessionId)
|
||||
{
|
||||
Map<Long, Long> listenerMap = activeListeners.remove(guildId);
|
||||
if(listenerMap == null)
|
||||
return;
|
||||
listenerMap.forEach((userId, listenerSessionId) ->
|
||||
{
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"UPDATE listener_sessions SET left_at=? WHERE id=?"))
|
||||
{
|
||||
ps.setLong(1, now());
|
||||
ps.setLong(2, listenerSessionId);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
catch(SQLException ex)
|
||||
{
|
||||
LOG.debug("closeListenerSession failed: {}", ex.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ensureGuild(Guild guild) throws SQLException
|
||||
{
|
||||
try(PreparedStatement ps = connection.prepareStatement("UPDATE guilds SET name=? WHERE guild_id=?"))
|
||||
{
|
||||
ps.setString(1, guild.getName());
|
||||
ps.setLong(2, guild.getIdLong());
|
||||
int updated = ps.executeUpdate();
|
||||
if(updated == 0)
|
||||
{
|
||||
try(PreparedStatement ins = connection.prepareStatement(
|
||||
"INSERT INTO guilds (guild_id, name, first_seen_at) VALUES (?,?,?)"))
|
||||
{
|
||||
ins.setLong(1, guild.getIdLong());
|
||||
ins.setString(2, guild.getName());
|
||||
ins.setLong(3, now());
|
||||
ins.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureColumnExists(String table, String column, String type) throws SQLException
|
||||
{
|
||||
if(table == null || column == null || type == null)
|
||||
return;
|
||||
try(Statement st = connection.createStatement();
|
||||
ResultSet rs = st.executeQuery("PRAGMA table_info(" + table + ")"))
|
||||
{
|
||||
while(rs.next())
|
||||
{
|
||||
String name = rs.getString("name");
|
||||
if(column.equalsIgnoreCase(name))
|
||||
return;
|
||||
}
|
||||
}
|
||||
try(Statement st = connection.createStatement())
|
||||
{
|
||||
st.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureUserAndProfile(Guild guild, User user, RequestMetadata rm) throws SQLException
|
||||
{
|
||||
long userId = user != null ? user.getIdLong() : rm.user.id;
|
||||
try(PreparedStatement ps = connection.prepareStatement("INSERT INTO users (user_id) VALUES (?)"))
|
||||
{
|
||||
ps.setLong(1, userId);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
catch(SQLException ignore)
|
||||
{
|
||||
// ignore duplicates
|
||||
}
|
||||
String username = user != null ? user.getName() : rm.user.username;
|
||||
String discrim = user != null ? user.getDiscriminator() : rm.user.discrim;
|
||||
String avatar = user != null ? user.getEffectiveAvatarUrl() : rm.user.avatar;
|
||||
String nickname = null;
|
||||
if(user != null)
|
||||
{
|
||||
Member member = guild.getMember(user);
|
||||
nickname = member == null ? null : member.getNickname();
|
||||
}
|
||||
long now = now();
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"SELECT username, discrim, avatar_url, nickname FROM user_profiles WHERE user_id=? AND guild_id=? ORDER BY seen_at DESC LIMIT 1"))
|
||||
{
|
||||
ps.setLong(1, userId);
|
||||
ps.setLong(2, guild.getIdLong());
|
||||
try(ResultSet rs = ps.executeQuery())
|
||||
{
|
||||
boolean changed = true;
|
||||
if(rs.next())
|
||||
{
|
||||
changed = !safeEquals(rs.getString(1), username)
|
||||
|| !safeEquals(rs.getString(2), discrim)
|
||||
|| !safeEquals(rs.getString(3), avatar)
|
||||
|| !safeEquals(rs.getString(4), nickname);
|
||||
}
|
||||
if(changed)
|
||||
{
|
||||
try(PreparedStatement ins = connection.prepareStatement(
|
||||
"INSERT INTO user_profiles (user_id, guild_id, username, discrim, avatar_url, nickname, seen_at) VALUES (?,?,?,?,?,?,?)"))
|
||||
{
|
||||
ins.setLong(1, userId);
|
||||
ins.setLong(2, guild.getIdLong());
|
||||
ins.setString(3, username);
|
||||
ins.setString(4, discrim);
|
||||
ins.setString(5, avatar);
|
||||
ins.setString(6, nickname);
|
||||
ins.setLong(7, now);
|
||||
ins.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long ensureTrack(AudioTrack track) throws SQLException
|
||||
{
|
||||
AudioTrackInfo info = track.getInfo();
|
||||
String source = inferSource(info);
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"SELECT id FROM tracks WHERE source=? AND identifier=?"))
|
||||
{
|
||||
ps.setString(1, source);
|
||||
ps.setString(2, track.getIdentifier());
|
||||
try(ResultSet rs = ps.executeQuery())
|
||||
{
|
||||
if(rs.next())
|
||||
return rs.getLong(1);
|
||||
}
|
||||
}
|
||||
try(PreparedStatement ins = connection.prepareStatement(
|
||||
"INSERT INTO tracks (source, identifier, uri, title, author, length_ms, is_stream) VALUES (?,?,?,?,?,?,?)",
|
||||
Statement.RETURN_GENERATED_KEYS))
|
||||
{
|
||||
ins.setString(1, source);
|
||||
ins.setString(2, track.getIdentifier());
|
||||
ins.setString(3, info.uri);
|
||||
ins.setString(4, info.title);
|
||||
ins.setString(5, info.author);
|
||||
ins.setLong(6, info.length);
|
||||
ins.setInt(7, info.isStream ? 1 : 0);
|
||||
ins.executeUpdate();
|
||||
try(ResultSet rs = ins.getGeneratedKeys())
|
||||
{
|
||||
if(rs.next())
|
||||
return rs.getLong(1);
|
||||
}
|
||||
}
|
||||
catch(SQLException ignore)
|
||||
{
|
||||
// duplicate or insert failure; fall through to select
|
||||
}
|
||||
try(PreparedStatement ps = connection.prepareStatement(
|
||||
"SELECT id FROM tracks WHERE source=? AND identifier=?"))
|
||||
{
|
||||
ps.setString(1, source);
|
||||
ps.setString(2, track.getIdentifier());
|
||||
try(ResultSet rs = ps.executeQuery())
|
||||
{
|
||||
if(rs.next())
|
||||
return rs.getLong(1);
|
||||
}
|
||||
}
|
||||
return -1L;
|
||||
}
|
||||
|
||||
private String inferSource(AudioTrackInfo info)
|
||||
{
|
||||
if(info == null || info.uri == null)
|
||||
return "unknown";
|
||||
String uri = info.uri.toLowerCase();
|
||||
if(uri.contains("youtube.com") || uri.contains("youtu.be"))
|
||||
return "youtube";
|
||||
if(uri.contains("soundcloud.com"))
|
||||
return "soundcloud";
|
||||
if(uri.contains("bandcamp.com"))
|
||||
return "bandcamp";
|
||||
if(uri.contains("vimeo.com"))
|
||||
return "vimeo";
|
||||
if(uri.contains("twitch.tv"))
|
||||
return "twitch";
|
||||
return "other";
|
||||
}
|
||||
|
||||
private void submit(Runnable r)
|
||||
{
|
||||
executor.submit(r);
|
||||
}
|
||||
|
||||
private static long now()
|
||||
{
|
||||
return Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
private static boolean safeEquals(String a, String b)
|
||||
{
|
||||
if(a == null && b == null)
|
||||
return true;
|
||||
if(a == null || b == null)
|
||||
return false;
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.entities;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
* @param <K>
|
||||
* @param <V>
|
||||
*/
|
||||
public class Pair<K,V>
|
||||
{
|
||||
private final K key;
|
||||
private final V value;
|
||||
|
||||
public Pair(K key, V value)
|
||||
{
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public K getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
public V getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh (jagrosh)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.entities;
|
||||
|
||||
import java.util.Scanner;
|
||||
import javax.swing.JOptionPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class Prompt
|
||||
{
|
||||
private final String title;
|
||||
private final String noguiMessage;
|
||||
|
||||
private boolean nogui;
|
||||
private boolean noprompt;
|
||||
private Scanner scanner;
|
||||
|
||||
public Prompt(String title)
|
||||
{
|
||||
this(title, null);
|
||||
}
|
||||
|
||||
public Prompt(String title, String noguiMessage)
|
||||
{
|
||||
this(title, noguiMessage, "true".equalsIgnoreCase(System.getProperty("nogui")), "true".equalsIgnoreCase(System.getProperty("noprompt")));
|
||||
}
|
||||
|
||||
public Prompt(String title, String noguiMessage, boolean nogui, boolean noprompt)
|
||||
{
|
||||
this.title = title;
|
||||
this.noguiMessage = noguiMessage == null ? "Switching to nogui mode. You can manually start in nogui mode by including the -Dnogui=true flag." : noguiMessage;
|
||||
this.nogui = nogui;
|
||||
this.noprompt = noprompt;
|
||||
}
|
||||
|
||||
public boolean isNoGUI()
|
||||
{
|
||||
return nogui;
|
||||
}
|
||||
|
||||
public void alert(Level level, String context, String message)
|
||||
{
|
||||
if(nogui)
|
||||
{
|
||||
Logger log = LoggerFactory.getLogger(context);
|
||||
switch(level)
|
||||
{
|
||||
case INFO:
|
||||
log.info(message);
|
||||
break;
|
||||
case WARNING:
|
||||
log.warn(message);
|
||||
break;
|
||||
case ERROR:
|
||||
log.error(message);
|
||||
break;
|
||||
default:
|
||||
log.info(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
int option = 0;
|
||||
switch(level)
|
||||
{
|
||||
case INFO:
|
||||
option = JOptionPane.INFORMATION_MESSAGE;
|
||||
break;
|
||||
case WARNING:
|
||||
option = JOptionPane.WARNING_MESSAGE;
|
||||
break;
|
||||
case ERROR:
|
||||
option = JOptionPane.ERROR_MESSAGE;
|
||||
break;
|
||||
default:
|
||||
option = JOptionPane.PLAIN_MESSAGE;
|
||||
break;
|
||||
}
|
||||
JOptionPane.showMessageDialog(null, "<html><body><p style='width: 400px;'>"+message, title, option);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
nogui = true;
|
||||
alert(Level.WARNING, context, noguiMessage);
|
||||
alert(level, context, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String prompt(String content)
|
||||
{
|
||||
if(noprompt)
|
||||
return null;
|
||||
if(nogui)
|
||||
{
|
||||
if(scanner==null)
|
||||
scanner = new Scanner(System.in);
|
||||
try
|
||||
{
|
||||
System.out.println(content);
|
||||
if(scanner.hasNextLine())
|
||||
return scanner.nextLine();
|
||||
return null;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
alert(Level.ERROR, title, "Unable to read input from command line.");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
return JOptionPane.showInputDialog(null, content, title, JOptionPane.QUESTION_MESSAGE);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
nogui = true;
|
||||
alert(Level.WARNING, title, noguiMessage);
|
||||
return prompt(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Level
|
||||
{
|
||||
INFO, WARNING, ERROR;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.gui;
|
||||
package com.jagrosh.jmusicbot.gui;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridLayout;
|
||||
+73
-64
@@ -1,64 +1,73 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.gui;
|
||||
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.WindowConstants;
|
||||
import me.jagrosh.jmusicbot.Bot;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class GUI extends JFrame {
|
||||
|
||||
private final ConsolePanel console;
|
||||
private final GuildsPanel guilds;
|
||||
private final Bot bot;
|
||||
|
||||
public GUI(Bot bot) {
|
||||
super();
|
||||
this.bot = bot;
|
||||
console = new ConsolePanel();
|
||||
guilds = new GuildsPanel(bot);
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
setTitle("JMusicBot");
|
||||
JTabbedPane tabs = new JTabbedPane();
|
||||
//tabs.add("Guilds", guilds);
|
||||
tabs.add("Console", console);
|
||||
getContentPane().add(tabs);
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
setVisible(true);
|
||||
addWindowListener(new WindowListener() {
|
||||
@Override public void windowOpened(WindowEvent e) {}
|
||||
@Override public void windowClosing(WindowEvent e) {bot.shutdown();}
|
||||
@Override public void windowClosed(WindowEvent e) {}
|
||||
@Override public void windowIconified(WindowEvent e) {}
|
||||
@Override public void windowDeiconified(WindowEvent e) {}
|
||||
@Override public void windowActivated(WindowEvent e) {}
|
||||
@Override public void windowDeactivated(WindowEvent e) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.gui;
|
||||
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.WindowConstants;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class GUI extends JFrame
|
||||
{
|
||||
private final ConsolePanel console;
|
||||
private final Bot bot;
|
||||
|
||||
public GUI(Bot bot)
|
||||
{
|
||||
super();
|
||||
this.bot = bot;
|
||||
console = new ConsolePanel();
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
setTitle("JMusicBot");
|
||||
JTabbedPane tabs = new JTabbedPane();
|
||||
tabs.add("Console", console);
|
||||
getContentPane().add(tabs);
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
setVisible(true);
|
||||
addWindowListener(new WindowListener()
|
||||
{
|
||||
@Override public void windowOpened(WindowEvent e) { /* unused */ }
|
||||
@Override public void windowClosing(WindowEvent e)
|
||||
{
|
||||
try
|
||||
{
|
||||
bot.shutdown();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@Override public void windowClosed(WindowEvent e) { /* unused */ }
|
||||
@Override public void windowIconified(WindowEvent e) { /* unused */ }
|
||||
@Override public void windowDeiconified(WindowEvent e) { /* unused */ }
|
||||
@Override public void windowActivated(WindowEvent e) { /* unused */ }
|
||||
@Override public void windowDeactivated(WindowEvent e) { /* unused */ }
|
||||
});
|
||||
}
|
||||
}
|
||||
+156
-146
@@ -1,147 +1,157 @@
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import javax.swing.*;
|
||||
/**
|
||||
*
|
||||
* @author Lawrence Dol
|
||||
*/
|
||||
public class TextAreaOutputStream extends OutputStream {
|
||||
|
||||
// *************************************************************************************************
|
||||
// INSTANCE MEMBERS
|
||||
// *************************************************************************************************
|
||||
|
||||
private byte[] oneByte; // array for write(int val);
|
||||
private Appender appender; // most recent action
|
||||
|
||||
public TextAreaOutputStream(JTextArea txtara) {
|
||||
this(txtara,1000);
|
||||
}
|
||||
|
||||
public TextAreaOutputStream(JTextArea txtara, int maxlin) {
|
||||
if(maxlin<1) { throw new IllegalArgumentException("TextAreaOutputStream maximum lines must be positive (value="+maxlin+")"); }
|
||||
oneByte=new byte[1];
|
||||
appender=new Appender(txtara,maxlin);
|
||||
}
|
||||
|
||||
/** Clear the current console text area. */
|
||||
public synchronized void clear() {
|
||||
if(appender!=null) { appender.clear(); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
appender=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int val) {
|
||||
oneByte[0]=(byte)val;
|
||||
write(oneByte,0,1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] ba) {
|
||||
write(ba,0,ba.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] ba,int str,int len) {
|
||||
if(appender!=null) { appender.append(bytesToString(ba,str,len)); }
|
||||
}
|
||||
|
||||
//@edu.umd.cs.findbugs.annotations.SuppressWarnings("DM_DEFAULT_ENCODING")
|
||||
static private String bytesToString(byte[] ba, int str, int len) {
|
||||
try { return new String(ba,str,len,"UTF-8"); } catch(UnsupportedEncodingException thr) { return new String(ba,str,len); } // all JVMs are required to support UTF-8
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// STATIC MEMBERS
|
||||
// *************************************************************************************************
|
||||
|
||||
static class Appender
|
||||
implements Runnable
|
||||
{
|
||||
private final JTextArea textArea;
|
||||
private final int maxLines; // maximum lines allowed in text area
|
||||
private final LinkedList<Integer> lengths; // length of lines within text area
|
||||
private final List<String> values; // values waiting to be appended
|
||||
|
||||
private int curLength; // length of current line
|
||||
private boolean clear;
|
||||
private boolean queue;
|
||||
|
||||
Appender(JTextArea txtara, int maxlin) {
|
||||
textArea =txtara;
|
||||
maxLines =maxlin;
|
||||
lengths =new LinkedList<>();
|
||||
values =new ArrayList<>();
|
||||
|
||||
curLength=0;
|
||||
clear =false;
|
||||
queue =true;
|
||||
}
|
||||
|
||||
synchronized void append(String val) {
|
||||
values.add(val);
|
||||
if(queue) { queue=false; EventQueue.invokeLater(this); }
|
||||
}
|
||||
|
||||
synchronized void clear() {
|
||||
clear=true;
|
||||
curLength=0;
|
||||
lengths.clear();
|
||||
values.clear();
|
||||
if(queue) { queue=false; EventQueue.invokeLater(this); }
|
||||
}
|
||||
|
||||
// MUST BE THE ONLY METHOD THAT TOUCHES textArea!
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
if(clear) { textArea.setText(""); }
|
||||
values.stream().map((val) -> {
|
||||
curLength+=val.length();
|
||||
return val;
|
||||
}).map((val) -> {
|
||||
if(val.endsWith(EOL1) || val.endsWith(EOL2)) {
|
||||
if(lengths.size()>=maxLines) { textArea.replaceRange("",0,lengths.removeFirst()); }
|
||||
lengths.addLast(curLength);
|
||||
curLength=0;
|
||||
}
|
||||
return val;
|
||||
}).forEach((val) -> {
|
||||
textArea.append(val);
|
||||
});
|
||||
values.clear();
|
||||
clear =false;
|
||||
queue =true;
|
||||
}
|
||||
|
||||
static private final String EOL1="\n";
|
||||
static private final String EOL2=System.getProperty("line.separator",EOL1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright 2017 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import javax.swing.*;
|
||||
/**
|
||||
*
|
||||
* @author Lawrence Dol
|
||||
*/
|
||||
public class TextAreaOutputStream extends OutputStream {
|
||||
|
||||
// *************************************************************************************************
|
||||
// INSTANCE MEMBERS
|
||||
// *************************************************************************************************
|
||||
|
||||
private byte[] oneByte; // array for write(int val);
|
||||
private Appender appender; // most recent action
|
||||
|
||||
public TextAreaOutputStream(JTextArea txtara) {
|
||||
this(txtara,1000);
|
||||
}
|
||||
|
||||
public TextAreaOutputStream(JTextArea txtara, int maxlin) {
|
||||
if(maxlin<1) { throw new IllegalArgumentException("TextAreaOutputStream maximum lines must be positive (value="+maxlin+")"); }
|
||||
oneByte=new byte[1];
|
||||
appender=new Appender(txtara,maxlin);
|
||||
}
|
||||
|
||||
/** Clear the current console text area. */
|
||||
public synchronized void clear() {
|
||||
if(appender!=null) { appender.clear(); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
appender=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flush() {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int val) {
|
||||
oneByte[0]=(byte)val;
|
||||
write(oneByte,0,1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] ba) {
|
||||
write(ba,0,ba.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] ba,int str,int len) {
|
||||
if(appender!=null) { appender.append(bytesToString(ba,str,len)); }
|
||||
}
|
||||
|
||||
//@edu.umd.cs.findbugs.annotations.SuppressWarnings("DM_DEFAULT_ENCODING")
|
||||
static private String bytesToString(byte[] ba, int str, int len) {
|
||||
try {
|
||||
return new String(ba,str,len,"UTF-8");
|
||||
} catch(UnsupportedEncodingException thr) {
|
||||
return new String(ba,str,len);
|
||||
} // all JVMs are required to support UTF-8
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// STATIC MEMBERS
|
||||
// *************************************************************************************************
|
||||
|
||||
static class Appender
|
||||
implements Runnable
|
||||
{
|
||||
static private final String EOL1="\n";
|
||||
static private final String EOL2=System.getProperty("line.separator",EOL1);
|
||||
|
||||
private final JTextArea textArea;
|
||||
private final int maxLines; // maximum lines allowed in text area
|
||||
private final LinkedList<Integer> lengths; // length of lines within text area
|
||||
private final List<String> values; // values waiting to be appended
|
||||
|
||||
private int curLength; // length of current line
|
||||
private boolean clear;
|
||||
private boolean queue;
|
||||
|
||||
Appender(JTextArea txtara, int maxlin) {
|
||||
textArea =txtara;
|
||||
maxLines =maxlin;
|
||||
lengths =new LinkedList<>();
|
||||
values =new ArrayList<>();
|
||||
|
||||
curLength=0;
|
||||
clear =false;
|
||||
queue =true;
|
||||
}
|
||||
|
||||
private synchronized void append(String val) {
|
||||
values.add(val);
|
||||
if(queue) {
|
||||
queue=false;
|
||||
EventQueue.invokeLater(this);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void clear() {
|
||||
clear=true;
|
||||
curLength=0;
|
||||
lengths.clear();
|
||||
values.clear();
|
||||
if(queue) {
|
||||
queue=false;
|
||||
EventQueue.invokeLater(this);
|
||||
}
|
||||
}
|
||||
|
||||
// MUST BE THE ONLY METHOD THAT TOUCHES textArea!
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
if(clear) { textArea.setText(""); }
|
||||
values.stream().map((val) -> {
|
||||
curLength+=val.length();
|
||||
return val;
|
||||
}).map((val) -> {
|
||||
if(val.endsWith(EOL1) || val.endsWith(EOL2)) {
|
||||
if(lengths.size()>=maxLines) { textArea.replaceRange("",0,lengths.removeFirst()); }
|
||||
lengths.addLast(curLength);
|
||||
curLength=0;
|
||||
}
|
||||
return val;
|
||||
}).forEach((val) -> {
|
||||
textArea.append(val);
|
||||
});
|
||||
values.clear();
|
||||
clear =false;
|
||||
queue =true;
|
||||
}
|
||||
}
|
||||
} /* END PUBLIC CLASS */
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.playlist;
|
||||
|
||||
import com.jagrosh.jmusicbot.BotConfig;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class PlaylistLoader
|
||||
{
|
||||
private final BotConfig config;
|
||||
|
||||
public PlaylistLoader(BotConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public List<String> getPlaylistNames()
|
||||
{
|
||||
if(folderExists())
|
||||
{
|
||||
File folder = new File(OtherUtil.getPath(config.getPlaylistsFolder()).toString());
|
||||
return Arrays.asList(folder.listFiles((pathname) -> pathname.getName().endsWith(".txt")))
|
||||
.stream().map(f -> f.getName().substring(0,f.getName().length()-4)).collect(Collectors.toList());
|
||||
}
|
||||
else
|
||||
{
|
||||
createFolder();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public void createFolder()
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.createDirectory(OtherUtil.getPath(config.getPlaylistsFolder()));
|
||||
}
|
||||
catch (IOException ignore) {}
|
||||
}
|
||||
|
||||
public boolean folderExists()
|
||||
{
|
||||
return Files.exists(OtherUtil.getPath(config.getPlaylistsFolder()));
|
||||
}
|
||||
|
||||
public void createPlaylist(String name) throws IOException
|
||||
{
|
||||
Files.createFile(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt"));
|
||||
}
|
||||
|
||||
public void deletePlaylist(String name) throws IOException
|
||||
{
|
||||
Files.delete(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt"));
|
||||
}
|
||||
|
||||
public void writePlaylist(String name, String text) throws IOException
|
||||
{
|
||||
Files.write(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt"), text.trim().getBytes());
|
||||
}
|
||||
|
||||
public Playlist getPlaylist(String name)
|
||||
{
|
||||
if(!getPlaylistNames().contains(name))
|
||||
return null;
|
||||
try
|
||||
{
|
||||
if(folderExists())
|
||||
{
|
||||
boolean[] shuffle = {false};
|
||||
List<String> list = new ArrayList<>();
|
||||
Files.readAllLines(OtherUtil.getPath(config.getPlaylistsFolder()+File.separator+name+".txt")).forEach(str ->
|
||||
{
|
||||
String s = str.trim();
|
||||
if(s.isEmpty())
|
||||
return;
|
||||
if(s.startsWith("#") || s.startsWith("//"))
|
||||
{
|
||||
s = s.replaceAll("\\s+", "");
|
||||
if(s.equalsIgnoreCase("#shuffle") || s.equalsIgnoreCase("//shuffle"))
|
||||
shuffle[0]=true;
|
||||
}
|
||||
else
|
||||
list.add(s);
|
||||
});
|
||||
if(shuffle[0])
|
||||
shuffle(list);
|
||||
return new Playlist(name, list, shuffle[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
createFolder();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static <T> void shuffle(List<T> list)
|
||||
{
|
||||
for(int first =0; first<list.size(); first++)
|
||||
{
|
||||
int second = (int)(Math.random()*list.size());
|
||||
T tmp = list.get(first);
|
||||
list.set(first, list.get(second));
|
||||
list.set(second, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Playlist
|
||||
{
|
||||
private final String name;
|
||||
private final List<String> items;
|
||||
private final boolean shuffle;
|
||||
private final List<AudioTrack> tracks = new LinkedList<>();
|
||||
private final List<PlaylistLoadError> errors = new LinkedList<>();
|
||||
private boolean loaded = false;
|
||||
|
||||
private Playlist(String name, List<String> items, boolean shuffle)
|
||||
{
|
||||
this.name = name;
|
||||
this.items = items;
|
||||
this.shuffle = shuffle;
|
||||
}
|
||||
|
||||
public void loadTracks(AudioPlayerManager manager, Consumer<AudioTrack> consumer, Runnable callback)
|
||||
{
|
||||
if(loaded)
|
||||
return;
|
||||
loaded = true;
|
||||
for(int i=0; i<items.size(); i++)
|
||||
{
|
||||
boolean last = i+1 == items.size();
|
||||
int index = i;
|
||||
manager.loadItemOrdered(name, items.get(i), new AudioLoadResultHandler()
|
||||
{
|
||||
private void done()
|
||||
{
|
||||
if(last)
|
||||
{
|
||||
if(shuffle)
|
||||
shuffleTracks();
|
||||
if(callback != null)
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trackLoaded(AudioTrack at)
|
||||
{
|
||||
if(config.isTooLong(at))
|
||||
errors.add(new PlaylistLoadError(index, items.get(index), "This track is longer than the allowed maximum"));
|
||||
else
|
||||
{
|
||||
at.setUserData(0L);
|
||||
tracks.add(at);
|
||||
consumer.accept(at);
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playlistLoaded(AudioPlaylist ap)
|
||||
{
|
||||
if(ap.isSearchResult())
|
||||
{
|
||||
trackLoaded(ap.getTracks().get(0));
|
||||
}
|
||||
else if(ap.getSelectedTrack()!=null)
|
||||
{
|
||||
trackLoaded(ap.getSelectedTrack());
|
||||
}
|
||||
else
|
||||
{
|
||||
List<AudioTrack> loaded = new ArrayList<>(ap.getTracks());
|
||||
if(shuffle)
|
||||
for(int first =0; first<loaded.size(); first++)
|
||||
{
|
||||
int second = (int)(Math.random()*loaded.size());
|
||||
AudioTrack tmp = loaded.get(first);
|
||||
loaded.set(first, loaded.get(second));
|
||||
loaded.set(second, tmp);
|
||||
}
|
||||
loaded.removeIf(track -> config.isTooLong(track));
|
||||
loaded.forEach(at -> at.setUserData(0L));
|
||||
tracks.addAll(loaded);
|
||||
loaded.forEach(at -> consumer.accept(at));
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noMatches()
|
||||
{
|
||||
errors.add(new PlaylistLoadError(index, items.get(index), "No matches found."));
|
||||
done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFailed(FriendlyException fe)
|
||||
{
|
||||
errors.add(new PlaylistLoadError(index, items.get(index), "Failed to load track: "+fe.getLocalizedMessage()));
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffleTracks()
|
||||
{
|
||||
shuffle(tracks);
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<String> getItems()
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
public List<AudioTrack> getTracks()
|
||||
{
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public List<PlaylistLoadError> getErrors()
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlaylistLoadError
|
||||
{
|
||||
private final int number;
|
||||
private final String item;
|
||||
private final String reason;
|
||||
|
||||
private PlaylistLoadError(int number, String item, String reason)
|
||||
{
|
||||
this.number = number;
|
||||
this.item = item;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public int getIndex()
|
||||
{
|
||||
return number;
|
||||
}
|
||||
|
||||
public String getItem()
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
public String getReason()
|
||||
{
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
-108
@@ -1,108 +1,130 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.queue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (jagrosh)
|
||||
*/
|
||||
public class FairQueue<T extends Queueable> {
|
||||
private final List<T> list = new ArrayList<>();
|
||||
private final Set<String> set = new HashSet<>();
|
||||
|
||||
public int add(T item)
|
||||
{
|
||||
int lastIndex;
|
||||
for(lastIndex=list.size()-1; lastIndex>-1; lastIndex--)
|
||||
if(list.get(lastIndex).getIdentifier().equals(item.getIdentifier()))
|
||||
break;
|
||||
lastIndex++;
|
||||
set.clear();
|
||||
for(; lastIndex<list.size(); lastIndex++)
|
||||
{
|
||||
if(set.contains(list.get(lastIndex).getIdentifier()))
|
||||
break;
|
||||
set.add(list.get(lastIndex).getIdentifier());
|
||||
}
|
||||
list.add(lastIndex, item);
|
||||
return lastIndex;
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
public T pull()
|
||||
{
|
||||
return list.remove(0);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
public List<T> getList()
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
public T get(int index)
|
||||
{
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
public T remove(int index)
|
||||
{
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
public int shuffle(String identifier)
|
||||
{
|
||||
List<Integer> iset = new ArrayList<>();
|
||||
for(int i=0; i<list.size(); i++)
|
||||
{
|
||||
if(list.get(i).getIdentifier().equals(identifier))
|
||||
iset.add(i);
|
||||
}
|
||||
for(int j=0; j<iset.size(); j++)
|
||||
{
|
||||
int first = iset.get(j);
|
||||
int second = iset.get((int)(Math.random()*iset.size()));
|
||||
T temp = list.get(first);
|
||||
list.set(first, list.get(second));
|
||||
list.set(second, temp);
|
||||
}
|
||||
return iset.size();
|
||||
}
|
||||
|
||||
public void skip(int number)
|
||||
{
|
||||
for(int i=0; i<number; i++)
|
||||
list.remove(0);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2022 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class AbstractQueue<T extends Queueable>
|
||||
{
|
||||
protected AbstractQueue(AbstractQueue<T> queue)
|
||||
{
|
||||
this.list = queue != null ? queue.getList() : new LinkedList<>();
|
||||
}
|
||||
|
||||
protected final List<T> list;
|
||||
|
||||
public abstract int add(T item);
|
||||
|
||||
public void addAt(int index, T item)
|
||||
{
|
||||
if(index >= list.size())
|
||||
list.add(item);
|
||||
else
|
||||
list.add(index, item);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
public T pull() {
|
||||
return list.remove(0);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
public List<T> getList()
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
public T get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
public T remove(int index)
|
||||
{
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
public int removeAll(long identifier)
|
||||
{
|
||||
int count = 0;
|
||||
for(int i=list.size()-1; i>=0; i--)
|
||||
{
|
||||
if(list.get(i).getIdentifier()==identifier)
|
||||
{
|
||||
list.remove(i);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
public int shuffle(long identifier)
|
||||
{
|
||||
List<Integer> iset = new ArrayList<>();
|
||||
for(int i=0; i<list.size(); i++)
|
||||
{
|
||||
if(list.get(i).getIdentifier()==identifier)
|
||||
iset.add(i);
|
||||
}
|
||||
for(int j=0; j<iset.size(); j++)
|
||||
{
|
||||
int first = iset.get(j);
|
||||
int second = iset.get((int)(Math.random()*iset.size()));
|
||||
T temp = list.get(first);
|
||||
list.set(first, list.get(second));
|
||||
list.set(second, temp);
|
||||
}
|
||||
return iset.size();
|
||||
}
|
||||
|
||||
public void skip(int number)
|
||||
{
|
||||
if (number > 0) {
|
||||
list.subList(0, number).clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item to a different position in the list
|
||||
* @param from The position of the item
|
||||
* @param to The new position of the item
|
||||
* @return the moved item
|
||||
*/
|
||||
public T moveItem(int from, int to)
|
||||
{
|
||||
T item = list.remove(from);
|
||||
list.add(to, item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (jagrosh)
|
||||
* @param <T>
|
||||
*/
|
||||
public class FairQueue<T extends Queueable> extends AbstractQueue<T>
|
||||
{
|
||||
public FairQueue(AbstractQueue<T> queue)
|
||||
{
|
||||
super(queue);
|
||||
}
|
||||
|
||||
protected final Set<Long> set = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public int add(T item)
|
||||
{
|
||||
int lastIndex;
|
||||
for(lastIndex=list.size()-1; lastIndex>-1; lastIndex--)
|
||||
if(list.get(lastIndex).getIdentifier() == item.getIdentifier())
|
||||
break;
|
||||
lastIndex++;
|
||||
set.clear();
|
||||
for(; lastIndex<list.size(); lastIndex++)
|
||||
{
|
||||
if(set.contains(list.get(lastIndex).getIdentifier()))
|
||||
break;
|
||||
set.add(list.get(lastIndex).getIdentifier());
|
||||
}
|
||||
list.add(lastIndex, item);
|
||||
return lastIndex;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
* @param <T>
|
||||
*/
|
||||
public class LinearQueue<T extends Queueable> extends AbstractQueue<T>
|
||||
{
|
||||
public LinearQueue(AbstractQueue<T> queue)
|
||||
{
|
||||
super(queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int add(T item)
|
||||
{
|
||||
list.add(item);
|
||||
return list.size() - 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QueueSupplier
|
||||
{
|
||||
<T extends Queueable> AbstractQueue<T> apply(AbstractQueue<T> queue);
|
||||
}
|
||||
+25
-25
@@ -1,25 +1,25 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.queue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public interface Queueable {
|
||||
|
||||
public String getIdentifier();
|
||||
}
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public interface Queueable {
|
||||
|
||||
public long getIdentifier();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.settings;
|
||||
|
||||
import com.jagrosh.jmusicbot.queue.AbstractQueue;
|
||||
import com.jagrosh.jmusicbot.queue.FairQueue;
|
||||
import com.jagrosh.jmusicbot.queue.LinearQueue;
|
||||
import com.jagrosh.jmusicbot.queue.Queueable;
|
||||
import com.jagrosh.jmusicbot.queue.QueueSupplier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
*/
|
||||
public enum QueueType
|
||||
{
|
||||
LINEAR("\u23E9", "Linear", LinearQueue::new), // ⏩
|
||||
FAIR("\uD83D\uDD22", "Fair", FairQueue::new); // 🔢
|
||||
|
||||
private final String userFriendlyName;
|
||||
private final String emoji;
|
||||
private final QueueSupplier supplier;
|
||||
|
||||
QueueType(final String emoji, final String userFriendlyName, QueueSupplier supplier)
|
||||
{
|
||||
this.userFriendlyName = userFriendlyName;
|
||||
this.emoji = emoji;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public static List<String> getNames()
|
||||
{
|
||||
return Arrays.stream(QueueType.values())
|
||||
.map(type -> type.name().toLowerCase())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public <T extends Queueable> AbstractQueue<T> createInstance(AbstractQueue<T> previous)
|
||||
{
|
||||
return supplier.apply(previous);
|
||||
}
|
||||
|
||||
public String getUserFriendlyName()
|
||||
{
|
||||
return userFriendlyName;
|
||||
}
|
||||
|
||||
public String getEmoji()
|
||||
{
|
||||
return emoji;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.settings;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Michaili K
|
||||
*/
|
||||
public enum RepeatMode
|
||||
{
|
||||
OFF(null, "Off"),
|
||||
ALL("\uD83D\uDD01", "All"), // 🔁
|
||||
SINGLE("\uD83D\uDD02", "Single"); // 🔂
|
||||
|
||||
private final String emoji;
|
||||
private final String userFriendlyName;
|
||||
|
||||
private RepeatMode(String emoji, String userFriendlyName)
|
||||
{
|
||||
this.emoji = emoji;
|
||||
this.userFriendlyName = userFriendlyName;
|
||||
}
|
||||
|
||||
public String getEmoji()
|
||||
{
|
||||
return emoji;
|
||||
}
|
||||
|
||||
public String getUserFriendlyName()
|
||||
{
|
||||
return userFriendlyName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.settings;
|
||||
|
||||
import com.jagrosh.jdautilities.command.GuildSettingsProvider;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class Settings implements GuildSettingsProvider
|
||||
{
|
||||
private final SettingsManager manager;
|
||||
protected long textId;
|
||||
protected long voiceId;
|
||||
protected long roleId;
|
||||
private int volume;
|
||||
private String defaultPlaylist;
|
||||
private RepeatMode repeatMode;
|
||||
private QueueType queueType;
|
||||
private String prefix;
|
||||
private double skipRatio;
|
||||
|
||||
public Settings(SettingsManager manager, String textId, String voiceId, String roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio, QueueType queueType)
|
||||
{
|
||||
this.manager = manager;
|
||||
try
|
||||
{
|
||||
this.textId = Long.parseLong(textId);
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
this.textId = 0;
|
||||
}
|
||||
try
|
||||
{
|
||||
this.voiceId = Long.parseLong(voiceId);
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
this.voiceId = 0;
|
||||
}
|
||||
try
|
||||
{
|
||||
this.roleId = Long.parseLong(roleId);
|
||||
}
|
||||
catch(NumberFormatException e)
|
||||
{
|
||||
this.roleId = 0;
|
||||
}
|
||||
this.volume = volume;
|
||||
this.defaultPlaylist = defaultPlaylist;
|
||||
this.repeatMode = repeatMode;
|
||||
this.prefix = prefix;
|
||||
this.skipRatio = skipRatio;
|
||||
this.queueType = queueType;
|
||||
}
|
||||
|
||||
public Settings(SettingsManager manager, long textId, long voiceId, long roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio, QueueType queueType)
|
||||
{
|
||||
this.manager = manager;
|
||||
this.textId = textId;
|
||||
this.voiceId = voiceId;
|
||||
this.roleId = roleId;
|
||||
this.volume = volume;
|
||||
this.defaultPlaylist = defaultPlaylist;
|
||||
this.repeatMode = repeatMode;
|
||||
this.prefix = prefix;
|
||||
this.skipRatio = skipRatio;
|
||||
this.queueType = queueType;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public TextChannel getTextChannel(Guild guild)
|
||||
{
|
||||
return guild == null ? null : guild.getTextChannelById(textId);
|
||||
}
|
||||
|
||||
public VoiceChannel getVoiceChannel(Guild guild)
|
||||
{
|
||||
return guild == null ? null : guild.getVoiceChannelById(voiceId);
|
||||
}
|
||||
|
||||
public Role getRole(Guild guild)
|
||||
{
|
||||
return guild == null ? null : guild.getRoleById(roleId);
|
||||
}
|
||||
|
||||
public int getVolume()
|
||||
{
|
||||
return volume;
|
||||
}
|
||||
|
||||
public String getDefaultPlaylist()
|
||||
{
|
||||
return defaultPlaylist;
|
||||
}
|
||||
|
||||
public RepeatMode getRepeatMode()
|
||||
{
|
||||
return repeatMode;
|
||||
}
|
||||
|
||||
public String getPrefix()
|
||||
{
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public double getSkipRatio()
|
||||
{
|
||||
return skipRatio;
|
||||
}
|
||||
|
||||
public QueueType getQueueType()
|
||||
{
|
||||
return queueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPrefixes()
|
||||
{
|
||||
return prefix == null ? Collections.emptySet() : Collections.singleton(prefix);
|
||||
}
|
||||
|
||||
// Setters
|
||||
public void setTextChannel(TextChannel tc)
|
||||
{
|
||||
this.textId = tc == null ? 0 : tc.getIdLong();
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setVoiceChannel(VoiceChannel vc)
|
||||
{
|
||||
this.voiceId = vc == null ? 0 : vc.getIdLong();
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setDJRole(Role role)
|
||||
{
|
||||
this.roleId = role == null ? 0 : role.getIdLong();
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setVolume(int volume)
|
||||
{
|
||||
this.volume = volume;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setDefaultPlaylist(String defaultPlaylist)
|
||||
{
|
||||
this.defaultPlaylist = defaultPlaylist;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setRepeatMode(RepeatMode mode)
|
||||
{
|
||||
this.repeatMode = mode;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setSkipRatio(double skipRatio)
|
||||
{
|
||||
this.skipRatio = skipRatio;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setQueueType(QueueType queueType)
|
||||
{
|
||||
this.queueType = queueType;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.settings;
|
||||
|
||||
import com.jagrosh.jdautilities.command.GuildSettingsManager;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.HashMap;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class SettingsManager implements GuildSettingsManager<Settings>
|
||||
{
|
||||
private final static Logger LOG = LoggerFactory.getLogger("Settings");
|
||||
private final static String SETTINGS_FILE = "serversettings.json";
|
||||
private final HashMap<Long,Settings> settings;
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
this.settings = new HashMap<>();
|
||||
|
||||
try {
|
||||
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(OtherUtil.getPath(SETTINGS_FILE))));
|
||||
loadedSettings.keySet().forEach((id) -> {
|
||||
JSONObject o = loadedSettings.getJSONObject(id);
|
||||
|
||||
// Legacy version support: On versions 0.3.3 and older, the repeat mode was represented as a boolean.
|
||||
if (!o.has("repeat_mode") && o.has("repeat") && o.getBoolean("repeat"))
|
||||
o.put("repeat_mode", RepeatMode.ALL);
|
||||
|
||||
|
||||
settings.put(Long.parseLong(id), new Settings(this,
|
||||
o.has("text_channel_id") ? o.getString("text_channel_id") : null,
|
||||
o.has("voice_channel_id")? o.getString("voice_channel_id") : null,
|
||||
o.has("dj_role_id") ? o.getString("dj_role_id") : null,
|
||||
o.has("volume") ? o.getInt("volume") : 100,
|
||||
o.has("default_playlist")? o.getString("default_playlist") : null,
|
||||
o.has("repeat_mode") ? o.getEnum(RepeatMode.class, "repeat_mode"): RepeatMode.OFF,
|
||||
o.has("prefix") ? o.getString("prefix") : null,
|
||||
o.has("skip_ratio") ? o.getDouble("skip_ratio") : -1,
|
||||
o.has("queue_type") ? o.getEnum(QueueType.class, "queue_type") : QueueType.FAIR));
|
||||
});
|
||||
} catch (NoSuchFileException e) {
|
||||
// create an empty json file
|
||||
try {
|
||||
LOG.info("serversettings.json will be created in " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
|
||||
Files.write(OtherUtil.getPath("serversettings.json"), new JSONObject().toString(4).getBytes());
|
||||
} catch(IOException ex) {
|
||||
LOG.warn("Failed to create new settings file: "+ex);
|
||||
}
|
||||
return;
|
||||
} catch(IOException | JSONException e) {
|
||||
LOG.warn("Failed to load server settings: "+e);
|
||||
}
|
||||
|
||||
LOG.info("serversettings.json loaded from " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets non-null settings for a Guild
|
||||
*
|
||||
* @param guild the guild to get settings for
|
||||
* @return the existing settings, or new settings for that guild
|
||||
*/
|
||||
@Override
|
||||
public Settings getSettings(Guild guild)
|
||||
{
|
||||
return getSettings(guild.getIdLong());
|
||||
}
|
||||
|
||||
public Settings getSettings(long guildId)
|
||||
{
|
||||
return settings.computeIfAbsent(guildId, id -> createDefaultSettings());
|
||||
}
|
||||
|
||||
private Settings createDefaultSettings()
|
||||
{
|
||||
return new Settings(this, 0, 0, 0, 100, null, RepeatMode.OFF, null, -1, QueueType.FAIR);
|
||||
}
|
||||
|
||||
protected void writeSettings()
|
||||
{
|
||||
JSONObject obj = new JSONObject();
|
||||
settings.keySet().stream().forEach(key -> {
|
||||
JSONObject o = new JSONObject();
|
||||
Settings s = settings.get(key);
|
||||
if(s.textId!=0)
|
||||
o.put("text_channel_id", Long.toString(s.textId));
|
||||
if(s.voiceId!=0)
|
||||
o.put("voice_channel_id", Long.toString(s.voiceId));
|
||||
if(s.roleId!=0)
|
||||
o.put("dj_role_id", Long.toString(s.roleId));
|
||||
if(s.getVolume()!=100)
|
||||
o.put("volume",s.getVolume());
|
||||
if(s.getDefaultPlaylist() != null)
|
||||
o.put("default_playlist", s.getDefaultPlaylist());
|
||||
if(s.getRepeatMode()!=RepeatMode.OFF)
|
||||
o.put("repeat_mode", s.getRepeatMode());
|
||||
if(s.getPrefix() != null)
|
||||
o.put("prefix", s.getPrefix());
|
||||
if(s.getSkipRatio() != -1)
|
||||
o.put("skip_ratio", s.getSkipRatio());
|
||||
if(s.getQueueType() != QueueType.FAIR)
|
||||
o.put("queue_type", s.getQueueType().name());
|
||||
obj.put(Long.toString(key), o);
|
||||
});
|
||||
try {
|
||||
Files.write(OtherUtil.getPath(SETTINGS_FILE), obj.toString(4).getBytes());
|
||||
} catch(IOException ex){
|
||||
LOG.warn("Failed to write to file: "+ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata.UserInfo;
|
||||
import java.util.List;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class FormatUtil {
|
||||
|
||||
public static String formatUsername(String username, String discrim)
|
||||
{
|
||||
if(discrim == null || discrim.equals("0000"))
|
||||
{
|
||||
return username;
|
||||
}
|
||||
else
|
||||
{
|
||||
return username + "#" + discrim;
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatUsername(UserInfo userinfo)
|
||||
{
|
||||
return formatUsername(userinfo.username, userinfo.discrim);
|
||||
}
|
||||
|
||||
public static String formatUsername(User user)
|
||||
{
|
||||
return formatUsername(user.getName(), user.getDiscriminator());
|
||||
}
|
||||
|
||||
public static String progressBar(double percent)
|
||||
{
|
||||
String str = "";
|
||||
for(int i=0; i<12; i++)
|
||||
if(i == (int)(percent*12))
|
||||
str+="\uD83D\uDD18"; // 🔘
|
||||
else
|
||||
str+="▬";
|
||||
return str;
|
||||
}
|
||||
|
||||
public static String volumeIcon(int volume)
|
||||
{
|
||||
if(volume == 0)
|
||||
return "\uD83D\uDD07"; // 🔇
|
||||
if(volume < 30)
|
||||
return "\uD83D\uDD08"; // 🔈
|
||||
if(volume < 70)
|
||||
return "\uD83D\uDD09"; // 🔉
|
||||
return "\uD83D\uDD0A"; // 🔊
|
||||
}
|
||||
|
||||
public static String listOfTChannels(List<TextChannel> list, String query)
|
||||
{
|
||||
String out = " Multiple text channels found matching \""+query+"\":";
|
||||
for(int i=0; i<6 && i<list.size(); i++)
|
||||
out+="\n - "+list.get(i).getName()+" (<#"+list.get(i).getId()+">)";
|
||||
if(list.size()>6)
|
||||
out+="\n**And "+(list.size()-6)+" more...**";
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String listOfVChannels(List<VoiceChannel> list, String query)
|
||||
{
|
||||
String out = " Multiple voice channels found matching \""+query+"\":";
|
||||
for(int i=0; i<6 && i<list.size(); i++)
|
||||
out+="\n - "+list.get(i).getAsMention()+" (ID:"+list.get(i).getId()+")";
|
||||
if(list.size()>6)
|
||||
out+="\n**And "+(list.size()-6)+" more...**";
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String listOfRoles(List<Role> list, String query)
|
||||
{
|
||||
String out = " Multiple roles found matching \""+query+"\":";
|
||||
for(int i=0; i<6 && i<list.size(); i++)
|
||||
out+="\n - "+list.get(i).getName()+" (ID:"+list.get(i).getId()+")";
|
||||
if(list.size()>6)
|
||||
out+="\n**And "+(list.size()-6)+" more...**";
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String filter(String input)
|
||||
{
|
||||
return input.replace("\u202E","")
|
||||
.replace("@everyone", "@\u0435veryone") // cyrillic letter e
|
||||
.replace("@here", "@h\u0435re") // cyrillic letter e
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2018 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
import com.jagrosh.jmusicbot.JMusicBot;
|
||||
import com.jagrosh.jmusicbot.entities.Prompt;
|
||||
import java.io.*;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.ApplicationInfo;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import okhttp3.*;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONTokener;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class OtherUtil
|
||||
{
|
||||
public final static String NEW_VERSION_AVAILABLE = "There is a new version of JMusicBot available!\n"
|
||||
+ "Current version: %s\n"
|
||||
+ "New Version: %s\n\n"
|
||||
+ "Please visit https://github.com/chrisb09/MusicBot/releases to get the latest release."
|
||||
+ "If you are using docker (or docker-compose), please pull the new image with:"
|
||||
+ "Docker: docker pull chrisb09/jmusicbot"
|
||||
+ "Docker-compose: docker-compose pull"+
|
||||
" and don't forget to restart the container.";
|
||||
private final static String WINDOWS_INVALID_PATH = "c:\\windows\\system32\\";
|
||||
|
||||
/**
|
||||
* gets a Path from a String
|
||||
* also fixes the windows tendency to try to start in system32
|
||||
* any time the bot tries to access this path, it will instead start in the location of the jar file
|
||||
*
|
||||
* @param path the string path
|
||||
* @return the Path object
|
||||
*/
|
||||
public static Path getPath(String path)
|
||||
{
|
||||
Path result = Paths.get(path);
|
||||
// special logic to prevent trying to access system32
|
||||
if(result.toAbsolutePath().toString().toLowerCase().startsWith(WINDOWS_INVALID_PATH))
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Paths.get(new File(JMusicBot.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath() + File.separator + path);
|
||||
}
|
||||
catch(URISyntaxException ignored) {}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a resource from the jar as a string
|
||||
*
|
||||
* @param clazz class base object
|
||||
* @param name name of resource
|
||||
* @return string containing the contents of the resource
|
||||
*/
|
||||
public static String loadResource(Object clazz, String name)
|
||||
{
|
||||
try(BufferedReader reader = new BufferedReader(new InputStreamReader(clazz.getClass().getResourceAsStream(name))))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
reader.lines().forEach(line -> sb.append("\r\n").append(line));
|
||||
return sb.toString().trim();
|
||||
}
|
||||
catch(IOException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads image data from a URL
|
||||
*
|
||||
* @param url url of image
|
||||
* @return inputstream of url
|
||||
*/
|
||||
public static InputStream imageFromUrl(String url)
|
||||
{
|
||||
if(url==null)
|
||||
return null;
|
||||
try
|
||||
{
|
||||
URL u = new URL(url);
|
||||
URLConnection urlConnection = u.openConnection();
|
||||
urlConnection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");
|
||||
return urlConnection.getInputStream();
|
||||
}
|
||||
catch(IOException | IllegalArgumentException ignore) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an activity from a string
|
||||
*
|
||||
* @param game the game, including the action such as 'playing' or 'watching'
|
||||
* @return the parsed activity
|
||||
*/
|
||||
public static Activity parseGame(String game)
|
||||
{
|
||||
if(game==null || game.trim().isEmpty() || game.trim().equalsIgnoreCase("default"))
|
||||
return null;
|
||||
String lower = game.toLowerCase();
|
||||
if(lower.startsWith("playing"))
|
||||
return Activity.playing(makeNonEmpty(game.substring(7).trim()));
|
||||
if(lower.startsWith("listening to"))
|
||||
return Activity.listening(makeNonEmpty(game.substring(12).trim()));
|
||||
if(lower.startsWith("listening"))
|
||||
return Activity.listening(makeNonEmpty(game.substring(9).trim()));
|
||||
if(lower.startsWith("watching"))
|
||||
return Activity.watching(makeNonEmpty(game.substring(8).trim()));
|
||||
if(lower.startsWith("streaming"))
|
||||
{
|
||||
String[] parts = game.substring(9).trim().split("\\s+", 2);
|
||||
if(parts.length == 2)
|
||||
{
|
||||
return Activity.streaming(makeNonEmpty(parts[1]), "https://twitch.tv/"+parts[0]);
|
||||
}
|
||||
}
|
||||
return Activity.playing(game);
|
||||
}
|
||||
|
||||
public static String makeNonEmpty(String str)
|
||||
{
|
||||
return str == null || str.isEmpty() ? "\u200B" : str;
|
||||
}
|
||||
|
||||
public static OnlineStatus parseStatus(String status)
|
||||
{
|
||||
if(status==null || status.trim().isEmpty())
|
||||
return OnlineStatus.ONLINE;
|
||||
OnlineStatus st = OnlineStatus.fromKey(status);
|
||||
return st == null ? OnlineStatus.ONLINE : st;
|
||||
}
|
||||
|
||||
public static void checkJavaVersion(Prompt prompt)
|
||||
{
|
||||
if(!System.getProperty("java.vm.name").contains("64"))
|
||||
prompt.alert(Prompt.Level.WARNING, "Java Version",
|
||||
"It appears that you may not be using a supported Java version. Please use 64-bit java.");
|
||||
}
|
||||
|
||||
public static void checkVersion(Prompt prompt)
|
||||
{
|
||||
// Get current version number
|
||||
String version = getCurrentVersion();
|
||||
|
||||
// Check for new version
|
||||
String latestVersion = getLatestVersion();
|
||||
|
||||
if(latestVersion!=null && !latestVersion.equals(version))
|
||||
{
|
||||
prompt.alert(Prompt.Level.WARNING, "JMusicBot Version", String.format(NEW_VERSION_AVAILABLE, version, latestVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public static String getCurrentVersion()
|
||||
{
|
||||
if(JMusicBot.class.getPackage()!=null && JMusicBot.class.getPackage().getImplementationVersion()!=null)
|
||||
return JMusicBot.class.getPackage().getImplementationVersion();
|
||||
else
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
public static String getLatestVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
Response response = new OkHttpClient.Builder().build()
|
||||
.newCall(new Request.Builder().get().url("https://api.github.com/repos/chrisb09/MusicBot/releases/latest").build())
|
||||
.execute();
|
||||
ResponseBody body = response.body();
|
||||
if(body != null)
|
||||
{
|
||||
try(Reader reader = body.charStream())
|
||||
{
|
||||
JSONObject obj = new JSONObject(new JSONTokener(reader));
|
||||
return obj.getString("tag_name");
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
catch(IOException | JSONException | NullPointerException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the bot JMusicBot is being run on is supported & returns the reason if it is not.
|
||||
* @return A string with the reason, or null if it is supported.
|
||||
*/
|
||||
public static String getUnsupportedBotReason(JDA jda)
|
||||
{
|
||||
if (jda.getSelfUser().getFlags().contains(User.UserFlag.VERIFIED_BOT))
|
||||
return "The bot is verified. Using JMusicBot in a verified bot is not supported.";
|
||||
|
||||
ApplicationInfo info = jda.retrieveApplicationInfo().complete();
|
||||
if (info.isBotPublic())
|
||||
return "\"Public Bot\" is enabled. Using JMusicBot as a public bot is not supported. Please disable it in the "
|
||||
+ "Developer Dashboard at https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot ."
|
||||
+ "You may also need to disable all Installation Contexts at https://discord.com/developers/applications/"
|
||||
+ jda.getSelfUser().getId() + "/installation .";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
public class TimeUtil
|
||||
{
|
||||
|
||||
public static String formatTime(long duration)
|
||||
{
|
||||
if(duration == Long.MAX_VALUE)
|
||||
return "LIVE";
|
||||
long seconds = Math.round(duration/1000.0);
|
||||
long hours = seconds/(60*60);
|
||||
seconds %= 60*60;
|
||||
long minutes = seconds/60;
|
||||
seconds %= 60;
|
||||
return (hours>0 ? hours+":" : "") + (minutes<10 ? "0"+minutes : minutes) + ":" + (seconds<10 ? "0"+seconds : seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a seek time string into milliseconds and determines if it's relative.
|
||||
* Supports "colon time" (HH:MM:SS) or "unit time" (1h20m)
|
||||
* @param args time string
|
||||
* @return SeekTime object, or null if the string could not be parsed
|
||||
*/
|
||||
public static SeekTime parseTime(String args)
|
||||
{
|
||||
if (args.length() == 0) return null;
|
||||
String timestamp = args;
|
||||
boolean relative = false; // seek forward or backward
|
||||
boolean isSeekingBackwards = false;
|
||||
char first = timestamp.charAt(0);
|
||||
if (first == '+' || first == '-')
|
||||
{
|
||||
relative = true;
|
||||
isSeekingBackwards = first == '-';
|
||||
timestamp = timestamp.substring(1);
|
||||
}
|
||||
|
||||
long milliseconds = parseColonTime(timestamp);
|
||||
if(milliseconds == -1) milliseconds = parseUnitTime(timestamp);
|
||||
if(milliseconds == -1) return null;
|
||||
|
||||
milliseconds *= isSeekingBackwards ? -1 : 1;
|
||||
|
||||
return new SeekTime(milliseconds, relative);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timestamp timestamp formatted as: [+ | -] <HH:MM:SS | MM:SS | SS>
|
||||
* @return Time in milliseconds
|
||||
*/
|
||||
public static long parseColonTime(String timestamp)
|
||||
{
|
||||
String[] timestampSplitArray = timestamp.split(":+");
|
||||
if(timestampSplitArray.length > 3 )
|
||||
return -1;
|
||||
double[] timeUnitArray = new double[3]; // hours, minutes, seconds
|
||||
for(int index = 0; index < timestampSplitArray.length; index++)
|
||||
{
|
||||
String unit = timestampSplitArray[index];
|
||||
if (unit.startsWith("+") || unit.startsWith("-")) return -1;
|
||||
unit = unit.replace(",", ".");
|
||||
try
|
||||
{
|
||||
timeUnitArray[index + 3 - timestampSplitArray.length] = Double.parseDouble(unit);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return Math.round(timeUnitArray[0] * 3600000 + timeUnitArray[1] * 60000 + timeUnitArray[2] * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param timestr time string formatted as a unit time, e.g. 20m10, 1d5h20m14s or 1h and 20m
|
||||
* @return Time in milliseconds
|
||||
*/
|
||||
public static long parseUnitTime(String timestr)
|
||||
{
|
||||
timestr = timestr.replaceAll("(?i)(\\s|,|and)","")
|
||||
.replaceAll("(?is)(-?\\d+|[a-z]+)", "$1 ")
|
||||
.trim();
|
||||
String[] vals = timestr.split("\\s+");
|
||||
int time = 0;
|
||||
try
|
||||
{
|
||||
for(int j=0; j<vals.length; j+=2)
|
||||
{
|
||||
int num = Integer.parseInt(vals[j]);
|
||||
|
||||
if(vals.length > j+1)
|
||||
{
|
||||
if(vals[j+1].toLowerCase().startsWith("m"))
|
||||
num*=60;
|
||||
else if(vals[j+1].toLowerCase().startsWith("h"))
|
||||
num*=60*60;
|
||||
else if(vals[j+1].toLowerCase().startsWith("d"))
|
||||
num*=60*60*24;
|
||||
}
|
||||
|
||||
time+=num*1000;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
public static class SeekTime
|
||||
{
|
||||
public final long milliseconds;
|
||||
public final boolean relative;
|
||||
|
||||
private SeekTime(long milliseconds, boolean relative)
|
||||
{
|
||||
this.milliseconds = milliseconds;
|
||||
this.relative = relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.*;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
|
||||
import dev.lavalink.youtube.clients.Web;
|
||||
import org.json.JSONObject;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriverService;
|
||||
import org.openqa.selenium.chrome.ChromeOptions;
|
||||
import org.openqa.selenium.chromium.ChromiumDriverLogLevel;
|
||||
import org.openqa.selenium.devtools.NetworkInterceptor;
|
||||
import org.openqa.selenium.os.ExecutableFinder;
|
||||
import org.openqa.selenium.remote.http.Contents;
|
||||
import org.openqa.selenium.remote.http.Filter;
|
||||
import org.openqa.selenium.remote.http.HttpMethod;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||
|
||||
public class YouTubeUtil {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(YouTubeUtil.class);
|
||||
|
||||
public enum RoutingPlanner {
|
||||
NONE,
|
||||
ROTATE_ON_BAN,
|
||||
LOAD_BALANCE,
|
||||
NANO_SWITCH,
|
||||
ROTATING_NANO_SWITCH
|
||||
}
|
||||
|
||||
public static IpBlock parseIpBlock(String cidr) {
|
||||
if (Ipv6Block.isIpv6CidrBlock(cidr))
|
||||
return new Ipv6Block(cidr);
|
||||
|
||||
if (Ipv4Block.isIpv4CidrBlock(cidr))
|
||||
return new Ipv4Block(cidr);
|
||||
|
||||
throw new IllegalArgumentException("Could not parse CIDR " + cidr);
|
||||
}
|
||||
|
||||
public static AbstractRoutePlanner createRouterPlanner(RoutingPlanner routingPlanner, List<IpBlock> ipBlocks) {
|
||||
|
||||
switch (routingPlanner) {
|
||||
case NONE:
|
||||
return null;
|
||||
case ROTATE_ON_BAN:
|
||||
return new RotatingIpRoutePlanner(ipBlocks);
|
||||
case LOAD_BALANCE:
|
||||
return new BalancingIpRoutePlanner(ipBlocks);
|
||||
case NANO_SWITCH:
|
||||
return new NanoIpRoutePlanner(ipBlocks, true);
|
||||
case ROTATING_NANO_SWITCH:
|
||||
return new RotatingNanoIpRoutePlanner(ipBlocks);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown RoutingPlanner value provided");
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetPoTokenAndVisitorData(String userChromePath, String userChromeDriverPath, boolean headless)
|
||||
{
|
||||
//
|
||||
// 1. Find a Chrome install & launch the WebDriver
|
||||
//
|
||||
WebDriverManager wdm = WebDriverManager.chromedriver();
|
||||
if (userChromePath != null) {
|
||||
wdm.browserVersionDetectionCommand(userChromePath + " --version");
|
||||
}
|
||||
else if (wdm.getBrowserPath().isEmpty())
|
||||
{
|
||||
// try finding a Chromium browser instead (Linux users usually install Chromium instead of Google Chrome)
|
||||
wdm = WebDriverManager.chromiumdriver();
|
||||
if (wdm.getBrowserPath().isEmpty()) {
|
||||
LOGGER.error("Could not obtain a PO token for YouTube playback, because no Chrome browser could be found. Please install Google Chrome or Chromium!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (userChromeDriverPath == null)
|
||||
// Automatically download & set up ChromeDriver for the Chrome/Chromium version that the user has installed
|
||||
wdm.setup();
|
||||
else {
|
||||
// Find & use the ChromeDriver executable the user provided
|
||||
String path = new ExecutableFinder().find(userChromeDriverPath);
|
||||
if (path == null) {
|
||||
LOGGER.error("Could not obtain a PO token for YouTube playback, because the specified ChromeDriver could not be found. " +
|
||||
"Please check your config's value of the chromedriverpath, or set it to \"AUTO\" to have JMusicBot automatically download ChromeDriver");
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Using ChromeDriver at {}", path);
|
||||
System.setProperty("webdriver.chrome.driver", path);
|
||||
}
|
||||
|
||||
ChromeOptions chromeOptions = new ChromeOptions();
|
||||
|
||||
chromeOptions.setBinary(userChromePath != null ? userChromePath : wdm.getBrowserPath().get().toString());
|
||||
|
||||
if (!headless)
|
||||
chromeOptions.addArguments("--auto-open-devtools-for-tabs");
|
||||
else
|
||||
chromeOptions.addArguments("--headless=new");
|
||||
|
||||
ChromeDriverService.Builder chromeDriverBuilder = new ChromeDriverService.Builder();
|
||||
if (LOGGER.isDebugEnabled())
|
||||
chromeDriverBuilder.withLogLevel(ChromiumDriverLogLevel.DEBUG);
|
||||
|
||||
WebDriver driver = new ChromeDriver(chromeDriverBuilder.build(), chromeOptions);
|
||||
|
||||
//
|
||||
// 2. Setup a network interceptor to intercept the player API request & obtain po token and visitor data
|
||||
//
|
||||
|
||||
// as the network interceptor below runs on a different thread, we'll have it notify us on this/current thread
|
||||
// whether retrieving the data failed (completes with the Exception) or succeeded (completes with null)
|
||||
CompletableFuture<Exception> interceptorFuture = new CompletableFuture<>();
|
||||
|
||||
NetworkInterceptor interceptor = new NetworkInterceptor(driver,
|
||||
(Filter) next -> req -> {
|
||||
|
||||
if (req.getMethod() != HttpMethod.POST || !req.getUri().contains("/youtubei/v1/player"))
|
||||
return next.execute(req);
|
||||
|
||||
try {
|
||||
String rawBody = Contents.string(req);
|
||||
JSONObject body = new JSONObject(rawBody);
|
||||
|
||||
String visitorData = body
|
||||
.getJSONObject("context")
|
||||
.getJSONObject("client")
|
||||
.getString("visitorData");
|
||||
|
||||
String poToken = body
|
||||
.getJSONObject("serviceIntegrityDimensions")
|
||||
.getString("poToken");
|
||||
|
||||
|
||||
LOGGER.info("Successfully retrieved PO token & visitor data for YouTube playback.");
|
||||
LOGGER.debug("PO token: {}", poToken);
|
||||
LOGGER.debug("Visitor Data: {}", visitorData);
|
||||
|
||||
if (poToken.length() < 160)
|
||||
LOGGER.warn("There is a high chance that the retrieved PO token will not work. Try on a different network.");
|
||||
|
||||
Web.setPoTokenAndVisitorData(poToken, visitorData);
|
||||
|
||||
interceptorFuture.complete(null);
|
||||
} catch (Exception e) {
|
||||
interceptorFuture.complete(e);
|
||||
}
|
||||
|
||||
return next.execute(req);
|
||||
});
|
||||
|
||||
//
|
||||
// 3. Navigate to YouTube & start the player
|
||||
//
|
||||
try {
|
||||
driver.get("https://www.youtube.com/embed/aqz-KE-bpKQ");
|
||||
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(15));
|
||||
WebElement moviePlayer = driver.findElement(By.cssSelector("#movie_player"));
|
||||
moviePlayer.click();
|
||||
|
||||
try {
|
||||
// Wait for the network interceptor to tell us if it got the data
|
||||
Exception result = interceptorFuture.get(20, TimeUnit.SECONDS);
|
||||
if (result != null)
|
||||
throw new Exception("Failed to read YouTube player request body", result);
|
||||
} catch (TimeoutException e) {
|
||||
throw new Exception("Timed out waiting for YouTube player request");
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to obtain PO token for YouTube playback: {}", e.getMessage());
|
||||
LOGGER.debug("Exception:", e);
|
||||
} finally {
|
||||
interceptor.close();
|
||||
// Only quit the browser when not in headless mode. Helps with troubleshooting.
|
||||
if (headless)
|
||||
driver.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.turbo.TurboFilter;
|
||||
import ch.qos.logback.core.spi.FilterReply;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.Marker;
|
||||
|
||||
import java.nio.file.Files;
|
||||
|
||||
/**
|
||||
* A logback turbo filter, used retrieve the YouTube OAuth2 refresh token that gets logged once authorized with YouTube.
|
||||
*
|
||||
* @author Michaili K. <git@michaili.dev>
|
||||
*/
|
||||
public class YoutubeOauth2TokenHandler extends TurboFilter {
|
||||
public final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(YoutubeOauth2TokenHandler.class);
|
||||
private Data data;
|
||||
|
||||
|
||||
public void init()
|
||||
{
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
context.addTurboFilter(this);
|
||||
}
|
||||
|
||||
public Data getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t)
|
||||
{
|
||||
if (!logger.getName().equals("dev.lavalink.youtube.http.YoutubeOauth2Handler"))
|
||||
return FilterReply.NEUTRAL;
|
||||
|
||||
if (format.equals("OAUTH INTEGRATION: To give youtube-source access to your account, go to {} and enter code {}"))
|
||||
{
|
||||
this.data = new Data((String) params[0], (String) params[1]);
|
||||
return FilterReply.NEUTRAL;
|
||||
}
|
||||
if (format.equals("OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. ({})"))
|
||||
{
|
||||
LOGGER.info(
|
||||
"Authorization successful & retrieved token! Storing the token in {}",
|
||||
OtherUtil.getPath("youtubetoken.txt").toAbsolutePath()
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
Files.write(OtherUtil.getPath("youtubetoken.txt"), params[0].toString().getBytes());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error(
|
||||
"Failed to write the YouTube OAuth2 refresh token to storage! You will need to authorize again on the next reboot",
|
||||
e
|
||||
);
|
||||
}
|
||||
return FilterReply.DENY;
|
||||
}
|
||||
|
||||
return FilterReply.NEUTRAL;
|
||||
}
|
||||
|
||||
public static class Data
|
||||
{
|
||||
private final String authorisationUrl;
|
||||
private final String code;
|
||||
|
||||
private Data(String authorisationUrl, String code)
|
||||
{
|
||||
this.authorisationUrl = authorisationUrl;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getAuthorisationUrl()
|
||||
{
|
||||
return authorisationUrl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import me.jagrosh.jdautilities.commandclient.Command.Category;
|
||||
import me.jagrosh.jdautilities.commandclient.CommandEvent;
|
||||
import me.jagrosh.jdautilities.waiter.EventWaiter;
|
||||
import me.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import me.jagrosh.jmusicbot.gui.GUI;
|
||||
import me.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.core.JDA;
|
||||
import net.dv8tion.jda.core.Permission;
|
||||
import net.dv8tion.jda.core.entities.Guild;
|
||||
import net.dv8tion.jda.core.entities.Role;
|
||||
import net.dv8tion.jda.core.entities.TextChannel;
|
||||
import net.dv8tion.jda.core.entities.VoiceChannel;
|
||||
import net.dv8tion.jda.core.events.ReadyEvent;
|
||||
import net.dv8tion.jda.core.events.ShutdownEvent;
|
||||
import net.dv8tion.jda.core.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.core.utils.PermissionUtil;
|
||||
import net.dv8tion.jda.core.utils.SimpleLog;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class Bot extends ListenerAdapter {
|
||||
|
||||
private final HashMap<String,Settings> settings;
|
||||
private final AudioPlayerManager manager;
|
||||
private final EventWaiter waiter;
|
||||
private final ScheduledExecutorService threadpool;
|
||||
private JDA jda;
|
||||
private GUI gui;
|
||||
//private GuildsPanel panel;
|
||||
public final Category MUSIC = new Category("Music");
|
||||
public final Category DJ = new Category("DJ", event ->
|
||||
{
|
||||
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
|
||||
return true;
|
||||
if(event.getGuild()==null)
|
||||
return true;
|
||||
if(PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER))
|
||||
return true;
|
||||
Role dj = event.getGuild().getRoleById(getSettings(event.getGuild()).getRoleId());
|
||||
return event.getMember().getRoles().contains(dj);
|
||||
});
|
||||
public final Category ADMIN = new Category("Admin", event ->
|
||||
{
|
||||
if(event.getAuthor().getId().equals(event.getClient().getOwnerId()))
|
||||
return true;
|
||||
if(event.getGuild()==null)
|
||||
return true;
|
||||
return PermissionUtil.checkPermission(event.getGuild(), event.getMember(), Permission.MANAGE_SERVER);
|
||||
});
|
||||
public final Category OWNER = new Category("Owner");
|
||||
|
||||
public Bot(EventWaiter waiter)
|
||||
{
|
||||
this.waiter = waiter;
|
||||
this.settings = new HashMap<>();
|
||||
manager = new DefaultAudioPlayerManager();
|
||||
threadpool = Executors.newSingleThreadScheduledExecutor();
|
||||
AudioSourceManagers.registerRemoteSources(manager);
|
||||
try {
|
||||
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(Paths.get("serversettings.json"))));
|
||||
for(String id: loadedSettings.keySet())
|
||||
{
|
||||
JSONObject o = loadedSettings.getJSONObject(id);
|
||||
settings.put(id, new Settings(o.has("text_channel_id")?o.getString("text_channel_id"):null,
|
||||
o.has("voice_channel_id")?o.getString("voice_channel_id"):null,
|
||||
o.has("dj_role_id")?o.getString("dj_role_id"):null));
|
||||
}
|
||||
} catch(IOException | JSONException e) {
|
||||
SimpleLog.getLog("Settings").warn("Failed to load server settings: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
public EventWaiter getWaiter()
|
||||
{
|
||||
return waiter;
|
||||
}
|
||||
|
||||
public AudioPlayerManager getAudioManager()
|
||||
{
|
||||
return manager;
|
||||
}
|
||||
|
||||
public int queueTrack(CommandEvent event, AudioTrack track)
|
||||
{
|
||||
return setUpHandler(event).addTrack(track, event.getAuthor());
|
||||
}
|
||||
|
||||
public AudioHandler setUpHandler(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler;
|
||||
if(event.getGuild().getAudioManager().getSendingHandler()==null)
|
||||
{
|
||||
AudioPlayer player = manager.createPlayer();
|
||||
handler = new AudioHandler(player, event.getGuild());
|
||||
player.addListener(handler);
|
||||
event.getGuild().getAudioManager().setSendingHandler(handler);
|
||||
threadpool.scheduleWithFixedDelay(() -> updateTopic(event.getGuild(),handler), 0, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
else
|
||||
{
|
||||
handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void updateTopic(Guild guild, AudioHandler handler)
|
||||
{
|
||||
TextChannel tchan = guild.getTextChannelById(getSettings(guild).getTextId());
|
||||
if(tchan!=null && PermissionUtil.checkPermission(tchan, guild.getSelfMember(), Permission.MANAGE_CHANNEL))
|
||||
{
|
||||
String otherText;
|
||||
if(tchan.getTopic()==null || tchan.getTopic().isEmpty())
|
||||
otherText = "";
|
||||
else if(tchan.getTopic().contains("\u200B"))
|
||||
otherText = tchan.getTopic().substring(tchan.getTopic().indexOf("\u200B"));
|
||||
else
|
||||
otherText = "\u200B\n "+tchan.getTopic();
|
||||
String text = FormatUtil.formattedAudio(handler, guild.getJDA(), true)+otherText;
|
||||
if(!text.equals(tchan.getTopic()))
|
||||
tchan.getManager().setTopic(text).queue();
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown(){
|
||||
manager.shutdown();
|
||||
threadpool.shutdownNow();
|
||||
jda.getGuilds().stream().forEach(g -> {
|
||||
g.getAudioManager().closeAudioConnection();
|
||||
AudioHandler ah = (AudioHandler)g.getAudioManager().getSendingHandler();
|
||||
if(ah!=null)
|
||||
{
|
||||
ah.getQueue().clear();
|
||||
ah.getPlayer().destroy();
|
||||
updateTopic(g, ah);
|
||||
}
|
||||
});
|
||||
jda.shutdown();
|
||||
}
|
||||
|
||||
public void setGUI(GUI gui)
|
||||
{
|
||||
this.gui = gui;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShutdown(ShutdownEvent event) {
|
||||
if(gui!=null)
|
||||
gui.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(ReadyEvent event) {
|
||||
this.jda = event.getJDA();
|
||||
//if(panel!=null)
|
||||
// panel.updateList(event.getJDA().getGuilds());
|
||||
}
|
||||
|
||||
|
||||
// settings
|
||||
|
||||
public Settings getSettings(Guild guild)
|
||||
{
|
||||
return settings.getOrDefault(guild.getId(), Settings.DEFAULT_SETTINGS);
|
||||
}
|
||||
|
||||
public void setTextChannel(TextChannel channel)
|
||||
{
|
||||
Settings s = settings.get(channel.getGuild().getId());
|
||||
if(s==null)
|
||||
{
|
||||
settings.put(channel.getGuild().getId(), new Settings(channel.getId(),null,null));
|
||||
}
|
||||
else
|
||||
{
|
||||
s.setTextId(channel.getId());
|
||||
}
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
public void setVoiceChannel(VoiceChannel channel)
|
||||
{
|
||||
Settings s = settings.get(channel.getGuild().getId());
|
||||
if(s==null)
|
||||
{
|
||||
settings.put(channel.getGuild().getId(), new Settings(null,channel.getId(),null));
|
||||
}
|
||||
else
|
||||
{
|
||||
s.setVoiceId(channel.getId());
|
||||
}
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
public void setRole(Role role)
|
||||
{
|
||||
Settings s = settings.get(role.getGuild().getId());
|
||||
if(s==null)
|
||||
{
|
||||
settings.put(role.getGuild().getId(), new Settings(null,null,role.getId()));
|
||||
}
|
||||
else
|
||||
{
|
||||
s.setRoleId(role.getId());
|
||||
}
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
public void clearTextChannel(Guild guild)
|
||||
{
|
||||
Settings s = getSettings(guild);
|
||||
if(s!=null)
|
||||
{
|
||||
if(s.getVoiceId()==null && s.getRoleId()==null)
|
||||
settings.remove(guild.getId());
|
||||
else
|
||||
s.setTextId(null);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearVoiceChannel(Guild guild)
|
||||
{
|
||||
Settings s = getSettings(guild);
|
||||
if(s!=null)
|
||||
{
|
||||
if(s.getTextId()==null && s.getRoleId()==null)
|
||||
settings.remove(guild.getId());
|
||||
else
|
||||
s.setVoiceId(null);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearRole(Guild guild)
|
||||
{
|
||||
Settings s = getSettings(guild);
|
||||
if(s!=null)
|
||||
{
|
||||
if(s.getVoiceId()==null && s.getTextId()==null)
|
||||
settings.remove(guild.getId());
|
||||
else
|
||||
s.setRoleId(null);
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSettings()
|
||||
{
|
||||
JSONObject obj = new JSONObject();
|
||||
settings.keySet().stream().forEach(key -> {
|
||||
JSONObject o = new JSONObject();
|
||||
Settings s = settings.get(key);
|
||||
if(s.getTextId()!=null)
|
||||
o.put("text_channel_id", s.getTextId());
|
||||
if(s.getVoiceId()!=null)
|
||||
o.put("voice_channel_id", s.getVoiceId());
|
||||
if(s.getRoleId()!=null)
|
||||
o.put("dj_role_id", s.getRoleId());
|
||||
obj.put(key, o);
|
||||
});
|
||||
try {
|
||||
Files.write(Paths.get("serversettings.json"), obj.toString(4).getBytes());
|
||||
} catch(IOException ex){
|
||||
SimpleLog.getLog("Settings").warn("Failed to write to file: "+ex);
|
||||
}
|
||||
}
|
||||
|
||||
//gui stuff
|
||||
/*public void registerPanel(GuildsPanel panel)
|
||||
{
|
||||
this.panel = panel;
|
||||
threadpool.scheduleWithFixedDelay(() -> updatePanel(), 0, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void updatePanel()
|
||||
{
|
||||
System.out.println("updating...");
|
||||
Guild guild = jda.getGuilds().get(panel.getIndex());
|
||||
panel.updatePanel((AudioHandler)guild.getAudioManager().getSendingHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildJoin(GuildJoinEvent event) {
|
||||
if(panel!=null)
|
||||
panel.updateList(event.getJDA().getGuilds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildLeave(GuildLeaveEvent event) {
|
||||
if(panel!=null)
|
||||
panel.updateList(event.getJDA().getGuilds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShutdown(ShutdownEvent event) {
|
||||
((GUI)panel.getTopLevelAncestor()).dispose();
|
||||
}*/
|
||||
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh (jagrosh)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (jagrosh)
|
||||
*/
|
||||
public class Config {
|
||||
private final boolean nogui;
|
||||
private String prefix;
|
||||
private String token;
|
||||
private String owner;
|
||||
private String success;
|
||||
private String warning;
|
||||
private String error;
|
||||
|
||||
public Config(boolean nogui)
|
||||
{
|
||||
this.nogui = nogui;
|
||||
List<String> lines;
|
||||
try {
|
||||
lines = Files.readAllLines(Paths.get("config.txt"));
|
||||
for(String line: lines)
|
||||
{
|
||||
String[] parts = line.split("=",2);
|
||||
String key = parts[0].trim().toLowerCase();
|
||||
String value = parts.length>1 ? parts[1].trim() : null;
|
||||
switch(key)
|
||||
{
|
||||
case "token":
|
||||
token = value;
|
||||
break;
|
||||
case "prefix":
|
||||
prefix = value;
|
||||
break;
|
||||
case "owner":
|
||||
owner = value;
|
||||
break;
|
||||
case "success":
|
||||
success = value;
|
||||
break;
|
||||
case "warning":
|
||||
warning = value;
|
||||
break;
|
||||
case "error":
|
||||
error = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
alert("'config.txt' was not found!");
|
||||
lines = new LinkedList<>();
|
||||
}
|
||||
boolean write = false;
|
||||
if(token==null || token.isEmpty())
|
||||
{
|
||||
token = prompt("Please provide a bot token."
|
||||
+ "\nInstructions for obtaining a token can be found here:"
|
||||
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Getting-a-Bot-Token."
|
||||
+ "\nBot Token: ");
|
||||
if(token==null)
|
||||
{
|
||||
alert("No token provided! Exiting.");
|
||||
System.exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.add("token="+token);
|
||||
write = true;
|
||||
}
|
||||
}
|
||||
if(owner==null || !owner.matches("\\d{17,20}"))
|
||||
{
|
||||
owner = prompt("Owner ID was missing, or the provided owner ID is not valid."
|
||||
+ "\nPlease provide the User ID of the bot's owner."
|
||||
+ "\nInstructions for obtaining your User ID can be found here:"
|
||||
+ "\nhttps://github.com/jagrosh/MusicBot/wiki/Finding-Your-User-ID"
|
||||
+ "\nOwner User ID: ");
|
||||
if(owner==null || !owner.matches("\\d{17,20}"))
|
||||
{
|
||||
alert("Invalid User ID! Exiting.");
|
||||
System.exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.add("owner="+owner);
|
||||
write = true;
|
||||
}
|
||||
}
|
||||
if(write)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
lines.stream().forEach(s -> builder.append(s).append("\r\n"));
|
||||
try {
|
||||
Files.write(Paths.get("config.txt"), builder.toString().trim().getBytes());
|
||||
} catch(IOException ex) {
|
||||
alert("Failed to write new config options to config.txt: "+ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrefix()
|
||||
{
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getToken()
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getOwnerId()
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
|
||||
public String getSuccess()
|
||||
{
|
||||
return success==null ? "\uD83C\uDFB6" : success;
|
||||
}
|
||||
|
||||
public String getWarning()
|
||||
{
|
||||
return warning==null ? "\uD83D\uDCA1" : warning;
|
||||
}
|
||||
|
||||
public String getError()
|
||||
{
|
||||
return error==null ? "\uD83D\uDEAB" : error;
|
||||
}
|
||||
|
||||
private void alert(String message)
|
||||
{
|
||||
if(nogui)
|
||||
System.out.println("[WARNING] "+message);
|
||||
else
|
||||
{
|
||||
JOptionPane.showMessageDialog(null, message, "JMusicBot", JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private String prompt(String content)
|
||||
{
|
||||
if(nogui)
|
||||
{
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.println(content);
|
||||
return scanner.next();
|
||||
}
|
||||
else
|
||||
{
|
||||
return JOptionPane.showInputDialog(null, content, "JMusicBot", JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot;
|
||||
|
||||
import java.awt.Color;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import me.jagrosh.jdautilities.commandclient.CommandClient;
|
||||
import me.jagrosh.jdautilities.commandclient.CommandClientBuilder;
|
||||
import me.jagrosh.jdautilities.commandclient.examples.*;
|
||||
import me.jagrosh.jdautilities.waiter.EventWaiter;
|
||||
import me.jagrosh.jmusicbot.commands.*;
|
||||
import me.jagrosh.jmusicbot.gui.GUI;
|
||||
import net.dv8tion.jda.core.AccountType;
|
||||
import net.dv8tion.jda.core.JDABuilder;
|
||||
import net.dv8tion.jda.core.OnlineStatus;
|
||||
import net.dv8tion.jda.core.Permission;
|
||||
import net.dv8tion.jda.core.entities.Game;
|
||||
import net.dv8tion.jda.core.exceptions.RateLimitedException;
|
||||
import net.dv8tion.jda.core.utils.SimpleLog;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (jagrosh)
|
||||
*/
|
||||
public class JMusicBot {
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args){
|
||||
// check run mode(s)
|
||||
boolean nogui = false;
|
||||
for(String arg: args)
|
||||
if("-nogui".equalsIgnoreCase(arg))
|
||||
nogui = true;
|
||||
|
||||
// load config
|
||||
Config config = new Config(nogui);
|
||||
|
||||
// set up the listener
|
||||
EventWaiter waiter = new EventWaiter();
|
||||
Bot bot = new Bot(waiter);
|
||||
|
||||
AboutCommand.IS_AUTHOR = false;
|
||||
|
||||
// set up the command client
|
||||
CommandClient client = new CommandClientBuilder()
|
||||
.setPrefix(config.getPrefix())
|
||||
.setOwnerId(config.getOwnerId())
|
||||
.setEmojis(config.getSuccess(), config.getWarning(), config.getError())
|
||||
.addCommands(
|
||||
new AboutCommand(Color.BLUE.brighter(),
|
||||
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot)",
|
||||
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
|
||||
Permission.MESSAGE_READ, Permission.MESSAGE_WRITE, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
|
||||
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI,
|
||||
Permission.MANAGE_CHANNEL, Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE),
|
||||
new PingCommand(),
|
||||
new SettingsCmd(bot),
|
||||
|
||||
new NowplayingCmd(bot),
|
||||
new PlayCmd(bot),
|
||||
new QueueCmd(bot),
|
||||
new RemoveCmd(bot),
|
||||
new SearchCmd(bot),
|
||||
new ShuffleCmd(bot),
|
||||
new SkipCmd(bot),
|
||||
|
||||
new ForceskipCmd(bot),
|
||||
new SkiptoCmd(bot),
|
||||
new StopCmd(bot),
|
||||
new VolumeCmd(bot),
|
||||
|
||||
new SetdjCmd(bot),
|
||||
new SettcCmd(bot),
|
||||
new SetvcCmd(bot),
|
||||
|
||||
new GuildlistCommand(waiter),
|
||||
new SetavatarCmd(bot),
|
||||
new SetgameCmd(bot),
|
||||
new SetnameCmd(bot),
|
||||
new ShutdownCmd(bot)
|
||||
).build();
|
||||
|
||||
|
||||
if(!nogui)
|
||||
{
|
||||
GUI gui = new GUI(bot);
|
||||
bot.setGUI(gui);
|
||||
gui.init();
|
||||
}
|
||||
|
||||
// attempt to log in and start
|
||||
try {
|
||||
new JDABuilder(AccountType.BOT)
|
||||
.setToken(config.getToken())
|
||||
.setAudioEnabled(true)
|
||||
.setGame(Game.of("loading..."))
|
||||
.setStatus(OnlineStatus.DO_NOT_DISTURB)
|
||||
.addListener(client)
|
||||
.addListener(waiter)
|
||||
.addListener(bot)
|
||||
.buildAsync();
|
||||
} catch (LoginException | IllegalArgumentException | RateLimitedException ex) {
|
||||
SimpleLog.getLog("Login").fatal(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class Settings {
|
||||
public final static Settings DEFAULT_SETTINGS = new Settings(null, null, null);
|
||||
|
||||
private String textId;
|
||||
private String voiceId;
|
||||
private String roleId;
|
||||
|
||||
public Settings(String textId, String voiceId, String roleId)
|
||||
{
|
||||
this.textId = textId;
|
||||
this.voiceId = voiceId;
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public String getTextId()
|
||||
{
|
||||
return textId;
|
||||
}
|
||||
|
||||
public String getVoiceId()
|
||||
{
|
||||
return voiceId;
|
||||
}
|
||||
|
||||
public String getRoleId()
|
||||
{
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setTextId(String id)
|
||||
{
|
||||
this.textId = id;
|
||||
}
|
||||
|
||||
public void setVoiceId(String id)
|
||||
{
|
||||
this.voiceId = id;
|
||||
}
|
||||
|
||||
public void setRoleId(String id)
|
||||
{
|
||||
this.roleId = id;
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
||||
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import me.jagrosh.jmusicbot.queue.FairQueue;
|
||||
import net.dv8tion.jda.core.audio.AudioSendHandler;
|
||||
import net.dv8tion.jda.core.entities.Guild;
|
||||
import net.dv8tion.jda.core.entities.User;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler {
|
||||
private final AudioPlayer audioPlayer;
|
||||
private final Guild guild;
|
||||
private final FairQueue<QueuedTrack> queue;
|
||||
private final Set<String> votes;
|
||||
private AudioFrame lastFrame;
|
||||
private QueuedTrack current;
|
||||
|
||||
public AudioHandler(AudioPlayer audioPlayer, Guild guild) {
|
||||
this.audioPlayer = audioPlayer;
|
||||
this.guild = guild;
|
||||
queue = new FairQueue<>();
|
||||
votes = new HashSet<>();
|
||||
}
|
||||
|
||||
public int addTrack(AudioTrack track, User user)
|
||||
{
|
||||
QueuedTrack qt = new QueuedTrack(track, user.getId());
|
||||
if(current==null)
|
||||
{
|
||||
current = qt;
|
||||
audioPlayer.playTrack(track);
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return queue.add(qt);
|
||||
}
|
||||
}
|
||||
|
||||
public QueuedTrack getCurrentTrack()
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
public FairQueue<QueuedTrack> getQueue()
|
||||
{
|
||||
return queue;
|
||||
}
|
||||
|
||||
public Set<String> getVotes()
|
||||
{
|
||||
return votes;
|
||||
}
|
||||
|
||||
public AudioPlayer getPlayer()
|
||||
{
|
||||
return audioPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
|
||||
if(queue.isEmpty())
|
||||
{
|
||||
current = null;
|
||||
guild.getAudioManager().closeAudioConnection();
|
||||
}
|
||||
else
|
||||
{
|
||||
current = queue.pull();
|
||||
player.playTrack(current.getTrack());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackStart(AudioPlayer player, AudioTrack track) {
|
||||
votes.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProvide() {
|
||||
lastFrame = audioPlayer.provide();
|
||||
return lastFrame != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] provide20MsAudio() {
|
||||
return lastFrame.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpus() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 129 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user