Remove deprecated classes and static singleton-based architecture
This commit is contained in:
1 parent
54a27883d1
commit
b8786c8dc9
133 files changed
+4984
-1038
No files matched your search
@@ -0,0 +1,233 @@
|
||||
package me.bobert.netneutrality;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.db.GSettings;
|
||||
import club.minnced.discord.jdave.interop.JDaveSessionFactory;
|
||||
import io.sentry.Sentry;
|
||||
import me.bobert.netneutrality.bootstrap.ApplicationContext;
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import me.bobert.netneutrality.bootstrap.BotState;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.command.CommandManager;
|
||||
import me.bobert.netneutrality.db.SessionManager;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManagerImpl;
|
||||
import me.bobert.netneutrality.core.thread.ScheduledTaskManager;
|
||||
import me.bobert.netneutrality.core.thread.ThreadManager;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProviderImpl;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.command.CommandRegistry;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.hooks.EventsHandler;
|
||||
import me.bobert.netneutrality.http.server.WebApi;
|
||||
import me.bobert.netneutrality.music.MusicServiceImpl;
|
||||
import me.bobert.netneutrality.core.thread.ExecutorProvider;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.utils.ChannelMessagePurger;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.audio.AudioModuleConfig;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
|
||||
public class App {
|
||||
private static final Logger logger = LogManager.getLogger(App.class);
|
||||
/**
|
||||
* Version string resolved from the early-bootstrap {@link Settings} singleton (properties must be
|
||||
* readable before the {@link ApplicationContext} exists).
|
||||
*/
|
||||
public static final String VERSION = Settings.getVersion();
|
||||
private static App instance;
|
||||
private JDA jda;
|
||||
private final ApplicationContext context = new ApplicationContext();
|
||||
|
||||
static void main() {
|
||||
System.out.println("Starting App v" + VERSION);
|
||||
|
||||
// Settings is intentionally a process-wide bootstrap singleton: Sentry/env/token must be
|
||||
// readable before the ApplicationContext is constructed.
|
||||
String env = Settings.getInstance().get("NETNEUTRALITY_BOT_ENV", "dev");
|
||||
String sentryDsn = Settings.getInstance().get("NETNEUTRALITY_SENTRY_DSN");
|
||||
if (sentryDsn != null) {
|
||||
logger.info("Found Sentry DSN in config, setting up the SDK..");
|
||||
|
||||
Sentry.init(options -> {
|
||||
options.setDsn(sentryDsn);
|
||||
options.setEnvironment(env);
|
||||
options.setTracesSampleRate(1.0);
|
||||
options.setDebug(false);
|
||||
options.setRelease(VERSION);
|
||||
});
|
||||
}
|
||||
|
||||
if ("dev".equals(env)) {
|
||||
Configurator.setLevel("net.dv8tion.jda", Level.DEBUG);
|
||||
Configurator.setLevel("dev.lavalink.youtube", Level.DEBUG);
|
||||
Configurator.setLevel("com.sedmelluq.discord", Level.DEBUG);
|
||||
}
|
||||
|
||||
instance = new App();
|
||||
}
|
||||
|
||||
public App() {
|
||||
startup();
|
||||
}
|
||||
|
||||
private void startup() {
|
||||
String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN");
|
||||
if (token == null) {
|
||||
throw new RuntimeException("No bot token provided, exiting.");
|
||||
}
|
||||
|
||||
buildContext();
|
||||
|
||||
try {
|
||||
context.start();
|
||||
} catch (Exception e) {
|
||||
LogHandler.log(e);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers domain services and remaining startup/shutdown adapters with the
|
||||
* {@link ApplicationContext}. Ascending {@link Lifecycle#priority()} drives startup order; the
|
||||
* context stops components in the exact reverse order.
|
||||
*/
|
||||
private void buildContext() {
|
||||
SettingsProvider settings = Settings.getInstance();
|
||||
|
||||
// Config first so SettingsProvider is resolvable for constructor injection.
|
||||
context.register(settings);
|
||||
|
||||
// Allow ApplicationContext itself to be injected into components that need it.
|
||||
context.register(context);
|
||||
|
||||
// Persistence: build the SessionFactory + run migrations; released last on shutdown.
|
||||
// SessionManager binds itself into JpaUtils for repository access.
|
||||
context.register(new SessionManager(settings));
|
||||
|
||||
// Executors: no explicit start today; shut down after music players are disconnected.
|
||||
ExecutorProvider executors = context.register(new ThreadManager());
|
||||
ChannelMessagePurger.bind(executors);
|
||||
|
||||
// Scheduled tasks (priority 55): constructed early so TaskManager can resolve it; cancelled
|
||||
// early on shutdown, before executors.
|
||||
context.register(new ScheduledTaskManager(executors));
|
||||
|
||||
// Command registry (priority 30).
|
||||
CommandRegistry commands = context.register(new CommandManager(context));
|
||||
|
||||
// Task registry (priority 30).
|
||||
TaskManager tasks = context.register(new TaskManagerImpl(context));
|
||||
|
||||
// Per-guild service cache (not a Lifecycle component).
|
||||
GuildServiceProvider guildServices = context.register(new GuildServiceProviderImpl());
|
||||
LogHandler.bind(guildServices);
|
||||
JpaUtils.bindGuildServiceLookup(guildServices::get);
|
||||
|
||||
// Music sources (priority 40): initialise the shared player manager; disconnect on shutdown.
|
||||
MusicService music = context.register(new MusicServiceImpl(settings, guildServices, tasks));
|
||||
BotState.bind(guildServices, music);
|
||||
|
||||
// JDA gateway; disconnected on shutdown.
|
||||
context.register(lifecycle(60, this::initJDA, this::disconnect));
|
||||
|
||||
// Register slash commands, attach the event handler and set the presence once JDA is ready.
|
||||
context.register(lifecycle(65, () -> {
|
||||
commands.registerSlashCommands(jda);
|
||||
jda.addEventListener(new EventsHandler(settings, guildServices, commands, tasks));
|
||||
jda.getPresence().setActivity(Activity.watching("you"));
|
||||
}, () -> { }));
|
||||
|
||||
// Web API (priority 70) — Lifecycle with graceful stop.
|
||||
context.register(new WebApi(settings, guildServices, this::getJDA, this::shutdown));
|
||||
}
|
||||
|
||||
private static Lifecycle lifecycle(int priority, ThrowingRunnable start, Runnable stop) {
|
||||
return new Lifecycle() {
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
start.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stop.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return priority;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowingRunnable {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
public static App getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void initJDA() {
|
||||
String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN");
|
||||
if (token == null) {
|
||||
SessionManager sessions = context.get(SessionManager.class);
|
||||
if (sessions != null) {
|
||||
sessions.stop();
|
||||
}
|
||||
throw new RuntimeException("No bot token provided, exiting.");
|
||||
}
|
||||
|
||||
try {
|
||||
jda = JDABuilder
|
||||
.createDefault(token)
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MEMBERS,
|
||||
GatewayIntent.GUILD_MESSAGE_REACTIONS,
|
||||
GatewayIntent.GUILD_VOICE_STATES,
|
||||
GatewayIntent.MESSAGE_CONTENT)
|
||||
.setAudioModuleConfig(
|
||||
new AudioModuleConfig().withDaveSessionFactory(new JDaveSessionFactory()))
|
||||
.setActivity(Activity.watching("me load..."))
|
||||
.build();
|
||||
jda.awaitReady();
|
||||
} catch (InvalidTokenException | IllegalArgumentException | InterruptedException e) {
|
||||
LogHandler.log(e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
System.out.println("[Net Neutrality] Shutting down...");
|
||||
context.stop();
|
||||
System.out.println("[Net Neutrality] Shutdown complete");
|
||||
}
|
||||
|
||||
public JDA getJDA() {
|
||||
return jda;
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
if (jda != null) {
|
||||
jda.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package me.bobert.netneutrality;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import club.minnced.discord.jdave.interop.JDaveSessionFactory;
|
||||
import io.sentry.Sentry;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.command.CommandManager;
|
||||
import me.bobert.netneutrality.core.db.DatabaseMigration;
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.core.thread.ScheduledTaskManager;
|
||||
import me.bobert.netneutrality.core.thread.ThreadManager;
|
||||
import me.bobert.netneutrality.hooks.EventsHandler;
|
||||
import me.bobert.netneutrality.http.server.WebApi;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.audio.AudioModuleConfig;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
|
||||
public class NetNeutrality {
|
||||
private static final Logger logger = LogManager.getLogger(NetNeutrality.class);
|
||||
public static final String VERSION = Settings.getVersion();
|
||||
private static NetNeutrality instance;
|
||||
private static JDA jda;
|
||||
|
||||
static void main() {
|
||||
System.out.println("Starting NetNeutrality v" + VERSION);
|
||||
|
||||
String env = Settings.getInstance().get("NETNEUTRALITY_BOT_ENV", "dev");
|
||||
String sentryDsn = Settings.getInstance().get("NETNEUTRALITY_SENTRY_DSN");
|
||||
if (sentryDsn != null) {
|
||||
logger.info("Found Sentry DSN in config, setting up the SDK..");
|
||||
|
||||
Sentry.init(options -> {
|
||||
options.setDsn(sentryDsn);
|
||||
options.setEnvironment(env);
|
||||
options.setTracesSampleRate(1.0);
|
||||
options.setDebug(false);
|
||||
options.setRelease(VERSION);
|
||||
});
|
||||
}
|
||||
|
||||
if ("dev".equals(env)) {
|
||||
Configurator.setLevel("net.dv8tion.jda", Level.DEBUG);
|
||||
Configurator.setLevel("dev.lavalink.youtube", Level.DEBUG);
|
||||
Configurator.setLevel("com.sedmelluq.discord", Level.DEBUG);
|
||||
}
|
||||
|
||||
instance = new NetNeutrality();
|
||||
}
|
||||
|
||||
public NetNeutrality() {
|
||||
startup();
|
||||
}
|
||||
|
||||
private void startup() {
|
||||
String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN");
|
||||
if (token == null) {
|
||||
throw new RuntimeException("No bot token provided, exiting.");
|
||||
}
|
||||
|
||||
SessionManager.getSessionFactory();
|
||||
DatabaseMigration.migrate();
|
||||
CommandManager.init();
|
||||
MusicManager.initPlayerManager();
|
||||
TaskManager.getInstance().init();
|
||||
initJDA();
|
||||
CommandManager.registerSlashCommands(jda);
|
||||
jda.addEventListener(new EventsHandler());
|
||||
new WebApi(Integer.parseInt(Settings.getInstance().get("NETNEUTRALITY_API_PORT", "9000")));
|
||||
jda.getPresence().setActivity(Activity.watching("you"));
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
|
||||
}
|
||||
|
||||
public static NetNeutrality getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void initJDA() {
|
||||
String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN");
|
||||
if (token == null) {
|
||||
SessionManager.shutdown();
|
||||
throw new RuntimeException("No bot token provided, exiting.");
|
||||
}
|
||||
|
||||
try {
|
||||
jda = JDABuilder
|
||||
.createDefault(token)
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MEMBERS,
|
||||
GatewayIntent.GUILD_MESSAGE_REACTIONS,
|
||||
GatewayIntent.GUILD_VOICE_STATES,
|
||||
GatewayIntent.MESSAGE_CONTENT)
|
||||
.setAudioModuleConfig(
|
||||
new AudioModuleConfig().withDaveSessionFactory(new JDaveSessionFactory()))
|
||||
.setActivity(Activity.watching("me load..."))
|
||||
.build();
|
||||
jda.awaitReady();
|
||||
} catch (InvalidTokenException | IllegalArgumentException | InterruptedException e) {
|
||||
LogHandler.log(e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
System.out.println("[Net Neutrality] Shutting down...");
|
||||
ScheduledTaskManager.cancelAll();
|
||||
MusicManager.disconnectPlayers();
|
||||
ThreadManager.shutdownExecutor();
|
||||
disconnect();
|
||||
SessionManager.shutdown();
|
||||
System.out.println("[Net Neutrality] Shutdown complete");
|
||||
}
|
||||
|
||||
public JDA getJDA() {
|
||||
return jda;
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
if (jda != null) {
|
||||
jda.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package me.bobert.netneutrality.bootstrap;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* A small hand-rolled application container that owns wired instances and drives ordered
|
||||
* startup/shutdown.
|
||||
*
|
||||
* <p>Components implementing {@link Lifecycle} are started in ascending {@link Lifecycle#priority()}
|
||||
* order and stopped in the exact reverse order, giving symmetric, deterministic startup and
|
||||
* shutdown without any DI framework.
|
||||
*/
|
||||
public final class ApplicationContext {
|
||||
private static final Logger logger = LogManager.getLogger(ApplicationContext.class);
|
||||
|
||||
private final List<Lifecycle> components = new ArrayList<>();
|
||||
private final Map<Class<?>, Object> instances = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Registers a component with the context. If the component implements {@link Lifecycle} it will
|
||||
* participate in ordered start/stop. The instance is also indexed by its concrete type and can
|
||||
* be resolved later via {@link #get(Class)}.
|
||||
*
|
||||
* @param component the component to register
|
||||
* @param <T> the component type
|
||||
* @return the same component, for fluent wiring
|
||||
*/
|
||||
public <T> T register(T component) {
|
||||
if (component instanceof Lifecycle lifecycle) {
|
||||
components.add(lifecycle);
|
||||
}
|
||||
instances.put(component.getClass(), component);
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a previously registered instance assignable to the given type.
|
||||
*
|
||||
* @param type the requested type
|
||||
* @param <T> the requested type
|
||||
* @return the matching instance, or {@code null} if none was registered
|
||||
*/
|
||||
public <T> T get(Class<T> type) {
|
||||
Object direct = instances.get(type);
|
||||
if (direct != null) {
|
||||
return type.cast(direct);
|
||||
}
|
||||
for (Object instance : instances.values()) {
|
||||
if (type.isInstance(instance)) {
|
||||
return type.cast(instance);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts all registered {@link Lifecycle} components in ascending priority order.
|
||||
*
|
||||
* @throws Exception if any component fails to start
|
||||
*/
|
||||
public void start() throws Exception {
|
||||
components.sort(Comparator.comparingInt(Lifecycle::priority));
|
||||
for (Lifecycle component : components) {
|
||||
logger.debug("Starting component {} (priority {})", component.name(), component.priority());
|
||||
component.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all registered {@link Lifecycle} components in the reverse of their start order. Failures
|
||||
* are logged and do not prevent the remaining components from being stopped.
|
||||
*/
|
||||
public void stop() {
|
||||
List<Lifecycle> reversed = new ArrayList<>(components);
|
||||
reversed.sort(Comparator.comparingInt(Lifecycle::priority));
|
||||
Collections.reverse(reversed);
|
||||
for (Lifecycle component : reversed) {
|
||||
try {
|
||||
logger.debug("Stopping component {} (priority {})", component.name(), component.priority());
|
||||
component.stop();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error while stopping component {}", component.name(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-3
@@ -1,11 +1,13 @@
|
||||
package me.bobert.netneutrality.core;
|
||||
package me.bobert.netneutrality.bootstrap;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.music.TrackScheduler;
|
||||
import org.json.JSONArray;
|
||||
@@ -19,14 +21,32 @@ import java.util.Map;
|
||||
public class BotState {
|
||||
public static boolean SAFE = false;
|
||||
|
||||
private static volatile GuildServiceProvider guildServices;
|
||||
private static volatile MusicService musicService;
|
||||
|
||||
/**
|
||||
* Binds providers used by the optional state dump. Called once during bootstrap.
|
||||
*
|
||||
* @param guildServices the guild service provider
|
||||
* @param musicService the music service
|
||||
*/
|
||||
public static void bind(GuildServiceProvider guildServices, MusicService musicService) {
|
||||
BotState.guildServices = guildServices;
|
||||
BotState.musicService = musicService;
|
||||
}
|
||||
|
||||
//TODO: is it needed?
|
||||
private static void saveGuildsState() {
|
||||
Map<String, GuildService> services = GuildService.getServices();
|
||||
if (guildServices == null || musicService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, GuildService> services = guildServices.getServices();
|
||||
|
||||
JSONObject main = new JSONObject();
|
||||
JSONArray guildsArray = new JSONArray();
|
||||
|
||||
for (MusicManager manager : MusicManager.getManagers()) {
|
||||
for (MusicManager manager : musicService.getManagers()) {
|
||||
TrackScheduler trackScheduler = manager.getScheduler();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package me.bobert.netneutrality.bootstrap;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contract for a component whose lifecycle is owned and driven by the {@link ApplicationContext}.
|
||||
*
|
||||
* <p>Components are {@linkplain #start() started} in ascending {@link #priority()} order and
|
||||
* {@linkplain #stop() stopped} in the exact reverse order, giving symmetric, deterministic startup
|
||||
* and shutdown without any DI framework. A lower {@code priority()} means the component is started
|
||||
* earlier and, consequently, stopped later.
|
||||
*/
|
||||
public interface Lifecycle {
|
||||
|
||||
/**
|
||||
* Brings the component up. Called exactly once, during {@link ApplicationContext#start()}.
|
||||
*
|
||||
* @throws Exception if the component cannot be started; startup is aborted
|
||||
*/
|
||||
void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Tears the component down, releasing whatever {@link #start()} acquired. Called at most once,
|
||||
* during {@link ApplicationContext#stop()}. Implementations should be null-safe / idempotent so
|
||||
* they can cope with a component that never fully started.
|
||||
*
|
||||
* <p>The default is a no-op for components that hold no resources.
|
||||
*
|
||||
* @throws Exception if teardown fails; the context logs it and continues stopping the rest
|
||||
*/
|
||||
default void stop() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Relative ordering weight. Lower values start earlier and stop later; equal values keep their
|
||||
* registration order. Defaults to {@code 0}.
|
||||
*
|
||||
* @return the priority of this component
|
||||
*/
|
||||
default int priority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable name used in lifecycle diagnostics/logging. Defaults to the simple class name.
|
||||
*
|
||||
* @return a short label identifying this component
|
||||
*/
|
||||
default String name() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package me.bobert.netneutrality.bootstrap;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ReflectionHelper {
|
||||
private static final Logger logger = LogManager.getLogger(ReflectionHelper.class);
|
||||
private static final BiConsumer<Class<?>, Exception> DEFAULT_FAILURE = (aClass, e) -> logger.error("Failed to load class: {}", aClass.getName(), e);
|
||||
|
||||
public static <E> void loadClassesFromPackage(String packageName, Class<E> clazz, Consumer<E> onInstanciated, BiConsumer<Class<?>, Exception> onFailed) {
|
||||
loadClassesFromPackage(packageName, clazz, null, onInstanciated, onFailed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers concrete subtypes of {@code clazz} under {@code packageName} and instantiates each
|
||||
* one. When a non-null {@link ApplicationContext} is provided, prefers the public constructor
|
||||
* with the most parameters whose every parameter type is resolvable via
|
||||
* {@link ApplicationContext#get(Class)}; otherwise (or when no such constructor exists) falls
|
||||
* back to the no-arg constructor.
|
||||
*
|
||||
* @param packageName root package to scan
|
||||
* @param clazz base type of classes to load
|
||||
* @param context optional DI context used for constructor injection; {@code null} keeps
|
||||
* legacy no-arg construction only
|
||||
* @param onInstanciated callback invoked with each successfully created instance
|
||||
* @param onFailed callback invoked when a class cannot be instantiated
|
||||
* @param <E> the base type
|
||||
*/
|
||||
public static <E> void loadClassesFromPackage(String packageName, Class<E> clazz, ApplicationContext context, Consumer<E> onInstanciated, BiConsumer<Class<?>, Exception> onFailed) {
|
||||
Set<Class<? extends E>> classes = new Reflections(packageName).getSubTypesOf(clazz);
|
||||
for (Class<? extends E> s : classes) {
|
||||
try {
|
||||
if (Modifier.isAbstract(s.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
E instance = instantiate(s, context);
|
||||
onInstanciated.accept(instance);
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
||||
onFailed.accept(s, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates {@code type} using context-based constructor injection when possible.
|
||||
*
|
||||
* <p>Selection algorithm: among public constructors, pick the one with the most parameters for
|
||||
* which every parameter type resolves to a non-null value via {@code context.get(paramType)}.
|
||||
* If no such constructor exists (or {@code context} is null), fall back to the public no-arg
|
||||
* constructor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E> E instantiate(Class<? extends E> type, ApplicationContext context)
|
||||
throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
||||
if (context != null) {
|
||||
Constructor<?> best = null;
|
||||
Object[] bestArgs = null;
|
||||
|
||||
for (Constructor<?> ctor : type.getConstructors()) {
|
||||
Class<?>[] paramTypes = ctor.getParameterTypes();
|
||||
if (paramTypes.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
boolean resolvable = true;
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
Object dependency = context.get(paramTypes[i]);
|
||||
if (dependency == null) {
|
||||
resolvable = false;
|
||||
break;
|
||||
}
|
||||
args[i] = dependency;
|
||||
}
|
||||
|
||||
if (resolvable && (best == null || paramTypes.length > best.getParameterCount())) {
|
||||
best = ctor;
|
||||
bestArgs = args;
|
||||
}
|
||||
}
|
||||
|
||||
if (best != null) {
|
||||
return (E) best.newInstance(bestArgs);
|
||||
}
|
||||
}
|
||||
|
||||
return type.getConstructor().newInstance();
|
||||
}
|
||||
|
||||
public static <E> void loadCommands(Class<E> clazz, Consumer<E> onInstanciated, BiConsumer<Class<?>, Exception> onFailed) {
|
||||
loadClassesFromPackage("me.bobert.netneutrality", clazz, onInstanciated, onFailed);
|
||||
}
|
||||
|
||||
public static <E> void loadCommands(Class<E> clazz, Consumer<E> onInstanciated) {
|
||||
loadClassesFromPackage("me.bobert.netneutrality", clazz, onInstanciated, DEFAULT_FAILURE);
|
||||
}
|
||||
|
||||
public static <E> void loadCommands(Class<E> clazz, ApplicationContext context, Consumer<E> onInstanciated) {
|
||||
loadClassesFromPackage("me.bobert.netneutrality", clazz, context, onInstanciated, DEFAULT_FAILURE);
|
||||
}
|
||||
|
||||
public static <E> void loadTasks(Class<E> clazz, Consumer<E> onInstanciated, BiConsumer<Class<?>, Exception> onFailed) {
|
||||
loadClassesFromPackage("me.bobert.netneutrality", clazz, onInstanciated, onFailed);
|
||||
}
|
||||
|
||||
public static <E> void loadTasks(Class<E> clazz, Consumer<E> onInstanciated) {
|
||||
loadClassesFromPackage("me.bobert.netneutrality", clazz, onInstanciated, DEFAULT_FAILURE);
|
||||
}
|
||||
|
||||
public static <E> void loadTasks(Class<E> clazz, ApplicationContext context, Consumer<E> onInstanciated) {
|
||||
loadClassesFromPackage("me.bobert.netneutrality", clazz, context, onInstanciated, DEFAULT_FAILURE);
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,23 @@ import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.db.entities.OBlacklist;
|
||||
import me.bobert.netneutrality.db.service.BlacklistService;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
|
||||
public class BlacklistCommand extends AdminCommand {
|
||||
|
||||
private final GuildServiceProvider guildServices;
|
||||
|
||||
public BlacklistCommand(GuildServiceProvider guildServices) {
|
||||
Objects.requireNonNull(guildServices, "GuildServiceProvider must not be null");
|
||||
this.guildServices = guildServices;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -55,7 +64,7 @@ public class BlacklistCommand extends AdminCommand {
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
String sub = context.getSubcommand();
|
||||
String word = context.getOption("word", "", CommandOption::getAsString);
|
||||
BlacklistService service = GuildService.get(context.getGuild()).getBlacklistService();
|
||||
BlacklistService service = guildServices.get(context.getGuild()).getBlacklistService();
|
||||
|
||||
switch (sub) {
|
||||
case "get" -> context.async("Retrieving list, please wait...", asyncMessage -> {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package me.bobert.netneutrality.command.admin;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.CommandCategory;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.AbstractCommand;
|
||||
import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.utils.ChannelMessagePurger;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
|
||||
public class PurgeCommand extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public boolean needAdmin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuildOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "purge";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Delete messages from the channel you are currently on";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommandOptions(CommandOptionsBuilder builder) {
|
||||
builder.addOptions(
|
||||
new OptionData(OptionType.INTEGER, "to-purge", "Number of messages to delete (max 2500)", true)
|
||||
.setRequiredRange(1, 2500));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandCategory getCategory() {
|
||||
return CommandCategory.ADMINISTRATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
int toDelete = context.getOption("to-purge", 0, CommandOption::getAsInt);
|
||||
context.ephemeral();
|
||||
|
||||
if (toDelete > 0) {
|
||||
GuildMessageChannel messageChannel = (GuildMessageChannel) context.getChannel();
|
||||
ChannelMessagePurger channelMessagePurger = ChannelMessagePurger.getPurger(messageChannel);
|
||||
boolean lock = channelMessagePurger.canLockBeAcquired();
|
||||
channelMessagePurger.purgeMessages(context, toDelete);
|
||||
|
||||
context.reply("Processing purge command.. This can take some time.");
|
||||
|
||||
if (!lock) {
|
||||
context.reply("Another purge is currently in progress on this channel, please wait before purging again");
|
||||
}
|
||||
}
|
||||
|
||||
return CommandExecutionResult.none();
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,7 @@ import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.AdminCommand;
|
||||
import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask;
|
||||
import me.bobert.netneutrality.tasks.reaction.EmojiReactionRoleTask;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
@@ -23,9 +22,18 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
|
||||
public class ReactionCommand extends AdminCommand {
|
||||
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public ReactionCommand(TaskManager taskManager) {
|
||||
Objects.requireNonNull(taskManager, "TaskManager must not be null");
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -93,11 +101,11 @@ public class ReactionCommand extends AdminCommand {
|
||||
|
||||
private CommandExecutionResult executeEmoji(CommandContext context, String guildId, String messageId, String sub) {
|
||||
String id = String.format("%s:%s", guildId, messageId);
|
||||
EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id);
|
||||
EmojiReactionRoleTask task = (EmojiReactionRoleTask) taskManager.getTask(id);
|
||||
|
||||
switch (sub) {
|
||||
case "list" -> {
|
||||
List<EmojiReactionRoleTask> tasks = TaskManager.getInstance().findTasksByPartialId(EmojiReactionRoleTask.class, guildId);
|
||||
List<EmojiReactionRoleTask> tasks = taskManager.findTasksByPartialId(EmojiReactionRoleTask.class, guildId);
|
||||
if (tasks.isEmpty()) {
|
||||
return CommandExecutionResult.info("No tasks found for this guild");
|
||||
}
|
||||
|
||||
@@ -8,16 +8,17 @@ package me.bobert.netneutrality.command.basic;
|
||||
import me.bobert.netneutrality.core.command.CommandCategory;
|
||||
import me.bobert.netneutrality.core.command.CommandContainer;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandManager;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.AbstractCommand;
|
||||
import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.tasks.Task;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.core.command.CommandRegistry;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.tasks.help.BotInfo;
|
||||
import me.bobert.netneutrality.tasks.help.CommandHelp;
|
||||
import me.bobert.netneutrality.tasks.help.ReportBug;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import net.dv8tion.jda.api.components.actionrow.ActionRow;
|
||||
import net.dv8tion.jda.api.components.buttons.Button;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
@@ -26,6 +27,18 @@ import java.util.List;
|
||||
|
||||
public class HelpCommand extends AbstractCommand {
|
||||
|
||||
private final CommandRegistry commands;
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public HelpCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public HelpCommand(CommandRegistry commands, TaskManager taskManager) {
|
||||
this.commands = commands;
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needAdmin() {
|
||||
return false;
|
||||
@@ -65,15 +78,15 @@ public class HelpCommand extends AbstractCommand {
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
String command = context.getOption("command", null, CommandOption::getAsString);
|
||||
if (command == null) {
|
||||
Button commands = Task.createButton(CommandHelp.class);
|
||||
Button info = Task.createButton(BotInfo.class);
|
||||
Button reportbug = Task.createButton(ReportBug.class);
|
||||
ActionRow row = ActionRow.of(commands, info, reportbug);
|
||||
Button commandsBtn = createTaskButton(CommandHelp.class);
|
||||
Button info = createTaskButton(BotInfo.class);
|
||||
Button reportbug = createTaskButton(ReportBug.class);
|
||||
ActionRow row = ActionRow.of(commandsBtn, info, reportbug);
|
||||
context.replyEphemeral("Thanks for using the Net Neutrality Bot! What do you need help with?", List.of(row));
|
||||
return CommandExecutionResult.none();
|
||||
}
|
||||
|
||||
CommandContainer container = CommandManager.getCommandContainer(command);
|
||||
CommandContainer container = commands.getCommandContainer(command);
|
||||
if (container.getCommand() == null) {
|
||||
return CommandExecutionResult.warn("Command does not exist");
|
||||
}
|
||||
@@ -81,4 +94,12 @@ public class HelpCommand extends AbstractCommand {
|
||||
context.replyEphemeral(MessageFunction.buildCommandHelpMessage(container));
|
||||
return CommandExecutionResult.none();
|
||||
}
|
||||
|
||||
private Button createTaskButton(Class<? extends WithButton> clazz) {
|
||||
WithButton withButton = taskManager.findButtonTaskByClass(clazz);
|
||||
if (withButton == null) {
|
||||
throw new IllegalArgumentException("Task " + clazz.getName() + " was not registered by the task manager.");
|
||||
}
|
||||
return withButton.createButton();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.command.bot;
|
||||
package me.bobert.netneutrality.command.basic;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
@@ -12,7 +12,6 @@ import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.db.GSettings;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
|
||||
@@ -22,9 +21,24 @@ import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
|
||||
public class ConfigureCommand extends AdminCommand {
|
||||
|
||||
private final GuildServiceProvider guildServices;
|
||||
private final MusicService musicService;
|
||||
|
||||
public ConfigureCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ConfigureCommand(GuildServiceProvider guildServices, MusicService musicService) {
|
||||
this.guildServices = guildServices;
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -63,7 +77,7 @@ public class ConfigureCommand extends AdminCommand {
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
String sub = context.getSubcommand();
|
||||
GuildService guildService = GuildService.get(context.getGuild().getId());
|
||||
GuildService guildService = guildServices.get(context.getGuild().getId());
|
||||
context.ephemeral();
|
||||
|
||||
if ("music-role".equals(sub)) {
|
||||
@@ -105,7 +119,7 @@ public class ConfigureCommand extends AdminCommand {
|
||||
if ("music-volume".equals(sub)) {
|
||||
int volume = context.getOption("volume", 30, CommandOption::getAsInt);
|
||||
guildService.getGuildSettingService().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume));
|
||||
MusicManager.get(context.getGuild()).setVolume(volume);
|
||||
musicService.get(context.getGuild()).setVolume(volume);
|
||||
|
||||
return CommandExecutionResult.success(String.format("Default volume for music player is now %d", volume));
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
*.java
|
||||
**/*.java
|
||||
@@ -12,11 +12,24 @@ import me.bobert.netneutrality.core.command.impl.AbstractCommand;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.http.WebModule;
|
||||
import me.bobert.netneutrality.tasks.git.ReportIssueTask;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
|
||||
public class ReportIssueCommand extends AbstractCommand {
|
||||
|
||||
private final SettingsProvider settings;
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public ReportIssueCommand(SettingsProvider settings, TaskManager taskManager) {
|
||||
Objects.requireNonNull(settings, "SettingsProvider must not be null");
|
||||
Objects.requireNonNull(taskManager, "TaskManager must not be null");
|
||||
this.settings = settings;
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needAdmin() {
|
||||
return false;
|
||||
@@ -59,16 +72,16 @@ public class ReportIssueCommand extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
String gitlabHost = Settings.getInstance().get("GITLAB_HOST");
|
||||
String gitlabToken = Settings.getInstance().get("GITLAB_TOKEN");
|
||||
String projectId = Settings.getInstance().get("GITLAB_PROJECT_ID");
|
||||
String gitlabHost = settings.get("GITLAB_HOST");
|
||||
String gitlabToken = settings.get("GITLAB_TOKEN");
|
||||
String projectId = settings.get("GITLAB_PROJECT_ID");
|
||||
|
||||
if (gitlabHost == null || gitlabToken == null || projectId == null) {
|
||||
return CommandExecutionResult.error("Command is not configured, failed to create issue.");
|
||||
}
|
||||
|
||||
if (new WebModule.Builder(gitlabHost).ping().timeout(2, TimeUnit.SECONDS).build().request().isSuccess()) {
|
||||
return CommandExecutionResult.task(new ReportIssueTask(context));
|
||||
return CommandExecutionResult.task(new ReportIssueTask(taskManager, context, settings));
|
||||
}
|
||||
|
||||
return CommandExecutionResult.error("Unable to create bug reports at this time, please try again later.");
|
||||
|
||||
@@ -10,11 +10,28 @@ import me.bobert.netneutrality.core.command.CommandCategory;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
|
||||
public class BitconnectCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public BitconnectCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public BitconnectCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public BitconnectCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -42,7 +59,7 @@ public class BitconnectCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
context.ephemeral();
|
||||
context.deleteMessage();
|
||||
|
||||
|
||||
@@ -9,12 +9,29 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
public class ClearQueueCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public ClearQueueCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ClearQueueCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public ClearQueueCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needAdmin() {
|
||||
return true;
|
||||
@@ -42,10 +59,10 @@ public class ClearQueueCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
context.ack();
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
return manager.getQueueManager().clearQueue();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,30 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
public class CurrentlyPlayingCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public CurrentlyPlayingCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public CurrentlyPlayingCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public CurrentlyPlayingCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -38,10 +55,10 @@ public class CurrentlyPlayingCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
context.ack();
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
if (!manager.isCurrentlyPlaying()) {
|
||||
return CommandExecutionResult.info("Bot isn't playing any tracks right now");
|
||||
}
|
||||
|
||||
@@ -9,11 +9,29 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
public class JoinCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public JoinCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public JoinCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public JoinCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -36,7 +54,9 @@ public class JoinCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
if (MusicUtils.connect(context)) {
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
|
||||
if (MusicUtils.connect(context, manager)) {
|
||||
context.reply("Joining current voice channel");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,35 @@ import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
/**
|
||||
* Leaves the voice channel for the current guild.
|
||||
*
|
||||
* <p>Representative command migrated to constructor injection of {@link MusicService} (and optional
|
||||
* {@link GuildServiceProvider} for the {@link MusicCommand} base). The no-arg constructor remains as
|
||||
* a fallback for non-DI loaders.
|
||||
*/
|
||||
public class LeaveCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public LeaveCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public LeaveCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public LeaveCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -37,9 +61,9 @@ public class LeaveCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
context.reply("Leaving voice channel and clearing queue");
|
||||
manager.disconnect();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
@@ -16,6 +18,21 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
|
||||
public class PlayCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public PlayCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public PlayCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public PlayCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -38,7 +55,7 @@ public class PlayCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
String query = context.getOption("query", CommandOption::getAsString);
|
||||
context.ack();
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
@@ -20,6 +22,21 @@ import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
|
||||
public class QueueCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public QueueCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public QueueCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public QueueCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -58,11 +75,11 @@ public class QueueCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
String sub = context.getSubcommand();
|
||||
context.ack();
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
|
||||
switch (sub) {
|
||||
case "clear-all" -> {
|
||||
|
||||
@@ -4,14 +4,37 @@ package me.bobert.netneutrality.command.music;/*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
public class ResumeCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public ResumeCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ResumeCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public ResumeCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -34,9 +57,9 @@ public class ResumeCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
return manager.resume();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,28 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
public class ShuffleCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public ShuffleCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ShuffleCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public ShuffleCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -36,10 +53,10 @@ public class ShuffleCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
context.ack();
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
return manager.getQueueManager().shuffle();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,33 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import me.bobert.netneutrality.utils.UserFunction;
|
||||
import me.bobert.netneutrality.shared.util.UserFunction;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
|
||||
public class SkipCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public SkipCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public SkipCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public SkipCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -42,12 +59,12 @@ public class SkipCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
context.ack();
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
int skips = context.getOption("skip", 1, CommandOption::getAsInt);
|
||||
if (UserFunction.isAdmin(context) || MusicUtils.isUserDj(context)) {
|
||||
if (UserFunction.isAdmin(context) || MusicUtils.isUserDj(context, manager)) {
|
||||
return manager.getQueueManager().skip(skips);
|
||||
} else {
|
||||
return manager.voteSkip(context);
|
||||
|
||||
@@ -9,13 +9,30 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import me.bobert.netneutrality.utils.UserFunction;
|
||||
import me.bobert.netneutrality.shared.util.UserFunction;
|
||||
|
||||
public class StopCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public StopCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public StopCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public StopCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -38,10 +55,10 @@ public class StopCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
context.ack();
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
if (UserFunction.isAdmin(context)) {
|
||||
manager.stopTrack();
|
||||
context.reply("Stopping current track");
|
||||
|
||||
@@ -8,13 +8,30 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
public class ViewQueueCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public ViewQueueCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ViewQueueCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public ViewQueueCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -37,10 +54,10 @@ public class ViewQueueCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
context.ack();
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
int queueSize = manager.getQueueSize();
|
||||
if (queueSize == 0) {
|
||||
return CommandExecutionResult.warn("Queue is empty!");
|
||||
|
||||
@@ -9,16 +9,33 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import me.bobert.netneutrality.utils.UserFunction;
|
||||
import me.bobert.netneutrality.shared.util.UserFunction;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
|
||||
public class VolumeCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public VolumeCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public VolumeCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public VolumeCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -42,11 +59,11 @@ public class VolumeCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
int volume = context.getOption("volume", 30, CommandOption::getAsInt);
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (UserFunction.isAdmin(context) || MusicUtils.isUserDj(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
if (UserFunction.isAdmin(context) || MusicUtils.isUserDj(context, manager)) {
|
||||
manager.setVolume(volume);
|
||||
return CommandExecutionResult.success(String.format("Volume set to %s", volume));
|
||||
}
|
||||
|
||||
@@ -8,12 +8,29 @@ package me.bobert.netneutrality.command.music;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.MusicCommand;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
|
||||
public class VoteSkipCommand extends MusicCommand {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public VoteSkipCommand() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public VoteSkipCommand(MusicService musicService) {
|
||||
this(musicService, null);
|
||||
}
|
||||
|
||||
public VoteSkipCommand(MusicService musicService, GuildServiceProvider guildServices) {
|
||||
super(guildServices);
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
@@ -36,9 +53,9 @@ public class VoteSkipCommand extends MusicCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
MusicManager manager = musicService.get(context.getGuild());
|
||||
|
||||
if (MusicUtils.checkIfConnected(context)) {
|
||||
if (MusicUtils.checkIfConnected(context, manager)) {
|
||||
return manager.voteSkip(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,22 +9,32 @@ package me.bobert.netneutrality.command.react;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.impl.MessageMenuCommand;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask;
|
||||
import me.bobert.netneutrality.tasks.reaction.EmojiReactionRoleTask;
|
||||
import me.bobert.netneutrality.tasks.reaction.EmojiRoleFirstStep;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class CreateEmojiReaction extends MessageMenuCommand {
|
||||
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public CreateEmojiReaction(TaskManager taskManager) {
|
||||
Objects.requireNonNull(taskManager, "TaskManager must not be null");
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(Message target, CommandContext context) {
|
||||
EmojiReactionRoleTask task = TaskManager.getInstance().findTaskByPartialId(EmojiReactionRoleTask.class, target.getId()).orElse(null);
|
||||
EmojiReactionRoleTask task = taskManager.findTaskByPartialId(EmojiReactionRoleTask.class, target.getId()).orElse(null);
|
||||
|
||||
if (task != null) {
|
||||
context.replyEphemeral("This message is already registered with the bot, please use the modify command instead");
|
||||
return CommandExecutionResult.none();
|
||||
}
|
||||
|
||||
return CommandExecutionResult.task(new EmojiRoleFirstStep(target));
|
||||
return CommandExecutionResult.task(new EmojiRoleFirstStep(taskManager, target));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,22 +9,32 @@ package me.bobert.netneutrality.command.react;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.impl.MessageMenuCommand;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask;
|
||||
import me.bobert.netneutrality.tasks.reaction.EmojiReactionRoleTask;
|
||||
import me.bobert.netneutrality.tasks.reaction.EmojiRoleModify;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ModifyEmojiReaction extends MessageMenuCommand {
|
||||
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public ModifyEmojiReaction(TaskManager taskManager) {
|
||||
Objects.requireNonNull(taskManager, "TaskManager must not be null");
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(Message target, CommandContext context) {
|
||||
EmojiReactionRoleTask task = TaskManager.getInstance().findTaskByPartialId(EmojiReactionRoleTask.class, target.getId()).orElse(null);
|
||||
EmojiReactionRoleTask task = taskManager.findTaskByPartialId(EmojiReactionRoleTask.class, target.getId()).orElse(null);
|
||||
if (task == null) {
|
||||
context.replyEphemeral("This message is not registered with the bot, please use the create command instead");
|
||||
return CommandExecutionResult.none();
|
||||
}
|
||||
|
||||
return CommandExecutionResult.task(new EmojiRoleModify(target, task));
|
||||
return CommandExecutionResult.task(new EmojiRoleModify(taskManager, target, task));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.context;
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.context.AbstractContext;
|
||||
import me.bobert.netneutrality.core.context.AsyncContext;
|
||||
import me.bobert.netneutrality.core.context.NoMessageContext;
|
||||
import net.dv8tion.jda.api.components.MessageTopLevelComponent;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
@@ -9,7 +9,7 @@ import me.bobert.netneutrality.core.command.options.CommandOption;
|
||||
import me.bobert.netneutrality.core.command.options.MessageCommandOption;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.MessageContext;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Mentions;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
@@ -30,8 +30,11 @@ public class MessageCommandContext extends CommandContext implements MessageCont
|
||||
private final MessageReceivedEvent event;
|
||||
private final Message message;
|
||||
|
||||
public MessageCommandContext(MessageReceivedEvent event) {
|
||||
private final SettingsProvider settings;
|
||||
|
||||
public MessageCommandContext(MessageReceivedEvent event, SettingsProvider settings) {
|
||||
super(event.getJDA(), event.isFromGuild() ? event.getGuild() : null, event.getChannel(), event.getMember(), event.getAuthor());
|
||||
this.settings = settings;
|
||||
this.event = event;
|
||||
this.message = event.getMessage();
|
||||
}
|
||||
@@ -320,7 +323,7 @@ public class MessageCommandContext extends CommandContext implements MessageCont
|
||||
@Override
|
||||
public String parseCommandName() {
|
||||
String rawMessage = message.getContentRaw().trim();
|
||||
String prefix = Settings.getInstance().getPrefix();
|
||||
String prefix = settings.getPrefix();
|
||||
|
||||
if (rawMessage.startsWith(prefix)) {
|
||||
return getFullCommand().split(" ", 2)[0];
|
||||
@@ -332,7 +335,7 @@ public class MessageCommandContext extends CommandContext implements MessageCont
|
||||
@Override
|
||||
public String getFullCommand() {
|
||||
String rawMessage = message.getContentRaw().trim();
|
||||
String prefix = Settings.getInstance().getPrefix();
|
||||
String prefix = settings.getPrefix();
|
||||
|
||||
return rawMessage.replaceFirst("^[" + prefix + "\\s]+", "").trim();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ package me.bobert.netneutrality.context.tasks;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.context.AsyncContext;
|
||||
import me.bobert.netneutrality.core.context.AsyncContext;
|
||||
import me.bobert.netneutrality.core.context.NoMessageContext;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import net.dv8tion.jda.api.components.MessageTopLevelComponent;
|
||||
|
||||
@@ -9,9 +9,9 @@ import io.sentry.Sentry;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.Context;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.exceptions.ContextException;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -20,6 +20,17 @@ import org.apache.logging.log4j.Logger;
|
||||
public class LogHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LogHandler.class);
|
||||
private static volatile GuildServiceProvider guildServices;
|
||||
|
||||
/**
|
||||
* Binds the guild-service provider used for debug-channel logging. Called once during bootstrap.
|
||||
*
|
||||
* @param provider the live guild service provider
|
||||
*/
|
||||
public static void bind(GuildServiceProvider provider) {
|
||||
guildServices = provider;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log an exception accordingly
|
||||
@@ -80,7 +91,8 @@ public class LogHandler {
|
||||
Context context = exception.getContext();
|
||||
|
||||
if (context.getGuild() != null) {
|
||||
if (GuildService.get(context.getGuild()).isDebugEnabled()) {
|
||||
GuildServiceProvider provider = guildServices;
|
||||
if (provider != null && provider.get(context.getGuild()).isDebugEnabled()) {
|
||||
context.getGuild()
|
||||
.getTextChannels()
|
||||
.stream()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.bobert.netneutrality.core;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
|
||||
public class NetNeutralityBot implements Lifecycle {
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ package me.bobert.netneutrality.core.command;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.utils.ReflectionHelper;
|
||||
import me.bobert.netneutrality.bootstrap.ApplicationContext;
|
||||
import me.bobert.netneutrality.bootstrap.ReflectionHelper;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.interactions.InteractionContextType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
@@ -17,15 +18,43 @@ import org.apache.logging.log4j.Logger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class CommandManager {
|
||||
/**
|
||||
* Instance-based {@link CommandRegistry} owning the discovered command map.
|
||||
*/
|
||||
public class CommandManager implements CommandRegistry {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(CommandManager.class);
|
||||
public static final Map<String, CommandContainer> commands = new ConcurrentHashMap<>();
|
||||
|
||||
public static void init() {
|
||||
private final Map<String, CommandContainer> commands = new ConcurrentHashMap<>();
|
||||
private final ApplicationContext context;
|
||||
|
||||
/**
|
||||
* Creates a command manager that can inject context-registered dependencies into reflectively
|
||||
* loaded commands.
|
||||
*
|
||||
* @param context the application context; must not be {@code null}
|
||||
*/
|
||||
public CommandManager(ApplicationContext context) {
|
||||
Objects.requireNonNull(context, "ApplicationContext must not be null");
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
init();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
logger.info("Loading commands..");
|
||||
|
||||
initCommands();
|
||||
@@ -33,12 +62,30 @@ public class CommandManager {
|
||||
logger.info("Loaded {} commands!", commands.size());
|
||||
}
|
||||
|
||||
public static CommandContainer getCommandContainer(String commandString) {
|
||||
return commands.getOrDefault(commandString, CommandContainer.NULL_COMMAND);
|
||||
@Override
|
||||
public CommandContainer getCommandContainer(String name) {
|
||||
return commands.getOrDefault(name, CommandContainer.NULL_COMMAND);
|
||||
}
|
||||
|
||||
public static void initCommands() {
|
||||
ReflectionHelper.loadCommands(Command.class, command -> {
|
||||
@Override
|
||||
public boolean hasCommand(String name) {
|
||||
return commands.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the live command map for call sites (and tests) that still iterate or mutate it
|
||||
* directly. Prefer {@link #hasCommand(String)} / {@link #getCommandContainer(String)} where
|
||||
* possible.
|
||||
*
|
||||
* @return the registered commands keyed by name
|
||||
*/
|
||||
@Override
|
||||
public Map<String, CommandContainer> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public void initCommands() {
|
||||
Consumer<Command> onLoaded = command -> {
|
||||
if (!command.isEnabled()) {
|
||||
logger.info("Skipping command {} as it is disabled", command.getClass().getName());
|
||||
return;
|
||||
@@ -58,10 +105,13 @@ public class CommandManager {
|
||||
if (!commands.containsKey(command.getCommandName())) {
|
||||
commands.put(command.getCommandName(), container);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ReflectionHelper.loadCommands(Command.class, context, onLoaded);
|
||||
}
|
||||
|
||||
public static void registerSlashCommands(JDA jda) {
|
||||
@Override
|
||||
public void registerSlashCommands(JDA jda) {
|
||||
CommandListUpdateAction commandListUpdateAction = jda.updateCommands();
|
||||
List<CommandData> slashCommands = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ import me.bobert.netneutrality.context.command.SlashCommandContext;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.InteractionContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.exceptions.CommandExecutionException;
|
||||
import me.bobert.netneutrality.exceptions.GracefulContextError;
|
||||
import me.bobert.netneutrality.utils.UserFunction;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.shared.util.UserFunction;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData;
|
||||
@@ -24,6 +24,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.util.function.Predicate.not;
|
||||
import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.ERROR;
|
||||
@@ -35,17 +36,21 @@ public class CommandProcessor {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(CommandProcessor.class);
|
||||
private final CommandContext context;
|
||||
private final CommandRegistry commands;
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public CommandProcessor(CommandContext context) {
|
||||
public CommandProcessor(CommandContext context, CommandRegistry commands, TaskManager taskManager) {
|
||||
this.context = context;
|
||||
this.commands = Objects.requireNonNull(commands, "commands");
|
||||
this.taskManager = Objects.requireNonNull(taskManager, "taskManager");
|
||||
}
|
||||
|
||||
public void dispatch() {
|
||||
if (!CommandManager.commands.containsKey(context.parseCommandName())) {
|
||||
if (!commands.hasCommand(context.parseCommandName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
CommandContainer container = CommandManager.getCommandContainer(context.parseCommandName());
|
||||
CommandContainer container = commands.getCommandContainer(context.parseCommandName());
|
||||
Command command = container.getCommand();
|
||||
if (command == null) {
|
||||
throw new IllegalArgumentException("Context has no command");
|
||||
@@ -98,7 +103,7 @@ public class CommandProcessor {
|
||||
}
|
||||
|
||||
if (result == TASK) {
|
||||
TaskManager.getInstance().insert(context, commandExecutionResult.getTask());
|
||||
taskManager.insert(context, commandExecutionResult.getTask());
|
||||
}
|
||||
|
||||
String message = commandExecutionResult.getMessage();
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package me.bobert.netneutrality.core.command;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Owns the bot command registry and slash-command registration against Discord.
|
||||
*
|
||||
* <p>As a {@link Lifecycle} component it discovers and loads commands on {@link #start()}.
|
||||
*/
|
||||
public interface CommandRegistry extends Lifecycle {
|
||||
|
||||
/**
|
||||
* Returns the container for the named command, or a null-object container if unknown.
|
||||
*
|
||||
* @param name the command name
|
||||
* @return the command container
|
||||
*/
|
||||
CommandContainer getCommandContainer(String name);
|
||||
|
||||
/**
|
||||
* Whether a command with the given name is registered.
|
||||
*
|
||||
* @param name the command name
|
||||
* @return {@code true} if registered
|
||||
*/
|
||||
boolean hasCommand(String name);
|
||||
|
||||
/**
|
||||
* Pushes every enabled slash command up to Discord via the given JDA instance.
|
||||
*
|
||||
* @param jda the connected JDA client
|
||||
*/
|
||||
void registerSlashCommands(JDA jda);
|
||||
|
||||
/**
|
||||
* Returns the live command map keyed by command name.
|
||||
*
|
||||
* @return the registered commands
|
||||
*/
|
||||
Map<String, CommandContainer> getCommands();
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ package me.bobert.netneutrality.core.command.impl;
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.command.CommandCategory;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.db.GSettings;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.utils.UserFunction;
|
||||
import me.bobert.netneutrality.shared.util.UserFunction;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
@@ -16,8 +17,45 @@ import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class MusicCommand extends AbstractCommand {
|
||||
|
||||
/**
|
||||
* Injected guild-service provider when constructed via DI.
|
||||
*/
|
||||
private final GuildServiceProvider guildServices;
|
||||
|
||||
/**
|
||||
* Preferred constructor for DI.
|
||||
*
|
||||
* @param guildServices the shared guild service provider, must not be {@code null}
|
||||
*/
|
||||
public MusicCommand(GuildServiceProvider guildServices) {
|
||||
Objects.requireNonNull(guildServices, "GuildServiceProvider must not be null");
|
||||
this.guildServices = guildServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link GuildService} for a guild via the injected provider.
|
||||
*
|
||||
* @param guild the guild
|
||||
* @return the guild service
|
||||
*/
|
||||
protected final GuildService guildService(Guild guild) {
|
||||
return guildServices.get(guild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link GuildService} for a guild id via the injected provider.
|
||||
*
|
||||
* @param guildId the guild id
|
||||
* @return the guild service
|
||||
*/
|
||||
protected final GuildService guildService(String guildId) {
|
||||
return guildServices.get(guildId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needAdmin() {
|
||||
return false;
|
||||
@@ -41,7 +79,7 @@ public abstract class MusicCommand extends AbstractCommand {
|
||||
|
||||
Guild guild = member.getGuild();
|
||||
|
||||
String roleDb = GuildService.get(guild.getId()).getGuildSettingService().getSetting(GSettings.MUSIC_ROLE);
|
||||
String roleDb = guildService(guild.getId()).getGuildSettingService().getSetting(GSettings.MUSIC_ROLE);
|
||||
|
||||
if (roleDb != null && !roleDb.isEmpty()) {
|
||||
if (UserFunction.isAdmin(member)) {
|
||||
@@ -51,7 +89,7 @@ public abstract class MusicCommand extends AbstractCommand {
|
||||
Role neededRole = guild.getRoleById(roleDb);
|
||||
if (neededRole == null) {
|
||||
// Role doesn't exist on guild anymore, remove from db
|
||||
GuildService.get(guild.getId()).getGuildSettingService().setSettingValue(GSettings.MUSIC_ROLE, null);
|
||||
guildService(guild.getId()).getGuildSettingService().setSettingValue(GSettings.MUSIC_ROLE, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,7 +106,7 @@ public abstract class MusicCommand extends AbstractCommand {
|
||||
return false;
|
||||
}
|
||||
|
||||
String channelDb = GuildService.get(guildMessageChannel.getGuild()).getGuildSettingService().getSetting(GSettings.MUSIC_CHANNEL);
|
||||
String channelDb = guildService(guildMessageChannel.getGuild()).getGuildSettingService().getSetting(GSettings.MUSIC_CHANNEL);
|
||||
if (channelDb != null && !channelDb.isEmpty()) {
|
||||
return channelDb.equals(channel.getId());
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,11 +1,10 @@
|
||||
package me.bobert.netneutrality.context;
|
||||
package me.bobert.netneutrality.core.context;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.context.AbstractContext;
|
||||
import net.dv8tion.jda.api.components.MessageTopLevelComponent;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
@@ -5,8 +5,7 @@ package me.bobert.netneutrality.core.context;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.context.AsyncContext;
|
||||
import me.bobert.netneutrality.utils.ContextUtils;
|
||||
import me.bobert.netneutrality.shared.util.ContextUtils;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.components.MessageTopLevelComponent;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
@@ -5,7 +5,6 @@ package me.bobert.netneutrality.core.context;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.context.AsyncContext;
|
||||
import net.dv8tion.jda.api.components.MessageTopLevelComponent;
|
||||
import net.dv8tion.jda.api.interactions.Interaction;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
|
||||
@@ -6,7 +6,6 @@ package me.bobert.netneutrality.core.context;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.context.AsyncContext;
|
||||
import net.dv8tion.jda.api.components.MessageTopLevelComponent;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
|
||||
@@ -6,7 +6,6 @@ package me.bobert.netneutrality.core.context;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.context.AsyncContext;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import net.dv8tion.jda.api.modals.Modal;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package me.bobert.netneutrality.core.db;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import io.sentry.Sentry;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.Metadata;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SessionManager {
|
||||
private static final Logger logger = LogManager.getLogger(SessionManager.class);
|
||||
private static StandardServiceRegistry registry;
|
||||
private static SessionFactory sessionFactory;
|
||||
|
||||
public static SessionFactory getSessionFactory() {
|
||||
if (sessionFactory == null) {
|
||||
try {
|
||||
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder().configure();
|
||||
Map<String, Object> settings = new HashMap<>();
|
||||
String dbName = Settings.getInstance().get("MYSQL_DATABASE", "netneutrality");
|
||||
String dbUser = Settings.getInstance().get("MYSQL_USER", "root");
|
||||
String dbPass = Settings.getInstance().get("MYSQL_PASSWORD", "");
|
||||
String dbHost = Settings.getInstance().get("MYSQL_HOST", "localhost");
|
||||
String dbPort = Settings.getInstance().get("MYSQL_PORT", "3306");
|
||||
logger.info("Connecting to database '{}' at {}:{} with user '{}'", dbName, dbHost, dbPort, dbUser);
|
||||
|
||||
settings.put(Environment.JAKARTA_JDBC_URL, String.format("jdbc:mysql://%s:%s/%s", dbHost, dbPort, dbName));
|
||||
settings.put(Environment.JAKARTA_JDBC_USER, dbUser);
|
||||
settings.put(Environment.JAKARTA_JDBC_PASSWORD, dbPass);
|
||||
registryBuilder.applySettings(settings);
|
||||
|
||||
registry = registryBuilder.build();
|
||||
MetadataSources sources = new MetadataSources(registry);
|
||||
Metadata metadata = sources.getMetadataBuilder().build();
|
||||
sessionFactory = metadata.getSessionFactoryBuilder().build();
|
||||
|
||||
} catch (Exception e) {
|
||||
String env = Settings.getInstance().get("NETNEUTRALITY_BOT_ENV", "dev");
|
||||
if (!"dev".equals(env)) {
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
|
||||
shutdown();
|
||||
logger.error("Error connecting to database. Shutting down..");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return sessionFactory;
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
if (registry != null) {
|
||||
StandardServiceRegistryBuilder.destroy(registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,14 +79,4 @@ public interface Task {
|
||||
Lifecycle execute(TaskContext context);
|
||||
|
||||
void insert(CommandContext context);
|
||||
|
||||
static Button createButton(Class<? extends WithButton> clazz) {
|
||||
WithButton withButton = TaskManager.getInstance().findButtonTaskByClass(clazz);
|
||||
|
||||
if (withButton == null) {
|
||||
throw new IllegalArgumentException("Task " + clazz.getName() + " was not registered by the task manager.");
|
||||
}
|
||||
|
||||
return withButton.createButton();
|
||||
}
|
||||
}
|
||||
+17
-10
@@ -1,22 +1,31 @@
|
||||
package me.bobert.netneutrality.tasks.scheduled;/*
|
||||
package me.bobert.netneutrality.core.tasks;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.tasks.BuiltInTask;
|
||||
import me.bobert.netneutrality.core.tasks.PersistableTask;
|
||||
import me.bobert.netneutrality.core.tasks.ScheduledTask;
|
||||
import me.bobert.netneutrality.core.tasks.Task;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Periodically removes expired in-memory tasks.
|
||||
*
|
||||
* <p>Scheduled task migrated to constructor injection of {@link TaskManager}.
|
||||
*/
|
||||
public class TaskCleanup implements ScheduledTask {
|
||||
private static final Logger logger = LogManager.getLogger(TaskCleanup.class);
|
||||
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public TaskCleanup(TaskManager taskManager) {
|
||||
Objects.requireNonNull(taskManager, "TaskManager must not be null");
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "TaskCleanup";
|
||||
@@ -29,9 +38,7 @@ public class TaskCleanup implements ScheduledTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
TaskManager manager = TaskManager.getInstance();
|
||||
|
||||
List<Task> expiredTasks = manager.getTasks()
|
||||
List<Task> expiredTasks = taskManager.getTasks()
|
||||
.stream()
|
||||
.filter(task -> !(task instanceof PersistableTask))
|
||||
.filter(task -> !(task instanceof BuiltInTask))
|
||||
@@ -40,7 +47,7 @@ public class TaskCleanup implements ScheduledTask {
|
||||
|
||||
if (!expiredTasks.isEmpty()) {
|
||||
logger.info("Cleaning up {} expired tasks..", expiredTasks.size());
|
||||
expiredTasks.forEach(manager::removeTask);
|
||||
expiredTasks.forEach(taskManager::removeTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,321 +1,120 @@
|
||||
package me.bobert.netneutrality.core.tasks;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.InteractionContext;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.trait.Deletable;
|
||||
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithParent;
|
||||
import me.bobert.netneutrality.core.thread.ScheduledTaskManager;
|
||||
import me.bobert.netneutrality.db.service.TaskService;
|
||||
import me.bobert.netneutrality.exceptions.GracefulContextError;
|
||||
import me.bobert.netneutrality.exceptions.TaskExecutionException;
|
||||
import me.bobert.netneutrality.utils.ReflectionHelper;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.db.service.TaskService;
|
||||
|
||||
public class TaskManager {
|
||||
private static final Logger logger = LogManager.getLogger(TaskManager.class);
|
||||
private static TaskManager instance;
|
||||
private final TaskService taskService;
|
||||
private final Map<String, Task> tasks;
|
||||
private final List<Task> pendingTasks;
|
||||
private final Map<String, CommandContext> deletableTasksContext;
|
||||
private final Map<String, ReentrantLock> tasksLock;
|
||||
/**
|
||||
* Owns the in-memory task registry, button/interaction dispatch, and persistable-task loading.
|
||||
*
|
||||
* <p>As a {@link Lifecycle} component it loads built-in, scheduled and persisted tasks on
|
||||
* {@link #start()}.
|
||||
*/
|
||||
public interface TaskManager extends Lifecycle {
|
||||
|
||||
private TaskManager() {
|
||||
this.tasks = new ConcurrentHashMap<>();
|
||||
this.pendingTasks = new CopyOnWriteArrayList<>();
|
||||
this.deletableTasksContext = new ConcurrentHashMap<>();
|
||||
this.tasksLock = new ConcurrentHashMap<>();
|
||||
this.taskService = new TaskService();
|
||||
}
|
||||
/**
|
||||
* Inserts a newly created task into the registry (and persists it when applicable).
|
||||
*
|
||||
* @param ctx the command context that produced the task
|
||||
* @param task the task to insert
|
||||
*/
|
||||
void insert(CommandContext ctx, Task task);
|
||||
|
||||
public static TaskManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new TaskManager();
|
||||
}
|
||||
/**
|
||||
* Looks up a task by its full id.
|
||||
*
|
||||
* @param id the task id
|
||||
* @return the task, or {@code null} if unknown
|
||||
*/
|
||||
Task getTask(String id);
|
||||
|
||||
return instance;
|
||||
}
|
||||
/**
|
||||
* Whether a task with the given id is registered or pending activation.
|
||||
*
|
||||
* @param id the task id
|
||||
* @return {@code true} if present
|
||||
*/
|
||||
boolean hasTask(String id);
|
||||
|
||||
public void init() {
|
||||
logger.info("Initializing task manager...");
|
||||
/**
|
||||
* Dispatches an interaction/context event to the matching task.
|
||||
*
|
||||
* @param ctx the task context
|
||||
*/
|
||||
void handle(TaskContext ctx);
|
||||
|
||||
loadBuiltInTasks();
|
||||
loadScheduledTasks();
|
||||
taskService.loadTasks();
|
||||
}
|
||||
/**
|
||||
* Promotes a pending step-task into the active registry under its (possibly new) id.
|
||||
*
|
||||
* @param task the task to activate
|
||||
* @param oldId the previous id used while the task was pending
|
||||
*/
|
||||
void activateTask(Task task, String oldId);
|
||||
|
||||
public TaskService getTaskService() {
|
||||
return taskService;
|
||||
}
|
||||
/**
|
||||
* Removes a task from the active registry.
|
||||
*
|
||||
* @param task the task to remove
|
||||
*/
|
||||
void removeTask(Task task);
|
||||
|
||||
public Task getTask(String id) {
|
||||
return tasks.get(id);
|
||||
}
|
||||
/**
|
||||
* Inserts a previously persisted task back into the active registry.
|
||||
*
|
||||
* @param task the persistable task
|
||||
*/
|
||||
void insertPersistableTask(PersistableTask task);
|
||||
|
||||
public boolean hasTask(String id) {
|
||||
return tasks.containsKey(id) || pendingTasks.stream().anyMatch(task -> task.getId().equals(id));
|
||||
}
|
||||
/**
|
||||
* Returns every currently active task.
|
||||
*
|
||||
* @return the active tasks
|
||||
*/
|
||||
Collection<Task> getTasks();
|
||||
|
||||
public synchronized Collection<Task> getTasks() {
|
||||
return tasks.values();
|
||||
}
|
||||
/**
|
||||
* Finds the first task of the given type whose id contains {@code partialId}.
|
||||
*
|
||||
* @param clazz the expected task type
|
||||
* @param partialId a fragment of the task id
|
||||
* @param <E> the task type
|
||||
* @return the matching task, if any
|
||||
*/
|
||||
<E extends Task> Optional<E> findTaskByPartialId(Class<E> clazz, String partialId);
|
||||
|
||||
public void insert(CommandContext context, Task task) {
|
||||
String id = task.getId();
|
||||
/**
|
||||
* Finds every task of the given type whose id contains {@code partialId}.
|
||||
*
|
||||
* @param clazz the expected task type
|
||||
* @param partialId a fragment of the task id
|
||||
* @param <E> the task type
|
||||
* @return the matching tasks
|
||||
*/
|
||||
<E extends Task> List<E> findTasksByPartialId(Class<E> clazz, String partialId);
|
||||
|
||||
if (hasTask(id)) {
|
||||
context.reply("A task with the same ID already exists!");
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Finds the first button-bearing task of the given class.
|
||||
*
|
||||
* @param clazz the expected button-task type
|
||||
* @return the matching task, or {@code null}
|
||||
*/
|
||||
WithButton findButtonTaskByClass(Class<? extends WithButton> clazz);
|
||||
|
||||
task.replyToInteraction(context);
|
||||
|
||||
if (task.isInsertable()) {
|
||||
logger.info("Inserting task: {}", task);
|
||||
|
||||
if (task instanceof PersistableTask persistableTask) {
|
||||
taskService.persistTask(persistableTask);
|
||||
}
|
||||
|
||||
if (task instanceof StepTask) {
|
||||
pendingTasks.add(task);
|
||||
} else {
|
||||
tasks.put(id, task);
|
||||
}
|
||||
}
|
||||
|
||||
if (task instanceof Deletable) {
|
||||
deletableTasksContext.put(task.getId(), context);
|
||||
}
|
||||
|
||||
if (task instanceof WithChildren taskWithChildren) {
|
||||
List<Task> childrens = taskWithChildren.getChildTasks();
|
||||
if (childrens == null || childrens.isEmpty()) {
|
||||
logger.warn("Parent task '{}' doesn't create any childs", task.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
for (Task child : childrens) {
|
||||
insert(context, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void activateTask(Task task, String oldId) {
|
||||
if (pendingTasks.remove(task)) {
|
||||
tasks.put(task.getId(), task);
|
||||
logger.info("Updating task {} with new id: {} (was: {})",
|
||||
task.getName(), task.getId(), oldId);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeTask(Task task) {
|
||||
String id = task.getId();
|
||||
|
||||
if (tasks.remove(id) != null) {
|
||||
logger.debug("Removing task: {}", id);
|
||||
|
||||
ReentrantLock lock = tasksLock.remove(task.getLockId());
|
||||
if (lock != null) {
|
||||
logger.debug("Cleaning up lock for removed task: {}", task.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handle(TaskContext context) {
|
||||
String taskId = context.getId();
|
||||
Task task = getTask(taskId);
|
||||
|
||||
if (task == null && context instanceof InteractionContext) {
|
||||
context.replyEphemeral("This task is no longer available, someone might have already used it or it might have expired");
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
String lockId = task.getLockId();
|
||||
ReentrantLock lock = tasksLock.computeIfAbsent(lockId, k -> {
|
||||
if (task instanceof PersistableTask || task instanceof BuiltInTask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ReentrantLock();
|
||||
});
|
||||
|
||||
try {
|
||||
if (lock != null) {
|
||||
logger.debug("Aquiring lock for task: {}", taskId);
|
||||
lock.lock();
|
||||
|
||||
if (!tasks.containsKey(taskId)) {
|
||||
logger.debug("Task {} is no longer available after aquiring lock", taskId);
|
||||
tasksLock.remove(lockId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Executing task: {}", taskId);
|
||||
Task.Lifecycle status = task.execute(context);
|
||||
handleTaskLifecycle(task, status);
|
||||
|
||||
deleteInteraction(task);
|
||||
|
||||
// If interaction is still not acknowledged after execution, send a placeholder response.
|
||||
if (!context.isAcknowledged() && !(task instanceof NotAcknowledgeable)) {
|
||||
logger.warn("Interaction not acknowledged");
|
||||
context.replyEphemeral("Task executed successfully but interaction wasn't acknowledged, report this bug by using the command /report-bug");
|
||||
}
|
||||
} catch (GracefulContextError exception) {
|
||||
logger.error(exception.getMessage());
|
||||
context.replyEphemeral(exception.getMessage());
|
||||
} catch (TaskExecutionException exception) {
|
||||
LogHandler.log(exception);
|
||||
} catch (Exception exception) {
|
||||
LogHandler.log(new TaskExecutionException("Error during task execution", context, exception));
|
||||
} finally {
|
||||
if (lock != null) {
|
||||
logger.debug("Releasing lock for task: {}", taskId);
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <E extends Task> Optional<E> findTaskByPartialId(Class<E> clazz, String partialId) {
|
||||
List<E> tasks = findTasksByPartialId(clazz, partialId);
|
||||
return Optional.ofNullable(tasks.isEmpty() ? null : tasks.getFirst());
|
||||
}
|
||||
|
||||
public <E extends Task> List<E> findTasksByPartialId(Class<E> clazz, String partialId) {
|
||||
return tasks.values().stream()
|
||||
.filter(clazz::isInstance)
|
||||
.map(clazz::cast)
|
||||
.filter(task -> task.getId().contains(partialId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public WithButton findButtonTaskByClass(Class<? extends WithButton> clazz) {
|
||||
return tasks.values().stream()
|
||||
.filter(clazz::isInstance)
|
||||
.map(task -> (WithButton) task)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public void insertPersistableTask(PersistableTask task) {
|
||||
if (tasks.containsKey(task.getId())) {
|
||||
logger.error("Trying to load persistable task but ID already exists: {}", task.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Loaded persistable task: {}", task);
|
||||
tasks.put(task.getId(), task);
|
||||
}
|
||||
|
||||
private void loadBuiltInTasks() {
|
||||
logger.info("Loading built-in tasks...");
|
||||
|
||||
ReflectionHelper.loadTasks(BuiltInTask.class, builtInTask -> {
|
||||
if (builtInTask.isInsertable()) {
|
||||
logger.info("Loaded task: {}", builtInTask.getClass().getName());
|
||||
tasks.put(builtInTask.getId(), builtInTask);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadScheduledTasks() {
|
||||
logger.info("Loading scheduled tasks...");
|
||||
|
||||
ReflectionHelper.loadTasks(ScheduledTask.class, scheduledTask -> {
|
||||
String taskId = scheduledTask.getId();
|
||||
|
||||
if (taskId == null || taskId.trim().isEmpty()) {
|
||||
logger.error("Cannot load scheduled task {}: task ID is null or empty", scheduledTask.getClass().getName());
|
||||
return;
|
||||
}
|
||||
|
||||
ScheduledTaskManager.schedule(scheduledTask);
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteInteraction(Task task) {
|
||||
if (task instanceof WithParent<?> taskWithParent) {
|
||||
Task parent = taskWithParent.getParentTask();
|
||||
|
||||
if (parent instanceof Deletable deletable && task.isInsertable()) {
|
||||
deleteInteraction(parent.getId(), deletable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (task instanceof Deletable deletable && task.isInsertable()) {
|
||||
deleteInteraction(task.getId(), deletable);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteInteraction(String taskId, Deletable deletable) {
|
||||
logger.debug("Deleting interaction: {}", taskId);
|
||||
|
||||
CommandContext context = deletableTasksContext.get(taskId);
|
||||
if (context != null) {
|
||||
deletableTasksContext.remove(taskId);
|
||||
|
||||
InteractionHook hook = context.getInteractionHook();
|
||||
if (hook != null) {
|
||||
if (hook.isExpired()) {
|
||||
context.replyEphemeral("This interaction has expired, can't remove it");
|
||||
}
|
||||
|
||||
if (!hook.isExpired()) {
|
||||
hook.deleteOriginal().queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletable.onInteractionDelete();
|
||||
}
|
||||
|
||||
private void handleTaskLifecycle(Task task, Task.Lifecycle status) {
|
||||
if (status == null) {
|
||||
throw new IllegalArgumentException("Task lifecycle status cannot be null");
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case REMOVE_SELF:
|
||||
removeTask(task);
|
||||
break;
|
||||
|
||||
case REMOVE_SELF_AND_CHILDREN:
|
||||
if (task instanceof WithChildren taskWithChildren) {
|
||||
for (Task child : taskWithChildren.getChildTasks()) {
|
||||
removeTask(child);
|
||||
}
|
||||
}
|
||||
removeTask(task);
|
||||
break;
|
||||
|
||||
case KEEP:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the persistence service used for loadable tasks.
|
||||
*
|
||||
* @return the task service
|
||||
*/
|
||||
TaskService getTaskService();
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package me.bobert.netneutrality.core.tasks;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.bootstrap.ApplicationContext;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.InteractionContext;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.trait.Deletable;
|
||||
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithParent;
|
||||
import me.bobert.netneutrality.core.thread.ScheduledTaskManager;
|
||||
import me.bobert.netneutrality.db.service.TaskService;
|
||||
import me.bobert.netneutrality.exceptions.GracefulContextError;
|
||||
import me.bobert.netneutrality.exceptions.TaskExecutionException;
|
||||
import me.bobert.netneutrality.bootstrap.ReflectionHelper;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Instance-based {@link TaskManager} owning the in-memory task registry.
|
||||
*/
|
||||
public class TaskManagerImpl implements TaskManager {
|
||||
private static final Logger logger = LogManager.getLogger(TaskManagerImpl.class);
|
||||
private final TaskService taskService;
|
||||
private final Map<String, Task> tasks;
|
||||
private final List<Task> pendingTasks;
|
||||
private final Map<String, CommandContext> deletableTasksContext;
|
||||
private final Map<String, ReentrantLock> tasksLock;
|
||||
private final ApplicationContext context;
|
||||
|
||||
/**
|
||||
* Creates a task manager that can inject context-registered dependencies into reflectively
|
||||
* loaded tasks.
|
||||
*
|
||||
* @param context the application context; must not be {@code null}
|
||||
*/
|
||||
public TaskManagerImpl(ApplicationContext context) {
|
||||
Objects.requireNonNull(context, "ApplicationContext must not be null");
|
||||
this.tasks = new ConcurrentHashMap<>();
|
||||
this.pendingTasks = new CopyOnWriteArrayList<>();
|
||||
this.deletableTasksContext = new ConcurrentHashMap<>();
|
||||
this.tasksLock = new ConcurrentHashMap<>();
|
||||
this.taskService = new TaskService(this);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
init();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
logger.info("Initializing task manager...");
|
||||
|
||||
loadBuiltInTasks();
|
||||
loadScheduledTasks();
|
||||
taskService.loadTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskService getTaskService() {
|
||||
return taskService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task getTask(String id) {
|
||||
return tasks.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTask(String id) {
|
||||
return tasks.containsKey(id) || pendingTasks.stream().anyMatch(task -> task.getId().equals(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Collection<Task> getTasks() {
|
||||
return tasks.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(CommandContext context, Task task) {
|
||||
String id = task.getId();
|
||||
|
||||
if (hasTask(id)) {
|
||||
context.reply("A task with the same ID already exists!");
|
||||
return;
|
||||
}
|
||||
|
||||
task.replyToInteraction(context);
|
||||
|
||||
if (task.isInsertable()) {
|
||||
logger.info("Inserting task: {}", task);
|
||||
|
||||
if (task instanceof PersistableTask persistableTask) {
|
||||
taskService.persistTask(persistableTask);
|
||||
}
|
||||
|
||||
if (task instanceof StepTask) {
|
||||
pendingTasks.add(task);
|
||||
} else {
|
||||
tasks.put(id, task);
|
||||
}
|
||||
}
|
||||
|
||||
if (task instanceof Deletable) {
|
||||
deletableTasksContext.put(task.getId(), context);
|
||||
}
|
||||
|
||||
if (task instanceof WithChildren taskWithChildren) {
|
||||
List<Task> childrens = taskWithChildren.getChildTasks();
|
||||
if (childrens == null || childrens.isEmpty()) {
|
||||
logger.warn("Parent task '{}' doesn't create any childs", task.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
for (Task child : childrens) {
|
||||
insert(context, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateTask(Task task, String oldId) {
|
||||
if (pendingTasks.remove(task)) {
|
||||
tasks.put(task.getId(), task);
|
||||
logger.info("Updating task {} with new id: {} (was: {})",
|
||||
task.getName(), task.getId(), oldId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTask(Task task) {
|
||||
String id = task.getId();
|
||||
|
||||
if (tasks.remove(id) != null) {
|
||||
logger.debug("Removing task: {}", id);
|
||||
|
||||
ReentrantLock lock = tasksLock.remove(task.getLockId());
|
||||
if (lock != null) {
|
||||
logger.debug("Cleaning up lock for removed task: {}", task.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(TaskContext context) {
|
||||
String taskId = context.getId();
|
||||
Task task = getTask(taskId);
|
||||
|
||||
if (task == null && context instanceof InteractionContext) {
|
||||
context.replyEphemeral("This task is no longer available, someone might have already used it or it might have expired");
|
||||
}
|
||||
|
||||
if (task != null) {
|
||||
String lockId = task.getLockId();
|
||||
ReentrantLock lock = tasksLock.computeIfAbsent(lockId, k -> {
|
||||
if (task instanceof PersistableTask || task instanceof BuiltInTask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ReentrantLock();
|
||||
});
|
||||
|
||||
try {
|
||||
if (lock != null) {
|
||||
logger.debug("Aquiring lock for task: {}", taskId);
|
||||
lock.lock();
|
||||
|
||||
if (!tasks.containsKey(taskId)) {
|
||||
logger.debug("Task {} is no longer available after aquiring lock", taskId);
|
||||
tasksLock.remove(lockId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Executing task: {}", taskId);
|
||||
Task.Lifecycle status = task.execute(context);
|
||||
handleTaskLifecycle(task, status);
|
||||
|
||||
deleteInteraction(task);
|
||||
|
||||
// If interaction is still not acknowledged after execution, send a placeholder response.
|
||||
if (!context.isAcknowledged() && !(task instanceof NotAcknowledgeable)) {
|
||||
logger.warn("Interaction not acknowledged");
|
||||
context.replyEphemeral("Task executed successfully but interaction wasn't acknowledged, report this bug by using the command /report-bug");
|
||||
}
|
||||
} catch (GracefulContextError exception) {
|
||||
logger.error(exception.getMessage());
|
||||
context.replyEphemeral(exception.getMessage());
|
||||
} catch (TaskExecutionException exception) {
|
||||
LogHandler.log(exception);
|
||||
} catch (Exception exception) {
|
||||
LogHandler.log(new TaskExecutionException("Error during task execution", context, exception));
|
||||
} finally {
|
||||
if (lock != null) {
|
||||
logger.debug("Releasing lock for task: {}", taskId);
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Task> Optional<E> findTaskByPartialId(Class<E> clazz, String partialId) {
|
||||
List<E> tasks = findTasksByPartialId(clazz, partialId);
|
||||
return Optional.ofNullable(tasks.isEmpty() ? null : tasks.getFirst());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Task> List<E> findTasksByPartialId(Class<E> clazz, String partialId) {
|
||||
return tasks.values().stream()
|
||||
.filter(clazz::isInstance)
|
||||
.map(clazz::cast)
|
||||
.filter(task -> task.getId().contains(partialId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WithButton findButtonTaskByClass(Class<? extends WithButton> clazz) {
|
||||
return tasks.values().stream()
|
||||
.filter(clazz::isInstance)
|
||||
.map(task -> (WithButton) task)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertPersistableTask(PersistableTask task) {
|
||||
if (tasks.containsKey(task.getId())) {
|
||||
logger.error("Trying to load persistable task but ID already exists: {}", task.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Loaded persistable task: {}", task);
|
||||
tasks.put(task.getId(), task);
|
||||
}
|
||||
|
||||
private void loadBuiltInTasks() {
|
||||
logger.info("Loading built-in tasks...");
|
||||
|
||||
Consumer<BuiltInTask> onLoaded = builtInTask -> {
|
||||
if (builtInTask.isInsertable()) {
|
||||
logger.info("Loaded task: {}", builtInTask.getClass().getName());
|
||||
tasks.put(builtInTask.getId(), builtInTask);
|
||||
}
|
||||
};
|
||||
|
||||
ReflectionHelper.loadTasks(BuiltInTask.class, context, onLoaded);
|
||||
}
|
||||
|
||||
private void loadScheduledTasks() {
|
||||
logger.info("Loading scheduled tasks...");
|
||||
|
||||
ScheduledTaskManager scheduledTaskManager = context.get(ScheduledTaskManager.class);
|
||||
if (scheduledTaskManager == null) {
|
||||
logger.error("ScheduledTaskManager is not available; cannot load scheduled tasks");
|
||||
return;
|
||||
}
|
||||
|
||||
Consumer<ScheduledTask> onLoaded = scheduledTask -> {
|
||||
String taskId = scheduledTask.getId();
|
||||
|
||||
if (taskId == null || taskId.trim().isEmpty()) {
|
||||
logger.error("Cannot load scheduled task {}: task ID is null or empty", scheduledTask.getClass().getName());
|
||||
return;
|
||||
}
|
||||
|
||||
scheduledTaskManager.schedule(scheduledTask);
|
||||
};
|
||||
|
||||
ReflectionHelper.loadTasks(ScheduledTask.class, context, onLoaded);
|
||||
}
|
||||
|
||||
private void deleteInteraction(Task task) {
|
||||
if (task instanceof WithParent<?> taskWithParent) {
|
||||
Task parent = taskWithParent.getParentTask();
|
||||
|
||||
if (parent instanceof Deletable deletable && task.isInsertable()) {
|
||||
deleteInteraction(parent.getId(), deletable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (task instanceof Deletable deletable && task.isInsertable()) {
|
||||
deleteInteraction(task.getId(), deletable);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteInteraction(String taskId, Deletable deletable) {
|
||||
logger.debug("Deleting interaction: {}", taskId);
|
||||
|
||||
CommandContext context = deletableTasksContext.get(taskId);
|
||||
if (context != null) {
|
||||
deletableTasksContext.remove(taskId);
|
||||
|
||||
InteractionHook hook = context.getInteractionHook();
|
||||
if (hook != null) {
|
||||
if (hook.isExpired()) {
|
||||
context.replyEphemeral("This interaction has expired, can't remove it");
|
||||
}
|
||||
|
||||
if (!hook.isExpired()) {
|
||||
hook.deleteOriginal().queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletable.onInteractionDelete();
|
||||
}
|
||||
|
||||
private void handleTaskLifecycle(Task task, Task.Lifecycle status) {
|
||||
if (status == null) {
|
||||
throw new IllegalArgumentException("Task lifecycle status cannot be null");
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case REMOVE_SELF:
|
||||
removeTask(task);
|
||||
break;
|
||||
|
||||
case REMOVE_SELF_AND_CHILDREN:
|
||||
if (task instanceof WithChildren taskWithChildren) {
|
||||
for (Task child : taskWithChildren.getChildTasks()) {
|
||||
removeTask(child);
|
||||
}
|
||||
}
|
||||
removeTask(task);
|
||||
break;
|
||||
|
||||
case KEEP:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-11
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.tasks.generic;
|
||||
package me.bobert.netneutrality.core.tasks.generic;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
@@ -7,6 +7,7 @@ package me.bobert.netneutrality.tasks.generic;
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.core.tasks.impl.ButtonTask;
|
||||
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
|
||||
@@ -24,26 +25,26 @@ public class AcceptButton<P extends WithChildren> extends ButtonTask implements
|
||||
private final P parentTask;
|
||||
private final Function<TaskContext, Lifecycle> function;
|
||||
|
||||
protected AcceptButton(P parentTask, ButtonStyle buttonStyle, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
super(buttonStyle, buttonLabel);
|
||||
protected AcceptButton(TaskManager taskManager, P parentTask, ButtonStyle buttonStyle, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
super(taskManager, buttonStyle, buttonLabel);
|
||||
this.parentTask = parentTask;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public static <P extends WithChildren> AcceptButton<P> createPrimaryButton(P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(parentTask, ButtonStyle.PRIMARY, buttonLabel, function);
|
||||
public static <P extends WithChildren> AcceptButton<P> createPrimaryButton(TaskManager taskManager, P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(taskManager, parentTask, ButtonStyle.PRIMARY, buttonLabel, function);
|
||||
}
|
||||
|
||||
public static <P extends WithChildren> AcceptButton<P> createSecondaryButton(P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(parentTask, ButtonStyle.SECONDARY, buttonLabel, function);
|
||||
public static <P extends WithChildren> AcceptButton<P> createSecondaryButton(TaskManager taskManager, P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(taskManager, parentTask, ButtonStyle.SECONDARY, buttonLabel, function);
|
||||
}
|
||||
|
||||
public static <P extends WithChildren> AcceptButton<P> createSuccessButton(P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(parentTask, ButtonStyle.SUCCESS, buttonLabel, function);
|
||||
public static <P extends WithChildren> AcceptButton<P> createSuccessButton(TaskManager taskManager, P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(taskManager, parentTask, ButtonStyle.SUCCESS, buttonLabel, function);
|
||||
}
|
||||
|
||||
public static <P extends WithChildren> AcceptButton<P> createDangerButton(P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(parentTask, ButtonStyle.DANGER, buttonLabel, function);
|
||||
public static <P extends WithChildren> AcceptButton<P> createDangerButton(TaskManager taskManager, P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new AcceptButton<>(taskManager, parentTask, ButtonStyle.DANGER, buttonLabel, function);
|
||||
}
|
||||
|
||||
@Override
|
||||
+6
-5
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.tasks.generic;
|
||||
package me.bobert.netneutrality.core.tasks.generic;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
@@ -7,6 +7,7 @@ package me.bobert.netneutrality.tasks.generic;
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.core.tasks.impl.ButtonTask;
|
||||
import me.bobert.netneutrality.core.tasks.trait.NotAcknowledgeable;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
|
||||
@@ -19,14 +20,14 @@ public class CancelButton<P extends WithChildren> extends ButtonTask implements
|
||||
private final P parentTask;
|
||||
private final Function<TaskContext, Lifecycle> function;
|
||||
|
||||
protected CancelButton(P parentTask, ButtonStyle buttonStyle, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
super(buttonStyle, buttonLabel);
|
||||
protected CancelButton(TaskManager taskManager, P parentTask, ButtonStyle buttonStyle, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
super(taskManager, buttonStyle, buttonLabel);
|
||||
this.parentTask = parentTask;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public static <P extends WithChildren> CancelButton<P> create(P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new CancelButton<>(parentTask, ButtonStyle.DANGER, buttonLabel, function);
|
||||
public static <P extends WithChildren> CancelButton<P> create(TaskManager taskManager, P parentTask, String buttonLabel, Function<TaskContext, Lifecycle> function) {
|
||||
return new CancelButton<>(taskManager, parentTask, ButtonStyle.DANGER, buttonLabel, function);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -8,9 +8,14 @@ package me.bobert.netneutrality.core.tasks.impl;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.BuiltInTask;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
|
||||
public abstract class AbstractBuiltInTask extends AbstractTask implements BuiltInTask {
|
||||
|
||||
public AbstractBuiltInTask(TaskManager taskManager) {
|
||||
super(taskManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getId();
|
||||
|
||||
@@ -12,6 +12,15 @@ import org.json.JSONObject;
|
||||
|
||||
public abstract class AbstractPersistableTask extends AbstractTask implements PersistableTask {
|
||||
|
||||
/**
|
||||
* Preferred constructor for DI.
|
||||
*
|
||||
* @param taskManager the application task manager, must not be {@code null}
|
||||
*/
|
||||
public AbstractPersistableTask(TaskManager taskManager) {
|
||||
super(taskManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInsertable() {
|
||||
return true;
|
||||
@@ -29,26 +38,26 @@ public abstract class AbstractPersistableTask extends AbstractTask implements Pe
|
||||
|
||||
@Override
|
||||
public void insert(CommandContext context) {
|
||||
if (TaskManager.getInstance().hasTask(getId())) {
|
||||
if (taskManager().hasTask(getId())) {
|
||||
throw new RuntimeException("Cannot insert a task that already exists");
|
||||
}
|
||||
|
||||
TaskManager.getInstance().insert(context, this);
|
||||
taskManager().insert(context, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
if (!TaskManager.getInstance().hasTask(getId())) {
|
||||
if (!taskManager().hasTask(getId())) {
|
||||
throw new RuntimeException("Cannot save a task that doesn't exist yet");
|
||||
}
|
||||
|
||||
TaskManager.getInstance().getTaskService().persistTask(this);
|
||||
taskManager().getTaskService().persistTask(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
TaskManager.getInstance().getTaskService().unpersistTask(this);
|
||||
TaskManager.getInstance().removeTask(this);
|
||||
taskManager().getTaskService().unpersistTask(this);
|
||||
taskManager().removeTask(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,6 +31,15 @@ public abstract class AbstractStepTask extends AbstractTask implements StepTask
|
||||
private Guild guild;
|
||||
private String id = UUID.randomUUID().toString();
|
||||
|
||||
/**
|
||||
* Preferred constructor for DI.
|
||||
*
|
||||
* @param taskManager the application task manager, must not be {@code null}
|
||||
*/
|
||||
public AbstractStepTask(TaskManager taskManager) {
|
||||
super(taskManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
@@ -156,7 +165,7 @@ public abstract class AbstractStepTask extends AbstractTask implements StepTask
|
||||
String oldId = id;
|
||||
synchronized (this) {
|
||||
this.id = newId;
|
||||
TaskManager.getInstance().activateTask(this, oldId);
|
||||
taskManager().activateTask(this, oldId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,29 @@ import java.util.Objects;
|
||||
|
||||
public abstract class AbstractTask implements Task {
|
||||
private final OffsetDateTime creationTime;
|
||||
/**
|
||||
* Injected task manager when constructed via DI.
|
||||
*/
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public AbstractTask() {
|
||||
creationTime = OffsetDateTime.now();
|
||||
/**
|
||||
* Preferred constructor for DI: stores the shared {@link TaskManager} for insert/activate/remove.
|
||||
*
|
||||
* @param taskManager the application task manager, must not be {@code null}
|
||||
*/
|
||||
public AbstractTask(TaskManager taskManager) {
|
||||
Objects.requireNonNull(taskManager, "TaskManager must not be null");
|
||||
this.creationTime = OffsetDateTime.now();
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the injected task manager.
|
||||
*
|
||||
* @return the task manager to use
|
||||
*/
|
||||
protected final TaskManager taskManager() {
|
||||
return taskManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -26,7 +46,7 @@ public abstract class AbstractTask implements Task {
|
||||
|
||||
@Override
|
||||
public void insert(CommandContext context) {
|
||||
TaskManager.getInstance().insert(context, this);
|
||||
taskManager().insert(context, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,7 @@ package me.bobert.netneutrality.core.tasks.impl;
|
||||
|
||||
import me.bobert.netneutrality.core.tasks.Task;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import net.dv8tion.jda.api.components.buttons.Button;
|
||||
import net.dv8tion.jda.api.components.buttons.ButtonStyle;
|
||||
import net.dv8tion.jda.api.entities.emoji.Emoji;
|
||||
@@ -18,7 +19,8 @@ public abstract class ButtonTask extends GenericRandomIdTask implements WithButt
|
||||
private final String buttonLabel;
|
||||
private Emoji emoji;
|
||||
|
||||
public ButtonTask(ButtonStyle buttonStyle, String buttonLabel) {
|
||||
public ButtonTask(TaskManager taskManager, ButtonStyle buttonStyle, String buttonLabel) {
|
||||
super(taskManager);
|
||||
this.buttonStyle = buttonStyle;
|
||||
this.buttonLabel = buttonLabel;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.core.tasks.impl;
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.context.tasks.EmojiTaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -16,7 +17,8 @@ public abstract class EmojiTask extends AbstractPersistableTask {
|
||||
private final String channelId;
|
||||
private final String messageId;
|
||||
|
||||
public EmojiTask(String guildId, String channelId, String messageId) {
|
||||
public EmojiTask(TaskManager taskManager, String guildId, String channelId, String messageId) {
|
||||
super(taskManager);
|
||||
this.guildId = guildId;
|
||||
this.channelId = channelId;
|
||||
this.messageId = messageId;
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
package me.bobert.netneutrality.core.tasks.impl;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
public abstract class GenericCustomIdTask extends AbstractTask {
|
||||
private final String customId;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
|
||||
public GenericCustomIdTask(String id) {
|
||||
this.customId = id;
|
||||
public abstract class GenericCustomIdTask extends AbstractTask {
|
||||
private final String id;
|
||||
|
||||
public GenericCustomIdTask(TaskManager taskManager, String id) {
|
||||
super(taskManager);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return customId;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package me.bobert.netneutrality.core.tasks.impl;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class GenericRandomIdTask extends GenericCustomIdTask {
|
||||
public GenericRandomIdTask() {
|
||||
super(UUID.randomUUID().toString());
|
||||
public GenericRandomIdTask(TaskManager taskManager) {
|
||||
super(taskManager, UUID.randomUUID().toString());
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.core.tasks.impl;
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
@@ -14,6 +15,10 @@ import java.util.Optional;
|
||||
|
||||
public abstract class ModalTask extends GenericRandomIdTask {
|
||||
|
||||
public ModalTask(TaskManager taskManager) {
|
||||
super(taskManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return getCreationTime().plusMinutes(5).isBefore(OffsetDateTime.now());
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.bobert.netneutrality.core.thread;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
|
||||
/**
|
||||
* Owns the shared executor services used across the bot.
|
||||
*
|
||||
* <p>As a {@link Lifecycle} component it shuts every pool down on {@link #stop()}.
|
||||
*/
|
||||
public interface ExecutorProvider extends Lifecycle {
|
||||
|
||||
/**
|
||||
* Returns (creating on first use) the named cached-thread-pool executor.
|
||||
*
|
||||
* @param name the executor name
|
||||
* @return the executor service
|
||||
*/
|
||||
ExecutorService getExecutor(String name);
|
||||
|
||||
/**
|
||||
* Returns the general-purpose executor.
|
||||
*
|
||||
* @return the general executor service
|
||||
*/
|
||||
ExecutorService getExecutor();
|
||||
|
||||
/**
|
||||
* Returns the shared scheduled executor.
|
||||
*
|
||||
* @return the scheduled executor service
|
||||
*/
|
||||
ScheduledExecutorService getScheduledExecutor();
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.core.thread;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.tasks.ScheduledTask;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -16,16 +17,38 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
public class ScheduledTaskManager {
|
||||
/**
|
||||
* Instance-based {@link Lifecycle} component that owns scheduled fixed-delay tasks.
|
||||
*/
|
||||
public class ScheduledTaskManager implements Lifecycle {
|
||||
private static final Logger logger = LogManager.getLogger(ScheduledTaskManager.class);
|
||||
private static final ScheduledExecutorService executorService = ThreadManager.getScheduledExecutor();
|
||||
private static final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
|
||||
|
||||
private ScheduledTaskManager() {}
|
||||
private final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService executorService;
|
||||
|
||||
public static void schedule(ScheduledTask task) {
|
||||
public ScheduledTaskManager(ExecutorProvider executors) {
|
||||
this.executorService = executors.getScheduledExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
// High priority number => stopped early on shutdown, before executors (priority 20).
|
||||
return 55;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// Tasks are scheduled by TaskManagerImpl during its own start; nothing to eagerly start.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
cancelAll();
|
||||
}
|
||||
|
||||
public void schedule(ScheduledTask task) {
|
||||
String taskId = task.getId();
|
||||
|
||||
|
||||
if (scheduledTasks.containsKey(taskId)) {
|
||||
logger.warn("Task {} is already scheduled.", taskId);
|
||||
return;
|
||||
@@ -47,16 +70,16 @@ public class ScheduledTaskManager {
|
||||
ScheduledFuture<?> future = executorService.scheduleWithFixedDelay(
|
||||
runnable,
|
||||
1,
|
||||
timeFrame.getInterval(),
|
||||
timeFrame.getInterval(),
|
||||
timeFrame.getUnit()
|
||||
);
|
||||
|
||||
|
||||
scheduledTasks.put(taskId, future);
|
||||
logger.info("Successfully scheduled task: {} with interval {} {}",
|
||||
logger.info("Successfully scheduled task: {} with interval {} {}",
|
||||
taskId, timeFrame.getInterval(), timeFrame.getUnit());
|
||||
}
|
||||
|
||||
public static void cancelAll() {
|
||||
public void cancelAll() {
|
||||
logger.info("Cancelling all {} scheduled tasks", scheduledTasks.size());
|
||||
scheduledTasks.forEach((id, future) -> future.cancel(false));
|
||||
scheduledTasks.clear();
|
||||
|
||||
@@ -12,11 +12,29 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ThreadManager {
|
||||
/**
|
||||
* Instance-based {@link ExecutorProvider} owning the named cached-thread pools and the shared
|
||||
* scheduled executor.
|
||||
*/
|
||||
public class ThreadManager implements ExecutorProvider {
|
||||
|
||||
private static final Map<String, ExecutorService> executors = new ConcurrentHashMap<>();
|
||||
private final Map<String, ExecutorService> executors = new ConcurrentHashMap<>();
|
||||
|
||||
public static ScheduledExecutorService getScheduledExecutor() {
|
||||
public ThreadManager() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// Executors are created lazily on first use; nothing to eagerly start.
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorService getScheduledExecutor() {
|
||||
return (ScheduledExecutorService) executors.computeIfAbsent(
|
||||
"scheduled-executor",
|
||||
s -> Executors.newScheduledThreadPool(
|
||||
@@ -26,15 +44,18 @@ public class ThreadManager {
|
||||
);
|
||||
}
|
||||
|
||||
public static ExecutorService getExecutor(String name) {
|
||||
@Override
|
||||
public ExecutorService getExecutor(String name) {
|
||||
return executors.computeIfAbsent(name, s -> Executors.newCachedThreadPool(new BaseThreadFactory(s)));
|
||||
}
|
||||
|
||||
public static ExecutorService getExecutor() {
|
||||
@Override
|
||||
public ExecutorService getExecutor() {
|
||||
return getExecutor("general-executor");
|
||||
}
|
||||
|
||||
public static void shutdownExecutor() {
|
||||
@Override
|
||||
public void stop() {
|
||||
System.out.println("[Executor] Shutting down...");
|
||||
|
||||
try {
|
||||
@@ -56,6 +77,4 @@ public class ThreadManager {
|
||||
|
||||
System.out.println("[Executor] Shutdown finished!");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+8
-8
@@ -1,11 +1,11 @@
|
||||
package me.bobert.netneutrality.core.db;
|
||||
package me.bobert.netneutrality.db;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.flywaydb.core.Flyway;
|
||||
@@ -14,12 +14,12 @@ import org.flywaydb.core.api.output.MigrateResult;
|
||||
public class DatabaseMigration {
|
||||
private final static Logger logger = LogManager.getLogger(DatabaseMigration.class);
|
||||
|
||||
public static void migrate() {
|
||||
String dbName = Settings.getInstance().get("MYSQL_DATABASE", "netneutrality");
|
||||
String dbUser = Settings.getInstance().get("MYSQL_USER", "root");
|
||||
String dbPass = Settings.getInstance().get("MYSQL_PASSWORD", "");
|
||||
String dbHost = Settings.getInstance().get("MYSQL_HOST", "localhost");
|
||||
String dbPort = Settings.getInstance().get("MYSQL_PORT", "3306");
|
||||
public static void migrate(SettingsProvider settings) {
|
||||
String dbName = settings.get("MYSQL_DATABASE", "netneutrality");
|
||||
String dbUser = settings.get("MYSQL_USER", "root");
|
||||
String dbPass = settings.get("MYSQL_PASSWORD", "");
|
||||
String dbHost = settings.get("MYSQL_HOST", "localhost");
|
||||
String dbPort = settings.get("MYSQL_PORT", "3306");
|
||||
String dbUrl = String.format("jdbc:mysql://%s:%s/%s", dbHost, dbPort, dbName);
|
||||
|
||||
try {
|
||||
+59
-7
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.utils;
|
||||
package me.bobert.netneutrality.db;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
@@ -6,7 +6,6 @@ package me.bobert.netneutrality.utils;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
import me.bobert.netneutrality.db.entities.OGuild;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -14,15 +13,59 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Static Hibernate helpers used by repositories and domain services.
|
||||
*
|
||||
* <p>Because repositories are created deep in the object graph without constructor injection,
|
||||
* this class holds a single {@link SessionProvider} bound once during bootstrap via
|
||||
* {@link #bind(SessionProvider)}. That is the only global access point for sessions — there is no
|
||||
* {@code SessionManager.instance()} shim.
|
||||
*/
|
||||
public class JpaUtils {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(JpaUtils.class);
|
||||
private static volatile SessionProvider sessions;
|
||||
private static volatile Function<String, GuildService> guildServiceLookup;
|
||||
|
||||
private JpaUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the application {@link SessionProvider}. Called once from {@code SessionManager}'s
|
||||
* constructor during context registration.
|
||||
*
|
||||
* @param provider the live session provider
|
||||
*/
|
||||
public static void bind(SessionProvider provider) {
|
||||
sessions = Objects.requireNonNull(provider, "SessionProvider");
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional guild-service lookup used when recovering a missing {@link OGuild} entity. Bound from
|
||||
* bootstrap once the {@code GuildServiceProvider} is available.
|
||||
*
|
||||
* @param lookup guild-id → service resolver
|
||||
*/
|
||||
public static void bindGuildServiceLookup(Function<String, GuildService> lookup) {
|
||||
guildServiceLookup = Objects.requireNonNull(lookup, "guildServiceLookup");
|
||||
}
|
||||
|
||||
private static SessionFactory factory() {
|
||||
SessionProvider provider = sessions;
|
||||
if (provider == null) {
|
||||
throw new IllegalStateException("JpaUtils has not been bound to a SessionProvider");
|
||||
}
|
||||
return provider.getSessionFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to read the persistent state into the object.
|
||||
@@ -44,7 +87,12 @@ public class JpaUtils {
|
||||
String cachedId = oGuild.getGuildId();
|
||||
logger.error("Trying to load Guild[{}] while entity doesn't exist in database, reloading..", cachedId);
|
||||
|
||||
GuildService service = GuildService.get(cachedId);
|
||||
Function<String, GuildService> lookup = guildServiceLookup;
|
||||
if (lookup == null) {
|
||||
session.close();
|
||||
throw exception;
|
||||
}
|
||||
GuildService service = lookup.apply(cachedId);
|
||||
service.reloadEntity();
|
||||
service.getBlacklistService().removeGuildBlacklist();
|
||||
logger.info("Guild[{}] reloaded", cachedId);
|
||||
@@ -56,14 +104,18 @@ public class JpaUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Session openSession() {
|
||||
return factory().openSession();
|
||||
}
|
||||
|
||||
public static void runWithSession(Consumer<Session> consumer) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = factory().openSession()) {
|
||||
consumer.accept(session);
|
||||
}
|
||||
}
|
||||
|
||||
public static void runWithTransaction(Consumer<Session> consumer) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = factory().openSession()) {
|
||||
session.beginTransaction();
|
||||
consumer.accept(session);
|
||||
session.getTransaction().commit();
|
||||
@@ -71,7 +123,7 @@ public class JpaUtils {
|
||||
}
|
||||
|
||||
public static void updateWithTransaction(Object persistantEntity, Runnable runnable) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = factory().openSession()) {
|
||||
Transaction transaction = session.beginTransaction();
|
||||
|
||||
try {
|
||||
@@ -98,7 +150,7 @@ public class JpaUtils {
|
||||
* @param consumer The comsumer, which exposes the session
|
||||
*/
|
||||
public static void getWithSession(Object persistantEntity, Serializable id, Consumer<Session> consumer) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = factory().openSession()) {
|
||||
tryLoad(session, persistantEntity, id);
|
||||
consumer.accept(session);
|
||||
}
|
||||
@@ -5,7 +5,6 @@ package me.bobert.netneutrality.db;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.hibernate.Session;
|
||||
@@ -24,7 +23,7 @@ public interface Repository<O> {
|
||||
default void save(O entity) {
|
||||
Transaction transaction = null;
|
||||
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = JpaUtils.openSession()) {
|
||||
transaction = session.beginTransaction();
|
||||
session.merge(entity);
|
||||
transaction.commit();
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package me.bobert.netneutrality.db;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import io.sentry.Sentry;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.Metadata;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Instance-based {@link SessionProvider} owning the Hibernate {@link SessionFactory} and its
|
||||
* service registry.
|
||||
*
|
||||
* <p>On construction the instance is bound into {@link JpaUtils} so repositories and other static
|
||||
* persistence helpers can open sessions without a global {@code SessionManager} singleton.
|
||||
*/
|
||||
public class SessionManager implements SessionProvider {
|
||||
private static final Logger logger = LogManager.getLogger(SessionManager.class);
|
||||
|
||||
private final SettingsProvider settings;
|
||||
private StandardServiceRegistry registry;
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
public SessionManager(SettingsProvider settings) {
|
||||
this.settings = settings;
|
||||
JpaUtils.bind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
getSessionFactory();
|
||||
DatabaseMigration.migrate(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (registry != null) {
|
||||
StandardServiceRegistryBuilder.destroy(registry);
|
||||
registry = null;
|
||||
sessionFactory = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionFactory getSessionFactory() {
|
||||
if (sessionFactory == null) {
|
||||
try {
|
||||
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder().configure();
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
String dbName = settings.get("MYSQL_DATABASE", "netneutrality");
|
||||
String dbUser = settings.get("MYSQL_USER", "root");
|
||||
String dbPass = settings.get("MYSQL_PASSWORD", "");
|
||||
String dbHost = settings.get("MYSQL_HOST", "localhost");
|
||||
String dbPort = settings.get("MYSQL_PORT", "3306");
|
||||
logger.info("Connecting to database '{}' at {}:{} with user '{}'", dbName, dbHost, dbPort, dbUser);
|
||||
|
||||
config.put(Environment.JAKARTA_JDBC_URL, String.format("jdbc:mysql://%s:%s/%s", dbHost, dbPort, dbName));
|
||||
config.put(Environment.JAKARTA_JDBC_USER, dbUser);
|
||||
config.put(Environment.JAKARTA_JDBC_PASSWORD, dbPass);
|
||||
registryBuilder.applySettings(config);
|
||||
|
||||
registry = registryBuilder.build();
|
||||
MetadataSources sources = new MetadataSources(registry);
|
||||
Metadata metadata = sources.getMetadataBuilder().build();
|
||||
sessionFactory = metadata.getSessionFactoryBuilder().build();
|
||||
|
||||
} catch (Exception e) {
|
||||
String env = settings.get("NETNEUTRALITY_BOT_ENV", "dev");
|
||||
if (!"dev".equals(env)) {
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
|
||||
stop();
|
||||
logger.error("Error connecting to database. Shutting down..");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return sessionFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.bobert.netneutrality.db;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import org.hibernate.SessionFactory;
|
||||
|
||||
/**
|
||||
* Owns the Hibernate {@link SessionFactory} and its underlying service registry.
|
||||
*
|
||||
* <p>As a {@link Lifecycle} component it builds the factory (and runs migrations) on
|
||||
* {@link #start()} and releases the registry on {@link #stop()}.
|
||||
*/
|
||||
public interface SessionProvider extends Lifecycle {
|
||||
|
||||
/**
|
||||
* Returns the shared Hibernate {@link SessionFactory}, building it lazily if necessary.
|
||||
*
|
||||
* @return the session factory
|
||||
*/
|
||||
SessionFactory getSessionFactory();
|
||||
}
|
||||
@@ -5,11 +5,10 @@ package me.bobert.netneutrality.db.repository;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
import me.bobert.netneutrality.db.Repository;
|
||||
import me.bobert.netneutrality.db.entities.OBlacklist;
|
||||
import me.bobert.netneutrality.db.entities.OGuild;
|
||||
import me.bobert.netneutrality.utils.JpaUtils;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.query.Query;
|
||||
@@ -92,7 +91,7 @@ public class BlacklistRepository implements Repository<OBlacklist> {
|
||||
}
|
||||
|
||||
private OBlacklist getWordFromDb(String guildId, String word) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = JpaUtils.openSession()) {
|
||||
Query<OBlacklist> query = session.createQuery("SELECT o FROM OBlacklist o WHERE upper(o.word) = upper(:word) AND o.guild.guildId = :guildId", OBlacklist.class);
|
||||
query.setParameter("guildId", guildId);
|
||||
query.setParameter("word", word);
|
||||
|
||||
@@ -4,10 +4,15 @@ package me.bobert.netneutrality.db.repository;/*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.db.Repository;
|
||||
import me.bobert.netneutrality.db.entities.OGuild;
|
||||
import me.bobert.netneutrality.utils.JpaUtils;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
@@ -30,7 +35,7 @@ public class GuildRepository implements Repository<OGuild> {
|
||||
|
||||
@Override
|
||||
public OGuild findById(String id) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = JpaUtils.openSession()) {
|
||||
Query<OGuild> query = session.createQuery("SELECT o FROM OGuild o WHERE o.guildId = :guildId", OGuild.class);
|
||||
query.setParameter("guildId", id);
|
||||
|
||||
|
||||
@@ -4,12 +4,17 @@ package me.bobert.netneutrality.db.repository;/*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.db.GSettings;
|
||||
import me.bobert.netneutrality.db.Repository;
|
||||
import me.bobert.netneutrality.db.entities.OGuild;
|
||||
import me.bobert.netneutrality.db.entities.OGuildSetting;
|
||||
import me.bobert.netneutrality.utils.JpaUtils;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
@@ -32,7 +37,7 @@ public class GuildSettingRepository implements Repository<OGuildSetting> {
|
||||
|
||||
@Override
|
||||
public OGuildSetting findById(String id) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = JpaUtils.openSession()) {
|
||||
Query<OGuildSetting> query = session.createQuery("SELECT o FROM OGuildSetting o WHERE o.id = :id", OGuildSetting.class);
|
||||
query.setParameter("id", id);
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@ package me.bobert.netneutrality.db.repository;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
import me.bobert.netneutrality.db.Repository;
|
||||
import me.bobert.netneutrality.db.entities.OGuild;
|
||||
import me.bobert.netneutrality.db.entities.OLogMessage;
|
||||
import me.bobert.netneutrality.utils.JpaUtils;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
@@ -35,7 +34,7 @@ public class MessageRepository implements Repository<OLogMessage> {
|
||||
|
||||
@Override
|
||||
public OLogMessage findById(String id) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = JpaUtils.openSession()) {
|
||||
Query<OLogMessage> query = session.createQuery("SELECT o FROM OLogMessage o WHERE o.messageId = :messageId", OLogMessage.class);
|
||||
query.setParameter("messageId", id);
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@ package me.bobert.netneutrality.db.repository;
|
||||
*/
|
||||
|
||||
|
||||
import me.bobert.netneutrality.core.db.SessionManager;
|
||||
import me.bobert.netneutrality.db.Repository;
|
||||
import me.bobert.netneutrality.db.entities.OTask;
|
||||
import me.bobert.netneutrality.utils.JpaUtils;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
@@ -33,7 +32,7 @@ public class TaskRepository implements Repository<OTask> {
|
||||
|
||||
@Override
|
||||
public OTask findById(String id) {
|
||||
try (Session session = SessionManager.getSessionFactory().openSession()) {
|
||||
try (Session session = JpaUtils.openSession()) {
|
||||
Query<OTask> query = session.createQuery("SELECT o FROM OTask o WHERE o.taskId = :taskId", OTask.class);
|
||||
query.setParameter("taskId", id);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.db.entities.OBlacklist;
|
||||
import me.bobert.netneutrality.db.entities.OGuild;
|
||||
import me.bobert.netneutrality.db.repository.BlacklistRepository;
|
||||
import me.bobert.netneutrality.utils.JpaUtils;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -8,40 +8,29 @@ package me.bobert.netneutrality.db.service;
|
||||
|
||||
import me.bobert.netneutrality.db.entities.OGuild;
|
||||
import me.bobert.netneutrality.db.repository.GuildRepository;
|
||||
import me.bobert.netneutrality.utils.JpaUtils;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import me.bobert.netneutrality.db.JpaUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Per-guild domain service (blacklist, settings, entity access).
|
||||
*
|
||||
* <p>Instances are owned by {@link GuildServiceProviderImpl}; obtain them via
|
||||
* {@link me.bobert.netneutrality.db.service.GuildServiceProvider}.
|
||||
*/
|
||||
public class GuildService {
|
||||
private static final Map<String, GuildService> services = new ConcurrentHashMap<>();
|
||||
private static final Logger logger = LoggerFactory.getLogger(GuildService.class);
|
||||
private final GuildRepository guildRepository = new GuildRepository();
|
||||
private final BlacklistService blacklistService;
|
||||
private final GuildSettingService guildSettingService;
|
||||
private OGuild oGuild;
|
||||
|
||||
private GuildService(String guildId) {
|
||||
GuildService(String guildId) {
|
||||
this.oGuild = findOrCreateGuild(guildId);
|
||||
this.blacklistService = new BlacklistService(this);
|
||||
this.guildSettingService = new GuildSettingService(this);
|
||||
}
|
||||
|
||||
public static GuildService get(String guildId) {
|
||||
return services.computeIfAbsent(guildId, GuildService::new);
|
||||
}
|
||||
|
||||
public static GuildService get(Guild guild) {
|
||||
return get(guild.getId());
|
||||
}
|
||||
|
||||
public static Map<String, GuildService> getServices() {
|
||||
return services;
|
||||
}
|
||||
|
||||
public OGuild getEntity() {
|
||||
return oGuild;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package me.bobert.netneutrality.db.service;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Owns the per-guild {@link GuildService} cache.
|
||||
*/
|
||||
public interface GuildServiceProvider {
|
||||
|
||||
/**
|
||||
* Returns (creating on first use) the service for the given guild id.
|
||||
*
|
||||
* @param guildId the Discord guild id
|
||||
* @return the guild service
|
||||
*/
|
||||
GuildService get(String guildId);
|
||||
|
||||
/**
|
||||
* Returns (creating on first use) the service for the given guild.
|
||||
*
|
||||
* @param guild the Discord guild
|
||||
* @return the guild service
|
||||
*/
|
||||
GuildService get(Guild guild);
|
||||
|
||||
/**
|
||||
* Returns the live cache of guild services.
|
||||
*
|
||||
* @return the services keyed by guild id
|
||||
*/
|
||||
Map<String, GuildService> getServices();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package me.bobert.netneutrality.db.service;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Instance-based {@link GuildServiceProvider} owning the per-guild {@link GuildService} cache.
|
||||
*/
|
||||
public class GuildServiceProviderImpl implements GuildServiceProvider {
|
||||
|
||||
private final Map<String, GuildService> services = new ConcurrentHashMap<>();
|
||||
|
||||
public GuildServiceProviderImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuildService get(String guildId) {
|
||||
return services.computeIfAbsent(guildId, GuildService::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuildService get(Guild guild) {
|
||||
return get(guild.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, GuildService> getServices() {
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,12 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class TaskService {
|
||||
private final TaskManager taskManager;
|
||||
private static final Logger logger = LogManager.getLogger(TaskService.class);
|
||||
private final TaskRepository taskRepository;
|
||||
|
||||
public TaskService() {
|
||||
public TaskService(TaskManager taskManager) {
|
||||
this.taskManager = taskManager;
|
||||
this.taskRepository = new TaskRepository();
|
||||
}
|
||||
|
||||
@@ -51,7 +53,7 @@ public class TaskService {
|
||||
}
|
||||
|
||||
validateTask(task, oTask);
|
||||
TaskManager.getInstance().insertPersistableTask(task);
|
||||
taskManager.insertPersistableTask(task);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error loading task with ID {}: {}", taskId, e.getMessage());
|
||||
}
|
||||
|
||||
@@ -19,9 +19,11 @@ import me.bobert.netneutrality.core.command.CommandProcessor;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.Context;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import me.bobert.netneutrality.core.command.CommandRegistry;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.events.Event;
|
||||
import net.dv8tion.jda.api.events.GenericEvent;
|
||||
@@ -40,15 +42,34 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class EventsHandler implements EventListener {
|
||||
private static final Map<Class<? extends Event>, Function<? extends Event, Context>> listeners = new ConcurrentHashMap<>();
|
||||
private static final Logger logger = LoggerFactory.getLogger(EventsHandler.class);
|
||||
|
||||
static {
|
||||
private final Map<Class<? extends Event>, Function<? extends Event, Context>> listeners = new ConcurrentHashMap<>();
|
||||
private final SettingsProvider settings;
|
||||
private final GuildServiceProvider guildServices;
|
||||
private final CommandRegistry commands;
|
||||
private final TaskManager taskManager;
|
||||
|
||||
public EventsHandler(
|
||||
SettingsProvider settings,
|
||||
GuildServiceProvider guildServices,
|
||||
CommandRegistry commands,
|
||||
TaskManager taskManager
|
||||
) {
|
||||
this.settings = Objects.requireNonNull(settings, "settings");
|
||||
this.guildServices = Objects.requireNonNull(guildServices, "guildServices");
|
||||
this.commands = Objects.requireNonNull(commands, "commands");
|
||||
this.taskManager = Objects.requireNonNull(taskManager, "taskManager");
|
||||
registerListeners();
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
addListener(MessageReceivedEvent.class, event -> {
|
||||
if (event.getAuthor().isBot()) {
|
||||
return null;
|
||||
@@ -59,9 +80,9 @@ public class EventsHandler implements EventListener {
|
||||
|
||||
Message message = event.getMessage();
|
||||
String rawMessage = message.getContentDisplay().trim();
|
||||
String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$");
|
||||
String prefix = settings.get("NETNEUTRALITY_BOT_PREFIX", "$");
|
||||
if (rawMessage.startsWith(prefix)) {
|
||||
return new MessageCommandContext(event);
|
||||
return new MessageCommandContext(event, settings);
|
||||
}
|
||||
|
||||
return new MessageTaskContext(event);
|
||||
@@ -74,13 +95,13 @@ public class EventsHandler implements EventListener {
|
||||
addListener(ButtonInteractionEvent.class, ButtonTaskContext::new);
|
||||
addListener(MessageReactionAddEvent.class, EmojiTaskContext::new);
|
||||
addGenericListener(GuildJoinEvent.class, event -> {
|
||||
GuildService service = GuildService.get(event.getGuild());
|
||||
GuildService service = guildServices.get(event.getGuild());
|
||||
|
||||
if (service.isDebugEnabled()) {
|
||||
LogHandler.sendMessageToDebugChannel(event.getGuild(), "Error logging enabled on this channel");
|
||||
}
|
||||
});
|
||||
addGenericListener(GuildLeaveEvent.class, event -> GuildService.get(event.getGuild()).deleteGuild());
|
||||
addGenericListener(GuildLeaveEvent.class, event -> guildServices.get(event.getGuild()).deleteGuild());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +112,7 @@ public class EventsHandler implements EventListener {
|
||||
* @param <T> the type of the event that extends the {@link Event} class
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Event> void addListener(Class<T> eventClass, Function<? super T, Context> function) {
|
||||
public <T extends Event> void addListener(Class<T> eventClass, Function<? super T, Context> function) {
|
||||
logger.info("Registering listener for {}", eventClass.getSimpleName());
|
||||
listeners.put(eventClass, (Function<? extends Event, Context>) function);
|
||||
}
|
||||
@@ -104,7 +125,7 @@ public class EventsHandler implements EventListener {
|
||||
* @param consumer the consumer to handle the event, which takes an event of the specified type and performs an action
|
||||
* @param <T> the type of the event that extends the {@link Event} class
|
||||
*/
|
||||
public static <T extends Event> void addGenericListener(Class<T> eventClass, Consumer<T> consumer) {
|
||||
public <T extends Event> void addGenericListener(Class<T> eventClass, Consumer<T> consumer) {
|
||||
addListener(eventClass, t -> {
|
||||
consumer.accept(t);
|
||||
return null;
|
||||
@@ -141,11 +162,11 @@ public class EventsHandler implements EventListener {
|
||||
}
|
||||
|
||||
private void handleCommand(CommandContext context) {
|
||||
CommandProcessor processor = new CommandProcessor(context);
|
||||
CommandProcessor processor = new CommandProcessor(context, commands, taskManager);
|
||||
processor.dispatch();
|
||||
}
|
||||
|
||||
private void handleTask(TaskContext context) {
|
||||
TaskManager.getInstance().handle(context);
|
||||
taskManager.handle(context);
|
||||
}
|
||||
}
|
||||
@@ -8,25 +8,38 @@ package me.bobert.netneutrality.http.server;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import me.bobert.netneutrality.NetNeutrality;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ActionHandler extends AbstractHandler {
|
||||
private final Supplier<JDA> jdaSupplier;
|
||||
private final GuildServiceProvider guildServices;
|
||||
private final Runnable shutdownHook;
|
||||
|
||||
public ActionHandler(Supplier<JDA> jdaSupplier, GuildServiceProvider guildServices, Runnable shutdownHook) {
|
||||
this.jdaSupplier = Objects.requireNonNull(jdaSupplier, "jdaSupplier");
|
||||
this.guildServices = Objects.requireNonNull(guildServices, "guildServices");
|
||||
this.shutdownHook = Objects.requireNonNull(shutdownHook, "shutdownHook");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
|
||||
String path = exchange.getRequestURI().getRawPath();
|
||||
|
||||
if (path.equals("/actions/shutdown")) {
|
||||
LogManager.getLogger(NetNeutrality.class).info("Request to shutdown received");
|
||||
NetNeutrality.getInstance().shutdown();
|
||||
LogManager.getLogger(ActionHandler.class).info("Request to shutdown received");
|
||||
shutdownHook.run();
|
||||
sendStringResponse(exchange, "Shutting down..");
|
||||
|
||||
System.exit(0);
|
||||
@@ -35,8 +48,9 @@ public class ActionHandler extends AbstractHandler {
|
||||
|
||||
if (path.equals("/actions/status")) {
|
||||
TreeMap<String, String> json = new TreeMap<>();
|
||||
json.put("health", String.valueOf(NetNeutrality.getInstance().getJDA().getStatus()));
|
||||
json.put("ping", String.valueOf(NetNeutrality.getInstance().getJDA().getGatewayPing()));
|
||||
JDA jda = jdaSupplier.get();
|
||||
json.put("health", String.valueOf(jda.getStatus()));
|
||||
json.put("ping", String.valueOf(jda.getGatewayPing()));
|
||||
|
||||
sendJsonResponse(exchange, new Gson().toJson(json));
|
||||
return;
|
||||
@@ -47,9 +61,9 @@ public class ActionHandler extends AbstractHandler {
|
||||
String[] args = exchange.getRequestURI().toString().split("/");
|
||||
String guildId = args[args.length - 1];
|
||||
|
||||
Guild guild = NetNeutrality.getInstance().getJDA().getGuildById(guildId);
|
||||
Guild guild = jdaSupplier.get().getGuildById(guildId);
|
||||
if (guild != null) {
|
||||
GuildService service = GuildService.get(guild);
|
||||
GuildService service = guildServices.get(guild);
|
||||
Map<String, String> settings = service.getGuildSettingService().getSettings();
|
||||
JSONObject setting = new JSONObject(settings);
|
||||
|
||||
@@ -69,7 +83,7 @@ public class ActionHandler extends AbstractHandler {
|
||||
String guildId = args[args.length - 1];
|
||||
|
||||
boolean ok = true; //TODO: add bool
|
||||
GuildService.get(guildId).getGuildSettingService().reloadSettingsCache();
|
||||
guildServices.get(guildId).getGuildSettingService().reloadSettingsCache();
|
||||
|
||||
json.put("status", String.valueOf(ok));
|
||||
sendJsonResponse(exchange, new Gson().toJson(json));
|
||||
|
||||
@@ -7,14 +7,20 @@ package me.bobert.netneutrality.http.server;
|
||||
*/
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import me.bobert.netneutrality.NetNeutrality;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class GuildHandler extends AbstractHandler {
|
||||
private final Supplier<JDA> jdaSupplier;
|
||||
|
||||
public GuildHandler(Supplier<JDA> jdaSupplier) {
|
||||
this.jdaSupplier = Objects.requireNonNull(jdaSupplier, "jdaSupplier");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
@@ -25,7 +31,7 @@ public class GuildHandler extends AbstractHandler {
|
||||
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
JDA jda = NetNeutrality.getInstance().getJDA();
|
||||
JDA jda = jdaSupplier.get();
|
||||
|
||||
jda.getGuilds().forEach(guild -> {
|
||||
JSONObject obj = new JSONObject();
|
||||
@@ -45,7 +51,7 @@ public class GuildHandler extends AbstractHandler {
|
||||
String guild = args[args.length - 1];
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
JDA jda = NetNeutrality.getInstance().getJDA();
|
||||
JDA jda = jdaSupplier.get();
|
||||
|
||||
if (jda.getGuildById(guild) == null) {
|
||||
sendStringResponse(exchange, path);
|
||||
@@ -69,7 +75,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.getInstance().getJDA();
|
||||
JDA jda = jdaSupplier.get();
|
||||
|
||||
if (jda.getGuildById(guild) == null) {
|
||||
sendStringResponse(exchange, path);
|
||||
|
||||
@@ -7,7 +7,11 @@ package me.bobert.netneutrality.http.server;
|
||||
*/
|
||||
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import me.bobert.netneutrality.core.thread.BaseThreadFactory;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -15,32 +19,73 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class WebApi {
|
||||
/**
|
||||
* Embedded HTTP API exposed as a {@link Lifecycle} component (priority 70, after JDA).
|
||||
*/
|
||||
public class WebApi implements Lifecycle {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(WebApi.class);
|
||||
|
||||
private final SettingsProvider settings;
|
||||
private final GuildServiceProvider guildServices;
|
||||
private final Supplier<JDA> jdaSupplier;
|
||||
private final Runnable shutdownHook;
|
||||
private HttpServer server;
|
||||
private ExecutorService executor;
|
||||
|
||||
public WebApi(int port) {
|
||||
public WebApi(
|
||||
SettingsProvider settings,
|
||||
GuildServiceProvider guildServices,
|
||||
Supplier<JDA> jdaSupplier,
|
||||
Runnable shutdownHook
|
||||
) {
|
||||
this.settings = settings;
|
||||
this.guildServices = guildServices;
|
||||
this.jdaSupplier = jdaSupplier;
|
||||
this.shutdownHook = shutdownHook;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
// After JDA (60) so handlers can resolve a ready client.
|
||||
return 70;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
int port = Integer.parseInt(settings.get("NETNEUTRALITY_API_PORT", "9000"));
|
||||
logger.info("Starting web server...");
|
||||
|
||||
try {
|
||||
InetSocketAddress sockAddr = new InetSocketAddress(port);
|
||||
server = HttpServer.create(sockAddr, 0);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to create HTTP server", e);
|
||||
return;
|
||||
}
|
||||
|
||||
server.createContext("/", new RootHandler());
|
||||
server.createContext("/actions", new ActionHandler());
|
||||
server.createContext("/guilds", new GuildHandler());
|
||||
ExecutorService executor = Executors.newFixedThreadPool(10, new BaseThreadFactory("web-api"));
|
||||
server.createContext("/actions", new ActionHandler(jdaSupplier, guildServices, shutdownHook));
|
||||
server.createContext("/guilds", new GuildHandler(jdaSupplier));
|
||||
executor = Executors.newFixedThreadPool(10, new BaseThreadFactory("web-api"));
|
||||
server.setExecutor(executor);
|
||||
server.start();
|
||||
logger.info("Web server started at [{}]", server.getAddress());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (server != null) {
|
||||
logger.info("Stopping web server...");
|
||||
server.stop(0);
|
||||
server = null;
|
||||
}
|
||||
if (executor != null) {
|
||||
executor.shutdownNow();
|
||||
executor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,89 +6,56 @@ package me.bobert.netneutrality.music;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import dev.lavalink.youtube.YoutubeAudioSourceManager;
|
||||
import dev.lavalink.youtube.YoutubeSourceOptions;
|
||||
import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.db.GSettings;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.music.source.UdioAudioSourceManager;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Per-guild music player and queue owner.
|
||||
*
|
||||
* <p>The shared {@link AudioPlayerManager} and the manager cache live on {@link MusicServiceImpl}.
|
||||
* Obtain instances via {@link me.bobert.netneutrality.music.MusicService#get(Guild)}.
|
||||
*/
|
||||
public class MusicManager {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MusicManager.class);
|
||||
private static final Map<String, MusicManager> managers = new ConcurrentHashMap<>();
|
||||
private static final AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
|
||||
private final Guild guild;
|
||||
private final AudioPlayerManager playerManager;
|
||||
private final AudioPlayer player;
|
||||
private final TrackScheduler scheduler;
|
||||
private final int baseVolume;
|
||||
private final GuildService guildService;
|
||||
private final TaskManager taskManager;
|
||||
|
||||
private MusicManager(Guild guild) {
|
||||
MusicManager(Guild guild, AudioPlayerManager playerManager, GuildService guildService, TaskManager taskManager) {
|
||||
this.guild = guild;
|
||||
this.playerManager = playerManager;
|
||||
this.player = playerManager.createPlayer();
|
||||
this.scheduler = new TrackScheduler(this);
|
||||
this.baseVolume = Integer.parseInt(GuildService.get(guild).getGuildSettingService().getSetting(GSettings.MUSIC_VOLUME));
|
||||
this.guildService = guildService;
|
||||
this.taskManager = taskManager;
|
||||
this.baseVolume = Integer.parseInt(guildService.getGuildSettingService().getSetting(GSettings.MUSIC_VOLUME));
|
||||
|
||||
guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player));
|
||||
player.addListener(scheduler);
|
||||
|
||||
managers.put(guild.getId(), this);
|
||||
}
|
||||
|
||||
public static MusicManager get(Guild guild) {
|
||||
if (managers.containsKey(guild.getId())) {
|
||||
return managers.get(guild.getId());
|
||||
} else {
|
||||
return new MusicManager(guild);
|
||||
}
|
||||
public GuildService getGuildService() {
|
||||
return guildService;
|
||||
}
|
||||
|
||||
public static void initPlayerManager() {
|
||||
logger.debug("Initializing music player sources..");
|
||||
|
||||
String cipherUrl = Settings.getInstance().get("NETNEUTRALITY_CIPHER_URL");
|
||||
String cipherPassword = Settings.getInstance().get("NETNEUTRALITY_CIPHER_PASSWORD");
|
||||
YoutubeSourceOptions options = new YoutubeSourceOptions()
|
||||
.setAllowSearch(true)
|
||||
.setAllowDirectVideoIds(true)
|
||||
.setAllowDirectPlaylistIds(true)
|
||||
.setRemoteCipher(cipherUrl, cipherPassword, "");
|
||||
playerManager.registerSourceManager(new YoutubeAudioSourceManager(options, YoutubeAudioSourceManager.DEFAULT_CLIENTS));
|
||||
playerManager.registerSourceManager(SoundCloudAudioSourceManager.createDefault());
|
||||
playerManager.registerSourceManager(new UdioAudioSourceManager());
|
||||
playerManager.registerSourceManager(new HttpAudioSourceManager());
|
||||
|
||||
String po = Settings.getInstance().get("NETNEUTRALITY_PO_TOKEN");
|
||||
String visitor = Settings.getInstance().get("NETNEUTRALITY_VISITOR_DATA");
|
||||
dev.lavalink.youtube.clients.Web.setPoTokenAndVisitorData(po, visitor);
|
||||
}
|
||||
|
||||
public static void disconnectPlayers() {
|
||||
for (MusicManager mm : managers.values()) {
|
||||
mm.getQueueManager().clearQueue();
|
||||
mm.player.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<MusicManager> getManagers() {
|
||||
return managers.values();
|
||||
public TaskManager getTaskManager() {
|
||||
return taskManager;
|
||||
}
|
||||
|
||||
public TrackScheduler getScheduler() {
|
||||
@@ -152,7 +119,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)) {
|
||||
if (MusicUtils.canConnect(context, this)) {
|
||||
SourceFinder.AudioSourceType type = SourceFinder.getSource(query);
|
||||
String identifier = SourceFinder.getIdentifier(query);
|
||||
if (identifier == null) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.bobert.netneutrality.music;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
import me.bobert.netneutrality.bootstrap.Lifecycle;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
/**
|
||||
* Owns the shared audio player manager and the per-guild {@link MusicManager} cache.
|
||||
*
|
||||
* <p>As a {@link Lifecycle} component it registers audio sources on {@link #start()} and
|
||||
* disconnects every player on {@link #stop()}.
|
||||
*/
|
||||
public interface MusicService extends Lifecycle {
|
||||
|
||||
/**
|
||||
* Returns (creating on first use) the music manager for the given guild.
|
||||
*
|
||||
* @param guild the guild
|
||||
* @return the guild's music manager
|
||||
*/
|
||||
MusicManager get(Guild guild);
|
||||
|
||||
/**
|
||||
* Returns every currently cached per-guild music manager.
|
||||
*
|
||||
* @return the managers
|
||||
*/
|
||||
Collection<MusicManager> getManagers();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package me.bobert.netneutrality.music;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager;
|
||||
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
|
||||
import dev.lavalink.youtube.YoutubeAudioSourceManager;
|
||||
import dev.lavalink.youtube.YoutubeSourceOptions;
|
||||
import me.bobert.netneutrality.db.service.GuildServiceProvider;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.music.source.UdioAudioSourceManager;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Instance-based {@link MusicService} owning the shared {@link AudioPlayerManager} and the
|
||||
* per-guild {@link MusicManager} cache.
|
||||
*/
|
||||
public class MusicServiceImpl implements MusicService {
|
||||
private static final Logger logger = LogManager.getLogger(MusicServiceImpl.class);
|
||||
|
||||
private final SettingsProvider settings;
|
||||
private final GuildServiceProvider guildServices;
|
||||
private final TaskManager taskManager;
|
||||
private final Map<String, MusicManager> managers = new ConcurrentHashMap<>();
|
||||
private final AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
|
||||
|
||||
public MusicServiceImpl(SettingsProvider settings, GuildServiceProvider guildServices, TaskManager taskManager) {
|
||||
this.settings = settings;
|
||||
this.guildServices = guildServices;
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 40;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
logger.debug("Initializing music player sources..");
|
||||
|
||||
String cipherUrl = settings.get("NETNEUTRALITY_CIPHER_URL");
|
||||
String cipherPassword = settings.get("NETNEUTRALITY_CIPHER_PASSWORD");
|
||||
YoutubeSourceOptions options = new YoutubeSourceOptions()
|
||||
.setAllowSearch(true)
|
||||
.setAllowDirectVideoIds(true)
|
||||
.setAllowDirectPlaylistIds(true)
|
||||
.setRemoteCipher(cipherUrl, cipherPassword, "");
|
||||
playerManager.registerSourceManager(new YoutubeAudioSourceManager(options, YoutubeAudioSourceManager.DEFAULT_CLIENTS));
|
||||
playerManager.registerSourceManager(SoundCloudAudioSourceManager.createDefault());
|
||||
playerManager.registerSourceManager(new UdioAudioSourceManager());
|
||||
playerManager.registerSourceManager(new HttpAudioSourceManager());
|
||||
|
||||
String po = settings.get("NETNEUTRALITY_PO_TOKEN");
|
||||
String visitor = settings.get("NETNEUTRALITY_VISITOR_DATA");
|
||||
dev.lavalink.youtube.clients.Web.setPoTokenAndVisitorData(po, visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
for (MusicManager mm : managers.values()) {
|
||||
mm.getQueueManager().clearQueue();
|
||||
mm.getPlayer().destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicManager get(Guild guild) {
|
||||
if (managers.containsKey(guild.getId())) {
|
||||
return managers.get(guild.getId());
|
||||
}
|
||||
MusicManager manager = new MusicManager(guild, playerManager, guildServices.get(guild), taskManager);
|
||||
managers.put(guild.getId(), manager);
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MusicManager> getManagers() {
|
||||
return managers.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shared Lavaplayer {@link AudioPlayerManager}.
|
||||
*
|
||||
* @return the player manager
|
||||
*/
|
||||
public AudioPlayerManager getPlayerManager() {
|
||||
return playerManager;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,12 @@ package me.bobert.netneutrality.music;/*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.music;
|
||||
*/
|
||||
|
||||
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
@@ -13,8 +14,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.tasks.music.LoadPlaylistTask;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -29,7 +29,10 @@ public class TrackLoadingHandler implements AudioLoadResultHandler {
|
||||
private final QueueManager queueManager;
|
||||
private final CommandContext context;
|
||||
|
||||
private final MusicManager manager;
|
||||
|
||||
public TrackLoadingHandler(CommandContext context, MusicManager manager, String identifier, SourceFinder.AudioSourceType type, boolean silent) {
|
||||
this.manager = manager;
|
||||
this.identifier = identifier;
|
||||
this.type = type;
|
||||
this.silent = silent;
|
||||
@@ -43,7 +46,7 @@ public class TrackLoadingHandler implements AudioLoadResultHandler {
|
||||
logger.debug("Guild {}: track loaded: {}", guild.getId(), audioTrack.getIdentifier());
|
||||
|
||||
context.deleteMessage();
|
||||
if (MusicUtils.connect(context)) {
|
||||
if (MusicUtils.connect(context, manager)) {
|
||||
queueManager.queue(audioTrack, context.getGuildUser());
|
||||
|
||||
if (!silent) {
|
||||
@@ -56,7 +59,7 @@ public class TrackLoadingHandler implements AudioLoadResultHandler {
|
||||
public void playlistLoaded(AudioPlaylist playlist) {
|
||||
logger.debug("Guild {}: playlist loaded: {}", guild.getId(), identifier);
|
||||
|
||||
new LoadPlaylistTask(playlist, type).insert(context);
|
||||
new LoadPlaylistTask(playlist, type, manager).insert(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,8 +10,8 @@ import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
|
||||
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
|
||||
import me.bobert.netneutrality.context.MusicChannelContext;
|
||||
import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.context.MusicChannelContext;
|
||||
import me.bobert.netneutrality.core.context.Context;
|
||||
import me.bobert.netneutrality.exceptions.ContextException;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.utils;
|
||||
package me.bobert.netneutrality.shared.util;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.utils;
|
||||
package me.bobert.netneutrality.shared.util;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.utils;
|
||||
package me.bobert.netneutrality.shared.util;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
@@ -13,7 +13,7 @@ import io.sentry.Sentry;
|
||||
import me.bobert.netneutrality.core.command.Command;
|
||||
import me.bobert.netneutrality.core.command.CommandCategory;
|
||||
import me.bobert.netneutrality.core.command.CommandContainer;
|
||||
import me.bobert.netneutrality.core.command.CommandManager;
|
||||
import me.bobert.netneutrality.core.command.CommandRegistry;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.Context;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
@@ -205,10 +205,10 @@ public class MessageFunction {
|
||||
return embed.build();
|
||||
}
|
||||
|
||||
public static String buildHelpMessage() {
|
||||
public static String buildHelpMessage(CommandRegistry commands) {
|
||||
Map<CommandCategory, String> help = new HashMap<>();
|
||||
|
||||
for (CommandContainer container : CommandManager.commands.values()) {
|
||||
for (CommandContainer container : commands.getCommands().values()) {
|
||||
Command command = container.getCommand();
|
||||
if (command.getCategory() == null) {
|
||||
continue;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.utils;
|
||||
package me.bobert.netneutrality.shared.util;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.utils;
|
||||
package me.bobert.netneutrality.shared.util;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
@@ -11,7 +11,8 @@ import me.bobert.netneutrality.core.LogHandler;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.impl.ModalTask;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import net.dv8tion.jda.api.components.label.Label;
|
||||
import net.dv8tion.jda.api.components.textinput.TextInput;
|
||||
import net.dv8tion.jda.api.components.textinput.TextInputStyle;
|
||||
@@ -27,9 +28,12 @@ import org.gitlab4j.api.models.Issue;
|
||||
public class ReportIssueTask extends ModalTask {
|
||||
private static final Logger logger = LogManager.getLogger(ReportIssueTask.class);
|
||||
private final boolean insertable;
|
||||
private final SettingsProvider settings;
|
||||
|
||||
public ReportIssueTask(CommandContext context) {
|
||||
public ReportIssueTask(TaskManager taskManager, CommandContext context, SettingsProvider settings) {
|
||||
super(taskManager);
|
||||
this.insertable = context instanceof SlashCommandContext;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,11 +72,13 @@ public class ReportIssueTask extends ModalTask {
|
||||
public Lifecycle execute(TaskContext context) {
|
||||
context.ephemeral();
|
||||
|
||||
String gitlabHost = Settings.getInstance().get("GITLAB_HOST");
|
||||
String gitlabToken = Settings.getInstance().get("GITLAB_TOKEN");
|
||||
String projectId = Settings.getInstance().get("GITLAB_PROJECT_ID");
|
||||
String gitlabHost = settings.get("GITLAB_HOST");
|
||||
String gitlabToken = settings.get("GITLAB_TOKEN");
|
||||
String projectId = settings.get("GITLAB_PROJECT_ID");
|
||||
|
||||
if (gitlabHost.isBlank() || gitlabToken.isBlank() || projectId.isBlank()) {
|
||||
if (gitlabHost == null || gitlabHost.isBlank()
|
||||
|| gitlabToken == null || gitlabToken.isBlank()
|
||||
|| projectId == null || projectId.isBlank()) {
|
||||
logger.error("Gitlab is not configured in the bot settings, can't execute task");
|
||||
context.reply("Command is not configured, failed to create issue.");
|
||||
return null;
|
||||
|
||||
@@ -7,12 +7,24 @@ package me.bobert.netneutrality.tasks.help;
|
||||
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.impl.AbstractBuiltInTask;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import net.dv8tion.jda.api.components.buttons.Button;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class BotInfo extends AbstractBuiltInTask implements WithButton {
|
||||
|
||||
private final SettingsProvider settings;
|
||||
|
||||
public BotInfo(TaskManager taskManager, SettingsProvider settings) {
|
||||
super(taskManager);
|
||||
Objects.requireNonNull(settings, "SettingsProvider must not be null");
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "help-info";
|
||||
@@ -26,6 +38,6 @@ public class BotInfo extends AbstractBuiltInTask implements WithButton {
|
||||
@Override
|
||||
public void executeTask(TaskContext context) {
|
||||
context.replyEphemeral("For information about the bot and to follow the development, " +
|
||||
"you can join the discord here: " + Settings.getDiscordInvite());
|
||||
"you can join the discord here: " + settings.get("NETNEUTRALITY_BOT_INVITE", "https://discord.gg/PyCpd7JGMp"));
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,23 @@ package me.bobert.netneutrality.tasks.help;
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.impl.AbstractBuiltInTask;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import me.bobert.netneutrality.core.command.CommandRegistry;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import net.dv8tion.jda.api.components.buttons.Button;
|
||||
|
||||
public class CommandHelp extends AbstractBuiltInTask implements WithButton {
|
||||
|
||||
private final SettingsProvider settings;
|
||||
private final CommandRegistry commands;
|
||||
|
||||
public CommandHelp(TaskManager taskManager, SettingsProvider settings, CommandRegistry commands) {
|
||||
super(taskManager);
|
||||
this.settings = settings;
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "help-commands";
|
||||
@@ -26,7 +37,7 @@ public class CommandHelp extends AbstractBuiltInTask implements WithButton {
|
||||
|
||||
@Override
|
||||
public void executeTask(TaskContext context) {
|
||||
context.replyEphemeral("The prefix for the bot is " + Settings.getInstance().getPrefix());
|
||||
context.replyEphemeral(MessageFunction.buildHelpMessage());
|
||||
context.replyEphemeral("The prefix for the bot is " + settings.getPrefix());
|
||||
context.replyEphemeral(MessageFunction.buildHelpMessage(commands));
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,17 @@ package me.bobert.netneutrality.tasks.help;
|
||||
|
||||
import me.bobert.netneutrality.core.context.TaskContext;
|
||||
import me.bobert.netneutrality.core.tasks.impl.AbstractBuiltInTask;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithButton;
|
||||
import me.bobert.netneutrality.utils.Settings;
|
||||
import net.dv8tion.jda.api.components.buttons.Button;
|
||||
|
||||
public class ReportBug extends AbstractBuiltInTask implements WithButton {
|
||||
|
||||
public ReportBug(TaskManager taskManager) {
|
||||
super(taskManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "help-reportbug";
|
||||
|
||||
@@ -9,17 +9,15 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import me.bobert.netneutrality.core.context.CommandContext;
|
||||
import me.bobert.netneutrality.core.tasks.Task;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.core.tasks.impl.ButtonTask;
|
||||
import me.bobert.netneutrality.core.tasks.trait.Deletable;
|
||||
import me.bobert.netneutrality.core.tasks.trait.WithChildren;
|
||||
import me.bobert.netneutrality.db.GSettings;
|
||||
import me.bobert.netneutrality.db.service.GuildService;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.music.SourceFinder;
|
||||
import me.bobert.netneutrality.tasks.generic.AcceptButton;
|
||||
import me.bobert.netneutrality.tasks.generic.CancelButton;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.core.tasks.generic.AcceptButton;
|
||||
import me.bobert.netneutrality.core.tasks.generic.CancelButton;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import net.dv8tion.jda.api.components.actionrow.ActionRow;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
@@ -32,11 +30,13 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
|
||||
private final List<Task> childrens = new ArrayList<>();
|
||||
private final AudioPlaylist playlist;
|
||||
private final SourceFinder.AudioSourceType sourceType;
|
||||
private final MusicManager musicManager;
|
||||
|
||||
public LoadPlaylistTask(AudioPlaylist playlist, SourceFinder.AudioSourceType sourceType) {
|
||||
super(null, null);
|
||||
public LoadPlaylistTask(AudioPlaylist playlist, SourceFinder.AudioSourceType sourceType, MusicManager musicManager) {
|
||||
super(musicManager.getTaskManager(), null, null);
|
||||
this.playlist = playlist;
|
||||
this.sourceType = sourceType;
|
||||
this.musicManager = musicManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,7 +57,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
|
||||
}
|
||||
|
||||
int searchResultsLimit = 5; // Max number of buttons per row
|
||||
int maxPlaylistSizeBeforeAsking = Integer.parseInt(GuildService.get(context.getGuild())
|
||||
int maxPlaylistSizeBeforeAsking = Integer.parseInt(musicManager.getGuildService()
|
||||
.getGuildSettingService()
|
||||
.getSetting(GSettings.MUSIC_MAXPLAYLISTSIZE));
|
||||
|
||||
@@ -68,7 +68,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
|
||||
|
||||
for (int i = 0; i < searchResults.size(); i++) {
|
||||
AudioTrack track = searchResults.get(i);
|
||||
loadTasks.add(new LoadTrackFromPlaylistTask(this, String.valueOf(i + 1), track));
|
||||
loadTasks.add(new LoadTrackFromPlaylistTask(this, String.valueOf(i + 1), track, musicManager));
|
||||
}
|
||||
|
||||
ActionRow firstRow = createActionRow(loadTasks);
|
||||
@@ -88,21 +88,19 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
|
||||
}
|
||||
|
||||
// Load playlist normally
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
if (MusicUtils.connect(context)) {
|
||||
manager.getQueueManager().queue(tracks, context.getGuildUser());
|
||||
if (MusicUtils.connect(context, musicManager)) {
|
||||
musicManager.getQueueManager().queue(tracks, context.getGuildUser());
|
||||
|
||||
context.edit(MessageFunction.buildAddingPlaylistMessage(tracks, playlist.getName(), context.getGuildUser(), sourceType));
|
||||
}
|
||||
}
|
||||
|
||||
private ActionRow commonButtons(List<AudioTrack> tracks, String addAllText, String cancelText) {
|
||||
Task accept = AcceptButton.createSuccessButton(this, addAllText, context -> {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
Task accept = AcceptButton.createSuccessButton(taskManager(), this, addAllText, context -> {
|
||||
Member member = context.getGuildUser();
|
||||
|
||||
if (MusicUtils.connect(context)) {
|
||||
manager.getQueueManager().queue(tracks, member);
|
||||
if (MusicUtils.connect(context, musicManager)) {
|
||||
musicManager.getQueueManager().queue(tracks, member);
|
||||
context.reply(MessageFunction.buildAddingPlaylistMessage(tracks, playlist.getName(), member, SourceFinder.AudioSourceType.Youtube));
|
||||
|
||||
return Lifecycle.REMOVE_SELF_AND_CHILDREN;
|
||||
@@ -111,7 +109,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
|
||||
return Lifecycle.KEEP;
|
||||
}).setEmoji(Emoji.fromFormatted("U+1F60E"));
|
||||
|
||||
Task cancel = CancelButton.create(this, cancelText, context -> Lifecycle.REMOVE_SELF_AND_CHILDREN)
|
||||
Task cancel = CancelButton.create(taskManager(), this, cancelText, context -> Lifecycle.REMOVE_SELF_AND_CHILDREN)
|
||||
.setEmoji(Emoji.fromFormatted("U+1F92E"));
|
||||
|
||||
return createActionRow(accept, cancel);
|
||||
@@ -125,7 +123,7 @@ public class LoadPlaylistTask extends ButtonTask implements WithChildren, Deleta
|
||||
@Override
|
||||
public void onInteractionDelete() {
|
||||
for (Task child : getChildTasks()) {
|
||||
TaskManager.getInstance().removeTask(child);
|
||||
taskManager().removeTask(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ package me.bobert.netneutrality.tasks.music;
|
||||
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.music.SourceFinder;
|
||||
import me.bobert.netneutrality.tasks.generic.AcceptButton;
|
||||
import me.bobert.netneutrality.utils.MessageFunction;
|
||||
import me.bobert.netneutrality.core.tasks.generic.AcceptButton;
|
||||
import me.bobert.netneutrality.shared.util.MessageFunction;
|
||||
import me.bobert.netneutrality.utils.MusicUtils;
|
||||
import net.dv8tion.jda.api.components.buttons.ButtonStyle;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
@@ -17,13 +17,12 @@ import net.dv8tion.jda.api.entities.Member;
|
||||
public class LoadTrackFromPlaylistTask extends AcceptButton<LoadPlaylistTask> {
|
||||
private final AudioTrack track;
|
||||
|
||||
public LoadTrackFromPlaylistTask(LoadPlaylistTask parentTask, String buttonLabel, AudioTrack track) {
|
||||
super(parentTask, ButtonStyle.PRIMARY, buttonLabel, context -> {
|
||||
MusicManager manager = MusicManager.get(context.getGuild());
|
||||
public LoadTrackFromPlaylistTask(LoadPlaylistTask parentTask, String buttonLabel, AudioTrack track, MusicManager musicManager) {
|
||||
super(musicManager.getTaskManager(), parentTask, ButtonStyle.PRIMARY, buttonLabel, context -> {
|
||||
Member member = context.getGuildUser();
|
||||
|
||||
if (MusicUtils.connect(context)) {
|
||||
manager.getQueueManager().queue(track, member);
|
||||
if (MusicUtils.connect(context, musicManager)) {
|
||||
musicManager.getQueueManager().queue(track, member);
|
||||
context.reply(MessageFunction.buildAddingTrackMessage(track, member, SourceFinder.AudioSourceType.Youtube));
|
||||
|
||||
return Lifecycle.REMOVE_SELF_AND_CHILDREN;
|
||||
|
||||
+13
-2
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.tasks.scheduled;
|
||||
package me.bobert.netneutrality.tasks.music;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
@@ -10,8 +10,19 @@ import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.music.TrackScheduler;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class MusicManagerCleanup implements ScheduledTask {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public MusicManagerCleanup(MusicService musicService) {
|
||||
Objects.requireNonNull(musicService, "MusicService must not be null");
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MusicManagerCleanup.class);
|
||||
|
||||
@Override
|
||||
@@ -27,7 +38,7 @@ public class MusicManagerCleanup implements ScheduledTask {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for (MusicManager manager : MusicManager.getManagers()) {
|
||||
for (MusicManager manager : musicService.getManagers()) {
|
||||
processManager(manager);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
+12
-2
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.tasks.scheduled;
|
||||
package me.bobert.netneutrality.tasks.music;
|
||||
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
@@ -8,14 +8,24 @@ package me.bobert.netneutrality.tasks.scheduled;
|
||||
|
||||
import me.bobert.netneutrality.core.tasks.ScheduledTask;
|
||||
import me.bobert.netneutrality.music.MusicManager;
|
||||
import me.bobert.netneutrality.music.MusicService;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MusicPlayerAutoDisconnect implements ScheduledTask {
|
||||
|
||||
private final MusicService musicService;
|
||||
|
||||
public MusicPlayerAutoDisconnect(MusicService musicService) {
|
||||
Objects.requireNonNull(musicService, "MusicService must not be null");
|
||||
this.musicService = musicService;
|
||||
}
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnect.class);
|
||||
|
||||
@Override
|
||||
@@ -31,7 +41,7 @@ public class MusicPlayerAutoDisconnect implements ScheduledTask {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for (MusicManager manager : MusicManager.getManagers()) {
|
||||
for (MusicManager manager : musicService.getManagers()) {
|
||||
processManager(manager);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user