mirror of
https://github.com/chrisb09/MusicBot.git
synced 2026-11-05 17:02:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f951dc63 | ||
|
|
01eee8813a | ||
|
|
3df09a4cd2 | ||
|
|
0084ba7baf | ||
|
|
35e756df9b | ||
|
|
426e28ed8c | ||
|
|
dd72baca0d | ||
|
|
ac4e09cbd9 | ||
|
|
fd01d94cf2 | ||
|
|
95f72937c8 | ||
|
|
792cb7a8b7 | ||
|
|
03ba83f06b | ||
|
|
3b3a483b17 | ||
|
|
1874252ff6 | ||
|
|
aa8e430ff1 | ||
|
|
204bb6f04a | ||
|
|
9ec21bf329 | ||
|
|
b419191bad | ||
|
|
aa89145ae8 | ||
|
|
293733f0e7 | ||
|
|
7be02b1fe2 | ||
|
|
1ac46d9ec5 | ||
|
|
026ce87440 | ||
|
|
e5f34cc900 | ||
|
|
c9a2477f86 | ||
|
|
61a18a1db7 | ||
|
|
7fb44e296b | ||
|
|
4ae9f05776 | ||
|
|
84facb8b82 | ||
|
|
070938b326 | ||
|
|
58a9e76edb | ||
|
|
96ffcd8291 | ||
|
|
844a1bb75a | ||
|
|
6094e49ab2 | ||
|
|
88ce3e1149 | ||
|
|
463bf75783 | ||
|
|
348fc3ceec | ||
|
|
30eafb9239 | ||
|
|
c95f2248f3 | ||
|
|
b80b0c8459 | ||
|
|
4a8392fdb9 | ||
|
|
9d6584525c | ||
|
|
909b655f5c | ||
|
|
859e5c5862 | ||
|
|
43b63f1ba8 | ||
|
|
eca2563abf | ||
|
|
e6bfd1841f | ||
|
|
f140b41bf5 | ||
|
|
8733899aa0 | ||
|
|
e5fd05e601 | ||
|
|
6a5a9c7a75 | ||
|
|
8557f7ab8e | ||
|
|
48e62f1a50 | ||
|
|
6f12c33781 | ||
|
|
0afb3db269 | ||
|
|
81322ef993 | ||
|
|
b3ffbdba7d | ||
|
|
801b3769a1 | ||
|
|
49c3ec7960 | ||
|
|
6752695b43 | ||
|
|
bb17c984a2 | ||
|
|
a7807b96e2 | ||
|
|
2e9dd5df49 | ||
|
|
1ad16181d0 | ||
|
|
bb53bad1ff | ||
|
|
8a1066cc87 | ||
|
|
23c2dfa78e | ||
|
|
18bf6ac0f6 | ||
|
|
fd6de60fc7 | ||
|
|
4c8ff655d8 | ||
|
|
ac8661c904 | ||
|
|
4f926baf76 | ||
|
|
c5ecf89852 | ||
|
|
a52103620a | ||
|
|
fa5e33e2a5 | ||
|
|
781a94ea91 | ||
|
|
ff1ede9d12 | ||
|
|
90d9a1631a | ||
|
|
c4502bbe16 | ||
|
|
2d4f2671c9 | ||
|
|
e595de2033 | ||
|
|
53875f2791 | ||
|
|
cd7e643228 | ||
|
|
0e1f0d3bfa | ||
|
|
2c76164f6d | ||
|
|
788cd40f2c | ||
|
|
1e12e4e01d | ||
|
|
c4cbfe2a11 | ||
|
|
bbed9e8990 | ||
|
|
354a0b3def | ||
|
|
2faef67b30 | ||
|
|
aee076c854 | ||
|
|
b76f6fa07c | ||
|
|
ec4e9e624c | ||
|
|
a7be2c4602 | ||
|
|
6007d3e99b | ||
|
|
a17b2f20ee | ||
|
|
4375d4caa8 | ||
|
|
c22807bcbb | ||
|
|
baf629a910 | ||
|
|
c4e11d682e | ||
|
|
041ab27b94 | ||
|
|
8457f944cd |
No files matched your search
@@ -7,7 +7,7 @@ jobs:
|
||||
build:
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: circleci/openjdk:8u171-jdk
|
||||
- image: cimg/openjdk:11.0
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
|
||||
@@ -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: "
|
||||
- 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
|
||||
@@ -1,19 +1,22 @@
|
||||
name: Test
|
||||
name: Build and Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 8
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '8'
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
- name: Build with Maven
|
||||
- name: Build and Test with Maven
|
||||
run: mvn --batch-mode --update-snapshots verify
|
||||
@@ -18,10 +18,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 8
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '8'
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
- name: Set Version
|
||||
@@ -62,8 +62,9 @@ jobs:
|
||||
artifacts: "*.jar"
|
||||
body: |
|
||||
${{ github.event.inputs.info }}
|
||||
|
||||
---
|
||||
### Setup
|
||||
https://jmusicbot.com/setup
|
||||
https://jmusicbot.com/config
|
||||
# Download *only* the `.jar` file below!
|
||||
# Download: [JMusicBot-${{ github.event.inputs.version_number }}.jar](https://github.com/jagrosh/MusicBot/releases/download/${{ github.event.inputs.version_number }}/JMusicBot-${{ github.event.inputs.version_number }}.jar)
|
||||
+2
-1
@@ -4,4 +4,5 @@
|
||||
/test/
|
||||
*.json
|
||||
*.txt
|
||||
nb*.xml
|
||||
nb*.xml
|
||||
dockerfile
|
||||
@@ -1,5 +1,79 @@
|
||||
<img align="right" src="https://i.imgur.com/zrE80HY.png" height="200" width="200">
|
||||
|
||||
## Demo
|
||||
|
||||
[https://discord.gg/4cp9CvjwaW](https://discord.gg/4cp9CvjwaW)
|
||||
|
||||
# This fork
|
||||
|
||||
Essentially, this is just a merge of three pull requests:
|
||||
|
||||
### 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
|
||||
|
||||
And I've now bumped the versions up to the newest [lavaplayer](https://mvnrepository.com/artifact/dev.arbjerg/lavaplayer)/[lavalink](https://maven.lavalink.dev/#/releases/dev/lavalink/youtube/common) versions. I will try to update this automatically in the future.
|
||||
|
||||
|
||||
### 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).
|
||||
|
||||
## Easy deployment via docker
|
||||
|
||||
Additionally, i thought a corresponding docker image would make things better:
|
||||
|
||||
The image can be found on [dockerhub](https://hub.docker.com/repository/docker/chrisb09/jmusicbot/general)
|
||||
|
||||
Docker-compose:
|
||||
|
||||
```yaml
|
||||
version: '2.2'
|
||||
|
||||
services:
|
||||
jmusicbot:
|
||||
image: chrisb09/jmusicbot:latest
|
||||
container_name: jmusicbot
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config:/jmb/config
|
||||
```
|
||||
|
||||
although, obviously you should change `./config` to whatever path is right for your setup.
|
||||
Source code for the docker image is also on [Github](https://github.com/chrisb09/jmb-container).
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
[](https://github.com/jagrosh/MusicBot/releases/latest)
|
||||
@@ -7,8 +81,8 @@
|
||||
[](https://github.com/jagrosh/MusicBot/releases/latest)
|
||||
[](https://github.com/jagrosh/MusicBot/blob/master/LICENSE)
|
||||
[](https://discord.gg/0p9LSGoRLu6Pet0k)<br>
|
||||
[](https://circleci.com/gh/jagrosh/MusicBot)
|
||||
[](https://ci.appveyor.com/project/jagrosh/musicbot/branch/master)
|
||||
[](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)
|
||||
|
||||
A cross-platform Discord music bot with a clean interface, and that is easy to set up and run yourself!
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.jagrosh</groupId>
|
||||
<artifactId>JMusicBot</artifactId>
|
||||
<version>Snapshot</version>
|
||||
<version>0.4.9</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dv8tion</id>
|
||||
@@ -20,53 +20,68 @@
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
<updatePolicy>always</updatePolicy>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>m2.duncte123.dev</id>
|
||||
<name>m2-duncte123</name>
|
||||
<url>https://m2.duncte123.dev/releases</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>arbjergDev</id>
|
||||
<name>Lavalink Repository</name>
|
||||
<url>https://maven.lavalink.dev/releases</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!-- Discord Dependencies -->
|
||||
<dependency>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
<version>4.3.0_324</version>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
<version>4.4.1_353</version>
|
||||
</dependency>
|
||||
<!-- using a fork of this to fix some issues faster -->
|
||||
<!-- dependency>
|
||||
<groupId>com.sedmelluq</groupId>
|
||||
<artifactId>lavaplayer</artifactId>
|
||||
<version>1.3.78</version>
|
||||
</dependency -->
|
||||
<dependency>
|
||||
<groupId>com.github.jagrosh</groupId>
|
||||
<artifactId>lavaplayer</artifactId>
|
||||
<version>jmusicbot-SNAPSHOT</version>
|
||||
<dependency>
|
||||
<groupId>com.github.Nagami-Yukki</groupId>
|
||||
<artifactId>JDA-Utilities</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
<!-- this is needed, but isn't actually hosted anywhere anymore... uh -->
|
||||
<!--dependency>
|
||||
<groupId>com.sedmelluq</groupId>
|
||||
<artifactId>lavaplayer-natives-extra</artifactId>
|
||||
<version>1.3.13</version>
|
||||
</dependency-->
|
||||
|
||||
<!-- Music Dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.jagrosh</groupId>
|
||||
<artifactId>jda-utilities</artifactId>
|
||||
<version>3.0.5</version>
|
||||
<type>pom</type>
|
||||
<groupId>dev.arbjerg</groupId>
|
||||
<artifactId>lavaplayer</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.arbjerg</groupId>
|
||||
<artifactId>lavaplayer-ext-youtube-rotator</artifactId>
|
||||
<version>2.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.lavalink.youtube</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.11.5</version>
|
||||
</dependency>
|
||||
<!-- jitpack for now, we need to version this correctly later -->
|
||||
<dependency>
|
||||
<groupId>com.github.jagrosh</groupId>
|
||||
<artifactId>JLyrics</artifactId>
|
||||
<version>-SNAPSHOT</version>
|
||||
<version>master-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!--dependency>
|
||||
<groupId>com.jagrosh</groupId>
|
||||
<artifactId>JLyrics</artifactId>
|
||||
<version>0.6</version>
|
||||
</dependency-->
|
||||
<dependency>
|
||||
<groupId>com.dunctebot</groupId>
|
||||
<artifactId>sourcemanagers</artifactId>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Misc Internal Dependencies -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.2.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.typesafe</groupId>
|
||||
@@ -76,8 +91,37 @@
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.14.2</version>
|
||||
<version>1.15.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-java</artifactId>
|
||||
<version>4.27.0</version>
|
||||
<!-- https://github.com/SeleniumHQ/selenium/issues/13791 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-devtools-v85</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-devtools-v129</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-devtools-v130</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.bonigarcia</groupId>
|
||||
<artifactId>webdrivermanager</artifactId>
|
||||
<version>5.9.2</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<!-- Testing Dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
@@ -91,7 +135,7 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -133,10 +177,10 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
</project>
|
||||
@@ -9,9 +9,9 @@ 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 \
|
||||
| grep -i "browser_download_url.*\.jar" \
|
||||
| sed 's/.*\(http.*\)"/\1/')
|
||||
FILENAME=$(echo $URL | sed 's/.*\/\([^\/]*\)/\1/')
|
||||
if [ -f $FILENAME ]; then
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
@@ -139,7 +148,6 @@ public class Bot
|
||||
{
|
||||
ah.stopAndClear();
|
||||
ah.getPlayer().destroy();
|
||||
nowplaying.updateTopic(g.getIdLong(), ah, true);
|
||||
}
|
||||
});
|
||||
jda.shutdown();
|
||||
|
||||
@@ -16,14 +16,18 @@
|
||||
package com.jagrosh.jmusicbot;
|
||||
|
||||
import com.jagrosh.jmusicbot.entities.Prompt;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
|
||||
@@ -40,10 +44,15 @@ public class BotConfig
|
||||
private final static String END_TOKEN = "/// END OF JMUSICBOT CONFIG ///";
|
||||
|
||||
private Path path = null;
|
||||
private String token, prefix, altprefix, helpWord, playlistsFolder,
|
||||
successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji;
|
||||
private boolean stayInChannel, songInGame, npImages, updatealerts, useEval, dbots;
|
||||
private String token, prefix, altprefix, helpWord, playlistsFolder, logLevel,
|
||||
successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji,
|
||||
chromePath, chromeDriverPath, evalEngine;
|
||||
private YouTubeUtil.RoutingPlanner ytRoutingPlanner;
|
||||
private List<IpBlock> ytIpBlocks;
|
||||
private boolean youtubeOauth2, stayInChannel, songInGame, npImages, updatealerts, chromeHeadless, useEval, dbots;
|
||||
private long owner, maxSeconds, aloneTimeUntilStop;
|
||||
private int maxYTPlaylistPages;
|
||||
private double skipratio;
|
||||
private OnlineStatus status;
|
||||
private Activity game;
|
||||
private Config aliases, transforms;
|
||||
@@ -63,13 +72,7 @@ public class BotConfig
|
||||
try
|
||||
{
|
||||
// get the path to the config, default config.txt
|
||||
path = OtherUtil.getPath(System.getProperty("config.file", System.getProperty("config", "config.txt")));
|
||||
if(path.toFile().exists())
|
||||
{
|
||||
if(System.getProperty("config.file") == null)
|
||||
System.setProperty("config.file", System.getProperty("config", path.toAbsolutePath().toString()));
|
||||
ConfigFactory.invalidateCaches();
|
||||
}
|
||||
path = getConfigPath();
|
||||
|
||||
// load in the config file, plus the default values
|
||||
//Config config = ConfigFactory.parseFile(path.toFile()).withFallback(ConfigFactory.load());
|
||||
@@ -88,16 +91,26 @@ 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");
|
||||
updatealerts = config.getBoolean("updatealerts");
|
||||
logLevel = config.getString("loglevel");
|
||||
useEval = config.getBoolean("eval");
|
||||
evalEngine = config.getString("evalengine");
|
||||
maxSeconds = config.getLong("maxtime");
|
||||
maxYTPlaylistPages = config.getInt("maxytplaylistpages");
|
||||
aloneTimeUntilStop = config.getLong("alonetimeuntilstop");
|
||||
playlistsFolder = config.getString("playlistsfolder");
|
||||
aliases = config.getConfig("aliases");
|
||||
chromePath = config.getString("chromepath");
|
||||
chromeDriverPath = config.getString("chromedriverpath");
|
||||
chromeHeadless = config.getBoolean("chromeheadless");
|
||||
ytRoutingPlanner = config.getEnum(YouTubeUtil.RoutingPlanner.class, "ytroutingplanner");
|
||||
ytIpBlocks = config.getStringList("ytipblocks").stream().map(YouTubeUtil::parseIpBlock).collect(Collectors.toList());
|
||||
transforms = config.getConfig("transforms");
|
||||
skipratio = config.getDouble("skipratio");
|
||||
dbots = owner == 113156185389092864L;
|
||||
|
||||
// we may need to write a new config file
|
||||
@@ -161,19 +174,9 @@ public class BotConfig
|
||||
|
||||
private void writeToFile()
|
||||
{
|
||||
String original = OtherUtil.loadResource(this, "/reference.conf");
|
||||
byte[] bytes;
|
||||
if(original==null)
|
||||
{
|
||||
bytes = ("token = "+token+"\r\nowner = "+owner).getBytes();
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes = original.substring(original.indexOf(START_TOKEN)+START_TOKEN.length(), original.indexOf(END_TOKEN))
|
||||
.replace("BOT_TOKEN_HERE", token)
|
||||
byte[] bytes = loadDefaultConfig().replace("BOT_TOKEN_HERE", token)
|
||||
.replace("0 // OWNER ID", Long.toString(owner))
|
||||
.trim().getBytes();
|
||||
}
|
||||
try
|
||||
{
|
||||
Files.write(path, bytes);
|
||||
@@ -186,6 +189,43 @@ public class BotConfig
|
||||
}
|
||||
}
|
||||
|
||||
private static String loadDefaultConfig()
|
||||
{
|
||||
String original = OtherUtil.loadResource(new JMusicBot(), "/reference.conf");
|
||||
return original==null
|
||||
? "token = BOT_TOKEN_HERE\r\nowner = 0 // OWNER ID"
|
||||
: original.substring(original.indexOf(START_TOKEN)+START_TOKEN.length(), original.indexOf(END_TOKEN)).trim();
|
||||
}
|
||||
|
||||
private static Path getConfigPath()
|
||||
{
|
||||
Path path = OtherUtil.getPath(System.getProperty("config.file", System.getProperty("config", "config.txt")));
|
||||
if(path.toFile().exists())
|
||||
{
|
||||
if(System.getProperty("config.file") == null)
|
||||
System.setProperty("config.file", System.getProperty("config", path.toAbsolutePath().toString()));
|
||||
ConfigFactory.invalidateCaches();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void writeDefaultConfig()
|
||||
{
|
||||
Prompt prompt = new Prompt(null, null, true, true);
|
||||
prompt.alert(Prompt.Level.INFO, "JMusicBot Config", "Generating default config file");
|
||||
Path path = BotConfig.getConfigPath();
|
||||
byte[] bytes = BotConfig.loadDefaultConfig().getBytes();
|
||||
try
|
||||
{
|
||||
prompt.alert(Prompt.Level.INFO, "JMusicBot Config", "Writing default config file to " + path.toAbsolutePath().toString());
|
||||
Files.write(path, bytes);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot Config", "An error occurred writing the default config file: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid()
|
||||
{
|
||||
return valid;
|
||||
@@ -211,6 +251,11 @@ public class BotConfig
|
||||
return token;
|
||||
}
|
||||
|
||||
public double getSkipRatio()
|
||||
{
|
||||
return skipratio;
|
||||
}
|
||||
|
||||
public long getOwnerId()
|
||||
{
|
||||
return owner;
|
||||
@@ -246,6 +291,11 @@ public class BotConfig
|
||||
return game;
|
||||
}
|
||||
|
||||
public boolean isGameNone()
|
||||
{
|
||||
return game != null && game.getName().equalsIgnoreCase("none");
|
||||
}
|
||||
|
||||
public OnlineStatus getStatus()
|
||||
{
|
||||
return status;
|
||||
@@ -256,6 +306,11 @@ public class BotConfig
|
||||
return helpWord;
|
||||
}
|
||||
|
||||
public boolean useYoutubeOauth2()
|
||||
{
|
||||
return youtubeOauth2;
|
||||
}
|
||||
|
||||
public boolean getStay()
|
||||
{
|
||||
return stayInChannel;
|
||||
@@ -280,12 +335,47 @@ public class BotConfig
|
||||
{
|
||||
return updatealerts;
|
||||
}
|
||||
|
||||
public String getLogLevel()
|
||||
{
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public String getChromePath()
|
||||
{
|
||||
return chromePath.equals("AUTO") ? null : chromePath;
|
||||
}
|
||||
|
||||
public String getChromeDriverPath()
|
||||
{
|
||||
return chromeDriverPath.equals("AUTO") ? null : chromeDriverPath;
|
||||
}
|
||||
|
||||
public boolean getChromeHeadless()
|
||||
{
|
||||
return chromeHeadless;
|
||||
}
|
||||
|
||||
public YouTubeUtil.RoutingPlanner getYTRoutingPlanner()
|
||||
{
|
||||
return ytRoutingPlanner;
|
||||
}
|
||||
|
||||
public List<IpBlock> getYTIpBlocks()
|
||||
{
|
||||
return ytIpBlocks;
|
||||
}
|
||||
|
||||
public boolean useEval()
|
||||
{
|
||||
return useEval;
|
||||
}
|
||||
|
||||
public String getEvalEngine()
|
||||
{
|
||||
return evalEngine;
|
||||
}
|
||||
|
||||
public boolean useNPImages()
|
||||
{
|
||||
return npImages;
|
||||
@@ -296,9 +386,14 @@ public class BotConfig
|
||||
return maxSeconds;
|
||||
}
|
||||
|
||||
public int getMaxYTPlaylistPages()
|
||||
{
|
||||
return maxYTPlaylistPages;
|
||||
}
|
||||
|
||||
public String getMaxTime()
|
||||
{
|
||||
return FormatUtil.formatTime(maxSeconds * 1000);
|
||||
return TimeUtil.formatTime(maxSeconds * 1000);
|
||||
}
|
||||
|
||||
public long getAloneTimeUntilStop()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
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.*;
|
||||
@@ -34,8 +35,12 @@ import net.dv8tion.jda.api.*;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
||||
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import ch.qos.logback.classic.Level;
|
||||
|
||||
import static com.jagrosh.jmusicbot.utils.YouTubeUtil.GetPoTokenAndVisitorData;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,44 +48,135 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class JMusicBot
|
||||
{
|
||||
public final static String PLAY_EMOJI = "\u25B6"; // ▶
|
||||
public final static String PAUSE_EMOJI = "\u23F8"; // ⏸
|
||||
public final static String STOP_EMOJI = "\u23F9"; // ⏹
|
||||
public final static 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,
|
||||
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_MANAGE, Permission.MESSAGE_EXT_EMOJI,
|
||||
Permission.MANAGE_CHANNEL, Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE};
|
||||
Permission.VOICE_CONNECT, Permission.VOICE_SPEAK, Permission.NICKNAME_CHANGE};
|
||||
public final static GatewayIntent[] INTENTS = {GatewayIntent.DIRECT_MESSAGES, GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGE_REACTIONS, GatewayIntent.GUILD_VOICE_STATES};
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args)
|
||||
{
|
||||
// startup log
|
||||
Logger log = LoggerFactory.getLogger("Startup");
|
||||
|
||||
if(args.length > 0)
|
||||
switch(args[0].toLowerCase())
|
||||
{
|
||||
case "generate-config":
|
||||
BotConfig.writeDefaultConfig();
|
||||
return;
|
||||
default:
|
||||
}
|
||||
startBot();
|
||||
}
|
||||
|
||||
private static void startBot()
|
||||
{
|
||||
// create prompt to handle startup
|
||||
Prompt prompt = new Prompt("JMusicBot", "Switching to nogui mode. You can manually start in nogui mode by including the -Dnogui=true flag.");
|
||||
Prompt prompt = new Prompt("JMusicBot");
|
||||
|
||||
// get and check latest version
|
||||
String version = OtherUtil.checkVersion(prompt);
|
||||
|
||||
// check for valid java version
|
||||
if(!System.getProperty("java.vm.name").contains("64"))
|
||||
prompt.alert(Prompt.Level.WARNING, "Java Version", "It appears that you may not be using a supported Java version. Please use 64-bit java.");
|
||||
// startup checks
|
||||
OtherUtil.checkVersion(prompt);
|
||||
OtherUtil.checkJavaVersion(prompt);
|
||||
|
||||
// load config
|
||||
BotConfig config = new BotConfig(prompt);
|
||||
config.load();
|
||||
if(!config.isValid())
|
||||
return;
|
||||
LOG.info("Loaded config from " + config.getConfigLocation());
|
||||
|
||||
// set log level from config
|
||||
((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(
|
||||
Level.toLevel(config.getLogLevel(), Level.INFO));
|
||||
|
||||
// set up the listener
|
||||
EventWaiter waiter = new EventWaiter();
|
||||
SettingsManager settings = new SettingsManager();
|
||||
Bot bot = new Bot(waiter, config, settings);
|
||||
CommandClient client = createCommandClient(config, settings, bot);
|
||||
|
||||
|
||||
if(!prompt.isNoGUI())
|
||||
{
|
||||
try
|
||||
{
|
||||
GUI gui = new GUI(bot);
|
||||
bot.setGUI(gui);
|
||||
gui.init();
|
||||
|
||||
LOG.info("Loaded config from " + config.getConfigLocation());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Could not start GUI. If you are "
|
||||
+ "running on a server or in a location where you cannot display a "
|
||||
+ "window, please run in nogui mode using the -Dnogui=true flag.");
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to log in and start
|
||||
try
|
||||
{
|
||||
JDA jda = JDABuilder.create(config.getToken(), Arrays.asList(INTENTS))
|
||||
.enableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE)
|
||||
.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.EMOTE, CacheFlag.ONLINE_STATUS)
|
||||
.setActivity(config.isGameNone() ? null : Activity.playing("loading..."))
|
||||
.setStatus(config.getStatus()==OnlineStatus.INVISIBLE || config.getStatus()==OnlineStatus.OFFLINE
|
||||
? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB)
|
||||
.addEventListeners(client, waiter, new Listener(bot))
|
||||
.setBulkDeleteSplittingEnabled(true)
|
||||
.build();
|
||||
bot.setJDA(jda);
|
||||
|
||||
// check if something about the current startup is not supported
|
||||
String unsupportedReason = OtherUtil.getUnsupportedBotReason(jda);
|
||||
if (unsupportedReason != null)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "JMusicBot cannot be run on this Discord bot: " + unsupportedReason);
|
||||
try{ Thread.sleep(5000);}catch(InterruptedException ignored){} // this is awful but until we have a better way...
|
||||
jda.shutdown();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// other check that will just be a warning now but may be required in the future
|
||||
// check if the user has changed the prefix and provide info about the
|
||||
// message content intent
|
||||
if(!"@mention".equals(config.getPrefix()))
|
||||
{
|
||||
LOG.info("JMusicBot", "You currently have a custom prefix set. "
|
||||
+ "If your prefix is not working, make sure that the 'MESSAGE CONTENT INTENT' is Enabled "
|
||||
+ "on https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot");
|
||||
}
|
||||
}
|
||||
catch (LoginException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nPlease make sure you are "
|
||||
+ "editing the correct config.txt file, and that you have used the "
|
||||
+ "correct token (not the 'secret'!)\nConfig Location: " + config.getConfigLocation());
|
||||
System.exit(1);
|
||||
}
|
||||
catch(IllegalArgumentException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "Some aspect of the configuration is "
|
||||
+ "invalid: " + ex + "\nConfig Location: " + config.getConfigLocation());
|
||||
System.exit(1);
|
||||
}
|
||||
catch(ErrorResponseException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nInvalid reponse returned when "
|
||||
+ "attempting to connect, please make sure you're connected to the internet");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
GetPoTokenAndVisitorData(config.getChromePath(), config.getChromeDriverPath(), config.getChromeHeadless());
|
||||
}
|
||||
|
||||
private static CommandClient createCommandClient(BotConfig config, SettingsManager settings, Bot bot)
|
||||
{
|
||||
// instantiate about command
|
||||
AboutCommand aboutCommand = new AboutCommand(Color.BLUE.brighter(),
|
||||
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot) (v"+version+")",
|
||||
"a music bot that is [easy to host yourself!](https://github.com/jagrosh/MusicBot) (v" + OtherUtil.getCurrentVersion() + ")",
|
||||
new String[]{"High-quality music playback", "FairQueue™ Technology", "Easy to host yourself"},
|
||||
RECOMMENDED_PERMS);
|
||||
aboutCommand.setIsAuthor(false);
|
||||
@@ -107,6 +203,7 @@ public class JMusicBot
|
||||
new RemoveCmd(bot),
|
||||
new SearchCmd(bot),
|
||||
new SCSearchCmd(bot),
|
||||
new SeekCmd(bot),
|
||||
new ShuffleCmd(bot),
|
||||
new SkipCmd(bot),
|
||||
|
||||
@@ -121,11 +218,12 @@ public class JMusicBot
|
||||
new VolumeCmd(bot),
|
||||
|
||||
new PrefixCmd(bot),
|
||||
new QueueTypeCmd(bot),
|
||||
new SetdjCmd(bot),
|
||||
new SkipratioCmd(bot),
|
||||
new SettcCmd(bot),
|
||||
new SetvcCmd(bot),
|
||||
|
||||
|
||||
new AutoplaylistCmd(bot),
|
||||
new DebugCmd(bot),
|
||||
new PlaylistCmd(bot),
|
||||
@@ -135,65 +233,23 @@ public class JMusicBot
|
||||
new SetstatusCmd(bot),
|
||||
new ShutdownCmd(bot)
|
||||
);
|
||||
|
||||
// enable eval if applicable
|
||||
if(config.useEval())
|
||||
cb.addCommand(new EvalCmd(bot));
|
||||
boolean nogame = false;
|
||||
if(config.getStatus()!=OnlineStatus.UNKNOWN)
|
||||
|
||||
// set status if set in config
|
||||
if(config.getStatus() != OnlineStatus.UNKNOWN)
|
||||
cb.setStatus(config.getStatus());
|
||||
if(config.getGame()==null)
|
||||
|
||||
// set game
|
||||
if(config.getGame() == null)
|
||||
cb.useDefaultGame();
|
||||
else if(config.getGame().getName().equalsIgnoreCase("none"))
|
||||
{
|
||||
else if(config.isGameNone())
|
||||
cb.setActivity(null);
|
||||
nogame = true;
|
||||
}
|
||||
else
|
||||
cb.setActivity(config.getGame());
|
||||
|
||||
if(!prompt.isNoGUI())
|
||||
{
|
||||
try
|
||||
{
|
||||
GUI gui = new GUI(bot);
|
||||
bot.setGUI(gui);
|
||||
gui.init();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
log.error("Could not start GUI. If you are "
|
||||
+ "running on a server or in a location where you cannot display a "
|
||||
+ "window, please run in nogui mode using the -Dnogui=true flag.");
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Loaded config from " + config.getConfigLocation());
|
||||
|
||||
// attempt to log in and start
|
||||
try
|
||||
{
|
||||
JDA jda = JDABuilder.create(config.getToken(), Arrays.asList(INTENTS))
|
||||
.enableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE)
|
||||
.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.EMOTE, CacheFlag.ONLINE_STATUS)
|
||||
.setActivity(nogame ? null : Activity.playing("loading..."))
|
||||
.setStatus(config.getStatus()==OnlineStatus.INVISIBLE || config.getStatus()==OnlineStatus.OFFLINE
|
||||
? OnlineStatus.INVISIBLE : OnlineStatus.DO_NOT_DISTURB)
|
||||
.addEventListeners(cb.build(), waiter, new Listener(bot))
|
||||
.setBulkDeleteSplittingEnabled(true)
|
||||
.build();
|
||||
bot.setJDA(jda);
|
||||
}
|
||||
catch (LoginException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", ex + "\nPlease make sure you are "
|
||||
+ "editing the correct config.txt file, and that you have used the "
|
||||
+ "correct token (not the 'secret'!)\nConfig Location: " + config.getConfigLocation());
|
||||
System.exit(1);
|
||||
}
|
||||
catch(IllegalArgumentException ex)
|
||||
{
|
||||
prompt.alert(Prompt.Level.ERROR, "JMusicBot", "Some aspect of the configuration is "
|
||||
+ "invalid: " + ex + "\nConfig Location: " + config.getConfigLocation());
|
||||
System.exit(1);
|
||||
}
|
||||
return cb.build();
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,10 @@ 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.PrivateChannel;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.VoiceChannel;
|
||||
import net.dv8tion.jda.api.events.ReadyEvent;
|
||||
@@ -82,9 +84,25 @@ public class Listener extends ListenerAdapter
|
||||
owner.openPrivateChannel().queue(pc -> pc.sendMessage(msg).queue());
|
||||
}
|
||||
}
|
||||
catch(Exception ex) {} // ignored
|
||||
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
|
||||
|
||||
@@ -15,11 +15,14 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jmusicbot.JMusicBot;
|
||||
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
|
||||
import com.jagrosh.jmusicbot.queue.AbstractQueue;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.jagrosh.jmusicbot.settings.RepeatMode;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
||||
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
|
||||
@@ -27,7 +30,6 @@ import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.jagrosh.jmusicbot.queue.FairQueue;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack;
|
||||
@@ -39,6 +41,8 @@ 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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -46,7 +50,12 @@ import net.dv8tion.jda.api.entities.User;
|
||||
*/
|
||||
public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
{
|
||||
private final FairQueue<QueuedTrack> queue = new FairQueue<>();
|
||||
public final static String PLAY_EMOJI = "\u25B6"; // ▶
|
||||
public final static String PAUSE_EMOJI = "\u23F8"; // ⏸
|
||||
public final static String STOP_EMOJI = "\u23F9"; // ⏹
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(AudioHandler.class);
|
||||
|
||||
private final List<AudioTrack> defaultQueue = new LinkedList<>();
|
||||
private final Set<String> votes = new HashSet<>();
|
||||
|
||||
@@ -55,12 +64,20 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
private final long guildId;
|
||||
|
||||
private AudioFrame lastFrame;
|
||||
private AbstractQueue<QueuedTrack> queue;
|
||||
|
||||
protected AudioHandler(PlayerManager manager, Guild guild, AudioPlayer player)
|
||||
{
|
||||
this.manager = manager;
|
||||
this.audioPlayer = player;
|
||||
this.guildId = guild.getIdLong();
|
||||
|
||||
this.setQueueType(manager.getBot().getSettingsManager().getSettings(guildId).getQueueType());
|
||||
}
|
||||
|
||||
public void setQueueType(QueueType type)
|
||||
{
|
||||
queue = type.createInstance(queue);
|
||||
}
|
||||
|
||||
public int addTrackToFront(QueuedTrack qtrack)
|
||||
@@ -88,7 +105,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
return queue.add(qtrack);
|
||||
}
|
||||
|
||||
public FairQueue<QueuedTrack> getQueue()
|
||||
public AbstractQueue<QueuedTrack> getQueue()
|
||||
{
|
||||
return queue;
|
||||
}
|
||||
@@ -171,7 +188,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
{
|
||||
if(!playFromDefault())
|
||||
{
|
||||
manager.getBot().getNowplayingHandler().onTrackUpdate(guildId, null, this);
|
||||
manager.getBot().getNowplayingHandler().onTrackUpdate(null);
|
||||
if(!manager.getBot().getConfig().getStay())
|
||||
manager.getBot().closeAudioConnection(guildId);
|
||||
// unpause, in the case when the player was paused and the track has been skipped.
|
||||
@@ -186,11 +203,28 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception)
|
||||
{
|
||||
if (
|
||||
exception.getMessage().equals("Sign in to confirm you're not a bot")
|
||||
|| exception.getMessage().equals("Please sign in")
|
||||
)
|
||||
LOGGER.error(
|
||||
"Track {} has failed to play: {}"
|
||||
+ "You will need to sign in to Google to play YouTube tracks. More info: https://jmusicbot.com/youtube-oauth2",
|
||||
track.getIdentifier(),
|
||||
exception.getMessage()
|
||||
);
|
||||
else
|
||||
LOGGER.error("Track {} has failed to play", track.getIdentifier(), exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackStart(AudioPlayer player, AudioTrack track)
|
||||
{
|
||||
votes.clear();
|
||||
manager.getBot().getNowplayingHandler().onTrackUpdate(guildId, track, this);
|
||||
manager.getBot().getNowplayingHandler().onTrackUpdate(track);
|
||||
}
|
||||
|
||||
|
||||
@@ -210,9 +244,9 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
{
|
||||
User u = guild.getJDA().getUserById(rm.user.id);
|
||||
if(u==null)
|
||||
eb.setAuthor(rm.user.username + "#" + rm.user.discrim, null, rm.user.avatar);
|
||||
eb.setAuthor(FormatUtil.formatUsername(rm.user), null, rm.user.avatar);
|
||||
else
|
||||
eb.setAuthor(u.getName() + "#" + u.getDiscriminator(), null, u.getEffectiveAvatarUrl());
|
||||
eb.setAuthor(FormatUtil.formatUsername(u), null, u.getEffectiveAvatarUrl());
|
||||
}
|
||||
|
||||
try
|
||||
@@ -233,12 +267,12 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
eb.setFooter("Source: " + track.getInfo().author, null);
|
||||
|
||||
double progress = (double)audioPlayer.getPlayingTrack().getPosition()/track.getDuration();
|
||||
eb.setDescription((audioPlayer.isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI)
|
||||
eb.setDescription(getStatusEmoji()
|
||||
+ " "+FormatUtil.progressBar(progress)
|
||||
+ " `[" + FormatUtil.formatTime(track.getPosition()) + "/" + FormatUtil.formatTime(track.getDuration()) + "]` "
|
||||
+ " `[" + TimeUtil.formatTime(track.getPosition()) + "/" + TimeUtil.formatTime(track.getDuration()) + "]` "
|
||||
+ FormatUtil.volumeIcon(audioPlayer.getVolume()));
|
||||
|
||||
return mb.setEmbed(eb.build()).build();
|
||||
return mb.setEmbeds(eb.build()).build();
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
@@ -248,28 +282,16 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
|
||||
Guild guild = guild(jda);
|
||||
return new MessageBuilder()
|
||||
.setContent(FormatUtil.filter(manager.getBot().getConfig().getSuccess()+" **Now Playing...**"))
|
||||
.setEmbed(new EmbedBuilder()
|
||||
.setEmbeds(new EmbedBuilder()
|
||||
.setTitle("No music playing")
|
||||
.setDescription(JMusicBot.STOP_EMOJI+" "+FormatUtil.progressBar(-1)+" "+FormatUtil.volumeIcon(audioPlayer.getVolume()))
|
||||
.setDescription(STOP_EMOJI+" "+FormatUtil.progressBar(-1)+" "+FormatUtil.volumeIcon(audioPlayer.getVolume()))
|
||||
.setColor(guild.getSelfMember().getColor())
|
||||
.build()).build();
|
||||
}
|
||||
|
||||
public String getTopicFormat(JDA jda)
|
||||
|
||||
public String getStatusEmoji()
|
||||
{
|
||||
if(isMusicPlaying(jda))
|
||||
{
|
||||
long userid = getRequestMetadata().getOwner();
|
||||
AudioTrack track = audioPlayer.getPlayingTrack();
|
||||
String title = track.getInfo().title;
|
||||
if(title==null || title.equals("Unknown Title"))
|
||||
title = track.getInfo().uri;
|
||||
return "**"+title+"** ["+(userid==0 ? "autoplay" : "<@"+userid+">")+"]"
|
||||
+ "\n" + (audioPlayer.isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI) + " "
|
||||
+ "[" + FormatUtil.formatTime(track.getDuration()) + "] "
|
||||
+ FormatUtil.volumeIcon(audioPlayer.getVolume());
|
||||
}
|
||||
else return "No music playing " + JMusicBot.STOP_EMOJI + " " + FormatUtil.volumeIcon(audioPlayer.getVolume());
|
||||
return audioPlayer.isPaused() ? PAUSE_EMOJI : PLAY_EMOJI;
|
||||
}
|
||||
|
||||
// Audio Send Handler methods
|
||||
|
||||
@@ -98,42 +98,9 @@ public class NowplayingHandler
|
||||
}
|
||||
toRemove.forEach(id -> lastNP.remove(id));
|
||||
}
|
||||
|
||||
public void updateTopic(long guildId, AudioHandler handler, boolean wait)
|
||||
{
|
||||
Guild guild = bot.getJDA().getGuildById(guildId);
|
||||
if(guild==null)
|
||||
return;
|
||||
Settings settings = bot.getSettingsManager().getSettings(guildId);
|
||||
TextChannel tchan = settings.getTextChannel(guild);
|
||||
if(tchan!=null && guild.getSelfMember().hasPermission(tchan, Permission.MANAGE_CHANNEL))
|
||||
{
|
||||
String otherText;
|
||||
String topic = tchan.getTopic();
|
||||
if(topic==null || topic.isEmpty())
|
||||
otherText = "\u200B";
|
||||
else if(topic.contains("\u200B"))
|
||||
otherText = topic.substring(topic.lastIndexOf("\u200B"));
|
||||
else
|
||||
otherText = "\u200B\n "+topic;
|
||||
String text = handler.getTopicFormat(bot.getJDA()) + otherText;
|
||||
if(!text.equals(tchan.getTopic()))
|
||||
{
|
||||
try
|
||||
{
|
||||
// normally here if 'wait' was false, we'd want to queue, however,
|
||||
// new discord ratelimits specifically limiting changing channel topics
|
||||
// mean we don't want a backlog of changes piling up, so if we hit a
|
||||
// ratelimit, we just won't change the topic this time
|
||||
tchan.getManager().setTopic(text).complete(wait);
|
||||
}
|
||||
catch(PermissionException | RateLimitedException ignore) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// "event"-based methods
|
||||
public void onTrackUpdate(long guildId, AudioTrack track, AudioHandler handler)
|
||||
public void onTrackUpdate(AudioTrack track)
|
||||
{
|
||||
// update bot status if applicable
|
||||
if(bot.getConfig().getSongInStatus())
|
||||
@@ -143,9 +110,6 @@ public class NowplayingHandler
|
||||
else
|
||||
bot.resetGame();
|
||||
}
|
||||
|
||||
// update channel topic if applicable
|
||||
updateTopic(guildId, handler, false);
|
||||
}
|
||||
|
||||
public void onMessageDelete(Guild guild, long messageId)
|
||||
|
||||
@@ -15,13 +15,38 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.dunctebot.sourcemanagers.DuncteBotSources;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.BotConfig;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import com.jagrosh.jmusicbot.utils.YouTubeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.container.MediaContainerRegistry;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
|
||||
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager;
|
||||
import com.typesafe.config.Config;
|
||||
import com.sedmelluq.discord.lavaplayer.source.bandcamp.BandcampAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.beam.BeamAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.getyarn.GetyarnAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.nico.NicoAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.YoutubeIpRotatorSetup;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.AbstractRoutePlanner;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.BalancingIpRoutePlanner;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.NanoIpRoutePlanner;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
|
||||
import dev.lavalink.youtube.YoutubeAudioSourceManager;
|
||||
import dev.lavalink.youtube.clients.Web;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -29,21 +54,83 @@ 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));
|
||||
AudioSourceManagers.registerRemoteSources(this);
|
||||
|
||||
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
|
||||
if (config.getYTRoutingPlanner() != YouTubeUtil.RoutingPlanner.NONE)
|
||||
{
|
||||
AbstractRoutePlanner routePlanner = YouTubeUtil.createRouterPlanner(config.getYTRoutingPlanner(), config.getYTIpBlocks());
|
||||
YoutubeIpRotatorSetup rotator = new YoutubeIpRotatorSetup(routePlanner);
|
||||
|
||||
rotator.forConfiguration(yt.getHttpInterfaceManager(), false)
|
||||
.withMainDelegateFilter(yt.getContextFilter())
|
||||
.setup();
|
||||
}
|
||||
|
||||
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
|
||||
registerSourceManager(yt);
|
||||
|
||||
registerSourceManager(SoundCloudAudioSourceManager.createDefault());
|
||||
registerSourceManager(new BandcampAudioSourceManager());
|
||||
registerSourceManager(new VimeoAudioSourceManager());
|
||||
registerSourceManager(new TwitchStreamAudioSourceManager());
|
||||
registerSourceManager(new BeamAudioSourceManager());
|
||||
registerSourceManager(new GetyarnAudioSourceManager());
|
||||
registerSourceManager(new NicoAudioSourceManager());
|
||||
registerSourceManager(new HttpAudioSourceManager(MediaContainerRegistry.DEFAULT_REGISTRY));
|
||||
|
||||
AudioSourceManagers.registerLocalSource(this);
|
||||
source(YoutubeAudioSourceManager.class).setPlaylistPageCount(10);
|
||||
|
||||
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;
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
|
||||
import com.jagrosh.jmusicbot.queue.Queueable;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
/**
|
||||
@@ -27,22 +28,22 @@ import net.dv8tion.jda.api.entities.User;
|
||||
public class QueuedTrack implements Queueable
|
||||
{
|
||||
private final AudioTrack track;
|
||||
|
||||
public QueuedTrack(AudioTrack track, User owner)
|
||||
{
|
||||
this(track, new RequestMetadata(owner));
|
||||
}
|
||||
|
||||
private final RequestMetadata requestMetadata;
|
||||
|
||||
public QueuedTrack(AudioTrack track, RequestMetadata rm)
|
||||
{
|
||||
this.track = track;
|
||||
this.track.setUserData(rm);
|
||||
this.track.setUserData(rm == null ? RequestMetadata.EMPTY : rm);
|
||||
|
||||
this.requestMetadata = rm;
|
||||
if (this.track.isSeekable() && rm != null)
|
||||
track.setPosition(rm.requestInfo.startTimestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIdentifier()
|
||||
{
|
||||
return track.getUserData(RequestMetadata.class).getOwner();
|
||||
return requestMetadata.getOwner();
|
||||
}
|
||||
|
||||
public AudioTrack getTrack()
|
||||
@@ -50,9 +51,17 @@ public class QueuedTrack implements Queueable
|
||||
return track;
|
||||
}
|
||||
|
||||
public RequestMetadata getRequestMetadata()
|
||||
{
|
||||
return requestMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "`[" + FormatUtil.formatTime(track.getDuration()) + "]` [**" + track.getInfo().title + "**]("+track.getInfo().uri+") - <@" + track.getUserData(RequestMetadata.class).getOwner() + ">";
|
||||
String entry = "`[" + TimeUtil.formatTime(track.getDuration()) + "]` ";
|
||||
AudioTrackInfo trackInfo = track.getInfo();
|
||||
entry = entry + (trackInfo.uri.startsWith("http") ? "[**" + trackInfo.title + "**]("+trackInfo.uri+")" : "**" + trackInfo.title + "**");
|
||||
return entry + " - <@" + track.getUserData(RequestMetadata.class).getOwner() + ">";
|
||||
}
|
||||
}
|
||||
@@ -15,40 +15,67 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class RequestMetadata
|
||||
{
|
||||
public static final RequestMetadata EMPTY = new RequestMetadata(null);
|
||||
public static final RequestMetadata EMPTY = new RequestMetadata(null, null);
|
||||
|
||||
public final UserInfo user;
|
||||
public final RequestInfo requestInfo;
|
||||
|
||||
public RequestMetadata(User user)
|
||||
public RequestMetadata(User user, RequestInfo requestInfo)
|
||||
{
|
||||
this.user = user == null ? null : new UserInfo(user.getIdLong(), user.getName(), user.getDiscriminator(), user.getEffectiveAvatarUrl());
|
||||
this.requestInfo = requestInfo;
|
||||
}
|
||||
|
||||
public long getOwner()
|
||||
{
|
||||
return user == null ? 0L : user.id;
|
||||
}
|
||||
|
||||
public static RequestMetadata fromResultHandler(AudioTrack track, CommandEvent event)
|
||||
{
|
||||
return new RequestMetadata(event.getAuthor(), new RequestInfo(event.getArgs(), track.getInfo().uri));
|
||||
}
|
||||
|
||||
public class RequestInfo
|
||||
public static class RequestInfo
|
||||
{
|
||||
public final String query, url;
|
||||
|
||||
private RequestInfo(String query, String url)
|
||||
public final long startTimestamp;
|
||||
|
||||
public RequestInfo(String query, String url)
|
||||
{
|
||||
this(query, url, tryGetTimestamp(query));
|
||||
}
|
||||
|
||||
private RequestInfo(String query, String url, long startTimestamp)
|
||||
{
|
||||
this.query = query;
|
||||
this.url = url;
|
||||
this.query = query;
|
||||
this.startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
private static final Pattern youtubeTimestampPattern = Pattern.compile("youtu(?:\\.be|be\\..+)/.*\\?.*(?!.*list=)t=([\\dhms]+)");
|
||||
private static long tryGetTimestamp(String url)
|
||||
{
|
||||
Matcher matcher = youtubeTimestampPattern.matcher(url);
|
||||
return matcher.find() ? TimeUtil.parseUnitTime(matcher.group(1)) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserInfo
|
||||
public static class UserInfo
|
||||
{
|
||||
public final long id;
|
||||
public final String username, discrim, avatar;
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
package com.jagrosh.jmusicbot.audio;
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioItem;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioReference;
|
||||
import com.typesafe.config.Config;
|
||||
import dev.lavalink.youtube.YoutubeAudioSourceManager;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.admin;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.AdminCommand;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
*/
|
||||
public class QueueTypeCmd extends AdminCommand
|
||||
{
|
||||
public QueueTypeCmd(Bot bot)
|
||||
{
|
||||
super();
|
||||
this.name = "queuetype";
|
||||
this.help = "changes the queue type";
|
||||
this.arguments = "[" + String.join("|", QueueType.getNames()) + "]";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
{
|
||||
String args = event.getArgs();
|
||||
QueueType value;
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
QueueType currentType = settings.getQueueType();
|
||||
event.reply(currentType.getEmoji() + " Current queue type is: `" + currentType.getUserFriendlyName() + "`.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
value = QueueType.valueOf(args.toUpperCase());
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
event.replyError("Invalid queue type. Valid types are: [" + String.join("|", QueueType.getNames()) + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.getQueueType() != value)
|
||||
{
|
||||
settings.setQueueType(value);
|
||||
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
if (handler != null)
|
||||
handler.setQueueType(value);
|
||||
}
|
||||
|
||||
event.reply(value.getEmoji() + " Queue type was set to `" + value.getUserFriendlyName() + "`.");
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.jagrosh.jdautilities.menu.OrderedMenu;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
@@ -113,7 +114,7 @@ public class ForceRemoveCmd extends DJCommand
|
||||
}
|
||||
else
|
||||
{
|
||||
event.replySuccess("Successfully removed `"+count+"` entries from **"+target.getName()+"**#"+target.getDiscriminator()+".");
|
||||
event.replySuccess("Successfully removed `"+count+"` entries from "+FormatUtil.formatUsername(target)+".");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,7 +43,7 @@ public class ForceskipCmd extends DJCommand
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = handler.getRequestMetadata();
|
||||
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title
|
||||
+"** "+(rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + rm.user.username + "**)"));
|
||||
+"** "+(rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + FormatUtil.formatUsername(rm.user) + "**)"));
|
||||
handler.getPlayer().stopTrack();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.queue.FairQueue;
|
||||
import com.jagrosh.jmusicbot.queue.AbstractQueue;
|
||||
|
||||
/**
|
||||
* Command that provides users the ability to move a track in the playlist.
|
||||
@@ -57,7 +57,7 @@ public class MoveTrackCmd extends DJCommand
|
||||
|
||||
// Validate that from and to are available
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
FairQueue<QueuedTrack> queue = handler.getQueue();
|
||||
AbstractQueue<QueuedTrack> queue = handler.getQueue();
|
||||
if (isUnavailablePosition(queue, from))
|
||||
{
|
||||
String reply = String.format("`%d` is not a valid position in the queue!", from);
|
||||
@@ -78,7 +78,7 @@ public class MoveTrackCmd extends DJCommand
|
||||
event.replySuccess(reply);
|
||||
}
|
||||
|
||||
private static boolean isUnavailablePosition(FairQueue<QueuedTrack> queue, int position)
|
||||
private static boolean isUnavailablePosition(AbstractQueue<QueuedTrack> queue, int position)
|
||||
{
|
||||
return (position < 1 || position > queue.size());
|
||||
}
|
||||
|
||||
@@ -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.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
@@ -79,13 +81,13 @@ public class PlaynextCmd extends DJCommand
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
int pos = handler.addTrackToFront(new QueuedTrack(track, event.getAuthor()))+1;
|
||||
int pos = handler.addTrackToFront(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
|
||||
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
|
||||
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
|
||||
m.editMessage(addMsg).queue();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.jagrosh.jmusicbot.commands.general;
|
||||
import com.jagrosh.jdautilities.command.Command;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.settings.RepeatMode;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
@@ -63,12 +64,15 @@ public class SettingsCmd extends Command
|
||||
+ "\nRepeat Mode: " + (s.getRepeatMode() == RepeatMode.OFF
|
||||
? s.getRepeatMode().getUserFriendlyName()
|
||||
: "**"+s.getRepeatMode().getUserFriendlyName()+"**")
|
||||
+ "\nQueue Type: " + (s.getQueueType() == QueueType.FAIR
|
||||
? s.getQueueType().getUserFriendlyName()
|
||||
: "**"+s.getQueueType().getUserFriendlyName()+"**")
|
||||
+ "\nDefault Playlist: " + (s.getDefaultPlaylist() == null ? "None" : "**" + s.getDefaultPlaylist() + "**")
|
||||
)
|
||||
.setFooter(event.getJDA().getGuilds().size() + " servers | "
|
||||
+ event.getJDA().getGuilds().stream().filter(g -> g.getSelfMember().getVoiceState().inVoiceChannel()).count()
|
||||
+ " audio connections", null);
|
||||
event.getChannel().sendMessage(builder.setEmbed(ebuilder.build()).build()).queue();
|
||||
event.getChannel().sendMessage(builder.setEmbeds(ebuilder.build()).build()).queue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
|
||||
@@ -108,13 +110,13 @@ public class PlayCmd extends MusicCommand
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+FormatUtil.formatTime(track.getDuration())+"` > `"+FormatUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+ TimeUtil.formatTime(bot.getConfig().getMaxSeconds()*1000)+"`")).queue();
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+1;
|
||||
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
String addMsg = FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
|
||||
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
|
||||
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0?"to begin playing":" to the queue at position "+pos));
|
||||
if(playlist==null || !event.getSelfMember().hasPermission(event.getTextChannel(), Permission.MESSAGE_ADD_REACTION))
|
||||
m.editMessage(addMsg).queue();
|
||||
else
|
||||
@@ -144,7 +146,7 @@ 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, event.getAuthor()));
|
||||
handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)));
|
||||
count[0]++;
|
||||
}
|
||||
});
|
||||
@@ -173,7 +175,12 @@ public class PlayCmd extends MusicCommand
|
||||
else
|
||||
{
|
||||
int count = loadPlaylist(playlist, null);
|
||||
if(count==0)
|
||||
if(playlist.getTracks().size() == 0)
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" The playlist "+(playlist.getName()==null ? "" : "(**"+playlist.getName()
|
||||
+"**) ")+" could not be loaded or contained 0 entries")).queue();
|
||||
}
|
||||
else if(count==0)
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" All entries in this playlist "+(playlist.getName()==null ? "" : "(**"+playlist.getName()
|
||||
+"**) ")+"were longer than the allowed maximum (`"+bot.getConfig().getMaxTime()+"`)")).queue();
|
||||
@@ -238,7 +245,7 @@ public class PlayCmd extends MusicCommand
|
||||
event.getChannel().sendMessage(loadingEmoji+" Loading playlist **"+event.getArgs()+"**... ("+playlist.getItems().size()+" items)").queue(m ->
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
playlist.loadTracks(bot.getPlayerManager(), (at)->handler.addTrack(new QueuedTrack(at, event.getAuthor())), () -> {
|
||||
playlist.loadTracks(bot.getPlayerManager(), (at)->handler.addTrack(new QueuedTrack(at, RequestMetadata.fromResultHandler(at, event))), () -> {
|
||||
StringBuilder builder = new StringBuilder(playlist.getTracks().isEmpty()
|
||||
? event.getClient().getWarning()+" No tracks were loaded!"
|
||||
: event.getClient().getSuccess()+" Loaded **"+playlist.getTracks().size()+"** tracks!");
|
||||
|
||||
@@ -20,13 +20,14 @@ import java.util.concurrent.TimeUnit;
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jdautilities.menu.Paginator;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.JMusicBot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.QueuedTrack;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.settings.QueueType;
|
||||
import com.jagrosh.jmusicbot.settings.RepeatMode;
|
||||
import com.jagrosh.jmusicbot.settings.Settings;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import net.dv8tion.jda.api.MessageBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
@@ -78,7 +79,7 @@ public class QueueCmd extends MusicCommand
|
||||
Message nonowp = ah.getNoMusicPlaying(event.getJDA());
|
||||
Message built = new MessageBuilder()
|
||||
.setContent(event.getClient().getWarning() + " There is no music in the queue!")
|
||||
.setEmbed((nowp==null ? nonowp : nowp).getEmbeds().get(0)).build();
|
||||
.setEmbeds((nowp==null ? nonowp : nowp).getEmbeds().get(0)).build();
|
||||
event.reply(built, m ->
|
||||
{
|
||||
if(nowp!=null)
|
||||
@@ -95,7 +96,7 @@ public class QueueCmd extends MusicCommand
|
||||
}
|
||||
Settings settings = event.getClient().getSettingsFor(event.getGuild());
|
||||
long fintotal = total;
|
||||
builder.setText((i1,i2) -> getQueueTitle(ah, event.getClient().getSuccess(), songs.length, fintotal, settings.getRepeatMode()))
|
||||
builder.setText((i1,i2) -> getQueueTitle(ah, event.getClient().getSuccess(), songs.length, fintotal, settings.getRepeatMode(), settings.getQueueType()))
|
||||
.setItems(songs)
|
||||
.setUsers(event.getAuthor())
|
||||
.setColor(event.getSelfMember().getColor())
|
||||
@@ -103,16 +104,17 @@ public class QueueCmd extends MusicCommand
|
||||
builder.build().paginate(event.getChannel(), pagenum);
|
||||
}
|
||||
|
||||
private String getQueueTitle(AudioHandler ah, String success, int songslength, long total, RepeatMode repeatmode)
|
||||
private String getQueueTitle(AudioHandler ah, String success, int songslength, long total, RepeatMode repeatmode, QueueType queueType)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(ah.getPlayer().getPlayingTrack()!=null)
|
||||
{
|
||||
sb.append(ah.getPlayer().isPaused() ? JMusicBot.PAUSE_EMOJI : JMusicBot.PLAY_EMOJI).append(" **")
|
||||
sb.append(ah.getStatusEmoji()).append(" **")
|
||||
.append(ah.getPlayer().getPlayingTrack().getInfo().title).append("**\n");
|
||||
}
|
||||
return FormatUtil.filter(sb.append(success).append(" Current Queue | ").append(songslength)
|
||||
.append(" entries | `").append(FormatUtil.formatTime(total)).append("` ")
|
||||
.append(repeatmode.getEmoji() != null ? "| "+repeatmode.getEmoji() : "").toString());
|
||||
.append(" entries | `").append(TimeUtil.formatTime(total)).append("` ")
|
||||
.append("| ").append(queueType.getEmoji()).append(" `").append(queueType.getUserFriendlyName()).append('`')
|
||||
.append(repeatmode.getEmoji() != null ? " | "+repeatmode.getEmoji() : "").toString());
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity;
|
||||
@@ -88,13 +90,13 @@ public class SearchCmd extends MusicCommand
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getWarning()+" This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+FormatUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue();
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`")).queue();
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+1;
|
||||
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
m.editMessage(FormatUtil.filter(event.getClient().getSuccess()+" Added **"+track.getInfo().title
|
||||
+"** (`"+FormatUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
|
||||
+"** (`"+ TimeUtil.formatTime(track.getDuration())+"`) "+(pos==0 ? "to begin playing"
|
||||
: " to the queue at position "+pos))).queue();
|
||||
}
|
||||
|
||||
@@ -110,13 +112,13 @@ public class SearchCmd extends MusicCommand
|
||||
if(bot.getConfig().isTooLong(track))
|
||||
{
|
||||
event.replyWarning("This track (**"+track.getInfo().title+"**) is longer than the allowed maximum: `"
|
||||
+FormatUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`");
|
||||
+ TimeUtil.formatTime(track.getDuration())+"` > `"+bot.getConfig().getMaxTime()+"`");
|
||||
return;
|
||||
}
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
int pos = handler.addTrack(new QueuedTrack(track, event.getAuthor()))+1;
|
||||
int pos = handler.addTrack(new QueuedTrack(track, RequestMetadata.fromResultHandler(track, event)))+1;
|
||||
event.replySuccess("Added **" + FormatUtil.filter(track.getInfo().title)
|
||||
+ "** (`" + FormatUtil.formatTime(track.getDuration()) + "`) " + (pos==0 ? "to begin playing"
|
||||
+ "** (`" + TimeUtil.formatTime(track.getDuration()) + "`) " + (pos==0 ? "to begin playing"
|
||||
: " to the queue at position "+pos));
|
||||
})
|
||||
.setCancel((msg) -> {})
|
||||
@@ -125,7 +127,7 @@ public class SearchCmd extends MusicCommand
|
||||
for(int i=0; i<4 && i<playlist.getTracks().size(); i++)
|
||||
{
|
||||
AudioTrack track = playlist.getTracks().get(i);
|
||||
builder.addChoices("`["+FormatUtil.formatTime(track.getDuration())+"]` [**"+track.getInfo().title+"**]("+track.getInfo().uri+")");
|
||||
builder.addChoices("`["+ TimeUtil.formatTime(track.getDuration())+"]` [**"+track.getInfo().title+"**]("+track.getInfo().uri+")");
|
||||
}
|
||||
builder.build().display(m);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.commands.music;
|
||||
|
||||
import com.jagrosh.jdautilities.command.CommandEvent;
|
||||
import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.DJCommand;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* @author Whew., Inc.
|
||||
*/
|
||||
public class SeekCmd extends MusicCommand
|
||||
{
|
||||
private final static Logger LOG = LoggerFactory.getLogger("Seeking");
|
||||
|
||||
public SeekCmd(Bot bot)
|
||||
{
|
||||
super(bot);
|
||||
this.name = "seek";
|
||||
this.help = "seeks the current song";
|
||||
this.arguments = "[+ | -] <HH:MM:SS | MM:SS | SS>|<0h0m0s | 0m0s | 0s>";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.beListening = true;
|
||||
this.bePlaying = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCommand(CommandEvent event)
|
||||
{
|
||||
AudioHandler handler = (AudioHandler) event.getGuild().getAudioManager().getSendingHandler();
|
||||
AudioTrack playingTrack = handler.getPlayer().getPlayingTrack();
|
||||
if (!playingTrack.isSeekable())
|
||||
{
|
||||
event.replyError("This track is not seekable.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!DJCommand.checkDJPermission(event) && playingTrack.getUserData(RequestMetadata.class).getOwner() != event.getAuthor().getIdLong())
|
||||
{
|
||||
event.replyError("You cannot seek **" + playingTrack.getInfo().title + "** because you didn't add it!");
|
||||
return;
|
||||
}
|
||||
|
||||
String args = event.getArgs();
|
||||
TimeUtil.SeekTime seekTime = TimeUtil.parseTime(args);
|
||||
if (seekTime == null)
|
||||
{
|
||||
event.replyError("Invalid seek! Expected format: " + arguments + "\nExamples: `1:02:23` `+1:10` `-90`, `1h10m`, `+90s`");
|
||||
return;
|
||||
}
|
||||
|
||||
long currentPosition = playingTrack.getPosition();
|
||||
long trackDuration = playingTrack.getDuration();
|
||||
|
||||
long seekMilliseconds = seekTime.relative ? currentPosition + seekTime.milliseconds : seekTime.milliseconds;
|
||||
if (seekMilliseconds > trackDuration)
|
||||
{
|
||||
event.replyError("Cannot seek to `" + TimeUtil.formatTime(seekMilliseconds) + "` because the current track is `" + TimeUtil.formatTime(trackDuration) + "` long!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
playingTrack.setPosition(seekMilliseconds);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
event.replyError("An error occurred while trying to seek: " + e.getMessage());
|
||||
LOG.warn("Failed to seek track " + playingTrack.getIdentifier(), e);
|
||||
return;
|
||||
}
|
||||
event.replySuccess("Successfully seeked to `" + TimeUtil.formatTime(playingTrack.getPosition()) + "/" + TimeUtil.formatTime(playingTrack.getDuration()) + "`!");
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import com.jagrosh.jmusicbot.Bot;
|
||||
import com.jagrosh.jmusicbot.audio.AudioHandler;
|
||||
import com.jagrosh.jmusicbot.audio.RequestMetadata;
|
||||
import com.jagrosh.jmusicbot.commands.MusicCommand;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import com.jagrosh.jmusicbot.utils.FormatUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,7 +43,11 @@ public class SkipCmd extends MusicCommand
|
||||
{
|
||||
AudioHandler handler = (AudioHandler)event.getGuild().getAudioManager().getSendingHandler();
|
||||
RequestMetadata rm = handler.getRequestMetadata();
|
||||
if(event.getAuthor().getIdLong() == rm.getOwner())
|
||||
double skipRatio = bot.getSettingsManager().getSettings(event.getGuild()).getSkipRatio();
|
||||
if(skipRatio == -1) {
|
||||
skipRatio = bot.getConfig().getSkipRatio();
|
||||
}
|
||||
if(event.getAuthor().getIdLong() == rm.getOwner() || skipRatio == 0)
|
||||
{
|
||||
event.reply(event.getClient().getSuccess()+" Skipped **"+handler.getPlayer().getPlayingTrack().getInfo().title+"**");
|
||||
handler.getPlayer().stopTrack();
|
||||
@@ -62,12 +66,12 @@ public class SkipCmd extends MusicCommand
|
||||
}
|
||||
int skippers = (int)event.getSelfMember().getVoiceState().getChannel().getMembers().stream()
|
||||
.filter(m -> handler.getVotes().contains(m.getUser().getId())).count();
|
||||
int required = (int)Math.ceil(listeners * bot.getSettingsManager().getSettings(event.getGuild()).getSkipRatio());
|
||||
int required = (int)Math.ceil(listeners * skipRatio);
|
||||
msg += skippers + " votes, " + required + "/" + listeners + " needed]`";
|
||||
if(skippers>=required)
|
||||
{
|
||||
msg += "\n" + event.getClient().getSuccess() + " Skipped **" + handler.getPlayer().getPlayingTrack().getInfo().title
|
||||
+ "** " + (rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + rm.user.username + "**)");
|
||||
+ "** " + (rm.getOwner() == 0L ? "(autoplay)" : "(requested by **" + FormatUtil.formatUsername(rm.user) + "**)");
|
||||
handler.getPlayer().stopTrack();
|
||||
}
|
||||
event.reply(msg);
|
||||
|
||||
@@ -20,6 +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;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -28,6 +29,7 @@ import com.jagrosh.jmusicbot.commands.OwnerCommand;
|
||||
public class EvalCmd extends OwnerCommand
|
||||
{
|
||||
private final Bot bot;
|
||||
private final String engine;
|
||||
|
||||
public EvalCmd(Bot bot)
|
||||
{
|
||||
@@ -35,18 +37,27 @@ public class EvalCmd extends OwnerCommand
|
||||
this.name = "eval";
|
||||
this.help = "evaluates nashorn code";
|
||||
this.aliases = bot.getConfig().getAliases(this.name);
|
||||
this.engine = bot.getConfig().getEvalEngine();
|
||||
this.guildOnly = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(CommandEvent event)
|
||||
{
|
||||
ScriptEngine se = new ScriptEngineManager().getEngineByName("Nashorn");
|
||||
ScriptEngine se = new ScriptEngineManager().getEngineByName(engine);
|
||||
if(se == null)
|
||||
{
|
||||
event.replyError("The eval engine provided in the config (`"+engine+"`) doesn't exist. This could be due to an invalid "
|
||||
+ "engine name, or the engine not existing in your version of java (`"+System.getProperty("java.version")+"`).");
|
||||
return;
|
||||
}
|
||||
se.put("bot", bot);
|
||||
se.put("event", event);
|
||||
se.put("jda", event.getJDA());
|
||||
se.put("guild", event.getGuild());
|
||||
se.put("channel", event.getChannel());
|
||||
if (event.getChannelType() != ChannelType.PRIVATE) {
|
||||
se.put("guild", event.getGuild());
|
||||
se.put("channel", event.getChannel());
|
||||
}
|
||||
try
|
||||
{
|
||||
event.reply(event.getClient().getSuccess()+" Evaluated Successfully:\n```\n"+se.eval(event.getArgs())+" ```");
|
||||
|
||||
@@ -72,7 +72,12 @@ public class PlaylistCmd extends OwnerCommand
|
||||
protected void execute(CommandEvent event)
|
||||
{
|
||||
String pname = event.getArgs().replaceAll("\\s+", "_");
|
||||
if(bot.getPlaylistLoader().getPlaylist(pname)==null)
|
||||
pname = pname.replaceAll("[*?|\\/\":<>]", "");
|
||||
if(pname == null || pname.isEmpty())
|
||||
{
|
||||
event.replyError("Please provide a name for the playlist!");
|
||||
}
|
||||
else if(bot.getPlaylistLoader().getPlaylist(pname) == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ public class PlaylistLoader
|
||||
else
|
||||
{
|
||||
createFolder();
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class AbstractQueue<T extends Queueable>
|
||||
{
|
||||
protected AbstractQueue(AbstractQueue<T> queue)
|
||||
{
|
||||
this.list = queue != null ? queue.getList() : new LinkedList<>();
|
||||
}
|
||||
|
||||
protected final List<T> list;
|
||||
|
||||
public abstract int add(T item);
|
||||
|
||||
public void addAt(int index, T item)
|
||||
{
|
||||
if(index >= list.size())
|
||||
list.add(item);
|
||||
else
|
||||
list.add(index, item);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
public T pull() {
|
||||
return list.remove(0);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
public List<T> getList()
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
public T get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
public T remove(int index)
|
||||
{
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
public int removeAll(long identifier)
|
||||
{
|
||||
int count = 0;
|
||||
for(int i=list.size()-1; i>=0; i--)
|
||||
{
|
||||
if(list.get(i).getIdentifier()==identifier)
|
||||
{
|
||||
list.remove(i);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
public int shuffle(long identifier)
|
||||
{
|
||||
List<Integer> iset = new ArrayList<>();
|
||||
for(int i=0; i<list.size(); i++)
|
||||
{
|
||||
if(list.get(i).getIdentifier()==identifier)
|
||||
iset.add(i);
|
||||
}
|
||||
for(int j=0; j<iset.size(); j++)
|
||||
{
|
||||
int first = iset.get(j);
|
||||
int second = iset.get((int)(Math.random()*iset.size()));
|
||||
T temp = list.get(first);
|
||||
list.set(first, list.get(second));
|
||||
list.set(second, temp);
|
||||
}
|
||||
return iset.size();
|
||||
}
|
||||
|
||||
public void skip(int number)
|
||||
{
|
||||
if (number > 0) {
|
||||
list.subList(0, number).clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item to a different position in the list
|
||||
* @param from The position of the item
|
||||
* @param to The new position of the item
|
||||
* @return the moved item
|
||||
*/
|
||||
public T moveItem(int from, int to)
|
||||
{
|
||||
T item = list.remove(from);
|
||||
list.add(to, item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -25,15 +23,21 @@ import java.util.Set;
|
||||
* @author John Grosh (jagrosh)
|
||||
* @param <T>
|
||||
*/
|
||||
public class FairQueue<T extends Queueable> {
|
||||
private final List<T> list = new ArrayList<>();
|
||||
private final Set<Long> set = new HashSet<>();
|
||||
|
||||
public class FairQueue<T extends Queueable> extends AbstractQueue<T>
|
||||
{
|
||||
public FairQueue(AbstractQueue<T> queue)
|
||||
{
|
||||
super(queue);
|
||||
}
|
||||
|
||||
protected final Set<Long> set = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public int add(T item)
|
||||
{
|
||||
int lastIndex;
|
||||
for(lastIndex=list.size()-1; lastIndex>-1; lastIndex--)
|
||||
if(list.get(lastIndex).getIdentifier()==item.getIdentifier())
|
||||
if(list.get(lastIndex).getIdentifier() == item.getIdentifier())
|
||||
break;
|
||||
lastIndex++;
|
||||
set.clear();
|
||||
@@ -46,99 +50,5 @@ public class FairQueue<T extends Queueable> {
|
||||
list.add(lastIndex, item);
|
||||
return lastIndex;
|
||||
}
|
||||
|
||||
public void addAt(int index, T item)
|
||||
{
|
||||
if(index >= list.size())
|
||||
list.add(item);
|
||||
else
|
||||
list.add(index, item);
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
public T pull()
|
||||
{
|
||||
return list.remove(0);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
public List<T> getList()
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
public T get(int index)
|
||||
{
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
public T remove(int index)
|
||||
{
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
public int removeAll(long identifier)
|
||||
{
|
||||
int count = 0;
|
||||
for(int i=list.size()-1; i>=0; i--)
|
||||
{
|
||||
if(list.get(i).getIdentifier()==identifier)
|
||||
{
|
||||
list.remove(i);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
public int shuffle(long identifier)
|
||||
{
|
||||
List<Integer> iset = new ArrayList<>();
|
||||
for(int i=0; i<list.size(); i++)
|
||||
{
|
||||
if(list.get(i).getIdentifier()==identifier)
|
||||
iset.add(i);
|
||||
}
|
||||
for(int j=0; j<iset.size(); j++)
|
||||
{
|
||||
int first = iset.get(j);
|
||||
int second = iset.get((int)(Math.random()*iset.size()));
|
||||
T temp = list.get(first);
|
||||
list.set(first, list.get(second));
|
||||
list.set(second, temp);
|
||||
}
|
||||
return iset.size();
|
||||
}
|
||||
|
||||
public void skip(int number)
|
||||
{
|
||||
for(int i=0; i<number; i++)
|
||||
list.remove(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item to a different position in the list
|
||||
* @param from The position of the item
|
||||
* @param to The new position of the item
|
||||
* @return the moved item
|
||||
*/
|
||||
public T moveItem(int from, int to)
|
||||
{
|
||||
T item = list.remove(from);
|
||||
list.add(to, item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh (jagrosh).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
* @param <T>
|
||||
*/
|
||||
public class LinearQueue<T extends Queueable> extends AbstractQueue<T>
|
||||
{
|
||||
public LinearQueue(AbstractQueue<T> queue)
|
||||
{
|
||||
super(queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int add(T item)
|
||||
{
|
||||
list.add(item);
|
||||
return list.size() - 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.queue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QueueSupplier
|
||||
{
|
||||
<T extends Queueable> AbstractQueue<T> apply(AbstractQueue<T> queue);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.settings;
|
||||
|
||||
import com.jagrosh.jmusicbot.queue.AbstractQueue;
|
||||
import com.jagrosh.jmusicbot.queue.FairQueue;
|
||||
import com.jagrosh.jmusicbot.queue.LinearQueue;
|
||||
import com.jagrosh.jmusicbot.queue.Queueable;
|
||||
import com.jagrosh.jmusicbot.queue.QueueSupplier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Wolfgang Schwendtbauer
|
||||
*/
|
||||
public enum QueueType
|
||||
{
|
||||
LINEAR("\u23E9", "Linear", LinearQueue::new), // ⏩
|
||||
FAIR("\uD83D\uDD22", "Fair", FairQueue::new); // 🔢
|
||||
|
||||
private final String userFriendlyName;
|
||||
private final String emoji;
|
||||
private final QueueSupplier supplier;
|
||||
|
||||
QueueType(final String emoji, final String userFriendlyName, QueueSupplier supplier)
|
||||
{
|
||||
this.userFriendlyName = userFriendlyName;
|
||||
this.emoji = emoji;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
public static List<String> getNames()
|
||||
{
|
||||
return Arrays.stream(QueueType.values())
|
||||
.map(type -> type.name().toLowerCase())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public <T extends Queueable> AbstractQueue<T> createInstance(AbstractQueue<T> previous)
|
||||
{
|
||||
return supplier.apply(previous);
|
||||
}
|
||||
|
||||
public String getUserFriendlyName()
|
||||
{
|
||||
return userFriendlyName;
|
||||
}
|
||||
|
||||
public String getEmoji()
|
||||
{
|
||||
return emoji;
|
||||
}
|
||||
}
|
||||
@@ -36,10 +36,11 @@ public class Settings implements GuildSettingsProvider
|
||||
private int volume;
|
||||
private String defaultPlaylist;
|
||||
private RepeatMode repeatMode;
|
||||
private QueueType queueType;
|
||||
private String prefix;
|
||||
private double skipRatio;
|
||||
|
||||
public Settings(SettingsManager manager, String textId, String voiceId, String roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio)
|
||||
public Settings(SettingsManager manager, String textId, String voiceId, String roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio, QueueType queueType)
|
||||
{
|
||||
this.manager = manager;
|
||||
try
|
||||
@@ -71,9 +72,10 @@ public class Settings implements GuildSettingsProvider
|
||||
this.repeatMode = repeatMode;
|
||||
this.prefix = prefix;
|
||||
this.skipRatio = skipRatio;
|
||||
this.queueType = queueType;
|
||||
}
|
||||
|
||||
public Settings(SettingsManager manager, long textId, long voiceId, long roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio)
|
||||
public Settings(SettingsManager manager, long textId, long voiceId, long roleId, int volume, String defaultPlaylist, RepeatMode repeatMode, String prefix, double skipRatio, QueueType queueType)
|
||||
{
|
||||
this.manager = manager;
|
||||
this.textId = textId;
|
||||
@@ -84,6 +86,7 @@ public class Settings implements GuildSettingsProvider
|
||||
this.repeatMode = repeatMode;
|
||||
this.prefix = prefix;
|
||||
this.skipRatio = skipRatio;
|
||||
this.queueType = queueType;
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -127,10 +130,15 @@ public class Settings implements GuildSettingsProvider
|
||||
return skipRatio;
|
||||
}
|
||||
|
||||
public QueueType getQueueType()
|
||||
{
|
||||
return queueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPrefixes()
|
||||
{
|
||||
return prefix == null ? Collections.EMPTY_SET : Collections.singleton(prefix);
|
||||
return prefix == null ? Collections.emptySet() : Collections.singleton(prefix);
|
||||
}
|
||||
|
||||
// Setters
|
||||
@@ -181,4 +189,10 @@ public class Settings implements GuildSettingsProvider
|
||||
this.skipRatio = skipRatio;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
|
||||
public void setQueueType(QueueType queueType)
|
||||
{
|
||||
this.queueType = queueType;
|
||||
this.manager.writeSettings();
|
||||
}
|
||||
}
|
||||
@@ -19,26 +19,30 @@ import com.jagrosh.jdautilities.command.GuildSettingsManager;
|
||||
import com.jagrosh.jmusicbot.utils.OtherUtil;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.HashMap;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author John Grosh (john.a.grosh@gmail.com)
|
||||
*/
|
||||
public class SettingsManager implements GuildSettingsManager
|
||||
public class SettingsManager implements GuildSettingsManager<Settings>
|
||||
{
|
||||
private final static double SKIP_RATIO = .55;
|
||||
private final static Logger LOG = LoggerFactory.getLogger("Settings");
|
||||
private final static String SETTINGS_FILE = "serversettings.json";
|
||||
private final HashMap<Long,Settings> settings;
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
this.settings = new HashMap<>();
|
||||
|
||||
try {
|
||||
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(OtherUtil.getPath("serversettings.json"))));
|
||||
JSONObject loadedSettings = new JSONObject(new String(Files.readAllBytes(OtherUtil.getPath(SETTINGS_FILE))));
|
||||
loadedSettings.keySet().forEach((id) -> {
|
||||
JSONObject o = loadedSettings.getJSONObject(id);
|
||||
|
||||
@@ -55,16 +59,28 @@ public class SettingsManager implements GuildSettingsManager
|
||||
o.has("default_playlist")? o.getString("default_playlist") : null,
|
||||
o.has("repeat_mode") ? o.getEnum(RepeatMode.class, "repeat_mode"): RepeatMode.OFF,
|
||||
o.has("prefix") ? o.getString("prefix") : null,
|
||||
o.has("skip_ratio") ? o.getDouble("skip_ratio") : SKIP_RATIO));
|
||||
o.has("skip_ratio") ? o.getDouble("skip_ratio") : -1,
|
||||
o.has("queue_type") ? o.getEnum(QueueType.class, "queue_type") : QueueType.FAIR));
|
||||
});
|
||||
} catch (NoSuchFileException e) {
|
||||
// create an empty json file
|
||||
try {
|
||||
LOG.info("serversettings.json will be created in " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
|
||||
Files.write(OtherUtil.getPath("serversettings.json"), new JSONObject().toString(4).getBytes());
|
||||
} catch(IOException ex) {
|
||||
LOG.warn("Failed to create new settings file: "+ex);
|
||||
}
|
||||
return;
|
||||
} catch(IOException | JSONException e) {
|
||||
LoggerFactory.getLogger("Settings").warn("Failed to load server settings (this is normal if no settings have been set yet): "+e);
|
||||
LOG.warn("Failed to load server settings: "+e);
|
||||
}
|
||||
|
||||
LOG.info("serversettings.json loaded from " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets non-null settings for a Guild
|
||||
*
|
||||
*
|
||||
* @param guild the guild to get settings for
|
||||
* @return the existing settings, or new settings for that guild
|
||||
*/
|
||||
@@ -73,17 +89,17 @@ public class SettingsManager implements GuildSettingsManager
|
||||
{
|
||||
return getSettings(guild.getIdLong());
|
||||
}
|
||||
|
||||
|
||||
public Settings getSettings(long guildId)
|
||||
{
|
||||
return settings.computeIfAbsent(guildId, id -> createDefaultSettings());
|
||||
}
|
||||
|
||||
|
||||
private Settings createDefaultSettings()
|
||||
{
|
||||
return new Settings(this, 0, 0, 0, 100, null, RepeatMode.OFF, null, SKIP_RATIO);
|
||||
return new Settings(this, 0, 0, 0, 100, null, RepeatMode.OFF, null, -1, QueueType.FAIR);
|
||||
}
|
||||
|
||||
|
||||
protected void writeSettings()
|
||||
{
|
||||
JSONObject obj = new JSONObject();
|
||||
@@ -104,14 +120,16 @@ public class SettingsManager implements GuildSettingsManager
|
||||
o.put("repeat_mode", s.getRepeatMode());
|
||||
if(s.getPrefix() != null)
|
||||
o.put("prefix", s.getPrefix());
|
||||
if(s.getSkipRatio() != SKIP_RATIO)
|
||||
if(s.getSkipRatio() != -1)
|
||||
o.put("skip_ratio", s.getSkipRatio());
|
||||
if(s.getQueueType() != QueueType.FAIR)
|
||||
o.put("queue_type", s.getQueueType().name());
|
||||
obj.put(Long.toString(key), o);
|
||||
});
|
||||
try {
|
||||
Files.write(OtherUtil.getPath("serversettings.json"), obj.toString(4).getBytes());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,11 @@
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -25,19 +27,29 @@ import net.dv8tion.jda.api.entities.VoiceChannel;
|
||||
* @author John Grosh <john.a.grosh@gmail.com>
|
||||
*/
|
||||
public class FormatUtil {
|
||||
|
||||
public static String formatTime(long duration)
|
||||
|
||||
public static String formatUsername(String username, String discrim)
|
||||
{
|
||||
if(duration == Long.MAX_VALUE)
|
||||
return "LIVE";
|
||||
long seconds = Math.round(duration/1000.0);
|
||||
long hours = seconds/(60*60);
|
||||
seconds %= 60*60;
|
||||
long minutes = seconds/60;
|
||||
seconds %= 60;
|
||||
return (hours>0 ? hours+":" : "") + (minutes<10 ? "0"+minutes : minutes) + ":" + (seconds<10 ? "0"+seconds : seconds);
|
||||
if(discrim == null || discrim.equals("0000"))
|
||||
{
|
||||
return username;
|
||||
}
|
||||
else
|
||||
{
|
||||
return username + "#" + discrim;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String formatUsername(UserInfo userinfo)
|
||||
{
|
||||
return formatUsername(userinfo.username, userinfo.discrim);
|
||||
}
|
||||
|
||||
public static String formatUsername(User user)
|
||||
{
|
||||
return formatUsername(user.getName(), user.getDiscriminator());
|
||||
}
|
||||
|
||||
public static String progressBar(double percent)
|
||||
{
|
||||
String str = "";
|
||||
@@ -82,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)
|
||||
|
||||
@@ -23,8 +23,12 @@ import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.ApplicationInfo;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import okhttp3.*;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -39,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\\";
|
||||
|
||||
/**
|
||||
@@ -60,7 +68,7 @@ public class OtherUtil
|
||||
{
|
||||
result = Paths.get(new File(JMusicBot.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath() + File.separator + path);
|
||||
}
|
||||
catch(URISyntaxException ex) {}
|
||||
catch(URISyntaxException ignored) {}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -80,7 +88,7 @@ public class OtherUtil
|
||||
reader.lines().forEach(line -> sb.append("\r\n").append(line));
|
||||
return sb.toString().trim();
|
||||
}
|
||||
catch(IOException ex)
|
||||
catch(IOException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -150,7 +158,14 @@ public class OtherUtil
|
||||
return st == null ? OnlineStatus.ONLINE : st;
|
||||
}
|
||||
|
||||
public static String checkVersion(Prompt prompt)
|
||||
public static void checkJavaVersion(Prompt prompt)
|
||||
{
|
||||
if(!System.getProperty("java.vm.name").contains("64"))
|
||||
prompt.alert(Prompt.Level.WARNING, "Java Version",
|
||||
"It appears that you may not be using a supported Java version. Please use 64-bit java.");
|
||||
}
|
||||
|
||||
public static void checkVersion(Prompt prompt)
|
||||
{
|
||||
// Get current version number
|
||||
String version = getCurrentVersion();
|
||||
@@ -160,11 +175,8 @@ public class OtherUtil
|
||||
|
||||
if(latestVersion!=null && !latestVersion.equals(version))
|
||||
{
|
||||
prompt.alert(Prompt.Level.WARNING, "Version", String.format(NEW_VERSION_AVAILABLE, version, latestVersion));
|
||||
prompt.alert(Prompt.Level.WARNING, "JMusicBot Version", String.format(NEW_VERSION_AVAILABLE, version, latestVersion));
|
||||
}
|
||||
|
||||
// Return the current version
|
||||
return version;
|
||||
}
|
||||
|
||||
public static String getCurrentVersion()
|
||||
@@ -180,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)
|
||||
@@ -203,4 +215,23 @@ public class OtherUtil
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the bot JMusicBot is being run on is supported & returns the reason if it is not.
|
||||
* @return A string with the reason, or null if it is supported.
|
||||
*/
|
||||
public static String getUnsupportedBotReason(JDA jda)
|
||||
{
|
||||
if (jda.getSelfUser().getFlags().contains(User.UserFlag.VERIFIED_BOT))
|
||||
return "The bot is verified. Using JMusicBot in a verified bot is not supported.";
|
||||
|
||||
ApplicationInfo info = jda.retrieveApplicationInfo().complete();
|
||||
if (info.isBotPublic())
|
||||
return "\"Public Bot\" is enabled. Using JMusicBot as a public bot is not supported. Please disable it in the "
|
||||
+ "Developer Dashboard at https://discord.com/developers/applications/" + jda.getSelfUser().getId() + "/bot ."
|
||||
+ "You may also need to disable all Installation Contexts at https://discord.com/developers/applications/"
|
||||
+ jda.getSelfUser().getId() + "/installation .";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
public class TimeUtil
|
||||
{
|
||||
|
||||
public static String formatTime(long duration)
|
||||
{
|
||||
if(duration == Long.MAX_VALUE)
|
||||
return "LIVE";
|
||||
long seconds = Math.round(duration/1000.0);
|
||||
long hours = seconds/(60*60);
|
||||
seconds %= 60*60;
|
||||
long minutes = seconds/60;
|
||||
seconds %= 60;
|
||||
return (hours>0 ? hours+":" : "") + (minutes<10 ? "0"+minutes : minutes) + ":" + (seconds<10 ? "0"+seconds : seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a seek time string into milliseconds and determines if it's relative.
|
||||
* Supports "colon time" (HH:MM:SS) or "unit time" (1h20m)
|
||||
* @param args time string
|
||||
* @return SeekTime object, or null if the string could not be parsed
|
||||
*/
|
||||
public static SeekTime parseTime(String args)
|
||||
{
|
||||
if (args.length() == 0) return null;
|
||||
String timestamp = args;
|
||||
boolean relative = false; // seek forward or backward
|
||||
boolean isSeekingBackwards = false;
|
||||
char first = timestamp.charAt(0);
|
||||
if (first == '+' || first == '-')
|
||||
{
|
||||
relative = true;
|
||||
isSeekingBackwards = first == '-';
|
||||
timestamp = timestamp.substring(1);
|
||||
}
|
||||
|
||||
long milliseconds = parseColonTime(timestamp);
|
||||
if(milliseconds == -1) milliseconds = parseUnitTime(timestamp);
|
||||
if(milliseconds == -1) return null;
|
||||
|
||||
milliseconds *= isSeekingBackwards ? -1 : 1;
|
||||
|
||||
return new SeekTime(milliseconds, relative);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timestamp timestamp formatted as: [+ | -] <HH:MM:SS | MM:SS | SS>
|
||||
* @return Time in milliseconds
|
||||
*/
|
||||
public static long parseColonTime(String timestamp)
|
||||
{
|
||||
String[] timestampSplitArray = timestamp.split(":+");
|
||||
if(timestampSplitArray.length > 3 )
|
||||
return -1;
|
||||
double[] timeUnitArray = new double[3]; // hours, minutes, seconds
|
||||
for(int index = 0; index < timestampSplitArray.length; index++)
|
||||
{
|
||||
String unit = timestampSplitArray[index];
|
||||
if (unit.startsWith("+") || unit.startsWith("-")) return -1;
|
||||
unit = unit.replace(",", ".");
|
||||
try
|
||||
{
|
||||
timeUnitArray[index + 3 - timestampSplitArray.length] = Double.parseDouble(unit);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return Math.round(timeUnitArray[0] * 3600000 + timeUnitArray[1] * 60000 + timeUnitArray[2] * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param timestr time string formatted as a unit time, e.g. 20m10, 1d5h20m14s or 1h and 20m
|
||||
* @return Time in milliseconds
|
||||
*/
|
||||
public static long parseUnitTime(String timestr)
|
||||
{
|
||||
timestr = timestr.replaceAll("(?i)(\\s|,|and)","")
|
||||
.replaceAll("(?is)(-?\\d+|[a-z]+)", "$1 ")
|
||||
.trim();
|
||||
String[] vals = timestr.split("\\s+");
|
||||
int time = 0;
|
||||
try
|
||||
{
|
||||
for(int j=0; j<vals.length; j+=2)
|
||||
{
|
||||
int num = Integer.parseInt(vals[j]);
|
||||
|
||||
if(vals.length > j+1)
|
||||
{
|
||||
if(vals[j+1].toLowerCase().startsWith("m"))
|
||||
num*=60;
|
||||
else if(vals[j+1].toLowerCase().startsWith("h"))
|
||||
num*=60*60;
|
||||
else if(vals[j+1].toLowerCase().startsWith("d"))
|
||||
num*=60*60*24;
|
||||
}
|
||||
|
||||
time+=num*1000;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
public static class SeekTime
|
||||
{
|
||||
public final long milliseconds;
|
||||
public final boolean relative;
|
||||
|
||||
private SeekTime(long milliseconds, boolean relative)
|
||||
{
|
||||
this.milliseconds = milliseconds;
|
||||
this.relative = relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.planner.*;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.IpBlock;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv4Block;
|
||||
import com.sedmelluq.lava.extensions.youtuberotator.tools.ip.Ipv6Block;
|
||||
import dev.lavalink.youtube.clients.Web;
|
||||
import org.json.JSONObject;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.chrome.ChromeDriver;
|
||||
import org.openqa.selenium.chrome.ChromeDriverService;
|
||||
import org.openqa.selenium.chrome.ChromeOptions;
|
||||
import org.openqa.selenium.chromium.ChromiumDriverLogLevel;
|
||||
import org.openqa.selenium.devtools.NetworkInterceptor;
|
||||
import org.openqa.selenium.os.ExecutableFinder;
|
||||
import org.openqa.selenium.remote.http.Contents;
|
||||
import org.openqa.selenium.remote.http.Filter;
|
||||
import org.openqa.selenium.remote.http.HttpMethod;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||
|
||||
public class YouTubeUtil {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(YouTubeUtil.class);
|
||||
|
||||
public enum RoutingPlanner {
|
||||
NONE,
|
||||
ROTATE_ON_BAN,
|
||||
LOAD_BALANCE,
|
||||
NANO_SWITCH,
|
||||
ROTATING_NANO_SWITCH
|
||||
}
|
||||
|
||||
public static IpBlock parseIpBlock(String cidr) {
|
||||
if (Ipv6Block.isIpv6CidrBlock(cidr))
|
||||
return new Ipv6Block(cidr);
|
||||
|
||||
if (Ipv4Block.isIpv4CidrBlock(cidr))
|
||||
return new Ipv4Block(cidr);
|
||||
|
||||
throw new IllegalArgumentException("Could not parse CIDR " + cidr);
|
||||
}
|
||||
|
||||
public static AbstractRoutePlanner createRouterPlanner(RoutingPlanner routingPlanner, List<IpBlock> ipBlocks) {
|
||||
|
||||
switch (routingPlanner) {
|
||||
case NONE:
|
||||
return null;
|
||||
case ROTATE_ON_BAN:
|
||||
return new RotatingIpRoutePlanner(ipBlocks);
|
||||
case LOAD_BALANCE:
|
||||
return new BalancingIpRoutePlanner(ipBlocks);
|
||||
case NANO_SWITCH:
|
||||
return new NanoIpRoutePlanner(ipBlocks, true);
|
||||
case ROTATING_NANO_SWITCH:
|
||||
return new RotatingNanoIpRoutePlanner(ipBlocks);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown RoutingPlanner value provided");
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetPoTokenAndVisitorData(String userChromePath, String userChromeDriverPath, boolean headless)
|
||||
{
|
||||
//
|
||||
// 1. Find a Chrome install & launch the WebDriver
|
||||
//
|
||||
WebDriverManager wdm = WebDriverManager.chromedriver();
|
||||
if (userChromePath != null) {
|
||||
wdm.browserVersionDetectionCommand(userChromePath + " --version");
|
||||
}
|
||||
else if (wdm.getBrowserPath().isEmpty())
|
||||
{
|
||||
// try finding a Chromium browser instead (Linux users usually install Chromium instead of Google Chrome)
|
||||
wdm = WebDriverManager.chromiumdriver();
|
||||
if (wdm.getBrowserPath().isEmpty()) {
|
||||
LOGGER.error("Could not obtain a PO token for YouTube playback, because no Chrome browser could be found. Please install Google Chrome or Chromium!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (userChromeDriverPath == null)
|
||||
// Automatically download & set up ChromeDriver for the Chrome/Chromium version that the user has installed
|
||||
wdm.setup();
|
||||
else {
|
||||
// Find & use the ChromeDriver executable the user provided
|
||||
String path = new ExecutableFinder().find(userChromeDriverPath);
|
||||
if (path == null) {
|
||||
LOGGER.error("Could not obtain a PO token for YouTube playback, because the specified ChromeDriver could not be found. " +
|
||||
"Please check your config's value of the chromedriverpath, or set it to \"AUTO\" to have JMusicBot automatically download ChromeDriver");
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Using ChromeDriver at {}", path);
|
||||
System.setProperty("webdriver.chrome.driver", path);
|
||||
}
|
||||
|
||||
ChromeOptions chromeOptions = new ChromeOptions();
|
||||
|
||||
chromeOptions.setBinary(userChromePath != null ? userChromePath : wdm.getBrowserPath().get().toString());
|
||||
|
||||
if (!headless)
|
||||
chromeOptions.addArguments("--auto-open-devtools-for-tabs");
|
||||
else
|
||||
chromeOptions.addArguments("--headless=new");
|
||||
|
||||
ChromeDriverService.Builder chromeDriverBuilder = new ChromeDriverService.Builder();
|
||||
if (LOGGER.isDebugEnabled())
|
||||
chromeDriverBuilder.withLogLevel(ChromiumDriverLogLevel.DEBUG);
|
||||
|
||||
WebDriver driver = new ChromeDriver(chromeDriverBuilder.build(), chromeOptions);
|
||||
|
||||
//
|
||||
// 2. Setup a network interceptor to intercept the player API request & obtain po token and visitor data
|
||||
//
|
||||
|
||||
// as the network interceptor below runs on a different thread, we'll have it notify us on this/current thread
|
||||
// whether retrieving the data failed (completes with the Exception) or succeeded (completes with null)
|
||||
CompletableFuture<Exception> interceptorFuture = new CompletableFuture<>();
|
||||
|
||||
NetworkInterceptor interceptor = new NetworkInterceptor(driver,
|
||||
(Filter) next -> req -> {
|
||||
|
||||
if (req.getMethod() != HttpMethod.POST || !req.getUri().contains("/youtubei/v1/player"))
|
||||
return next.execute(req);
|
||||
|
||||
try {
|
||||
String rawBody = Contents.string(req);
|
||||
JSONObject body = new JSONObject(rawBody);
|
||||
|
||||
String visitorData = body
|
||||
.getJSONObject("context")
|
||||
.getJSONObject("client")
|
||||
.getString("visitorData");
|
||||
|
||||
String poToken = body
|
||||
.getJSONObject("serviceIntegrityDimensions")
|
||||
.getString("poToken");
|
||||
|
||||
|
||||
LOGGER.info("Successfully retrieved PO token & visitor data for YouTube playback.");
|
||||
LOGGER.debug("PO token: {}", poToken);
|
||||
LOGGER.debug("Visitor Data: {}", visitorData);
|
||||
|
||||
if (poToken.length() < 160)
|
||||
LOGGER.warn("There is a high chance that the retrieved PO token will not work. Try on a different network.");
|
||||
|
||||
Web.setPoTokenAndVisitorData(poToken, visitorData);
|
||||
|
||||
interceptorFuture.complete(null);
|
||||
} catch (Exception e) {
|
||||
interceptorFuture.complete(e);
|
||||
}
|
||||
|
||||
return next.execute(req);
|
||||
});
|
||||
|
||||
//
|
||||
// 3. Navigate to YouTube & start the player
|
||||
//
|
||||
try {
|
||||
driver.get("https://www.youtube.com/embed/aqz-KE-bpKQ");
|
||||
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(15));
|
||||
WebElement moviePlayer = driver.findElement(By.cssSelector("#movie_player"));
|
||||
moviePlayer.click();
|
||||
|
||||
try {
|
||||
// Wait for the network interceptor to tell us if it got the data
|
||||
Exception result = interceptorFuture.get(20, TimeUnit.SECONDS);
|
||||
if (result != null)
|
||||
throw new Exception("Failed to read YouTube player request body", result);
|
||||
} catch (TimeoutException e) {
|
||||
throw new Exception("Timed out waiting for YouTube player request");
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to obtain PO token for YouTube playback: {}", e.getMessage());
|
||||
LOGGER.debug("Exception:", e);
|
||||
} finally {
|
||||
interceptor.close();
|
||||
// Only quit the browser when not in headless mode. Helps with troubleshooting.
|
||||
if (headless)
|
||||
driver.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.jagrosh.jmusicbot.utils;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.turbo.TurboFilter;
|
||||
import ch.qos.logback.core.spi.FilterReply;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.Marker;
|
||||
|
||||
import java.nio.file.Files;
|
||||
|
||||
/**
|
||||
* A logback turbo filter, used retrieve the YouTube OAuth2 refresh token that gets logged once authorized with YouTube.
|
||||
*
|
||||
* @author Michaili K. <git@michaili.dev>
|
||||
*/
|
||||
public class YoutubeOauth2TokenHandler extends TurboFilter {
|
||||
public final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(YoutubeOauth2TokenHandler.class);
|
||||
private Data data;
|
||||
|
||||
|
||||
public void init()
|
||||
{
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
context.addTurboFilter(this);
|
||||
}
|
||||
|
||||
public Data getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t)
|
||||
{
|
||||
if (!logger.getName().equals("dev.lavalink.youtube.http.YoutubeOauth2Handler"))
|
||||
return FilterReply.NEUTRAL;
|
||||
|
||||
if (format.equals("OAUTH INTEGRATION: To give youtube-source access to your account, go to {} and enter code {}"))
|
||||
{
|
||||
this.data = new Data((String) params[0], (String) params[1]);
|
||||
return FilterReply.NEUTRAL;
|
||||
}
|
||||
if (format.equals("OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. ({})"))
|
||||
{
|
||||
LOGGER.info(
|
||||
"Authorization successful & retrieved token! Storing the token in {}",
|
||||
OtherUtil.getPath("youtubetoken.txt").toAbsolutePath()
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
Files.write(OtherUtil.getPath("youtubetoken.txt"), params[0].toString().getBytes());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error(
|
||||
"Failed to write the YouTube OAuth2 refresh token to storage! You will need to authorize again on the next reboot",
|
||||
e
|
||||
);
|
||||
}
|
||||
return FilterReply.DENY;
|
||||
}
|
||||
|
||||
return FilterReply.NEUTRAL;
|
||||
}
|
||||
|
||||
public static class Data
|
||||
{
|
||||
private final String authorisationUrl;
|
||||
private final String code;
|
||||
|
||||
private Data(String authorisationUrl, String code)
|
||||
{
|
||||
this.authorisationUrl = authorisationUrl;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode()
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getAuthorisationUrl()
|
||||
{
|
||||
return authorisationUrl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -98,6 +106,22 @@ stayinchannel = false
|
||||
|
||||
maxtime = 0
|
||||
|
||||
// This sets the maximum number of pages of songs that can be loaded from a YouTube
|
||||
// playlist. Each page can contain up to 100 tracks. Playing a playlist with more
|
||||
// pages than the maximum will stop loading after the provided number of pages.
|
||||
// For example, if the max was set to 15 and a playlist contained 1850 tracks,
|
||||
// only the first 1500 tracks (15 pages) would be loaded. By default, this is
|
||||
// set to 10 pages (1000 tracks).
|
||||
|
||||
maxytplaylistpages = 10
|
||||
|
||||
|
||||
// This sets the ratio of users that must vote to skip the currently playing song.
|
||||
// Guild owners can define their own skip ratios, but this will be used if a guild
|
||||
// has not defined their own skip ratio.
|
||||
|
||||
skipratio = 0.55
|
||||
|
||||
|
||||
// This sets the amount of seconds the bot will stay alone on a voice channel until it
|
||||
// automatically leaves the voice channel and clears the queue. If not set or set
|
||||
@@ -167,6 +191,7 @@ aliases {
|
||||
movetrack = [ move ]
|
||||
pause = []
|
||||
playnext = []
|
||||
queuetype = []
|
||||
repeat = []
|
||||
skipto = [ jumpto ]
|
||||
stop = [ leave ]
|
||||
@@ -174,6 +199,58 @@ aliases {
|
||||
}
|
||||
|
||||
|
||||
// This sets the logging verbosity.
|
||||
// Available levels: off, error, warn, info, debug, trace, all
|
||||
//
|
||||
// It is recommended to leave this at info. Debug log levels might help with troubleshooting,
|
||||
// but can contain sensitive data.
|
||||
|
||||
loglevel = info
|
||||
|
||||
|
||||
// This sets the executable path of where a Chrome or Chromium installation is located JMusicBot can use.
|
||||
// JMusicBot may launch a web browser to get around anti-bot checks.
|
||||
// Set this to "AUTO" to make JMusicBot attempt to find a Chrome/Chromium installation itself.
|
||||
|
||||
chromepath = "AUTO"
|
||||
|
||||
|
||||
// This sets the executable path of where a ChromeDriver installation is located JMusicBot can use.
|
||||
// Set this to "AUTO" to make JMusicBot automatically download the ChromeDriver version
|
||||
// which matches the installed Chrome version.
|
||||
|
||||
chromedriverpath = "AUTO"
|
||||
|
||||
|
||||
// This sets whether JMusicBot should launch Chrome/Chromium instances without a GUI.
|
||||
// It is recommended to leave this set to true.
|
||||
// If set to false, JMusicBot will not close the browser automatically (even when finished)
|
||||
// and will automatically open DevTools for troubleshooting purposes.
|
||||
|
||||
chromeheadless = true
|
||||
|
||||
|
||||
// This configures the routing planner to use for IP rotation on YouTube.
|
||||
// When set, you need to also set an IP block below.
|
||||
// The following routing strategies are available:
|
||||
// NONE = Disable
|
||||
// ROTATE_ON_BAN = Switch IP when currently used address gets banned.
|
||||
// LOAD_BALANCE = Selects random IP for each track playback.
|
||||
// NANO_SWITCH = Selects IPv6 address based on current nanosecond. Recommended for one /64 IPv6 blocks.
|
||||
// ROTATING_NANO_SWITCH = Same as above, but uses the next /64 IPv6 block in the list in case of a ban.
|
||||
|
||||
ytroutingplanner = NONE
|
||||
|
||||
|
||||
// The IPv4 and IPv6 blocks to use when using the above routing planner.
|
||||
// This does nothing when ytroutingplanner is set to none.
|
||||
// The list accepts a list of IPv4 or IPv6 CIDR blocks (not a mix of them however)
|
||||
// IPv4 example: [ "192.0.2.183", "198.51.100.153/29" ]
|
||||
// IPv6 example: [ "2a02:1234:5678:9abc::/64" ]
|
||||
|
||||
ytipblocks = [ ]
|
||||
|
||||
|
||||
// Transforms are used to modify specific play inputs and convert them to different kinds of inputs
|
||||
// These are quite complicated to use, and have limited use-cases, but in theory allow for rough
|
||||
// whitelists or blacklists, roundabout loading from some sources, and customization of how things are
|
||||
@@ -198,6 +275,7 @@ transforms = {}
|
||||
// IF SOMEONE ASKS YOU TO ENABLE THIS, THERE IS AN 11/10 CHANCE THEY ARE TRYING TO SCAM YOU
|
||||
|
||||
eval=false
|
||||
evalengine="Nashorn"
|
||||
|
||||
|
||||
/// END OF JMUSICBOT CONFIG ///
|
||||
@@ -29,7 +29,7 @@ public class FairQueueTest
|
||||
@Test
|
||||
public void differentIdentifierSize()
|
||||
{
|
||||
FairQueue<Q> queue = new FairQueue<>();
|
||||
FairQueue<Q> queue = new FairQueue<>(null);
|
||||
int size = 100;
|
||||
for(int i=0; i<size; i++)
|
||||
queue.add(new Q(i));
|
||||
@@ -39,7 +39,7 @@ public class FairQueueTest
|
||||
@Test
|
||||
public void sameIdentifierSize()
|
||||
{
|
||||
FairQueue<Q> queue = new FairQueue<>();
|
||||
FairQueue<Q> queue = new FairQueue<>(null);
|
||||
int size = 100;
|
||||
for(int i=0; i<size; i++)
|
||||
queue.add(new Q(0));
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2020 John Grosh <john.a.grosh@gmail.com>.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jagrosh.jmusicbot;
|
||||
|
||||
|
||||
import com.jagrosh.jmusicbot.utils.TimeUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Whew., Inc.
|
||||
*/
|
||||
public class TimeUtilTest
|
||||
{
|
||||
@Test
|
||||
public void singleDigit()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("5");
|
||||
assertNotNull(seek);
|
||||
assertEquals(5000, seek.milliseconds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleDigits()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("99:9:999");
|
||||
assertNotNull(seek);
|
||||
assertEquals(357939000, seek.milliseconds);
|
||||
|
||||
seek = TimeUtil.parseTime("99h9m999s");
|
||||
assertNotNull(seek);
|
||||
assertEquals(357939000, seek.milliseconds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decimalDigits()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("99.5:9.0:999.777");
|
||||
assertNotNull(seek);
|
||||
assertEquals(359739777, seek.milliseconds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seeking()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("5");
|
||||
assertNotNull(seek);
|
||||
assertFalse(seek.relative);
|
||||
assertEquals(5000, seek.milliseconds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativeSeekingForward()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("+5");
|
||||
assertNotNull(seek);
|
||||
assertTrue(seek.relative);
|
||||
assertEquals(5000, seek.milliseconds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativeSeekingBackward()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("-5");
|
||||
assertNotNull(seek);
|
||||
assertTrue(seek.relative);
|
||||
assertEquals(-5000, seek.milliseconds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseTimeArgumentLength()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("");
|
||||
assertNull(seek);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timestampTotalUnits()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("1:1:1:1");
|
||||
assertNull(seek);
|
||||
|
||||
seek = TimeUtil.parseTime("1h2m3m4s5s");
|
||||
assertNotNull(seek);
|
||||
assertEquals(3909000, seek.milliseconds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void relativeSymbol()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("+-1:-+1:+-1");
|
||||
assertNull(seek);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timestampNumberFormat()
|
||||
{
|
||||
TimeUtil.SeekTime seek = TimeUtil.parseTime("1:1:a");
|
||||
assertNull(seek);
|
||||
|
||||
seek = TimeUtil.parseTime("1a2s");
|
||||
assertNotNull(seek);
|
||||
assertEquals(3000, seek.milliseconds);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user