almost done

This commit is contained in:
Telesphoreo 2024-05-26 15:56:07 -05:00
parent 6400fdbf5a
commit 0c6de52df8
17 changed files with 387 additions and 88 deletions

View File

@ -30,6 +30,7 @@ dependencies {
library(libs.hikari)
compileOnly(libs.paperApi)
implementation(libs.bundles.bstats) { isTransitive = false }
library(libs.mariadb.java.client)
annotationProcessor(libs.lombok)
}
@ -52,6 +53,7 @@ paper {
name = "Medina"
version = project.version.toString()
main = "dev.plex.medina.Medina"
loader = "dev.plex.medina.MedinaLibraryManager"
apiVersion = "1.20"
foliaSupported = true
authors = listOf("Telesphoreo")

View File

@ -3,13 +3,15 @@ paper = "1.20.4-R0.1-SNAPSHOT"
bstats = "3.0.2"
lombok = "1.18.32"
hikari = "5.1.0"
mariadb-java-client = "3.4.0"
[libraries]
paperApi = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" }
bstatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" }
bstats-base = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" }
bstats-bukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" }
lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" }
hikari = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikari" }
mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version.ref = "mariadb-java-client"}
[bundles]
bstats = ["bstatsBase", "bstatsBukkit"]
bstats = ["bstats-base", "bstats-bukkit"]

View File

@ -3,6 +3,7 @@ package dev.plex.medina;
import dev.plex.medina.config.Config;
import dev.plex.medina.registration.CommandRegistration;
import dev.plex.medina.storage.SQLConnection;
import dev.plex.medina.storage.SQLReports;
import dev.plex.medina.util.MedinaUtils;
import lombok.Getter;
import org.bstats.bukkit.Metrics;
@ -20,6 +21,9 @@ public class Medina extends JavaPlugin
@Getter
private SQLConnection sqlConnection;
@Getter
private SQLReports sqlReports;
@Override
public void onLoad()
{
@ -40,6 +44,8 @@ public class Medina extends JavaPlugin
sqlConnection = new SQLConnection();
MedinaUtils.testConnection();
sqlReports = new SQLReports();
new CommandRegistration();
}
}

View File

@ -0,0 +1,57 @@
package dev.plex.medina;
import com.google.gson.Gson;
import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
import io.papermc.paper.plugin.loader.PluginLoader;
import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.repository.RemoteRepository;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class MedinaLibraryManager implements PluginLoader
{
@Override
public void classloader(@NotNull PluginClasspathBuilder classpathBuilder)
{
MavenLibraryResolver resolver = new MavenLibraryResolver();
PluginLibraries pluginLibraries = load();
pluginLibraries.asDependencies().forEach(resolver::addDependency);
pluginLibraries.asRepositories().forEach(resolver::addRepository);
classpathBuilder.addLibrary(resolver);
}
public PluginLibraries load()
{
try (var in = getClass().getResourceAsStream("/paper-libraries.json"))
{
return new Gson().fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), PluginLibraries.class);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
private record PluginLibraries(Map<String, String> repositories, List<String> dependencies)
{
public Stream<Dependency> asDependencies()
{
return dependencies.stream()
.map(d -> new Dependency(new DefaultArtifact(d), null));
}
public Stream<RemoteRepository> asRepositories()
{
return repositories.entrySet().stream()
.map(e -> new RemoteRepository.Builder(e.getKey(), "default", e.getValue()).build());
}
}
}

View File

