Compare commits

...
13 Commits
Author SHA1 Message Date
John Grosh eca2563abf fix npe and an import (#1567)
* fix npe and an import

* stop double posting

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

* Update youtube source to 1.5.1

---------

Co-authored-by: Michaili K <git@michaili.dev>
2024-08-01 09:14:51 -04:00
Timothy Gillespie f140b41bf5 Use shell comparsion (#1627) 2024-08-01 09:10:04 -04:00
John Grosh 8733899aa0 Update bug-report.yml 2024-07-10 17:35:50 -04:00
Moritz Bender e5fd05e601 Update dependencies to fix youtube issues (#1609) 2024-07-10 13:19:13 +02:00
John Grosh 6a5a9c7a75 Change playlist page count (#1542)
* change playlist page count

* make the value configurable

* small refactor
2024-05-10 17:43:49 -04:00
Michaili K 8557f7ab8e Log track exceptions in the audio handler (#1558) 2024-05-10 15:36:43 -04:00
Michaili K 48e62f1a50 Revert "Always self-deafen (#1491)" (#1551)
This reverts commit a7807b96e2.
2024-05-10 15:32:30 -04:00
John Grosh 6f12c33781 custom eval engine (#1530) 2024-05-10 15:23:30 -04:00
Michaili K 0afb3db269 Switch to lavalink's new YouTube audio source manager (#1552)
* Switch to new lavalink's new YouTube audio source manager

* Add back setPlaylistPageCount call for the youtube audio source manager

* Manually add all the individual audio sources EXCEPT for old youtube

* Remove unused import

* Upgrade lavaplayer-youtube-source to 1.0.3

* Upgrade lavaplayer-youtube-source to 1.0.4
2024-05-10 14:19:11 -04:00
Michaili KandWhew., Inc 81322ef993 Add seek command (supersedes #344) (#674)
* Add seek command

* Combine nested if statements

* Put the seek command in correct place to keep alphabetical order

* Add license header

* Brackets on next line

* Restructure if-statements

* Check for permissions with DJCommand#checkDJPermission

* Make regex slightly smaller

* Optimize imports

* Output length of current track if requested seek time is invalid

* Restate seeked point when seeked successfully

* Add empty newline at end of file to keep consistency

* Create TimeUtil class for parsing and formatting time

* Move FormatUtil#formatTime to TimeUtil, and refactor

* Apply requested changes (Pass 2)

* Seek based on current position in track

* Apply requested changes (Pass 3)

* Add javadoc param

* Apply requested changes (Pass 4)

* Fix merge

* Avoid reassigning parameter (Codacy)

* Rework timestamp parsing

* Refactor timestamp parsing

* Apply requested changes (Pass 5)

* Add examples in help

* Apply requested changes (Pass 6)

* Fix missing import

* Keep track of start timestamp & add "unit" times

* Fix my abdominal merge with QueuedTrack

* Use RequestMetadata to store start timestamp

* Store request info in request metadata

* Add regex to try getting a timestamp from the url

* Require RequestMetadata for QueuedTracks

* Add some unit tests for unit seeking

* Add docs & examples

---------

Co-authored-by: Whew., Inc <22574706+Whew-Inc@users.noreply.github.com>
2024-05-10 14:14:28 -04:00
Michaili KandJohn Grosh b3ffbdba7d Add log level configuration (#1156)
* Add loglevel config

* Change order of fields

---------

Co-authored-by: John Grosh <john.a.grosh@gmail.com>
2024-03-30 15:34:46 -04:00
John Grosh 801b3769a1 Fix startup message type and add URL to message (#1521)
* fix

* add URL to error message
2024-03-30 15:18:52 -04:00
25 changed files with 554 additions and 120 deletions

No files matched your search

+8 -1
View File
@@ -53,5 +53,12 @@ body:
required: true
- label: I have searched for similar issues [on the issues page](https://github.com/jagrosh/MusicBot/issues?q=is%3Aissue)
required: true
- label: "I am running the latest version of the bot: ![Release](https://img.shields.io/github/release/jagrosh/MusicBot.svg)"
- label: I am using an official release from [the releases page](https://github.com/jagrosh/MusicBot/releases)
required: true
- type: input
id: version
attributes:
label: JMusicBot Version
description: Which version of JMusicBot are you running?
validations:
required: true
+8 -3
View File
@@ -31,9 +31,9 @@
<url>https://m2.duncte123.dev/releases</url>
</repository>
<repository>
<id>arbjergDev-snapshots</id>
<id>arbjergDev</id>
<name>Lavalink Repository</name>
<url>https://maven.lavalink.dev/snapshots</url>
<url>https://maven.lavalink.dev/releases</url>
</repository>
</repositories>
@@ -55,7 +55,12 @@
<dependency>
<groupId>dev.arbjerg</groupId>
<artifactId>lavaplayer</artifactId>
<version>727959e9f621fc457b3a5adafcfffb55fdeaa538-SNAPSHOT</version>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>dev.lavalink.youtube</groupId>
<artifactId>common</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>com.github.jagrosh</groupId>
+1 -1
View File
@@ -9,7 +9,7 @@ DOWNLOAD=true
LOOP=true
download() {
if [ $DOWNLOAD == true ]; then
if [ $DOWNLOAD = true ]; then
URL=$(curl -s https://api.github.com/repos/jagrosh/MusicBot/releases/latest \
| grep -i "browser_download_url.*\.jar" \
| sed 's/.*\(http.*\)"/\1/')
@@ -16,8 +16,8 @@
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.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.typesafe.config.*;
import java.io.IOException;
@@ -39,10 +39,12 @@ 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 String token, prefix, altprefix, helpWord, playlistsFolder, logLevel,
successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji,
evalEngine;
private boolean stayInChannel, songInGame, npImages, updatealerts, useEval, dbots;
private long owner, maxSeconds, aloneTimeUntilStop;
private int maxYTPlaylistPages;
private double skipratio;
private OnlineStatus status;
private Activity game;
@@ -86,8 +88,11 @@ public class BotConfig
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");
@@ -312,12 +317,22 @@ public class BotConfig
{
return updatealerts;
}
public String getLogLevel()
{
return logLevel;
}
public boolean useEval()
{
return useEval;
}
public String getEvalEngine()
{
return evalEngine;
}
public boolean useNPImages()
{
return npImages;
@@ -328,9 +343,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()
@@ -38,6 +38,7 @@ 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;
/**
*
@@ -82,6 +83,10 @@ public class JMusicBot
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();
@@ -137,7 +142,7 @@ public class JMusicBot
// message content intent
if(!"@mention".equals(config.getPrefix()))
{
prompt.alert(Prompt.Level.INFO, "JMusicBot", "You currently have a custom prefix set. "
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");
}
@@ -194,6 +199,7 @@ public class JMusicBot
new RemoveCmd(bot),
new SearchCmd(bot),
new SCSearchCmd(bot),
new SeekCmd(bot),
new ShuffleCmd(bot),
new SkipCmd(bot),
@@ -18,9 +18,11 @@ package com.jagrosh.jmusicbot.audio;
import com.jagrosh.jmusicbot.playlist.PlaylistLoader.Playlist;
import com.jagrosh.jmusicbot.queue.AbstractQueue;
import com.jagrosh.jmusicbot.settings.QueueType;
import com.jagrosh.jmusicbot.utils.TimeUtil;
import com.jagrosh.jmusicbot.settings.RepeatMode;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
@@ -39,6 +41,7 @@ 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.LoggerFactory;
/**
*
@@ -50,6 +53,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
public final static String PAUSE_EMOJI = "\u23F8"; // ⏸
public final static String STOP_EMOJI = "\u23F9"; // ⏹
private final List<AudioTrack> defaultQueue = new LinkedList<>();
private final Set<String> votes = new HashSet<>();
@@ -197,6 +201,11 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
}
}
@Override
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) {
LoggerFactory.getLogger("AudioHandler").error("Track " + track.getIdentifier() + " has failed to play", exception);
}
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track)
{
@@ -246,7 +255,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler
double progress = (double)audioPlayer.getPlayingTrack().getPosition()/track.getDuration();
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.setEmbeds(eb.build()).build();
@@ -17,10 +17,19 @@ package com.jagrosh.jmusicbot.audio;
import com.dunctebot.sourcemanagers.DuncteBotSources;
import com.jagrosh.jmusicbot.Bot;
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.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 dev.lavalink.youtube.YoutubeAudioSourceManager;
import net.dv8tion.jda.api.entities.Guild;
/**
@@ -39,10 +48,23 @@ public class PlayerManager extends DefaultAudioPlayerManager
public void init()
{
TransformativeAudioSourceManager.createTransforms(bot.getConfig().getTransforms()).forEach(t -> registerSourceManager(t));
AudioSourceManagers.registerRemoteSources(this);
YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true);
yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages());
registerSourceManager(yt);
registerSourceManager(SoundCloudAudioSourceManager.createDefault());
registerSourceManager(new BandcampAudioSourceManager());
registerSourceManager(new VimeoAudioSourceManager());
registerSourceManager(new TwitchStreamAudioSourceManager());
registerSourceManager(new BeamAudioSourceManager());
registerSourceManager(new GetyarnAudioSourceManager());
registerSourceManager(new NicoAudioSourceManager());
registerSourceManager(new HttpAudioSourceManager(MediaContainerRegistry.DEFAULT_REGISTRY));
AudioSourceManagers.registerLocalSource(this);
DuncteBotSources.registerAll(this, "en-US");
source(YoutubeAudioSourceManager.class).setPlaylistPageCount(10);
}
public Bot getBot()
@@ -15,10 +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;
/**
@@ -28,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 == 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() == null ? 0L : track.getUserData(RequestMetadata.class).getOwner();
return requestMetadata.getOwner();
}
public AudioTrack getTrack()
@@ -51,10 +51,15 @@ public class QueuedTrack implements Queueable
return track;
}
public RequestMetadata getRequestMetadata()
{
return requestMetadata;
}
@Override
public String toString()
{
String entry = "`[" + FormatUtil.formatTime(track.getDuration()) + "]` ";
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;
@@ -86,7 +86,6 @@ public abstract class MusicCommand extends Command
try
{
event.getGuild().getAudioManager().openAudioConnection(userState.getChannel());
event.getGuild().getAudioManager().setSelfDeafened(true);
}
catch(PermissionException ex)
{
@@ -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();
}
@@ -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]++;
}
});
@@ -243,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!");
@@ -27,6 +27,7 @@ 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;
@@ -112,7 +113,7 @@ public class QueueCmd extends MusicCommand
.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(" 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()) + "`!");
}
}
@@ -29,6 +29,7 @@ import net.dv8tion.jda.api.entities.ChannelType;
public class EvalCmd extends OwnerCommand
{
private final Bot bot;
private final String engine;
public EvalCmd(Bot bot)
{
@@ -36,13 +37,20 @@ 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());
@@ -24,6 +24,7 @@ import java.util.HashMap;
import net.dv8tion.jda.api.entities.Guild;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
@@ -32,6 +33,7 @@ import org.slf4j.LoggerFactory;
*/
public class SettingsManager implements GuildSettingsManager<Settings>
{
private final static Logger LOG = LoggerFactory.getLogger("Settings");
private final static String SETTINGS_FILE = "serversettings.json";
private final HashMap<Long,Settings> settings;
@@ -63,17 +65,17 @@ public class SettingsManager implements GuildSettingsManager<Settings>
} catch (NoSuchFileException e) {
// create an empty json file
try {
LoggerFactory.getLogger("Settings").info("serversettings.json will be created in " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
LOG.info("serversettings.json will be created in " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
Files.write(OtherUtil.getPath("serversettings.json"), new JSONObject().toString(4).getBytes());
} catch(IOException ex) {
LoggerFactory.getLogger("Settings").warn("Failed to create new settings file: "+ex);
LOG.warn("Failed to create new settings file: "+ex);
}
return;
} catch(IOException | JSONException e) {
LoggerFactory.getLogger("Settings").warn("Failed to load server settings: "+e);
LOG.warn("Failed to load server settings: "+e);
}
LoggerFactory.getLogger("Settings").info("serversettings.json loaded from " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
LOG.info("serversettings.json loaded from " + OtherUtil.getPath("serversettings.json").toAbsolutePath());
}
/**
@@ -127,7 +129,7 @@ public class SettingsManager implements GuildSettingsManager<Settings>
try {
Files.write(OtherUtil.getPath(SETTINGS_FILE), obj.toString(4).getBytes());
} catch(IOException ex){
LoggerFactory.getLogger("Settings").warn("Failed to write to file: "+ex);
LOG.warn("Failed to write to file: "+ex);
}
}
}
@@ -27,41 +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)
{
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);
}
public static String formatUsername(String username, String discrim)
public static String formatUsername(String username, String discrim)
{
if(discrim == null || discrim.equals("0000"))
if(discrim == null || discrim.equals("0000"))
{
return username;
}
else
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 = "";
@@ -1,44 +0,0 @@
/*
* Copyright 2024 John Grosh <john.a.grosh@gmail.com>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jagrosh.jmusicbot.utils;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.spi.FilterReply;
import org.slf4j.Marker;
/**
* A TurboFilter, currently only used to suppress specific log messages from libraries.
*
* @author Michaili K. <git@michaili.dev>
*/
public class LogBackTurboFilter extends TurboFilter
{
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t)
{
// Suppresses the auth token warning from the YoutubeAudioSourceManager
// https://github.com/jagrosh/MusicBot/pull/1490#issuecomment-1974070225
if (logger.getName().equals("com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAccessTokenTracker")
&& format.equals("YouTube auth tokens can't be retrieved because email and password is not set in YoutubeAudioSourceManager, age restricted videos will throw exceptions.")
) {
return FilterReply.DENY;
}
return FilterReply.NEUTRAL;
}
}
@@ -223,7 +223,10 @@ public class OtherUtil
ApplicationInfo info = jda.retrieveApplicationInfo().complete();
if (info.isBotPublic())
return "\"Public Bot\" is enabled. Using JMusicBot as a public bot is not supported. Please disable it in the Developer Dashboard.";
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: [+ | -] &lt;HH:MM:SS | MM:SS | SS&gt;
* @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;
}
}
}
-2
View File
@@ -14,6 +14,4 @@
<appender-ref ref="Simple"/>
</root>
<turboFilter class="com.jagrosh.jmusicbot.utils.LogBackTurboFilter" />
</configuration>
+19
View File
@@ -98,6 +98,15 @@ 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
@@ -182,6 +191,15 @@ 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
// 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
@@ -206,6 +224,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 ///
@@ -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);
}
}