Compare commits

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

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

* stop double posting

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

* Update youtube source to 1.5.1

---------

Co-authored-by: Michaili K <git@michaili.dev>
2024-08-01 09:14:51 -04:00
Timothy Gillespie f140b41bf5 Use shell comparsion (#1627) 2024-08-01 09:10:04 -04:00
John Grosh 8733899aa0 Update bug-report.yml 2024-07-10 17:35:50 -04:00
28 changed files with 870 additions and 109 deletions

No files matched your search

+8 -1
View File
@@ -53,5 +53,12 @@ body:
required: true
- label: I have searched for similar issues [on the issues page](https://github.com/jagrosh/MusicBot/issues?q=is%3Aissue)
required: true
- label: "I am running the latest version of the bot: ![Release](https://img.shields.io/github/release/jagrosh/MusicBot.svg)"
- label: I am using an official release from [the releases page](https://github.com/jagrosh/MusicBot/releases)
required: true
- type: input
id: version
attributes:
label: JMusicBot Version
description: Which version of JMusicBot are you running?
validations:
required: true
+2 -1
View File
@@ -4,4 +4,5 @@
/test/
*.json
*.txt
nb*.xml
nb*.xml
dockerfile
+100
View File
@@ -1,5 +1,105 @@
<img align="right" src="https://i.imgur.com/zrE80HY.png" height="200" width="200">
## Demo
If you only want to temporarily join the demo server, use this link: [https://discord.gg/4cp9CvjwaW](https://discord.gg/4cp9CvjwaW)
If you want to join the demo server permanently, use this link: [https://discord.gg/hK95396uB3](https://discord.gg/hK95396uB3)
# This fork
We had to update to JDA 6, switch to jda-chewtils to make the bot work again.
Also, we somewhat regularly bump [lavaplayer](https://mvnrepository.com/artifact/dev.arbjerg/lavaplayer)/[lavalink](https://maven.lavalink.dev/#/releases/dev/lavalink/youtube/common) to their new versions. I will try to update this automatically in the future.
Additionally, this fork includes the following PRs from other people:
### Support for logging into your (burner) youtube account:
[PR-1670](https://github.com/jagrosh/MusicBot/pull/1670)
### To allow building the project from source
[PR-1703](https://github.com/jagrosh/MusicBot/pull/1703)
### Add YouTube PO token & IP rotation
https://github.com/jagrosh/MusicBot/pull/1772
### For PO_Tokens: In your bot folder, edit "config.txt" and add
```
ytpotoken = "PO_TOKEN_HERE"
ytvisitordata = "VISITOR_DATA_HERE"
```
To get po_token and visitor_data, use [youtube-trusted-session-generator](https://github.com/iv-org/youtube-trusted-session-generator)
If you have problems with this fork, you could also check out [this other fork](https://github.com/SeVile/MusicBot).
## Requirements for 0.5+ and above
Since we updated JDA from version 4 to 6 to fix various issues, we had to enable the MESSAGE_CONTENT intent. If you let your bot join more than 100 servers(guilds), you now need to enable this intent in the [Discord Developer Portal](https://discord.com/developers/applications).
More information can be found in the [JDA documentation](https://jda.wiki/using-jda/gateway-intents-and-member-cache-policy/).
Note, however, that this is a privileged intent and will require a valid use-case for your bot to be verified in over 75 servers (it will only show up in the portal when on at least 76 servers according to the documentation).
### DAVE / Voice encryption (JDA 6.3+)
Discord requires DAVE for voice on modern API versions (JDA 6.3+).
This fork uses `libdave-jvm` (`0.1.0`) and configures DAVE during startup.
The shaded jar includes native binaries for:
- Linux glibc x86_64
- Linux musl x86_64 (Alpine containers)
- Windows x86_64
- macOS (universal)
If you deploy on a different architecture, add the matching `moe.kyokobot.libdave:natives-*` dependency in `pom.xml`.
## Easy deployment via docker
Additionally, i thought a corresponding docker image would make things better:
The image can be found on [dockerhub](https://hub.docker.com/r/chrisb09/jmusicbot)
Docker-compose:
```yaml
version: '2.2'
services:
jmusicbot:
image: chrisb09/jmusicbot:latest
container_name: jmusicbot
restart: unless-stopped
volumes:
- ./config:/jmb/config
```
although, obviously you should change `./config` to whatever path is right for your setup.
Source code for the docker image is also on [Github](https://github.com/chrisb09/jmb-container).
### Windows
If you want to run the bot on a windows host without docker, you obviously need to install java, and if you want to run it in the background something like [nssm](http://nssm.cc/) can be helpful.
## YouTube-Login
Obviously you still need to log in if you want to use youtube properly, hence you need to enable this in the config.
If you are using a pre-existing config, simply add:
```
/ If you set this to true, the bot will use a Google account to play YouTube tracks.
// This is only needed if you are experiencing issues with YouTube playback.
// Once enabled, instructions for login will be written to the console and DMs.
// DO NOT use your main Google account! Use an alternative account.
youtubeoauth2=true
```
If you start with a clean slate this line should already be present, albeit with `false` being the default.
As the author of the PR reiterates multiple times, do not use your main google account as using your account for a bot is most certainly against the ToS and thus you are endangering said account.
To add the account you simply have to follow the very basic instructions printed either in the console or sent to you from the bot via discord after you have started it, open the linked google page and paste the code.
# JMusicBot
[![Downloads](https://img.shields.io/github/downloads/jagrosh/MusicBot/total.svg)](https://github.com/jagrosh/MusicBot/releases/latest)
+143 -15
View File
@@ -3,10 +3,15 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.jagrosh</groupId>
<artifactId>JMusicBot</artifactId>
<version>Snapshot</version>
<version>0.6.0</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>
@@ -35,6 +43,11 @@
<name>Lavalink Repository</name>
<url>https://maven.lavalink.dev/releases</url>
</repository>
<repository>
<id>arbjergDevSnapshots</id>
<name>Lavalink Repository</name>
<url>https://maven.lavalink.dev/snapshots</url>
</repository>
</repositories>
<dependencies>
@@ -42,30 +55,118 @@
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>4.4.1_353</version>
<version>6.3.1</version>
</dependency>
<dependency>
<groupId>com.jagrosh</groupId>
<artifactId>jda-utilities</artifactId>
<version>3.0.5</version>
<type>pom</type>
<groupId>moe.kyokobot.libdave</groupId>
<artifactId>adapter-jda</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>moe.kyokobot.libdave</groupId>
<artifactId>impl-jni</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>moe.kyokobot.libdave</groupId>
<artifactId>natives-linux-x86-64</artifactId>
<version>0.1.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>moe.kyokobot.libdave</groupId>
<artifactId>natives-linux-musl-x86-64</artifactId>
<version>0.1.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>moe.kyokobot.libdave</groupId>
<artifactId>natives-win-x86-64</artifactId>
<version>0.1.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>moe.kyokobot.libdave</groupId>
<artifactId>natives-darwin</artifactId>
<version>0.1.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>pw.chew</groupId>
<artifactId>jda-chewtils-command</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>pw.chew</groupId>
<artifactId>jda-chewtils-commons</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>pw.chew</groupId>
<artifactId>jda-chewtils-examples</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>pw.chew</groupId>
<artifactId>jda-chewtils-menu</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<!-- <dependency>
<groupId>com.github.chew.jda-chewtils</groupId>
<artifactId>jda-chewtils-command</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.github.chew.jda-chewtils</groupId>
<artifactId>jda-chewtils-menu</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.github.chew.jda-chewtils</groupId>
<artifactId>jda-chewtils-commons</artifactId>
<version>2.2.1</version>
</dependency> -->
<!--
<dependency>
<groupId>com.github.chew.jda-chewtils</groupId>
<artifactId>jda-chewtils-command</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.github.chew.jda-chewtils</groupId>
<artifactId>jda-chewtils-menu</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.github.chew.jda-chewtils</groupId>
<artifactId>jda-chewtils-commons</artifactId>
<version>2.2.1</version>
</dependency>
-->
<!-- Music Dependencies -->
<dependency>
<groupId>dev.arbjerg</groupId>
<artifactId>lavaplayer</artifactId>
<version>2.2.1</version>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>dev.arbjerg</groupId>
<artifactId>lavaplayer-ext-youtube-rotator</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>dev.lavalink.youtube</groupId>
<artifactId>common</artifactId>
<version>1.4.0</version>
<version>1.16.0</version>
</dependency>
<dependency>
<groupId>com.github.jagrosh</groupId>
<artifactId>JLyrics</artifactId>
<version>master-SNAPSHOT</version>
<version>master</version>
</dependency>
<dependency>
<groupId>com.dunctebot</groupId>
@@ -77,7 +178,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.13</version>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
@@ -87,8 +188,35 @@
<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>
<artifactId>selenium-java</artifactId>
<version>4.27.0</version>
<!-- https://github.com/SeleniumHQ/selenium/issues/13791 -->
<exclusions>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-devtools-v85</artifactId>
</exclusion>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-devtools-v129</artifactId>
</exclusion>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-devtools-v130</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.9.2</version>
</dependency>
<!-- Testing Dependencies -->
<dependency>
+1 -1
View File
@@ -9,7 +9,7 @@ DOWNLOAD=true
LOOP=true
download() {
if [ $DOWNLOAD == true ]; then
if [ $DOWNLOAD = true ]; then
URL=$(curl -s https://api.github.com/repos/jagrosh/MusicBot/releases/latest \
| grep -i "browser_download_url.*\.jar" \
| sed 's/.*\(http.*\)"/\1/')
+10 -1
View File
@@ -26,6 +26,7 @@ import com.jagrosh.jmusicbot.gui.GUI;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader;
import com.jagrosh.jmusicbot.settings.SettingsManager;
import java.util.Objects;
import com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
@@ -44,6 +45,7 @@ public class Bot
private final PlaylistLoader playlists;
private final NowplayingHandler nowplaying;
private final AloneInVoiceHandler aloneInVoiceHandler;
private final YoutubeOauth2TokenHandler youTubeOauth2TokenHandler;
private boolean shuttingDown = false;
private JDA jda;
@@ -56,7 +58,9 @@ public class Bot
this.settings = settings;
this.playlists = new PlaylistLoader(config);
this.threadpool = Executors.newSingleThreadScheduledExecutor();
this.players = new PlayerManager(this);
this.youTubeOauth2TokenHandler = new YoutubeOauth2TokenHandler();
this.youTubeOauth2TokenHandler.init();
this.players = new PlayerManager(this, config);
this.players.init();
this.nowplaying = new NowplayingHandler(this);
this.nowplaying.init();
@@ -103,6 +107,11 @@ public class Bot
{
return aloneInVoiceHandler;
}
public YoutubeOauth2TokenHandler getYouTubeOauth2Handler()
{
return youTubeOauth2TokenHandler;
}
public JDA getJDA()
{
@@ -18,11 +18,16 @@ package com.jagrosh.jmusicbot;
import com.jagrosh.jmusicbot.entities.Prompt;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.jagrosh.jmusicbot.utils.YouTubeUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
import com.typesafe.config.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
@@ -41,8 +46,10 @@ public class BotConfig
private Path path = null;
private String token, prefix, altprefix, helpWord, playlistsFolder, logLevel,
successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji,
evalEngine;
private boolean stayInChannel, songInGame, npImages, updatealerts, useEval, dbots;
chromePath, chromeDriverPath, evalEngine;
private YouTubeUtil.RoutingPlanner ytRoutingPlanner;
private List<IpBlock> ytIpBlocks;
private boolean youtubeOauth2, stayInChannel, songInGame, npImages, updatealerts, chromeHeadless, useEval, dbots;
private long owner, maxSeconds, aloneTimeUntilStop;
private int maxYTPlaylistPages;
private double skipratio;
@@ -84,6 +91,7 @@ public class BotConfig
searchingEmoji = config.getString("searching");
game = OtherUtil.parseGame(config.getString("game"));
status = OtherUtil.parseStatus(config.getString("status"));
youtubeOauth2 = config.getBoolean("youtubeoauth2");
stayInChannel = config.getBoolean("stayinchannel");
songInGame = config.getBoolean("songinstatus");
npImages = config.getBoolean("npimages");
@@ -96,6 +104,11 @@ public class BotConfig
aloneTimeUntilStop = config.getLong("alonetimeuntilstop");
playlistsFolder = config.getString("playlistsfolder");
aliases = config.getConfig("aliases");
chromePath = config.getString("chromepath");
chromeDriverPath = config.getString("chromedriverpath");
chromeHeadless = config.getBoolean("chromeheadless");
ytRoutingPlanner = config.getEnum(YouTubeUtil.RoutingPlanner.class, "ytroutingplanner");
ytIpBlocks = config.getStringList("ytipblocks").stream().map(YouTubeUtil::parseIpBlock).collect(Collectors.toList());
transforms = config.getConfig("transforms");
skipratio = config.getDouble("skipratio");
dbots = owner == 113156185389092864L;
@@ -293,6 +306,11 @@ public class BotConfig
return helpWord;
}
public boolean useYoutubeOauth2()
{
return youtubeOauth2;
}
public boolean getStay()
{
return stayInChannel;
@@ -323,6 +341,31 @@ public class BotConfig
return logLevel;
}
public String getChromePath()
{
return chromePath.equals("AUTO") ? null : chromePath;
}
public String getChromeDriverPath()
{
return chromeDriverPath.equals("AUTO") ? null : chromeDriverPath;
}
public boolean getChromeHeadless()
{
return chromeHeadless;
}
public YouTubeUtil.RoutingPlanner getYTRoutingPlanner()
{
return ytRoutingPlanner;
}
public List<IpBlock> getYTIpBlocks()
{
return ytIpBlocks;
}
public boolean useEval()
{
return useEval;
@@ -28,10 +28,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;
@@ -40,6 +43,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import static com.jagrosh.jmusicbot.utils.YouTubeUtil.GetPoTokenAndVisitorData;
/**
*
* @author John Grosh (jagrosh)
@@ -47,10 +52,10 @@ import ch.qos.logback.classic.Level;
public class JMusicBot
{
public final static Logger LOG = LoggerFactory.getLogger(JMusicBot.class);
public final static Permission[] RECOMMENDED_PERMS = {Permission.MESSAGE_READ, Permission.MESSAGE_WRITE, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
public final static Permission[] RECOMMENDED_PERMS = {Permission.VIEW_CHANNEL, Permission.MESSAGE_SEND, Permission.MESSAGE_HISTORY, Permission.MESSAGE_ADD_REACTION,
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI,
Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE};
public final static GatewayIntent[] INTENTS = {GatewayIntent.DIRECT_MESSAGES, GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGE_REACTIONS, GatewayIntent.GUILD_VOICE_STATES};
public final static GatewayIntent[] INTENTS = {GatewayIntent.DIRECT_MESSAGES, GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGE_REACTIONS, GatewayIntent.GUILD_VOICE_STATES, GatewayIntent.MESSAGE_CONTENT, GatewayIntent.GUILD_EXPRESSIONS, GatewayIntent.SCHEDULED_EVENTS};
/**
* @param args the command line arguments
@@ -118,10 +123,11 @@ public class JMusicBot
{
JDA jda = JDABuilder.create(config.getToken(), Arrays.asList(INTENTS))
.enableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE)
.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.EMOTE, CacheFlag.ONLINE_STATUS)
.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.ONLINE_STATUS)
.setActivity(config.isGameNone() ? null : Activity.playing("loading..."))
.setStatus(config.getStatus()==OnlineStatus.INVISIBLE || config.getStatus()==OnlineStatus.OFFLINE
? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB)
.setAudioModuleConfig(createAudioModuleConfig())
.addEventListeners(client, waiter, new Listener(bot))
.setBulkDeleteSplittingEnabled(true)
.build();
@@ -147,11 +153,10 @@ public class JMusicBot
+ "on https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot");
}
}
catch (LoginException ex)
catch(IllegalStateException ex)
{
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nPlease make sure you are "
+ "editing the correct config.txt file, and that you have used the "
+ "correct token (not the 'secret'!)\nConfig Location: " + config.getConfigLocation());
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex.getMessage());
LOG.error("Failed to initialize DAVE protocol support", ex);
System.exit(1);
}
catch(IllegalArgumentException ex)
@@ -166,6 +171,24 @@ public class JMusicBot
+ "attempting to connect, please make sure you're connected to the internet");
System.exit(1);
}
GetPoTokenAndVisitorData(config.getChromePath(), config.getChromeDriverPath(), config.getChromeHeadless());
}
private static AudioModuleConfig createAudioModuleConfig()
{
try
{
NativeDaveFactory.ensureAvailable();
LOG.info("Configured DAVE protocol support using libdave-jvm.");
return new AudioModuleConfig()
.withDaveSessionFactory(new LDJDADaveSessionFactory(new NativeDaveFactory()));
}
catch(Throwable ex)
{
throw new IllegalStateException("Unable to initialize DAVE protocol support via libdave-jvm. "
+ "Verify native dependencies are present for this platform (Linux glibc/musl x86_64, Windows x86_64, or macOS).", ex);
}
}
private static CommandClient createCommandClient(BotConfig config, SettingsManager settings, Bot bot)
@@ -17,15 +17,17 @@ package com.jagrosh.jmusicbot;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import java.util.concurrent.TimeUnit;
import com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.events.ReadyEvent;
import net.dv8tion.jda.api.events.ShutdownEvent;
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.events.session.ReadyEvent;
import net.dv8tion.jda.api.events.session.ShutdownEvent;
import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageDeleteEvent;
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@@ -85,12 +87,29 @@ public class Listener extends ListenerAdapter
catch(Exception ignored) {} // ignored
}, 0, 24, TimeUnit.HOURS);
}
if (bot.getConfig().useYoutubeOauth2())
{
YoutubeOauth2TokenHandler.Data data = bot.getYouTubeOauth2Handler().getData();
if (data != null)
{
PrivateChannel channel = bot.getJDA().openPrivateChannelById(bot.getConfig().getOwnerId()).complete();
channel
.sendMessage(
"# DO NOT AUTHORISE THIS WITH YOUR MAIN GOOGLE ACCOUNT!!!\n"
+ "## Create or use an alternative/burner Google account!\n"
+ "To give JMusicBot access to your Google account, go to "
+ data.getAuthorisationUrl()
+ " and enter the code **" + data.getCode() + "**")
.queue();
}
}
}
@Override
public void onGuildMessageDelete(GuildMessageDeleteEvent event)
public void onMessageDelete(MessageDeleteEvent event)
{
bot.getNowplayingHandler().onMessageDelete(event.getGuild(), event.getMessageIdLong());
if(event.isFromGuild())
bot.getNowplayingHandler().onMessageDelete(event.getGuild(), event.getMessageIdLong());
}
@Override
@@ -20,7 +20,6 @@ import com.jagrosh.jmusicbot.queue.AbstractQueue;
import com.jagrosh.jmusicbot.settings.QueueType;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
@@ -37,11 +36,13 @@ import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack;
import java.nio.ByteBuffer;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.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;
/**
@@ -54,6 +55,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
public final static String PAUSE_EMOJI = "\u23F8"; // ⏸
public final static String STOP_EMOJI = "\u23F9"; // ⏹
private final static Logger LOGGER = LoggerFactory.getLogger(AudioHandler.class);
private final List<AudioTrack> defaultQueue = new LinkedList<>();
private final Set<String> votes = new HashSet<>();
@@ -119,7 +121,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
public boolean isMusicPlaying(JDA jda)
{
return guild(jda).getSelfMember().getVoiceState().inVoiceChannel() && audioPlayer.getPlayingTrack()!=null;
return guild(jda).getSelfMember().getVoiceState().inAudioChannel() && audioPlayer.getPlayingTrack()!=null;
}
public Set<String> getVotes()
@@ -203,8 +205,20 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
}
@Override
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) {
LoggerFactory.getLogger("AudioHandler").error("Track " + track.getIdentifier() + " has failed to play", exception);
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception)
{
if (
exception.getMessage().equals("Sign in to confirm you're not a bot")
|| exception.getMessage().equals("Please sign in")
)
LOGGER.error(
"Track {} has failed to play: {}"
+ "You will need to sign in to Google to play YouTube tracks. More info: https://jmusicbot.com/youtube-oauth2",
track.getIdentifier(),
exception.getMessage()
);
else
LOGGER.error("Track {} has failed to play", track.getIdentifier(), exception);
}
@Override
@@ -216,14 +230,14 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
// Formatting
public Message getNowPlaying(JDA jda)
public MessageCreateData getNowPlaying(JDA jda)
{
if(isMusicPlaying(jda))
{
Guild guild = guild(jda);
AudioTrack track = audioPlayer.getPlayingTrack();
MessageBuilder mb = new MessageBuilder();
mb.append(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing in "+guild.getSelfMember().getVoiceState().getChannel().getAsMention()+"...**"));
MessageCreateBuilder mb = new MessageCreateBuilder();
mb.addContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing in "+guild.getSelfMember().getVoiceState().getChannel().getAsMention()+"...**"));
EmbedBuilder eb = new EmbedBuilder();
eb.setColor(guild.getSelfMember().getColor());
RequestMetadata rm = getRequestMetadata();
@@ -264,10 +278,10 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
else return null;
}
public Message getNoMusicPlaying(JDA jda)
public MessageCreateData getNoMusicPlaying(JDA jda)
{
Guild guild = guild(jda);
return new MessageBuilder()
return new MessageCreateBuilder()
.setContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing...**"))
.setEmbeds(new EmbedBuilder()
.setTitle("No music playing")
@@ -27,9 +27,11 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.exceptions.PermissionException;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
/**
*
@@ -54,7 +56,7 @@ public class NowplayingHandler
public void setLastNPMessage(Message m)
{
lastNP.put(m.getGuild().getIdLong(), new Pair<>(m.getTextChannel().getIdLong(), m.getIdLong()));
lastNP.put(m.getGuild().getIdLong(), new Pair<>(m.getChannel().getIdLong(), m.getIdLong()));
}
public void clearLastNPMessage(Guild guild)
@@ -81,7 +83,7 @@ public class NowplayingHandler
continue;
}
AudioHandler handler = (AudioHandler)guild.getAudioManager().getSendingHandler();
Message msg = handler.getNowPlaying(bot.getJDA());
MessageCreateData msg = handler.getNowPlaying(bot.getJDA());
if(msg==null)
{
msg = handler.getNoMusicPlaying(bot.getJDA());
@@ -89,7 +91,7 @@ public class NowplayingHandler
}
try
{
tc.editMessageById(pair.getValue(), msg).queue(m->{}, t -> lastNP.remove(guildId));
tc.editMessageById(pair.getValue(), MessageEditData.fromCreateData(msg)).queue(m->{}, t -> lastNP.remove(guildId));
}
catch(Exception e)
{
@@ -105,7 +107,7 @@ public class NowplayingHandler
// update bot status if applicable
if(bot.getConfig().getSongInStatus())
{
if(track!=null && bot.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()<=1)
if(track!=null && bot.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inAudioChannel()).count()<=1)
bot.getJDA().getPresence().setActivity(Activity.listening(track.getInfo().title));
else
bot.resetGame();
@@ -17,6 +17,9 @@ package com.jagrosh.jmusicbot.audio;
import com.dunctebot.sourcemanagers.DuncteBotSources;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.BotConfig;
import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.jagrosh.jmusicbot.utils.YouTubeUtil;
import com.sedmelluq.discord.lavaplayer.container.MediaContainerRegistry;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
@@ -29,8 +32,21 @@ import com.sedmelluq.discord.lavaplayer.source.nico.NicoAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager;
import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup;
import com.sedmelluq.lava.extensions.youtuberotator.planner.AbstractRoutePlanner;
import com.sedmelluq.lava.extensions.youtuberotator.planner.BalancingIpRoutePlanner;
import com.sedmelluq.lava.extensions.youtuberotator.planner.NanoIpRoutePlanner;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
import dev.lavalink.youtube.YoutubeAudioSourceManager;
import dev.lavalink.youtube.clients.Web;
import net.dv8tion.jda.api.entities.Guild;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
/**
*
@@ -38,18 +54,31 @@ import net.dv8tion.jda.api.entities.Guild;
*/
public class PlayerManager extends DefaultAudioPlayerManager
{
private final static Logger LOGGER = LoggerFactory.getLogger(PlayerManager.class);
private final Bot bot;
public PlayerManager(Bot bot)
private final BotConfig config;
public PlayerManager(Bot bot, BotConfig config)
{
this.bot = bot;
this.config = config;
}
public void init()
{
TransformativeAudioSourceManager.createTransforms(bot.getConfig().getTransforms()).forEach(t -> registerSourceManager(t));
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
if (config.getYTRoutingPlanner() != YouTubeUtil.RoutingPlanner.NONE)
{
AbstractRoutePlanner routePlanner = YouTubeUtil.createRouterPlanner(config.getYTRoutingPlanner(), config.getYTIpBlocks());
YoutubeIpRotatorSetup rotator = new YoutubeIpRotatorSetup(routePlanner);
rotator.forConfiguration(yt.getHttpInterfaceManager(), false)
.withMainDelegateFilter(yt.getContextFilter())
.setup();
}
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
registerSourceManager(yt);
@@ -66,7 +95,42 @@ public class PlayerManager extends DefaultAudioPlayerManager
DuncteBotSources.registerAll(this, "en-US");
}
private YoutubeAudioSourceManager setupYoutubeAudioSourceManager()
{
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
// OAuth2 setup
if (bot.getConfig().useYoutubeOauth2())
{
String token = null;
try
{
token = Files.readString(OtherUtil.getPath("youtubetoken.txt"));
}
catch (NoSuchFileException e)
{
/* ignored */
}
catch (IOException e)
{
LOGGER.warn("Failed to read YouTube OAuth2 token file: {}", e.getMessage());
return yt;
}
LOGGER.debug("Read YouTube OAuth2 refresh token from youtubetoken.txt");
try
{
yt.useOauth2(token, false);
}
catch (Exception e)
{
LOGGER.warn("Failed to authorise with YouTube. If this issue persists, delete the youtubetoken.txt file to reauthorise.", e);
}
}
return yt;
}
public Bot getBot()
{
return bot;
@@ -21,8 +21,8 @@ import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.exceptions.PermissionException;
/**
@@ -64,11 +64,11 @@ public abstract class MusicCommand extends Command
}
if(beListening)
{
VoiceChannel current = event.getGuild().getSelfMember().getVoiceState().getChannel();
VoiceChannel current = (VoiceChannel) event.getGuild().getSelfMember().getVoiceState().getChannel();
if(current==null)
current = settings.getVoiceChannel(event.getGuild());
GuildVoiceState userState = event.getMember().getVoiceState();
if(!userState.inVoiceChannel() || userState.isDeafened() || (current!=null && !userState.getChannel().equals(current)))
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!");
return;
@@ -81,7 +81,7 @@ public abstract class MusicCommand extends Command
return;
}
if(!event.getGuild().getSelfMember().getVoiceState().inVoiceChannel())
if(!event.getGuild().getSelfMember().getVoiceState().inAudioChannel())
{
try
{
@@ -22,7 +22,7 @@ import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
/**
*
@@ -54,7 +54,8 @@ public class SettcCmd extends AdminCommand
}
else
{
List<TextChannel> list = FinderUtil.findTextChannels(event.getArgs(), event.getGuild());
@SuppressWarnings({"unchecked", "rawtypes"})
List<TextChannel> list = (List) FinderUtil.findTextChannels(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Text Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
@@ -22,7 +22,7 @@ import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.AdminCommand;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
/**
*
@@ -54,7 +54,8 @@ public class SetvcCmd extends AdminCommand
}
else
{
List<VoiceChannel> list = FinderUtil.findVoiceChannels(event.getArgs(), event.getGuild());
@SuppressWarnings({"unchecked", "rawtypes"})
List<VoiceChannel> list = (List) FinderUtil.findVoiceChannels(event.getArgs(), event.getGuild());
if(list.isEmpty())
event.reply(event.getClient().getWarning()+" No Voice Channels found matching \""+event.getArgs()+"\"");
else if (list.size()>1)
@@ -23,10 +23,10 @@ import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
/**
*
@@ -48,10 +48,10 @@ public class SettingsCmd extends Command
protected void execute(CommandEvent event)
{
Settings s = event.getClient().getSettingsFor(event.getGuild());
MessageBuilder builder = new MessageBuilder()
.append(EMOJI + " **")
.append(FormatUtil.filter(event.getSelfUser().getName()))
.append("** settings:");
MessageCreateBuilder builder = new MessageCreateBuilder()
.addContent(EMOJI + " **")
.addContent(FormatUtil.filter(event.getSelfUser().getName()))
.addContent("** settings:");
TextChannel tchan = s.getTextChannel(event.getGuild());
VoiceChannel vchan = s.getVoiceChannel(event.getGuild());
Role role = s.getRole(event.getGuild());
@@ -70,9 +70,9 @@ public class SettingsCmd extends Command
+ "\nDefault Playlist: " + (s.getDefaultPlaylist() == null ? "None" : "**" + s.getDefaultPlaylist() + "**")
)
.setFooter(event.getJDA().getGuilds().size() + " servers | "
+ event.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()
+ event.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inAudioChannel()).count()
+ " audio connections", null);
event.getChannel().sendMessage(builder.setEmbeds(ebuilder.build()).build()).queue();
event.getMessage().getChannel().sendMessage(builder.setEmbeds(ebuilder.build()).build()).queue();
}
}
@@ -20,7 +20,7 @@ import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
/**
*
@@ -41,15 +41,15 @@ public class NowplayingCmd extends MusicCommand
public void doCommand(CommandEvent event)
{
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
Message m = handler.getNowPlaying(event.getJDA());
MessageCreateData m = handler.getNowPlaying(event.getJDA());
if(m==null)
{
event.reply(handler.getNoMusicPlaying(event.getJDA()));
event.getMessage().getChannel().sendMessage(handler.getNoMusicPlaying(event.getJDA())).queue();
bot.getNowplayingHandler().clearLastNPMessage(event.getGuild());
}
else
{
event.reply(m, msg -> bot.getNowplayingHandler().setLastNPMessage(msg));
event.getMessage().getChannel().sendMessage(m).queue(msg -> bot.getNowplayingHandler().setLastNPMessage(msg));
}
}
}
@@ -28,7 +28,8 @@ import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.jagrosh.jmusicbot.settings.Settings;
import com.jagrosh.jmusicbot.utils.FormatUtil;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.exceptions.PermissionException;
@@ -75,12 +76,12 @@ public class QueueCmd extends MusicCommand
List<QueuedTrack> list = ah.getQueue().getList();
if(list.isEmpty())
{
Message nowp = ah.getNowPlaying(event.getJDA());
Message nonowp = ah.getNoMusicPlaying(event.getJDA());
Message built = new MessageBuilder()
MessageCreateData nowp = ah.getNowPlaying(event.getJDA());
MessageCreateData nonowp = ah.getNoMusicPlaying(event.getJDA());
MessageCreateData built = new MessageCreateBuilder()
.setContent(event.getClient().getWarning() + " There is no music in the queue!")
.setEmbeds((nowp==null ? nonowp : nowp).getEmbeds().get(0)).build();
event.reply(built, m ->
event.getMessage().getChannel().sendMessage(built).queue(m ->
{
if(nowp!=null)
bot.getNowplayingHandler().setLastNPMessage(m);
@@ -18,10 +18,13 @@ package com.jagrosh.jmusicbot.commands.music;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.audio.AudioHandler;
import com.jagrosh.jmusicbot.audio.RequestMetadata;
import com.jagrosh.jmusicbot.commands.DJCommand;
import com.jagrosh.jmusicbot.commands.MusicCommand;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
@@ -29,6 +32,8 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
*/
public class SeekCmd extends MusicCommand
{
private final static Logger LOG = LoggerFactory.getLogger("Seeking");
public SeekCmd(Bot bot)
{
super(bot);
@@ -52,7 +57,7 @@ public class SeekCmd extends MusicCommand
}
if (!DJCommand.checkDJPermission(event) && playingTrack.getUserData(Long.class) != event.getAuthor().getIdLong())
if (!DJCommand.checkDJPermission(event) && playingTrack.getUserData(RequestMetadata.class).getOwner() != event.getAuthor().getIdLong())
{
event.replyError("You cannot seek **" + playingTrack.getInfo().title + "** because you didn't add it!");
return;
@@ -73,19 +78,18 @@ 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!");
return;
}
else
try
{
try
{
playingTrack.setPosition(seekMilliseconds);
}
catch (Exception e)
{
event.replyError("An error occurred while trying to seek!");
e.printStackTrace();
return;
}
playingTrack.setPosition(seekMilliseconds);
}
catch (Exception e)
{
event.replyError("An error occurred while trying to seek: " + e.getMessage());
LOG.warn("Failed to seek track " + playingTrack.getIdentifier(), e);
return;
}
event.replySuccess("Successfully seeked to `" + TimeUtil.formatTime(playingTrack.getPosition()) + "/" + TimeUtil.formatTime(playingTrack.getDuration()) + "`!");
}
@@ -23,7 +23,8 @@ import com.jagrosh.jmusicbot.utils.OtherUtil;
import com.sedmelluq.discord.lavaplayer.tools.PlayerLibrary;
import net.dv8tion.jda.api.JDAInfo;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.utils.FileUpload;
/**
*
@@ -79,8 +80,8 @@ public class DebugCmd extends OwnerCommand
sb.append("\n```");
if(event.isFromType(ChannelType.PRIVATE)
|| event.getSelfMember().hasPermission(event.getTextChannel(), Permission.MESSAGE_ATTACH_FILES))
event.getChannel().sendFile(sb.toString().getBytes(), "debug_information.txt").queue();
|| event.getSelfMember().hasPermission(event.getGuildChannel(), Permission.MESSAGE_ATTACH_FILES))
event.getChannel().sendFiles(FileUpload.fromData(sb.toString().getBytes(), "debug_information.txt")).queue();
else
event.reply("Debug Information: " + sb.toString());
}
@@ -20,7 +20,7 @@ import javax.script.ScriptEngineManager;
import com.jagrosh.jdautilities.command.CommandEvent;
import com.jagrosh.jmusicbot.Bot;
import com.jagrosh.jmusicbot.commands.OwnerCommand;
import net.dv8tion.jda.api.entities.ChannelType;
import net.dv8tion.jda.api.entities.channel.ChannelType;
/**
*
@@ -20,8 +20,8 @@ import java.util.Collection;
import java.util.Collections;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
/**
*
@@ -24,6 +24,7 @@ import java.util.HashMap;
import net.dv8tion.jda.api.entities.Guild;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
@@ -32,6 +33,7 @@ import org.slf4j.LoggerFactory;
*/
public class SettingsManager implements GuildSettingsManager<Settings>
{
private final static Logger LOG = LoggerFactory.getLogger("Settings");
private final static String SETTINGS_FILE = "serversettings.json";
private final HashMap<Long,Settings> settings;
@@ -63,17 +65,17 @@ public class SettingsManager implements GuildSettingsManager<Settings>
} catch (NoSuchFileException e) {
// create an empty json file
try {
LoggerFactory.getLogger("Settings").info("serversettings.json will be created in " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
LOG.info("serversettings.json will be created in " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
Files.write(OtherUtil.getPath("serversettings.json"), new JSONObject().toString(4).getBytes());
} catch(IOException ex) {
LoggerFactory.getLogger("Settings").warn("Failed to create new settings file: "+ex);
LOG.warn("Failed to create new settings file: "+ex);
}
return;
} catch(IOException | JSONException e) {
LoggerFactory.getLogger("Settings").warn("Failed to load server settings: "+e);
LOG.warn("Failed to load server settings: "+e);
}
LoggerFactory.getLogger("Settings").info("serversettings.json loaded from " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
LOG.info("serversettings.json loaded from " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
}
/**
@@ -127,7 +129,7 @@ public class SettingsManager implements GuildSettingsManager<Settings>
try {
Files.write(OtherUtil.getPath(SETTINGS_FILE), obj.toString(4).getBytes());
} catch(IOException ex){
LoggerFactory.getLogger("Settings").warn("Failed to write to file: "+ex);
LOG.warn("Failed to write to file: "+ex);
}
}
}
@@ -18,9 +18,9 @@ package com.jagrosh.jmusicbot.utils;
import com.jagrosh.jmusicbot.audio.RequestMetadata.UserInfo;
import java.util.List;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
/**
*
@@ -94,7 +94,7 @@ public class FormatUtil {
public static String listOfRoles(List<Role> list, String query)
{
String out = " Multiple text channels found matching \""+query+"\":";
String out = " Multiple roles found matching \""+query+"\":";
for(int i=0; i<6 && i<list.size(); i++)
out+="\n - "+list.get(i).getName()+" (ID:"+list.get(i).getId()+")";
if(list.size()>6)
@@ -43,7 +43,11 @@ public class OtherUtil
public final static String NEW_VERSION_AVAILABLE = "There is a new version of JMusicBot available!\n"
+ "Current version: %s\n"
+ "New Version: %s\n\n"
+ "Please visit https://github.com/jagrosh/MusicBot/releases/latest to get the latest release.";
+ "Please visit https://github.com/chrisb09/MusicBot/releases to get the latest release."
+ "If you are using docker (or docker-compose), please pull the new image with:"
+ "Docker: docker pull chrisb09/jmusicbot"
+ "Docker-compose: docker-compose pull"+
" and don't forget to restart the container.";
private final static String WINDOWS_INVALID_PATH = "c:\\windows\\system32\\";
/**
@@ -188,7 +192,7 @@ public class OtherUtil
try
{
Response response = new OkHttpClient.Builder().build()
.newCall(new Request.Builder().get().url("https://api.github.com/repos/jagrosh/MusicBot/releases/latest").build())
.newCall(new Request.Builder().get().url("https://api.github.com/repos/chrisb09/MusicBot/releases/latest").build())
.execute();
ResponseBody body = response.body();
if(body != null)
@@ -224,7 +228,9 @@ public class OtherUtil
ApplicationInfo info = jda.retrieveApplicationInfo().complete();
if (info.isBotPublic())
return "\"Public Bot\" is enabled. Using JMusicBot as a public bot is not supported. Please disable it in the "
+ "Developer Dashboard at https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot.";
+ "Developer Dashboard at https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot ."
+ "You may also need to disable all Installation Contexts at https://discord.com/developers/applications/"
+ jda.getSelfUser().getId() + "/installation .";
return null;
}
@@ -0,0 +1,193 @@
package com.jagrosh.jmusicbot.utils;
import com.sedmelluq.lava.extensions.youtuberotator.planner.*;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
import dev.lavalink.youtube.clients.Web;
import org.json.JSONObject;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.chromium.ChromiumDriverLogLevel;
import org.openqa.selenium.devtools.NetworkInterceptor;
import org.openqa.selenium.os.ExecutableFinder;
import org.openqa.selenium.remote.http.Contents;
import org.openqa.selenium.remote.http.Filter;
import org.openqa.selenium.remote.http.HttpMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import io.github.bonigarcia.wdm.WebDriverManager;
public class YouTubeUtil {
private final static Logger LOGGER = LoggerFactory.getLogger(YouTubeUtil.class);
public enum RoutingPlanner {
NONE,
ROTATE_ON_BAN,
LOAD_BALANCE,
NANO_SWITCH,
ROTATING_NANO_SWITCH
}
public static IpBlock parseIpBlock(String cidr) {
if (Ipv6Block.isIpv6CidrBlock(cidr))
return new Ipv6Block(cidr);
if (Ipv4Block.isIpv4CidrBlock(cidr))
return new Ipv4Block(cidr);
throw new IllegalArgumentException("Could not parse CIDR " + cidr);
}
public static AbstractRoutePlanner createRouterPlanner(RoutingPlanner routingPlanner, List<IpBlock> ipBlocks) {
switch (routingPlanner) {
case NONE:
return null;
case ROTATE_ON_BAN:
return new RotatingIpRoutePlanner(ipBlocks);
case LOAD_BALANCE:
return new BalancingIpRoutePlanner(ipBlocks);
case NANO_SWITCH:
return new NanoIpRoutePlanner(ipBlocks, true);
case ROTATING_NANO_SWITCH:
return new RotatingNanoIpRoutePlanner(ipBlocks);
default:
throw new IllegalArgumentException("Unknown RoutingPlanner value provided");
}
}
public static void GetPoTokenAndVisitorData(String userChromePath, String userChromeDriverPath, boolean headless)
{
//
// 1. Find a Chrome install & launch the WebDriver
//
WebDriverManager wdm = WebDriverManager.chromedriver();
if (userChromePath != null) {
wdm.browserVersionDetectionCommand(userChromePath + " --version");
}
else if (wdm.getBrowserPath().isEmpty())
{
// try finding a Chromium browser instead (Linux users usually install Chromium instead of Google Chrome)
wdm = WebDriverManager.chromiumdriver();
if (wdm.getBrowserPath().isEmpty()) {
LOGGER.error("Could not obtain a PO token for YouTube playback, because no Chrome browser could be found. Please install Google Chrome or Chromium!");
return;
}
}
if (userChromeDriverPath == null)
// Automatically download & set up ChromeDriver for the Chrome/Chromium version that the user has installed
wdm.setup();
else {
// Find & use the ChromeDriver executable the user provided
String path = new ExecutableFinder().find(userChromeDriverPath);
if (path == null) {
LOGGER.error("Could not obtain a PO token for YouTube playback, because the specified ChromeDriver could not be found. " +
"Please check your config's value of the chromedriverpath, or set it to \"AUTO\" to have JMusicBot automatically download ChromeDriver");
return;
}
LOGGER.info("Using ChromeDriver at {}", path);
System.setProperty("webdriver.chrome.driver", path);
}
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setBinary(userChromePath != null ? userChromePath : wdm.getBrowserPath().get().toString());
if (!headless)
chromeOptions.addArguments("--auto-open-devtools-for-tabs");
else
chromeOptions.addArguments("--headless=new");
ChromeDriverService.Builder chromeDriverBuilder = new ChromeDriverService.Builder();
if (LOGGER.isDebugEnabled())
chromeDriverBuilder.withLogLevel(ChromiumDriverLogLevel.DEBUG);
WebDriver driver = new ChromeDriver(chromeDriverBuilder.build(), chromeOptions);
//
// 2. Setup a network interceptor to intercept the player API request & obtain po token and visitor data
//
// as the network interceptor below runs on a different thread, we'll have it notify us on this/current thread
// whether retrieving the data failed (completes with the Exception) or succeeded (completes with null)
CompletableFuture<Exception> interceptorFuture = new CompletableFuture<>();
NetworkInterceptor interceptor = new NetworkInterceptor(driver,
(Filter) next -> req -> {
if (req.getMethod() != HttpMethod.POST || !req.getUri().contains("/youtubei/v1/player"))
return next.execute(req);
try {
String rawBody = Contents.string(req);
JSONObject body = new JSONObject(rawBody);
String visitorData = body
.getJSONObject("context")
.getJSONObject("client")
.getString("visitorData");
String poToken = body
.getJSONObject("serviceIntegrityDimensions")
.getString("poToken");
LOGGER.info("Successfully retrieved PO token & visitor data for YouTube playback.");
LOGGER.debug("PO token: {}", poToken);
LOGGER.debug("Visitor Data: {}", visitorData);
if (poToken.length() < 160)
LOGGER.warn("There is a high chance that the retrieved PO token will not work. Try on a different network.");
Web.setPoTokenAndVisitorData(poToken, visitorData);
interceptorFuture.complete(null);
} catch (Exception e) {
interceptorFuture.complete(e);
}
return next.execute(req);
});
//
// 3. Navigate to YouTube & start the player
//
try {
driver.get("https://www.youtube.com/embed/aqz-KE-bpKQ");
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(15));
WebElement moviePlayer = driver.findElement(By.cssSelector("#movie_player"));
moviePlayer.click();
try {
// Wait for the network interceptor to tell us if it got the data
Exception result = interceptorFuture.get(20, TimeUnit.SECONDS);
if (result != null)
throw new Exception("Failed to read YouTube player request body", result);
} catch (TimeoutException e) {
throw new Exception("Timed out waiting for YouTube player request");
}
} catch (Exception e) {
LOGGER.error("Failed to obtain PO token for YouTube playback: {}", e.getMessage());
LOGGER.debug("Exception:", e);
} finally {
interceptor.close();
// Only quit the browser when not in headless mode. Helps with troubleshooting.
if (headless)
driver.quit();
}
}
}
@@ -0,0 +1,91 @@
package com.jagrosh.jmusicbot.utils;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.spi.FilterReply;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import java.nio.file.Files;
/**
* A logback turbo filter, used retrieve the YouTube OAuth2 refresh token that gets logged once authorized with YouTube.
*
* @author Michaili K. <git@michaili.dev>
*/
public class YoutubeOauth2TokenHandler extends TurboFilter {
public final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(YoutubeOauth2TokenHandler.class);
private Data data;
public void init()
{
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.addTurboFilter(this);
}
public Data getData()
{
return data;
}
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t)
{
if (!logger.getName().equals("dev.lavalink.youtube.http.YoutubeOauth2Handler"))
return FilterReply.NEUTRAL;
if (format.equals("OAUTH INTEGRATION: To give youtube-source access to your account, go to {} and enter code {}"))
{
this.data = new Data((String) params[0], (String) params[1]);
return FilterReply.NEUTRAL;
}
if (format.equals("OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. ({})"))
{
LOGGER.info(
"Authorization successful & retrieved token! Storing the token in {}",
OtherUtil.getPath("youtubetoken.txt").toAbsolutePath()
);
try
{
Files.write(OtherUtil.getPath("youtubetoken.txt"), params[0].toString().getBytes());
}
catch (Exception e)
{
LOGGER.error(
"Failed to write the YouTube OAuth2 refresh token to storage! You will need to authorize again on the next reboot",
e
);
}
return FilterReply.DENY;
}
return FilterReply.NEUTRAL;
}
public static class Data
{
private final String authorisationUrl;
private final String code;
private Data(String authorisationUrl, String code)
{
this.authorisationUrl = authorisationUrl;
this.code = code;
}
public String getCode()
{
return code;
}
public String getAuthorisationUrl()
{
return authorisationUrl;
}
}
}
+51
View File
@@ -48,6 +48,14 @@ game = "DEFAULT"
status = ONLINE
// If you set this to true, the bot will use a Google account to play YouTube tracks.
// This is only needed if you are experiencing issues with YouTube playback.
// Once enabled, instructions for login will be written to the console and DMs.
// DO NOT use your main Google account! Use an alternative account.
youtubeoauth2=false
// If you set this to true, the bot will list the title of the song it is currently playing in its
// "Playing" status. Note that this will ONLY work if the bot is playing music on ONE guild;
// if the bot is playing on multiple guilds, this will not work.
@@ -200,6 +208,49 @@ aliases {
loglevel = info
// This sets the executable path of where a Chrome or Chromium installation is located JMusicBot can use.
// JMusicBot may launch a web browser to get around anti-bot checks.
// Set this to "AUTO" to make JMusicBot attempt to find a Chrome/Chromium installation itself.
chromepath = "AUTO"
// This sets the executable path of where a ChromeDriver installation is located JMusicBot can use.
// Set this to "AUTO" to make JMusicBot automatically download the ChromeDriver version
// which matches the installed Chrome version.
chromedriverpath = "AUTO"
// This sets whether JMusicBot should launch Chrome/Chromium instances without a GUI.
// It is recommended to leave this set to true.
// If set to false, JMusicBot will not close the browser automatically (even when finished)
// and will automatically open DevTools for troubleshooting purposes.
chromeheadless = true
// This configures the routing planner to use for IP rotation on YouTube.
// When set, you need to also set an IP block below.
// The following routing strategies are available:
// NONE = Disable
// ROTATE_ON_BAN = Switch IP when currently used address gets banned.
// LOAD_BALANCE = Selects random IP for each track playback.
// NANO_SWITCH = Selects IPv6 address based on current nanosecond. Recommended for one /64 IPv6 blocks.
// ROTATING_NANO_SWITCH = Same as above, but uses the next /64 IPv6 block in the list in case of a ban.
ytroutingplanner = NONE
// The IPv4 and IPv6 blocks to use when using the above routing planner.
// This does nothing when ytroutingplanner is set to none.
// The list accepts a list of IPv4 or IPv6 CIDR blocks (not a mix of them however)
// IPv4 example: [ "192.0.2.183", "198.51.100.153/29" ]
// IPv6 example: [ "2a02:1234:5678:9abc::/64" ]
ytipblocks = [ ]
// Transforms are used to modify specific play inputs and convert them to different kinds of inputs
// These are quite complicated to use, and have limited use-cases, but in theory allow for rough
// whitelists or blacklists, roundabout loading from some sources, and customization of how things are