diff --git a/src/main/java/online/monkegame/monkemodmail/DiscordHandler.java b/src/main/java/online/monkegame/monkemodmail/DiscordHandler.java new file mode 100644 index 0000000..31c8fb0 --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/DiscordHandler.java @@ -0,0 +1,107 @@ +package online.monkegame.monkemodmail; + +import online.monkegame.monkemodmail.utils.ColorGenerator; +import online.monkegame.monkemodmail.utils.Database; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.Objects; + +public class DiscordHandler extends ListenerAdapter { + + public FileConfiguration c; + public Database db; + public Main p; + public ColorGenerator cg; + public DiscordHandler(FileConfiguration config, Main plugin) { + this.p = plugin; + this.c = config; + this.db = new Database(p); + this.cg = new ColorGenerator(); + } + + Guild g; + + @Override + public void onMessageReceived(MessageReceivedEvent event) { + JDA j = event.getJDA(); + MessageChannel ch = event.getChannel(); + User a = event.getAuthor(); + String m = event.getMessage().getContentRaw(); + String p = c.getString("discord.bot-prefix"); + Member am = event.getMember(); + g = event.getGuild(); + Role ro = g.getRoles().stream().filter(r -> r.getName().equalsIgnoreCase(c.getString("access-role"))).findFirst().orElse(null); + if (m.startsWith(p) && !a.isBot() && Objects.requireNonNull(am).getRoles().contains(ro)) { + String[] co = m.substring(p.length()).split(" "); + switch (co[0]) { + + case "log": + if (co.length>1) { + switch (co[1]) { + case "channel": + try { + String channel = co[2]; + if (!channel.contains("<")) { + c.set("logging-channel", channel); + } else { + channel.replaceAll("<#>", ""); + c.set("logging-channel", channel); + } + } catch (IndexOutOfBoundsException e) { + ch.sendMessage("Please specify a channel ID!").submit(); + break; + } + ch.sendMessage("Channel set successfully! Please restart the server to apply the changes!").submit(); + break; + case "settings": + MessageEmbed lSE = new EmbedBuilder() + .setTitle("Logging Settings") + .addField("Logging channel", "<#" + c.getString("logging-channel").replace("\"", "") + ">", false) + .addField("Settings role", c.getString("access-role"), false) + .setColor(0x08adf4) + .build(); + ch.sendMessageEmbeds(lSE).submit(); + break; + case "role": + try { + if (c.contains("@")) { + ch.sendMessage("Please specify the role's name instead of pinging it!").submit(); + break; + } + c.set("role", co[2]); + } catch (IndexOutOfBoundsException e) { + ch.sendMessage("Please specify a role's name!").submit(); + break; + } + ch.sendMessage("Role set successfully! Please restart the server to apply the changes!").submit(); + break; + default: + ch.sendMessage("Unknown command!").submit(); + break; + } + } else { + ch.sendMessage(p + "log role/channel/settings").submit(); + } + break; + case "help": + MessageEmbed ca = new EmbedBuilder() + .setTitle("Help! What commands are there?") + .addField("log", "-``settings`` -> shows settings\n-``channel `` -> sets the channel where things will be logged\n-``role `` -> sets the role that can edit the settings",false) + .setColor(cg.randomColor()) + .build(); + ch.sendMessageEmbeds(ca).submit(); + default: + ch.sendMessage("Unknown command!").submit(); + break; + } + } else if (m.startsWith(p) && !a.isBot()) { + ch.sendMessage("You don't have permission to run the command!").submit(); + } + } + +} diff --git a/src/main/java/online/monkegame/monkemodmail/Main.java b/src/main/java/online/monkegame/monkemodmail/Main.java new file mode 100644 index 0000000..470f28f --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/Main.java @@ -0,0 +1,68 @@ +package online.monkegame.monkemodmail; + +import online.monkegame.monkemodmail.commands.ModmailCommand; +import online.monkegame.monkemodmail.commands.ReportCommand; +import online.monkegame.monkemodmail.utils.Database; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.requests.GatewayIntent; +import net.dv8tion.jda.api.utils.MemberCachePolicy; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.security.auth.login.LoginException; +import java.io.IOException; + +public class Main extends JavaPlugin { + + public FileConfiguration configuration; + public JDA j; + public Database db; + public Main main = this; + public Main() { + this.db = new Database(this); + } + + @Override + public void onEnable() { + + saveDefaultConfig(); + configuration = getConfig(); + getLogger().info("Config loaded!"); + getLogger().info(" _ __ __ _ __ __ _ _"); + getLogger().info(" _ __ ___ _ _ | |_____| \\/ |___ __| | \\/ |__ _(_) |"); + getLogger().info("| ' \\/ _ \\ ' \\| / / -_) |\\/| / _ \\/ _` | |\\/| / _` | | |"); + getLogger().info("|_|_|_\\___/_||_|_\\_\\___|_| |_\\___/\\__,_|_| |_\\__,_|_|_|"); + getLogger().info("---------------------------------------------------------"); + try { + j = JDABuilder.createLight(getConfig().getString("discord.bot-token"), GatewayIntent.GUILD_MESSAGES) + .addEventListeners(new DiscordHandler(configuration, main)) + .setMemberCachePolicy(MemberCachePolicy.ONLINE) + .build() + .awaitReady(); + + getLogger().info("Connected to Discord!"); + + } catch (InterruptedException | LoginException e) { + + getLogger().severe(e.getMessage()); + getLogger().severe("Failed to start! Please configure the plugin!"); + + } + try { + db.createDB(this.getLogger()); + } catch (IOException e) { + e.printStackTrace(); + } + getCommand("modmail").setExecutor(new ModmailCommand(configuration, j, this.getLogger())); + getCommand("report").setExecutor(new ReportCommand(configuration, j, main)); + } + @Override + public void onDisable() { + if (j!=null) { + j.shutdown(); + } + saveConfig(); + } + +} diff --git a/src/main/java/online/monkegame/monkemodmail/commands/ModmailCommand.java b/src/main/java/online/monkegame/monkemodmail/commands/ModmailCommand.java new file mode 100644 index 0000000..d516079 --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/commands/ModmailCommand.java @@ -0,0 +1,64 @@ +package online.monkegame.monkemodmail.commands; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.MessageEmbed; +import online.monkegame.monkemodmail.utils.ColorGenerator; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +import java.time.Instant; +import java.util.*; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class ModmailCommand implements CommandExecutor { + + ColorGenerator g; + JDA j; + FileConfiguration conf; + Logger l; + + public ModmailCommand(FileConfiguration c, JDA jda, Logger logger) { + this.conf = c; + this.j = jda; + this.l = logger; + this.g = new ColorGenerator(); + } + + + Map cooldowns = new HashMap<>(); + + @Override + public boolean onCommand(CommandSender s, Command n, String y, String[] args) { + int cooldownTime = conf.getInt("command-cooldowns.modmail-cooldown"); + if (!(s instanceof Player)) { + return false; + } else { + List l = new ArrayList<>(Arrays.asList(args)); + MessageChannel ch = j.getTextChannelById(conf.getString("discord.logging-channel")); + short secondsLeft; + if (cooldowns.containsKey(s.getName())) { + secondsLeft = (short) (((cooldowns.get(s.getName()) / 1000) + cooldownTime) - (Instant.now().toEpochMilli() / 1000)); + if (secondsLeft>0) { + s.sendMessage("Please wait "+secondsLeft+" more seconds!"); + return true; + } else if (secondsLeft <= 0) { + cooldowns.remove(s.getName()); + } + } + MessageEmbed hlepPls = new EmbedBuilder() + .setTitle("``" + s.getName() + "`` has requested your help!") + .addField("This is what's wrong: ", "``"+l.stream().map(Object::toString).collect(Collectors.joining(" "))+"``" , false) + .setColor(g.randomColor()) + .build(); + cooldowns.put(s.getName(), (short) (Instant.now().toEpochMilli()/1000)); + ch.sendMessageEmbeds(hlepPls).submit(); + return true; + } + } +} diff --git a/src/main/java/online/monkegame/monkemodmail/commands/ReportCommand.java b/src/main/java/online/monkegame/monkemodmail/commands/ReportCommand.java new file mode 100644 index 0000000..608c09f --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/commands/ReportCommand.java @@ -0,0 +1,113 @@ +package online.monkegame.monkemodmail.commands; + +import online.monkegame.monkemodmail.utils.ColorGenerator; +import online.monkegame.monkemodmail.utils.Database; +import online.monkegame.monkemodmail.utils.PlayerboundMessages; +import online.monkegame.monkemodmail.utils.SuspicionLevel; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.MessageChannel; +import net.dv8tion.jda.api.entities.MessageEmbed; +import online.monkegame.monkemodmail.Main; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +import java.sql.SQLException; +import java.time.Instant; +import java.util.*; + +public class ReportCommand implements CommandExecutor { + + public FileConfiguration co; + JDA j; + public Database db; + public ColorGenerator cg; + public SuspicionLevel sl; + public PlayerboundMessages pbm; + public Main plugin; + public ReportCommand(FileConfiguration config, JDA jda, Main plugin) { + this.plugin = plugin; + this.sl = new SuspicionLevel(plugin); + this.co = config; + this.j = jda; + this.db = new Database(plugin); + this.cg = new ColorGenerator(); + this.pbm = new PlayerboundMessages(plugin); + } + + String uAsString; + UUID u; + Short reason; + Player p; + Map cooldowns = new HashMap<>(); + @Override + public boolean onCommand(CommandSender s, Command c, String a, String[] args) { + int cooldownTime = co.getInt("command-cooldowns.report-cooldown"); + List l = getReasons(co); + if (s instanceof Player && args != null && !s.getName().equals(args[0])) { + MessageChannel ch = j.getTextChannelById(co.getString("discord.logging-channel")); + long secondsLeft = 0; + if (cooldowns.containsKey(s.getName())) { + secondsLeft = ((cooldowns.get(s.getName()) / 1000) + cooldownTime) - (Instant.now().toEpochMilli() / 1000); + if (secondsLeft > 0) { + s.sendMessage("Please wait "+secondsLeft+" more seconds!"); + return true; + } else if (secondsLeft <= 0) { + cooldowns.remove(s.getName()); + } + } + + if (args.length < 2 || !args[1].matches("[0-9]")) { + s.sendMessage("Please specify a report type!"); + pbm.sendReasons(s, l); + return false; + } else { + cooldowns.put(s.getName(), Instant.now().toEpochMilli()); + u = Bukkit.getPlayerUniqueId(args[0]); + uAsString = u.toString(); + p = Bukkit.getPlayer(u); + reason = Short.parseShort(args[1]); + if ((reason > l.size() || reason < 1) && args[1].matches("[0-9]")) { + s.sendMessage("Bad ID! Here are the report type IDs you can use:"); + cooldowns.remove(s.getName()); + pbm.sendReasons(s, l); + return true; + } + + try { + db.insertReport(uAsString, reason); + } catch (SQLException e) { + e.printStackTrace(); + } + + MessageEmbed e = new EmbedBuilder() + .setTitle("Report " + db.countReports()) + .setDescription("``" + args[0] + "``" + " has been reported by ``" + s.getName() + "``") + .addField("Player has been reported "+db.countReportsPerPlayer(uAsString)+" time(s)", "", false) + .addField("Reported for", "``" + l.get(reason - 1) + "``", false) + .addField("", "Distrust: " + sl.checkSuspicion(uAsString, p), false) + .setColor(cg.randomColor()) + .build(); + + ch.sendMessageEmbeds(e).submit(); + s.sendMessage("Player reported successfully."); + return true; + } + } + return false; + } + + + public List getReasons(FileConfiguration conf) { + + List l = new ArrayList<>(); + for (Object r : conf.getList("report-reasons")) { + l.add((String) r); + } + return l; + } +} \ No newline at end of file diff --git a/src/main/java/online/monkegame/monkemodmail/utils/ColorGenerator.java b/src/main/java/online/monkegame/monkemodmail/utils/ColorGenerator.java new file mode 100644 index 0000000..10178fd --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/utils/ColorGenerator.java @@ -0,0 +1,23 @@ +package online.monkegame.monkemodmail.utils; + +import net.kyori.adventure.util.HSVLike; + +import java.awt.*; + +public class ColorGenerator { + + //generates a random color + public Color randomColor() { + int r = (int) (Math.random() * 257) - 1; + int g = (int) (Math.random() * 257) - 1; + int b = (int) (Math.random() * 257) - 1; + return new Color(r, g ,b); + } + + public HSVLike randomKyoriColor() { + int r = (int) (Math.random() * 257) - 1; + int g = (int) (Math.random() * 257) - 1; + int b = (int) (Math.random() * 257) - 1; + return HSVLike.of(r, g, b); + } +} \ No newline at end of file diff --git a/src/main/java/online/monkegame/monkemodmail/utils/Database.java b/src/main/java/online/monkegame/monkemodmail/utils/Database.java new file mode 100644 index 0000000..79bdf8f --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/utils/Database.java @@ -0,0 +1,109 @@ +package online.monkegame.monkemodmail.utils; + +import online.monkegame.monkemodmail.Main; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitScheduler; +import org.checkerframework.checker.units.qual.A; + +import java.io.File; +import java.io.IOException; +import java.sql.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +public class Database { + + public String path = System.getProperty("user.dir") + System.getProperty("file.separator") + "plugins" + System.getProperty("file.separator") + "monkeModMail" + System.getProperty("file.separator"); + + String reportsTable = + "CREATE TABLE IF NOT EXISTS reports(" + + "'uuid' TEXT, " + + "'reason' TEXT)"; + private Main plugin; + public Database(Main plugin) { + this.plugin = plugin; + } + + //creates database with corresponding tables + public void createDB(Logger l) throws IOException { + Thread thread = new Thread(); + thread.start(); + File db = new File(path + "files.db"); + boolean dbY = db.createNewFile(); + if (dbY) { + try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + path + "files.db"); + Statement stmt = conn.createStatement()) { + stmt.execute(reportsTable); + } catch (SQLException e) { + e.printStackTrace(); + } + l.info("Database created!"); + } else { + try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + path + "files.db"); + Statement stmt = conn.createStatement()) { + stmt.execute(reportsTable); + } catch (SQLException e) { + e.printStackTrace(); + } + l.info("Database already exists, using existing database!"); + } + } + + //inserts the report into the db + public void insertReport(String uuid, Short reason) throws SQLException { + BukkitScheduler b = Bukkit.getScheduler(); + b.runTaskLaterAsynchronously(plugin, ()-> { + try (Connection c = DriverManager.getConnection("jdbc:sqlite:" + path + "files.db"); + Statement p = c.createStatement()) { + p.execute("INSERT INTO reports VALUES('" + uuid + "','" + reason + "')"); + } catch (SQLException e) { + e.printStackTrace(); + } + }, 10L); + } + + //gets the amount of reports + public int countReports() { + AtomicInteger amount = new AtomicInteger(); + BukkitScheduler b = Bukkit.getScheduler(); + b.runTaskAsynchronously(plugin, () -> { + + String amountOfReports = + "SELECT COUNT(reason) AS reports " + + "FROM reports;"; + try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + path + "files.db"); + Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery(amountOfReports); + while (rs.next()) { + amount.set(rs.getInt("reports")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + }); + return amount.get() +1; + } + + public int countReportsPerPlayer(String uuid) { + AtomicInteger amount = new AtomicInteger(); + BukkitScheduler b = Bukkit.getScheduler(); + b.runTaskAsynchronously(plugin, () -> { + + String amountOfReports = + "SELECT COUNT(reason) AS reports " + + "FROM reports " + + "WHERE uuid='"+uuid+"';"; + try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + path + "files.db"); + Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery(amountOfReports); + while (rs.next()) { + amount.set(rs.getInt("reports")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + }); + return amount.get() +1; + } + +} diff --git a/src/main/java/online/monkegame/monkemodmail/utils/PlayerboundMessages.java b/src/main/java/online/monkegame/monkemodmail/utils/PlayerboundMessages.java new file mode 100644 index 0000000..e585e21 --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/utils/PlayerboundMessages.java @@ -0,0 +1,41 @@ +package online.monkegame.monkemodmail.utils; + + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import online.monkegame.monkemodmail.Main; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.scheduler.BukkitScheduler; + +import java.util.List; + +public class PlayerboundMessages { + + private final ColorGenerator CG; + public Main m; + + public PlayerboundMessages(Main plugin) { + this.m = plugin; + this.CG = new ColorGenerator(); + } + + public void sendReasons(CommandSender s, List l) { + + BukkitScheduler b = Bukkit.getScheduler(); + b.runTaskAsynchronously(m, ()-> { + TextComponent c = Component.text() + .content("Valid reasons:") + .color(NamedTextColor.RED) + .decoration(TextDecoration.ITALIC, false).build(); + s.sendMessage(c); + for (String a : l) { + s.sendMessage(Component.text((l.indexOf(a) + 1) + ": " + a).decoration(TextDecoration.ITALIC, false).color(NamedTextColor.YELLOW)); + } + }); + } + + +} diff --git a/src/main/java/online/monkegame/monkemodmail/utils/SuspicionLevel.java b/src/main/java/online/monkegame/monkemodmail/utils/SuspicionLevel.java new file mode 100644 index 0000000..db28ec3 --- /dev/null +++ b/src/main/java/online/monkegame/monkemodmail/utils/SuspicionLevel.java @@ -0,0 +1,63 @@ +package online.monkegame.monkemodmail.utils; + +import online.monkegame.monkemodmail.Main; +import org.bukkit.Statistic; +import org.bukkit.entity.Player; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class SuspicionLevel { + + public Database db; + public Main plugin; + public SuspicionLevel(Main plugin) { + this.plugin = plugin; + this.db = new Database(plugin); + } + + //similar reports + + //amount of reports ++ + //playtime ~ + Float output; + public String checkSuspicion(String uuid, Player p) { + + List a = new ArrayList<>(); + String reportType = + "SELECT reason " + + "FROM reports " + + "WHERE uuid='" + uuid + "'"; + try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + db.path + "files.db"); + Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery(reportType); + while (rs.next()) { + a.add(rs.getInt("reason")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + Object[] similarReports = a.toArray(); + + int values = 0; + int size = similarReports.length; + for (Object o : similarReports) { + values = values + (Integer.parseInt(o.toString())); + } + + float averageReport = (values * 0.7f) / size; + int reports = db.countReports(); + int playtime = p.getPlayer().getStatistic(Statistic.PLAY_ONE_MINUTE) / 7200; + output = (Float)((float)(averageReport * (reports * 0.37f) - Math.pow(0.01 * playtime, 1.12)+1)); + + if (output <= 0 || output.isNaN()) { + return "Distrust too low to make a judgement!"; + } else if (output >10) { + return String.format("%.2f", output) + "\n**This player has very high distrust!**"; + } else { + return String.format("%.2f", output); + } + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a993d2d..2a7e059 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,14 +2,22 @@ # Below you can configure the plugin. # If you need any help, head over to https://monkegame.online/contact # If something's really wrong, create an issue on GitHub (https://github.com/monkegame/monkemodmail) - -discord-bot-token: - -# Set these through commands, or input these values manually. These are related to Discord. -logging-channel: -prefix: "!" -access-role: - -# The height of the suspicion level before someone gets automatically banned. Keep this above 20, just to be safe. -suspicion-threshold: 10 - +# Cooldowns are in seconds. +# But how do the report reasons work???? +# Put what you think are the worst offences lower on the list. +# The way the system works, is that it weighs it in more, the farther down on the list. +# You can even add your own! +discord: + bot-token: "" + logging-channel: "" + bot-prefix: "!" + access-role: "" +command-cooldowns: + report-cooldown: 30 + modmail-cooldown: 30 +report-reasons: + - "Griefing" + - "Scam" + - "Rude to me or others" + - "Hacking/abusing exploits" + - "" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 81b53d2..d8cdf79 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,9 +2,13 @@ name: 'monkeModMail' main: 'online.monkegame.monkemodmail.Main' version: '1.0' api-version: '1.17' -author: 'monkegame' +author: 'Mrs_Herobrine_' commands: report: description: 'reports a player' usage: '/report ' permission: 'monkemodmail.report' + modmail: + description: 'mails the mods' + usage: '/modmail ' + permission: 'monkemodmail.modmail'