Compare commits
9 Commits
7d3363812f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4797f0dc87 | |||
| ab2c9bf1d6 | |||
| 7aec608825 | |||
| e980768210 | |||
| 5064ac5903 | |||
| 55881630bc | |||
|
|
4cd24ad467 | ||
| db3649a2f5 | |||
| fec5f78cbc |
16
.gitea/workflows/build.yml
Normal file
16
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Build
|
||||||
|
run-name: ${{ gitea.ref }} - ${{ gitea.sha }}
|
||||||
|
on: [push, workflow_dispatch]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Explore-Gitea-Actions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '25'
|
||||||
|
- run: ./gradlew assemble
|
||||||
|
- run: ./gradlew test
|
||||||
|
- run: ./gradlew jar
|
||||||
19
.gitea/workflows/demo.yml
Normal file
19
.gitea/workflows/demo.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Gitea Actions Demo
|
||||||
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
||||||
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Explore-Gitea-Actions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||||
|
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
|
- name: List files in the repository
|
||||||
|
run: |
|
||||||
|
ls ${{ gitea.workspace }}
|
||||||
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
package net.ardakaz.griefalert;
|
package net.ardakaz.griefalert;
|
||||||
|
|
||||||
|
import net.ardakaz.griefalert.database.DatabaseService;
|
||||||
|
import net.ardakaz.griefalert.database.Migrations;
|
||||||
|
import net.ardakaz.griefalert.database.PlayerRepository;
|
||||||
|
import net.ardakaz.griefalert.listener.PlayerListener;
|
||||||
import net.coreprotect.CoreProtect;
|
import net.coreprotect.CoreProtect;
|
||||||
import net.coreprotect.CoreProtectAPI;
|
import net.coreprotect.CoreProtectAPI;
|
||||||
import net.coreprotect.CoreProtectAPI.ParseResult;
|
import net.coreprotect.CoreProtectAPI.ParseResult;
|
||||||
@@ -28,6 +32,7 @@ import org.bukkit.inventory.EntityEquipment;
|
|||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
@@ -35,13 +40,16 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
public class GriefAlert extends JavaPlugin implements Listener {
|
public class GriefAlert extends JavaPlugin implements Listener {
|
||||||
|
|
||||||
private static CoreProtectAPI coreProtectAPI;
|
private static CoreProtectAPI coreProtectAPI;
|
||||||
private final String DB_FILE = "ignored_locations.db";
|
|
||||||
private Integer identicalAlerts = 1;
|
private Integer identicalAlerts = 1;
|
||||||
private String lastAlert;
|
private String lastAlert;
|
||||||
private Set<Material> EXCLUDED_BLOCKS;
|
private Set<Material> EXCLUDED_BLOCKS;
|
||||||
@@ -49,6 +57,12 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
private String MAP_LINK;
|
private String MAP_LINK;
|
||||||
private Boolean ALLOW_STEALING;
|
private Boolean ALLOW_STEALING;
|
||||||
private Connection connection;
|
private Connection connection;
|
||||||
|
private DatabaseService database;
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
/*
|
||||||
|
UTILITY FUNCTIONS
|
||||||
|
*/
|
||||||
|
|
||||||
// Block inspector: only the most recent placement counts for ownership.
|
// Block inspector: only the most recent placement counts for ownership.
|
||||||
private static String inspectBlock(Block block, Player player) {
|
private static String inspectBlock(Block block, Player player) {
|
||||||
@@ -70,7 +84,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we see a break before a placement, stop (block is gone)
|
// If we see a break before a placement, stop (block is gone)
|
||||||
if (parseResult.getActionId() == 0) {
|
if (parseResult.getActionId() == 0 && !parseResult.isRolledBack()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,9 +104,69 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sends the alert (or cancels it)
|
||||||
|
private void alert(String message, String playerName, String mapLink, String target, int x, int y, int z, String world) {
|
||||||
|
// Exclude trusted people
|
||||||
|
Player griefer = Bukkit.getPlayer(playerName);
|
||||||
|
if (griefer.hasPermission("griefalert.exclude") || griefer.hasPermission("griefalert.exclude." + target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Spam limiter
|
||||||
|
String realAlertMessage = message;
|
||||||
|
String[] alert1 = null;
|
||||||
|
if (lastAlert != null) {
|
||||||
|
alert1 = lastAlert.split(" ");
|
||||||
|
}
|
||||||
|
String[] alert2 = message.split(" ");
|
||||||
|
if (alert1 != null) {
|
||||||
|
if (alert1[2].equals(alert2[2]) && alert1[5].equals(alert2[5]) && alert1[1].equals("broke") && alert2[1].equals("broke")) {
|
||||||
|
identicalAlerts += 1;
|
||||||
|
} else if (Arrays.equals(alert1, alert2)) {
|
||||||
|
identicalAlerts += 1;
|
||||||
|
} else {
|
||||||
|
identicalAlerts = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (identicalAlerts == 4) {
|
||||||
|
message = ChatColor.GRAY + "Same behavior continues.";
|
||||||
|
mapLink = null;
|
||||||
|
}
|
||||||
|
if (identicalAlerts > 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use direct location check
|
||||||
|
if (world != null && isLocationIgnored(x, y, z, world)) {
|
||||||
|
return; // Do not alert if location is ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an event for external hooks
|
||||||
|
GriefAlertEvent griefalert_event;
|
||||||
|
if (MAP_LINK != null && !MAP_LINK.isEmpty() && mapLink != null) {
|
||||||
|
griefalert_event = new GriefAlertEvent(message + " (" + mapLink + ")");
|
||||||
|
} else {
|
||||||
|
griefalert_event = new GriefAlertEvent(message);
|
||||||
|
}
|
||||||
|
getServer().getPluginManager().callEvent(griefalert_event);
|
||||||
|
|
||||||
|
// Notify staff ingame
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (player.hasPermission("griefalert.notify")) {
|
||||||
|
player.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAlert = realAlertMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
INITIALIZATION
|
||||||
|
*/
|
||||||
|
|
||||||
// Init GriefAlert
|
// Init GriefAlert
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
logger = getLogger();
|
||||||
|
|
||||||
coreProtectAPI = getCoreProtect();
|
coreProtectAPI = getCoreProtect();
|
||||||
if (coreProtectAPI == null) {
|
if (coreProtectAPI == null) {
|
||||||
@@ -101,7 +175,6 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getServer().getPluginManager().registerEvents(this, this);
|
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
@@ -114,9 +187,18 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
|
|
||||||
setupDatabase();
|
setupDatabase();
|
||||||
|
|
||||||
|
PlayerRepository playerRepository = new PlayerRepository(database, logger);
|
||||||
|
PlayerListener playerListener = new PlayerListener(playerRepository, logger);
|
||||||
|
|
||||||
|
|
||||||
|
PluginManager pluginManager = getServer().getPluginManager();
|
||||||
|
pluginManager.registerEvents(this, this);
|
||||||
|
pluginManager.registerEvents(playerListener, this);
|
||||||
|
|
||||||
getCommand("griefalert").setTabCompleter(this);
|
getCommand("griefalert").setTabCompleter(this);
|
||||||
|
|
||||||
getLogger().info("GriefAlert has been enabled.");
|
getLogger().info("GriefAlert has been enabled.");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -131,16 +213,26 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupDatabase() {
|
private void setupDatabase() {
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
String jdbcUrl = getConfig().getString("jdbc-url");
|
||||||
try {
|
try {
|
||||||
connection = DriverManager.getConnection("jdbc:sqlite:" + getDataFolder().getAbsolutePath() + "/" + DB_FILE);
|
connection = DriverManager.getConnection(jdbcUrl);
|
||||||
Statement stmt = connection.createStatement();
|
database = new DatabaseService(executor, connection);
|
||||||
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ignored_locations (x INTEGER, y INTEGER, z INTEGER, world TEXT, PRIMARY KEY (x, y, z, world))");
|
logger.info("Connected to " + jdbcUrl);
|
||||||
stmt.close();
|
|
||||||
|
CompletableFuture<Void> migrationExecution = new Migrations(database, logger).execute();
|
||||||
|
migrationExecution.join();
|
||||||
|
logger.info("Database migration finished");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().severe("Could not set up SQLite database: " + e.getMessage());
|
logger.severe("Could not set up SQLite database: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
LOCATION IGNORING
|
||||||
|
*/
|
||||||
|
|
||||||
private boolean isLocationIgnored(int x, int y, int z, String world) {
|
private boolean isLocationIgnored(int x, int y, int z, String world) {
|
||||||
try {
|
try {
|
||||||
PreparedStatement ps = connection.prepareStatement("SELECT 1 FROM ignored_locations WHERE x=? AND y=? AND z=? AND world=?");
|
PreparedStatement ps = connection.prepareStatement("SELECT 1 FROM ignored_locations WHERE x=? AND y=? AND z=? AND world=?");
|
||||||
@@ -191,6 +283,10 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
EVENT LISTENERS
|
||||||
|
*/
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
@EventHandler(ignoreCancelled = true)
|
||||||
// Block break alerts
|
// Block break alerts
|
||||||
public void onBlockBreak(BlockBreakEvent event) {
|
public void onBlockBreak(BlockBreakEvent event) {
|
||||||
@@ -336,7 +432,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//event handler for item frames
|
// Placing item to an item frame
|
||||||
@EventHandler(ignoreCancelled = true)
|
@EventHandler(ignoreCancelled = true)
|
||||||
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||||
if (!(event.getRightClicked() instanceof ItemFrame frame)) return;
|
if (!(event.getRightClicked() instanceof ItemFrame frame)) return;
|
||||||
@@ -370,7 +466,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Breaking item frame item (uses a different event then placing an item)
|
// Item frame stealing alerts
|
||||||
@EventHandler(ignoreCancelled = true)
|
@EventHandler(ignoreCancelled = true)
|
||||||
public void onItemFrameDamage(EntityDamageByEntityEvent event) {
|
public void onItemFrameDamage(EntityDamageByEntityEvent event) {
|
||||||
if (!(event.getEntity() instanceof ItemFrame frame)) return;
|
if (!(event.getEntity() instanceof ItemFrame frame)) return;
|
||||||
@@ -398,6 +494,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName);
|
alert(message, playerName, "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", target, x, y, z, worldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Item frame breaking alerts
|
||||||
@EventHandler(ignoreCancelled = true)
|
@EventHandler(ignoreCancelled = true)
|
||||||
public void onItemFrameBreak(HangingBreakByEntityEvent event) {
|
public void onItemFrameBreak(HangingBreakByEntityEvent event) {
|
||||||
if (!(event.getEntity() instanceof ItemFrame frame)) return;
|
if (!(event.getEntity() instanceof ItemFrame frame)) return;
|
||||||
@@ -423,6 +520,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Command handler
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
if (!command.getName().equalsIgnoreCase("griefalert")) return false;
|
if (!command.getName().equalsIgnoreCase("griefalert")) return false;
|
||||||
@@ -584,57 +682,6 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends the alert (or cancels it)
|
|
||||||
private void alert(String message, String playerName, String mapLink, String target, int x, int y, int z, String world) {
|
|
||||||
// Exclude trusted people
|
|
||||||
Player griefer = Bukkit.getPlayer(playerName);
|
|
||||||
if (griefer.hasPermission("griefalert.exclude") || griefer.hasPermission("griefalert.exclude." + target)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Spam limiter
|
|
||||||
String realAlertMessage = message;
|
|
||||||
String[] alert1 = null;
|
|
||||||
if (lastAlert != null) {
|
|
||||||
alert1 = lastAlert.split(" ");
|
|
||||||
}
|
|
||||||
String[] alert2 = message.split(" ");
|
|
||||||
if (alert1 != null) {
|
|
||||||
if (alert1[2].equals(alert2[2]) && alert1[5].equals(alert2[5]) && alert1[1].equals("broke") && alert2[1].equals("broke")) {
|
|
||||||
identicalAlerts += 1;
|
|
||||||
} else if (Arrays.equals(alert1, alert2)) {
|
|
||||||
identicalAlerts += 1;
|
|
||||||
} else {
|
|
||||||
identicalAlerts = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (identicalAlerts == 4) {
|
|
||||||
message = ChatColor.GRAY + "Same behavior continues.";
|
|
||||||
mapLink = null;
|
|
||||||
}
|
|
||||||
if (identicalAlerts > 4) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Use direct location check
|
|
||||||
if (world != null && isLocationIgnored(x, y, z, world)) {
|
|
||||||
return; // Do not alert if location is ignored
|
|
||||||
}
|
|
||||||
// Send an event for external hooks
|
|
||||||
GriefAlertEvent griefalert_event;
|
|
||||||
if (MAP_LINK != null && !MAP_LINK.isEmpty() && mapLink != null) {
|
|
||||||
griefalert_event = new GriefAlertEvent(message + " (" + mapLink + ")");
|
|
||||||
} else {
|
|
||||||
griefalert_event = new GriefAlertEvent(message);
|
|
||||||
}
|
|
||||||
getServer().getPluginManager().callEvent(griefalert_event);
|
|
||||||
// Notify staff ingame
|
|
||||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
|
||||||
if (player.hasPermission("griefalert.notify")) {
|
|
||||||
player.sendMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastAlert = realAlertMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
//pet alert(dog and cat only)
|
//pet alert(dog and cat only)
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPetKill(EntityDeathEvent event) {
|
public void onPetKill(EntityDeathEvent event) {
|
||||||
@@ -662,7 +709,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//farm animal handler
|
// Mob killing alerts
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onFarmAnimalDeath(EntityDeathEvent event) {
|
public void onFarmAnimalDeath(EntityDeathEvent event) {
|
||||||
if (!(event.getEntity().getKiller() instanceof Player killer)) return;
|
if (!(event.getEntity().getKiller() instanceof Player killer)) return;
|
||||||
@@ -686,7 +733,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
String worldName = event.getEntity().getWorld().getName();
|
String worldName = event.getEntity().getWorld().getName();
|
||||||
|
|
||||||
|
|
||||||
String message = ChatColor.GRAY + killer.getName() + " killed" + " " + typeName + "in " + animalOwner + "'s build at " + x + " " + y + " " + z + getHumanWorldName(worldName);
|
String message = ChatColor.GRAY + killer.getName() + " killed" + " " + typeName + " in " + animalOwner + "'s build at " + x + " " + y + " " + z + getHumanWorldName(worldName);
|
||||||
|
|
||||||
alert(message, killer.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", animalOwner, x, y, z, worldName);
|
alert(message, killer.getName(), "[Map Link](" + MAP_LINK + "/?worldname=" + worldName + "&zoom=7&x=" + x + "&y=" + y + "&z=" + z + ")", animalOwner, x, y, z, worldName);
|
||||||
|
|
||||||
@@ -698,6 +745,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
and its waxed, ill assume ur unwaxing, and therefor sending an alert
|
and its waxed, ill assume ur unwaxing, and therefor sending an alert
|
||||||
but some of this code was written at 1am so idk how and why im why and howing */
|
but some of this code was written at 1am so idk how and why im why and howing */
|
||||||
|
|
||||||
|
// Waxing/unwaxing alerts
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onBlockClick(PlayerInteractEvent event) {
|
public void onBlockClick(PlayerInteractEvent event) {
|
||||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||||
@@ -760,7 +808,7 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
}, 1L);
|
}, 1L);
|
||||||
}
|
}
|
||||||
|
|
||||||
//stripping logs
|
// Log stripping alerts
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onLogStrip(PlayerInteractEvent event) {
|
public void onLogStrip(PlayerInteractEvent event) {
|
||||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||||
@@ -811,6 +859,10 @@ public class GriefAlert extends JavaPlugin implements Listener {
|
|||||||
}, 1L);
|
}, 1L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CoreProtect API
|
||||||
|
*/
|
||||||
|
|
||||||
private CoreProtectAPI getCoreProtect() {
|
private CoreProtectAPI getCoreProtect() {
|
||||||
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
|
Plugin plugin = getServer().getPluginManager().getPlugin("CoreProtect");
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ public class GriefAlertEvent extends Event {
|
|||||||
public String getAlert() {
|
public String getAlert() {
|
||||||
return this.alert;
|
return this.alert;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package net.ardakaz.griefalert.database;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DatabaseOperation<T> {
|
||||||
|
T execute(Connection connection) throws SQLException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package net.ardakaz.griefalert.database;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class DatabaseService {
|
||||||
|
|
||||||
|
private final ExecutorService executor;
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
public DatabaseService(ExecutorService executor, Connection connection) {
|
||||||
|
this.executor = executor;
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> CompletableFuture<T> submit(DatabaseOperation<T> operation) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
return operation.execute(connection);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, executor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package net.ardakaz.griefalert.database;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class Migrations {
|
||||||
|
|
||||||
|
private DatabaseService database;
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
public Migrations(DatabaseService database, Logger logger) {
|
||||||
|
this.database = database;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String CREATE_TABLE_IGNORED_LOCATIONS =
|
||||||
|
"CREATE TABLE IF NOT EXISTS ignored_locations (" +
|
||||||
|
"x INTEGER, " +
|
||||||
|
"y INTEGER, " +
|
||||||
|
"z INTEGER, " +
|
||||||
|
"world TEXT, " +
|
||||||
|
"PRIMARY KEY (x, y, z, world)" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
private static final String CREATE_TABLE_PLAYERS =
|
||||||
|
"CREATE TABLE IF NOT EXISTS players (" +
|
||||||
|
"id TEXT PRIMARY KEY, " +
|
||||||
|
"names TEXT NOT NULL, " +
|
||||||
|
"firstseen INTEGER NOT NULL, " +
|
||||||
|
"lastseen INTEGER NOT NULL, " +
|
||||||
|
"playtime INTEGER NOT NULL DEFAULT 0 " +
|
||||||
|
")";
|
||||||
|
|
||||||
|
private static final String CREATE_TABLE_ACTIONS =
|
||||||
|
"CREATE TABLE IF NOT EXISTS actions (" +
|
||||||
|
"id INTEGER PRIMARY KEY, " +
|
||||||
|
"player TEXT NOT NULL, " +
|
||||||
|
"type TEXT NOT NULL, " +
|
||||||
|
"moderator TEXT NOT NULL, " +
|
||||||
|
"reason TEXT, " +
|
||||||
|
"time TEXT, " +
|
||||||
|
"timestamp INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
|
||||||
|
"FOREIGN KEY(player) REFERENCES players(id)" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
private static final String CREATE_TABLE_ALERTS =
|
||||||
|
"CREATE TABLE IF NOT EXISTS alerts (" +
|
||||||
|
"id INTEGER PRIMARY KEY, " +
|
||||||
|
"player TEXT NOT NULL, " +
|
||||||
|
"action TEXT NOT NULL, " +
|
||||||
|
"target TEXT NOT NULL, " +
|
||||||
|
"target_amount INTEGER, " +
|
||||||
|
"target_player TEXT NOT NULL, " +
|
||||||
|
"x INTEGER NOT NULL, " +
|
||||||
|
"y INTEGER NOT NULL, " +
|
||||||
|
"z INTEGER NOT NULL, " +
|
||||||
|
"world TEXT NOT NULL, " +
|
||||||
|
"is_flagged INTEGER, " +
|
||||||
|
"flag_text TEXT, " +
|
||||||
|
"resolved_by TEXT, " +
|
||||||
|
"state TEXT NOT NULL DEFAULT 'UNRESOLVED', " +
|
||||||
|
"timestamp INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
|
||||||
|
"FOREIGN KEY(player) REFERENCES players(id)" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
private static final String CREATE_TABLE_REPORTS =
|
||||||
|
"CREATE TABLE IF NOT EXISTS reports (" +
|
||||||
|
"id INTEGER PRIMARY KEY, " +
|
||||||
|
"player TEXT NOT NULL, " +
|
||||||
|
"x INTEGER NOT NULL, " +
|
||||||
|
"y INTEGER NOT NULL, " +
|
||||||
|
"z INTEGER NOT NULL, " +
|
||||||
|
"world TEXT NOT NULL, " +
|
||||||
|
"text TEXT NOT NULL, " +
|
||||||
|
"resolved_by TEXT, " +
|
||||||
|
"timestamp INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
|
||||||
|
"state TEXT NOT NULL DEFAULT 'UNRESOLVED'" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
public CompletableFuture<Void> execute() {
|
||||||
|
return database.submit(connection -> {
|
||||||
|
try(Statement stmt = connection.createStatement()){
|
||||||
|
stmt.executeUpdate(Migrations.CREATE_TABLE_IGNORED_LOCATIONS);
|
||||||
|
stmt.executeUpdate(Migrations.CREATE_TABLE_PLAYERS);
|
||||||
|
stmt.executeUpdate(Migrations.CREATE_TABLE_ACTIONS);
|
||||||
|
stmt.executeUpdate(Migrations.CREATE_TABLE_ALERTS);
|
||||||
|
stmt.executeUpdate(Migrations.CREATE_TABLE_REPORTS);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.severe("Failed to execute database migrations: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package net.ardakaz.griefalert.database;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record PlayerEntity(String id, List<String> names, long playtime, long firstSeen, long lastSeen) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package net.ardakaz.griefalert.database;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class PlayerRepository {
|
||||||
|
private Logger logger;
|
||||||
|
private final DatabaseService database;
|
||||||
|
|
||||||
|
public PlayerRepository(DatabaseService database, Logger logger) {
|
||||||
|
this.database = database;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<PlayerEntity> get(String uuid) {
|
||||||
|
return database.submit(connection -> {
|
||||||
|
|
||||||
|
try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM players WHERE id=?")) {
|
||||||
|
ps.setString(1, uuid);
|
||||||
|
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
return new PlayerEntity(
|
||||||
|
rs.getString("id"),
|
||||||
|
Arrays.stream(rs.getString("names").split(",")).toList(),
|
||||||
|
rs.getLong("playtime"),
|
||||||
|
rs.getLong("firstseen"),
|
||||||
|
rs.getLong("lastseen")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.severe("DB error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> upsert(PlayerEntity entity) {
|
||||||
|
return database.submit(connection -> {
|
||||||
|
String upsert = """
|
||||||
|
INSERT INTO players(id, names, firstseen, lastseen) VALUES(?,?,?,?)
|
||||||
|
ON CONFLICT(id)
|
||||||
|
DO UPDATE SET names=excluded.names, playtime=?, lastseen=excluded.lastseen;
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (PreparedStatement ps = connection.prepareStatement(upsert)) {
|
||||||
|
ps.setString(1, entity.id());
|
||||||
|
String names = String.join(",", entity.names());
|
||||||
|
ps.setString(2, names);
|
||||||
|
ps.setLong(3, entity.firstSeen());
|
||||||
|
ps.setLong(4, entity.lastSeen());
|
||||||
|
ps.setLong(5, entity.playtime());
|
||||||
|
|
||||||
|
ps.executeUpdate();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.severe("Failed to save player: " + entity);
|
||||||
|
logger.severe(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<Void> updateNames(String id, List<String> names) {
|
||||||
|
return database.submit(connection -> {
|
||||||
|
String update = """
|
||||||
|
UPDATE players
|
||||||
|
SET names = ?
|
||||||
|
WHERE id = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (PreparedStatement ps = connection.prepareStatement(update)) {
|
||||||
|
ps.setString(1, String.join(",", names));
|
||||||
|
ps.setString(2, id);
|
||||||
|
|
||||||
|
ps.executeUpdate();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.severe("Failed to update names for playerID: " + id);
|
||||||
|
logger.severe(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package net.ardakaz.griefalert.listener;
|
||||||
|
|
||||||
|
import net.ardakaz.griefalert.database.PlayerEntity;
|
||||||
|
import net.ardakaz.griefalert.database.PlayerRepository;
|
||||||
|
import org.bukkit.Statistic;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
|
private Logger logger;
|
||||||
|
private PlayerRepository repository;
|
||||||
|
|
||||||
|
public PlayerListener(PlayerRepository repository, Logger logger) {
|
||||||
|
this.logger = logger;
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerLogin(PlayerJoinEvent event) {
|
||||||
|
Player eventPlayer = event.getPlayer();
|
||||||
|
repository.get(
|
||||||
|
eventPlayer.getUniqueId().toString()
|
||||||
|
).thenAccept(dbPlayer -> {
|
||||||
|
if (dbPlayer == null) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
repository.upsert(new PlayerEntity(
|
||||||
|
eventPlayer.getUniqueId().toString(),
|
||||||
|
List.of(eventPlayer.getName()),
|
||||||
|
0,
|
||||||
|
now,
|
||||||
|
now
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
List<String> newNames = updateNames(eventPlayer.getName(), dbPlayer.names());
|
||||||
|
repository.updateNames(dbPlayer.id(), newNames);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerLogout(PlayerQuitEvent event) {
|
||||||
|
Player eventPlayer = event.getPlayer();
|
||||||
|
long playtimeSeconds = eventPlayer.getStatistic(Statistic.PLAY_ONE_MINUTE) / 20L;
|
||||||
|
repository.get(
|
||||||
|
eventPlayer.getUniqueId().toString()
|
||||||
|
).thenAccept(dbPlayer -> {
|
||||||
|
if (dbPlayer != null) {
|
||||||
|
PlayerEntity updatedPlayer = new PlayerEntity(
|
||||||
|
dbPlayer.id(),
|
||||||
|
dbPlayer.names(),
|
||||||
|
playtimeSeconds,
|
||||||
|
dbPlayer.firstSeen(),
|
||||||
|
System.currentTimeMillis()
|
||||||
|
);
|
||||||
|
repository.upsert(updatedPlayer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> updateNames(String currentName, List<String> historicalNames) {
|
||||||
|
List<String> newNames = new ArrayList<>(historicalNames);
|
||||||
|
while (newNames.contains(currentName)) {
|
||||||
|
newNames.remove(currentName);
|
||||||
|
}
|
||||||
|
newNames.add(currentName);
|
||||||
|
return newNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
# Ignore all stealing?
|
# Ignore all stealing?
|
||||||
allow-stealing: false
|
allow-stealing: false
|
||||||
|
|
||||||
|
jdbc-url: "./plugins/GriefAlert/database.db"
|
||||||
|
|
||||||
# Breaking these blocks don't cause alerts. (Material)
|
# Breaking these blocks don't cause alerts. (Material)
|
||||||
excluded-blocks:
|
excluded-blocks:
|
||||||
- WHEAT
|
- WHEAT
|
||||||
|
|||||||
Reference in New Issue
Block a user