diff options
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | gradle.properties | 2 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/Blame.java | 47 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/command/BlameBlockCommand.java | 8 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/command/BlameStatCommand.java | 111 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/data/entry/LogEntry.java | 5 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/data/entry/LogEntryNames.java (renamed from src/main/java/com/keuin/blame/data/entry/LogEntryNamesV1.java) | 3 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/data/helper/VersionedLogEntryHelper.java | 22 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/data/transformer/AbstractLogEntryTransformer.java | 7 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/data/transformer/LogEntryV1ToV2Transformer.java | 34 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/data/transformer/TransformerManager.java | 32 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/lookup/BlockPosLookupFilter.java | 6 | ||||
-rw-r--r-- | src/main/java/com/keuin/blame/util/DatabaseUtil.java | 15 |
13 files changed, 283 insertions, 22 deletions
@@ -1,9 +1,12 @@ -# Fabric Example Mod +# Blame -## Setup +When you find something has been messed up in the Minecraft server you're managing, then who to blame? -For setup instructions please see the [fabric wiki page](https://fabricmc.net/wiki/tutorial:setup) that relates to the IDE that you are using. +*Blame* records players' interactions with blocks and entities, thus enabling you to find out the unruly player. -## License +## Usage -This template is available under the CC0 license. Feel free to learn from it and incorporate it in your own projects. +- Install MongoDB +- Configure your database in `blame.json` sitting with the server `.jar` file +- Start your server +- Use command `/blame` to look up the history
\ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 2527577..aa2141a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.15.2 yarn_mappings=1.15.2+build.17 loader_version=0.10.8 # Mod Properties -mod_version=1.2.0-alpha +mod_version=1.3.1-alpha maven_group=com.keuin.blame archives_base_name=blame-fabric # Dependencies diff --git a/src/main/java/com/keuin/blame/Blame.java b/src/main/java/com/keuin/blame/Blame.java index ab22590..a4c806d 100644 --- a/src/main/java/com/keuin/blame/Blame.java +++ b/src/main/java/com/keuin/blame/Blame.java @@ -5,8 +5,10 @@ import com.keuin.blame.adapter.*; import com.keuin.blame.adapter.handler.PlaceBlockHandler; import com.keuin.blame.command.BlameBlockCommand; import com.keuin.blame.command.BlameLimitCommand; +import com.keuin.blame.command.BlameStatCommand; import com.keuin.blame.config.BlameConfig; import com.keuin.blame.lookup.LookupManager; +import com.keuin.blame.util.DatabaseUtil; import com.keuin.blame.util.PrintUtil; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.IntegerArgumentType; @@ -24,12 +26,10 @@ import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.logging.Level; -import java.util.logging.Logger; public class Blame implements ModInitializer { - private static final Logger logger = Logger.getLogger(Blame.class.getName()); + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Blame.class.getName()); public static BlameConfig config; @@ -53,9 +53,32 @@ public class Blame implements ModInitializer { return true; } - private static void disableMongoSpamming() { - Logger mongoLogger = Logger.getLogger("org.mongodb.driver"); - mongoLogger.setLevel(Level.WARNING); + + + private static void upgradeOldLogEntries() { +// try (final MongoClient mongoClient = MongoClients.create(DatabaseUtil.CLIENT_SETTINGS)) { +// final MongoDatabase db = mongoClient.getDatabase( +// DatabaseUtil.MONGO_CONFIG.getDatabaseName() +// ); +// final MongoCollection<LogEntry> collection = db.getCollection( +// DatabaseUtil.MONGO_CONFIG.getLogCollectionName(), LogEntry.class +// ); +// collection.updateMany() +// FindIterable<LogEntry> iterable = +// collection.find(Filters.ne(LogEntryNames.VERSION, TransformerManager.LATEST_VERSION)) +// .showRecordId(true); +// for (LogEntry logEntry : iterable) { +// if (logEntry.version > TransformerManager.LATEST_VERSION) { +// logger.warning("Detected a newer entry in the database! " + +// "Downgrading of Blame is not supported and may cause " + +// "unexpected behaviour."); +// continue; +// } +// +// collection.updateOne(iterable.showRecordId()) +// } +// +// } } @Override @@ -67,7 +90,7 @@ public class Blame implements ModInitializer { if (!loadConfig()) return; - disableMongoSpamming(); + DatabaseUtil.disableMongoSpamming(); // hook disable event ServerLifecycleEvents.SERVER_STOPPING.register(new ServerLifecycleEvents.ServerStopping() { @@ -102,11 +125,17 @@ public class Blame implements ModInitializer { .then(CommandManager.argument("y", IntegerArgumentType.integer()) .then(CommandManager.argument("z", IntegerArgumentType.integer()) .then(CommandManager.argument("world", StringArgumentType.greedyString()) - .executes(BlameBlockCommand::blameBlock))))))); + .executes(BlameBlockCommand::blameGivenBlockPos)))))) + ); commandDispatcher.register( CommandManager.literal("blame").then(CommandManager.literal("limit") .then(CommandManager.argument("limit", IntegerArgumentType.integer(1, 255)) - .executes(BlameLimitCommand::setLimit)))); + .executes(BlameLimitCommand::setLimit))) + ); + commandDispatcher.register( + CommandManager.literal("blame").then(CommandManager.literal("stat") + .executes(BlameStatCommand::showStat)) + ); } }); } diff --git a/src/main/java/com/keuin/blame/command/BlameBlockCommand.java b/src/main/java/com/keuin/blame/command/BlameBlockCommand.java index 578870d..4fdd350 100644 --- a/src/main/java/com/keuin/blame/command/BlameBlockCommand.java +++ b/src/main/java/com/keuin/blame/command/BlameBlockCommand.java @@ -16,8 +16,7 @@ import static com.keuin.blame.command.Commands.SUCCESS; public class BlameBlockCommand { - - public static int blameBlock(CommandContext<ServerCommandSource> context) { + public static int blameGivenBlockPos(CommandContext<ServerCommandSource> context) { Entity entity = context.getSource().getEntity(); if (!(entity instanceof ServerPlayerEntity)) { // can only be executed by player @@ -39,6 +38,10 @@ public class BlameBlockCommand { return SUCCESS; } + public static int blameGivenBlockRange(CommandContext<ServerCommandSource> context) { + return SUCCESS; + } + private static class Callback implements LookupCallback { private final CommandContext<ServerCommandSource> context; @@ -63,4 +66,5 @@ public class BlameBlockCommand { PrintUtil.msgInfo(context, printBuilder.toString()); } } + } diff --git a/src/main/java/com/keuin/blame/command/BlameStatCommand.java b/src/main/java/com/keuin/blame/command/BlameStatCommand.java new file mode 100644 index 0000000..fade0a1 --- /dev/null +++ b/src/main/java/com/keuin/blame/command/BlameStatCommand.java @@ -0,0 +1,111 @@ +package com.keuin.blame.command; + +import com.google.common.base.Strings; +import com.keuin.blame.data.entry.LogEntry; +import com.keuin.blame.data.helper.VersionedLogEntryHelper; +import com.keuin.blame.util.DatabaseUtil; +import com.keuin.blame.util.PrintUtil; +import com.mojang.brigadier.context.CommandContext; +import com.mongodb.MongoClientException; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import net.minecraft.server.command.ServerCommandSource; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.logging.Logger; + +public class BlameStatCommand { + + private static final Logger logger = Logger.getLogger(BlameStatCommand.class.getName()); + + public static int showStat(CommandContext<ServerCommandSource> context) { + PrintUtil.msgInfo(context, "Collecting statistics. This may take a few seconds..."); + showStat(new ShowStatCallback() { + @Override + public void showStat(@Nullable BlameStat stat) { + StringBuilder sb = new StringBuilder(); + if (stat != null) { + sb.append("Statistics\n"); + sb.append("====\n"); + sb.append("# Count by subjects\n"); + stat.getCountMap().forEach((subjectId, count) -> { + sb.append("<").append( + Optional.ofNullable(Strings.emptyToNull(subjectId)).orElse("null") + ).append(">: ").append(count).append("\n"); + }); + sb.append("=== END ==="); + } else { + sb.append("Failed to get statistics. Please refer to server log for more information."); + } + PrintUtil.msgInfo(context, sb.toString()); + } + }); + return Commands.SUCCESS; + } + + public static void showStat(ShowStatCallback callback) { + Objects.requireNonNull(callback); + new Thread(new Runnable() { + @Override + public void run() { + logger.info("Collecting statistics..."); + try (final MongoClient mongoClient = MongoClients.create(DatabaseUtil.CLIENT_SETTINGS)) { + final MongoDatabase db = mongoClient.getDatabase( + DatabaseUtil.MONGO_CONFIG.getDatabaseName() + ); + final MongoCollection<LogEntry> collection = db.getCollection( + DatabaseUtil.MONGO_CONFIG.getLogCollectionName(), LogEntry.class + ); + Collection<String> ids = VersionedLogEntryHelper.getLoggedSubjectsId(collection); + + // count by distinct subjects + Map<String, Long> countMap = new HashMap<>(); + for (String subjectId : ids) { + long count = VersionedLogEntryHelper.countBySubjectId(collection, subjectId); + countMap.put(subjectId, count); + } + + // invoke callback + callback.showStat(new BlameStat(countMap)); + } catch (MongoClientException exception) { + logger.severe("Failed when querying the database: " + exception + + ". Failed to get statistics."); + callback.showStat(null); + } + } + }, "BlameStatCommandThread").start(); + } + + public static class BlameStat { + private final Map<String, Long> countMap; + + public BlameStat(Map<String, Long> countMap) { + this.countMap = countMap; + } + + public Map<String, Long> getCountMap() { + return Collections.unmodifiableMap(countMap); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BlameStat blameStat = (BlameStat) o; + return Objects.equals(countMap, blameStat.countMap); + } + + @Override + public int hashCode() { + return Objects.hash(countMap); + } + } + + public interface ShowStatCallback { + void showStat(@Nullable BlameStat stat); + } + +} diff --git a/src/main/java/com/keuin/blame/data/entry/LogEntry.java b/src/main/java/com/keuin/blame/data/entry/LogEntry.java index e47cc92..04f52ca 100644 --- a/src/main/java/com/keuin/blame/data/entry/LogEntry.java +++ b/src/main/java/com/keuin/blame/data/entry/LogEntry.java @@ -11,7 +11,7 @@ import org.bson.codecs.pojo.annotations.BsonProperty; import java.util.Objects; import java.util.UUID; -import static com.keuin.blame.data.entry.LogEntryNamesV1.*; +import static com.keuin.blame.data.entry.LogEntryNames.*; public class LogEntry { @@ -70,6 +70,9 @@ public class LogEntry { @BsonProperty(OBJECT_POS) public WorldPos objectPos = WorldPos.NULL_POS; + @BsonProperty(RADIUS) + public double radius = 0; + public LogEntry() { } diff --git a/src/main/java/com/keuin/blame/data/entry/LogEntryNamesV1.java b/src/main/java/com/keuin/blame/data/entry/LogEntryNames.java index d21be97..e375d8e 100644 --- a/src/main/java/com/keuin/blame/data/entry/LogEntryNamesV1.java +++ b/src/main/java/com/keuin/blame/data/entry/LogEntryNames.java @@ -1,6 +1,6 @@ package com.keuin.blame.data.entry; -public class LogEntryNamesV1 { +public class LogEntryNames { public static final String VERSION = "version"; public static final String GAME_VERSION = "game_version"; public static final String TIMESTAMP_MILLIS = "timestamp_millis"; @@ -11,4 +11,5 @@ public class LogEntryNamesV1 { public static final String OBJECT_TYPE = "object_type"; public static final String OBJECT_ID = "object_id"; public static final String OBJECT_POS = "object_pos"; + public static final String RADIUS = "radius"; } diff --git a/src/main/java/com/keuin/blame/data/helper/VersionedLogEntryHelper.java b/src/main/java/com/keuin/blame/data/helper/VersionedLogEntryHelper.java new file mode 100644 index 0000000..064ae87 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/helper/VersionedLogEntryHelper.java @@ -0,0 +1,22 @@ +package com.keuin.blame.data.helper; + +import com.keuin.blame.data.entry.LogEntry; +import com.keuin.blame.data.entry.LogEntryNames; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class VersionedLogEntryHelper { + public static Collection<String> getLoggedSubjectsId(MongoCollection<LogEntry> collection) { + List<String> list = new ArrayList<>(); + for (String s : collection.distinct(LogEntryNames.SUBJECT_ID, String.class)) + list.add(s); + return list; + } + public static long countBySubjectId(MongoCollection<LogEntry> collection, String subjectId) { + return collection.countDocuments(Filters.eq(LogEntryNames.SUBJECT_ID, subjectId)); + } +} diff --git a/src/main/java/com/keuin/blame/data/transformer/AbstractLogEntryTransformer.java b/src/main/java/com/keuin/blame/data/transformer/AbstractLogEntryTransformer.java new file mode 100644 index 0000000..78872c5 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/transformer/AbstractLogEntryTransformer.java @@ -0,0 +1,7 @@ +package com.keuin.blame.data.transformer; + +import com.keuin.blame.data.entry.LogEntry; + +public abstract class AbstractLogEntryTransformer { + public abstract LogEntry transform(LogEntry entry); +} diff --git a/src/main/java/com/keuin/blame/data/transformer/LogEntryV1ToV2Transformer.java b/src/main/java/com/keuin/blame/data/transformer/LogEntryV1ToV2Transformer.java new file mode 100644 index 0000000..4f5c660 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/transformer/LogEntryV1ToV2Transformer.java @@ -0,0 +1,34 @@ +package com.keuin.blame.data.transformer; + +import com.keuin.blame.data.WorldPos; +import com.keuin.blame.data.entry.LogEntry; + +public class LogEntryV1ToV2Transformer extends AbstractLogEntryTransformer { + + private static final LogEntryV1ToV2Transformer INSTANCE = new LogEntryV1ToV2Transformer(); + + static { + TransformerManager.setTransformer(1, INSTANCE); + } + + @Override + public LogEntry transform(LogEntry entry) { + LogEntry entryV2 = new LogEntry(); + entryV2.version = 2; + entryV2.gameVersion = entry.gameVersion; + entryV2.timeMillis = entry.timeMillis; + entryV2.subjectId = entry.subjectId; + entryV2.subjectUUID = entry.subjectUUID; + entryV2.subjectPos = entry.subjectPos; + entryV2.actionType = entry.actionType; + entryV2.objectType = entry.objectType; + entryV2.objectId = entry.objectId; + entryV2.objectPos = entry.objectPos; + entryV2.radius = getRadius(entry.objectPos); + return entryV2; + } + + private static double getRadius(WorldPos objectPos) { + return Math.sqrt(Math.pow(objectPos.getX(), 2) + Math.pow(objectPos.getY(), 2) + Math.pow(objectPos.getZ(), 2)); + } +} diff --git a/src/main/java/com/keuin/blame/data/transformer/TransformerManager.java b/src/main/java/com/keuin/blame/data/transformer/TransformerManager.java new file mode 100644 index 0000000..645c990 --- /dev/null +++ b/src/main/java/com/keuin/blame/data/transformer/TransformerManager.java @@ -0,0 +1,32 @@ +package com.keuin.blame.data.transformer; + +import com.keuin.blame.data.entry.LogEntry; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class TransformerManager { + + public static int LATEST_VERSION = 2; + + private static final Map<Integer, AbstractLogEntryTransformer> transformerMap = new HashMap<>(); + + static void setTransformer(int baseVersion, AbstractLogEntryTransformer transformer) { + transformerMap.put(baseVersion, transformer); + } + + public static Object toLatestVersion(LogEntry baseEntry) { + return transformTo(LATEST_VERSION, baseEntry); + } + + public static Object transformTo(int targetVersion, LogEntry baseEntry) { + Objects.requireNonNull(baseEntry); + if (baseEntry.version > targetVersion) + throw new RuntimeException("Downgrade is not supported."); + LogEntry entry = baseEntry; + for (int currentVersion = baseEntry.version; currentVersion != targetVersion; ++currentVersion) + entry = transformerMap.get(currentVersion + 1).transform(entry); + return entry; + } +} diff --git a/src/main/java/com/keuin/blame/lookup/BlockPosLookupFilter.java b/src/main/java/com/keuin/blame/lookup/BlockPosLookupFilter.java index a4b2475..d7cec43 100644 --- a/src/main/java/com/keuin/blame/lookup/BlockPosLookupFilter.java +++ b/src/main/java/com/keuin/blame/lookup/BlockPosLookupFilter.java @@ -2,7 +2,7 @@ package com.keuin.blame.lookup; import com.keuin.blame.data.WorldPos; import com.keuin.blame.data.entry.LogEntry; -import com.keuin.blame.data.entry.LogEntryNamesV1; +import com.keuin.blame.data.entry.LogEntryNames; import com.mongodb.client.FindIterable; import com.mongodb.client.model.Filters; @@ -16,8 +16,8 @@ public class BlockPosLookupFilter extends AbstractLookupFilter { @Override FindIterable<LogEntry> find(FindIterable<LogEntry> iterable) { return iterable.filter(Filters.and( - Filters.eq(LogEntryNamesV1.VERSION, 1), - Filters.eq(LogEntryNamesV1.OBJECT_POS, blockPos) + Filters.eq(LogEntryNames.VERSION, 1), + Filters.eq(LogEntryNames.OBJECT_POS, blockPos) )); } } diff --git a/src/main/java/com/keuin/blame/util/DatabaseUtil.java b/src/main/java/com/keuin/blame/util/DatabaseUtil.java index 45ea98a..d68bc4d 100644 --- a/src/main/java/com/keuin/blame/util/DatabaseUtil.java +++ b/src/main/java/com/keuin/blame/util/DatabaseUtil.java @@ -1,5 +1,8 @@ package com.keuin.blame.util; +//import ch.qos.logback.classic.Level; +//import ch.qos.logback.classic.LoggerContext; + import com.keuin.blame.Blame; import com.keuin.blame.config.MongoConfig; import com.keuin.blame.data.enums.codec.ActionTypeCodec; @@ -11,6 +14,9 @@ import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.configuration.CodecRegistry; import org.bson.codecs.pojo.PojoCodecProvider; +import java.util.logging.Level; +import java.util.logging.Logger; + import static org.bson.codecs.configuration.CodecRegistries.fromProviders; public class DatabaseUtil { @@ -34,4 +40,13 @@ public class DatabaseUtil { // db.log.createIndex({ timestamp_millis: -1 }) // db.log.createIndex({ timestamp_millis: -1, object_id: "hashed" }) // db.log.createIndex({ timestamp_millis: -1, subject_id: "hashed" }) + + public static void disableMongoSpamming() { +// ((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger("org.mongodb.driver").setLevel(Level.ERROR); + String[] mongoDrivers = new String[] {"org.mongodb", "org.mongodb.driver", "org.mongodb.driver.cluster", "org.mongodb.driver.connection"}; + for (String driverName : mongoDrivers) { + Logger mongoLogger = Logger.getLogger(driverName); + mongoLogger.setLevel(Level.OFF); // Plz be silent, my boy. + } + } } |