From 8325256e5ea9ee72873f7fb80c4024ff565a7c44 Mon Sep 17 00:00:00 2001 From: anthorob Date: Fri, 27 May 2022 18:16:22 -0400 Subject: [PATCH] Improve music manager and add more debug messages --- .../java/netneutrality/NetNeutrality.java | 8 +- .../command/crypto/BitcoinPriceCommand.java | 4 +- .../command/crypto/CryptoPriceCommand.java | 4 +- .../command/crypto/EthereumPriceCommand.java | 4 +- .../command/crypto/LitecoinPriceCommand.java | 4 +- .../command/message/BlacklistCommand.java | 2 +- .../command/music/DeleteQueueCommand.java | 7 +- .../command/music/JoinCommand.java | 6 +- .../command/music/LeaveCommand.java | 1 + .../command/music/PlayCommand.java | 3 - .../command/music/SkipCommand.java | 3 + .../command/music/StopCommand.java | 1 + .../command/specific/DatabaseCommand.java | 4 +- .../netneutrality/core/CommandManager.java | 4 +- .../java/netneutrality/core/GuildManager.java | 2 +- .../core/context/CommandContext.java | 29 +++ .../netneutrality/db/AbstractController.java | 7 - .../java/netneutrality/db/DBController.java | 72 +----- .../db/controllers/DBBlacklist.java | 5 - .../db/controllers/DBMessage.java | 6 - .../handler/hooks/message/EditHandler.java | 4 +- .../java/netneutrality/http/WebModule.java | 13 +- .../netneutrality/music/MusicManager.java | 233 +++++++++++++----- .../netneutrality/music/QueueManager.java | 58 ++++- .../netneutrality/music/TopicManager.java | 20 +- .../netneutrality/music/TrackScheduler.java | 14 +- .../music/loadable/AppleMostPlayed.java | 42 ++++ .../music/loadable/CachedSource.java | 40 +++ .../music/loadable/LoadableMusicSource.java | 26 ++ .../music/loadable/LoadableSong.java | 19 ++ .../music/loadable/LoadableSourceManager.java | 59 +++++ .../MusicPlayerAutoDisconnectThread.java | 5 +- .../netneutrality/utils/MessageFunction.java | 13 +- .../java/netneutrality/utils/MusicUtils.java | 2 +- .../music/LoadableSourceTest.java | 31 +++ 35 files changed, 557 insertions(+), 198 deletions(-) create mode 100644 src/main/java/netneutrality/music/loadable/AppleMostPlayed.java create mode 100644 src/main/java/netneutrality/music/loadable/CachedSource.java create mode 100644 src/main/java/netneutrality/music/loadable/LoadableMusicSource.java create mode 100644 src/main/java/netneutrality/music/loadable/LoadableSong.java create mode 100644 src/main/java/netneutrality/music/loadable/LoadableSourceManager.java create mode 100644 src/test/java/netneutrality/music/LoadableSourceTest.java diff --git a/src/main/java/netneutrality/NetNeutrality.java b/src/main/java/netneutrality/NetNeutrality.java index e4d5c4e..3b8f600 100644 --- a/src/main/java/netneutrality/NetNeutrality.java +++ b/src/main/java/netneutrality/NetNeutrality.java @@ -18,6 +18,8 @@ import netneutrality.thread.BaseThreadFactory; import netneutrality.thread.ShutdownThread; import netneutrality.thread.ThreadManager; import netneutrality.utils.Settings; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.Configurator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,8 +40,12 @@ public class NetNeutrality { options.setDebug(env.equals("dev")); }); - initJDA(); + if ("dev".equals(env)) { + Configurator.setLevel("net.dv8tion.jda", Level.DEBUG); + } + DBController.init(); + initJDA(); CommandManager.init(); MusicManager.initPlayerManager(); jda.addEventListener(new MessageReceivedHandler(), new EditHandler(), new ServerJoinHandler()); diff --git a/src/main/java/netneutrality/command/crypto/BitcoinPriceCommand.java b/src/main/java/netneutrality/command/crypto/BitcoinPriceCommand.java index adba8db..8e31c2d 100644 --- a/src/main/java/netneutrality/command/crypto/BitcoinPriceCommand.java +++ b/src/main/java/netneutrality/command/crypto/BitcoinPriceCommand.java @@ -86,14 +86,14 @@ public class BitcoinPriceCommand extends AbstractCommand { try { WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request(); JSONObject json = http.getAsObject(); - if (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error")) { + if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) { t.editMessage(String.format("Failed getting price of %s", crypto)).queue(); return; } price = dollarFormat.format(json.getDouble(finalCurrency)); t.editMessage(String.format("The current price of %s in %s is %s", crypto, finalCurrency, price)).queue(); - } catch (IOException | JSONException | IllegalArgumentException | ArithmeticException e) { + } catch (JSONException | IllegalArgumentException | ArithmeticException e) { t.editMessage(String.format("Error getting price of %s!", crypto)).queue(); LogHandler.log(e); } diff --git a/src/main/java/netneutrality/command/crypto/CryptoPriceCommand.java b/src/main/java/netneutrality/command/crypto/CryptoPriceCommand.java index c05a072..1a220bb 100644 --- a/src/main/java/netneutrality/command/crypto/CryptoPriceCommand.java +++ b/src/main/java/netneutrality/command/crypto/CryptoPriceCommand.java @@ -89,7 +89,7 @@ public class CryptoPriceCommand extends AbstractCommand { try { WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request(); JSONObject json = http.getAsObject(); - if (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error")) { + if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) { t.editMessage("Crypto doesn't exist.. yet").queue(); return; } @@ -97,7 +97,7 @@ public class CryptoPriceCommand extends AbstractCommand { price = dollarFormat.format(json.getDouble(finalCurrency)); t.editMessage(String.format("The current price of %s in %s is %s", crypto, finalCurrency, price)).queue(); - } catch (IOException | JSONException | IllegalArgumentException | ArithmeticException e) { + } catch (JSONException | IllegalArgumentException | ArithmeticException e) { t.editMessage(String.format("Error getting price of %s!", crypto)).queue(); LogHandler.log(e); } diff --git a/src/main/java/netneutrality/command/crypto/EthereumPriceCommand.java b/src/main/java/netneutrality/command/crypto/EthereumPriceCommand.java index 65948cf..414f445 100644 --- a/src/main/java/netneutrality/command/crypto/EthereumPriceCommand.java +++ b/src/main/java/netneutrality/command/crypto/EthereumPriceCommand.java @@ -86,14 +86,14 @@ public class EthereumPriceCommand extends AbstractCommand { try { WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request(); JSONObject json = http.getAsObject(); - if (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error")) { + if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) { t.editMessage(String.format("Failed getting price of %s", crypto)).queue(); return; } price = dollarFormat.format(json.getDouble(finalCurrency)); t.editMessage(String.format("The current price of %s in %s is %s", crypto, finalCurrency, price)).queue(); - } catch (IOException | JSONException | IllegalArgumentException e) { + } catch (JSONException | IllegalArgumentException e) { t.editMessage(String.format("Error getting price of %s!", crypto)).queue(); LogHandler.log(e); } diff --git a/src/main/java/netneutrality/command/crypto/LitecoinPriceCommand.java b/src/main/java/netneutrality/command/crypto/LitecoinPriceCommand.java index 165cc51..4a1efb7 100644 --- a/src/main/java/netneutrality/command/crypto/LitecoinPriceCommand.java +++ b/src/main/java/netneutrality/command/crypto/LitecoinPriceCommand.java @@ -86,14 +86,14 @@ public class LitecoinPriceCommand extends AbstractCommand { try { WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request(); JSONObject json = http.getAsObject(); - if (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error")) { + if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) { t.editMessage(String.format("Failed getting price of %s", crypto)).queue(); return; } price = dollarFormat.format(json.getDouble(finalCurrency)); t.editMessage(String.format("The current price of %s in %s is %s", crypto, finalCurrency, price)).queue(); - } catch (IOException | JSONException | IllegalArgumentException | ArithmeticException e) { + } catch (JSONException | IllegalArgumentException | ArithmeticException e) { t.editMessage(String.format("Error getting price of %s!", crypto)).queue(); LogHandler.log(e); } diff --git a/src/main/java/netneutrality/command/message/BlacklistCommand.java b/src/main/java/netneutrality/command/message/BlacklistCommand.java index fc13906..920b88d 100644 --- a/src/main/java/netneutrality/command/message/BlacklistCommand.java +++ b/src/main/java/netneutrality/command/message/BlacklistCommand.java @@ -57,7 +57,7 @@ public class BlacklistCommand extends AbstractCommand { @Override public void execute(CommandContext context) { String[] args = context.getArgumentsAsArray(); - DBBlacklist controller = DBController.get("blacklist", DBBlacklist.class); + DBBlacklist controller = DBController.get(DBBlacklist.class); if (args.length > 0 && args.length < 3) { String word = (args.length == 2 ? args[1] : ""); diff --git a/src/main/java/netneutrality/command/music/DeleteQueueCommand.java b/src/main/java/netneutrality/command/music/DeleteQueueCommand.java index 669bb6c..000d060 100644 --- a/src/main/java/netneutrality/command/music/DeleteQueueCommand.java +++ b/src/main/java/netneutrality/command/music/DeleteQueueCommand.java @@ -79,8 +79,11 @@ public class DeleteQueueCommand extends AbstractMusicCommand { int num = Integer.parseInt(context.getArg(0)); if (num > 0) { - player.removeFromQueue(num); - context.reply("Removing track #%d from the queue", num); + if (player.removeFromQueue(num)) { + context.reply("Removed track #%d from the queue", num); + } else { + context.reply("Error while removing track #%d from the queue", num); + } } else { context.reply("Number out of range"); } diff --git a/src/main/java/netneutrality/command/music/JoinCommand.java b/src/main/java/netneutrality/command/music/JoinCommand.java index d9c9b57..c12784e 100644 --- a/src/main/java/netneutrality/command/music/JoinCommand.java +++ b/src/main/java/netneutrality/command/music/JoinCommand.java @@ -60,6 +60,10 @@ public class JoinCommand extends AbstractMusicCommand { } VoiceChannel vChannel = context.getGuildUser().getVoiceState().getChannel(); - player.connect(vChannel); + if (vChannel != null) { + player.connect(vChannel); + context.reply("Joining current voice channel"); + } + } } diff --git a/src/main/java/netneutrality/command/music/LeaveCommand.java b/src/main/java/netneutrality/command/music/LeaveCommand.java index 9a1d844..0b1df1f 100644 --- a/src/main/java/netneutrality/command/music/LeaveCommand.java +++ b/src/main/java/netneutrality/command/music/LeaveCommand.java @@ -50,6 +50,7 @@ public class LeaveCommand extends AbstractMusicCommand { if (MusicUtils.checkIfConnected(player, context)) { player.leave(); + context.reply("Leaving voice channel and clearing queue"); } } } diff --git a/src/main/java/netneutrality/command/music/PlayCommand.java b/src/main/java/netneutrality/command/music/PlayCommand.java index 07df0c2..9c219ac 100644 --- a/src/main/java/netneutrality/command/music/PlayCommand.java +++ b/src/main/java/netneutrality/command/music/PlayCommand.java @@ -12,8 +12,6 @@ import netneutrality.command.CommandUsage; import netneutrality.core.context.CommandContext; import netneutrality.music.MusicManager; -import java.util.concurrent.TimeUnit; - public class PlayCommand extends AbstractMusicCommand { @Override @@ -47,7 +45,6 @@ public class PlayCommand extends AbstractMusicCommand { @Override public void execute(CommandContext context) { MusicManager player = MusicManager.get(context.getGuild()); - context.getMessage().delete().queueAfter(1, TimeUnit.SECONDS); if (context.getArgs().length > 0) { player.play(context.getMessageText(), context, false, false); diff --git a/src/main/java/netneutrality/command/music/SkipCommand.java b/src/main/java/netneutrality/command/music/SkipCommand.java index 86cab80..eb784f3 100644 --- a/src/main/java/netneutrality/command/music/SkipCommand.java +++ b/src/main/java/netneutrality/command/music/SkipCommand.java @@ -52,11 +52,14 @@ public class SkipCommand extends AbstractMusicCommand { try { int skips = Integer.parseInt(context.getArg(0)); player.skip(skips); + + context.reply("Skipping %s track%s", skips, (skips > 1 ? "s" : "")); } catch (NumberFormatException e) { context.reply("Please enter how many track(s) to skip"); } } else { player.skip(1); + context.reply("Skipping current track"); } } else { player.voteSkip(context); diff --git a/src/main/java/netneutrality/command/music/StopCommand.java b/src/main/java/netneutrality/command/music/StopCommand.java index 3c23419..fe0c0e6 100644 --- a/src/main/java/netneutrality/command/music/StopCommand.java +++ b/src/main/java/netneutrality/command/music/StopCommand.java @@ -46,6 +46,7 @@ public class StopCommand extends AbstractMusicCommand { if (MusicUtils.checkIfConnected(player, context) && MusicUtils.isDj(player, context)) { player.stopTrack(); + context.reply("Stopping current track"); } } } diff --git a/src/main/java/netneutrality/command/specific/DatabaseCommand.java b/src/main/java/netneutrality/command/specific/DatabaseCommand.java index 210b317..edd2928 100644 --- a/src/main/java/netneutrality/command/specific/DatabaseCommand.java +++ b/src/main/java/netneutrality/command/specific/DatabaseCommand.java @@ -74,7 +74,7 @@ public class DatabaseCommand extends AbstractCommand { if (args.length > 1) { - OLogMessage message = DBController.get("message", DBMessage.class).getMessage(args[1]); + OLogMessage message = DBController.get(DBMessage.class).getMessage(args[1]); if (message != null) { EmbedBuilder embed = new EmbedBuilder(); embed.setColor(Color.BLUE); @@ -88,7 +88,7 @@ public class DatabaseCommand extends AbstractCommand { //TODO: cached user lol xd User user = context.getJDA().getUserById(args[1]); if (user != null) { - List messages = DBController.get("message", DBMessage.class) + List messages = DBController.get(DBMessage.class) .getMessagesFromUser(user.getName(), context.getGuild().getName(), 10); EmbedBuilder embed = new EmbedBuilder(); embed.setColor(Color.BLUE); diff --git a/src/main/java/netneutrality/core/CommandManager.java b/src/main/java/netneutrality/core/CommandManager.java index 16a5234..049becc 100644 --- a/src/main/java/netneutrality/core/CommandManager.java +++ b/src/main/java/netneutrality/core/CommandManager.java @@ -49,8 +49,8 @@ public class CommandManager { commands.put(command.getCommandName(), command); } } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException e) { - e.printStackTrace(); + | InvocationTargetException | NoSuchMethodException | SecurityException e) { + LogHandler.log(e); } } diff --git a/src/main/java/netneutrality/core/GuildManager.java b/src/main/java/netneutrality/core/GuildManager.java index 9e311ef..e0a5558 100644 --- a/src/main/java/netneutrality/core/GuildManager.java +++ b/src/main/java/netneutrality/core/GuildManager.java @@ -228,7 +228,7 @@ public class GuildManager { public void checkAndLogMessage(Message message, Member member, TextChannel channel) { if (isLoggingEnabled()) { - ThreadManager.getExecutor("log-messages").submit(() -> DBController.get("message", DBMessage.class).log(this, channel, member, message)); + ThreadManager.getExecutor("log-messages").submit(() -> DBController.get(DBMessage.class).log(this, channel, member, message)); } } diff --git a/src/main/java/netneutrality/core/context/CommandContext.java b/src/main/java/netneutrality/core/context/CommandContext.java index 0dd780a..22a08d4 100644 --- a/src/main/java/netneutrality/core/context/CommandContext.java +++ b/src/main/java/netneutrality/core/context/CommandContext.java @@ -24,6 +24,7 @@ import netneutrality.utils.UserFunction; import java.util.Collection; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; public class CommandContext { @@ -153,6 +154,18 @@ public class CommandContext { } } + public void replyToMessage(String msg) { + if (channel != null && channel.canTalk() && message != null) { + replyToMessage(new MessageBuilder(msg).build()); + } + } + + public void replyToMessage(Message msg) { + if (channel != null && channel.canTalk() && message != null) { + message.reply(msg).queue(); + } + } + public void replyPrivate(String msg) { replyPrivate(new MessageBuilder(msg).build()); } @@ -169,6 +182,22 @@ public class CommandContext { user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embeds)).queue(); } + public void deleteMessage() { + if (message != null) { + message.delete().queueAfter(1, TimeUnit.SECONDS); + } + } + + public boolean isMessageFromOwner() { + return member.isOwner(); + } + + public boolean isMessageFromCreator() { + String creatorId = Settings.getInstance().getCreatorId(); + + return creatorId.equals(member.getId()); + } + public void dispatch(AbstractCommand command) { this.command = command; //TODO: Add usage check diff --git a/src/main/java/netneutrality/db/AbstractController.java b/src/main/java/netneutrality/db/AbstractController.java index 7b8deae..b282546 100644 --- a/src/main/java/netneutrality/db/AbstractController.java +++ b/src/main/java/netneutrality/db/AbstractController.java @@ -14,13 +14,6 @@ public abstract class AbstractController { protected SessionFactory sessionFactory; - /** - * The controller name to be used in {@link DBController#get(String controller, Class returnType)} method - * - * @return String - */ - public abstract String getControllerName(); - public AbstractController() { sessionFactory = SessionManager.getSessionFactory(); } diff --git a/src/main/java/netneutrality/db/DBController.java b/src/main/java/netneutrality/db/DBController.java index 2476b64..051db3e 100644 --- a/src/main/java/netneutrality/db/DBController.java +++ b/src/main/java/netneutrality/db/DBController.java @@ -8,86 +8,32 @@ package netneutrality.db; */ import netneutrality.core.SessionManager; -import netneutrality.thread.ThreadManager; -import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Set; -/** - * When creating a controller, all database actions must be executed in another thread using an executor. One is provided in the {@link ThreadManager} class - * A simple thread can't be used in this case because overlapping actions might happen and won't be executed. - */ public class DBController { - - /** - * A map that contains all the controllers currently loaded - */ - private final static HashMap controllers = new HashMap<>(); + private final static Logger logger = LoggerFactory.getLogger(DBController.class); - /** - * @param controller The name of the controller as defined in the {@link AbstractController#getControllerName()}'s method - * @return {@link AbstractController} The class with an AbstractController type - */ - @SuppressWarnings("unchecked") - public static T get(String controller, Class returnType) { + public static T get(Class returnType) { if (returnType == null) throw new IllegalArgumentException("return type is null"); - if (controllers.isEmpty()) - init(); - - if (controllers.containsKey(controller)) { - return (T) controllers.get(controller); - } else - throw new RuntimeException("Error getting database controller"); + try { + return returnType.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } } public static void shutdown() { SessionManager.shutdown(); } - - /** - * Optional, will be called if the method {@link #get(String, Class) get()} is used - */ + public static void init() { - if (controllers.isEmpty()) - loadControllers(); + SessionManager.getSessionFactory(); } - /** - * Loads all the controllers which extends {@link AbstractController} - */ - private static void loadControllers() { - //TODO: to delete - logger.info("Loading databases controllers.."); - - Reflections reflections = new Reflections("netneutrality.db.controllers"); - Set> classes = reflections.getSubTypesOf(AbstractController.class); - - for (Class s : classes) { - try { - if (Modifier.isAbstract(s.getModifiers())) { - continue; - } - - AbstractController c = s.getConstructor().newInstance(); - - if (!controllers.containsKey(c.getControllerName())) { - controllers.put(c.getControllerName(), c); - logger.debug("Loaded controller: " + c.getControllerName()); - } - - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) { - e.printStackTrace(); - } - } - } - - } diff --git a/src/main/java/netneutrality/db/controllers/DBBlacklist.java b/src/main/java/netneutrality/db/controllers/DBBlacklist.java index 9f381e7..725316e 100644 --- a/src/main/java/netneutrality/db/controllers/DBBlacklist.java +++ b/src/main/java/netneutrality/db/controllers/DBBlacklist.java @@ -128,9 +128,4 @@ public class DBBlacklist extends AbstractController { session.close(); return blacklist; } - - @Override - public String getControllerName() { - return "blacklist"; - } } diff --git a/src/main/java/netneutrality/db/controllers/DBMessage.java b/src/main/java/netneutrality/db/controllers/DBMessage.java index 3978b45..84d2641 100644 --- a/src/main/java/netneutrality/db/controllers/DBMessage.java +++ b/src/main/java/netneutrality/db/controllers/DBMessage.java @@ -96,10 +96,4 @@ public class DBMessage extends AbstractController { return message.getMessage(); } - - - @Override - public String getControllerName() { - return "message"; - } } diff --git a/src/main/java/netneutrality/handler/hooks/message/EditHandler.java b/src/main/java/netneutrality/handler/hooks/message/EditHandler.java index 1f0bd94..03cc806 100644 --- a/src/main/java/netneutrality/handler/hooks/message/EditHandler.java +++ b/src/main/java/netneutrality/handler/hooks/message/EditHandler.java @@ -46,8 +46,8 @@ public class EditHandler extends ListenerAdapter { TextChannel channel = event.getGuild().getTextChannelById(guildEntity.getLogChannel()); if (channel != null) { - String originalMessage = DBController.get("message", DBMessage.class).getMessageString(event.getMessage().getId()); - DBController.get("message", DBMessage.class).logEdit(event.getMessage().getId(), event.getMessage().getContentDisplay()); + String originalMessage = DBController.get(DBMessage.class).getMessageString(event.getMessage().getId()); + DBController.get(DBMessage.class).logEdit(event.getMessage().getId(), event.getMessage().getContentDisplay()); channel.sendMessageEmbeds(MessageFunction.buildEditMessage(event.getMember(), event.getMessage(), originalMessage)).queue(); } diff --git a/src/main/java/netneutrality/http/WebModule.java b/src/main/java/netneutrality/http/WebModule.java index 38a0b84..376037b 100644 --- a/src/main/java/netneutrality/http/WebModule.java +++ b/src/main/java/netneutrality/http/WebModule.java @@ -2,6 +2,7 @@ package netneutrality.http; import io.sentry.Breadcrumb; import io.sentry.Sentry; +import netneutrality.core.LogHandler; import okhttp3.*; import org.json.JSONArray; import org.json.JSONException; @@ -144,11 +145,17 @@ public class WebModule { /** * @return the response as a JSONObject */ - public JSONObject getAsObject() throws JSONException, IOException { + public JSONObject getAsObject() { if (response == null) throw new RuntimeException("Request has not been sent"); - - return new JSONObject(response.body().string()); + + try { + ResponseBody body = response.body(); + return new JSONObject(body.string()); + } catch (IOException | NullPointerException e) { + LogHandler.log(e); + return new JSONObject(); + } } /** diff --git a/src/main/java/netneutrality/music/MusicManager.java b/src/main/java/netneutrality/music/MusicManager.java index 64e0ca9..01c4eea 100644 --- a/src/main/java/netneutrality/music/MusicManager.java +++ b/src/main/java/netneutrality/music/MusicManager.java @@ -21,16 +21,14 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.VoiceChannel; import netneutrality.core.GuildManager; -import netneutrality.core.LogHandler; import netneutrality.core.context.CommandContext; -import netneutrality.exceptions.CommandErrorException; +import netneutrality.music.loadable.LoadableSong; import netneutrality.thread.MusicPlayerAutoDisconnectThread; import netneutrality.thread.ThreadManager; import netneutrality.utils.MessageFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Queue; @@ -45,21 +43,19 @@ public class MusicManager { private final static Map managers = new ConcurrentHashMap<>(); private final static ScheduledExecutorService executor = (ScheduledExecutorService) ThreadManager.getExecutor("music", ThreadManager.ExecutorType.SCHEDULED); + private final static AudioPlayerManager playerManager = new DefaultAudioPlayerManager(); private final Guild guild; private final AudioPlayer player; - private final AudioPlayerManager playerManager; private final TrackScheduler scheduler; private ScheduledFuture autoDisconnectThread; private final int baseVolume; private MusicManager(Guild guild) { this.guild = guild; - this.playerManager = new DefaultAudioPlayerManager(); this.player = playerManager.createPlayer(); this.scheduler = new TrackScheduler(player, guild); this.baseVolume = Integer.parseInt(GuildManager.getManager(guild).getSetting(GuildManager.Settings.MUSIC_VOLUME)); - AudioSourceManagers.registerRemoteSources(playerManager); guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player)); player.addListener(scheduler); @@ -75,6 +71,8 @@ public class MusicManager { } public static void initPlayerManager() { + AudioSourceManagers.registerRemoteSources(playerManager); + YoutubeHttpContextFilter.setPAPISID(netneutrality.utils.Settings.getInstance().get("BOT_PAPISID", "")); YoutubeHttpContextFilter.setPSID(netneutrality.utils.Settings.getInstance().get("BOT_PSID", "")); } @@ -85,6 +83,7 @@ public class MusicManager { public synchronized void connect(VoiceChannel vChannel) { if (vChannel != null && !isConnectedTo(vChannel)) { + logger.debug("Guild {}: connecting to voice channel {}", vChannel.getGuild().getId(), vChannel.getId()); guild.getAudioManager().openAudioConnection(vChannel); guild.getAudioManager().setSelfDeafened(true); player.setVolume(baseVolume); @@ -124,6 +123,7 @@ public class MusicManager { public synchronized void leave() { if (isConnected()) { + logger.debug("Guild {}: leaving voice channel {}", guild.getId(), guild.getAudioManager().getConnectedChannel().getId()); player.stopTrack(); guild.getAudioManager().closeAudioConnection(); } @@ -155,6 +155,100 @@ public class MusicManager { public synchronized void play(String url, CommandContext context, boolean needToBeConnected, boolean silent) { Member member = context.getGuildUser(); TextChannel channel = context.getChannel(); + logger.debug("Guild {}: request to play by user {}, string=[{}]", guild.getId(), context.getGuildUser().getId(), url); + + if (member.getVoiceState() == null) { + context.deleteMessage(); + return; + } + + if (!member.getVoiceState().inVoiceChannel()) { + context.reply("You are not in a voice channel"); + context.deleteMessage(); + return; + } + + VoiceChannel vChannel = member.getVoiceState().getChannel(); + if (needToBeConnected && !isConnectedTo(vChannel)) { + context.reply("The player is not in your voice channel"); + context.deleteMessage(); + return; + } + + if (!isConnectedTo(vChannel)) { + connect(vChannel); + } + + String identifier = SourceFinder.getIdentifier(url); + scheduler.getTopicManager().attachTextChannel(channel); + + if (identifier != null) { + playerManager.loadItemOrdered(this, identifier, new AudioLoadResultHandler() { + + @Override + public void trackLoaded(AudioTrack audioTrack) { + logger.debug("Guild {}: track loaded: {}", guild.getId(), identifier); + context.deleteMessage(); + + scheduler.getQueueManager().queue(audioTrack, member); + + if (!silent) { + //context.replyToMessage("Added track to queue"); + context.reply(MessageFunction.buildAddingTrackMessage(audioTrack, member, identifier)); + } + } + + @Override + public void playlistLoaded(AudioPlaylist audioPlaylist) { + logger.debug("Guild {}: playlist loaded: {}", guild.getId(), identifier); + context.deleteMessage(); + + if (audioPlaylist.isSearchResult()) { + AudioTrack track = audioPlaylist.getTracks().get(0); + scheduler.getQueueManager().queue(track, member); + + if (!silent) { + //context.replyToMessage(new MessageBuilder("Added track to queue: ").append(String.format("https://youtu.be/%s", track.getIdentifier())).build()); + context.reply(MessageFunction.buildAddingTrackMessage(track, member, String.format("https://youtu.be/%s", track.getIdentifier()))); + } + + } else { + if (!silent) { + //context.replyToMessage("Added playlist to queue"); + context.reply(MessageFunction.buildAddingPlaylistMessage(audioPlaylist, member, identifier)); + } + + for (AudioTrack track : audioPlaylist.getTracks()) { + synchronized (scheduler.getQueueManager().getQueue()) { + scheduler.getQueueManager().queue(track, member); + } + } + } + + + } + + @Override + public void noMatches() { + logger.debug("Guild {}: no matches for identifier {}", guild.getId(), identifier); + context.replyToMessage("No matches found"); + } + + @Override + public void loadFailed(FriendlyException e) { + logger.debug("Guild {}: load failed for identifier {}", guild.getId(), identifier, e); + context.replyToMessage("Failed to load track: " + e.getMessage()); + } + + }); + } else { + context.replyToMessage("No source found to play the given url"); + } + } + + public void play(List songs, CommandContext context, boolean needToBeConnected, boolean silent) { + Member member = context.getGuildUser(); + TextChannel channel = context.getChannel(); if (member.getVoiceState() == null) { return; @@ -175,72 +269,89 @@ public class MusicManager { connect(vChannel); } - String identifier = SourceFinder.getIdentifier(url); scheduler.getTopicManager().attachTextChannel(channel); + boolean fromOnlineSource = songs.size() > 1; - if (identifier != null) { + for (LoadableSong song : songs) { + String identifier = SourceFinder.getIdentifier(song.name()); - playerManager.loadItemOrdered(this, identifier, new AudioLoadResultHandler() { + if (identifier != null) { + playerManager.loadItemOrdered(this, identifier, new AudioLoadResultHandler() { + @Override + public void trackLoaded(AudioTrack track) { + if (!fromOnlineSource) { + logger.debug("Guild {}: track loaded: {}", guild.getId(), identifier); + context.deleteMessage(); - @Override - public void trackLoaded(AudioTrack audioTrack) { - logger.debug("Found results for identifier: {}", identifier); - scheduler.getQueueManager().queue(audioTrack, member); + scheduler.getQueueManager().queue(track, member); - if (!silent) { - channel.sendMessageEmbeds(MessageFunction.buildAddingTrackMessage(audioTrack, member, identifier)).queue(); - } - } - - @Override - public void playlistLoaded(AudioPlaylist audioPlaylist) { - if (identifier.startsWith("ytsearch:")) { - AudioTrack track = audioPlaylist.getTracks().get(0); - logger.debug("Found results for identifier: {}", identifier); - scheduler.getQueueManager().queue(track, member); - - if (!silent) { - channel.sendMessageEmbeds(MessageFunction.buildAddingTrackMessage(track, member, String.format("https://youtu.be/%s", track.getIdentifier()))).queue(); - } - - } else { - logger.debug("Found results for playlist identifier: {}", identifier); - - if (!silent) { - channel.sendMessageEmbeds(MessageFunction.buildAddingPlaylistMessage(audioPlaylist, member, identifier)).queue(); - } - - for (AudioTrack track : audioPlaylist.getTracks()) { - synchronized (scheduler.getQueueManager().getQueue()) { - scheduler.getQueueManager().queue(track, member); + if (!silent) { + //context.replyToMessage("Added track to queue"); + context.reply(MessageFunction.buildAddingTrackMessage(track, member, identifier)); } } } - } - @Override - public void noMatches() { - context.reply("No matches found"); - } + @Override + public void playlistLoaded(AudioPlaylist playlist) { + logger.debug("Guild {}: playlist loaded: {}", guild.getId(), identifier); - @Override - public void loadFailed(FriendlyException e) { - context.reply("Failed to load track: " + e.getMessage()); - LogHandler.log(e); - } + if (!playlist.getTracks().isEmpty()) { + context.deleteMessage(); + + if (playlist.isSearchResult()) { + AudioTrack track = playlist.getTracks().get(0); + scheduler.getQueueManager().queue(track, member); + + if (!silent && !fromOnlineSource) { + //context.replyToMessage(new MessageBuilder("Added track to queue: ").append(String.format("https://youtu.be/%s", track.getIdentifier())).build()); + context.reply(MessageFunction.buildAddingTrackMessage(track, member, String.format("https://youtu.be/%s", track.getIdentifier()))); + } + + } else { + if (!silent && !fromOnlineSource) { + //context.replyToMessage("Added playlist to queue"); + context.reply(MessageFunction.buildAddingPlaylistMessage(playlist, member, identifier)); + } + + for (AudioTrack track : playlist.getTracks()) { + synchronized (scheduler.getQueueManager().getQueue()) { + scheduler.getQueueManager().queue(track, member); + } + } + } + } else { + context.replyToMessage("Playlist is empty"); + } + + } + + @Override + public void noMatches() { + logger.debug("Guild {}: no matches for identifier {}", guild.getId(), identifier); + + if (fromOnlineSource) { + //TODO: + } + context.replyToMessage("No matches found"); + } + + @Override + public void loadFailed(FriendlyException e) { + logger.debug("Guild {}: load failed for identifier {}", guild.getId(), identifier, e); + context.replyToMessage("Failed to load track: " + e.getMessage()); + } + }); + } else { + context.replyToMessage("No source found to play the given url"); + } - }); - } else { - throw new CommandErrorException("No source found to play the given url", context, null); } + } - public synchronized void removeFromQueue(int num) { - if (num == 1) { - skip(1); - } else { - scheduler.getQueueManager().removeFromQueue(num); - } + public synchronized boolean removeFromQueue(int index) { + return scheduler.getQueueManager().removeFromQueue(index); } public synchronized void stopTrack() { @@ -253,6 +364,7 @@ public class MusicManager { nbTrack = 1; } + logger.debug("Guild {}: skipping tracks, toSkip={}", guild.getId(), nbTrack); scheduler.getQueueManager().skipAndPlay(nbTrack); } else { leave(); @@ -278,13 +390,14 @@ public class MusicManager { if (connected > 2) { double needed = connected / 2.1; - info.toggleSkip(context.getGuildUser()); + info.addSkip(context.getGuildUser()); + logger.debug("VoteSkip: guild={}, connected={}, needed={}, skips={}, user={}", context.getGuild().getId(), connected, needed, info.getSkips().size(), context.getGuildUser().getId()); if (info.getSkips().size() > needed) { context.reply("Skipping current track: %s", info.getTrack().getInfo().title); skip(1); } else { - context.reply("Current skips: %d/%d", info.getSkips().size(), (int) Math.ceil(needed)); + context.reply("Adding your vote to skip the current track. Current votes: %d/%d", info.getSkips().size(), (int) Math.ceil(needed)); } } else { diff --git a/src/main/java/netneutrality/music/QueueManager.java b/src/main/java/netneutrality/music/QueueManager.java index cf27ad5..6e67c78 100644 --- a/src/main/java/netneutrality/music/QueueManager.java +++ b/src/main/java/netneutrality/music/QueueManager.java @@ -9,6 +9,8 @@ import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.LinkedBlockingDeque; @@ -20,6 +22,7 @@ public class QueueManager { private final AudioTrack track; private final Member member; private final Set skips; + private final Logger logger = LoggerFactory.getLogger(QueueManager.class); public TrackInfo(AudioTrack track, Member member) { this.track = track; @@ -35,11 +38,21 @@ public class QueueManager { return skips; } - public void toggleSkip(Member member) { - if (!hasSkip(member)) + public void addSkip(Member member) { + if (!hasSkip(member)) { + logger.debug("VoteSkip: user={}, skip doesn't exist, before={}, after={}", member.getId(), skips.size(), skips.size() + 1); skips.add(member.getId()); - else + } + } + + public void toggleSkip(Member member) { + if (hasSkip(member)) { + logger.debug("VoteSkip: user={}, skip already exist, before={}, after={}", member.getId(), skips.size(), skips.size() - 1); skips.remove(member.getId()); + } else { + logger.debug("VoteSkip: user={}, skip doesn't exist, before={}, after={}", member.getId(), skips.size(), skips.size() + 1); + skips.add(member.getId()); + } } public boolean hasSkip(Member member) { @@ -73,6 +86,7 @@ public class QueueManager { } } + private final Logger logger = LoggerFactory.getLogger(QueueManager.class); private final AudioPlayer player; private final Guild guild; private final Deque queue; @@ -120,7 +134,8 @@ public class QueueManager { } if (player.getPlayingTrack() == null) { - player.startTrack(queue.peek().getTrack(), false); + AudioTrack toPlay = (queue.peek() == null ? null : queue.peek().getTrack()); + player.startTrack(toPlay, true); } } } @@ -128,9 +143,15 @@ public class QueueManager { public void nextTrack(AudioPlayer player) { synchronized (queue) { TrackInfo last = queue.poll(); + TrackInfo next = queue.peek(); - if (queue.peek() != null) { - player.startTrack(queue.peek().getTrack(), false); + if (next != null) { + if (last != null && last.getTrack().equals(next.getTrack())) { + logger.debug("Guild {}: duplicate track in queue found, skipping to next. last={}, next={}", guild.getId(), last.getTrack().getIdentifier(), next.getTrack().getIdentifier()); + nextTrack(player); + } else { + player.startTrack(next.getTrack(), false); + } } else { player.stopTrack(); } @@ -159,7 +180,8 @@ public class QueueManager { public void shuffle() { synchronized (queue) { - if (queue.size() > 2) { + if (queue.size() > 3) { + logger.debug("Guild {}: shuffling queue", guild.getId()); TrackInfo current = queue.poll(); ArrayList newQueue = new ArrayList<>(queue); @@ -174,13 +196,25 @@ public class QueueManager { } } - protected void removeFromQueue(int index) { + protected boolean removeFromQueue(int index) { synchronized (queue) { - ArrayList newQueue = new ArrayList<>(queue); - newQueue.remove(index - 1); + if (queue.size() > 0 && index <= queue.size()) { + logger.debug("Guild {}: removing index {} from queue", guild.getId(), index - 1); - queue.clear(); - queue.addAll(newQueue); + if (index == 1) { + skipAndPlay(1); + return true; + } + + ArrayList newQueue = new ArrayList<>(queue); + newQueue.remove(index - 1); + + queue.clear(); + queue.addAll(newQueue); + return true; + } + + return false; } } diff --git a/src/main/java/netneutrality/music/TopicManager.java b/src/main/java/netneutrality/music/TopicManager.java index 76e2b99..6a7e267 100644 --- a/src/main/java/netneutrality/music/TopicManager.java +++ b/src/main/java/netneutrality/music/TopicManager.java @@ -9,22 +9,18 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.TextChannel; import netneutrality.core.GuildManager; -import netneutrality.thread.ThreadManager; import org.apache.logging.log4j.LogManager; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class TopicManager { - private final ScheduledExecutorService service; private final Guild guild; private TextChannel channel; private String originalTopic; public TopicManager(Guild guild) { this.guild = guild; - this.service = (ScheduledExecutorService) ThreadManager.getExecutor("topic-management", ThreadManager.ExecutorType.SCHEDULED); } public Guild getGuild() { @@ -59,16 +55,24 @@ public class TopicManager { if (isEnabled() && channel != null) { if (track == null) { - LogManager.getLogger(TopicManager.class).debug("Resetting topic for channel [{}] in guild [{}]", channel.getId(), guild.getId()); - channel.getManager().setTopic(originalTopic).queueAfter(3, TimeUnit.SECONDS, service); + LogManager.getLogger(TopicManager.class).debug("Guild {}: Resetting topic for channel {}", guild.getId(), channel.getId()); + channel.getManager().setTopic(originalTopic).queueAfter(5, TimeUnit.SECONDS); } else { - channel.getManager().setTopic(":speaker: Now playing: " + track.getInfo().title).queueAfter(3, TimeUnit.SECONDS, service); + LogManager.getLogger(TopicManager.class).debug("Guild {}: Setting topic for channel {} to [{}]", guild.getId(), channel.getId(), track.getInfo().title); + channel.getManager().setTopic(":speaker: Now playing: " + track.getInfo().title).queueAfter(5, TimeUnit.SECONDS); } } } + public void sendMessageToChannel(String msg) { + if (channel != null) { + channel.sendMessage(msg).queue(); + } + } + private boolean isEnabled() { - return "1".equals(GuildManager.getManager(guild).getSetting(GuildManager.Settings.MUSIC_TOPIC)); + // rate limiting too much + return false; } } diff --git a/src/main/java/netneutrality/music/TrackScheduler.java b/src/main/java/netneutrality/music/TrackScheduler.java index a3e13de..4d499fe 100644 --- a/src/main/java/netneutrality/music/TrackScheduler.java +++ b/src/main/java/netneutrality/music/TrackScheduler.java @@ -55,16 +55,21 @@ public class TrackScheduler extends AudioEventAdapter { @Override public void onTrackStart(AudioPlayer player, AudioTrack track) { - logger.debug("Starting track on guild {}: {}", guild.getId(), track.getIdentifier()); + logger.debug("Guild {}: starting track: {}", guild.getId(), track.getIdentifier()); topicManager.updateTopic(track); } @Override public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { + logger.debug("Guild {}: currently playing track ended: {}", guild.getId(), track.getIdentifier()); + + if (endReason == AudioTrackEndReason.LOAD_FAILED) { + logger.debug("Guild {}: failed loading track: {}", guild.getId(), track.getIdentifier()); + } + // check if mayStartNext if (endReason == AudioTrackEndReason.FINISHED || endReason == AudioTrackEndReason.LOAD_FAILED) { queueManager.nextTrack(player); - } if (endReason == AudioTrackEndReason.STOPPED) { @@ -84,13 +89,16 @@ public class TrackScheduler extends AudioEventAdapter { @Override public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) { + logger.debug("Guild {}: exception while playing track [{}]: {}", guild.getId(), track.getIdentifier(), exception.getMessage()); topicManager.updateTopic(null); - topicManager.getTextChannel().sendMessage("Error while playing track: " + track.getInfo().title).queue(); + topicManager.sendMessageToChannel("Error while playing track [" + track.getInfo().title + "]: " + exception.getMessage()); } @Override public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) { + logger.debug("Guild {}: currently playing track is stuck: {}", guild.getId(), track.getIdentifier()); topicManager.updateTopic(null); + topicManager.sendMessageToChannel("Current track is stuck, skipping to next"); queueManager.nextTrack(player); } } diff --git a/src/main/java/netneutrality/music/loadable/AppleMostPlayed.java b/src/main/java/netneutrality/music/loadable/AppleMostPlayed.java new file mode 100644 index 0000000..b6e961b --- /dev/null +++ b/src/main/java/netneutrality/music/loadable/AppleMostPlayed.java @@ -0,0 +1,42 @@ +package netneutrality.music.loadable; +/* + * Copyright (c) anthorob + * + * https://xtremcommunity.com/ + * https://discord.gg/CdFcyQ + */ + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class AppleMostPlayed implements LoadableMusicSource { + @Override + public String url() { + return "https://rss.applemarketingtools.com/api/v2/ca/music/most-played/25/songs.json"; + } + + @Override + public List parse(JSONObject obj) { + List songs = new ArrayList<>(); + JSONArray results = obj.getJSONObject("feed").getJSONArray("results"); + + results.forEach(o -> { + JSONObject song = (JSONObject) o; + String artist = song.getString("artistName"); + String songName = song.getString("name"); + String artwork = song.getString("artworkUrl100"); + + songs.add(new LoadableSong(artist, songName, artwork)); + }); + + return songs; + } + + @Override + public String getIdentifier() { + return "apple"; + } +} diff --git a/src/main/java/netneutrality/music/loadable/CachedSource.java b/src/main/java/netneutrality/music/loadable/CachedSource.java new file mode 100644 index 0000000..2dc83c3 --- /dev/null +++ b/src/main/java/netneutrality/music/loadable/CachedSource.java @@ -0,0 +1,40 @@ +package netneutrality.music.loadable; +/* + * Copyright (c) anthorob + * + * https://xtremcommunity.com/ + * https://discord.gg/CdFcyQ + */ + + +import java.time.OffsetDateTime; +import java.util.List; + +public class CachedSource { + private final LoadableMusicSource source; + private OffsetDateTime lastAccess; + private final List songs; + + public CachedSource(LoadableMusicSource source, List songs) { + this.source = source; + this.lastAccess = OffsetDateTime.now(); + this.songs = songs; + } + + public List getSongs() { + return songs; + } + + public OffsetDateTime getLastAccess() { + return lastAccess; + } + + public LoadableMusicSource getSource() { + return source; + } + + public void updateAccessTime() { + this.lastAccess = OffsetDateTime.now(); + } + +} diff --git a/src/main/java/netneutrality/music/loadable/LoadableMusicSource.java b/src/main/java/netneutrality/music/loadable/LoadableMusicSource.java new file mode 100644 index 0000000..c1f6cd4 --- /dev/null +++ b/src/main/java/netneutrality/music/loadable/LoadableMusicSource.java @@ -0,0 +1,26 @@ +package netneutrality.music.loadable; +/* + * Copyright (c) anthorob + * + * https://xtremcommunity.com/ + * https://discord.gg/CdFcyQ + */ + +/* + * Copyright (c) anthorob + * + * https://xtremcommunity.com/ + * https://discord.gg/CdFcyQ + */ + +import org.json.JSONObject; + +import java.util.List; + +public interface LoadableMusicSource { + String url(); + + List parse(JSONObject obj); + + String getIdentifier(); +} diff --git a/src/main/java/netneutrality/music/loadable/LoadableSong.java b/src/main/java/netneutrality/music/loadable/LoadableSong.java new file mode 100644 index 0000000..d5e2872 --- /dev/null +++ b/src/main/java/netneutrality/music/loadable/LoadableSong.java @@ -0,0 +1,19 @@ +package netneutrality.music.loadable; +/* + * Copyright (c) anthorob + * + * https://xtremcommunity.com/ + * https://discord.gg/CdFcyQ + */ + +public record LoadableSong(String artist, String name, String artworkUrl) { + + @Override + public String toString() { + return "LoadableSong{" + + "artist='" + artist + '\'' + + ", name='" + name + '\'' + + ", artworkUrl='" + artworkUrl + '\'' + + '}'; + } +} diff --git a/src/main/java/netneutrality/music/loadable/LoadableSourceManager.java b/src/main/java/netneutrality/music/loadable/LoadableSourceManager.java new file mode 100644 index 0000000..4265a3d --- /dev/null +++ b/src/main/java/netneutrality/music/loadable/LoadableSourceManager.java @@ -0,0 +1,59 @@ +package netneutrality.music.loadable; +/* + * Copyright (c) anthorob + * + * https://xtremcommunity.com/ + * https://discord.gg/CdFcyQ + */ + +import netneutrality.http.WebModule; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LoadableSourceManager { + + private final Logger logger = LoggerFactory.getLogger(LoadableSourceManager.class); + private static final Map cache = new HashMap<>(); + + public CachedSource get(LoadableMusicSource source) { + + if (cache.containsKey(source.getIdentifier())) { + CachedSource cachedSource = cache.get(source.getIdentifier()); + + if (OffsetDateTime.now().isAfter(cachedSource.getLastAccess().plusHours(4))) { + cachedSource.getSongs().clear(); + cachedSource.getSongs().addAll(loadFromUrl(source)); + cachedSource.updateAccessTime(); + } + + return cachedSource; + } else { + List songs = loadFromUrl(source); + CachedSource newCachedSource = new CachedSource(source, songs); + + cache.put(source.getIdentifier(), newCachedSource); + return newCachedSource; + } + + } + + private List loadFromUrl(LoadableMusicSource source) { + WebModule web = new WebModule(source.url()); + JSONObject obj = web.request().getAsObject(); + + if (obj == null) + return Collections.emptyList(); + + List songs = source.parse(obj); + Collections.shuffle(songs); + + return songs; + } +} diff --git a/src/main/java/netneutrality/thread/MusicPlayerAutoDisconnectThread.java b/src/main/java/netneutrality/thread/MusicPlayerAutoDisconnectThread.java index a7141c5..2a22301 100644 --- a/src/main/java/netneutrality/thread/MusicPlayerAutoDisconnectThread.java +++ b/src/main/java/netneutrality/thread/MusicPlayerAutoDisconnectThread.java @@ -10,6 +10,7 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.VoiceChannel; import netneutrality.NetNeutrality; import netneutrality.music.MusicManager; +import org.slf4j.LoggerFactory; import java.util.List; @@ -29,8 +30,10 @@ public class MusicPlayerAutoDisconnectThread implements Runnable { if (channel != null) { List members = channel.getMembers().stream().filter(member -> !member.getId().equals(NetNeutrality.getSelf().getId())).toList(); - if (members.size() == 0 || manager.getScheduler().getQueueManager().getQueue().isEmpty()) + if (members.size() == 0 || manager.getScheduler().getQueueManager().getQueue().isEmpty()) { + LoggerFactory.getLogger(MusicPlayerAutoDisconnectThread.class).debug("Guild {}: auto-disconnect player from voice channel: {}", guild.getId(), channel.getId()); manager.leave(); + } } } } diff --git a/src/main/java/netneutrality/utils/MessageFunction.java b/src/main/java/netneutrality/utils/MessageFunction.java index 6fb83be..1214cd7 100644 --- a/src/main/java/netneutrality/utils/MessageFunction.java +++ b/src/main/java/netneutrality/utils/MessageFunction.java @@ -9,6 +9,7 @@ package netneutrality.utils; import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.*; import netneutrality.command.AbstractCommand; @@ -69,7 +70,6 @@ public class MessageFunction { message.setTitle("Currently playing:"); message.setColor(Color.BLUE); message.addField("Track title: ", current.getInfo().title, false); - message.addField("Uploaded by: ", current.getInfo().author, false); message.addField("Duration: ", DurationFormatUtils.formatDuration(current.getPosition(), "**HH:mm:ss**", true) + " / " + DurationFormatUtils.formatDuration(current.getDuration(), "**HH:mm:ss**", true), false); message.setFooter("Requested by " + requester.getEffectiveName(), requester.getAvatarUrl()); message.setTimestamp(OffsetDateTime.now()); @@ -84,7 +84,6 @@ public class MessageFunction { message.setTitle("Adding track to queue.."); message.setColor(Color.BLUE); message.addField("Track title: ", track.getInfo().title, false); - message.addField("Uploaded by: ", track.getInfo().author, false); message.addField("Duration: ", DurationFormatUtils.formatDuration(track.getDuration(), "**HH:mm:ss**", true), false); message.setFooter("Requested by " + user.getEffectiveName(), user.getAvatarUrl()); message.setTimestamp(OffsetDateTime.now()); @@ -111,15 +110,17 @@ public class MessageFunction { AudioTrack current = manager.getCurrentlyPlaying(); EmbedBuilder message = new EmbedBuilder(); - message.setTitle("Current queue (showing max 10 items):"); + message.setTitle("Current queue (showing 10 out of " + tracks.size() + " items):"); message.setColor(Color.BLUE); int index = 0; for (AudioTrack track : tracks) { - if (track.getInfo().title.equals(current.getInfo().title) && currentlyPlaying) { - message.addField("Track #" + ++index + " (Currently playing)", track.getInfo().title, false); + AudioTrackInfo info = track.getInfo(); + + if (current != null && current.getInfo().title.equals(info.title) && currentlyPlaying) { + message.addField("Track #" + ++index + " (Currently playing)", info.title, false); } else { - message.addField("Track #" + ++index, track.getInfo().title, false); + message.addField("Track #" + ++index, info.title, false); } if (index == 10) break; diff --git a/src/main/java/netneutrality/utils/MusicUtils.java b/src/main/java/netneutrality/utils/MusicUtils.java index fee8dee..947ea00 100644 --- a/src/main/java/netneutrality/utils/MusicUtils.java +++ b/src/main/java/netneutrality/utils/MusicUtils.java @@ -41,7 +41,7 @@ public class MusicUtils { if (UserFunction.isAdmin(context.getGuildUser()) || manager.getRequesterOfCurrentTrack().getId().equals(context.getGuildUser().getId())) { return true; } else { - context.reply("You are not powerful enough, scrub"); + context.reply("You're not powerful enough, scrub"); return false; } } diff --git a/src/test/java/netneutrality/music/LoadableSourceTest.java b/src/test/java/netneutrality/music/LoadableSourceTest.java new file mode 100644 index 0000000..925408c --- /dev/null +++ b/src/test/java/netneutrality/music/LoadableSourceTest.java @@ -0,0 +1,31 @@ +package netneutrality.music; +/* + * Copyright (c) anthorob + * + * https://xtremcommunity.com/ + * https://discord.gg/CdFcyQ + */ + +import netneutrality.music.loadable.AppleMostPlayed; +import netneutrality.music.loadable.CachedSource; +import netneutrality.music.loadable.LoadableSong; +import netneutrality.music.loadable.LoadableSourceManager; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class LoadableSourceTest { + + @Test + public void getAppleMostPlayed() { + LoadableSourceManager loadableSourceManager = new LoadableSourceManager(); + CachedSource cachedSource = loadableSourceManager.get(new AppleMostPlayed()); + + System.out.println(cachedSource.getLastAccess()); + System.out.println(cachedSource.getSongs()); + + assertTrue(cachedSource.getSongs().size() > 0); + } +} \ No newline at end of file