mirror of
https://github.com/chrisb09/MusicBot.git
synced 2026-11-05 17:02:35 -05:00
Compare commits
14
Commits
chewutils-fix
...
0.6.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f95b50af89 | ||
|
|
1e6904d329 | ||
|
|
5ff7b2f28e | ||
|
|
28dfdcf680 | ||
|
|
c5073b8755 | ||
|
|
5501f02a97 | ||
|
|
66e207eabe | ||
|
|
db3a57d517 | ||
|
|
81fa3fed25 | ||
|
|
9484c373ff | ||
|
|
860822d520 | ||
|
|
a0c313047f | ||
|
|
db69ea6787 | ||
|
|
5dba2c355b |
No files matched your search
+2
-1
@@ -5,4 +5,5 @@
|
||||
*.json
|
||||
*.txt
|
||||
nb*.xml
|
||||
dockerfile
|
||||
dockerfile
|
||||
.*
|
||||
@@ -1,147 +1,122 @@
|
||||
<img align="right" src="https://i.imgur.com/zrE80HY.png" height="200" width="200">
|
||||
|
||||
# JMusicBot Fork
|
||||
|
||||
A self-hosted Discord music bot fork based on JMusicBot, updated to keep working on modern Discord and JDA versions.
|
||||
|
||||
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.
|
||||
|
||||
## Demo
|
||||
|
||||
If you only want to temporarily join the demo server, use this link: [https://discord.gg/4cp9CvjwaW](https://discord.gg/4cp9CvjwaW)
|
||||
Temporary invite: [discord.gg/4cp9CvjwaW](https://discord.gg/4cp9CvjwaW)
|
||||
Permanent invite: [discord.gg/hK95396uB3](https://discord.gg/hK95396uB3)
|
||||
|
||||
If you want to join the demo server permanently, use this link: [https://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
|
||||
|
||||
# This fork
|
||||
## Quick Links
|
||||
|
||||
We had to update to JDA 6, switch to jda-chewtils to make the bot work again.
|
||||
Also, we somewhat regularly bump [lavaplayer](https://mvnrepository.com/artifact/dev.arbjerg/lavaplayer)/[lavalink](https://maven.lavalink.dev/#/releases/dev/lavalink/youtube/common) to their new versions. I will try to update this automatically in the future.
|
||||
- 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)
|
||||
|
||||
Additionally, this fork includes the following PRs from other people:
|
||||
## Features
|
||||
|
||||
### Support for logging into your (burner) youtube account:
|
||||
- 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
|
||||
|
||||
[PR-1670](https://github.com/jagrosh/MusicBot/pull/1670)
|
||||
## Supported Sources And Formats
|
||||
|
||||
### To allow building the project from source
|
||||
[PR-1703](https://github.com/jagrosh/MusicBot/pull/1703)
|
||||
JMusicBot supports all sources and formats supported by [lavaplayer](https://github.com/sedmelluq/lavaplayer#supported-formats).
|
||||
|
||||
### Add YouTube PO token & IP rotation
|
||||
https://github.com/jagrosh/MusicBot/pull/1772
|
||||
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
|
||||
|
||||
### For PO_Tokens: In your bot folder, edit "config.txt" and add
|
||||
```
|
||||
ytpotoken = "PO_TOKEN_HERE"
|
||||
ytvisitordata = "VISITOR_DATA_HERE"
|
||||
```
|
||||
## Setup Notes
|
||||
|
||||
To get po_token and visitor_data, use [youtube-trusted-session-generator](https://github.com/iv-org/youtube-trusted-session-generator)
|
||||
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.
|
||||
|
||||
If you have problems with this fork, you could also check out [this other fork](https://github.com/SeVile/MusicBot).
|
||||
For the detailed version, see [docs/setup.md](/git/MusicBot/docs/setup.md).
|
||||
|
||||
## Requirements for 0.5+ and above
|
||||
## Docker
|
||||
|
||||
Since we updated JDA from version 4 to 6 to fix various issues, we had to enable the MESSAGE_CONTENT intent. If you let your bot join more than 100 servers(guilds), you now need to enable this intent in the [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
More information can be found in the [JDA documentation](https://jda.wiki/using-jda/gateway-intents-and-member-cache-policy/).
|
||||
Note, however, that this is a privileged intent and will require a valid use-case for your bot to be verified in over 75 servers (it will only show up in the portal when on at least 76 servers according to the documentation).
|
||||
A Docker image is published here: [docker.io/chrisb09/jmusicbot](https://hub.docker.com/r/chrisb09/jmusicbot)
|
||||
|
||||
## Easy deployment via docker
|
||||
For compose examples and deployment notes, see [docs/docker.md](/git/MusicBot/docs/docker.md).
|
||||
|
||||
Additionally, i thought a corresponding docker image would make things better:
|
||||
## YouTube Login
|
||||
|
||||
The image can be found on [dockerhub](https://hub.docker.com/repository/docker/chrisb09/jmusicbot/general)
|
||||
This fork supports Google account login for YouTube playback workarounds.
|
||||
|
||||
Docker-compose:
|
||||
|
||||
```yaml
|
||||
version: '2.2'
|
||||
|
||||
services:
|
||||
jmusicbot:
|
||||
image: chrisb09/jmusicbot:latest
|
||||
container_name: jmusicbot
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config:/jmb/config
|
||||
```
|
||||
|
||||
although, obviously you should change `./config` to whatever path is right for your setup.
|
||||
Source code for the docker image is also on [Github](https://github.com/chrisb09/jmb-container).
|
||||
|
||||
### Windows
|
||||
|
||||
If you want to run the bot on a windows host without docker, you obviously need to install java, and if you want to run it in the background something like [nssm](http://nssm.cc/) can be helpful.
|
||||
|
||||
|
||||
## YouTube-Login
|
||||
|
||||
Obviously you still need to log in if you want to use youtube properly, hence you need to enable this in the config.
|
||||
If you are using a pre-existing config, simply add:
|
||||
```
|
||||
/ If you set this to true, the bot will use a Google account to play YouTube tracks.
|
||||
// This is only needed if you are experiencing issues with YouTube playback.
|
||||
// Once enabled, instructions for login will be written to the console and DMs.
|
||||
// DO NOT use your main Google account! Use an alternative account.
|
||||
Enable:
|
||||
|
||||
```conf
|
||||
youtubeoauth2=true
|
||||
```
|
||||
|
||||
If you start with a clean slate this line should already be present, albeit with `false` being the default.
|
||||
As the author of the PR reiterates multiple times, do not use your main google account as using your account for a bot is most certainly against the ToS and thus you are endangering said account.
|
||||
Do not use your main Google account. Use a burner or otherwise disposable account instead.
|
||||
|
||||
To add the account you simply have to follow the very basic instructions printed either in the console or sent to you from the bot via discord after you have started it, open the linked google page and paste the code.
|
||||
More details are in [docs/setup.md](/git/MusicBot/docs/setup.md).
|
||||
|
||||
# JMusicBot
|
||||
If you use PO tokens or visitor data for YouTube, those details are also covered in [docs/setup.md](/git/MusicBot/docs/setup.md).
|
||||
|
||||
[](https://github.com/jagrosh/MusicBot/releases/latest)
|
||||
[](https://github.com/jagrosh/MusicBot/stargazers)
|
||||
[](https://github.com/jagrosh/MusicBot/releases/latest)
|
||||
[](https://github.com/jagrosh/MusicBot/blob/master/LICENSE)
|
||||
[](https://discord.gg/0p9LSGoRLu6Pet0k)<br>
|
||||
[](https://dl.circleci.com/status-badge/redirect/gh/jagrosh/MusicBot/tree/master)
|
||||
[](https://github.com/jagrosh/MusicBot/actions/workflows/build-and-test.yml)
|
||||
[](https://www.codefactor.io/repository/github/jagrosh/musicbot)
|
||||
## DAVE / Voice Encryption
|
||||
|
||||
A cross-platform Discord music bot with a clean interface, and that is easy to set up and run yourself!
|
||||
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.
|
||||
|
||||
[](https://jmusicbot.com/setup)
|
||||
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.
|
||||
|
||||
## Features
|
||||
* Easy to run (just make sure Java is installed, and run!)
|
||||
* Fast loading of songs
|
||||
* No external keys needed (besides a Discord Bot token)
|
||||
* Smooth playback
|
||||
* Server-specific setup for the "DJ" role that can moderate the music
|
||||
* Clean and beautiful menus
|
||||
* Supports many sites, including Youtube, Soundcloud, and more
|
||||
* Supports many online radio/streams
|
||||
* Supports local files
|
||||
* Playlist support (both web/youtube, and local)
|
||||
## Upstream And Related Work
|
||||
|
||||
## Supported sources and formats
|
||||
JMusicBot supports all sources and formats supported by [lavaplayer](https://github.com/sedmelluq/lavaplayer#supported-formats):
|
||||
### Sources
|
||||
* YouTube
|
||||
* SoundCloud
|
||||
* Bandcamp
|
||||
* Vimeo
|
||||
* Twitch streams
|
||||
* Local files
|
||||
* HTTP URLs
|
||||
### Formats
|
||||
* MP3
|
||||
* FLAC
|
||||
* WAV
|
||||
* Matroska/WebM (AAC, Opus or Vorbis codecs)
|
||||
* MP4/M4A (AAC codec)
|
||||
* OGG streams (Opus, Vorbis and FLAC codecs)
|
||||
* AAC streams
|
||||
* Stream playlists (M3U and PLS)
|
||||
This fork incorporates ideas and patches related to:
|
||||
|
||||
## Example
|
||||

|
||||
- [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)
|
||||
|
||||
## Setup
|
||||
Please see the [Setup Page](https://jmusicbot.com/setup) to run this bot yourself!
|
||||
If this fork does not fit your needs, another fork you may want to inspect is [SeVile/MusicBot](https://github.com/SeVile/MusicBot).
|
||||
|
||||
## Questions/Suggestions/Bug Reports
|
||||
**Please read the [Issues List](https://github.com/jagrosh/MusicBot/issues) before suggesting a feature**. If you have a question, need troubleshooting help, or want to brainstorm a new feature, please start a [Discussion](https://github.com/jagrosh/MusicBot/discussions). If you'd like to suggest a feature or report a reproducible bug, please open an [Issue](https://github.com/jagrosh/MusicBot/issues) on this repository. If you like this bot, be sure to add a star to the libraries that make this possible: [**JDA**](https://github.com/DV8FromTheWorld/JDA) and [**lavaplayer**](https://github.com/sedmelluq/lavaplayer)!
|
||||
## Questions And Contributions
|
||||
|
||||
## Editing
|
||||
This bot (and the source code here) might not be easy to edit for inexperienced programmers. The main purpose of having the source public is to show the capabilities of the libraries, to allow others to understand how the bot works, and to allow those knowledgeable about java, JDA, and Discord bot development to contribute. There are many requirements and dependencies required to edit and compile it, and there will not be support provided for people looking to make changes on their own. Instead, consider making a feature request (see the above section). If you choose to make edits, please do so in accordance with the Apache 2.0 License.
|
||||
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).
|
||||
@@ -3,10 +3,15 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.jagrosh</groupId>
|
||||
<artifactId>JMusicBot</artifactId>
|
||||
<version>0.5.1</version>
|
||||
<version>0.6.5</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>
|
||||
@@ -14,14 +19,17 @@
|
||||
</repository>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<name>bintray</name>
|
||||
<url>https://jcenter.bintray.com</url>
|
||||
<name>Maven Central</name>
|
||||
<url>https://repo.maven.apache.org/maven2</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
<snapshots>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
<updatePolicy>always</updatePolicy>
|
||||
</snapshots>
|
||||
</repository>
|
||||
@@ -47,15 +55,98 @@
|
||||
<dependency>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
<version>6.1.1</version>
|
||||
<version>6.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.chrisb09</groupId>
|
||||
<artifactId>JDA-Chewtils</artifactId>
|
||||
<!-- <version>fix~components-v2-SNAPSHOT</version> -->
|
||||
<version>jda-6.1.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<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>${jda.chewtils.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>pw.chew</groupId>
|
||||
<artifactId>jda-chewtils-commons</artifactId>
|
||||
<version>${jda.chewtils.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>pw.chew</groupId>
|
||||
<artifactId>jda-chewtils-examples</artifactId>
|
||||
<version>${jda.chewtils.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>pw.chew</groupId>
|
||||
<artifactId>jda-chewtils-menu</artifactId>
|
||||
<version>${jda.chewtils.version}</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>
|
||||
@@ -70,12 +161,12 @@
|
||||
<dependency>
|
||||
<groupId>dev.lavalink.youtube</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.16.0</version>
|
||||
<version>1.18.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.jagrosh</groupId>
|
||||
<artifactId>JLyrics</artifactId>
|
||||
<version>master-SNAPSHOT</version>
|
||||
<version>master</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.dunctebot</groupId>
|
||||
@@ -89,6 +180,16 @@
|
||||
<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>
|
||||
@@ -97,7 +198,7 @@
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.15.3</version>
|
||||
<version>1.14.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
@@ -143,6 +244,16 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/filtered-resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -186,6 +297,7 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<jda.chewtils.version>2.2.1</jda.chewtils.version>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
jda-chewtils.version=${jda.chewtils.version}
|
||||
@@ -22,6 +22,8 @@ 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;
|
||||
@@ -44,7 +46,9 @@ public class Bot
|
||||
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;
|
||||
@@ -64,8 +68,23 @@ public class Bot
|
||||
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()
|
||||
@@ -103,11 +122,21 @@ public class Bot
|
||||
return nowplaying;
|
||||
}
|
||||
|
||||
public StatusMessageHandler getStatusMessageHandler()
|
||||
{
|
||||
return statusMessageHandler;
|
||||
}
|
||||
|
||||
public AloneInVoiceHandler getAloneInVoiceHandler()
|
||||
{
|
||||
return aloneInVoiceHandler;
|
||||
}
|
||||
|
||||
public DataLogService getDataLogService()
|
||||
{
|
||||
return dataLogService;
|
||||
}
|
||||
|
||||
public YoutubeOauth2TokenHandler getYouTubeOauth2Handler()
|
||||
{
|
||||
return youTubeOauth2TokenHandler;
|
||||
@@ -154,6 +183,8 @@ public class Bot
|
||||
}
|
||||
if(gui!=null)
|
||||
gui.dispose();
|
||||
if(dataLogService != null)
|
||||
dataLogService.shutdown();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ public class BotConfig
|
||||
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;
|
||||
@@ -94,6 +97,7 @@ public class BotConfig
|
||||
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");
|
||||
@@ -112,6 +116,10 @@ public class BotConfig
|
||||
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;
|
||||
@@ -260,6 +268,26 @@ public class BotConfig
|
||||
{
|
||||
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()
|
||||
{
|
||||
@@ -320,6 +348,11 @@ public class BotConfig
|
||||
{
|
||||
return songInGame;
|
||||
}
|
||||
|
||||
public boolean useStatusMessages()
|
||||
{
|
||||
return statusMessages;
|
||||
}
|
||||
|
||||
public String getPlaylistsFolder()
|
||||
{
|
||||
|
||||
@@ -18,7 +18,8 @@ package com.jagrosh.jmusicbot;
|
||||
import com.jagrosh.jdautilities.command.CommandClient;
|
||||
import com.jagrosh.jdautilities.command.CommandClientBuilder;
|
||||
import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
|
||||
import com.jagrosh.jdautilities.examples.command.*;
|
||||
import com.jagrosh.jmusicbot.commands.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.*;
|
||||
@@ -28,10 +29,13 @@ 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;
|
||||
@@ -124,6 +128,7 @@ public class JMusicBot
|
||||
.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();
|
||||
@@ -149,6 +154,12 @@ public class JMusicBot
|
||||
+ "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 "
|
||||
@@ -164,12 +175,28 @@ public class JMusicBot
|
||||
|
||||
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
|
||||
AboutCommand aboutCommand = new AboutCommand(Color.BLUE.brighter(),
|
||||
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot) (v" + OtherUtil.getCurrentVersion() + ")",
|
||||
LoggedAboutCmd aboutCommand = new LoggedAboutCmd(bot, Color.BLUE.brighter(),
|
||||
"a music bot that is [easy to host yourself!](https://github.com/chrisb09/MusicBot) (v" + OtherUtil.getCurrentVersion() + ")",
|
||||
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
|
||||
RECOMMENDED_PERMS);
|
||||
aboutCommand.setIsAuthor(false);
|
||||
@@ -185,7 +212,7 @@ public class JMusicBot
|
||||
.setLinkedCacheSize(200)
|
||||
.setGuildSettingsManager(settings)
|
||||
.addCommands(aboutCommand,
|
||||
new PingCommand(),
|
||||
new LoggedPingCmd(bot),
|
||||
new SettingsCmd(bot),
|
||||
|
||||
new LyricsCmd(bot),
|
||||
|
||||
@@ -28,6 +28,7 @@ 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;
|
||||
@@ -108,14 +109,24 @@ public class Listener extends ListenerAdapter
|
||||
@Override
|
||||
public void onMessageDelete(MessageDeleteEvent event)
|
||||
{
|
||||
if(event.isFromGuild())
|
||||
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
|
||||
|
||||
@@ -40,7 +40,6 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
|
||||
import net.dv8tion.jda.api.audio.AudioSendHandler;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -158,10 +157,13 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
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())
|
||||
@@ -174,6 +176,8 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
@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)
|
||||
@@ -190,6 +194,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
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.
|
||||
@@ -226,6 +231,15 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +304,84 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
.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;
|
||||
|
||||
@@ -29,15 +29,17 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class RequestMetadata
|
||||
{
|
||||
public static final RequestMetadata EMPTY = new RequestMetadata(null, null);
|
||||
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)
|
||||
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()
|
||||
@@ -47,7 +49,20 @@ public class RequestMetadata
|
||||
|
||||
public static RequestMetadata fromResultHandler(AudioTrack track, CommandEvent event)
|
||||
{
|
||||
return new RequestMetadata(event.getAuthor(), new RequestInfo(event.getArgs(), track.getInfo().uri));
|
||||
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
|
||||
@@ -70,10 +85,28 @@ public class RequestMetadata
|
||||
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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -24,8 +28,11 @@ import net.dv8tion.jda.api.Permission;
|
||||
*/
|
||||
public abstract class AdminCommand extends Command
|
||||
{
|
||||
public AdminCommand()
|
||||
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()))
|
||||
@@ -36,4 +43,40 @@ public abstract class AdminCommand extends Command
|
||||
});
|
||||
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,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());
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ 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;
|
||||
@@ -45,6 +47,7 @@ public abstract class MusicCommand extends Command
|
||||
@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))
|
||||
@@ -54,12 +57,14 @@ public abstract class MusicCommand extends Command
|
||||
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)
|
||||
@@ -71,6 +76,7 @@ public abstract class MusicCommand extends Command
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -78,6 +84,7 @@ public abstract class MusicCommand extends Command
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -90,13 +97,41 @@ public abstract class MusicCommand extends Command
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doCommand(event);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -23,9 +27,48 @@ import com.jagrosh.jdautilities.command.Command;
|
||||
*/
|
||||
public abstract class OwnerCommand extends Command
|
||||
{
|
||||
public OwnerCommand()
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,6 +30,7 @@ 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>";
|
||||
@@ -35,24 +38,30 @@ public class PrefixCmd extends AdminCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -30,7 +32,7 @@ public class QueueTypeCmd extends AdminCommand
|
||||
{
|
||||
public QueueTypeCmd(Bot bot)
|
||||
{
|
||||
super();
|
||||
super(bot);
|
||||
this.name = "queuetype";
|
||||
this.help = "changes the queue type";
|
||||
this.arguments = "[" + String.join("|", QueueType.getNames()) + "]";
|
||||
@@ -38,16 +40,18 @@ public class QueueTypeCmd extends AdminCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -58,6 +62,7 @@ public class QueueTypeCmd extends AdminCommand
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
event.replyError("Invalid queue type. Valid types are: [" + String.join("|", QueueType.getNames()) + "]");
|
||||
CommandLogContext.setError("invalid_queue_type");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,5 +76,6 @@ public class QueueTypeCmd extends AdminCommand
|
||||
}
|
||||
|
||||
event.reply(value.getEmoji() + " Queue type was set to `" + value.getUserFriendlyName() + "`.");
|
||||
CommandLogContext.setMeta(new JSONObject().put("before", before.name()).put("after", value.name()));
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,11 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -32,6 +34,7 @@ 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>";
|
||||
@@ -39,30 +42,40 @@ public class SetdjCmd extends AdminCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,11 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -32,6 +34,7 @@ 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>";
|
||||
@@ -39,31 +42,41 @@ public class SettcCmd extends AdminCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,11 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -32,6 +34,7 @@ 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>";
|
||||
@@ -39,31 +42,41 @@ public class SetvcCmd extends AdminCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,6 +30,7 @@ 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>";
|
||||
@@ -35,7 +38,7 @@ public class SkipratioCmd extends AdminCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -43,15 +46,21 @@ public class SkipratioCmd extends AdminCommand
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,12 @@ 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;
|
||||
@@ -53,6 +55,7 @@ public class ForceRemoveCmd extends DJCommand
|
||||
if (event.getArgs().isEmpty())
|
||||
{
|
||||
event.replyError("You need to mention a user!");
|
||||
CommandLogContext.setError("missing_user");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,6 +63,7 @@ public class ForceRemoveCmd extends DJCommand
|
||||
if (handler.getQueue().isEmpty())
|
||||
{
|
||||
event.replyError("There is nothing in the queue!");
|
||||
CommandLogContext.setError("empty_queue");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,6 +74,7 @@ public class ForceRemoveCmd extends DJCommand
|
||||
if(found.isEmpty())
|
||||
{
|
||||
event.replyError("Unable to find the user!");
|
||||
CommandLogContext.setError("user_not_found");
|
||||
return;
|
||||
}
|
||||
else if(found.size()>1)
|
||||
@@ -107,14 +112,26 @@ public class ForceRemoveCmd extends DJCommand
|
||||
|
||||
private void removeAllEntries(User target, CommandEvent event)
|
||||
{
|
||||
int count = ((AudioHandler) event.getGuild().getAudioManager().getSendingHandler()).getQueue().removeAll(target.getIdLong());
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -44,6 +46,13 @@ public class ForceskipCmd extends DJCommand
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ 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.
|
||||
@@ -34,6 +36,7 @@ public class MoveTrackCmd extends DJCommand
|
||||
if(parts.length < 2)
|
||||
{
|
||||
event.replyError("Please include two valid indexes.");
|
||||
CommandLogContext.setError("missing_indexes");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,12 +49,14 @@ public class MoveTrackCmd extends DJCommand
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -62,24 +67,36 @@ public class MoveTrackCmd extends DJCommand
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -42,9 +44,16 @@ public class PauseCmd extends DJCommand
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ 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;
|
||||
@@ -28,6 +29,7 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -52,9 +54,11 @@ public class PlaynextCmd extends DJCommand
|
||||
@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(">")
|
||||
@@ -82,13 +86,18 @@ public class PlaynextCmd extends DJCommand
|
||||
{
|
||||
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();
|
||||
int pos = handler.addTrackToFront(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
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
|
||||
@@ -114,7 +123,10 @@ public class PlaynextCmd extends DJCommand
|
||||
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));
|
||||
}
|
||||
@@ -126,6 +138,15 @@ public class PlaynextCmd extends DJCommand
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,10 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -37,13 +39,13 @@ public class RepeatCmd extends DJCommand
|
||||
this.guildOnly = true;
|
||||
}
|
||||
|
||||
// override musiccommand's execute because we don't actually care where this is used
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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)
|
||||
@@ -66,12 +68,11 @@ public class RepeatCmd extends DJCommand
|
||||
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()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event) { /* Intentionally Empty */ }
|
||||
}
|
||||
@@ -19,6 +19,8 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -47,16 +49,27 @@ public class SkiptoCmd extends DJCommand
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -39,6 +41,16 @@ public class StopCmd extends DJCommand
|
||||
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.");
|
||||
|
||||
@@ -19,8 +19,10 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -46,6 +48,7 @@ public class VolumeCmd extends DJCommand
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
event.reply(FormatUtil.volumeIcon(volume)+" Current volume is `"+volume+"`");
|
||||
CommandLogContext.setMeta(new JSONObject().put("current", volume));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -56,12 +59,16 @@ public class VolumeCmd extends DJCommand
|
||||
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,201 @@
|
||||
/*
|
||||
* 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 com.jagrosh.jmusicbot.JMusicBot;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import java.awt.Color;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDAInfo;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.ApplicationInfo;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class LoggedAboutCmd extends AboutCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
private final Color color;
|
||||
private final String description;
|
||||
private final String[] features;
|
||||
private final Permission[] perms;
|
||||
private boolean isAuthor = true;
|
||||
private String replacementCharacter = "+";
|
||||
private String oauthLink;
|
||||
|
||||
public LoggedAboutCmd(Bot bot, Color color, String description, String[] features, Permission[] perms)
|
||||
{
|
||||
super(color, description, features, perms);
|
||||
this.bot = bot;
|
||||
this.color = color;
|
||||
this.description = description;
|
||||
this.features = features == null ? new String[0] : features.clone();
|
||||
this.perms = perms == null ? new Permission[0] : perms.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIsAuthor(boolean isAuthor)
|
||||
{
|
||||
super.setIsAuthor(isAuthor);
|
||||
this.isAuthor = isAuthor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReplacementCharacter(String replacementCharacter)
|
||||
{
|
||||
super.setReplacementCharacter(replacementCharacter);
|
||||
this.replacementCharacter = replacementCharacter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
{
|
||||
if(oauthLink == null)
|
||||
oauthLink = resolveOauthLink(event);
|
||||
|
||||
EmbedBuilder builder = new EmbedBuilder()
|
||||
.setColor(event.isFromType(ChannelType.TEXT)
|
||||
? event.getGuild().getSelfMember().getColor()
|
||||
: color)
|
||||
.setAuthor("All about " + event.getSelfUser().getName() + "!",
|
||||
null,
|
||||
event.getSelfUser().getAvatarUrl())
|
||||
.setDescription(buildDescription(event));
|
||||
|
||||
addStats(builder, event);
|
||||
builder.setFooter("Last restart", null)
|
||||
.setTimestamp(event.getClient().getStartTime());
|
||||
|
||||
event.reply(builder.build());
|
||||
if(bot.getDataLogService() != null && event.getGuild() != null)
|
||||
{
|
||||
bot.getDataLogService().logCommandEvent(event.getGuild(), event.getAuthor(), getName(),
|
||||
event.getArgs(), "OK", new JSONObject().put("about", true).toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveOauthLink(CommandEvent event)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplicationInfo info = event.getJDA().retrieveApplicationInfo().complete();
|
||||
return info.isBotPublic() ? info.getInviteUrl(0, perms) : "";
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
JMusicBot.LOG.error("Could not generate invite link", ex);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String buildDescription(CommandEvent event)
|
||||
{
|
||||
String owner = getOwnerDisplay(event);
|
||||
String links = getLinksSection(event);
|
||||
StringBuilder builder = new StringBuilder()
|
||||
.append("Hello! I am **").append(event.getSelfUser().getName()).append("**, ")
|
||||
.append(description)
|
||||
.append("\nI ")
|
||||
.append(isAuthor ? "was written in Java" : "am owned")
|
||||
.append(" by **").append(owner).append("**")
|
||||
.append(" using Chew's [Commands Extension](https://github.com/Chew/JDA-Chewtils) (")
|
||||
.append(OtherUtil.getCommandLibraryVersion())
|
||||
.append(") and the [JDA library](https://github.com/DV8FromTheWorld/JDA) (")
|
||||
.append(JDAInfo.VERSION)
|
||||
.append(")\nType `")
|
||||
.append(event.getClient().getTextualPrefix())
|
||||
.append(event.getClient().getHelpWord())
|
||||
.append("` to see my commands!");
|
||||
|
||||
if(!links.isEmpty())
|
||||
builder.append(links);
|
||||
|
||||
builder.append("\n\nSome of my features include: ```css");
|
||||
for(String feature : features)
|
||||
{
|
||||
builder.append("\n")
|
||||
.append(event.getClient().getSuccess().startsWith("<")
|
||||
? replacementCharacter
|
||||
: event.getClient().getSuccess())
|
||||
.append(" ")
|
||||
.append(feature);
|
||||
}
|
||||
builder.append("```");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String getOwnerDisplay(CommandEvent event)
|
||||
{
|
||||
User owner = event.getJDA().getUserById(event.getClient().getOwnerId());
|
||||
return owner == null ? "<@" + event.getClient().getOwnerId() + ">" : owner.getName();
|
||||
}
|
||||
|
||||
private String getLinksSection(CommandEvent event)
|
||||
{
|
||||
String serverInvite = event.getClient().getServerInvite();
|
||||
boolean hasServerInvite = serverInvite != null && !serverInvite.isEmpty();
|
||||
boolean hasOauthLink = oauthLink != null && !oauthLink.isEmpty();
|
||||
if(!hasServerInvite && !hasOauthLink)
|
||||
return "";
|
||||
|
||||
StringBuilder builder = new StringBuilder("\n");
|
||||
if(hasServerInvite)
|
||||
builder.append("Join my server [`here`](").append(serverInvite).append(")");
|
||||
else if(hasOauthLink)
|
||||
builder.append("Please");
|
||||
|
||||
if(hasOauthLink)
|
||||
{
|
||||
if(hasServerInvite)
|
||||
builder.append(", or");
|
||||
builder.append(" [`invite`](").append(oauthLink).append(") me to your server");
|
||||
}
|
||||
builder.append("!");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void addStats(EmbedBuilder builder, CommandEvent event)
|
||||
{
|
||||
JDA jda = event.getJDA();
|
||||
if(jda.getShardInfo() == JDA.ShardInfo.SINGLE)
|
||||
{
|
||||
builder.addField("Stats", jda.getGuilds().size() + " servers\n1 shard", true)
|
||||
.addField("Users",
|
||||
jda.getUsers().size() + " unique\n"
|
||||
+ jda.getGuilds().stream().mapToInt(Guild::getMemberCount).sum() + " total",
|
||||
true)
|
||||
.addField("Channels",
|
||||
jda.getTextChannels().size() + " Text\n" + jda.getVoiceChannels().size() + " Voice",
|
||||
true);
|
||||
return;
|
||||
}
|
||||
|
||||
builder.addField("Stats",
|
||||
event.getClient().getTotalGuilds() + " Servers\nShard "
|
||||
+ (jda.getShardInfo().getShardId() + 1) + "/" + jda.getShardInfo().getShardTotal(),
|
||||
true)
|
||||
.addField("This shard", jda.getUsers().size() + " Users\n" + jda.getGuilds().size() + " Servers", true)
|
||||
.addField("",
|
||||
jda.getTextChannels().size() + " Text Channels\n" + jda.getVoiceChannels().size() + " Voice Channels",
|
||||
true);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.general;
|
||||
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.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;
|
||||
@@ -27,17 +28,19 @@ 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 Command
|
||||
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);
|
||||
@@ -45,9 +48,18 @@ public class SettingsCmd extends Command
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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()))
|
||||
|
||||
@@ -20,8 +20,10 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -44,6 +46,7 @@ public class LyricsCmd extends MusicCommand
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
String title;
|
||||
if(event.getArgs().isEmpty())
|
||||
{
|
||||
@@ -53,6 +56,7 @@ public class LyricsCmd extends MusicCommand
|
||||
else
|
||||
{
|
||||
event.replyError("There must be music playing to use that!");
|
||||
logCommandEvent(event, "ERROR", new JSONObject().put("error_reason", "no_music_playing"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -64,6 +68,7 @@ public class LyricsCmd extends MusicCommand
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -95,6 +100,15 @@ public class LyricsCmd extends MusicCommand
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -46,10 +48,14 @@ public class NowplayingCmd extends MusicCommand
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,14 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -64,6 +66,7 @@ public class PlayCmd extends MusicCommand
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
if(event.getArgs().isEmpty() && event.getMessage().getAttachments().isEmpty())
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
@@ -73,9 +76,20 @@ public class PlayCmd extends MusicCommand
|
||||
{
|
||||
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");
|
||||
@@ -84,6 +98,7 @@ public class PlayCmd extends MusicCommand
|
||||
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(">")
|
||||
@@ -111,10 +126,14 @@ public class PlayCmd extends MusicCommand
|
||||
{
|
||||
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();
|
||||
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
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))
|
||||
@@ -137,6 +156,7 @@ public class PlayCmd extends MusicCommand
|
||||
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)
|
||||
@@ -146,7 +166,10 @@ public class PlayCmd extends MusicCommand
|
||||
if(!bot.getConfig().isTooLong(track) && !track.equals(exclude))
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)));
|
||||
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]++;
|
||||
}
|
||||
});
|
||||
@@ -179,11 +202,13 @@ public class PlayCmd extends MusicCommand
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -192,6 +217,7 @@ public class PlayCmd extends MusicCommand
|
||||
+ 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +226,13 @@ public class PlayCmd extends MusicCommand
|
||||
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));
|
||||
}
|
||||
@@ -212,6 +244,10 @@ public class PlayCmd extends MusicCommand
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,21 +267,30 @@ public class PlayCmd extends MusicCommand
|
||||
@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)->handler.addTrack(new QueuedTrack(at, RequestMetadata.fromResultHandler(at, event))), () -> {
|
||||
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!");
|
||||
@@ -256,8 +301,25 @@ public class PlayCmd extends MusicCommand
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -45,19 +47,27 @@ public class PlaylistsCmd extends MusicCommand
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -33,6 +34,7 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -86,6 +88,7 @@ public class QueueCmd extends MusicCommand
|
||||
if(nowp!=null)
|
||||
bot.getNowplayingHandler().setLastNPMessage(m);
|
||||
});
|
||||
CommandLogContext.setMeta(new JSONObject().put("queue_size", 0));
|
||||
return;
|
||||
}
|
||||
String[] songs = new String[list.size()];
|
||||
@@ -97,6 +100,11 @@ public class QueueCmd extends MusicCommand
|
||||
}
|
||||
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())
|
||||
|
||||
@@ -20,9 +20,11 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -48,15 +50,28 @@ public class RemoveCmd extends MusicCommand
|
||||
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;
|
||||
@@ -68,6 +83,7 @@ public class RemoveCmd extends MusicCommand
|
||||
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());
|
||||
@@ -79,6 +95,11 @@ public class RemoveCmd extends MusicCommand
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -91,10 +112,16 @@ public class RemoveCmd extends MusicCommand
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,11 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -64,9 +66,11 @@ public class SearchCmd extends MusicCommand
|
||||
@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()+"]`",
|
||||
@@ -91,13 +95,21 @@ public class SearchCmd extends MusicCommand
|
||||
{
|
||||
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();
|
||||
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
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
|
||||
@@ -113,10 +125,17 @@ public class SearchCmd extends MusicCommand
|
||||
{
|
||||
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();
|
||||
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
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));
|
||||
@@ -130,12 +149,17 @@ public class SearchCmd extends MusicCommand
|
||||
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
|
||||
@@ -145,6 +169,18 @@ public class SearchCmd extends MusicCommand
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,12 @@ 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;
|
||||
|
||||
|
||||
/**
|
||||
@@ -53,6 +55,7 @@ public class SeekCmd extends MusicCommand
|
||||
if (!playingTrack.isSeekable())
|
||||
{
|
||||
event.replyError("This track is not seekable.");
|
||||
CommandLogContext.setError("not_seekable");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,6 +63,7 @@ public class SeekCmd extends MusicCommand
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -68,6 +72,7 @@ public class SeekCmd extends MusicCommand
|
||||
if (seekTime == null)
|
||||
{
|
||||
event.replyError("Invalid seek! Expected format: " + arguments + "\nExamples: `1:02:23` `+1:10` `-90`, `1h10m`, `+90s`");
|
||||
CommandLogContext.setError("invalid_seek");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,6 +83,7 @@ public class SeekCmd extends MusicCommand
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -89,8 +95,16 @@ public class SeekCmd extends MusicCommand
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -40,17 +42,28 @@ public class ShuffleCmd extends MusicCommand
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -50,6 +52,13 @@ public class SkipCmd extends MusicCommand
|
||||
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
|
||||
@@ -63,15 +72,36 @@ public class SkipCmd extends MusicCommand
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -18,7 +18,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -26,11 +28,9 @@ import com.jagrosh.jmusicbot.settings.Settings;
|
||||
*/
|
||||
public class AutoplaylistCmd extends OwnerCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
|
||||
public AutoplaylistCmd(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
super(bot);
|
||||
this.guildOnly = true;
|
||||
this.name = "autoplaylist";
|
||||
this.arguments = "<name|NONE>";
|
||||
@@ -39,30 +39,34 @@ public class AutoplaylistCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandEvent event)
|
||||
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 settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
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 settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,16 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -35,11 +36,9 @@ public class DebugCmd extends OwnerCommand
|
||||
private final static String[] PROPERTIES = {"java.version", "java.vm.name", "java.vm.specification.version",
|
||||
"java.runtime.name", "java.runtime.version", "java.specification.version", "os.arch", "os.name"};
|
||||
|
||||
private final Bot bot;
|
||||
|
||||
public DebugCmd(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
super(bot);
|
||||
this.name = "debug";
|
||||
this.help = "shows debug info";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
@@ -47,8 +46,9 @@ public class DebugCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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)
|
||||
@@ -66,7 +66,7 @@ public class DebugCmd extends OwnerCommand
|
||||
.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 JDA-Utilities Version = ").append(OtherUtil.getCommandLibraryVersion())
|
||||
.append("\n Lavaplayer Version = ").append(PlayerLibrary.VERSION);
|
||||
long total = Runtime.getRuntime().totalMemory() / 1024 / 1024;
|
||||
long used = total - (Runtime.getRuntime().freeMemory() / 1024 / 1024);
|
||||
|
||||
@@ -20,7 +20,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,12 +30,11 @@ import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
*/
|
||||
public class EvalCmd extends OwnerCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
private final String engine;
|
||||
|
||||
public EvalCmd(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
super(bot);
|
||||
this.name = "eval";
|
||||
this.help = "evaluates nashorn code";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
@@ -42,13 +43,15 @@ public class EvalCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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);
|
||||
@@ -61,11 +64,21 @@ public class EvalCmd extends OwnerCommand
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,11 +17,12 @@ package com.jagrosh.jmusicbot.commands.owner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
import com.jagrosh.jmusicbot.datalog.CommandLogContext;
|
||||
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -29,10 +30,9 @@ import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
|
||||
*/
|
||||
public class PlaylistCmd extends OwnerCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
public PlaylistCmd(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
super(bot);
|
||||
this.guildOnly = false;
|
||||
this.name = "playlist";
|
||||
this.arguments = "<append|delete|make|setdefault>";
|
||||
@@ -48,19 +48,21 @@ public class PlaylistCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandEvent event)
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(event.getClient().getWarning()+" Playlist Management Commands:\n");
|
||||
for(Command cmd: this.children)
|
||||
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";
|
||||
@@ -69,13 +71,14 @@ public class PlaylistCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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)
|
||||
{
|
||||
@@ -83,14 +86,19 @@ public class PlaylistCmd extends OwnerCommand
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +106,7 @@ public class PlaylistCmd extends OwnerCommand
|
||||
{
|
||||
public DeletelistCmd()
|
||||
{
|
||||
super(PlaylistCmd.this.bot);
|
||||
this.name = "delete";
|
||||
this.aliases = new String[]{"remove"};
|
||||
this.help = "deletes an existing playlist";
|
||||
@@ -106,21 +115,26 @@ public class PlaylistCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +144,7 @@ public class PlaylistCmd extends OwnerCommand
|
||||
{
|
||||
public AppendlistCmd()
|
||||
{
|
||||
super(PlaylistCmd.this.bot);
|
||||
this.name = "append";
|
||||
this.aliases = new String[]{"add"};
|
||||
this.help = "appends songs to an existing playlist";
|
||||
@@ -138,18 +153,22 @@ public class PlaylistCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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();
|
||||
@@ -166,10 +185,12 @@ public class PlaylistCmd extends OwnerCommand
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,6 +212,7 @@ public class PlaylistCmd extends OwnerCommand
|
||||
{
|
||||
public ListCmd()
|
||||
{
|
||||
super(PlaylistCmd.this.bot);
|
||||
this.name = "all";
|
||||
this.aliases = new String[]{"available","list"};
|
||||
this.help = "lists all available playlists";
|
||||
@@ -198,25 +220,33 @@ public class PlaylistCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -31,6 +33,7 @@ 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>";
|
||||
@@ -39,8 +42,9 @@ public class SetavatarCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
CommandLogContext.setManualLogging();
|
||||
String url;
|
||||
if(event.getArgs().isEmpty())
|
||||
if(!event.getMessage().getAttachments().isEmpty() && event.getMessage().getAttachments().get(0).isImage())
|
||||
@@ -53,16 +57,33 @@ public class SetavatarCmd extends OwnerCommand
|
||||
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."),
|
||||
t -> event.reply(event.getClient().getError()+" Failed to set avatar."));
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,6 +30,7 @@ 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]";
|
||||
@@ -41,7 +44,7 @@ public class SetgameCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
String title = event.getArgs().toLowerCase().startsWith("playing") ? event.getArgs().substring(7).trim() : event.getArgs();
|
||||
try
|
||||
@@ -49,10 +52,14 @@ public class SetgameCmd extends OwnerCommand
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +67,7 @@ public class SetgameCmd 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";
|
||||
@@ -68,12 +76,13 @@ public class SetgameCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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
|
||||
@@ -81,10 +90,15 @@ public class SetgameCmd extends OwnerCommand
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +107,7 @@ public class SetgameCmd 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";
|
||||
@@ -101,11 +116,12 @@ public class SetgameCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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();
|
||||
@@ -113,8 +129,10 @@ public class SetgameCmd extends OwnerCommand
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +141,7 @@ public class SetgameCmd 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";
|
||||
@@ -131,11 +150,12 @@ public class SetgameCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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();
|
||||
@@ -143,8 +163,10 @@ public class SetgameCmd extends OwnerCommand
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,6 +30,7 @@ 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>";
|
||||
@@ -36,21 +39,24 @@ public class SetnameCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,6 +30,7 @@ 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>";
|
||||
@@ -36,21 +39,27 @@ public class SetstatusCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -25,11 +27,9 @@ import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
*/
|
||||
public class ShutdownCmd extends OwnerCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
|
||||
public ShutdownCmd(Bot bot)
|
||||
{
|
||||
this.bot = bot;
|
||||
super(bot);
|
||||
this.name = "shutdown";
|
||||
this.help = "safely shuts down";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
@@ -37,8 +37,9 @@ public class ShutdownCmd extends OwnerCommand
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
import com.jagrosh.jdautilities.commons.JDAUtilitiesInfo;
|
||||
import com.jagrosh.jmusicbot.JMusicBot;
|
||||
import com.jagrosh.jmusicbot.entities.Prompt;
|
||||
import java.io.*;
|
||||
@@ -23,6 +24,7 @@ import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
@@ -186,6 +188,26 @@ public class OtherUtil
|
||||
else
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
public static String getCommandLibraryVersion()
|
||||
{
|
||||
try(InputStream input = OtherUtil.class.getResourceAsStream("/com/jagrosh/jmusicbot/versions.properties"))
|
||||
{
|
||||
if(input != null)
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
properties.load(input);
|
||||
String version = properties.getProperty("jda-chewtils.version");
|
||||
if(version != null && !version.trim().isEmpty())
|
||||
return version.trim();
|
||||
}
|
||||
}
|
||||
catch(IOException ignored) {}
|
||||
|
||||
if(JDAUtilitiesInfo.VERSION_REVISION != null && !JDAUtilitiesInfo.VERSION_REVISION.startsWith("@"))
|
||||
return JDAUtilitiesInfo.VERSION;
|
||||
return JDAUtilitiesInfo.VERSION_MAJOR + "." + JDAUtilitiesInfo.VERSION_MINOR;
|
||||
}
|
||||
|
||||
public static String getLatestVersion()
|
||||
{
|
||||
|
||||
@@ -63,6 +63,13 @@ youtubeoauth2=false
|
||||
songinstatus=false
|
||||
|
||||
|
||||
// If you set this to true, the bot will maintain a single status message in the text channel
|
||||
// showing the currently playing song and queue summary. Set this to false to disable
|
||||
// the status message feature entirely.
|
||||
|
||||
statusmessages=true
|
||||
|
||||
|
||||
// If you set this, the bot will also use this prefix in addition to
|
||||
// the one provided above
|
||||
|
||||
@@ -230,6 +237,16 @@ chromedriverpath = "AUTO"
|
||||
chromeheadless = true
|
||||
|
||||
|
||||
// Data logging (analytics) configuration
|
||||
// Enable to log queue/playback events into a SQL database.
|
||||
// SQLite example: jdbc:sqlite:musicbot.db
|
||||
|
||||
dblog.enabled = false
|
||||
dblog.jdbc_url = "jdbc:sqlite:musicbot.db"
|
||||
dblog.user = ""
|
||||
dblog.password = ""
|
||||
|
||||
|
||||
// This configures the routing planner to use for IP rotation on YouTube.
|
||||
// When set, you need to also set an IP block below.
|
||||
// The following routing strategies are available:
|
||||
|
||||
Reference in new issue
Block a user