Improve cleanup for music player, fix resume logic, check if player is connected before checking url, refactor context

This commit is contained in:
anthorob committed 2024-11-16 03:14:23 -05:00
1 parent bcf78920e9
commit 6f2ba795d4
27 files changed
+321 -282

No files matched your search

@@ -11,7 +11,6 @@ import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.command.CommandManager;
import me.bobert.netneutrality.core.db.SessionManager;
import me.bobert.netneutrality.core.tasks.TaskManager;
import me.bobert.netneutrality.core.thread.BaseThreadFactory;
import me.bobert.netneutrality.core.thread.ThreadManager;
import me.bobert.netneutrality.hooks.guilds.ServerJoinHook;
import me.bobert.netneutrality.hooks.interaction.ButtonInteractionHook;
@@ -35,10 +34,10 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
public class NetNeutrality {
private static JDA jda;
private final static Logger logger = LogManager.getLogger(NetNeutrality.class);
private static final Logger logger = LogManager.getLogger(NetNeutrality.class);
public static final String VERSION = Settings.getInstance().getMavenVersion();
private static NetNeutrality instance;
private static JDA jda;
public static void main(String[] args) {
String env = Settings.getInstance().get("NETNEUTRALITY_BOT_ENV", "dev");
@@ -60,6 +59,14 @@ public class NetNeutrality {
Configurator.setLevel("net.dv8tion.jda", Level.DEBUG);
}
instance = new NetNeutrality();
}
public NetNeutrality() {
startup();
}
private void startup() {
// DatabaseMigration.migrate();
SessionManager.getSessionFactory();
initJDA();
@@ -72,11 +79,14 @@ public class NetNeutrality {
new WebApi(Integer.parseInt(Settings.getInstance().get("NETNEUTRALITY_API_PORT", "9000")));
jda.getPresence().setActivity(Activity.watching("you"));
Runtime.getRuntime().addShutdownHook(new BaseThreadFactory("shutdown").newThread(new ShutdownThread()));
Runtime.getRuntime().addShutdownHook(new ShutdownThread(this));
}
public static void initJDA() {
public static NetNeutrality getInstance() {
return instance;
}
public void initJDA() {
String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN");
if (token == null) {
SessionManager.shutdown();
@@ -96,7 +106,7 @@ public class NetNeutrality {
}
}
public static void shutdown() {
public void shutdown() {
System.out.println("[Net Neutrality] Shutting down...");
MusicManager.disconnectPlayers();
ThreadManager.shutdownExecutor();
@@ -105,16 +115,16 @@ public class NetNeutrality {
System.out.println("[Net Neutrality] Shutdown complete");
}
public static JDA getJDA() {
public JDA getJDA() {
return jda;
}
public static User getSelf() {
public User getSelf() {
return jda.getSelfUser();
}
private static void disconnect() {
private void disconnect() {
if (jda != null) {
jda.shutdown();
}
@@ -9,7 +9,6 @@ import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.context.SlashCommandContext;
import me.bobert.netneutrality.core.command.CommandExecutionResult;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.QueueManager;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.entities.Member;
@@ -62,32 +61,25 @@ public class QueueCommand extends AbstractMusicCommand {
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
QueueManager queueManager = manager.getQueueManager();
context.ack();
String sub = context.getSubCommand();
context.ack();
if (MusicUtils.checkIfConnected(context)) {
switch (sub) {
case "clear-all" -> {
return queueManager.clearQueue();
return manager.getQueueManager().clearQueue();
}
case "delete-track" -> {
int position = context.getEvent().getOption("position", 0, OptionMapping::getAsInt);
return queueManager.removeFromQueue(position);
return manager.getQueueManager().removeFromQueue(position);
}
case "show-queue" -> {
if (queueManager.getQueueSize() == 0) {
return CommandExecutionResult.info("Queue is empty!");
}
context.reply(MessageFunction.buildViewQueueMessage(manager));
}
case "show-queue" -> context.reply(MessageFunction.buildViewQueueMessage(manager));
case "shuffle" -> {
return queueManager.shuffle();
return manager.getQueueManager().shuffle();
}
case "from-user" -> {
@@ -105,13 +97,7 @@ public class QueueCommand extends AbstractMusicCommand {
return CommandExecutionResult.warn("The user isn't from this guild");
}
int size = queueManager.getQueueWithFilter(trackInfo -> member.equals(trackInfo.getRequester())).size();
if (size == 0) {
return CommandExecutionResult.info(String.format("No tracks added by user %s", member.getEffectiveName()));
}
queueManager.removeTracksFromUser(member);
context.reply("Removed all tracks from user %s", member.getEffectiveName());
return manager.getQueueManager().removeTracksFromUser(member);
}
}
}
@@ -37,7 +37,7 @@ public class ResumeCommand extends AbstractMusicCommand {
MusicManager manager = MusicManager.get(context.getGuild());
if (MusicUtils.checkIfConnected(context)) {
return manager.getQueueManager().skip();
return manager.resume();
}
return CommandExecutionResult.none();
@@ -1,4 +1,4 @@
package me.bobert.netneutrality.core;
package me.bobert.netneutrality.context;
/*
* Copyright (c) anthorob
*
@@ -14,19 +14,20 @@ import net.dv8tion.jda.api.interactions.modals.Modal;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import java.util.List;
import java.util.function.Consumer;
public class AsyncMessage extends AbstractContext {
public class AsyncContext extends AbstractContext {
private final InteractionHook hook;
private final Message message;
public AsyncMessage(InteractionHook hook) {
public AsyncContext(InteractionHook hook) {
super(hook.getJDA(), hook.getInteraction().getGuild(), hook.getInteraction().getMessageChannel(), hook.getInteraction().getMember(), hook.getInteraction().getUser());
this.hook = hook;
this.message = null;
}
public AsyncMessage(Message message) {
public AsyncContext(Message message) {
super(message.getJDA(), message.getGuild(), message.getChannel(), message.getMember(), message.getAuthor());
this.hook = null;
this.message = message;
@@ -49,7 +50,7 @@ public class AsyncMessage extends AbstractContext {
@Override
public boolean isFromGuild() {
return false;
return message != null ? message.isFromGuild() : hook.getInteraction().isFromGuild();
}
@Override
@@ -69,7 +70,7 @@ public class AsyncMessage extends AbstractContext {
}
@Override
public void reply(MessageCreateData messageCreateData, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
public void reply(MessageCreateData messageCreateData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer) {
if (hook != null) {
hook.sendMessage(messageCreateData).queue();
}
@@ -80,7 +81,7 @@ public class AsyncMessage extends AbstractContext {
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
public void defer(boolean ephemeral, Consumer<AsyncContext> consumer) {
}
@@ -90,7 +91,7 @@ public class AsyncMessage extends AbstractContext {
}
@Override
public void edit(MessageEditData messageEditData, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
public void edit(MessageEditData messageEditData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer) {
if (hook != null) {
hook.editOriginal(messageEditData).queue();
}
@@ -5,7 +5,6 @@ package me.bobert.netneutrality.context;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.entities.Message;
@@ -15,6 +14,7 @@ import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.modals.Modal;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -77,14 +77,14 @@ public class MessageCommandContext extends CommandContext {
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
public void defer(boolean ephemeral, Consumer<AsyncContext> consumer) {
reply("Bot is thinking...", consumer);
}
@Override
public void reply(MessageCreateData messageCreateData, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
public void reply(MessageCreateData messageCreateData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer) {
if (getChannel() != null && getChannel().canTalk()) {
message.reply(messageCreateData).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1)));
message.reply(messageCreateData).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message)));
}
}
@@ -94,8 +94,8 @@ public class MessageCommandContext extends CommandContext {
}
@Override
public void replyEphemeral(MessageCreateData message) {
sendMessageInPrivate(message);
public void replyEphemeral(MessageCreateData messageCreateData) {
sendMessageInPrivate(messageCreateData);
}
@Override
@@ -5,7 +5,6 @@ package me.bobert.netneutrality.context;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.core.context.AbstractContext;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
@@ -15,6 +14,7 @@ import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.modals.Modal;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.List;
import java.util.function.Consumer;
public class MusicChannelContext extends AbstractContext {
@@ -54,14 +54,14 @@ public class MusicChannelContext extends AbstractContext {
}
@Override
public void reply(MessageCreateData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
public void reply(MessageCreateData messageCreateData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer) {
if (getChannel().canTalk()) {
getChannel().sendMessage(message).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1)));
getChannel().sendMessage(messageCreateData).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message)));
}
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
public void defer(boolean ephemeral, Consumer<AsyncContext> consumer) {
reply("Bot is thinking", consumer);
}
@@ -5,17 +5,13 @@ package me.bobert.netneutrality.context;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.InteractionContext;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.modals.Modal;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.function.Consumer;
public class SlashCommandContext extends CommandContext {
public class SlashCommandContext extends CommandContext implements InteractionContext {
private final SlashCommandInteractionEvent event;
public SlashCommandContext(SlashCommandInteractionEvent event) {
@@ -28,16 +24,6 @@ public class SlashCommandContext extends CommandContext {
return event;
}
@Override
public boolean isAcknowledged() {
return event.isAcknowledged();
}
@Override
public boolean isFromGuild() {
return event.isFromGuild();
}
@Override
public InteractionHook getInteractionHook() {
if (!event.isAcknowledged())
@@ -51,36 +37,11 @@ public class SlashCommandContext extends CommandContext {
// not implemented
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
event.deferReply().queue(hook -> consumer.accept(new AsyncMessage(hook)));
}
@Override
public void reply(MessageCreateData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
if (isAcknowledged()) {
getInteractionHook().sendMessage(message).setEphemeral(ephemeral).setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
return;
}
event.reply(message).setEphemeral(ephemeral).setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
}
@Override
public void reply(Modal modal) {
event.replyModal(modal).queue();
}
@Override
public void ack(boolean ephemeral) {
if (event.isAcknowledged())
throw new RuntimeException("Interaction is already acknowledged, please use getInteractionHook() instead.");
event.deferReply(ephemeral).queue();
}
@Override
public String parseCommandName() {
return event.getName();
@@ -5,16 +5,12 @@ package me.bobert.netneutrality.context.tasks;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.core.context.InteractionContext;
import me.bobert.netneutrality.core.context.TaskContext;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.function.Consumer;
public class ButtonTaskContext extends TaskContext {
public class ButtonTaskContext extends TaskContext implements InteractionContext {
private final ButtonInteractionEvent event;
public ButtonTaskContext(ButtonInteractionEvent event) {
@@ -36,40 +32,4 @@ public class ButtonTaskContext extends TaskContext {
public InteractionHook getInteractionHook() {
return event.getHook();
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
event.deferReply().queue(hook -> consumer.accept(new AsyncMessage(hook)));
}
@Override
public boolean isAcknowledged() {
return event.isAcknowledged();
}
@Override
public boolean isFromGuild() {
return event.isFromGuild();
}
@Override
public void reply(MessageCreateData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
if (isAcknowledged()) {
getInteractionHook().sendMessage(message)
.setEphemeral(ephemeral)
.setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
return;
}
event.reply(message)
.setEphemeral(ephemeral)
.setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
}
@Override
public void ack(boolean ephemeral) {
event.deferReply(ephemeral).queue();
}
}
@@ -5,7 +5,7 @@ package me.bobert.netneutrality.context.tasks;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.context.AsyncContext;
import me.bobert.netneutrality.core.context.TaskContext;
import net.dv8tion.jda.api.entities.MessageReaction;
import net.dv8tion.jda.api.entities.emoji.EmojiUnion;
@@ -14,6 +14,7 @@ import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.List;
import java.util.function.Consumer;
public class EmojiTaskContext extends TaskContext {
@@ -50,7 +51,7 @@ public class EmojiTaskContext extends TaskContext {
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
public void defer(boolean ephemeral, Consumer<AsyncContext> consumer) {
reply("Bot is thinking...", consumer);
}
@@ -65,15 +66,15 @@ public class EmojiTaskContext extends TaskContext {
}
@Override
public void reply(MessageCreateData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
public void reply(MessageCreateData messageCreateData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer) {
if (getChannel() != null && getChannel().canTalk()) {
getChannel().sendMessage(message).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1)));
getChannel().sendMessage(messageCreateData).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message)));
}
}
@Override
public void replyEphemeral(MessageCreateData message) {
sendMessageInPrivate(message);
public void replyEphemeral(MessageCreateData messageCreateData) {
sendMessageInPrivate(messageCreateData);
}
@Override
@@ -5,16 +5,12 @@ package me.bobert.netneutrality.context.tasks;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.core.context.InteractionContext;
import me.bobert.netneutrality.core.context.TaskContext;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.function.Consumer;
public class ModalTaskContext extends TaskContext {
public class ModalTaskContext extends TaskContext implements InteractionContext {
private final ModalInteractionEvent event;
public ModalTaskContext(ModalInteractionEvent event) {
@@ -36,36 +32,4 @@ public class ModalTaskContext extends TaskContext {
public InteractionHook getInteractionHook() {
return event.getHook();
}
@Override
public void defer(Consumer<AsyncMessage> consumer) {
event.deferReply().queue(hook -> consumer.accept(new AsyncMessage(hook)));
}
@Override
public boolean isAcknowledged() {
return event.isAcknowledged();
}
@Override
public boolean isFromGuild() {
return event.isFromGuild();
}
@Override
public void reply(MessageCreateData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
if (isAcknowledged()) {
getInteractionHook().sendMessage(message).setEphemeral(ephemeral).setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
return;
}
event.reply(message).setEphemeral(ephemeral).setComponents(components)
.queue(message1 -> consumer.accept(new AsyncMessage(message1)));
}
@Override
public void ack(boolean ephemeral) {
event.deferReply(ephemeral).queue();
}
}
@@ -55,7 +55,7 @@ public class GuildManager {
return managers.get(guildId);
}
Guild temp = NetNeutrality.getJDA().getGuildById(guildId);
Guild temp = NetNeutrality.getInstance().getJDA().getGuildById(guildId);
if (temp == null) {
throw new IllegalArgumentException("Guild does not exist in current cache");
}
@@ -60,7 +60,12 @@ public class LogHandler {
private static void logInternally(ContextException exception) {
Context context = exception.getContext();
logger.error("Exception thrown from context in guild {}: {}", context.getGuild() == null ? "PrivateMessage": context.getGuild().getId(), exception.getMessage());
String guildId = context.getGuild() == null ? "PrivateMessage" : context.getGuild().getId();
logger.error("Exception thrown from context (guild: {}, user: {}): {}", guildId,
context.getDiscordUser().getId(),
exception.getMessage()
);
if (context instanceof CommandContext commandContext) {
logger.error("Caused by command: {}", commandContext.getCommand().getCommandName(), exception);
@@ -5,7 +5,7 @@ package me.bobert.netneutrality.core.context;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.AsyncMessage;
import me.bobert.netneutrality.context.AsyncContext;
import me.bobert.netneutrality.core.GuildManager;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
@@ -47,9 +47,9 @@ public interface Context {
void reply(Modal modal);
void reply(MessageCreateData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components);
void reply(MessageCreateData messageCreateData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer);
void defer(Consumer<AsyncMessage> consumer);
void defer(boolean ephemeral, Consumer<AsyncContext> consumer);
void ack(boolean ephemeral);
@@ -65,8 +65,8 @@ public interface Context {
ack(true);
}
default void reply(MessageCreateData message) {
reply(message, false, asyncMessage -> {});
default void reply(MessageCreateData messageCreateData) {
reply(messageCreateData, false, List.of(), asyncMessage -> {});
}
default void reply(String message) {
@@ -81,21 +81,21 @@ public interface Context {
reply(String.format(format, arguments));
}
default void edit(MessageEditData message, boolean ephemeral, Consumer<AsyncMessage> consumer, LayoutComponent... components) {
default void edit(MessageEditData messageEditData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer) {
if (isAcknowledged() && getInteractionHook() != null) {
getInteractionHook().editOriginal(message).setComponents(components).queue(message1 -> consumer.accept(new AsyncMessage(message1)));
getInteractionHook().editOriginal(messageEditData).setComponents(components).queue(message1 -> consumer.accept(new AsyncContext(message1)));
return;
}
reply(MessageCreateData.fromEditData(message), ephemeral, consumer, components);
reply(MessageCreateData.fromEditData(messageEditData), ephemeral, components, consumer);
}
default void edit(MessageEditData message, LayoutComponent... components) {
edit(message, false, asyncMessage -> {}, components);
default void edit(MessageEditData messageEditData, List<LayoutComponent> components) {
edit(messageEditData, false, components, asyncMessage -> {});
}
default void edit(MessageEditData message) {
edit(message, false, asyncMessage -> {});
default void edit(MessageEditData messageEditData) {
edit(messageEditData, false, List.of(), asyncMessage -> {});
}
default void edit(String message) {
@@ -106,7 +106,7 @@ public interface Context {
edit(MessageEditData.fromEmbeds(embed));
}
default void edit(MessageEmbed embeds, LayoutComponent... components) {
default void edit(MessageEmbed embeds, List<LayoutComponent> components) {
edit(MessageEditData.fromEmbeds(embeds), components);
}
@@ -114,20 +114,20 @@ public interface Context {
edit(String.format(format, arguments));
}
default void async(MessageCreateData message, Consumer<AsyncMessage> consumer) {
reply(message, false, consumer);
default void async(MessageCreateData messageCreateData, Consumer<AsyncContext> consumer) {
reply(messageCreateData, false, List.of(), consumer);
}
default void async(String message, Consumer<AsyncMessage> consumer) {
default void async(String message, Consumer<AsyncContext> consumer) {
async(MessageCreateData.fromContent(message), consumer);
}
default void async(Consumer<AsyncMessage> consumer) {
defer(consumer);
default void async(Consumer<AsyncContext> consumer) {
defer(false, consumer);
}
default void replyEphemeral(MessageCreateData message) {
reply(message, true, asyncMessage -> {});
default void replyEphemeral(MessageCreateData messageCreateData) {
reply(messageCreateData, true, List.of(), asyncMessage -> {});
}
default void replyEphemeral(String message) {
@@ -142,10 +142,10 @@ public interface Context {
replyEphemeral(MessageCreateData.fromEmbeds(embeds));
}
default void sendMessageInPrivate(MessageCreateData msg) {
default void sendMessageInPrivate(MessageCreateData messageCreateData) {
User user = getDiscordUser();
if (user != null) {
user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue();
user.openPrivateChannel().flatMap(channel -> channel.sendMessage(messageCreateData)).queue();
}
}
@@ -0,0 +1,73 @@
package me.bobert.netneutrality.core.context;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.context.AsyncContext;
import net.dv8tion.jda.api.events.Event;
import net.dv8tion.jda.api.interactions.Interaction;
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.List;
import java.util.function.Consumer;
public interface InteractionContext extends Context {
@Override
default void ack(boolean ephemeral) {
defer(ephemeral, asyncMessage -> {});
}
@Override
default void defer(boolean ephemeral, Consumer<AsyncContext> consumer) {
if (isAcknowledged()) {
throw new RuntimeException("Interaction is already acknowledged, please use getInteractionHook() instead.");
}
getReplyCallback(getEvent()).deferReply(ephemeral).queue(hook -> consumer.accept(new AsyncContext(hook)));
}
@Override
default boolean isFromGuild() {
Event event = getEvent();
try {
Interaction interaction = (Interaction) event;
return interaction.isFromGuild();
} catch (ClassCastException e) {
throw new IllegalArgumentException(event.getClass().getSimpleName() + " does not implement class 'Interaction'", e);
}
}
@Override
default boolean isAcknowledged() {
Event event = getEvent();
try {
Interaction interaction = (Interaction) event;
return interaction.isAcknowledged();
} catch (ClassCastException e) {
throw new IllegalArgumentException(event.getClass().getSimpleName() + " does not implement class 'Interaction'", e);
}
}
@Override
default void reply(MessageCreateData messageCreateData, boolean ephemeral, List<LayoutComponent> components, Consumer<AsyncContext> consumer) {
if (isAcknowledged()) {
getInteractionHook().sendMessage(messageCreateData).setEphemeral(ephemeral).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message)));
return;
}
getReplyCallback(getEvent()).reply(messageCreateData).setEphemeral(ephemeral).setComponents(components).queue(message -> consumer.accept(new AsyncContext(message)));
}
static IReplyCallback getReplyCallback(Event event) {
try {
return (IReplyCallback) event;
} catch (ClassCastException e) {
throw new IllegalArgumentException(event.getClass().getSimpleName() + " does not implement class 'IReplyCallback'", e);
}
}
}
@@ -16,9 +16,9 @@ public interface Task {
/**
* <p>This method should generate a unique ID to differentiate it from other tasks.</p>
* <p>
* For button and modal interaction, this is the ID of the component sent to discord.
* For other types of interaction, for example emoji reaction,
* each reaction should get a unique ID generated by the context.
* This determines what triggers a task to be executed.
* For example, a modal or button would use the component id.
* For an emoji reaction, the guild and message id would be combined.
* </p>
* @return a unique ID for this task
*/
@@ -26,7 +26,7 @@ public class ActionHandler extends AbstractHandler {
if (path.equals("/actions/shutdown")) {
LogManager.getLogger(NetNeutrality.class).info("Request to shutdown received");
NetNeutrality.shutdown();
NetNeutrality.getInstance().shutdown();
sendStringResponse(exchange, "Shutting down..");
System.exit(0);
@@ -35,8 +35,8 @@ public class ActionHandler extends AbstractHandler {
if (path.equals("/actions/status")) {
TreeMap<String, String> json = new TreeMap<>();
json.put("health", String.valueOf(NetNeutrality.getJDA().getStatus()));
json.put("ping", String.valueOf(NetNeutrality.getJDA().getGatewayPing()));
json.put("health", String.valueOf(NetNeutrality.getInstance().getJDA().getStatus()));
json.put("ping", String.valueOf(NetNeutrality.getInstance().getJDA().getGatewayPing()));
sendJsonResponse(exchange, WebModule.createJson(json));
return;
@@ -47,7 +47,7 @@ public class ActionHandler extends AbstractHandler {
String[] args = exchange.getRequestURI().toString().split("/");
String guildId = args[args.length - 1];
Guild guild = NetNeutrality.getJDA().getGuildById(guildId);
Guild guild = NetNeutrality.getInstance().getJDA().getGuildById(guildId);
if (guild != null) {
GuildManager manager = GuildManager.getManager(guild);
Map<String, String> settings = manager.getGuildConfigManager().getSettings();
@@ -68,7 +68,7 @@ public class ActionHandler extends AbstractHandler {
String[] args = exchange.getRequestURI().toString().split("/");
String guildId = args[args.length - 1];
Guild guild = NetNeutrality.getJDA().getGuildById(guildId);
Guild guild = NetNeutrality.getInstance().getJDA().getGuildById(guildId);
boolean ok = true; //TODO: add bool
if (guild == null)
GuildManager.getManager(guildId).getGuildConfigManager().reloadSettingsCache();
@@ -25,7 +25,7 @@ public class GuildHandler extends AbstractHandler {
JSONArray array = new JSONArray();
JDA jda = NetNeutrality.getJDA();
JDA jda = NetNeutrality.getInstance().getJDA();
jda.getGuilds().forEach(guild -> {
JSONObject obj = new JSONObject();
@@ -45,7 +45,7 @@ public class GuildHandler extends AbstractHandler {
String guild = args[args.length - 1];
JSONArray array = new JSONArray();
JDA jda = NetNeutrality.getJDA();
JDA jda = NetNeutrality.getInstance().getJDA();
if (jda.getGuildById(guild) == null) {
sendStringResponse(exchange, path);
@@ -69,7 +69,7 @@ public class GuildHandler extends AbstractHandler {
String[] args = exchange.getRequestURI().toString().split("/");
String guild = args[args.length - 1];
JSONArray array = new JSONArray();
JDA jda = NetNeutrality.getJDA();
JDA jda = NetNeutrality.getInstance().getJDA();
if (jda.getGuildById(guild) == null) {
sendStringResponse(exchange, path);
@@ -19,6 +19,7 @@ import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.thread.ThreadManager;
import me.bobert.netneutrality.db.GSettings;
import me.bobert.netneutrality.music.source.UdioAudioSourceManager;
import me.bobert.netneutrality.thread.MusicManagerCleanupThread;
import me.bobert.netneutrality.thread.MusicPlayerAutoDisconnectThread;
import me.bobert.netneutrality.utils.MusicUtils;
import me.bobert.netneutrality.utils.Settings;
@@ -27,24 +28,20 @@ import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class MusicManager {
private static final Logger logger = LogManager.getLogger(MusicManager.class);
private static final Map<String, MusicManager> managers = new ConcurrentHashMap<>();
private static final ScheduledExecutorService executor = ThreadManager.getScheduledExecutor();
private static final AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
private final Guild guild;
private final AudioPlayer player;
private final TrackScheduler scheduler;
private final int baseVolume;
private ScheduledFuture<?> autoDisconnectThread;
private MusicManager(Guild guild) {
this.guild = guild;
@@ -75,6 +72,9 @@ public class MusicManager {
String po = Settings.getInstance().get("NETNEUTRALITY_PO_TOKEN");
String visitor = Settings.getInstance().get("NETNEUTRALITY_VISITOR_DATA");
dev.lavalink.youtube.clients.Web.setPoTokenAndVisitorData(po, visitor);
ThreadManager.getScheduledExecutor().scheduleWithFixedDelay(new MusicPlayerAutoDisconnectThread(), 15, 15, TimeUnit.MINUTES);
ThreadManager.getScheduledExecutor().scheduleWithFixedDelay(new MusicManagerCleanupThread(), 1, 1, TimeUnit.HOURS);
}
public static void disconnectPlayers() {
@@ -84,6 +84,10 @@ public class MusicManager {
}
}
public static Collection<MusicManager> getManagers() {
return managers.values();
}
public TrackScheduler getScheduler() {
return scheduler;
}
@@ -105,14 +109,11 @@ public class MusicManager {
}
public synchronized void connect(AudioChannel audioChannel) {
if (audioChannel != null && isNotConnectedTo(audioChannel)) {
if (audioChannel != null) {
logger.debug("Guild {}: connecting to voice channel {}", audioChannel.getGuild().getId(), audioChannel.getId());
guild.getAudioManager().openAudioConnection(audioChannel);
guild.getAudioManager().setSelfDeafened(true);
player.setVolume(baseVolume);
if (autoDisconnectThread == null)
autoDisconnectThread = executor.scheduleWithFixedDelay(new MusicPlayerAutoDisconnectThread(this), 240, 240, TimeUnit.SECONDS);
} else {
throw new RuntimeException("Error connecting to voice channel");
}
@@ -126,11 +127,6 @@ public class MusicManager {
}
scheduler.detachContext();
if (autoDisconnectThread != null) {
autoDisconnectThread.cancel(true);
autoDisconnectThread = null;
}
}
public boolean isConnected() {
@@ -152,6 +148,7 @@ public class MusicManager {
public synchronized void play(String query, CommandContext context, boolean silent) {
logger.debug("Guild {}: request to play '{}' by user {}", guild.getId(), query, context.getGuildUser().getId());
if (MusicUtils.canConnect(context)) {
SourceFinder.AudioSourceType type = SourceFinder.getSource(query);
String identifier = SourceFinder.getIdentifier(query);
if (identifier == null) {
@@ -159,7 +156,6 @@ public class MusicManager {
return;
}
if (MusicUtils.canConnect(context)) {
TrackLoadingHandler loadingHandler = new TrackLoadingHandler(context, this, identifier, type, silent);
playerManager.loadItemOrdered(this, identifier, loadingHandler);
}
@@ -169,6 +165,10 @@ public class MusicManager {
player.stopTrack();
}
public synchronized CommandExecutionResult resume() {
return getQueueManager().resume();
}
public synchronized CommandExecutionResult voteSkip(CommandContext context) {
AudioChannel audioChannel = guild.getAudioManager().getConnectedChannel();
if (audioChannel == null) {
@@ -90,14 +90,19 @@ public class QueueManager {
}
}
public void removeTracksFromUser(Member member) {
public CommandExecutionResult removeTracksFromUser(Member member) {
if (queue.stream().filter(trackInfo -> member.equals(trackInfo.getRequester())).toList().isEmpty()) {
return CommandExecutionResult.info(String.format("No tracks added by user %s", member.getEffectiveName()));
}
synchronized (queue) {
int size = queue.stream().filter(trackInfo -> member.equals(trackInfo.getRequester())).toList().size();
if (size == 0) {
return CommandExecutionResult.info(String.format("No tracks added by user %s", member.getEffectiveName()));
}
if (size > 0) {
if (size == queue.size()) {
clearQueue();
return;
return clearQueue();
}
TrackInfo first = queue.peek();
@@ -106,7 +111,8 @@ public class QueueManager {
if (first != null && member.equals(first.getRequester())) {
skip(1);
}
}
return CommandExecutionResult.success(String.format("Removed all tracks from user %s", member.getEffectiveName()));
}
}
@@ -119,6 +125,10 @@ public class QueueManager {
}
}
public void queue(List<AudioTrack> tracks, Member member) {
tracks.forEach(track -> queue(track, member));
}
public void queue(AudioTrack track, Member member) {
TrackInfo info = new TrackInfo(track, member);
if (queue.contains(info)) {
@@ -145,6 +155,20 @@ public class QueueManager {
}
}
public CommandExecutionResult resume() {
synchronized (queue) {
TrackInfo next = queue.peek();
if (next != null) {
player.startTrack(next.getTrack().makeClone(), false);
return CommandExecutionResult.success("Starting playback of track " + next.getTrack().getInfo().title);
}
player.stopTrack();
return CommandExecutionResult.info("Queue is empty, leaving voice channel");
}
}
public void startNextTrack() {
synchronized (queue) {
TrackInfo last = queue.poll();
@@ -67,6 +67,10 @@ public class TrackScheduler extends AudioEventAdapter {
this.context = null;
}
public Context getContext() {
return context;
}
@Override
public void onPlayerPause(AudioPlayer player) {
// Player was paused
@@ -96,6 +100,7 @@ public class TrackScheduler extends AudioEventAdapter {
}
if (endReason == AudioTrackEndReason.STOPPED) {
// maybe skip removing head to make resuming easier?
queueManager.removeHeadOfQueue();
}
@@ -109,7 +114,7 @@ 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());
logger.error("Guild {}: exception while playing track: {}", guild.getId(), track.getIdentifier());
LogHandler.log(exception);
context.reply(
@@ -44,9 +44,7 @@ public class AddAllTracksTask extends ButtonTask implements WithParent<LoadPlayl
Member member = context.getGuildUser();
if (MusicUtils.connect(context)) {
for (AudioTrack track : tracks) {
manager.getQueueManager().queue(track, member);
}
manager.getQueueManager().queue(tracks, member);
context.reply(MessageFunction.buildAddingPlaylistMessage(tracks, parentTask.getPlaylist().getName(), member, SourceFinder.AudioSourceType.Youtube));
}
@@ -80,7 +80,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
ActionRow firstRow = ActionRow.of(buttonList);
ActionRow secondRow = commonButtons(searchResults, "Add all tracks", "Not for me honey");
context.edit(MessageFunction.buildSearchResultsMessage(searchResults, context.getGuildUser()), firstRow, secondRow);
context.edit(MessageFunction.buildSearchResultsMessage(searchResults, context.getGuildUser()), List.of(firstRow, secondRow));
return;
}
@@ -89,16 +89,14 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
if (tracks.size() > maxPlaylistSizeBeforeAsking) {
ActionRow firstRow = commonButtons(playlist.getTracks(), "I'm ready", "Please don't");
context.edit(MessageFunction.buildPlaylistLoadMessage(playlist, context.getGuildUser()), firstRow);
context.edit(MessageFunction.buildPlaylistLoadMessage(playlist, context.getGuildUser()), List.of(firstRow));
return;
}
// Load playlist normally
MusicManager manager = MusicManager.get(context.getGuild());
if (MusicUtils.connect(context)) {
for (AudioTrack track : tracks) {
manager.getQueueManager().queue(track, context.getGuildUser());
}
manager.getQueueManager().queue(tracks, context.getGuildUser());
context.edit(MessageFunction.buildAddingPlaylistMessage(tracks, playlist.getName(), context.getGuildUser(), Youtube));
}
@@ -0,0 +1,39 @@
package me.bobert.netneutrality.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.TrackScheduler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MusicManagerCleanupThread implements Runnable {
private static final Logger logger = LogManager.getLogger(MusicManagerCleanupThread.class);
@Override
public void run() {
try {
for (MusicManager manager : MusicManager.getManagers()) {
processManager(manager);
}
} catch (InterruptedException e) {
logger.error("Interrupted while running MusicManagerCleanupThread", e);
}
}
private void processManager(MusicManager manager) throws InterruptedException {
TrackScheduler scheduler = manager.getScheduler();
// Context is still attached after bot is disconnected, the bot might have been kicked from the channel
if (scheduler.getContext() != null && !manager.isConnected()) {
manager.disconnect();
manager.getQueueManager().clearQueue();
}
}
}
@@ -5,9 +5,7 @@ package me.bobert.netneutrality.thread;
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.NetNeutrality;
import me.bobert.netneutrality.music.MusicManager;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
import org.apache.logging.log4j.LogManager;
@@ -16,40 +14,47 @@ import org.apache.logging.log4j.Logger;
import java.util.List;
public class MusicPlayerAutoDisconnectThread implements Runnable {
private static final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnectThread.class);
private final MusicManager manager;
//TODO: move to scheduler
public MusicPlayerAutoDisconnectThread(MusicManager musicManager) {
manager = musicManager;
}
@Override
public void run() {
Guild guild = manager.getGuild();
AudioChannel channel = guild.getAudioManager().getConnectedChannel();
try {
for (MusicManager manager : MusicManager.getManagers()) {
processManager(manager);
}
} catch (InterruptedException e) {
logger.error("Interrupted while running MusicPlayerAutoDisconnectThread", e);
}
}
private void processManager(MusicManager manager) throws InterruptedException {
logger.debug("Processing MusicPlayerAutoDisconnectThread for guild {}", manager.getGuild().getId());
AudioChannel channel = manager.getGuild().getAudioManager().getConnectedChannel();
if (channel != null) {
if (conditionsMetToDisconnect(channel)) {
if (conditionsMetToDisconnect(manager)) {
logger.debug("Guild {}: Sleeping 30 seconds and checking again", manager.getGuild().getId());
// Sleep 30 seconds then check again (prevents disconnecting right after a track ends)
Thread.sleep(30000);
if (conditionsMetToDisconnect(channel)) {
logger.debug("Guild {}: auto-disconnect player from voice channel: {}", guild.getId(), channel.getId());
if (conditionsMetToDisconnect(manager)) {
logger.debug("Guild {}: auto-disconnect player from voice channel: {}", manager.getGuild().getId(), channel.getId());
manager.disconnect();
}
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private boolean conditionsMetToDisconnect(AudioChannel channel) {
List<Member> members = channel.getMembers().stream().filter(member -> !member.getId().equals(NetNeutrality.getSelf().getId())).toList();
private boolean conditionsMetToDisconnect(MusicManager manager) {
AudioChannel channel = manager.getGuild().getAudioManager().getConnectedChannel();
if (channel != null) {
List<Member> members = channel.getMembers().stream().filter(member -> !member.getId().equals(channel.getJDA().getSelfUser().getId())).toList();
return members.isEmpty() || manager.getQueueManager().isEmpty();
}
return false;
}
}
@@ -11,7 +11,12 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configurator;
public class ShutdownThread implements Runnable {
public class ShutdownThread extends Thread {
private final NetNeutrality main;
public ShutdownThread(NetNeutrality main) {
this.main = main;
}
@Override
public void run() {
@@ -20,8 +25,8 @@ public class ShutdownThread implements Runnable {
Configurator.shutdown((LoggerContext) LogManager.getContext());
}
if (NetNeutrality.getJDA() != null) {
NetNeutrality.shutdown();
if (main.getJDA() != null) {
main.shutdown();
}
}
@@ -31,10 +31,8 @@ import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.*;
public class MessageFunction {
@@ -44,12 +42,12 @@ public class MessageFunction {
AudioTrack current = manager.getCurrentlyPlaying();
EmbedBuilder message = new EmbedBuilder();
message.setAuthor("Link to track", String.format("https://youtu.be/%s", current.getIdentifier()));
message.setAuthor("Link to track (click me)", String.format("https://youtu.be/%s", current.getIdentifier()));
message.setThumbnail(String.format("https://img.youtube.com/vi/%s/default.jpg", current.getIdentifier()));
message.setTitle("Currently playing:");
//message.setTitle("Currently playing:");
message.setColor(Color.BLUE);
message.addField("Track title: ", current.getInfo().title, false);
message.addField("Duration: ", DurationFormatUtils.formatDuration(current.getPosition(), "**HH:mm:ss**", true) + " / " + DurationFormatUtils.formatDuration(current.getDuration(), "**HH:mm:ss**", true), false);
message.addField("Currently playing: ", current.getInfo().title, true);
message.addField("Duration: ", DurationFormatUtils.formatDuration(current.getPosition(), "HH:mm:ss", true) + " / " + DurationFormatUtils.formatDuration(current.getDuration(), "HH:mm:ss", true), true);
message.setTimestamp(OffsetDateTime.now());
return message.build();
@@ -110,8 +108,12 @@ public class MessageFunction {
}
public static String buildViewQueueMessage(MusicManager manager) {
int limit = 15;
List<AudioTrack> tracks = manager.getQueueManager().getAllQueuedTracks();
if (tracks.isEmpty()) {
return "Queue is empty!";
}
int limit = 10;
AudioTrack current = manager.getCurrentlyPlaying();
int numberofTracksShown = Math.min(tracks.size(), limit);
@@ -125,13 +127,14 @@ public class MessageFunction {
int index = 0;
for (AudioTrack track : tracks) {
index++;
AudioTrackInfo info = track.getInfo();
message.append("\n");
if (current != null && current.getInfo().title.equals(info.title)) {
message.append("**").append(++index).append(". ").append(info.title).append("**");
message.append("- ").append("**").append(info.title).append("**");
} else {
message.append(++index).append(". ").append(info.title);
message.append("- ").append(info.title);
}
if (index == limit) break;
@@ -197,8 +200,8 @@ public class MessageFunction {
return embed.build();
}
public static ArrayList<MessageEmbed> buildHelpMessage() {
HashMap<CommandType, EmbedBuilder> help = new HashMap<>();
public static List<MessageEmbed> buildHelpMessage() {
Map<CommandType, EmbedBuilder> help = new HashMap<>();
for (Command command : CommandManager.commands.values()) {
if (command.getType() == null)
@@ -226,7 +229,7 @@ public class MessageFunction {
}
}
ArrayList<MessageEmbed> messages = new ArrayList<>();
List<MessageEmbed> messages = new ArrayList<>();
help.values().forEach(embedBuilder -> messages.add(embedBuilder.build()));
return messages;
@@ -262,10 +265,11 @@ public class MessageFunction {
if (exception.getCause() != null) {
String cause;
if (exception.getCause().getMessage() != null && !exception.getCause().getMessage().isEmpty()) {
cause = exception.getCause().getMessage();
cause = exception.getCause().getMessage().substring(0, 64);
} else {
cause = exception.getCause().getClass().getName();
}
message.addField("Caused by:", cause, false);
}
@@ -17,10 +17,10 @@ import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
public class MusicUtils {
public static boolean connect(Context context) {
return connect(context, false, false);
return connect(context, false);
}
public static boolean connect(Context context, boolean skipChecks, boolean checkOnly) {
public static boolean connect(Context context, boolean checkOnly) {
MusicManager manager = MusicManager.get(context.getGuild());
Member member = context.getGuildUser();
if (member == null) {
@@ -34,7 +34,7 @@ public class MusicUtils {
return false;
}
if (!skipChecks && !isUserDj(member) && (manager.isConnected() && manager.isNotConnectedTo(audioChannel))) {
if (!isUserDj(member) && (manager.isConnected() && manager.isNotConnectedTo(audioChannel))) {
context.reply("The bot isn't connected to your voice channel");
return false;
}
@@ -48,7 +48,7 @@ public class MusicUtils {
}
public static boolean canConnect(Context context) {
return connect(context, false, true);
return connect(context, true);
}
public static boolean checkIfConnected(Context context) {