refactor: change package path to reflect group id of pom.xml

BREAKING CHANGE:
This commit is contained in:
anthorob committed 2024-07-13 23:10:06 -04:00
1 parent 2b08351f6d
commit f4c7e9c703
94 files changed
+373 -372

No files matched your search

@@ -0,0 +1,118 @@
package me.bobert.netneutrality;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import io.sentry.Sentry;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
import net.dv8tion.jda.api.requests.GatewayIntent;
import me.bobert.netneutrality.command.CommandManager;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.db.DBController;
import me.bobert.netneutrality.hooks.SlashCommandHooks;
import me.bobert.netneutrality.hooks.guilds.ServerJoinHandler;
import me.bobert.netneutrality.hooks.message.EditHandler;
import me.bobert.netneutrality.hooks.message.MessageReceivedHandler;
import me.bobert.netneutrality.http.server.WebApi;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.thread.BaseThreadFactory;
import me.bobert.netneutrality.thread.ShutdownThread;
import me.bobert.netneutrality.thread.ThreadManager;
import me.bobert.netneutrality.utils.Settings;
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 JDA jda;
public final static Logger logger = LogManager.getLogger(NetNeutrality.class);
public static final String VERSION = Settings.getInstance().getMavenVersion();
public static void main(String[] args) {
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);
});
}
if ("dev".equals(env)) {
Configurator.setLevel("net.dv8tion.jda", Level.DEBUG);
}
DBController.init();
initJDA();
CommandManager.init();
MusicManager.initPlayerManager();
jda.addEventListener(new MessageReceivedHandler(), new EditHandler(), new ServerJoinHandler());
jda.addEventListener(new SlashCommandHooks());
new WebApi(Integer.parseInt(Settings.getInstance().get("NETNEUTRALITY_API_PORT", "9000")));
jda.getPresence().setActivity(Activity.watching("you"));
Runtime.getRuntime().addShutdownHook(new BaseThreadFactory("shutdown").newThread(new ShutdownThread()));
}
public static void initJDA() {
String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN");
if (token == null) {
DBController.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)
.setActivity(Activity.watching("me load..."))
.build();
jda.awaitReady();
} catch (InvalidTokenException | IllegalArgumentException | InterruptedException e) {
LogHandler.log(e);
System.exit(1);
}
}
public static void shutdown() {
System.out.println("[Net Neutrality] Shutting down...");
MusicManager.disconnectPlayers();
ThreadManager.shutdownExecutor();
disconnect();
DBController.shutdown();
System.out.println("[Net Neutrality] Shutdown complete");
}
public static JDA getJDA() {
return jda;
}
public static User getSelf() {
return jda.getSelfUser();
}
private static void disconnect() {
if (jda != null) {
jda.shutdown();
}
}
}
@@ -0,0 +1,43 @@
package me.bobert.netneutrality.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.MessageCommandContext;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
public abstract class AbstractCommand implements ICommand {
@Override
public boolean isSlashCommand() {
return true;
}
@Override
public DefaultMemberPermissions getSlashCommandPermission() {
return DefaultMemberPermissions.ENABLED;
}
@Override
public boolean canBeExecutedBy(Guild guild, Member member) {
return true;
}
@Override
public boolean canBeExecutedFrom(Guild guild, MessageChannel channel) {
return true;
}
@Deprecated
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
// Prefer slash commands
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,69 @@
package me.bobert.netneutrality.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.db.GSettings;
import me.bobert.netneutrality.utils.UserFunction;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
public abstract class AbstractMusicCommand extends AbstractCommand {
@Override
public boolean needAdmin() {
return false;
}
@Override
public boolean isGuildOnly() {
return true;
}
@Override
public CommandType getType() {
return CommandType.MUSIC;
}
@Override
public boolean canBeExecutedBy(Guild guild, Member member) {
String roleDb = GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE);
if (roleDb != null && !roleDb.isEmpty()) {
if (UserFunction.isAdmin(member)) {
return true;
}
Role neededRole = guild.getRoleById(roleDb);
if (neededRole == null) {
// Role doesn't exist on guild anymore, remove from db
GuildManager.getManager(guild).getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null);
return true;
}
return member.getRoles().contains(neededRole);
}
return true;
}
@Override
public boolean canBeExecutedFrom(Guild guild, MessageChannel channel) {
if (channel.getType() != ChannelType.TEXT)
return false;
String channelDb = GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL);
if (channelDb != null && !channelDb.isEmpty()) {
return channelDb.equals(channel.getId());
}
return true;
}
}
@@ -0,0 +1,64 @@
package me.bobert.netneutrality.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
public class CommandExecutionResult {
public enum Result {
SUCCESS, INFO, WARN, ERROR, NOACTION
}
private final Result result;
private final String message;
private final Exception exception;
private CommandExecutionResult(Result result, String message, Exception exception) {
this.result = result;
this.message = message;
this.exception = exception;
}
private CommandExecutionResult(Result result, String message) {
this(result, message, null);
}
public Result getResult() {
return result;
}
public String getMessage() {
return message;
}
public Exception getException() {
return exception;
}
public static CommandExecutionResult success(String message) {
return new CommandExecutionResult(Result.SUCCESS, message);
}
public static CommandExecutionResult info(String message) {
return new CommandExecutionResult(Result.INFO, message);
}
public static CommandExecutionResult warn(String message) {
return new CommandExecutionResult(Result.WARN, message);
}
public static CommandExecutionResult error(String message, Exception exception) {
return new CommandExecutionResult(Result.ERROR, message, exception);
}
public static CommandExecutionResult error(String message) {
return error(message, null);
}
public static CommandExecutionResult none() {
return new CommandExecutionResult(Result.NOACTION, null);
}
}
@@ -0,0 +1,91 @@
package me.bobert.netneutrality.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.NetNeutrality;
import me.bobert.netneutrality.core.LogHandler;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class CommandManager {
private final static Logger logger = LogManager.getLogger(CommandManager.class);
public static final HashMap<String, AbstractCommand> commands = new HashMap<>();
public static void init() {
initCommands();
registerSlashCommands();
}
public static boolean isValidCommand(String commandString) {
return commands.containsKey(commandString);
}
public static AbstractCommand getCommand(String commandString) {
return commands.get(commandString);
}
private static void initCommands() {
logger.info("Loading commands..");
Reflections reflections = new Reflections("netneutrality.command");
Set<Class<? extends AbstractCommand>> classes = reflections.getSubTypesOf(AbstractCommand.class);
for (Class<? extends AbstractCommand> s : classes) {
try {
if (Modifier.isAbstract(s.getModifiers())) {
continue;
}
AbstractCommand command = s.getConstructor().newInstance();
if (!command.isEnabled())
continue;
if (!commands.containsKey(command.getCommandName())) {
commands.put(command.getCommandName(), command);
}
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
LogHandler.log(e);
}
}
logger.info("Loaded " + commands.size() + " commands!");
}
private static void registerSlashCommands() {
CommandListUpdateAction commandListUpdateAction = NetNeutrality.getJDA().updateCommands();
List<CommandData> slashCommands = new ArrayList<>();
for (AbstractCommand command : commands.values()) {
if (!command.isEnabled() || !command.isSlashCommand()) {
continue;
}
SlashCommandData data = Commands.slash(command.getCommandName(), command.getDescription());
data.setDefaultPermissions(command.getSlashCommandPermission());
data.setGuildOnly(command.isGuildOnly());
command.registerSlashCommandOptions(data);
slashCommands.add(data);
}
commandListUpdateAction.addCommands(slashCommands).queue();
}
}
@@ -0,0 +1,26 @@
package me.bobert.netneutrality.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
public enum CommandType {
MUSIC("Music", 1), ADMINISTRATION("Administration", 0), UTILITY("Utility", 5), CRYPTO("Crypto", 6), UNLISTED("", -1);
private final String description;
private final int id;
CommandType(String description, int id) {
this.description = description;
this.id = id;
}
public String getDescription() {
return description;
}
public int getId() {
return id;
}
}
@@ -0,0 +1,44 @@
package me.bobert.netneutrality.command;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public interface ICommand {
boolean needAdmin();
boolean isEnabled();
boolean isGuildOnly();
boolean isSlashCommand();
String getCommandName();
String getDescription();
DefaultMemberPermissions getSlashCommandPermission();
void registerSlashCommandOptions(SlashCommandData data);
CommandType getType();
boolean canBeExecutedBy(Guild guild, Member member);
boolean canBeExecutedFrom(Guild guild, MessageChannel channel);
@Deprecated
CommandExecutionResult execute(MessageCommandContext context);
CommandExecutionResult execute(SlashCommandContext context);
}
@@ -0,0 +1,113 @@
package me.bobert.netneutrality.command.admin;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.db.DBController;
import me.bobert.netneutrality.db.controllers.DBBlacklist;
import me.bobert.netneutrality.db.entities.OBlacklist;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import java.util.ArrayList;
public class BlacklistCommand extends AbstractCommand {
@Override
public boolean needAdmin() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isGuildOnly() {
return true;
}
@Override
public String getCommandName() {
return "blacklist";
}
@Override
public String getDescription() {
return "Manages the blacklist of your server.";
}
@Override
public DefaultMemberPermissions getSlashCommandPermission() {
return DefaultMemberPermissions.DISABLED;
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addSubcommands(
new SubcommandData("get", "Get all blacklisted words for this guild"),
new SubcommandData("add", "Add a word to the blacklist")
.addOption(OptionType.STRING, "word", "Word to add"),
new SubcommandData("remove", "Remove a word from the blacklist")
.addOption(OptionType.STRING, "word", "Word to remove")
);
}
@Override
public CommandType getType() {
return CommandType.ADMINISTRATION;
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
String sub = context.getEvent().getFullCommandName().split(" ")[1];
DBBlacklist controller = DBController.get(DBBlacklist.class);
String word = context.getEvent().getOption("word", "", OptionMapping::getAsString);
context.ackAndWait(true);
switch (sub) {
case "get" -> {
context.getHook().editOriginal("Retrieving list, please wait...").queue(t -> {
ArrayList<OBlacklist> blacklists = new ArrayList<>();
try {
blacklists = controller.getBlacklistForGuild(context.getGuild());
} catch (Exception e) {
LogHandler.log(e);
}
StringBuilder sb = new StringBuilder();
for (OBlacklist bl : blacklists) {
sb.append(bl.getWord()).append(" -> ").append(bl.getEnabled() ? "enabled" : "disabled").append("\n");
}
t.editMessage("List of blacklisted words for this server [" + context.getGuild().getName() + "] :\n" + (blacklists.isEmpty() ? "No word found" : sb.toString())).queue();
});
}
case "add" -> controller.add(word, context);
case "remove" -> controller.delete(word, context);
case "disable" -> controller.disable(word, context);
case "enable" -> controller.enable(word, context);
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,76 @@
package me.bobert.netneutrality.command.basic;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.utils.MessageFunction;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import java.util.List;
public class HelpCommand extends AbstractCommand {
@Override
public boolean needAdmin() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isGuildOnly() {
return false;
}
@Override
public String getCommandName() {
return "help";
}
@Override
public String getDescription() {
return "Get every command with its usage";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandType getType() {
return CommandType.UTILITY;
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
if (context.getArgs().length == 0) {
List<MessageEmbed> help = MessageFunction.buildHelpMessage(context.getGuildUser());
context.reply(new MessageCreateBuilder().addContent(context.getDiscordUser().getAsMention()).addContent(" help has been sent your way!").build());
context.replyPrivate(help);
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
List<MessageEmbed> help = MessageFunction.buildHelpMessage(context.getGuildUser());
context.sendMessageInPrivate(help);
return CommandExecutionResult.success("Help has been sent in DM!");
}
}
@@ -0,0 +1,65 @@
package me.bobert.netneutrality.command.basic;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class PingCommand extends AbstractCommand {
@Override
public boolean needAdmin() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isGuildOnly() {
return false;
}
@Override
public String getCommandName() {
return "ping";
}
@Override
public String getDescription() {
return "Pong!";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandType getType() {
return CommandType.UTILITY;
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
if (context.getArgs().length == 0)
context.reply(String.format("Pong! [%d ms]", context.getJDA().getGatewayPing()));
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
return CommandExecutionResult.success(String.format("Pong! [%d ms]", context.getJDA().getGatewayPing()));
}
}
@@ -0,0 +1,289 @@
package me.bobert.netneutrality.command.bot;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.db.GSettings;
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;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import java.util.List;
import java.util.Map;
public class ConfigureCommand extends AbstractCommand {
@Override
public boolean needAdmin() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isGuildOnly() {
return true;
}
@Override
public String getCommandName() {
return "config";
}
@Override
public String getDescription() {
return "Used to configure the bot on this server";
}
@Override
public DefaultMemberPermissions getSlashCommandPermission() {
return DefaultMemberPermissions.DISABLED;
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addSubcommands(
new SubcommandData("music-volume", "Volume for the music player")
.addOptions(new OptionData(OptionType.INTEGER, "volume", "Integer between 0 and 100", true)
.setRequiredRange(0, 100)),
new SubcommandData("error-logging", "Send errors to #error and/or #log channel")
.addOption(OptionType.BOOLEAN, "debug", "Enable or disable the fonctionnality", true),
new SubcommandData("music-role", "Role needed to use music commands")
.addOption(OptionType.ROLE, "role", "Name of role", true),
new SubcommandData("music-channel", "Only allow music commands on this channel")
.addOption(OptionType.CHANNEL, "channel", "Leave empty to allow everywhere")
);
}
@Override
public CommandType getType() {
return CommandType.ADMINISTRATION;
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
String[] args = context.getArgs();
GuildManager guildManager = GuildManager.getManager(context.getGuild());
if (args.length > 0) {
// Role for music commands
if (context.getArgument(0).equals("music-role")) {
if (args.length == 1) {
String roleDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE);
Role role = null;
if (!roleDb.isEmpty()) {
role = context.getGuild().getRoleById(roleDb);
}
context.reply("Current role needed to use music commands (default is everyone): %s", (role == null ? "Everyone" : role.getAsMention()));
context.reply("Reset role using 'conf music-role reset' or set it using 'conf musicrole @'");
}
if (args.length == 2) {
if (context.getArgument(1).equals("reset")) {
if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE).isEmpty()) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null);
}
context.reply("Successful reset of the role");
}
List<Role> roles = context.getMessage().getMentions().getRoles();
if (roles.size() == 1) {
Role role = roles.get(0);
if (role.getGuild().getId().equals(context.getGuild().getId())) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, role.getId());
context.reply("Successfully set %s as the role needed for music command", role.getAsMention());
}
}
}
}
// Role for music commands
if (context.getArgument(0).equals("music-channel")) {
if (args.length == 1) {
String channelDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL);
TextChannel channel = null;
if (!channelDb.isEmpty()) {
channel = context.getGuild().getTextChannelById(channelDb);
}
context.reply("Current channel where music commands can be executed (default is everywhere): %s", (channel == null ? "Everywhere" : channel.getAsMention()));
context.reply("Reset channel using 'conf music-channel reset' or set it using 'conf music-channel #'");
}
if (args.length == 2) {
if (context.getArgument(1).equals("reset")) {
if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL).isEmpty()) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null);
}
context.reply("Successful reset of the channel");
}
List<TextChannel> channels = context.getMessage().getMentions().getChannels(TextChannel.class);
if (channels.size() == 1) {
TextChannel channel = channels.get(0);
if (channel.getGuild().getId().equals(context.getGuild().getId())) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, channel.getId());
context.reply("Successfully set %s as the channel required to execute music command", channel.getAsMention());
}
}
}
}
// Default volume for music player
if (context.getArgument(0).equals("music-volume")) {
if (args.length == 1) {
int volume = Integer.parseInt(guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_VOLUME));
context.reply("Default volume for music player: %d", volume);
context.reply("Set using 'conf music-volume <value between 0 and 100>'");
}
if (args.length == 2) {
try {
int volume = Integer.parseInt(args[1]);
if (volume > 0 && volume <= 100) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume));
MusicManager.get(context.getGuild()).setVolume(volume);
context.reply("Default volume for music player is now %d", volume);
} else
throw new NumberFormatException();
} catch (NumberFormatException e) {
context.reply("Not a value between 0 and 100");
}
}
}
// Debug
if (context.getArgument(0).equals("debug")) {
if (args.length == 1) {
boolean debug = guildManager.isDebugEnabled();
context.reply("Error logging is currently %s for this guild.", (debug ? "enabled" : "disabled"));
context.reply("Use 'conf debug 0/1' to toggle between states (0 = disabled, 1 = enabled).");
}
if (args.length == 2) {
String str = args[1];
boolean debug = guildManager.isDebugEnabled();
if (str.equals("0") || str.equalsIgnoreCase("disable")) {
debug = false;
}
if (str.equals("1") || str.equalsIgnoreCase("enable")) {
debug = true;
}
guildManager.setDebug(debug);
context.reply("Error logging is now %s for this guild", (debug ? "enabled" : "disabled"));
}
}
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
String sub = context.getEvent().getFullCommandName().split(" ")[1];
GuildManager guildManager = GuildManager.getManager(context.getGuild());
context.ackAndWait(true);
if ("music-role".equals(sub)) {
Role role = context.getEvent().getOption("role", OptionMapping::getAsRole);
assert role != null;
if (role.getPosition() == -1) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null);
return CommandExecutionResult.success("Everyone can now use music commands");
}
if (role.getGuild().getId().equals(context.getGuild().getId())) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, role.getId());
return CommandExecutionResult.success(String.format("Successfully set %s as the role needed for music command", role.getAsMention()));
}
}
if ("music-channel".equals(sub)) {
GuildChannelUnion channel = context.getEvent().getOption("channel", OptionMapping::getAsChannel);
if (channel == null) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null);
return CommandExecutionResult.success("No channel restrictions for music commands");
}
if (channel instanceof TextChannel) {
if (channel.getGuild().getId().equals(context.getGuild().getId())) {
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, channel.getId());
return CommandExecutionResult.success(String.format("Successfully set %s as the channel required to execute music command", channel.getAsMention()));
}
}
}
if ("music-volume".equals(sub)) {
int volume = context.getEvent().getOption("volume", 30, OptionMapping::getAsInt);
guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume));
MusicManager.get(context.getGuild()).setVolume(volume);
return CommandExecutionResult.success(String.format("Default volume for music player is now %d", volume));
}
if ("debug".equals(sub)) {
boolean debug = context.getEvent().getOption("debug", true, OptionMapping::getAsBoolean);
guildManager.setDebug(debug);
return CommandExecutionResult.success(String.format("Error logging is now %s for this guild", (debug ? "enabled" : "disabled")));
}
if ("show-all".equals(sub)) {
Map<String, String> settings = guildManager.getGuildConfigManager().getSettings();
MessageCreateBuilder builder = new MessageCreateBuilder();
for (GSettings o : GSettings.values()) {
String value = o.getDefaultValue();
if (settings.containsKey(o.getName())) {
value = settings.get(o.getName());
}
builder.addContent(o.getName()).addContent(": ").addContent(value);
builder.addContent("\n");
}
context.replyEphemeral(builder.build());
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,151 @@
package me.bobert.netneutrality.command.crypto;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.http.WebModule;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;
public class CryptoPriceCommand extends AbstractCommand {
private final String baseURL = "https://min-api.cryptocompare.com/data/price";
@Override
public boolean needAdmin() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isGuildOnly() {
return false;
}
@Override
public String getCommandName() {
return "crypto";
}
@Override
public String getDescription() {
return "Get the price of cryptocurrencies";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addOption(OptionType.STRING, "symbol", "The crypto symbol to fetch price for", true);
data.addOption(OptionType.STRING, "currency", "In which currency should the price be displayed");
}
@Override
public CommandType getType() {
return CommandType.CRYPTO;
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
String[] args = context.getArgs();
String currency = "USD";
if (args.length > 0) {
if (args.length == 2) {
try {
currency = Currency.getInstance(args[1].toUpperCase()).getCurrencyCode();
} catch (IllegalArgumentException exception) {
return CommandExecutionResult.warn("Please enter a valid currency");
}
}
String crypto = args[0].toUpperCase();
String finalCurrency = currency;
context.getChannel().sendMessage("Retreiving price, please wait...").queue(t -> {
String price;
Locale usa = new Locale("en", "US");
NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(usa);
dollarFormat.setMaximumFractionDigits(24);
try {
WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request();
JSONObject json = http.getAsObject();
if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) {
t.editMessage("Crypto doesn't exist.. yet").queue();
return;
}
price = dollarFormat.format(json.getDouble(finalCurrency));
t.editMessage(String.format("The current price of %s in %s is %s", crypto, finalCurrency, price)).queue();
} catch (JSONException | IllegalArgumentException | ArithmeticException e) {
t.editMessage(String.format("Error getting price of %s!", crypto)).queue();
LogHandler.log(e);
}
});
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
Locale usa = new Locale("en", "US");
NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(usa);
dollarFormat.setMaximumFractionDigits(24);
String symbol = context.getEvent().getOption("symbol", OptionMapping::getAsString);
String currency = context.getEvent().getOption("currency", "USD", OptionMapping::getAsString);
try {
currency = Currency.getInstance(currency).getCurrencyCode();
} catch (IllegalArgumentException exception) {
return CommandExecutionResult.error("Please enter a valid currency");
}
String finalCurrency = currency.toUpperCase();
context.getEvent().deferReply().queue(interactionHook -> {
try {
WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, symbol, finalCurrency)).request();
JSONObject json = http.getAsObject();
if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) {
interactionHook.editOriginal("Crypto doesn't exist.. yet").queue();
return;
}
double value = json.getDouble(finalCurrency);
String price = dollarFormat.format(value);
interactionHook.editOriginal(String.format("The current price of %s in %s is %s", symbol, finalCurrency, price)).queue();
} catch (JSONException | IllegalArgumentException | ArithmeticException e) {
interactionHook.editOriginal(String.format("Error getting price of %s!", symbol)).queue();
LogHandler.log(e);
}
});
return CommandExecutionResult.none();
}
}
@@ -0,0 +1 @@
*.java
@@ -0,0 +1,61 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class BitconnectCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "bitconnect";
}
@Override
public String getDescription() {
return "Ready to invest?";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandType getType() {
return CommandType.UNLISTED;
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, false, true);
context.deleteMessage();
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
context.ackAndWait();
player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, false, true);
return CommandExecutionResult.success("Ready to invest?");
}
}
@@ -0,0 +1,99 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class ClearQueueCommand extends AbstractMusicCommand {
@Override
public boolean needAdmin() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "clearqueue";
}
@Override
public String getDescription() {
return "Empties the music queue";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (!MusicUtils.checkIfConnected(player, context))
return CommandExecutionResult.none();
if (context.getArgs().length == 0) {
context.reply("Select the track you want to delete from the queue (track# or all)");
context.reply(MessageFunction.buildViewQueueMessage(player, true));
}
if (context.getArgs().length > 0) {
int queueSize = player.getQueueSize();
if (context.getArgument(0).equals("all")) {
if (queueSize > 0) {
player.getScheduler().getQueueManager().clearQueue();
context.reply(String.format("Removed %d track%s from the queue", queueSize--, (queueSize > 1 ? "s" : "")));
} else {
context.reply("Queue is empty!");
}
} else {
try {
int num = Integer.parseInt(context.getArgument(0));
if (num > 0) {
return player.getScheduler().getQueueManager().removeFromQueue(num);
} else {
context.reply("Number out of range");
}
} catch (NumberFormatException e) {
context.reply("Please enter a number");
}
}
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
context.ackAndWait();
if (!MusicUtils.checkIfConnected(manager, context))
return CommandExecutionResult.none();
return manager.getScheduler().getQueueManager().clearQueue();
}
}
@@ -0,0 +1,64 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class CurrentlyPlayingCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "nowplaying";
}
@Override
public String getDescription() {
return "View currently playing track";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
// no arguments
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (MusicUtils.checkIfConnected(player, context) && player.isCurrentlyPlaying()) {
context.reply(MessageFunction.buildCurrentlyPlayingMessage(player));
} else {
context.reply("Bot isn't playing any tracks right now");
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
context.ackAndWait();
if (MusicUtils.checkIfConnected(manager, context) && manager.isCurrentlyPlaying()) {
context.reply(MessageFunction.buildCurrentlyPlayingMessage(manager));
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,90 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.exceptions.CommandExecutionException;
import me.bobert.netneutrality.music.MusicManager;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class JoinCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "join";
}
@Override
public String getDescription() {
return "Make the bot join your current voice channel";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (context.getGuildUser().getVoiceState() == null) {
throw new CommandExecutionException("Voice state is null, this should not happen", context, null);
}
if (!context.getGuildUser().getVoiceState().inAudioChannel()) {
return CommandExecutionResult.warn("You are not in a voice channel");
}
if (player.isConnected()) {
return CommandExecutionResult.warn("Player is already in a voice channel");
}
VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel();
if (vChannel != null) {
player.connect(vChannel);
context.reply("Joining current voice channel");
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
context.ackAndWait();
if (context.getGuildUser().getVoiceState() == null) {
throw new CommandExecutionException("Voice state is null, this should not happen", context, null);
}
if (!context.getGuildUser().getVoiceState().inAudioChannel()) {
return CommandExecutionResult.warn("You are not in a voice channel");
}
if (player.isConnected()) {
return CommandExecutionResult.warn("Player is already in a voice channel");
}
VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel();
if (vChannel != null) {
player.connect(vChannel);
context.reply("Joining current voice channel");
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,63 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class LeaveCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "leave";
}
@Override
public String getDescription() {
return "Make the bot leave the voice channel";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (MusicUtils.checkIfConnected(player, context)) {
player.leave();
context.reply("Leaving voice channel and clearing queue");
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
context.ackAndWait();
if (MusicUtils.checkIfConnected(player, context)) {
player.leave();
context.reply("Leaving voice channel and clearing queue");
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,60 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class PlayCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "play";
}
@Override
public String getDescription() {
return "Get the party going!";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addOption(OptionType.STRING, "query", "URL or search query", true);
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (context.getArgs().length > 0) {
player.play(context.getMessageText(), context, false, false);
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
String query = context.getEvent().getOption("query", OptionMapping::getAsString);
context.ackAndWait();
manager.play(query, context, false, false);
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,163 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
public class QueueCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "queue";
}
@Override
public String getDescription() {
return "Manage the music queue";
}
@Override
public DefaultMemberPermissions getSlashCommandPermission() {
return DefaultMemberPermissions.DISABLED;
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addSubcommands(
new SubcommandData("clear-all", "Clear all queued tracks"),
new SubcommandData("delete-track", "Remove a single track from the queue")
.addOptions(new OptionData(OptionType.INTEGER, "position", "Position of track", true)
.setMinValue(1)),
new SubcommandData("show-queue", "Display current queue"),
new SubcommandData("shuffle", "Shuffle the queue"),
new SubcommandData("from-user", "View tracks added by a user")
.addOption(OptionType.USER, "user", "Which user?", true),
new SubcommandData("remove-from-user", "Remove all tracks added by a user")
.addOption(OptionType.USER, "user", "Which user?", true)
);
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (!MusicUtils.checkIfConnected(player, context))
return CommandExecutionResult.none();
if (context.getArgs().length == 0) {
context.reply("Select the track you want to delete from the queue (track# or all)");
context.reply(MessageFunction.buildViewQueueMessage(player, true));
}
if (context.getArgs().length > 0) {
int queueSize = player.getQueueSize();
if (context.getArgument(0).equals("all")) {
if (queueSize > 0) {
player.getScheduler().getQueueManager().clearQueue();
context.reply(String.format("Removed %d track%s from the queue", queueSize--, (queueSize > 1 ? "s" : "")));
} else {
context.reply("Queue is empty!");
}
} else {
try {
int num = Integer.parseInt(context.getArgument(0));
if (num > 0) {
return player.getScheduler().getQueueManager().removeFromQueue(num);
} else {
context.reply("Number out of range");
}
} catch (NumberFormatException e) {
context.reply("Please enter a number");
}
}
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
context.ackAndWait();
String sub = context.getEvent().getFullCommandName().split(" ")[1];
if (!MusicUtils.checkIfConnected(manager, context)) {
return CommandExecutionResult.none();
}
switch (sub) {
case "clear-all" -> {
return manager.getScheduler().getQueueManager().clearQueue();
}
case "delete-track" -> {
int position = context.getEvent().getOption("position", 0, OptionMapping::getAsInt);
return manager.getScheduler().getQueueManager().removeFromQueue(position);
}
case "show-queue" -> context.reply(MessageFunction.buildViewQueueMessage(manager, true));
case "shuffle" -> {
return manager.getScheduler().getQueueManager().shuffle();
}
case "from-user" -> {
Member user = context.getEvent().getOption("user", OptionMapping::getAsMember);
if (user == null) {
context.reply("The user isn't from this guild");
break;
}
context.reply(MessageFunction.buildUserTracksMessage(manager, user));
}
case "remove-from-user" -> {
Member member = context.getEvent().getOption("user", OptionMapping::getAsMember);
if (member == null) {
context.reply("The user isn't from this guild");
break;
}
int size = manager.getScheduler()
.getQueueManager()
.getQueueWithFilter(trackInfo -> member.equals(trackInfo.getRequester()))
.size();
if (size == 0) {
context.reply("No tracks added by user %s", member.getEffectiveName());
} else {
manager.getScheduler().getQueueManager().removeTracksFromUser(member);
context.reply("Removed all tracks from user %s", member.getEffectiveName());
}
}
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,48 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class ShuffleCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "shuffle";
}
@Override
public String getDescription() {
return "Shuffle the music queue";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
context.ackAndWait();
if (MusicUtils.checkIfConnected(manager, context)) {
return manager.getScheduler().getQueueManager().shuffle();
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,84 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MusicUtils;
import me.bobert.netneutrality.utils.UserFunction;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class SkipCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "skip";
}
@Override
public String getDescription() {
return "Skip to the next track in the queue";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addOptions(new OptionData(OptionType.INTEGER, "skip", "Number of tracks to skip")
.setMinValue(1));
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (!MusicUtils.checkIfConnected(player, context))
return CommandExecutionResult.none();
if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) {
if (UserFunction.isAdmin(context.getGuildUser()) && context.getArgs().length > 0) {
try {
int skips = Integer.parseInt(context.getArgument(0));
player.getScheduler().getQueueManager().skip(skips);
context.reply("Skipping %s track%s", skips, (skips > 1 ? "s" : ""));
} catch (NumberFormatException e) {
context.reply("Please enter how many track(s) to skip");
}
} else {
player.getScheduler().getQueueManager().skip(1);
context.reply("Skipping current track");
}
} else {
player.voteSkip(context);
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
int skips = context.getEvent().getOption("skip", 1, OptionMapping::getAsInt);
context.ackAndWait();
if (MusicUtils.checkIfConnected(manager, context)) {
return manager.getScheduler().getQueueManager().skip(skips);
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,68 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MusicUtils;
import me.bobert.netneutrality.utils.UserFunction;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class StopCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "stop";
}
@Override
public String getDescription() {
return "Stop the music player, keeping the current queue.";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (!MusicUtils.checkIfConnected(player, context))
return CommandExecutionResult.none();
// Need a resume command before
if (UserFunction.isAdmin(context.getGuildUser())) {
player.stopTrack();
context.reply("Stopping current track");
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
context.ackAndWait();
if (MusicUtils.checkIfConnected(manager, context)) {
manager.stopTrack();
return CommandExecutionResult.success("Stopping music playback");
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,72 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MessageFunction;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class ViewQueueCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "viewqueue";
}
@Override
public String getDescription() {
return "Get the current queue for the music player";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (MusicUtils.checkIfConnected(player, context)) {
int queueSize = player.getQueueSize();
if (queueSize > 0) {
context.reply(MessageFunction.buildViewQueueMessage(player, true));
} else {
context.reply("Queue is empty!");
}
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
context.ackAndWait();
if (MusicUtils.checkIfConnected(manager, context)) {
int queueSize = manager.getQueueSize();
if (queueSize > 0) {
context.reply(MessageFunction.buildViewQueueMessage(manager, true));
} else {
return CommandExecutionResult.warn("Queue is empty!");
}
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,81 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MusicUtils;
import me.bobert.netneutrality.utils.UserFunction;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class VolumeCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "volume";
}
@Override
public String getDescription() {
return "Sets the volume for the music player";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
data.addOptions(new OptionData(OptionType.INTEGER, "volume", "Playback volume for this session", true)
.setRequiredRange(0, 100));
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager player = MusicManager.get(context.getGuild());
if (!MusicUtils.checkIfConnected(player, context))
return CommandExecutionResult.none();
if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) {
try {
int volume = Integer.parseInt(context.getArgument(0));
if (volume > 0 && volume <= 100) {
player.setVolume(volume);
context.reply("Volume set to %s", volume);
} else
throw new NumberFormatException();
} catch (NumberFormatException e) {
context.reply("Please enter a value from 0 to 100");
}
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
int volume = context.getEvent().getOption("volume", 30, OptionMapping::getAsInt);
if (MusicUtils.checkIfConnected(manager, context)) {
manager.setVolume(volume);
return CommandExecutionResult.success(String.format("Volume set to %s", volume));
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,59 @@
package me.bobert.netneutrality.command.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractMusicCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.utils.MusicUtils;
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
public class VoteSkipCommand extends AbstractMusicCommand {
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getCommandName() {
return "voteskip";
}
@Override
public String getDescription() {
return "Cast a vote to skip the currently playing track";
}
@Override
public void registerSlashCommandOptions(SlashCommandData data) {
}
@Override
public CommandExecutionResult execute(MessageCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
if (MusicUtils.checkIfConnected(manager, context)) {
manager.voteSkip(context);
}
return CommandExecutionResult.none();
}
@Override
public CommandExecutionResult execute(SlashCommandContext context) {
MusicManager manager = MusicManager.get(context.getGuild());
if (MusicUtils.checkIfConnected(manager, context)) {
return manager.voteSkip(context);
}
return CommandExecutionResult.none();
}
}
@@ -0,0 +1,71 @@
package me.bobert.netneutrality.core;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.TrackScheduler;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
public class BotState {
public static boolean SAFE = false;
//TODO: is it needed?
private static void saveGuildsState() {
Map<String, GuildManager> managers = GuildManager.getManagers();
JSONObject main = new JSONObject();
JSONArray guildsArray = new JSONArray();
for (GuildManager manager : managers.values()) {
MusicManager musicManager = MusicManager.get(manager.getGuild());
TrackScheduler trackScheduler = musicManager.getScheduler();
JSONObject guild = new JSONObject();
JSONObject music = new JSONObject();
guild.put("id", manager.getGuild().getId());
MessageChannel currentChannel = trackScheduler.getTextChannel();
if (currentChannel == null) {
music.put("current_channel", "null");
} else {
music.put("current_channel", currentChannel.getId());
}
guild.put("music", music);
guildsArray.put(guild);
}
main.put("guilds", guildsArray);
File state = new File("state.json");
try {
if (!state.exists()) {
state.createNewFile();
}
try (FileWriter writer = new FileWriter(state)) {
writer.write(main.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void dump() {
if (!SAFE) {
saveGuildsState();
}
}
}
@@ -0,0 +1,112 @@
package me.bobert.netneutrality.core;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import io.sentry.Sentry;
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.command.CommandManager;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.exceptions.CommandExecutionException;
import me.bobert.netneutrality.utils.UserFunction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import static me.bobert.netneutrality.command.CommandExecutionResult.Result.ERROR;
import static me.bobert.netneutrality.command.CommandExecutionResult.Result.NOACTION;
public class CommandProcessor {
private final static Logger logger = LogManager.getLogger(CommandProcessor.class);
private final String commandName;
private final CommandContext context;
public CommandProcessor(String commandName, CommandContext context) {
this.commandName = commandName;
this.context = context;
}
public void dispatch() {
if (!CommandManager.isValidCommand(commandName)) {
return;
}
AbstractCommand command = CommandManager.getCommand(commandName);
logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild().getId());
if (context instanceof SlashCommandContext slashCommandContext)
logger.debug("SlashCommandInteractionId: {}", slashCommandContext.getEvent().getInteraction().getId());
configureSentry(command);
try {
if (context instanceof MessageCommandContext && command.needAdmin() && !UserFunction.isAdmin(context.getGuildUser())) {
context.reply("You are not powerful enough, scrub");
return;
}
if (command.canBeExecutedBy(context.getGuild(), context.getGuildUser())) {
if (command.canBeExecutedFrom(context.getGuild(), context.getChannel())) {
CommandExecutionResult commandExecutionResult = null;
if (context instanceof SlashCommandContext slashCommandContext) {
commandExecutionResult = command.execute(slashCommandContext);
}
if (context instanceof MessageCommandContext messageCommandContext) {
commandExecutionResult = command.execute(messageCommandContext);
}
if (commandExecutionResult == null) {
throw new RuntimeException("Command type is not implemented.");
}
CommandExecutionResult.Result result = commandExecutionResult.getResult();
String message = commandExecutionResult.getMessage();
if (result == NOACTION) {
return;
}
if (result == ERROR) {
Exception exception = commandExecutionResult.getException();
logger.error("Error from command {}: {}", command.getCommandName(), message, exception);
}
context.reply(message);
}
}
//TODO: make better
} catch (CommandExecutionException exception) {
LogHandler.log(exception);
} catch (Exception exception) {
LogHandler.log(new CommandExecutionException("Error during command execution", context, exception));
}
}
private void configureSentry(AbstractCommand command) {
Sentry.configureScope(scope -> {
scope.setContexts("command", command.getCommandName());
scope.setContexts("isAdmin", UserFunction.isAdmin(context.getGuildUser()));
scope.setContexts("canBeExecuted", command.canBeExecutedBy(context.getGuild(), context.getGuildUser()));
scope.setTag("channel", context.getChannel().getId());
if (context instanceof SlashCommandContext slashCommandContext)
scope.setTag("interactionId", slashCommandContext.getEvent().getInteraction().getId());
if (context instanceof MessageCommandContext messageCommandContext)
scope.setTag("message", messageCommandContext.getMessage().getId());
scope.setTag("guild", context.getGuild().getId());
});
}
}
@@ -0,0 +1,97 @@
package me.bobert.netneutrality.core;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.db.GSettings;
import me.bobert.netneutrality.db.entities.OGuild;
import me.bobert.netneutrality.db.entities.OGuildSetting;
import me.bobert.netneutrality.utils.JpaUtils;
import net.dv8tion.jda.api.entities.Guild;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.Session;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class GuildConfigManager {
private static final Logger logger = LogManager.getLogger(GuildConfigManager.class);
private final Guild guild;
private final OGuild guildEntity;
private final Map<String, String> settingsCache = new HashMap<>();
public GuildConfigManager(Guild guild, OGuild guildEntity) {
this.guild = guild;
this.guildEntity = guildEntity;
reloadSettingsCache();
}
public Map<String, String> getSettings() {
return settingsCache;
}
public String getSetting(GSettings setting) {
String value = settingsCache.get(setting.getName());
if (value == null) {
value = setting.getDefaultValue();
}
return value;
}
public void setSettingValue(GSettings setting, String value) {
Session session = SessionManager.getSessionFactory().openSession();
JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId());
OGuildSetting oGuildSetting = guildEntity.getSettingByName(setting.getName());
session.beginTransaction();
if (value == null || setting.getDefaultValue().equals(value)) {
if (oGuildSetting != null) {
session.delete(oGuildSetting);
}
} else {
if (oGuildSetting == null) {
oGuildSetting = new OGuildSetting();
oGuildSetting.setGuild(guildEntity);
oGuildSetting.setName(setting.getName());
oGuildSetting.setValue(value);
session.save(oGuildSetting);
} else {
oGuildSetting.setValue(value);
session.update(oGuildSetting);
}
}
session.getTransaction().commit();
session.close();
reloadSettingsCache();
}
public void reloadSettingsCache() {
logger.debug("Reloading settings cache for guild: " + guild.getId());
settingsCache.clear();
JpaUtils.getWithSession(guildEntity, session -> {
Set<OGuildSetting> settingSet = guildEntity.getGuildSettings();
for (OGuildSetting setting : settingSet) {
settingsCache.put(setting.getName(), setting.getValue());
}
});
}
}
@@ -0,0 +1,179 @@
package me.bobert.netneutrality.core;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.NetNeutrality;
import me.bobert.netneutrality.db.DBController;
import me.bobert.netneutrality.db.controllers.DBMessage;
import me.bobert.netneutrality.db.entities.OBlacklist;
import me.bobert.netneutrality.db.entities.OGuild;
import me.bobert.netneutrality.thread.ThreadManager;
import me.bobert.netneutrality.utils.JpaUtils;
import me.bobert.netneutrality.utils.MessageFunction;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class GuildManager {
private static final Logger logger = LogManager.getLogger(GuildManager.class);
private static final Map<String, GuildManager> managers = new ConcurrentHashMap<>();
private static final SessionFactory sessionFactory = SessionManager.getSessionFactory();
private final Set<String> blacklistCache = new HashSet<>();
private final GuildConfigManager guildConfigManager;
private final Guild guild;
private OGuild guildEntity;
private GuildManager(Guild guild) {
this.guild = guild;
this.guildEntity = loadGuildFromDb(guild.getId());
this.guildConfigManager = new GuildConfigManager(guild, this.guildEntity);
managers.put(guild.getId(), this);
reloadBlacklistCache();
}
private GuildManager(String guildId) {
Guild temp = NetNeutrality.getJDA().getGuildById(guildId);
if (temp == null)
throw new IllegalArgumentException("Guild does not exist in current cache");
this.guild = temp;
this.guildEntity = loadGuildFromDb(guild.getId());
this.guildConfigManager = new GuildConfigManager(guild, this.guildEntity);
managers.put(guild.getId(), this);
reloadBlacklistCache();
}
public static GuildManager getManager(Guild guild) {
if (managers.containsKey(guild.getId())) {
return managers.get(guild.getId());
} else {
return new GuildManager(guild);
}
}
public static GuildManager getManager(String guildId) {
if (managers.containsKey(guildId)) {
return managers.get(guildId);
} else {
return new GuildManager(guildId);
}
}
// Getter
public static Map<String, GuildManager> getManagers() {
return managers;
}
public Guild getGuild() {
return guild;
}
public OGuild getGuildEntity() {
return guildEntity;
}
public Set<String> getBlacklistCache() {
return blacklistCache;
}
public GuildConfigManager getGuildConfigManager() {
return guildConfigManager;
}
// Methods
public boolean isLoggingEnabled() {
return guildEntity.getLogging();
}
public boolean isDebugEnabled() {
return guildEntity.getDebug();
}
public void setDebug(boolean toggle) {
if (toggle == isDebugEnabled())
return;
JpaUtils.updateWithTransaction(guildEntity, () -> guildEntity.setDebug(toggle));
}
public void reloadBlacklistCache() {
logger.debug("Reloading blacklist cache for guild: " + guild.getId());
blacklistCache.clear();
JpaUtils.getWithSession(guildEntity, session -> {
Set<OBlacklist> oBlacklistSet = guildEntity.getBlacklists();
for (OBlacklist bl : oBlacklistSet) {
if (bl.getEnabled()) {
blacklistCache.add(bl.getWord());
}
}
});
}
public void checkAndLogMessage(Message message, Member member, MessageChannel channel) {
if (isLoggingEnabled()) {
ThreadManager.getExecutor().submit(() -> DBController.get(DBMessage.class).log(this, channel, member, message));
}
}
public void checkBlacklist(Message message, MessageChannel channel) {
MessageFunction.testMessageAgainstBlacklist(message, channel, blacklistCache);
}
private OGuild loadGuildFromDb(String guildId) {
Session session = sessionFactory.openSession();
OGuild entity = session.get(OGuild.class, guildId);
if (entity == null) {
logger.debug("Guild[{}] doesn't exist in DB, creating entity..", guildId);
session.beginTransaction();
entity = new OGuild();
entity.setGuildId(guildId);
session.save(entity);
session.getTransaction().commit();
guild.getTextChannels()
.stream()
.filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log"))
.forEach(textChannel -> textChannel.sendMessage("Error logging enabled on channel " + textChannel.getAsMention()).queue());
}
session.close();
return entity;
}
public void reloadEntity() {
this.guildEntity = loadGuildFromDb(guild.getId());
}
public void removeFromDB() {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.delete(guildEntity);
session.getTransaction().commit();
session.close();
managers.remove(guild.getId());
}
}
@@ -0,0 +1,85 @@
package me.bobert.netneutrality.core;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import io.sentry.Sentry;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import me.bobert.netneutrality.exceptions.CommandException;
import me.bobert.netneutrality.exceptions.CommandExecutionException;
import me.bobert.netneutrality.utils.MessageFunction;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LogHandler {
private final static Logger logger = LogManager.getLogger(LogHandler.class);
/**
* Log an exception accordingly
*
* @param e The exceptions to log
*/
public static void log(Exception e) {
Sentry.captureException(e);
if (e instanceof CommandExecutionException exception) {
CommandContext context = exception.getCommandContext();
logInternally(exception, context);
tryLogToDebugChannel(exception, context);
tryGracefulSlashContextError(exception, context);
return;
}
logger.error(e.getMessage(), e);
}
public static void log(String s, Exception e) {
logger.error(s, e);
}
public static void logThread(Thread t, Throwable e) {
logger.error("Uncaught exception thrown in thread {}", t);
log((Exception) e);
}
public static void sendMessageToDebugChannel(Guild guild, String message) {
if (GuildManager.getManager(guild).isDebugEnabled()) {
guild.getTextChannels()
.stream()
.filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log"))
.filter(TextChannel::canTalk)
.forEach(textChannel -> textChannel.sendMessage(message).queue());
}
}
private static void logInternally(CommandException exception, CommandContext context) {
logger.error("Exception thrown from command [{}] in guild [{}]: {}", context.getCommand().getCommandName(), context.getGuild().getId(), exception.getMessage(), exception);
}
private static void tryLogToDebugChannel(CommandException exception, CommandContext context) {
if (context.getGuild() != null) {
if (GuildManager.getManager(context.getGuild()).isDebugEnabled()) {
context.getGuild()
.getTextChannels()
.stream()
.filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log"))
.filter(TextChannel::canTalk)
.forEach(textChannel -> textChannel.sendMessageEmbeds(MessageFunction.buildDebugMessage(context, exception)).queue());
}
}
}
private static void tryGracefulSlashContextError(CommandException exception, CommandContext context) {
if (context instanceof SlashCommandContext slashCommandContext) {
slashCommandContext.reply(MessageFunction.buildSentryErrorMessage(exception.getMessage()));
}
}
}
@@ -0,0 +1,68 @@
package me.bobert.netneutrality.core;
/*
* 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 final static 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("NETNEUTRALITY_MYSQL_DATABASE", "netneutrality");
String dbUser = Settings.getInstance().get("NETNEUTRALITY_MYSQL_USER", "root");
String dbPass = Settings.getInstance().get("NETNEUTRALITY_MYSQL_PASSWORD", "");
String dbHost = Settings.getInstance().get("NETNEUTRALITY_MYSQL_HOST", "localhost");
logger.info(String.format("Connecting to database '%s' at %s:3306 with user '%s'", dbName, dbHost, dbUser));
settings.put(Environment.URL, String.format("jdbc:mysql://%s:3306/%s", dbHost, dbName));
settings.put(Environment.USER, dbUser);
settings.put(Environment.PASS, 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);
}
}
}
@@ -0,0 +1,60 @@
package me.bobert.netneutrality.core.context;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractCommand;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.Event;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
public interface CommandContext {
AbstractCommand getCommand();
Event getEvent();
Guild getGuild();
MessageChannel getChannel();
Member getGuildUser();
User getDiscordUser();
JDA getJDA();
void deleteMessage();
void reply(MessageCreateData message);
void reply(String message);
void reply(MessageEmbed... embeds);
default void reply(String format, Object... arguments) {
reply(String.format(format, arguments));
}
void replyToMessage(String message);
default void replyToMessage(MessageCreateData message) {
reply(message);
}
default void replyToMessage(String format, Object... arguments) {
replyToMessage(String.format(format, arguments));
}
default void replyToMessage(MessageEmbed... embeds) {
replyToMessage(MessageCreateData.fromEmbeds(embeds));
}
}
@@ -0,0 +1,192 @@
package me.bobert.netneutrality.core.context;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.thread.ThreadManager;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class MessageCommandContext implements CommandContext {
private static final ExecutorService commandExecutor = ThreadManager.getExecutor();
private AbstractCommand command;
private final MessageReceivedEvent event;
private final Message message;
private final Guild guild;
private final MessageChannel channel;
private final Member member;
private final User user;
private final JDA jda;
public MessageCommandContext(MessageReceivedEvent event, Message message, Member member, User user, Guild guild, MessageChannel channel, JDA jda) {
this.event = event;
this.message = message;
this.member = member;
this.guild = guild;
this.channel = channel;
this.user = user;
this.jda = jda;
io.sentry.protocol.User sentryUser = new io.sentry.protocol.User();
sentryUser.setId(member.getId());
sentryUser.setUsername(member.getEffectiveName());
Sentry.setUser(sentryUser);
Sentry.addBreadcrumb(Breadcrumb.info("New context for message id " + message.getId()));
}
public static MessageCommandContext createContext(MessageReceivedEvent event) {
Message message = event.getMessage();
Member member = event.getMember();
User user = event.getAuthor();
Guild guild = event.getGuild();
MessageChannel channel = event.getChannel();
JDA jda = event.getJDA();
return new MessageCommandContext(event, message, member, user, guild, channel, jda);
}
@Override
public MessageReceivedEvent getEvent() {
return event;
}
public String getArgument(int index) {
return getArgs()[index];
}
public String[] getArgs() {
String msg = message.getContentDisplay().trim();
msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim();
String cmd = msg.split(" ", 2)[0];
String[] args = msg.replaceFirst("(" + cmd + ")(\\s+)?", "").split("\s");
if (args.length == 1 && args[0].isEmpty())
args = new String[0];
return args;
}
public String getMessageText() {
String msg = message.getContentDisplay().trim();
msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim();
String cmd = msg.split(" ", 2)[0];
return msg.replaceFirst("(" + cmd + ")(\s)?", "");
}
public Message getMessage() {
return message;
}
public Guild getGuild() {
return guild;
}
public MessageChannel getChannel() {
return channel;
}
public Member getGuildUser() {
return member;
}
public User getDiscordUser() {
return user;
}
public JDA getJDA() {
return jda;
}
public AbstractCommand getCommand() {
return command;
}
public GuildManager getGuildManager() {
return GuildManager.getManager(guild);
}
public void reply(String message) {
reply(MessageCreateData.fromContent(message));
}
public void reply(String format, Object... arguments) {
reply(String.format(format, arguments));
}
public void reply(MessageEmbed... embeds) {
reply(MessageCreateData.fromEmbeds(embeds));
}
public void reply(MessageCreateData msg) {
if (channel != null && channel.canTalk()) {
channel.sendMessage(msg).queue();
}
}
@Override
public void replyToMessage(String message) {
replyToMessage(MessageCreateData.fromContent(message));
}
@Override
public void replyToMessage(String format, Object... arguments) {
replyToMessage(String.format(format, arguments));
}
public void replyToMessage(MessageCreateData msg) {
if (channel != null && channel.canTalk() && message != null) {
message.reply(msg).queue();
}
}
public void replyPrivate(String message) {
replyPrivate(MessageCreateData.fromContent(message));
}
public void replyPrivate(MessageCreateData msg) {
user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue();
}
public void replyPrivate(MessageEmbed... embeds) {
replyPrivate(MessageCreateData.fromEmbeds(embeds));
}
public void replyPrivate(Collection<? extends MessageEmbed> embeds) {
user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embeds)).queue();
}
@Override
public void deleteMessage() {
if (message != null) {
message.delete().queueAfter(1, TimeUnit.SECONDS);
}
}
public boolean isMessageFromOwner() {
return member.isOwner();
}
public boolean isMessageFromCreator() {
String creatorId = Settings.getInstance().get("NETNEUTRALITY_BOT_OWNER");
return creatorId.equals(member.getId());
}
}
@@ -0,0 +1,180 @@
package me.bobert.netneutrality.core.context;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.command.AbstractCommand;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import java.util.Collection;
public class SlashCommandContext implements CommandContext {
private final SlashCommandInteractionEvent event;
private AbstractCommand command;
private final JDA jda;
private final Guild guild;
private final MessageChannel channel;
private final Member member;
private final User user;
private SlashCommandContext(JDA jda, SlashCommandInteractionEvent event, Guild guild, MessageChannel channel, Member member, User user) {
this.jda = jda;
this.event = event;
this.guild = guild;
this.channel = channel;
this.member = member;
this.user = user;
}
public static SlashCommandContext createContext(SlashCommandInteractionEvent event) {
Member member = event.getMember();
User user = event.getUser();
Guild guild = event.getGuild();
MessageChannel channel = event.getChannel();
JDA jda = event.getJDA();
return new SlashCommandContext(jda, event, guild, channel, member, user);
}
@Override
public SlashCommandInteractionEvent getEvent() {
return event;
}
@Override
public AbstractCommand getCommand() {
return command;
}
@Override
public JDA getJDA() {
return jda;
}
@Override
public Guild getGuild() {
return guild;
}
@Override
public MessageChannel getChannel() {
return channel;
}
@Override
public Member getGuildUser() {
return member;
}
@Override
public User getDiscordUser() {
return user;
}
@Override
public void deleteMessage() {
// not implemented
}
public void sendMessageInPrivate(String msg) {
sendMessageInPrivate(new MessageCreateBuilder().setContent(msg).build());
}
public void sendMessageInPrivate(MessageCreateData msg) {
user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue();
}
public void sendMessageInPrivate(MessageEmbed embed, MessageEmbed... embeds) {
user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embed, embeds)).queue();
}
public void sendMessageInPrivate(Collection<? extends MessageEmbed> embeds) {
user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embeds)).queue();
}
public void reply(MessageCreateData message) {
if (event.isAcknowledged()) {
getHook().editOriginal(MessageEditData.fromCreateData(message)).queue();
//getHook().sendMessage(message).setEphemeral(false).queue();
return;
}
event.reply(message).queue();
}
public void reply(MessageEmbed... embeds) {
reply(MessageCreateData.fromEmbeds(embeds));
}
@Override
public void reply(String message) {
reply(MessageCreateData.fromContent(message));
}
public void replyEphemeral(MessageCreateData message) {
if (event.isAcknowledged()) {
getHook().editOriginal(MessageEditData.fromCreateData(message)).queue();
//getHook().sendMessage(message).setEphemeral(true).queue();
return;
}
event.reply(message).setEphemeral(true).queue();
}
public void replyEphemeral(MessageEmbed... embeds) {
replyEphemeral(MessageCreateData.fromEmbeds(embeds));
}
public void replyEphemeral(String format, Object... arguments) {
replyEphemeral(String.format(format, arguments));
}
public void replyEphemeral(String message) {
replyEphemeral(MessageCreateData.fromContent(message));
}
@Override
public void replyToMessage(String message) {
replyEphemeral(message);
}
public InteractionHook getHook() {
if (!event.isAcknowledged())
throw new RuntimeException("Interaction isn't acknowledged yet");
return event.getHook();
}
public void ackAndWait(boolean ephemeral) {
if (event.isAcknowledged())
throw new RuntimeException("Interaction is already acknowledged, please use getHook() instead.");
event.deferReply(ephemeral).queue();
}
public void ackAndWait() {
ackAndWait(false);
}
public void ack() {
ackAndWait();
}
public void ephemeral() {
ackAndWait(true);
}
}
@@ -0,0 +1,23 @@
package me.bobert.netneutrality.db;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.SessionManager;
import org.hibernate.SessionFactory;
public abstract class AbstractController {
protected SessionFactory sessionFactory;
public AbstractController() {
sessionFactory = SessionManager.getSessionFactory();
}
protected SessionFactory getSessionFactory() {
return sessionFactory;
}
}
@@ -0,0 +1,38 @@
package me.bobert.netneutrality.db;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.SessionManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.InvocationTargetException;
public class DBController {
private final static Logger logger = LogManager.getLogger(DBController.class);
public static <T> T get(Class<T> returnType) {
if (returnType == null)
throw new IllegalArgumentException("return type is null");
try {
return returnType.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static void shutdown() {
SessionManager.shutdown();
}
public static void init() {
SessionManager.getSessionFactory();
}
}
@@ -0,0 +1,29 @@
package me.bobert.netneutrality.db;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
public enum GSettings {
//TODO: description
MUSIC_ROLE("MUSIC_ROLE", ""),
MUSIC_CHANNEL("MUSIC_CHANNEL", ""),
MUSIC_VOLUME("MUSIC_VOLUME", "30");
private final String name;
private final String defaultValue;
GSettings(String name, String defaultValue) {
this.name = name;
this.defaultValue = defaultValue;
}
public String getName() {
return name;
}
public String getDefaultValue() {
return defaultValue;
}
}
@@ -0,0 +1,141 @@
package me.bobert.netneutrality.db.controllers;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.db.AbstractController;
import me.bobert.netneutrality.db.entities.OBlacklist;
import me.bobert.netneutrality.db.entities.OGuild;
import me.bobert.netneutrality.utils.JpaUtils;
import net.dv8tion.jda.api.entities.Guild;
import org.hibernate.Session;
import org.hibernate.query.Query;
import java.util.ArrayList;
public class DBBlacklist extends AbstractController {
public void add(String word, CommandContext context) {
GuildManager guildManager = GuildManager.getManager(context.getGuild());
OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
if (bl == null) {
Session session = getSessionFactory().openSession();
session.beginTransaction();
bl = new OBlacklist();
bl.setGuild(guildManager.getGuildEntity());
bl.setCreatorId(context.getGuildUser().getId());
bl.setWord(word);
bl.setEnabled(true);
session.save(bl);
session.getTransaction().commit();
session.close();
guildManager.reloadBlacklistCache();
context.reply(String.format("Added the word [%s] to the blacklist.", word));
} else {
context.reply("Word is already blacklisted");
}
}
public void delete(String word, CommandContext context) {
GuildManager guildManager = GuildManager.getManager(context.getGuild());
OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
if (bl != null) {
Session session = getSessionFactory().openSession();
session.beginTransaction();
session.delete(bl);
session.getTransaction().commit();
session.close();
guildManager.reloadBlacklistCache();
context.reply("Removed the word [%s] from the blacklist.", word);
} else {
context.reply("Can't delete word because it doesn't exist");
}
}
public void enable(String word, CommandContext context) {
GuildManager guildManager = GuildManager.getManager(context.getGuild());
OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
if (bl != null && !bl.getEnabled()) {
Session session = getSessionFactory().openSession();
session.beginTransaction();
bl.setEnabled(true);
session.update(bl);
session.getTransaction().commit();
session.close();
guildManager.reloadBlacklistCache();
context.reply("Enabled the word [%s] for the blacklist.", word);
} else {
context.reply("Either word doesn't exist or is already enabled");
}
}
public void disable(String word, CommandContext context) {
GuildManager guildManager = GuildManager.getManager(context.getGuild());
OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
if (bl != null && bl.getEnabled()) {
Session session = getSessionFactory().openSession();
session.beginTransaction();
bl.setEnabled(false);
session.update(bl);
session.getTransaction().commit();
session.close();
guildManager.reloadBlacklistCache();
context.reply("Disabled the word [%s] for the blacklist.", word);
} else {
context.reply("Either word doesn't exist or is already disabled");
}
}
public ArrayList<OBlacklist> getBlacklistForGuild(Guild guild) {
GuildManager guildManager = GuildManager.getManager(guild);
Session session = getSessionFactory().openSession();
OGuild guildEntity = guildManager.getGuildEntity();
JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId());
ArrayList<OBlacklist> blacklists = new ArrayList<>(guildEntity.getBlacklists());
session.close();
return blacklists;
}
private OBlacklist getWordFromDb(String word, String guildId) {
Session session = getSessionFactory().openSession();
Query<OBlacklist> query = session.createNamedQuery("OBlacklist.findByWordEqualsIgnoreCaseAndGuild_GuildIdEquals", OBlacklist.class);
query.setParameter("guildId", guildId);
query.setParameter("word", word);
OBlacklist blacklist = query.uniqueResult();
session.close();
return blacklist;
}
}
@@ -0,0 +1,97 @@
package me.bobert.netneutrality.db.controllers;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.db.AbstractController;
import me.bobert.netneutrality.db.entities.OLogMessage;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.query.Query;
import java.util.List;
public class DBMessage extends AbstractController {
private final static Logger logger = LogManager.getLogger(DBMessage.class);
public void log(GuildManager guildManager, MessageChannel channel, Member author, Message message) {
Session session = getSessionFactory().openSession();
session.beginTransaction();
OLogMessage logMessage = new OLogMessage();
logMessage.setGuild(guildManager.getGuildEntity());
logMessage.setMessageId(message.getId());
logMessage.setMessage(message.getContentRaw().trim());
logMessage.setChannelId(channel.getId());
logMessage.setUserId(author.getId());
logMessage.setDateSent(message.getTimeCreated().toInstant());
session.save(logMessage);
session.getTransaction().commit();
session.close();
}
public void logEdit(String messageId, String newMessage) {
Session session = getSessionFactory().openSession();
Query<OLogMessage> query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class);
query.setParameter("messageId", messageId);
OLogMessage message = query.uniqueResult();
if (message != null) {
session.beginTransaction();
message.setEditedMessage(newMessage);
session.update(message);
session.getTransaction().commit();
}
session.close();
}
public OLogMessage getMessage(String messageId) {
Session session = getSessionFactory().openSession();
Query<OLogMessage> query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class);
query.setParameter("messageId", messageId);
OLogMessage message = query.uniqueResult();
session.close();
return message;
}
public List<OLogMessage> getMessagesFromUser(String userId, String guildId, int max) {
Session session = getSessionFactory().openSession();
Query<OLogMessage> query = session.createNamedQuery("OLogMessage.findByUserIdEquals", OLogMessage.class);
query.setParameter("guildId", guildId);
query.setParameter("userId", userId);
query.setMaxResults(max);
List<OLogMessage> messages = query.getResultList();
session.close();
return messages;
}
public String getMessageString(String messageId) {
Session session = getSessionFactory().openSession();
Query<OLogMessage> query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class);
query.setParameter("messageId", messageId);
OLogMessage message = query.uniqueResult();
session.close();
if (message == null)
return null;
else
return message.getMessage();
}
}
@@ -0,0 +1,74 @@
package me.bobert.netneutrality.db.entities;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import jakarta.persistence.*;
@Entity
@Table(name = "blacklist")
@NamedQueries({
@NamedQuery(name = "OBlacklist.findByWordEqualsIgnoreCaseAndGuild_GuildIdEquals", query = "select o from OBlacklist o where upper(o.word) = upper(:word) and o.guild.guildId = :guildId")
})
public class OBlacklist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "word", nullable = false)
private String word;
@Column(name = "enabled", nullable = false)
private Boolean enabled = false;
@Column(name = "creator_id", nullable = false)
private String creatorId;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "guild_id", nullable = false)
private OGuild guild;
public OGuild getGuild() {
return guild;
}
public void setGuild(OGuild guild) {
this.guild = guild;
}
public String getCreatorId() {
return creatorId;
}
public void setCreatorId(String creatorId) {
this.creatorId = creatorId;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
@@ -0,0 +1,118 @@
package me.bobert.netneutrality.db.entities;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import jakarta.persistence.*;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
@Entity
@Table(name = "guilds")
public class OGuild {
@Id
@Column(name = "guild_id", nullable = false)
private String guildId;
@Column(name = "logging", nullable = false)
private Boolean logging = false;
@Column(name = "debug", nullable = false)
private Boolean debug = true;
@Column(name = "log_channel")
private String logChannel;
@OneToMany(mappedBy = "guild")
private Set<OGuildSetting> guildSettings = new LinkedHashSet<>();
@OneToMany(mappedBy = "guild")
private Set<OBlacklist> blacklists = new LinkedHashSet<>();
@OneToMany(mappedBy = "guild")
private Set<OLogMessage> logMessages = new LinkedHashSet<>();
@OneToMany(mappedBy = "guild")
private Set<OLogging> OLoggings = new LinkedHashSet<>();
public Set<OLogging> getLoggings() {
return OLoggings;
}
public void setLoggings(Set<OLogging> OLoggings) {
this.OLoggings = OLoggings;
}
public Set<OLogMessage> getLogMessages() {
return logMessages;
}
public void setLogMessages(Set<OLogMessage> logMessages) {
this.logMessages = logMessages;
}
public Set<OBlacklist> getBlacklists() {
return blacklists;
}
public void setBlacklists(Set<OBlacklist> blacklists) {
this.blacklists = blacklists;
}
public Set<OGuildSetting> getGuildSettings() {
return guildSettings;
}
public void setGuildSettings(Set<OGuildSetting> guildSettings) {
this.guildSettings = guildSettings;
}
public String getLogChannel() {
return logChannel;
}
public void setLogChannel(String logChannel) {
this.logChannel = logChannel;
}
public Boolean getLogging() {
return logging;
}
public void setLogging(Boolean logging) {
this.logging = logging;
}
public Boolean getDebug() {
return debug;
}
public void setDebug(Boolean debug) {
this.debug = debug;
}
public String getGuildId() {
return guildId;
}
public void setGuildId(String guildId) {
this.guildId = guildId;
}
public OGuildSetting getSettingByName(String name) {
Optional<OGuildSetting> setting = getGuildSettings()
.stream()
.filter(setting1 -> setting1.getName().equalsIgnoreCase(name))
.findFirst();
return setting.orElse(null);
}
}
@@ -0,0 +1,64 @@
package me.bobert.netneutrality.db.entities;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import jakarta.persistence.*;
@Entity
@Table(name = "guild_settings")
@NamedQueries({
@NamedQuery(name = "settings_findByName", query = "select o from OGuildSetting o where o.guild.id = :id and upper(o.name) = upper(:name)")
})
public class OGuildSetting {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name", nullable = false)
private String name;
@Lob
@Column(name = "value", nullable = false)
private String value;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "guild_id", nullable = false)
private OGuild guild;
public OGuild getGuild() {
return guild;
}
public void setGuild(OGuild guild) {
this.guild = guild;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
@@ -0,0 +1,111 @@
package me.bobert.netneutrality.db.entities;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import jakarta.persistence.*;
import java.time.Instant;
@Entity
@Table(name = "log_message")
@NamedQueries({
@NamedQuery(name = "OLogMessage.findByMessageIdEquals", query = "select o from OLogMessage o where o.messageId = :messageId"),
@NamedQuery(name = "OLogMessage.findByGuild_GuildIdEqualsAndUserIdEquals", query = "select o from OLogMessage o where o.guild.guildId = :guildId and o.userId = :userId")
})
public class OLogMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "channel_id", nullable = false)
private String channelId;
@Column(name = "message_id", nullable = false)
private String messageId;
@Column(name = "user_id", nullable = false)
private String userId;
@Convert(disableConversion = true)
@Column(name = "date_sent", nullable = false)
private Instant dateSent;
@Column(name = "message", length = 2000)
private String message;
@Column(name = "edited_message", length = 2000)
private String editedMessage;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "guild_id", nullable = false)
private OGuild guild;
public OGuild getGuild() {
return guild;
}
public void setGuild(OGuild guild) {
this.guild = guild;
}
public String getEditedMessage() {
return editedMessage;
}
public void setEditedMessage(String editedMessage) {
this.editedMessage = editedMessage;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Instant getDateSent() {
return dateSent;
}
public void setDateSent(Instant dateSent) {
this.dateSent = dateSent;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getMessageId() {
return messageId;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
@@ -0,0 +1,48 @@
package me.bobert.netneutrality.db.entities;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import jakarta.persistence.*;
@Entity
@Table(name = "logging")
public class OLogging {
@EmbeddedId
private OLoggingId id;
@MapsId("guildId")
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "guild_id", nullable = false)
private OGuild guild;
@Column(name = "enabled", nullable = false)
private Boolean enabled = false;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public OGuild getGuild() {
return guild;
}
public void setGuild(OGuild guild) {
this.guild = guild;
}
public OLoggingId getId() {
return id;
}
public void setId(OLoggingId id) {
this.id = id;
}
}
@@ -0,0 +1,55 @@
package me.bobert.netneutrality.db.entities;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import org.hibernate.Hibernate;
import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
public class OLoggingId implements Serializable {
@Serial
private static final long serialVersionUID = 8216581265618800768L;
@Column(name = "guild_id", nullable = false)
private String guildId;
@Column(name = "channel_id", nullable = false)
private String channelId;
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public String getGuildId() {
return guildId;
}
public void setGuildId(String guildId) {
this.guildId = guildId;
}
@Override
public int hashCode() {
return Objects.hash(guildId, channelId);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
OLoggingId entity = (OLoggingId) o;
return Objects.equals(this.guildId, entity.guildId) &&
Objects.equals(this.channelId, entity.channelId);
}
}
@@ -0,0 +1,24 @@
package me.bobert.netneutrality.exceptions;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
public abstract class CommandException extends RuntimeException {
public static final String DEFAULT_MESSAGE = "Unknown error during command execution.";
private final CommandContext context;
public CommandException(String message, CommandContext context, Throwable cause) {
super(message == null ? DEFAULT_MESSAGE : message, cause);
this.context = context;
}
public CommandContext getCommandContext() {
return context;
}
}
@@ -0,0 +1,24 @@
package me.bobert.netneutrality.exceptions;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
/**
* Logs the error internally, without sending a message
*/
public class CommandExecutionException extends CommandException {
/**
* Logs the error internally, without sending a message
* @param context The command context
*/
public CommandExecutionException(String message, CommandContext context, Throwable cause) {
super(message, context, cause);
}
}
@@ -0,0 +1,17 @@
package me.bobert.netneutrality.exceptions;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.LogHandler;
public class ExecutorException implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
LogHandler.logThread(t, e);
}
}
@@ -0,0 +1,25 @@
package me.bobert.netneutrality.hooks;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.CommandProcessor;
import me.bobert.netneutrality.core.context.SlashCommandContext;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class SlashCommandHooks extends ListenerAdapter {
@Override
public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) {
String commandName = event.getName();
SlashCommandContext commandContext = SlashCommandContext.createContext(event);
CommandProcessor processor = new CommandProcessor(commandName, commandContext);
processor.dispatch();
}
}
@@ -0,0 +1,26 @@
package me.bobert.netneutrality.hooks.guilds;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.GuildManager;
import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
import net.dv8tion.jda.api.events.guild.GuildLeaveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class ServerJoinHandler extends ListenerAdapter {
@Override
public void onGuildJoin(@NotNull GuildJoinEvent event) {
GuildManager.getManager(event.getGuild());
}
@Override
public void onGuildLeave(@NotNull GuildLeaveEvent event) {
GuildManager.getManager(event.getGuild()).removeFromDB();
}
}
@@ -0,0 +1,58 @@
package me.bobert.netneutrality.hooks.message;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.core.SessionManager;
import me.bobert.netneutrality.db.DBController;
import me.bobert.netneutrality.db.controllers.DBMessage;
import me.bobert.netneutrality.db.entities.OGuild;
import me.bobert.netneutrality.utils.JpaUtils;
import me.bobert.netneutrality.utils.MessageFunction;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.hibernate.Session;
import java.util.stream.Collectors;
public class EditHandler extends ListenerAdapter {
@Override
public void onMessageUpdate(MessageUpdateEvent event) {
if (event.getAuthor().isBot()) return;
GuildManager guildManager = GuildManager.getManager(event.getGuild());
if (guildManager.getGuildEntity().getLogging()) {
OGuild guildEntity = guildManager.getGuildEntity();
if (guildEntity.getLogChannel() != null && !guildEntity.getLogChannel().isEmpty()) {
Session session = SessionManager.getSessionFactory().openSession();
JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId());
int size = guildEntity.getLoggings()
.stream()
.filter(oLogging -> oLogging.getId().getChannelId().equals(event.getChannel().getId()) && oLogging.getEnabled())
.collect(Collectors.toSet())
.size();
if (size > 0) {
TextChannel channel = event.getGuild().getTextChannelById(guildEntity.getLogChannel());
if (channel != null) {
String originalMessage = DBController.get(DBMessage.class).getMessageString(event.getMessage().getId());
DBController.get(DBMessage.class).logEdit(event.getMessage().getId(), event.getMessage().getContentDisplay());
channel.sendMessageEmbeds(MessageFunction.buildEditMessage(event.getMember(), event.getMessage(), originalMessage)).queue();
}
}
}
}
}
}
@@ -0,0 +1,45 @@
package me.bobert.netneutrality.hooks.message;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.CommandProcessor;
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.core.context.MessageCommandContext;
import me.bobert.netneutrality.utils.Settings;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class MessageReceivedHandler extends ListenerAdapter {
@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
if (event.getAuthor().isBot()) return;
if (event.isWebhookMessage()) return;
if (!event.isFromGuild())
return;
Message message = event.getMessage();
MessageCommandContext context = MessageCommandContext.createContext(event);
GuildManager.getManager(context.getGuild()).checkAndLogMessage(message, context.getGuildUser(), context.getChannel());
String rawMessage = message.getContentDisplay().trim();
String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$");
if (rawMessage.startsWith(prefix)) {
rawMessage = rawMessage.replaceFirst(String.format("^[%s\\s]{1,}", prefix), "").trim();
// rawMessage = rawMessage.substring(prefix.length()).trim();
String commandString = rawMessage.split(" ", 2)[0];
CommandProcessor processor = new CommandProcessor(commandString, context);
processor.dispatch();
}
}
}
@@ -0,0 +1,31 @@
package me.bobert.netneutrality.hooks.message;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class ReactionHandler extends ListenerAdapter {
@Override
public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
Member member = event.getMember();
}
@Override
public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) {
}
@Override
public void onMessageUpdate(@NotNull MessageUpdateEvent event) {
}
}
@@ -0,0 +1,18 @@
package me.bobert.netneutrality.hooks.voice;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
public class VoiceEventHandler extends ListenerAdapter {
@Override
public void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) {
super.onGuildVoiceUpdate(event);
}
}
@@ -0,0 +1,195 @@
package me.bobert.netneutrality.http;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import me.bobert.netneutrality.NetNeutrality;
import me.bobert.netneutrality.core.LogHandler;
import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class WebModule {
private final String url;
private final OkHttpClient client = new OkHttpClient();
private Response response;
//private final String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 OPR/82.0.4227.43";
private static final String userAgent = String.format("Net Neutrality/%s (Discord bot, [email protected])", NetNeutrality.VERSION);
public WebModule(String url) {
this.url = url;
}
/**
* Send a request to the specified URL in the constructor.
* <br>Needs to be executed before any other method call
*
* @return an instance of the WebModule class with the Response of the Web Request
*/
public WebModule request() {
Sentry.addBreadcrumb(Breadcrumb.http(url, "get"));
Request request = new Request.Builder().url(this.url)
.addHeader("User-Agent", userAgent)
.build();
try {
response = client.newCall(request).execute();
Sentry.addBreadcrumb(Breadcrumb.http(url, "get", response.code()));
} catch (IOException e) {
Sentry.captureException(e);
}
return this;
}
public WebModule request(Map<String, String> headers) throws IOException {
Sentry.addBreadcrumb(Breadcrumb.http(url, "get"));
Request.Builder builder = new Request.Builder().url(this.url)
.addHeader("User-Agent", userAgent);
for (Map.Entry<String, String> header : headers.entrySet()) {
builder.addHeader(header.getKey(), header.getValue());
}
Request request = builder.build();
try {
response = client.newCall(request).execute();
Sentry.addBreadcrumb(Breadcrumb.http(url, "get", response.code()));
} catch (IOException e) {
Sentry.captureException(e);
}
return this;
}
public WebModule post(TreeMap<String, String> params, Map<String, String> headers) {
Sentry.addBreadcrumb(Breadcrumb.http(url, "post"));
RequestBody body = RequestBody.create(createJson(params), MediaType.parse("application/json; charset=utf-8"));
Request.Builder builder = new Request.Builder().url(this.url)
.addHeader("User-Agent", userAgent);
for (Map.Entry<String, String> header : headers.entrySet()) {
builder.addHeader(header.getKey(), header.getValue());
}
Request request = builder.post(body).build();
try {
response = client.newCall(request).execute();
Sentry.addBreadcrumb(Breadcrumb.http(url, "post", response.code()));
} catch (IOException e) {
Sentry.captureException(e);
}
return this;
}
public static String createJson(TreeMap<String, String> params) {
StringBuilder json = new StringBuilder();
for (Map.Entry<String, String> param : params.entrySet()) {
String str = "\"" + param.getKey() + "\":\"" + param.getValue() + "\"";
json.append(str);
json.append(",");
}
json.insert(0, "{");
return (json.length() > 1 ? json.substring(0, json.length() - 1) + "}" : "");
}
public static String createJsonObject(List<String> key, List<String> value) {
if (key.size() != value.size())
throw new IllegalArgumentException("Key list size not equal to value list size");
StringBuilder json = new StringBuilder();
for (int index = 0; index < key.size();) {
json.append(String.format("\"%s\":\"%s\"", key.get(index), value.get(index)));
json.append(",");
}
json.insert(0, "{");
return (json.length() > 1 ? json.substring(0, json.length() - 1) + "}" : "");
}
public static String createJsonArray(List<String> list) {
StringBuilder json = new StringBuilder();
for (String str : list) {
json.append("\"").append(str).append("\"");
json.append(",");
}
json.insert(0, "[");
return (json.length() > 1 ? json.substring(0, json.length() - 1) + "]" : "");
}
public static String createJsonArray(TreeMap<String, String> users) {
StringBuilder json = new StringBuilder();
for (Map.Entry<String, String> param : users.entrySet()) {
String str = "{\"" + param.getKey() + "\":\"" + param.getValue() + "\"}";
json.append(str);
json.append(",");
}
json.insert(0, "[");
return (json.length() > 1 ? json.substring(0, json.length() - 1) + "]" : "[]");
}
/**
* @return the response as a JSONObject
*/
public JSONObject getAsObject() {
if (response == null)
throw new RuntimeException("Request has not been sent");
try {
ResponseBody body = response.body();
return new JSONObject(body.string());
} catch (IOException | NullPointerException e) {
LogHandler.log(e);
return new JSONObject();
}
}
/**
* Use if your response start with an array : [ ]
*
* @return the response as a JSONArray
*/
public JSONArray getAsArray() {
if (response == null)
throw new RuntimeException("Request has not been sent");
try {
ResponseBody body = response.body();
return new JSONArray(body.string());
} catch (IOException | NullPointerException e) {
LogHandler.log(e);
return new JSONArray();
}
}
/**
* @return the response as a String
*/
public String getRawData() throws IOException {
if (response == null)
throw new RuntimeException("Request has not been sent");
return response.body().string();
}
}
@@ -0,0 +1,33 @@
package me.bobert.netneutrality.http.server;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
public abstract class AbstractHandler implements HttpHandler {
public abstract void handle(HttpExchange exchange) throws IOException;
public void sendStringResponse(HttpExchange exchange, String str) throws IOException {
exchange.sendResponseHeaders(200, str.length());
OutputStream os = exchange.getResponseBody();
os.write(str.getBytes());
os.close();
}
public void sendJsonResponse(HttpExchange exchange, String str) throws IOException {
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(200, str.length());
OutputStream os = exchange.getResponseBody();
os.write(str.getBytes());
os.close();
}
}
@@ -0,0 +1,88 @@
package me.bobert.netneutrality.http.server;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sun.net.httpserver.HttpExchange;
import me.bobert.netneutrality.NetNeutrality;
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.http.WebModule;
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.TreeMap;
public class ActionHandler extends AbstractHandler {
@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.shutdown();
sendStringResponse(exchange, "Shutting down..");
System.exit(0);
return;
}
if (path.equals("/actions/status")) {
TreeMap<String, String> json = new TreeMap<>();
json.put("health", String.valueOf(NetNeutrality.getJDA().getStatus()));
json.put("ping", String.valueOf(NetNeutrality.getJDA().getGatewayPing()));
sendJsonResponse(exchange, WebModule.createJson(json));
return;
}
if (path.startsWith("/actions/settings/current/")) {
JSONObject json = new JSONObject();
String[] args = exchange.getRequestURI().toString().split("/");
String guildId = args[args.length - 1];
Guild guild = NetNeutrality.getJDA().getGuildById(guildId);
if (guild != null) {
GuildManager manager = GuildManager.getManager(guild);
Map<String, String> settings = manager.getGuildConfigManager().getSettings();
JSONObject setting = new JSONObject(settings);
json.put("status", "ok");
json.put("settings", setting);
} else {
json.put("status", "error");
}
sendJsonResponse(exchange, json.toString());
return;
}
if (path.startsWith("/actions/settings/reload/")) {
TreeMap<String, String> json = new TreeMap<>();
String[] args = exchange.getRequestURI().toString().split("/");
String guildId = args[args.length - 1];
Guild guild = NetNeutrality.getJDA().getGuildById(guildId);
boolean ok = true; //TODO: add bool
if (guild == null)
GuildManager.getManager(guildId).getGuildConfigManager().reloadSettingsCache();
else
GuildManager.getManager(guild).getGuildConfigManager().reloadSettingsCache();
json.put("status", String.valueOf(ok));
sendJsonResponse(exchange, WebModule.createJson(json));
return;
}
sendStringResponse(exchange, path);
}
}
@@ -0,0 +1,94 @@
package me.bobert.netneutrality.http.server;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
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;
public class GuildHandler extends AbstractHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String path = exchange.getRequestURI().getRawPath();
if (path.equals("/guilds/owner")) {
JSONArray array = new JSONArray();
JDA jda = NetNeutrality.getJDA();
jda.getGuilds().forEach(guild -> {
JSONObject obj = new JSONObject();
obj.put("guild_id", guild.getId());
obj.put("owner_id", guild.getOwnerId());
array.put(obj);
});
sendJsonResponse(exchange, array.toString());
return;
}
if (path.startsWith("/guilds/channels/")) {
String[] args = exchange.getRequestURI().toString().split("/");
String guild = args[args.length - 1];
JSONArray array = new JSONArray();
JDA jda = NetNeutrality.getJDA();
if (jda.getGuildById(guild) == null) {
sendStringResponse(exchange, path);
return;
}
jda.getGuildById(guild).getTextChannels().forEach(channel -> {
JSONObject obj = new JSONObject();
obj.put("channel_id", channel.getId());
obj.put("channel_name", channel.getName());
array.put(obj);
});
sendJsonResponse(exchange, array.toString());
return;
}
if (path.startsWith("/guilds/roles/")) {
String[] args = exchange.getRequestURI().toString().split("/");
String guild = args[args.length - 1];
JSONArray array = new JSONArray();
JDA jda = NetNeutrality.getJDA();
if (jda.getGuildById(guild) == null) {
sendStringResponse(exchange, path);
return;
}
jda.getGuildById(guild).getRoles().forEach(role -> {
JSONObject obj = new JSONObject();
obj.put("channel_id", role.getId());
obj.put("channel_name", role.getName());
array.put(obj);
});
sendJsonResponse(exchange, array.toString());
return;
}
sendStringResponse(exchange, path);
}
}
@@ -0,0 +1,22 @@
package me.bobert.netneutrality.http.server;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
public class RootHandler extends AbstractHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String response = "<h1>Server start success if you see this message</h1>";
sendStringResponse(exchange, response);
}
}
@@ -0,0 +1,46 @@
package me.bobert.netneutrality.http.server;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sun.net.httpserver.HttpServer;
import me.bobert.netneutrality.thread.BaseThreadFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WebApi {
private final static Logger logger = LogManager.getLogger(WebApi.class);
private HttpServer server;
public WebApi(int port) {
logger.info("Starting web server...");
try {
InetSocketAddress sockAddr = new InetSocketAddress(port);
server = HttpServer.create(sockAddr, 0);
} catch (IOException e) {
e.printStackTrace();
}
server.createContext("/", new RootHandler());
server.createContext("/actions", new ActionHandler());
server.createContext("/guilds", new GuildHandler());
ExecutorService executor = Executors.newFixedThreadPool(10, new BaseThreadFactory("web-api"));
server.setExecutor(executor);
server.start();
logger.info("Web server started at [" + server.getAddress() + "]");
}
}
@@ -0,0 +1,45 @@
package me.bobert.netneutrality.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
import net.dv8tion.jda.api.audio.AudioSendHandler;
import java.nio.ByteBuffer;
/**
* This is a wrapper around AudioPlayer which makes it behave as an AudioSendHandler for JDA. As JDA calls canProvide
* before every call to provide20MsAudio(), we pull the frame in canProvide() and use the frame we already pulled in
* provide20MsAudio().
*/
public class AudioPlayerSendHandler implements AudioSendHandler {
private final AudioPlayer audioPlayer;
private AudioFrame lastFrame;
/**
* @param audioPlayer Audio player to wrap.
*/
public AudioPlayerSendHandler(AudioPlayer audioPlayer) {
this.audioPlayer = audioPlayer;
}
@Override
public boolean canProvide() {
lastFrame = audioPlayer.provide();
return lastFrame != null;
}
@Override
public ByteBuffer provide20MsAudio() {
return ByteBuffer.wrap(lastFrame.getData());
}
@Override
public boolean isOpus() {
return true;
}
}
@@ -0,0 +1,253 @@
package me.bobert.netneutrality.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
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 me.bobert.netneutrality.command.CommandExecutionResult;
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.db.GSettings;
import me.bobert.netneutrality.exceptions.CommandExecutionException;
import me.bobert.netneutrality.music.loadable.LoadableSong;
import me.bobert.netneutrality.music.source.UdioAudioSourceManager;
import me.bobert.netneutrality.thread.MusicPlayerAutoDisconnectThread;
import me.bobert.netneutrality.thread.ThreadManager;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class MusicManager {
private final static Logger logger = LogManager.getLogger(MusicManager.class);
private final static Map<String, MusicManager> managers = new ConcurrentHashMap<>();
private final static ScheduledExecutorService executor = ThreadManager.getMusicExecutor();
private final static AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
private final Guild guild;
private final AudioPlayer player;
private final TrackScheduler scheduler;
private TrackLoader lastTrackLoader;
private ScheduledFuture<?> autoDisconnectThread;
private final int baseVolume;
private MusicManager(Guild guild) {
this.guild = guild;
this.player = playerManager.createPlayer();
this.scheduler = new TrackScheduler(this);
this.baseVolume = Integer.parseInt(GuildManager.getManager(guild).getGuildConfigManager().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 static void initPlayerManager() {
playerManager.registerSourceManager(new YoutubeAudioSourceManager());
playerManager.registerSourceManager(SoundCloudAudioSourceManager.createDefault());
playerManager.registerSourceManager(new UdioAudioSourceManager());
playerManager.registerSourceManager(new HttpAudioSourceManager());
}
public boolean isNotConnectedTo(VoiceChannel vChannel) {
return vChannel == null || !vChannel.equals(guild.getAudioManager().getConnectedChannel());
}
public synchronized void connect(VoiceChannel vChannel) {
if (vChannel != null && isNotConnectedTo(vChannel)) {
logger.debug("Guild {}: connecting to voice channel {}", vChannel.getGuild().getId(), vChannel.getId());
guild.getAudioManager().openAudioConnection(vChannel);
guild.getAudioManager().setSelfDeafened(true);
player.setVolume(baseVolume);
if (autoDisconnectThread == null)
autoDisconnectThread = executor.scheduleWithFixedDelay(new MusicPlayerAutoDisconnectThread(this), 240, 240, TimeUnit.SECONDS);
} else {
throw new RuntimeException("Error connecting to voice channel");
}
}
public boolean isConnected() {
return guild != null && guild.getAudioManager().getConnectedChannel() != null;
}
public synchronized AudioTrack getCurrentlyPlaying() {
return player.getPlayingTrack();
}
public synchronized boolean isCurrentlyPlaying() {
return player.getPlayingTrack() != null;
}
public synchronized Member getRequesterOfCurrentTrack() {
QueueManager.TrackInfo info = scheduler.getQueueManager().peekHeadOfQueue();
if (info == null)
return null;
return info.getRequester();
}
protected AudioPlayer getPlayer() {
return player;
}
public Guild getGuild() {
return guild;
}
public void stopLoading() {
lastTrackLoader.stopLoading();
}
public synchronized void leave() {
if (guild != null && guild.getAudioManager().getConnectedChannel() != null) {
logger.debug("Guild {}: leaving voice channel {}", guild.getId(), guild.getAudioManager().getConnectedChannel().getId());
player.stopTrack();
guild.getAudioManager().closeAudioConnection();
}
scheduler.detachTextChannel();
if (autoDisconnectThread != null) {
autoDisconnectThread.cancel(true);
autoDisconnectThread = null;
}
}
public static void disconnectPlayers() {
for (MusicManager mm : managers.values()) {
mm.getScheduler().getQueueManager().clearQueue();
mm.player.destroy();
}
}
public synchronized void play(String url, CommandContext context, boolean needToBeConnected, boolean silent) {
logger.debug("Guild {}: request to play '{}' by user {}", guild.getId(), url, context.getGuildUser().getId());
List<LoadableSong> toLoad = new ArrayList<>();
toLoad.add(new LoadableSong(null, url, null));
play(toLoad, context, needToBeConnected, silent);
}
public void play(List<LoadableSong> songs, CommandContext context, boolean needToBeConnected, boolean silent) {
Member member = context.getGuildUser();
MessageChannel channel = context.getChannel();
if (member.getVoiceState() == null) {
logger.error("Guild {}: Voice state is null", guild.getId());
context.replyToMessage("Voice state is null, something has gone terribly wrong.");
LogHandler.log(new CommandExecutionException("Voice state is null", context, null));
return;
}
if (!member.getVoiceState().inAudioChannel()) {
context.replyToMessage("You are not in a voice channel");
return;
}
VoiceChannel vChannel = (VoiceChannel) member.getVoiceState().getChannel();
if (needToBeConnected && isNotConnectedTo(vChannel)) {
context.replyToMessage("The player is not in your voice channel");
return;
}
if (isNotConnectedTo(vChannel)) {
connect(vChannel);
scheduler.attachTextChannel(channel);
}
TrackLoader loader = new TrackLoader(this, context, playerManager, silent);
lastTrackLoader = loader;
loader.loadAllSongs(songs);
}
public synchronized void stopTrack() {
player.stopTrack();
}
public synchronized CommandExecutionResult voteSkip(CommandContext context) {
VoiceChannel voiceChannel = (VoiceChannel) guild.getAudioManager().getConnectedChannel();
if (voiceChannel == null) {
return CommandExecutionResult.none();
}
if (!isCurrentlyPlaying()) {
return CommandExecutionResult.none();
}
QueueManager.TrackInfo info = scheduler.getQueueManager().peekHeadOfQueue();
if (info == null) {
return CommandExecutionResult.none();
}
// Minus the bot itself
int connected = voiceChannel.getMembers().size() - 1;
if (connected < 3) {
context.reply("Skipping current track since not enough people are connected to the voice channel.");
scheduler.getQueueManager().skip();
return CommandExecutionResult.none();
}
double needed = connected / 2.1;
info.addSkip(context.getGuildUser());
logger.debug("VoteSkip: guild={}, connected={}, needed={}, skips={}, user={}", context.getGuild().getId(), connected, needed, info.getSkips().size(), context.getGuildUser().getId());
if (info.getSkips().size() > needed) {
return scheduler.getQueueManager().skip();
} else {
context.reply("Adding your vote to skip the current track. Current votes: %d/%d", info.getSkips().size(), (int) Math.ceil(needed));
}
return CommandExecutionResult.none();
}
public synchronized void setVolume(int volume) {
player.setVolume(volume);
}
public int getQueueSize() {
return scheduler.getQueueManager().getQueueSize();
}
public TrackScheduler getScheduler() {
return scheduler;
}
}
@@ -0,0 +1,316 @@
package me.bobert.netneutrality.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import me.bobert.netneutrality.command.CommandExecutionResult;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Predicate;
public class QueueManager {
public static class TrackInfo {
private final AudioTrack track;
private final Member member;
private final Set<String> skips;
private final Logger logger = LogManager.getLogger(QueueManager.class);
public TrackInfo(AudioTrack track, Member member) {
this.track = track;
this.member = member;
this.skips = new HashSet<>();
}
public AudioTrack getTrack() {
return track;
}
public Set<String> getSkips() {
return skips;
}
public void addSkip(Member member) {
if (!hasSkip(member)) {
logger.debug("VoteSkip: user={}, skip doesn't exist, before={}, after={}", member.getId(), skips.size(), skips.size() + 1);
skips.add(member.getId());
}
}
public boolean hasSkip(Member member) {
return skips.contains(member.getId());
}
public Member getRequester() {
return member;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TrackInfo trackInfo = (TrackInfo) o;
return track.equals(trackInfo.track);
}
@Override
public int hashCode() {
return Objects.hash(track);
}
@Override
public String toString() {
return "TrackInfo{" +
"track=" + track.getInfo().title +
", member=" + member.getEffectiveName() +
", skips=" + skips +
'}';
}
}
private final Logger logger = LogManager.getLogger(QueueManager.class);
private final TrackScheduler scheduler;
private final AudioPlayer player;
private final Guild guild;
private final Deque<TrackInfo> queue;
public QueueManager(TrackScheduler scheduler) {
this.scheduler = scheduler;
this.player = scheduler.getPlayer();
this.guild = scheduler.getGuild();
this.queue = new LinkedBlockingDeque<>();
}
public List<TrackInfo> getQueue() {
synchronized (queue) {
return queue.stream().toList();
}
}
public TrackScheduler getScheduler() {
return scheduler;
}
public Guild getGuild() {
return guild;
}
public int getQueueSize() {
synchronized (queue) {
return queue.size();
}
}
public List<TrackInfo> getQueueWithFilter(Predicate<? super TrackInfo> filter) {
synchronized (queue) {
return queue.stream().filter(filter).toList();
}
}
public TrackInfo peekHeadOfQueue() {
synchronized (queue) {
return queue.peek();
}
}
public void removeHeadOfQueue() {
synchronized (queue) {
queue.poll();
}
}
public boolean isEmpty() {
synchronized (queue) {
return queue.isEmpty();
}
}
public CommandExecutionResult clearQueue() {
scheduler.getMusicManager().stopLoading();
scheduler.getMusicManager().leave();
if (queue.isEmpty()) {
return CommandExecutionResult.warn("Queue is empty!");
}
synchronized (queue) {
int queueSize = queue.size();
if (queueSize == 0) {
return CommandExecutionResult.warn("Queue is empty!");
}
queue.clear();
return CommandExecutionResult.success(String.format("Removed %d track%s from the queue", queueSize--, (queueSize > 1 ? "s" : "")));
}
}
public void removeTracksFromUser(Member member) {
synchronized (queue) {
int size = queue.stream().filter(trackInfo -> member.equals(trackInfo.getRequester())).toList().size();
if (size > 0) {
if (size == queue.size()) {
clearQueue();
return;
}
TrackInfo first = queue.peek();
queue.removeIf(trackInfo -> member.equals(trackInfo.getRequester()));
if (first != null && member.equals(first.getRequester())) {
skip(1);
}
}
}
}
public ArrayList<AudioTrack> getAllQueuedTrack() {
synchronized (queue) {
ArrayList<AudioTrack> tracks = new ArrayList<>();
queue.forEach(trackInfo -> tracks.add(trackInfo.getTrack()));
return tracks;
}
}
public void queue(AudioTrack track, Member member) {
TrackInfo info = new TrackInfo(track, member);
synchronized (queue) {
if (queue.contains(info)) {
return;
}
if (player.getPlayingTrack() == null && !queue.isEmpty()) {
queue.addFirst(info);
}
if ((player.getPlayingTrack() == null && queue.isEmpty()) || player.getPlayingTrack() != null) {
queue.add(info);
}
if (player.getPlayingTrack() == null) {
AudioTrack toPlay = (queue.peek() == null ? null : queue.peek().getTrack());
player.startTrack(toPlay, true);
}
}
}
public void startNextTrack() {
synchronized (queue) {
TrackInfo last = queue.poll();
TrackInfo next = queue.peek();
if (next != null) {
player.startTrack(next.getTrack().makeClone(), false);
} else {
player.stopTrack();
}
}
}
public CommandExecutionResult skip() {
return skip(1);
}
public CommandExecutionResult skip(int nbTrack) {
if (nbTrack == 0) {
nbTrack = 1;
}
synchronized (queue) {
if (queue.isEmpty()) {
scheduler.getMusicManager().leave();
return CommandExecutionResult.info("Queue is empty, leaving voice channel");
}
if (nbTrack > queue.size()) {
nbTrack = 1;
}
logger.debug("Guild {}: skipping {} tracks", guild.getId(), nbTrack);
for (int i = 1; i < nbTrack; i++) {
queue.poll();
}
if (queue.isEmpty()) {
scheduler.getMusicManager().leave();
return CommandExecutionResult.info("Queue is empty, leaving voice channel");
}
startNextTrack();
return CommandExecutionResult.success(String.format("Skipping %s track%s..", nbTrack, (nbTrack > 1 ? "s" : "")));
}
}
public CommandExecutionResult shuffle() {
if (queue.size() > 3) {
return CommandExecutionResult.none();
}
synchronized (queue) {
if (queue.size() > 3) {
logger.debug("Guild {}: shuffling queue", guild.getId());
TrackInfo current = queue.poll();
ArrayList<TrackInfo> newQueue = new ArrayList<>(queue);
Collections.shuffle(newQueue);
queue.clear();
queue.add(current);
queue.addAll(newQueue);
return CommandExecutionResult.success("Queue is now shuffled.");
}
return CommandExecutionResult.none();
}
}
public CommandExecutionResult removeFromQueue(int index) {
if (queue.isEmpty() || index < 1) {
return CommandExecutionResult.none();
}
synchronized (queue) {
if (queue.isEmpty()) {
return CommandExecutionResult.none();
}
if (index > queue.size()) {
return CommandExecutionResult.error("Position is out of bounds");
}
logger.debug("Guild {}: removing index {} from queue", guild.getId(), index - 1);
if (index > 1) {
List<TrackInfo> newQueue = new ArrayList<>(queue);
newQueue.remove(index - 1);
queue.clear();
queue.addAll(newQueue);
return CommandExecutionResult.success(String.format("Removed track #%d from the queue", index));
}
skip();
return CommandExecutionResult.none();
}
}
}
@@ -0,0 +1,72 @@
package me.bobert.netneutrality.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SourceFinder {
public enum AudioSourceType {
Youtube,
YTMusic,
Soundcloud,
Bandcamp,
Vimeo,
YTSearch,
Udio,
DirectLinkMp3,
Nothing
}
private static final Pattern youtubePattern = Pattern.compile("^(?:https?://)?(?:www\\.)?(?:youtube\\.com|youtu\\.be)/.*?(?:embed|e|v|watch.*?v=)?/?([-_a-zA-Z0-9]{10,})?(?:&?index=\\d+)?(?>(?:playlist\\?|&)?list=([^#\\\\&?]{12,}))?", Pattern.CASE_INSENSITIVE);
private static final Pattern youtubeMusicPattern = Pattern.compile("^(?:https?://)?(?:www\\.)?music\\.(?:youtube\\.com|youtu\\.be)/.*?(?:embed|e|v|watch.*?v=)?/?([-_a-zA-Z0-9]{10,})?(?:&?index=\\d+)?(?>(?:playlist\\?|&)?list=([^#\\\\&?]{12,}))?", Pattern.CASE_INSENSITIVE);
private static final Pattern soundcloudPattern = Pattern.compile("^(?:https?://)?(?:www\\.)?(?:m\\.)?soundcloud\\.com/([a-zA-Z0-9-_]+)/([a-zA-Z0-9-_]+)(?:\\\\?.*|)$", Pattern.CASE_INSENSITIVE);
private static final Pattern udioPattern = Pattern.compile("^https://www.udio.com/songs/[0-9A-Za-z]+$", Pattern.CASE_INSENSITIVE);
private static final Pattern mp3Pattern = Pattern.compile("^https?://(?:.*)(?:.mp3)$", Pattern.CASE_INSENSITIVE);
public static AudioSourceType getSource(String url) {
Matcher youtubeMatcher = youtubePattern.matcher(url);
Matcher youtubeMusicMatcher = youtubeMusicPattern.matcher(url);
Matcher soundcloudMatcher = soundcloudPattern.matcher(url);
Matcher udioMatcher = udioPattern.matcher(url);
Matcher mp3Matcher = mp3Pattern.matcher(url);
if (youtubeMatcher.find())
return AudioSourceType.Youtube;
if (youtubeMusicMatcher.find())
return AudioSourceType.YTMusic;
if (soundcloudMatcher.find())
return AudioSourceType.Soundcloud;
if (udioMatcher.find())
return AudioSourceType.Udio;
if (mp3Matcher.find())
return AudioSourceType.DirectLinkMp3;
if (!(url.startsWith("http://") || url.startsWith("https://")))
return AudioSourceType.YTSearch;
return AudioSourceType.Nothing;
}
public static String getIdentifier(String url) {
String identifier;
identifier = switch (getSource(url)) {
case Youtube, YTMusic, Soundcloud, Udio, DirectLinkMp3 -> url;
case YTSearch -> "ytsearch:" + url;
default -> null;
};
return identifier;
}
}
@@ -0,0 +1,126 @@
package me.bobert.netneutrality.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.music.loadable.CachedSource;
import me.bobert.netneutrality.music.loadable.LoadableMusicSource;
import me.bobert.netneutrality.music.loadable.LoadableSong;
import me.bobert.netneutrality.thread.ThreadManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
public class TrackLoader implements Runnable {
private final Logger logger = LogManager.getLogger(TrackLoader.class);
private final CommandContext context;
private final MusicManager musicManager;
private final AudioPlayerManager audioPlayerManager;
private TrackLoadingHandler lastLoadingHandler;
private List<LoadableSong> songs;
private LoadableMusicSource source;
private int numberOfTracksLoaded;
private int totalNumberOfTracks;
private boolean loadingDisabled = false;
private boolean silent;
private final Object sync = new Object();
public TrackLoader(MusicManager musicManager, CommandContext context, AudioPlayerManager playerManager, boolean silent) {
this.musicManager = musicManager;
this.context = context;
this.audioPlayerManager = playerManager;
this.silent = silent;
}
public void loadAllSongs(List<LoadableSong> songs) {
this.songs = songs;
if (songs.size() > 1) {
this.silent = true;
}
ThreadManager.getMusicExecutor().submit(this);
}
public void loadAllSongs(CachedSource cachedSource) {
this.source = cachedSource.getSource();
loadAllSongs(cachedSource.getSongs());
}
public void stopLoading() {
loadingDisabled = true;
if (lastLoadingHandler != null) {
lastLoadingHandler.stopLoading();
}
}
@Override
public void run() {
List<LoadableSong> copy = new ArrayList<>(songs);
for (LoadableSong song : songs) {
if (loadingDisabled) {
copy.remove(song);
break;
}
SourceFinder.AudioSourceType type = SourceFinder.getSource(song.name());
String identifier = SourceFinder.getIdentifier(song.name());
if (identifier != null) {
TrackLoadingHandler loadingHandler = new TrackLoadingHandler(context, musicManager, identifier, type, silent);
loadingHandler.useCallbackOnLoad(success -> {
synchronized (sync) {
if (success) {
numberOfTracksLoaded++;
}
totalNumberOfTracks++;
logger.debug("callback notify; total={}, loaded={}", totalNumberOfTracks, numberOfTracksLoaded);
sync.notify();
}
});
lastLoadingHandler = loadingHandler;
audioPlayerManager.loadItemOrdered(this, identifier, loadingHandler);
} else {
copy.remove(song);
if (songs.size() == 1) {
context.replyToMessage("No source found to play the given url");
}
}
}
try {
synchronized (sync) {
while (!(copy.size() == totalNumberOfTracks)) {
logger.debug("waiting for load; size={}, total={}, loaded={}", songs.size(), totalNumberOfTracks, numberOfTracksLoaded);
sync.wait();
}
//TODO: logic
if (copy.size() > 1 && source != null && !loadingDisabled) {
context.replyToMessage("Loaded %d of %d tracks from online source", numberOfTracksLoaded, totalNumberOfTracks);
}
}
} catch (InterruptedException exception) {
stopLoading();
context.replyToMessage("Loading of tracks was interrupted due to an exception");
LogHandler.log(exception);
}
}
}
@@ -0,0 +1,129 @@
package me.bobert.netneutrality.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.utils.MessageFunction;
import net.dv8tion.jda.api.entities.Guild;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.Consumer;
public class TrackLoadingHandler implements AudioLoadResultHandler {
private final Logger logger = LogManager.getLogger(TrackLoadingHandler.class);
private final String identifier;
private final SourceFinder.AudioSourceType type;
private final boolean silent;
private Consumer<Boolean> loadConsumer;
private boolean loadingDisabled = false;
private final Guild guild;
private final TrackScheduler scheduler;
private final CommandContext context;
public TrackLoadingHandler(CommandContext context, MusicManager manager, String identifier, SourceFinder.AudioSourceType type, boolean silent) {
this.identifier = identifier;
this.type = type;
this.silent = silent;
this.guild = manager.getGuild();
this.scheduler = manager.getScheduler();
this.context = context;
}
public synchronized void stopLoading() {
logger.debug("Guild {}: stopped loading of new tracks", guild.getId());
loadingDisabled = true;
}
public void useCallbackOnLoad(Consumer<Boolean> consumer) {
this.loadConsumer = consumer;
}
@Override
public void trackLoaded(AudioTrack audioTrack) {
if (loadConsumer != null) {
loadConsumer.accept(true);
}
if (loadingDisabled)
return;
context.deleteMessage();
logger.debug("Guild {}: track loaded: {}", guild.getId(), audioTrack.getIdentifier());
scheduler.getQueueManager().queue(audioTrack, context.getGuildUser());
if (!silent) {
context.reply(MessageFunction.buildAddingTrackMessage(audioTrack, type));
}
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
if (loadConsumer != null) {
loadConsumer.accept(true);
}
if (loadingDisabled)
return;
logger.debug("Guild {}: playlist loaded: {}", guild.getId(), identifier);
if (!playlist.getTracks().isEmpty()) {
if (playlist.isSearchResult()) {
AudioTrack track = playlist.getTracks().get(0);
scheduler.getQueueManager().queue(track, context.getGuildUser());
if (!silent) {
context.reply(MessageFunction.buildAddingTrackMessage(track, type));
}
} else {
context.deleteMessage();
if (!silent) {
context.reply(MessageFunction.buildAddingPlaylistMessage(playlist, identifier, type));
}
for (AudioTrack track : playlist.getTracks()) {
if (loadingDisabled) {
break;
}
scheduler.getQueueManager().queue(track, context.getGuildUser());
}
}
}
}
@Override
public void noMatches() {
if (loadConsumer != null)
loadConsumer.accept(false);
if (!silent)
context.replyToMessage("No matches found");
}
@Override
public void loadFailed(FriendlyException e) {
if (loadConsumer != null)
loadConsumer.accept(false);
logger.debug("Guild {}: load failed for identifier {}", guild.getId(), identifier, e);
LogHandler.log(e);
if (!silent)
context.replyToMessage(MessageFunction.buildSentryErrorMessage("Failed to load track: " + e.getMessage()));
}
}
@@ -0,0 +1,130 @@
package me.bobert.netneutrality.music;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import me.bobert.netneutrality.core.LogHandler;
import me.bobert.netneutrality.utils.MessageFunction;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TrackScheduler extends AudioEventAdapter {
private final static Logger logger = LogManager.getLogger(TrackScheduler.class);
private final QueueManager queueManager;
private final MusicManager manager;
private final AudioPlayer player;
private MessageChannel currentChannel;
private final Guild guild;
public TrackScheduler(MusicManager manager) {
this.manager = manager;
this.player = manager.getPlayer();
this.guild = manager.getGuild();
this.queueManager = new QueueManager(this);
}
public QueueManager getQueueManager() {
return queueManager;
}
public MusicManager getMusicManager() {
return manager;
}
public AudioPlayer getPlayer() {
return player;
}
public Guild getGuild() {
return guild;
}
public MessageChannel getTextChannel() {
return currentChannel;
}
public synchronized void attachTextChannel(MessageChannel channel) {
this.currentChannel = channel;
}
public synchronized void detachTextChannel() {
this.currentChannel = null;
}
public void sendMessageToChannel(String msg) {
if (currentChannel != null && currentChannel.canTalk()) {
currentChannel.sendMessage(msg).queue();
}
}
public void sendMessageToChannel(MessageEmbed embed, MessageEmbed... embeds) {
if (currentChannel != null && currentChannel.canTalk()) {
currentChannel.sendMessageEmbeds(embed, embeds).queue();
}
}
@Override
public void onPlayerPause(AudioPlayer player) {
// Player was paused
}
@Override
public void onPlayerResume(AudioPlayer player) {
// Player was resumed
}
@Override
public void onTrackStart(AudioPlayer player, AudioTrack track) {
logger.debug("Guild {}: starting track: {}", guild.getId(), track.getIdentifier());
}
@Override
public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
logger.debug("Guild {}: currently playing track ended: {}", guild.getId(), track.getIdentifier());
if (endReason == AudioTrackEndReason.LOAD_FAILED) {
logger.debug("Guild {}: failed loading track: {}", guild.getId(), track.getIdentifier());
}
// check if mayStartNext
if (endReason == AudioTrackEndReason.FINISHED || endReason == AudioTrackEndReason.LOAD_FAILED) {
queueManager.startNextTrack();
}
if (endReason == AudioTrackEndReason.STOPPED) {
queueManager.removeHeadOfQueue();
}
// endReason == FINISHED: A track finished or died by an exception (mayStartNext = true).
// endReason == LOAD_FAILED: Loading of a track failed (mayStartNext = true).
// endReason == STOPPED: The player was stopped.
// endReason == REPLACED: Another track started playing while this had not finished
// endReason == CLEANUP: Player hasn't been queried for a while, if you want you can put a
// clone of this back to your queue
}
@Override
public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) {
logger.debug("Guild {}: exception while playing track [{}]: {}", guild.getId(), track.getIdentifier(), exception.getMessage());
LogHandler.log(exception);
sendMessageToChannel(MessageFunction.buildSentryErrorMessage(String.format("Error while playing track [%s]: %s", track.getInfo().title, exception.getMessage())));
}
@Override
public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) {
logger.debug("Guild {}: currently playing track is stuck: {}", guild.getId(), track.getIdentifier());
sendMessageToChannel("Current track is stuck, skipping to next");
queueManager.startNextTrack();
}
}
@@ -0,0 +1,41 @@
package me.bobert.netneutrality.music.loadable;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class AppleMostPlayed implements LoadableMusicSource {
@Override
public String url() {
return "https://rss.applemarketingtools.com/api/v2/ca/music/most-played/25/songs.json";
}
@Override
public List<LoadableSong> parse(JSONObject obj) {
List<LoadableSong> songs = new ArrayList<>();
JSONArray results = obj.getJSONObject("feed").getJSONArray("results");
results.forEach(o -> {
JSONObject song = (JSONObject) o;
String artist = song.getString("artistName");
String songName = song.getString("name");
String artwork = song.getString("artworkUrl100");
songs.add(new LoadableSong(artist, songName, artwork));
});
return songs;
}
@Override
public String getIdentifier() {
return "apple";
}
}
@@ -0,0 +1,39 @@
package me.bobert.netneutrality.music.loadable;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.time.OffsetDateTime;
import java.util.List;
public class CachedSource {
private final LoadableMusicSource source;
private OffsetDateTime lastAccess;
private final List<LoadableSong> songs;
protected CachedSource(LoadableMusicSource source, List<LoadableSong> songs) {
this.source = source;
this.lastAccess = OffsetDateTime.now();
this.songs = songs;
}
public List<LoadableSong> getSongs() {
return songs;
}
public OffsetDateTime getLastAccess() {
return lastAccess;
}
public LoadableMusicSource getSource() {
return source;
}
public void updateAccessTime() {
this.lastAccess = OffsetDateTime.now();
}
}
@@ -0,0 +1,19 @@
package me.bobert.netneutrality.music.loadable;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import org.json.JSONObject;
import java.util.List;
public interface LoadableMusicSource {
String url();
List<LoadableSong> parse(JSONObject obj);
String getIdentifier();
}
@@ -0,0 +1,18 @@
package me.bobert.netneutrality.music.loadable;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
public record LoadableSong(String artist, String name, String artworkUrl) {
@Override
public String toString() {
return "LoadableSong{" +
"artist='" + artist + '\'' +
", name='" + name + '\'' +
", artworkUrl='" + artworkUrl + '\'' +
'}';
}
}
@@ -0,0 +1,57 @@
package me.bobert.netneutrality.music.loadable;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.http.WebModule;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LoadableSourceManager {
private final Logger logger = LogManager.getLogger(LoadableSourceManager.class);
private static final Map<String, CachedSource> cache = new HashMap<>();
public CachedSource get(LoadableMusicSource source) {
if (cache.containsKey(source.getIdentifier())) {
CachedSource cachedSource = cache.get(source.getIdentifier());
if (OffsetDateTime.now().isAfter(cachedSource.getLastAccess().plusHours(4))) {
cachedSource.getSongs().clear();
cachedSource.getSongs().addAll(loadFromUrl(source));
cachedSource.updateAccessTime();
}
return cachedSource;
}
List<LoadableSong> songs = loadFromUrl(source);
CachedSource newCachedSource = new CachedSource(source, songs);
cache.put(source.getIdentifier(), newCachedSource);
return newCachedSource;
}
private List<LoadableSong> loadFromUrl(LoadableMusicSource source) {
WebModule web = new WebModule(source.url());
JSONObject obj = web.request().getAsObject();
if (obj == null)
return Collections.emptyList();
List<LoadableSong> songs = source.parse(obj);
Collections.shuffle(songs);
return songs;
}
}
@@ -0,0 +1,160 @@
package me.bobert.netneutrality.music.source;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.fasterxml.jackson.core.JsonParseException;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager;
import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools;
import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterfaceManager;
import com.sedmelluq.discord.lavaplayer.track.AudioItem;
import com.sedmelluq.discord.lavaplayer.track.AudioReference;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS;
public class UdioAudioSourceManager implements AudioSourceManager {
private static final Logger log = LogManager.getLogger(UdioAudioSourceManager.class);
private static final String TRACK_URL_REGEX = "^https://www.udio.com/songs/[0-9A-Za-z]+$";
private static final Pattern trackUrlPattern = Pattern.compile(TRACK_URL_REGEX);
private final HttpInterfaceManager httpInterfaceManager;
public UdioAudioSourceManager() {
httpInterfaceManager = HttpClientTools.createDefaultThreadLocalManager();
}
@Override
public String getSourceName() {
return "udio";
}
@Override
public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference) {
if (!trackUrlPattern.matcher(reference.identifier).matches()) {
return null;
}
try (HttpInterface httpInterface = httpInterfaceManager.getInterface()) {
return loadFromTrackPage(httpInterface, reference.identifier);
} catch (IOException e) {
throw new FriendlyException("Loading Udio track information failed.", SUSPICIOUS, e);
}
}
@Override
public boolean isTrackEncodable(AudioTrack track) {
return true;
}
@Override
public void encodeTrack(AudioTrack track, DataOutput output) throws IOException {
}
@Override
public AudioTrack decodeTrack(AudioTrackInfo trackInfo, DataInput input) throws IOException {
return new UdioAudioTrack(trackInfo, this);
}
@Override
public void shutdown() {
ExceptionTools.closeWithWarnings(httpInterfaceManager);
}
public HttpInterface getHttpInterface() {
return httpInterfaceManager.getInterface();
}
private AudioItem loadFromTrackPage(HttpInterface httpInterface, String trackUrl) throws IOException {
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(trackUrl))) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_NOT_FOUND) {
return AudioReference.NO_TRACK;
} else if (!HttpClientTools.isSuccessWithContent(statusCode)) {
throw new FriendlyException("Server responded with an error.", SUSPICIOUS,
new IllegalStateException("Response code is " + statusCode));
}
return loadTrackFromPageContent(trackUrl, IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
}
}
private AudioTrack loadTrackFromPageContent(String trackUrl, String content) throws IOException {
JsonBrowser trackInfo = loadTrackInfoFromPageContent(content);
if (trackInfo == null) {
log.debug("Udio HTML: {}", content);
throw new FriendlyException("Udio: Failed to extract track information from web page.", SUSPICIOUS, null);
}
String title = trackInfo.get("title").text();
String artist = trackInfo.get("artist").text();
long duration = (long) (trackInfo.get("duration").as(Double.class) * 1000.0);
String playbackUrl = trackInfo.get("song_path").text();
String artworkUrl = getArtworkUrlFromImagePath(trackInfo);
AudioTrackInfo audioTrackInfo = new AudioTrackInfo(title, artist, duration, trackUrl, false, playbackUrl, artworkUrl, null);
return new UdioAudioTrack(audioTrackInfo, this);
}
protected JsonBrowser loadTrackInfoFromPageContent(String content) throws IOException {
String startTag = "self.__next_f.push([1,\"9:";
String endTag = "\\n\"])";
String json = DataFormatTools.extractBetween(content, startTag, endTag);
if (json == null) {
log.error("Track JSON not found in page");
return null;
}
try {
String unescapedJson = json.replace("\\", "");
JsonBrowser parsedJson = JsonBrowser.parse(unescapedJson);
return parsedJson.index(3).get("children").index(3).get("children").index(3).get("track");
} catch (JsonParseException e) {
log.error("Failed to parse track JSON: {}", json);
return null;
}
}
private String getArtworkUrlFromImagePath(JsonBrowser trackInfo) {
String imagePath = trackInfo.get("image_path").text();
String artworkOptions = "width=3840,quality=75";
return imagePath.replace("public", artworkOptions);
}
}
@@ -0,0 +1,80 @@
package me.bobert.netneutrality.music.source;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.container.mp3.Mp3AudioTrack;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
import com.sedmelluq.discord.lavaplayer.tools.io.PersistentHttpStream;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import com.sedmelluq.discord.lavaplayer.track.DelegatedAudioTrack;
import com.sedmelluq.discord.lavaplayer.track.playback.LocalAudioTrackExecutor;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS;
public class UdioAudioTrack extends DelegatedAudioTrack {
private static final Logger log = LogManager.getLogger(UdioAudioTrack.class);
private final UdioAudioSourceManager sourceManager;
/**
* @param trackInfo Track info
* @param sourceManager Source manager which was used to find this track
*/
public UdioAudioTrack(AudioTrackInfo trackInfo, UdioAudioSourceManager sourceManager) {
super(trackInfo);
this.sourceManager = sourceManager;
}
@Override
public void process(LocalAudioTrackExecutor localExecutor) throws Exception {
try (HttpInterface httpInterface = sourceManager.getHttpInterface()) {
String playbackUrl = loadPlaybackUrl(httpInterface);
log.debug("Starting Udio track from URL: {}", playbackUrl);
try (PersistentHttpStream stream = new PersistentHttpStream(httpInterface, new URI(playbackUrl), null)) {
processDelegate(new Mp3AudioTrack(trackInfo, stream), localExecutor);
}
}
}
private String loadPlaybackUrl(HttpInterface httpInterface) throws IOException {
JsonBrowser trackInfo = loadTrackInfo(httpInterface);
if (trackInfo == null) {
throw new FriendlyException("Udio: Failed to extract track information from web page.", SUSPICIOUS, null);
}
return trackInfo.get("song_path").text();
}
private JsonBrowser loadTrackInfo(HttpInterface httpInterface) throws IOException {
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(trackInfo.identifier))) {
int statusCode = response.getStatusLine().getStatusCode();
if (!HttpClientTools.isSuccessWithContent(statusCode)) {
throw new FriendlyException("Server responded with an error.", SUSPICIOUS,
new IllegalStateException("Response code for player config is " + statusCode));
}
return sourceManager.loadTrackInfoFromPageContent(IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
}
}
}
@@ -0,0 +1,31 @@
package me.bobert.netneutrality.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.exceptions.ExecutorException;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
public class BaseThreadFactory implements ThreadFactory {
private final AtomicLong threadId = new AtomicLong(1);
private final String name;
public BaseThreadFactory(String name) {
this.name = name;
}
@Override
public Thread newThread(@NotNull Runnable r) {
Thread thread = new Thread(r);
thread.setName(name + "-" + threadId.getAndIncrement());
thread.setUncaughtExceptionHandler(new ExecutorException());
return thread;
}
}
@@ -0,0 +1,55 @@
package me.bobert.netneutrality.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.NetNeutrality;
import me.bobert.netneutrality.music.MusicManager;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
public class MusicPlayerAutoDisconnectThread implements Runnable {
private final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnectThread.class);
private final MusicManager manager;
//TODO: move to scheduler
public MusicPlayerAutoDisconnectThread(MusicManager musicManager) {
manager = musicManager;
}
@Override
public void run() {
Guild guild = manager.getGuild();
VoiceChannel channel = (VoiceChannel) guild.getAudioManager().getConnectedChannel();
try {
if (channel != null) {
if (conditionsMetToDisconnect(channel)) {
// Sleep 30 seconds then check again (prevents disconnecting right after a track ends)
Thread.sleep(30000);
if (conditionsMetToDisconnect(channel)) {
logger.debug("Guild {}: auto-disconnect player from voice channel: {}", guild.getId(), channel.getId());
manager.leave();
}
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private boolean conditionsMetToDisconnect(VoiceChannel channel) {
List<Member> members = channel.getMembers().stream().filter(member -> !member.getId().equals(NetNeutrality.getSelf().getId())).toList();
return members.isEmpty() || manager.getScheduler().getQueueManager().isEmpty();
}
}
@@ -0,0 +1,28 @@
package me.bobert.netneutrality.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.NetNeutrality;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configurator;
public class ShutdownThread implements Runnable {
@Override
public void run() {
if (LogManager.getContext() instanceof LoggerContext) {
System.out.println("Shutting down log4j2");
Configurator.shutdown((LoggerContext) LogManager.getContext());
}
if (NetNeutrality.getJDA() != null) {
NetNeutrality.shutdown();
}
}
}
@@ -0,0 +1,75 @@
package me.bobert.netneutrality.thread;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.util.Map;
import java.util.concurrent.*;
public class ThreadManager {
public enum ExecutorType {
SINGLE, SCHEDULED, FIXED
}
private static final Map<String, ExecutorService> executors = new ConcurrentHashMap<>();
public static ScheduledExecutorService getMusicExecutor() {
return (ScheduledExecutorService) executors.computeIfAbsent(
"music-executor",
s -> Executors.newScheduledThreadPool(
Runtime.getRuntime().availableProcessors(),
new BaseThreadFactory("music-executor")
)
);
}
public static ExecutorService getExecutor() {
return executors.computeIfAbsent("general-executor", s -> Executors.newCachedThreadPool(new BaseThreadFactory("general-executor")));
}
public static ExecutorService getExecutor(String name) {
return executors.computeIfAbsent(name, s -> Executors.newSingleThreadExecutor(new BaseThreadFactory(name)));
}
public static ExecutorService getExecutor(String name, ExecutorType type) {
ExecutorService executor;
switch (type) {
case FIXED -> executor = executors.computeIfAbsent(name, s -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new BaseThreadFactory(name)));
case SCHEDULED -> executor = executors.computeIfAbsent(name, s -> Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new BaseThreadFactory(name)));
default -> executor = executors.computeIfAbsent(name, s -> Executors.newSingleThreadExecutor(new BaseThreadFactory(name)));
}
return executor;
}
public static void shutdownExecutor() {
System.out.println("[Executor] Shutting down...");
try {
for (ExecutorService ex : executors.values()) {
ex.shutdown();
ex.awaitTermination(1, TimeUnit.SECONDS);
}
}
catch (InterruptedException e) {
System.err.println("[Executor] Running tasks interrupted");
}
finally {
executors.forEach((name, ex) -> {
if (!ex.isTerminated()) {
System.err.println("[Executor: " + name + "] Forcing shutdown of running tasks..");
ex.shutdownNow();
}
});
}
System.out.println("[Executor] Shutdown finished!");
}
}
@@ -0,0 +1,170 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.Sentry;
import io.sentry.SpanStatus;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.thread.ThreadManager;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageHistory;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ChannelMessagePurger {
private static final Logger logger = LogManager.getLogger(ChannelMessagePurger.class);
private static final Map<GuildMessageChannel, ChannelMessagePurger> instances = new HashMap<>();
private final GuildMessageChannel channel;
private final Lock lock;
private ChannelMessagePurger(GuildMessageChannel channel) {
this.channel = channel;
this.lock = new ReentrantLock();
}
public static ChannelMessagePurger getPurger(GuildMessageChannel channel) {
return instances.computeIfAbsent(channel, ChannelMessagePurger::new);
}
public boolean canLockBeAcquired() {
boolean acquired = lock.tryLock();
if (acquired)
lock.unlock();
return acquired;
}
public void purgeMessages(CommandContext context, int toPurge) {
ThreadManager.getExecutor().submit(() -> {
if (lock.tryLock()) {
try {
int total = countAllChannelMessages(channel);
int finalToPurge = toPurge;
if (finalToPurge > total) {
finalToPurge = total;
}
safePurge(context, finalToPurge);
} finally {
lock.unlock();
}
}
});
}
public void purgeChannel(CommandContext context) {
ThreadManager.getExecutor().submit(() -> {
if (lock.tryLock()) {
try {
int total = countAllChannelMessages(channel);
context.reply("Number of messages to purge: %d", total);
safePurge(context, total);
} finally {
lock.unlock();
}
}
});
}
private synchronized int countAllChannelMessages(GuildMessageChannel channel) {
logger.info("Guild {}: counting all messages from channel {}", channel.getGuild().getId(), channel.getId());
MessageHistory history = channel.getHistory();
int totalCount = 0;
int iterationCount = 0;
while (totalCount == iterationCount) {
history.retrievePast(100).complete();
iterationCount += 100;
totalCount = history.size();
}
logger.info("Guild {}: Counted {} messages from channel {}", channel.getGuild().getId(), totalCount, channel.getId());
return totalCount;
}
private synchronized void safePurge(CommandContext context, int toPurge) {
ITransaction transaction = Sentry.startTransaction(String.format("purge messages from guild %s)", channel.getGuild().getId()), "task");
transaction.setData("channel", channel.getId());
transaction.setData("toPurge", toPurge);
if (toPurge == 1) {
toPurge += 1;
}
int tracker = toPurge;
int iteration = tracker / 100;
boolean olderThan2Weeks = false;
context.replyToMessage("Purging iterations to do: %d", iteration);
try {
for (int i = 0; i < iteration; i++) {
ISpan innerSpan = transaction.startChild("retrieve-messages", "Processing iteration " + (i + 1) + "/" + iteration);
try {
List<Message> messages = channel.getHistory().retrievePast(100).complete();
channel.deleteMessages(messages).complete();
tracker -= 100;
innerSpan.setStatus(SpanStatus.OK);
} catch (IllegalArgumentException ex) {
olderThan2Weeks = true;
} catch (Exception e) {
innerSpan.setThrowable(e);
innerSpan.setStatus(SpanStatus.INTERNAL_ERROR);
} finally {
innerSpan.finish();
}
}
if (tracker > 0) {
ISpan innerSpan = transaction.startChild("retrieve-messages", String.format("Processing final iteration (%d messages)", tracker));
try {
List<Message> messages = channel.getHistory().retrievePast(tracker).complete();
channel.deleteMessages(messages).complete();
innerSpan.setStatus(SpanStatus.OK);
} catch (IllegalArgumentException ex) {
olderThan2Weeks = true;
} catch (Exception e) {
innerSpan.setThrowable(e);
innerSpan.setStatus(SpanStatus.INTERNAL_ERROR);
} finally {
innerSpan.finish();
}
innerSpan.finish();
}
} catch (Exception e) {
transaction.setThrowable(e);
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
} finally {
transaction.finish();
MessageCreateBuilder builder = new MessageCreateBuilder()
.addContent("Finished purging ")
.addContent(String.valueOf(toPurge))
.addContent(" messages.");
if (olderThan2Weeks) {
builder.addContent("\n").addContent("Messages older than two weeks weren't deleted.");
}
context.reply(builder.build());
lock.unlock();
}
}
}
@@ -0,0 +1,87 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
import me.bobert.netneutrality.core.GuildManager;
import me.bobert.netneutrality.core.SessionManager;
import me.bobert.netneutrality.db.entities.OGuild;
import org.hibernate.HibernateException;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Session;
import java.io.Serializable;
import java.util.function.Consumer;
public class JpaUtils {
/**
* Tries to read the persistent state into the object.
*
* If the object is not found in the database and is of type {@link OGuild}, tries to recreate entity and throws
* an exception to block execution.
*
* The session is not closed
*
* @param session The session in which to load the persistent object
* @param object The persistent object
* @param id Identifier
*/
public static void tryLoad(Session session, Object object, Serializable id) {
try {
session.load(object, id);
} catch (HibernateException exception) {
if (exception instanceof ObjectNotFoundException) {
Sentry.addBreadcrumb(Breadcrumb.error("ObjectNotFoundException"));
if (object instanceof OGuild oGuild) {
String cachedId = oGuild.getGuildId();
Sentry.captureMessage("Trying to load Guild[" + cachedId + "] while entity doesn't exist in database, reloading..", SentryLevel.ERROR);
GuildManager guildManager = GuildManager.getManager(cachedId);
guildManager.reloadEntity();
guildManager.getBlacklistCache().clear();
Sentry.captureMessage("Guild[" + cachedId + "] reloaded", SentryLevel.INFO);
}
}
session.close();
throw exception;
}
}
public static void updateWithTransaction(Object persistantEntity, Runnable runnable) {
Session session = SessionManager.getSessionFactory().openSession();
session.beginTransaction();
runnable.run();
session.update(persistantEntity);
session.getTransaction().commit();
session.close();
}
public static void getWithSession(OGuild oGuild, Consumer<Session> consumer) {
getWithSession(oGuild, oGuild.getGuildId(), consumer);
}
/**
* @param persistantEntity The entity to load
* @param id The identifier for the entity
* @param close If the session should be closed after consumer is executed
* @param consumer The comsumer, which exposes the session
*/
public static void getWithSession(Object persistantEntity, Serializable id, Consumer<Session> consumer) {
Session session = SessionManager.getSessionFactory().openSession();
tryLoad(session, persistantEntity, id);
consumer.accept(session);
session.close();
}
}
@@ -0,0 +1,285 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import io.sentry.Sentry;
import me.bobert.netneutrality.command.AbstractCommand;
import me.bobert.netneutrality.command.CommandManager;
import me.bobert.netneutrality.command.CommandType;
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.exceptions.CommandException;
import me.bobert.netneutrality.music.MusicManager;
import me.bobert.netneutrality.music.QueueManager;
import me.bobert.netneutrality.music.SourceFinder;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class MessageFunction {
private final static Logger logger = LogManager.getLogger(MessageFunction.class);
public static MessageEmbed buildCurrentlyPlayingMessage(MusicManager manager) {
AudioTrack current = manager.getCurrentlyPlaying();
EmbedBuilder message = new EmbedBuilder();
message.setAuthor("Link to track", String.format("https://youtu.be/%s", current.getIdentifier()));
message.setThumbnail(String.format("https://img.youtube.com/vi/%s/default.jpg", current.getIdentifier()));
message.setTitle("Currently playing:");
message.setColor(Color.BLUE);
message.addField("Track title: ", current.getInfo().title, false);
message.addField("Duration: ", DurationFormatUtils.formatDuration(current.getPosition(), "**HH:mm:ss**", true) + " / " + DurationFormatUtils.formatDuration(current.getDuration(), "**HH:mm:ss**", true), false);
message.setTimestamp(OffsetDateTime.now());
return message.build();
}
public static MessageEmbed buildAddingTrackMessage(AudioTrack track, SourceFinder.AudioSourceType type) {
EmbedBuilder message = new EmbedBuilder();
String url = track.getIdentifier();
String thumbnail = null;
if (type == SourceFinder.AudioSourceType.Youtube || type == SourceFinder.AudioSourceType.YTSearch || type == SourceFinder.AudioSourceType.YTMusic) {
url = String.format("https://youtu.be/%s", track.getIdentifier());
thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", track.getIdentifier());
}
if (type == SourceFinder.AudioSourceType.Udio) {
thumbnail = track.getInfo().artworkUrl;
}
if (thumbnail != null) {
message.setThumbnail(thumbnail);
}
message.setTitle("Adding track to queue..");
message.setColor(Color.BLUE);
message.addField("Track title: ", String.format("[**`%s`**](%s)", track.getInfo().title, url), false);
message.addField("Duration: ", DurationFormatUtils.formatDuration(track.getDuration(), "**HH:mm:ss**", true), false);
message.setTimestamp(OffsetDateTime.now());
return message.build();
}
public static MessageEmbed buildSearchResultsMessage(List<AudioTrack> tracks) {
EmbedBuilder message = new EmbedBuilder();
message.setTitle("Select the track to add to the queue");
message.setColor(Color.BLUE);
int i = 0;
for (AudioTrack track : tracks) {
message.addField(++i + ": ", String.format("**`%s`**", track.getInfo().title), false);
}
return message.build();
}
public static MessageEmbed buildAddingPlaylistMessage(AudioPlaylist playlist, String identifier, SourceFinder.AudioSourceType type) {
EmbedBuilder message = new EmbedBuilder();
String url = identifier;
String thumbnail = null;
if (type == SourceFinder.AudioSourceType.Youtube || type == SourceFinder.AudioSourceType.YTSearch) {
url = String.format("https://youtu.be/%s", playlist.getTracks().get(0).getIdentifier());
thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", playlist.getTracks().get(0).getIdentifier());
}
if (type == SourceFinder.AudioSourceType.YTMusic) {
thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", playlist.getTracks().get(0).getIdentifier());
}
if (thumbnail != null) {
message.setThumbnail(thumbnail);
}
message.setTitle("Adding playlist to queue..");
message.setColor(Color.BLUE);
message.addField("Playlist: ", String.format("[**`%s`**](%s)", playlist.getName(), url), false);
message.addField("Number of tracks: ", String.valueOf(playlist.getTracks().size()), false);
message.setTimestamp(OffsetDateTime.now());
return message.build();
}
public static MessageEmbed buildViewQueueMessage(MusicManager manager, boolean currentlyPlaying) {
ArrayList<AudioTrack> tracks = manager.getScheduler().getQueueManager().getAllQueuedTrack();
AudioTrack current = manager.getCurrentlyPlaying();
int numberofTracksShown = Math.min(tracks.size(), 10);
EmbedBuilder message = new EmbedBuilder();
message.setTitle(String.format("Current queue (showing %d out of %d items):", numberofTracksShown, tracks.size()));
message.setColor(Color.BLUE);
int index = 0;
for (AudioTrack track : tracks) {
AudioTrackInfo info = track.getInfo();
if (current != null && current.getInfo().title.equals(info.title) && currentlyPlaying) {
message.addField("Track #" + ++index + " (Currently playing)", info.title, false);
} else {
message.addField("Track #" + ++index, info.title, false);
}
if (index == 10) break;
}
message.setTimestamp(OffsetDateTime.now());
return message.build();
}
public static MessageEmbed buildUserTracksMessage(MusicManager manager, Member member) {
List<QueueManager.TrackInfo> tracks = manager.getScheduler()
.getQueueManager()
.getQueueWithFilter(trackInfo -> member.equals(trackInfo.getRequester()));
AudioTrack current = manager.getCurrentlyPlaying();
int numberofTracksShown = Math.min(tracks.size(), 10);
EmbedBuilder message = new EmbedBuilder();
message.setTitle(String.format("Tracks added by %s (showing %d out of %d items):", member.getAsMention(), numberofTracksShown, tracks.size()));
message.setColor(Color.BLUE);
int index = 0;
for (QueueManager.TrackInfo track : tracks) {
AudioTrackInfo info = track.getTrack().getInfo();
if (current != null && current.getInfo().title.equals(info.title)) {
message.addField("Track #" + ++index + " (Currently playing)", info.title, false);
} else {
message.addField("Track #" + ++index, info.title, false);
}
if (index == 10) break;
}
message.setTimestamp(OffsetDateTime.now());
return message.build();
}
public static ArrayList<MessageEmbed> buildHelpMessage(Member member) {
HashMap<CommandType, EmbedBuilder> help = new HashMap<>();
for (AbstractCommand command : CommandManager.commands.values()) {
if (command.getType() == null)
continue;
String description = "No description yet";
if (command.isEnabled() && command.getType() != CommandType.UNLISTED) {
if (command.getType() == CommandType.ADMINISTRATION && !UserFunction.isAdmin(member))
continue;
if (command.getDescription() != null)
description = command.getDescription();
EmbedBuilder message;
if (help.containsKey(command.getType())) {
message = help.get(command.getType());
} else {
help.put(command.getType(), message = new EmbedBuilder());
}
if (message.isEmpty()) {
message.setTitle(command.getType().getDescription() + " commands for NetNeutrality");
message.setColor(Color.BLUE);
}
message.addField(String.format("%s", command.getCommandName()), description, false);
}
}
ArrayList<MessageEmbed> messages = new ArrayList<>();
help.values().forEach(embedBuilder -> messages.add(embedBuilder.build()));
return messages;
}
public static MessageEmbed buildSentryErrorMessage(String error) {
EmbedBuilder message = new EmbedBuilder();
message.setColor(Color.RED);
message.setTitle(":warning: Error during execution :warning:");
message.addField("Error message:", error, false);
message.addField("Error ID:", Sentry.getLastEventId().toString(), false);
message.setTimestamp(OffsetDateTime.now());
return message.build();
}
public static MessageEmbed buildDebugMessage(CommandContext context, CommandException error) {
EmbedBuilder message = new EmbedBuilder();
message.setColor(Color.RED);
message.setTitle(":warning: Exception in command execution");
message.addField("Caused by command:", context.getCommand().getCommandName(), false);
message.addField("Exception:", error.getMessage(), false);
if (error.getCause() != null) {
String cause;
if (error.getCause().getMessage() != null && !error.getCause().getMessage().isEmpty()) {
cause = error.getCause().getMessage();
} else {
cause = error.getCause().getClass().getName();
}
message.addField("Caused by:", cause, false);
}
message.setFooter("Requested by " + context.getGuildUser().getEffectiveName(), context.getGuildUser().getAvatarUrl());
message.setTimestamp(OffsetDateTime.now());
return message.build();
}
public static MessageEmbed buildEditMessage(Member member, Message message, String originalMessage) {
EmbedBuilder editEmbed = new EmbedBuilder();
editEmbed.setColor(Color.RED);
editEmbed.setDescription(":pencil2: **Message edited by " + member.getEffectiveName() + "**");
try {
editEmbed.addField("Original message", (originalMessage == null ? "Not available" : originalMessage), false);
} catch (IllegalArgumentException ex) {
editEmbed.addField("Original message:", "Message too long.", false);
}
try {
editEmbed.addField("Edited to:", message.getContentDisplay(), false);
} catch (IllegalArgumentException ex) {
editEmbed.addField("Edited to:", "Message too long.", false);
}
editEmbed.setFooter("ID : " + message.getId(), null);
editEmbed.setTimestamp(message.getTimeCreated());
return editEmbed.build();
}
public static void testMessageAgainstBlacklist(Message message, MessageChannel channel, Set<String> cache) {
boolean found = false;
for (String word : cache) {
if (message.getContentRaw().toLowerCase().contains(word)) {
found = true;
break;
}
}
if (found) {
message.delete().queue();
channel.sendMessage("Not in my christian server").queue();
}
}
}
@@ -0,0 +1,25 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
public class MiscUtils {
public static boolean isValidUrl(String url) {
try {
URL object = new URL(url);
object.toURI();
return true;
} catch (MalformedURLException | URISyntaxException e) {
return false;
}
}
}
@@ -0,0 +1,49 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.core.context.CommandContext;
import me.bobert.netneutrality.exceptions.CommandExecutionException;
import me.bobert.netneutrality.music.MusicManager;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
public class MusicUtils {
public static boolean checkIfConnected(MusicManager manager, CommandContext context) {
if (context.getGuildUser().getVoiceState() == null) {
throw new CommandExecutionException("Voice state is null, this should not happen", context, null);
}
if (!context.getGuildUser().getVoiceState().inAudioChannel()) {
context.reply("You are not in a voice channel");
return false;
}
if (!manager.isConnected()) {
context.reply("The bot isn't connected to a voice channel");
return false;
}
VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel();
if (manager.isNotConnectedTo(vChannel)) {
context.reply("The bot isn't in your voice channel");
return false;
}
return true;
}
public static boolean isUserDj(Member member) {
MusicManager manager = MusicManager.get(member.getGuild());
if (manager.isCurrentlyPlaying()) {
return manager.getRequesterOfCurrentTrack().equals(member);
}
return false;
}
}
@@ -0,0 +1,106 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import me.bobert.netneutrality.http.WebModule;
import org.apache.logging.log4j.LogManager;
import org.json.JSONArray;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
public class Settings {
private static Settings INSTANCE;
private final File propFile = new File("bot.properties");
private final Properties prop = new Properties();
public final String DATETIMEFORMAT = "yyyy-MM-dd HH:mm:ss";
private Settings() {
checkAndLoadPropertiesFile();
loadEnvironmentVariables();
}
private void checkAndLoadPropertiesFile() {
try (FileInputStream fis = new FileInputStream(propFile)) {
prop.load(fis);
} catch (IOException e) {
LogManager.getLogger(Settings.class).info("Can't load settings from bot.properties, bot is most likely running in docker. Using environment variables instead.");
}
}
private void loadEnvironmentVariables() {
Map<String, String> envs = System.getenv();
for (Map.Entry<String, String> env : envs.entrySet()) {
prop.setProperty(env.getKey(), env.getValue());
}
}
public static Settings getInstance() {
if (INSTANCE == null) {
INSTANCE = new Settings();
}
return INSTANCE;
}
public String get(String key) {
String property = prop.getProperty(key);
if (property == null) {
LogManager.getLogger(Settings.class).error("Can't find property with key {}", key);
}
return property;
}
public String get(String key, String def) {
String property = get(key);
if (property == null || property.isEmpty())
return def;
return property;
}
public String getMavenVersion() {
String version = null;
try {
Properties p = new Properties();
InputStream is = getClass().getResourceAsStream("/app.properties");
if (is != null) {
p.load(is);
version = p.getProperty("app.version", "");
}
} catch (Exception ignored) {}
if (version == null) {
version = "0.0.0";
}
return version;
}
public String getGitVersion() {
String gitlabHost = "https://git.bobert.me";
String gitlabProjectId = "22";
String latestReleaseUrl = String.format("%s/api/v4/projects/%s/releases/", gitlabHost, gitlabProjectId);
JSONArray json = new WebModule(latestReleaseUrl).request().getAsArray();
if (json.isNull(0)) {
return "0.0.0";
}
return json.getJSONObject(0).getString("tag_name");
}
}
@@ -0,0 +1,25 @@
package me.bobert.netneutrality.utils;
/*
* Copyright (c) anthorob
*
* https://discord.gg/qUa6zNFZsd
*/
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
public class UserFunction {
public static boolean isAdmin(Member user) {
boolean isAdmin = false;
for(Role role : user.getRoles()) {
if (role.getPermissions().contains(Permission.MANAGE_SERVER) || role.getPermissions().contains(Permission.ADMINISTRATOR))
isAdmin = true;
}
return isAdmin;
}
}