mirror of
https://github.com/kaboomserver/commandspy.git
synced 2025-05-11 11:02:56 +00:00
Merge pull request #11 from amyavi/cleanups
This commit is contained in:
commit
fac2dfe8f2
8 changed files with 282 additions and 75 deletions
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
|
@ -1,10 +1,5 @@
|
|||
name: Maven CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -31,3 +26,4 @@ jobs:
|
|||
with:
|
||||
name: CommandSpy
|
||||
path: target/CommandSpy.jar
|
||||
compression-level: 0
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,3 +6,5 @@ target/
|
|||
.classpath
|
||||
.project
|
||||
*.iml
|
||||
.theia/
|
||||
run/
|
|
@ -6,9 +6,9 @@ The plugin is created for the Kaboom server.
|
|||
|
||||
## Commands
|
||||
|
||||
| Command | Alias | Permission | Description |
|
||||
| ------- | ----- | ---------- | ----------- |
|
||||
|/commandspy | /c, /cs, /cspy | commandspy.command | Allows you to spy on players' commands|
|
||||
| Command | Alias | Permission | Description |
|
||||
|-------------|----------------|--------------------|----------------------------------------|
|
||||
| /commandspy | /c, /cs, /cspy | commandspy.command | Allows you to spy on players' commands |
|
||||
|
||||
## Compiling
|
||||
|
||||
|
|
16
pom.xml
16
pom.xml
|
@ -15,7 +15,7 @@
|
|||
<dependency>
|
||||
<groupId>io.papermc.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.18.2-R0.1-SNAPSHOT</version>
|
||||
<version>1.20.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -30,10 +30,22 @@
|
|||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<version>3.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>checkstyle</id>
|
||||
|
|
127
src/main/java/pw/kaboom/commandspy/CommandSpyState.java
Normal file
127
src/main/java/pw/kaboom/commandspy/CommandSpyState.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
package pw.kaboom.commandspy;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class CommandSpyState {
|
||||
private static final Logger LOGGER = JavaPlugin.getPlugin(Main.class).getSLF4JLogger();
|
||||
|
||||
private final ObjectOpenHashSet<UUID> users = new ObjectOpenHashSet<>();
|
||||
private final StampedLock usersLock = new StampedLock();
|
||||
private final AtomicBoolean dirty = new AtomicBoolean();
|
||||
private final File file;
|
||||
|
||||
public CommandSpyState(final @NotNull File file) {
|
||||
this.file = file;
|
||||
|
||||
try {
|
||||
this.load();
|
||||
} catch (final FileNotFoundException exception) {
|
||||
try {
|
||||
this.save(); // Create file if it doesn't exist
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
} catch (final IOException exception) {
|
||||
LOGGER.error("Failed to load state file:", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
final InputStream reader = new BufferedInputStream(new FileInputStream(this.file));
|
||||
|
||||
int read;
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
|
||||
|
||||
// Loop until we read less than 16 bytes
|
||||
while ((read = reader.readNBytes(buffer.array(), 0, 16)) == 16) {
|
||||
this.users.add(new UUID(buffer.getLong(0), buffer.getLong(8)));
|
||||
}
|
||||
|
||||
reader.close();
|
||||
if (read != 0) {
|
||||
throw new IOException("Found " + read + " bytes extra whilst reading file");
|
||||
}
|
||||
}
|
||||
|
||||
private void save() throws IOException {
|
||||
Files.createParentDirs(this.file);
|
||||
final OutputStream writer = new BufferedOutputStream(new FileOutputStream(this.file));
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(new byte[16]);
|
||||
|
||||
final long stamp = this.usersLock.readLock();
|
||||
for (final UUID uuid : this.users) {
|
||||
buffer.putLong(0, uuid.getMostSignificantBits());
|
||||
buffer.putLong(8, uuid.getLeastSignificantBits());
|
||||
writer.write(buffer.array());
|
||||
}
|
||||
this.usersLock.unlockRead(stamp);
|
||||
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
public void trySave() {
|
||||
// If the state is not dirty, then we don't need to do anything.
|
||||
if (!this.dirty.compareAndExchange(true, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.save();
|
||||
} catch (final IOException exception) {
|
||||
LOGGER.error("Failed to save state file:", exception);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getCommandSpyState(final @NotNull UUID playerUUID) {
|
||||
final long stamp = this.usersLock.readLock();
|
||||
final boolean result = this.users.contains(playerUUID);
|
||||
this.usersLock.unlockRead(stamp);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setCommandSpyState(final @NotNull UUID playerUUID, final boolean state) {
|
||||
final long stamp = this.usersLock.writeLock();
|
||||
|
||||
final boolean dirty;
|
||||
if (state) {
|
||||
dirty = this.users.add(playerUUID);
|
||||
} else {
|
||||
dirty = this.users.remove(playerUUID);
|
||||
}
|
||||
|
||||
this.usersLock.unlockWrite(stamp);
|
||||
if (dirty) {
|
||||
this.dirty.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastSpyMessage(final @NotNull Component message) {
|
||||
// Raw access here, so we can get more performance by not locking/unlocking over and over
|
||||
final long stamp = this.usersLock.readLock();
|
||||
final Collection<Player> players = Bukkit.getOnlinePlayers()
|
||||
.stream()
|
||||
.filter(p -> this.users.contains(p.getUniqueId()))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
this.usersLock.unlockRead(stamp);
|
||||
|
||||
for (final Player recipient : players) {
|
||||
recipient.sendMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +1,188 @@
|
|||
package pw.kaboom.commandspy;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class Main extends JavaPlugin implements CommandExecutor, Listener {
|
||||
private FileConfiguration config;
|
||||
private CommandSpyState config;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
config = getConfig();
|
||||
this.config = new CommandSpyState(new File(this.getDataFolder(), "state.bin"));
|
||||
|
||||
//noinspection DataFlowIssue
|
||||
this.getCommand("commandspy").setExecutor(this);
|
||||
this.getServer().getPluginManager().registerEvents(this, this);
|
||||
|
||||
// Save the state every 30 seconds
|
||||
Bukkit.getScheduler().runTaskTimerAsynchronously(this, this.config::trySave, 600L, 600L);
|
||||
}
|
||||
|
||||
private void enableCommandSpy(final Player player) {
|
||||
config.set(player.getUniqueId().toString(), true);
|
||||
saveConfig();
|
||||
player.sendMessage(Component.text("Successfully enabled CommandSpy"));
|
||||
@Override
|
||||
public void onDisable() {
|
||||
this.config.trySave();
|
||||
}
|
||||
|
||||
private void disableCommandSpy(final Player player) {
|
||||
config.set(player.getUniqueId().toString(), null);
|
||||
saveConfig();
|
||||
player.sendMessage(Component.text("Successfully disabled CommandSpy"));
|
||||
private void updateCommandSpyState(final @NotNull Player target,
|
||||
final @NotNull CommandSender source, final boolean state) {
|
||||
this.config.setCommandSpyState(target.getUniqueId(), state);
|
||||
|
||||
final Component stateString = Component.text(state ? "enabled" : "disabled");
|
||||
|
||||
target.sendMessage(Component.empty()
|
||||
.append(Component.text("Successfully "))
|
||||
.append(stateString)
|
||||
.append(Component.text(" CommandSpy")));
|
||||
|
||||
if (source != target) {
|
||||
source.sendMessage(Component.empty()
|
||||
.append(Component.text("Successfully "))
|
||||
.append(stateString)
|
||||
.append(Component.text(" CommandSpy for "))
|
||||
.append(target.name())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private NamedTextColor getTextColor(final Player player) {
|
||||
if (config.contains(player.getUniqueId().toString())) {
|
||||
if (this.config.getCommandSpyState(player.getUniqueId())) {
|
||||
return NamedTextColor.YELLOW;
|
||||
}
|
||||
|
||||
return NamedTextColor.AQUA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(final CommandSender sender, final Command cmd, final String label,
|
||||
final String[] args) {
|
||||
if (sender instanceof ConsoleCommandSender) {
|
||||
sender.sendMessage(Component.text("Command has to be run by a player"));
|
||||
return true;
|
||||
public boolean onCommand(final @NotNull CommandSender sender, final @NotNull Command cmd,
|
||||
final @NotNull String label, final String[] args) {
|
||||
|
||||
Player target = null;
|
||||
Boolean state = null;
|
||||
|
||||
switch (args.length) {
|
||||
case 0 -> {
|
||||
}
|
||||
case 1, 2 -> {
|
||||
// Get the last argument as a state. Fail if we have 2 arguments.
|
||||
state = getState(args[args.length - 1]);
|
||||
if (state != null && args.length == 1) {
|
||||
break;
|
||||
} else if (state == null && args.length == 2) {
|
||||
sender.sendMessage(Component.text("Usage: ", NamedTextColor.RED)
|
||||
.append(Component.text(cmd.getUsage().replace("<command>", label)))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the first argument as a player. Fail if it can't be found.
|
||||
target = getPlayer(args[0]);
|
||||
if (target != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
sender.sendMessage(Component.empty()
|
||||
.append(Component.text("Player \""))
|
||||
.append(Component.text(args[0]))
|
||||
.append(Component.text("\" not found"))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
default -> {
|
||||
sender.sendMessage(Component.text("Usage: ", NamedTextColor.RED)
|
||||
.append(Component.text(cmd.getUsage().replace("<command>", label)))
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final Player player = (Player) sender;
|
||||
if (target == null) {
|
||||
if (!(sender instanceof final Player player)) {
|
||||
sender.sendMessage(Component.text("Command has to be run by a player"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length >= 1 && "on".equalsIgnoreCase(args[0])) {
|
||||
enableCommandSpy(player);
|
||||
return true;
|
||||
target = player;
|
||||
}
|
||||
if ((args.length >= 1 && "off".equalsIgnoreCase(args[0]))
|
||||
|| config.contains(player.getUniqueId().toString())) {
|
||||
disableCommandSpy(player);
|
||||
return true;
|
||||
|
||||
if (state == null) {
|
||||
state = !this.config.getCommandSpyState(target.getUniqueId());
|
||||
}
|
||||
enableCommandSpy(player);
|
||||
|
||||
this.updateCommandSpyState(target, sender, state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
void onPlayerCommandPreprocess(final PlayerCommandPreprocessEvent event) {
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = event.getPlayer();
|
||||
final NamedTextColor color = getTextColor(player);
|
||||
|
||||
final Component message = Component.text(player.getName(), color)
|
||||
.append(Component.text(": "))
|
||||
.append(Component.text(event.getMessage()));
|
||||
.append(Component.text(": "))
|
||||
.append(Component.text(event.getMessage()));
|
||||
|
||||
for (String uuidString : config.getKeys(false)) {
|
||||
final UUID uuid = UUID.fromString(uuidString);
|
||||
final Player recipient = Bukkit.getPlayer(uuid);
|
||||
|
||||
if (recipient == null) {
|
||||
continue;
|
||||
}
|
||||
recipient.sendMessage(message);
|
||||
}
|
||||
this.config.broadcastSpyMessage(message);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
void onSignChange(final SignChangeEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
final NamedTextColor color = getTextColor(player);
|
||||
Component message = Component.text(player.getName(), color)
|
||||
.append(Component.text(" created a sign with contents:"));
|
||||
.append(Component.text(" created a sign with contents:"));
|
||||
|
||||
for (Component line : event.lines()) {
|
||||
for (final Component line : event.lines()) {
|
||||
message = message
|
||||
.append(Component.text("\n "))
|
||||
.append(line);
|
||||
.append(Component.text("\n "))
|
||||
.append(line);
|
||||
}
|
||||
|
||||
for (String uuidString : config.getKeys(false)) {
|
||||
final UUID uuid = UUID.fromString(uuidString);
|
||||
final Player recipient = Bukkit.getPlayer(uuid);
|
||||
this.config.broadcastSpyMessage(message);
|
||||
}
|
||||
|
||||
if (recipient == null) {
|
||||
continue;
|
||||
private static Player getPlayer(final String arg) {
|
||||
final Player player = Bukkit.getPlayer(arg);
|
||||
if (player != null) {
|
||||
return player;
|
||||
}
|
||||
|
||||
final UUID uuid;
|
||||
try {
|
||||
uuid = UUID.fromString(arg);
|
||||
} catch (final IllegalArgumentException ignored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Bukkit.getPlayer(uuid);
|
||||
}
|
||||
|
||||
private static Boolean getState(final String arg) {
|
||||
switch (arg) {
|
||||
case "on", "enable" -> {
|
||||
return true;
|
||||
}
|
||||
case "off", "disable" -> {
|
||||
return false;
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
recipient.sendMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
name: CommandSpy
|
||||
main: pw.kaboom.commandspy.Main
|
||||
description: Plugin that allows you to spy on players' commands.
|
||||
api-version: 1.13
|
||||
api-version: '1.20'
|
||||
version: master
|
||||
folia-supported: true
|
||||
|
||||
commands:
|
||||
commandspy:
|
||||
aliases: [c,cs,cspy]
|
||||
aliases: [ c, cs, cspy ]
|
||||
description: Allows you to spy on players' commands
|
||||
usage: /commandspy
|
||||
permission: commandspy.command
|
||||
usage: '/<command> [player] [on|enable|off|disable]'
|
||||
|
|
|
@ -5,4 +5,6 @@
|
|||
|
||||
<suppressions>
|
||||
<suppress checks="Javadoc" files="."/>
|
||||
</suppressions>
|
||||
<suppress checks="MagicNumber" files="."/>
|
||||
<suppress checks="MethodLength" files="."/>
|
||||
</suppressions>
|
Loading…
Reference in a new issue