@ -2,10 +2,12 @@ package dev.plex.medina.command;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import com.google.common.collect.Lists;
import dev.plex.medina.Medina;
import dev.plex.medina.command.annotation.CommandParameters;
import dev.plex.medina.command.exception.PlayerNotFoundException;
import dev.plex.medina.command.source.RequiredCommandSource;
import dev.plex.medina.util.MedinaUtils;
import net.kyori.adventure.audience.Audience;
@ -148,7 +150,7 @@ public abstract class MedinaCommand extends Command implements PluginIdentifiabl
send(sender, component);
}
}
catch (NumberFormatException ex)
catch (PlayerNotFoundException | NumberFormatException ex)
{
send(sender, MedinaUtils.mmDeserialize(ex.getMessage()));
}
@ -224,6 +226,25 @@ public abstract class MedinaCommand extends Command implements PluginIdentifiabl
return !(sender instanceof Player);
}
protected Player getNonNullPlayer(String name)
{
try
{
UUID uuid = UUID.fromString(name);
return Bukkit.getPlayer(uuid);
}
catch (IllegalArgumentException ignored)
{
}
Player player = Bukkit.getPlayer(name);
if (player == null)
{
throw new PlayerNotFoundException();
}
return player;
}
/**
* Converts a message entry from the "messages.yml" to a Component
*
@ -304,6 +325,19 @@ public abstract class MedinaCommand extends Command implements PluginIdentifiabl
return MedinaUtils.mmDeserialize(s);
}
protected Integer parseInt(CommandSender sender, String string)
{
try
{
return Integer.parseInt(string);
}
catch (NumberFormatException ex)
{
sender.sendMessage(mmString("<red>Not a valid number."));
}
return null;
}
public CommandMap getMap()
{
return Medina.getPlugin().getServer().getCommandMap();

View File

@ -0,0 +1,11 @@
package dev.plex.medina.command.exception;
import static dev.plex.medina.util.MedinaUtils.messageString;
public class PlayerNotFoundException extends RuntimeException
{
public PlayerNotFoundException()
{
super(messageString("playerNotFound"));
}
}

View File

@ -1,28 +0,0 @@
package dev.plex.medina.command.impl;
import dev.plex.medina.command.MedinaCommand;
import dev.plex.medina.command.annotation.CommandParameters;
import dev.plex.medina.command.source.RequiredCommandSource;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@CommandParameters(name = "anycommand", usage = "/<command>", description = "Tests the command system with a command", permission = "medina.anycommand", source = RequiredCommandSource.ANY)
public class AnyCommand extends MedinaCommand
{
@Override
protected Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, @NotNull String[] args)
{
return mmString("<green>AnyCommand registered.");
}
@Override
public @NotNull List<String> smartTabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
{
return List.of();
}
}

View File

@ -1,28 +0,0 @@
package dev.plex.medina.command.impl;
import dev.plex.medina.command.MedinaCommand;
import dev.plex.medina.command.annotation.CommandParameters;
import dev.plex.medina.command.source.RequiredCommandSource;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@CommandParameters(name = "consoleonly", usage = "/<command>", description = "Tests the command system with a console only command", permission = "medina.consoleonly", source = RequiredCommandSource.CONSOLE)
public class ConsoleOnlyCommand extends MedinaCommand
{
@Override
protected Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, @NotNull String[] args)
{
return mmString("<green>ConsoleOnlyCommand registered.");
}
@Override
public @NotNull List<String> smartTabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
{
return List.of();
}
}

View File

@ -3,12 +3,18 @@ package dev.plex.medina.command.impl;
import dev.plex.medina.command.MedinaCommand;
import dev.plex.medina.command.annotation.CommandParameters;
import dev.plex.medina.command.source.RequiredCommandSource;
import dev.plex.medina.data.Report;
import dev.plex.medina.util.MedinaUtils;
import net.kyori.adventure.text.Component;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.List;
@CommandParameters(name = "report", usage = "/<command> <player> <message>", description = "Reports a player", permission = "medina.report", source = RequiredCommandSource.IN_GAME)
@ -17,12 +23,36 @@ public class ReportCommand extends MedinaCommand
@Override
protected Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, @NotNull String[] args)
{
return mmString("<green>Report command registered.");
if (args.length < 2)
{
return usage();
}
Player player = getNonNullPlayer(args[0]);
String reason = StringUtils.join(args, " ", 1, args.length);
Report report = new Report(
0,
Bukkit.getPlayer(sender.getName()).getUniqueId(),
sender.getName(),
player.getUniqueId(),
player.getName(),
ZonedDateTime.now(),
reason,
false);
plugin.getSqlReports().addReport(report);
return messageComponent("reportSubmitted", player.getName());
}
@Override
public @NotNull List<String> smartTabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
{
return List.of();
if (args.length == 1 && sender.hasPermission("medina.report"))
{
return MedinaUtils.getPlayerNameList();
}
return Collections.emptyList();
}
}

View File

@ -0,0 +1,110 @@
package dev.plex.medina.command.impl;
import dev.plex.medina.command.MedinaCommand;
import dev.plex.medina.command.annotation.CommandParameters;
import dev.plex.medina.command.source.RequiredCommandSource;
import dev.plex.medina.data.Report;
import dev.plex.medina.util.MedinaLog;
import dev.plex.medina.util.MedinaUtils;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@CommandParameters(name = "reports", usage = "/<command> <player> <list | details <id> | delete <id>>", description = "View existing reports on a player", permission = "medina.reports", source = RequiredCommandSource.ANY)
public class ReportsCommand extends MedinaCommand
{
@Override
protected Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, @NotNull String[] args)
{
if (args.length == 0)
{
return usage();
}
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayerIfCached(args[0]);
if (offlinePlayer == null || !offlinePlayer.hasPlayedBefore())
{
return messageComponent("playerNotFound");
}
switch (args[1].toLowerCase())
{
case "list":
{
plugin.getSqlReports().getReports(offlinePlayer.getUniqueId()).whenComplete((reports, ex) ->
{
// we don't want to include deleted reports in the logic
long count = reports.stream()
.filter(report -> !report.isDeleted())
.count();
if (count <= 0)
{
send(sender, messageComponent("noReports", offlinePlayer.getName()));
return;
}
readReports(sender, offlinePlayer, reports);
});
return null;
}
case "delete":
{
int id = parseInt(sender, args[2]);
plugin.getSqlReports().getReports(id).whenComplete(((report, ex) ->
{
if (report.isDeleted())
{
send(sender, messageComponent("reportDoesntExist"));
return;
}
else
{
plugin.getSqlReports().deleteReport(id, offlinePlayer.getUniqueId()).whenComplete((report1, ex1) ->
{
send(sender, messageComponent("deletedReport", id));
});
}
}));
return null;
}
}
return null;
}
private void readReports(@NotNull CommandSender sender, OfflinePlayer player, List<Report> reports)
{
AtomicReference<Component> reportList = new AtomicReference<>(messageComponent("reportHeader", player.getName()));
for (Report report : reports)
{
Component reportLine = messageComponent("reportPrefix", report.getReportId(), player.getName(), MedinaUtils.useTimezone(report.getTimestamp()));
reportLine = reportLine.append(messageComponent("reportLine", report.getReason()));
reportList.set(reportList.get().append(Component.newline()));
reportList.set(reportList.get().append(reportLine));
}
send(sender, reportList.get());
}
@Override
public @NotNull List<String> smartTabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException
{
if (args.length == 1 && sender.hasPermission("medina.reports"))
{
return MedinaUtils.getPlayerNameList();
}
if (args.length == 2 && sender.hasPermission("medina.reports"))
{
return List.of("list", "delete");
}
return Collections.emptyList();
}
}

View File

@ -3,10 +3,10 @@ package dev.plex.medina.data;
import com.google.gson.GsonBuilder;
import dev.plex.medina.storage.annotation.PrimaryKey;
import dev.plex.medina.storage.annotation.TableName;
import dev.plex.medina.storage.annotation.VarcharLimit;
import dev.plex.medina.util.adapter.ZonedDateTimeAdapter;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.time.ZonedDateTime;
import java.util.UUID;
@ -16,19 +16,24 @@ import java.util.UUID;
public class Report
{
@PrimaryKey
private int reportId; // This will be automatically set from addReport
private final int reportId; // This will be automatically set from addReport
@Getter
private final UUID reporterUUID;
private final String reporterName;
@Getter
private final UUID reportedUUID;
private final String reportedName;
@Getter
private final ZonedDateTime timestamp;
@VarcharLimit(2000)
@Getter
private final String reason;
@Getter
private final boolean deleted;
public String toJSON()

View File

@ -49,7 +49,7 @@ public class SQLConnection implements MedinaBase
try (Connection con = getCon())
{
con.prepareStatement("CREATE TABLE IF NOT EXISTS `reports` (" +
"`reportId` INT NOT NULL AUTOINCREMENT, " +
"`reportId` INT NOT NULL AUTO_INCREMENT, " +
"`reporterUUID` VARCHAR(46) NOT NULL, " +
"`reporterName` VARCHAR(18), " +
"`reportedUUID` VARCHAR(46) NOT NULL, " +

View File

@ -1,15 +1,13 @@
package dev.plex.medina.storage;
import com.google.common.collect.Lists;
import dev.plex.medina.Medina;
import dev.plex.medina.MedinaBase;
import dev.plex.medina.data.Report;
import dev.plex.medina.util.MedinaUtils;
import dev.plex.medina.util.MedinaLog;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@ -20,33 +18,80 @@ import java.util.concurrent.CompletableFuture;
public class SQLReports implements MedinaBase
{
private static final String SELECT = "SELECT * FROM `reports` WHERE reportedUUID=?";
private static final String SELECT_ID = "SELECT * FROM `reports` WHERE reportId=?";
private static final String INSERT = "INSERT INTO `reports` (`reportId`, `reporterUUID`, `reporterName`, `reportedUUID`, `reportedName`, `timestamp`, `reason`, `deleted`) VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
private static final String DELETE = "DELETE FROM `reports` WHERE reportId=? AND reportedUUID=?";
private static final String DELETE = "UPDATE `reports` SET `deleted`=true WHERE reportId=? AND reportedUUID=?";
public CompletableFuture<Report> getReports(int reportedId)
{
MedinaLog.log("getting reports for: " + reportedId);
return CompletableFuture.supplyAsync(() ->
{
Report report;
MedinaLog.log("initialized List<Report>");
try (Connection con = plugin.getSqlConnection().getCon())
{
MedinaLog.log("opened connection");
PreparedStatement statement = con.prepareStatement(SELECT_ID);
MedinaLog.log("prepared select statement");
statement.setInt(1, reportedId);
MedinaLog.log("set reportedUUID to " + reportedId);
ResultSet set = statement.executeQuery();
MedinaLog.log("executing query");
MedinaLog.log("adding a report...");
report = new Report(
reportedId,
UUID.fromString(set.getString("reporterUUID")),
set.getString("reporterName"),
UUID.fromString(set.getString("reportedUUID")),
set.getString("reportedName"),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("timestamp")), ZoneId.systemDefault()),
set.getString("reason"),
set.getBoolean("deleted"));
MedinaLog.log("added a report, id is " + report.getReportId());
return report;
}
catch (Throwable e)
{
e.printStackTrace();
return null;
}
});
}
public CompletableFuture<List<Report>> getReports(UUID reportedUUID)
{
MedinaLog.log("getting reports for: " + reportedUUID);
return CompletableFuture.supplyAsync(() ->
{
List<Report> reports = Lists.newArrayList();
MedinaLog.log("initialized List<Report>");
try (Connection con = plugin.getSqlConnection().getCon())
{
MedinaLog.log("opened connection");
PreparedStatement statement = con.prepareStatement(SELECT);
MedinaLog.log("prepared select statement");
statement.setString(1, reportedUUID.toString());
MedinaLog.log("set reportedUUID to " + reportedUUID);
ResultSet set = statement.executeQuery();
MedinaLog.log("executing query");
while (set.next())
{
MedinaLog.log("adding a report...");
Report report = new Report(
set.getInt("reportId"),
UUID.fromString(set.getString("reporterUUID")),
set.getString("reporterName"),
reportedUUID,
set.getString("reportedName"),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("timestamp")), ZoneId.of(MedinaUtils.TIMEZONE)),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(set.getLong("timestamp")), ZoneId.systemDefault()),
set.getString("reason"),
set.getBoolean("deleted"));
reports.add(report);
MedinaLog.log("added a report, id is " + report.getReportId());
}
}
catch (SQLException e)
catch (Throwable e)
{
e.printStackTrace();
return reports;
@ -57,16 +102,20 @@ public class SQLReports implements MedinaBase
public CompletableFuture<Void> deleteReport(int reportId, UUID reportedUUID)
{
MedinaLog.log("deleting report");
return CompletableFuture.runAsync(() ->
{
MedinaLog.log("running async");
try (Connection con = plugin.getSqlConnection().getCon())
{
MedinaLog.log("established connection");
PreparedStatement statement = con.prepareStatement(DELETE);
statement.setInt(1, reportId);
statement.setString(2, reportedUUID.toString());
statement.execute();
MedinaLog.log("deleted report");
}
catch (SQLException e)
catch (Throwable e)
{
e.printStackTrace();
}
@ -82,16 +131,17 @@ public class SQLReports implements MedinaBase
try (Connection con = plugin.getSqlConnection().getCon())
{
PreparedStatement statement = con.prepareStatement(INSERT);
statement.setString(1, report.getReporterUUID().toString());
statement.setString(2, report.getReporterName());
statement.setString(3, report.getReportedUUID().toString());
statement.setString(4, report.getReportedName());
statement.setLong(5, report.getTimestamp().toInstant().toEpochMilli());
statement.setString(6, report.getReason());
statement.setBoolean(7, report.isDeleted());
statement.setInt(1, reports.size() + 1);
statement.setString(2, report.getReporterUUID().toString());
statement.setString(3, report.getReporterName());
statement.setString(4, report.getReportedUUID().toString());
statement.setString(5, report.getReportedName());
statement.setLong(6, report.getTimestamp().toInstant().toEpochMilli());
statement.setString(7, report.getReason());
statement.setBoolean(8, report.isDeleted());
statement.execute();
}
catch (SQLException e)
catch (Throwable e)
{
e.printStackTrace();
}

View File

@ -0,0 +1,13 @@
package dev.plex.medina.storage.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface VarcharLimit
{
int value();
}

View File

@ -1,19 +1,26 @@
package dev.plex.medina.util;
import dev.plex.medina.Medina;
import dev.plex.medina.MedinaBase;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.time.format.DateTimeFormatter;
public class MedinaUtils implements MedinaBase
{
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
public static String TIMEZONE = plugin.config.getString("timezone");
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("MM/dd/yyyy 'at' hh:mm:ss a z");
public static Component mmDeserialize(String input)
{
@ -60,6 +67,16 @@ public class MedinaUtils implements MedinaBase
return f;
}
public static List<String> getPlayerNameList()
{
return Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList());
}
public static String useTimezone(ZonedDateTime date)
{
return DATE_FORMAT.withZone(ZoneId.systemDefault()).format(date);
}
public static void testConnection()
{
MedinaLog.log("Attempting to connect to DB: {0}", plugin.config.getString("database.name"));

View File

@ -6,7 +6,4 @@ database:
hostname: 127.0.0.1
port: 3306
username: minecraft
password: medina
# The timezone the reports will show up as
timezone: Etc/UTC
password: medina

View File

@ -16,4 +16,25 @@ specifyPlayer: "<red>You must specify a player!"
noPermissionNode: "<red>You must have the permission: {0} <red>to use this command!"
noPermissionInGame: "<red>You must be in console to use this command!"
noPermissionConsole: "<red>You must be in-game to use this command!"
consoleOnly: "<red>This command can only be executed by the console."
consoleOnly: "<red>This command can only be executed by the console."
# 0 - the reported player's username
reportSubmitted: "<green>Your report against {0} was submitted."
noReports: "<red>This player has no existing reports."
# 0 - Player who is having their reports fetched
reportHeader: "<gold>Player reports for: <yellow>{0}"
# 0 - Report ID
# 1 - Author of the note
# 2 - Timestamp
reportPrefix: "<gold><!italic>{0} - Written by: {1} on {2}"
# 0 - The content of the report
reportLine: "<newline><yellow># {0}"
# 0 - the Report ID
deletedReport: "<green>Report ID: {0} deleted"
reportDoesntExist: "<red>There is no report with this ID belonging to that player."