Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import java.io.*

plugins {
java
id("com.github.johnrengelman.shadow") version "7.1.2"
id("com.gradleup.shadow") version "9.0.0-beta4"
`maven-publish`
id("xyz.jpenilla.run-velocity") version "2.0.0"
id("io.papermc.hangar-publish-plugin") version "0.0.4"
Expand Down Expand Up @@ -40,7 +40,7 @@ fun runCommand(command: String): String {
val release = System.getenv("GRADLE_RELEASE").equals("true", ignoreCase = true)
val gitHash = runCommand("git rev-parse --short HEAD")
group = "com.oskarsmc"
version = "1.4.0"
version = "1.5.0"

if (!release) {
version = "$version-$gitHash-SNAPSHOT"
Expand All @@ -61,6 +61,7 @@ tasks {
relocate("org.bstats", "com.oskarsmc.message.relocated.bstats")
relocate("cloud.commandframework", "com.oskarsmc.message.relocated.cloud")
relocate("io.leangen.geantyref", "com.oskarsmc.message.relocated.geantyref")
archiveFileName.set("${project.name}-${project.version}.jar")
}

build {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 2 additions & 0 deletions src/main/java/com/oskarsmc/message/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.oskarsmc.message.command.IgnoreCommand;
import com.oskarsmc.message.command.MessageCommand;
import com.oskarsmc.message.command.ReplyCommand;
import com.oskarsmc.message.command.SocialSpyCommand;
Expand Down Expand Up @@ -93,6 +94,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) {
injector.getInstance(MessageCommand.class);
injector.getInstance(SocialSpyCommand.class);
injector.getInstance(ReplyCommand.class);
injector.getInstance(IgnoreCommand.class);

// Metrics
injector.getInstance(MessageMetrics.class);
Expand Down
119 changes: 119 additions & 0 deletions src/main/java/com/oskarsmc/message/command/IgnoreCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.oskarsmc.message.command;

import cloud.commandframework.Command;
import cloud.commandframework.minecraft.extras.RichDescription;
import cloud.commandframework.velocity.VelocityCommandManager;
import cloud.commandframework.velocity.arguments.PlayerArgument;
import com.google.inject.Inject;
import com.oskarsmc.message.configuration.MessageSettings;
import com.oskarsmc.message.logic.IgnoreManager;
import com.oskarsmc.message.util.DefaultPermission;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.NotNull;

import java.util.Set;
import java.util.UUID;

/**
* /ignore command with subcommands: on, off, list, and toggle
* Player being ignored doesnt knows
*/
public final class IgnoreCommand {
private final IgnoreManager ignoreManager;
private final ProxyServer proxyServer;

@Inject
public IgnoreCommand(@NotNull VelocityCommandManager<CommandSource> commandManager,
@NotNull MessageSettings messageSettings,
@NotNull IgnoreManager ignoreManager,
@NotNull ProxyServer proxyServer) {
this.ignoreManager = ignoreManager;
this.proxyServer = proxyServer;

Command.Builder<CommandSource> builder = commandManager.commandBuilder("ignore",
messageSettings.ignoreAliases().toArray(new String[0]))
.senderType(Player.class)
.permission(new DefaultPermission("osmc.message.ignore"));

// /ignore <player> toggle
commandManager.command(builder
.argument(PlayerArgument.of("player"), RichDescription.translatable("oskarsmc.message.command.ignore.argument.player"))
.handler(context -> handleToggle((Player) context.getSender(), context.get("player")))
);

// /ignore on <player>
commandManager.command(builder
.literal("on")
.argument(PlayerArgument.of("player"), RichDescription.translatable("oskarsmc.message.command.ignore.argument.player"))
.handler(context -> handleOn((Player) context.getSender(), context.get("player")))
);

// /ignore off <player>
commandManager.command(builder
.literal("off")
.argument(PlayerArgument.of("player"), RichDescription.translatable("oskarsmc.message.command.ignore.argument.player"))
.handler(context -> handleOff((Player) context.getSender(), context.get("player")))
);

// /ignore list
commandManager.command(builder
.literal("list")
.handler(context -> handleList((Player) context.getSender()))
);
}

private void handleToggle(Player ignorer, Player target) {
if (ignorer.getUniqueId().equals(target.getUniqueId())) {
ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.self-error", NamedTextColor.RED));
return;
}
boolean nowIgnoring = ignoreManager.toggleIgnore(ignorer, target);
sendFeedback(ignorer, target, nowIgnoring);
}

private void handleOn(Player ignorer, Player target) {
if (ignorer.getUniqueId().equals(target.getUniqueId())) {
ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.self-error", NamedTextColor.RED));
return;
}
ignoreManager.ignore(ignorer, target);
sendFeedback(ignorer, target, true);
}

private void handleOff(Player ignorer, Player target) {
if (ignorer.getUniqueId().equals(target.getUniqueId())) {
ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.self-error", NamedTextColor.RED));
return;
}
ignoreManager.unignore(ignorer, target);
sendFeedback(ignorer, target, false);
}

private void handleList(Player ignorer) {
Set<UUID> ignored = ignoreManager.getIgnored(ignorer);
if (ignored.isEmpty()) {
ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.list.empty"));
return;
}

ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.list.header"));
for (UUID uuid : ignored) {
String name = proxyServer.getPlayer(uuid)
.map(Player::getUsername)
.orElse(uuid.toString());
ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.list.entry", Component.text(name)));
}
}

private void sendFeedback(Player ignorer, Player target, boolean nowIgnoring) {
if (nowIgnoring) {
ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.on", Component.text(target.getUsername())));
} else {
ignorer.sendMessage(Component.translatable("oskarsmc.message.command.ignore.off", Component.text(target.getUsername())));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public final class MessageSettings {
private List<String> messageAlias;
private List<String> replyAlias;
private List<String> socialSpyAlias;
private List<String> ignoreAlias;

private boolean luckpermsIntegration;
private boolean miniPlaceholdersIntegration;
Expand Down Expand Up @@ -84,6 +85,7 @@ public MessageSettings(@DataDirectory @NotNull Path dataFolder, Logger logger) {
this.messageAlias = toml.getList("aliases.message");
this.replyAlias = toml.getList("aliases.reply");
this.socialSpyAlias = toml.getList("aliases.socialspy");
this.ignoreAlias = toml.getList("aliases.ignore");

// Exceptions
this.customErrorHandlers = new HashMap<>();
Expand Down Expand Up @@ -277,6 +279,16 @@ public List<String> replyAliases() {
return replyAlias;
}

/**
* Get the aliases of the ignore command.
*
* @return The aliases of the ignore command.
*/
@Pure
public List<String> ignoreAliases() {
return ignoreAlias;
}

/**
* Get all defined custom error handlers
*
Expand Down
102 changes: 102 additions & 0 deletions src/main/java/com/oskarsmc/message/logic/IgnoreManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.oskarsmc.message.logic;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* Manages persistent player ignore lists (A ignores B = A never sees messages from B).
* Player B has no idea they are ignored.
*/
public final class IgnoreManager {
private static final Type MAP_TYPE = new TypeToken<Map<UUID, Set<UUID>>>() {}.getType();

private final Path ignoresFile;
private final Gson gson = new Gson();
private final Map<UUID, Set<UUID>> ignoredPlayers = new ConcurrentHashMap<>(); // ignorer UUID → set of ignored UUIDs

public IgnoreManager(Path dataFolder) {
this.ignoresFile = dataFolder.resolve("ignores.json");
loadIgnores();
}

private void loadIgnores() {
if (!Files.exists(ignoresFile)) return;
try {
Map<UUID, Set<UUID>> loaded = gson.fromJson(Files.readString(ignoresFile), MAP_TYPE);
if (loaded != null) ignoredPlayers.putAll(loaded);
} catch (IOException e) {
// silent fail - file will be recreated on first save
}
}

private void saveIgnores() {
try {
Files.writeString(ignoresFile, gson.toJson(ignoredPlayers));
} catch (IOException ignored) {
}
}

/**
* Toggle ignore status for the given player.
* @return true if the target is now ignored
*/
public boolean toggleIgnore(@NotNull Player ignorer, @NotNull Player target) {
UUID ignorerId = ignorer.getUniqueId();
UUID targetId = target.getUniqueId();

Set<UUID> set = ignoredPlayers.computeIfAbsent(ignorerId, k -> ConcurrentHashMap.newKeySet());

if (set.remove(targetId)) {
saveIgnores();
return false; // no longer ignoring
} else {
set.add(targetId);
saveIgnores();
return true; // now ignoring
}
}

public void ignore(@NotNull Player ignorer, @NotNull Player target) {
UUID ignorerId = ignorer.getUniqueId();
UUID targetId = target.getUniqueId();
ignoredPlayers.computeIfAbsent(ignorerId, k -> ConcurrentHashMap.newKeySet()).add(targetId);
saveIgnores();
}

public void unignore(@NotNull Player ignorer, @NotNull Player target) {
UUID ignorerId = ignorer.getUniqueId();
UUID targetId = target.getUniqueId();
Set<UUID> set = ignoredPlayers.get(ignorerId);
if (set != null) {
set.remove(targetId);
saveIgnores();
}
}

/**
* Check if the recipient has ignored the sender (silent check).
*/
public boolean isIgnored(@NotNull Player recipient, @NotNull CommandSource sender) {
if (!(sender instanceof Player senderPlayer)) return false;
Set<UUID> ignored = ignoredPlayers.get(recipient.getUniqueId());
return ignored != null && ignored.contains(senderPlayer.getUniqueId());
}

/**
* Get all players the given ignorer is ignoring (for /ignore list).
*/
public @NotNull Set<UUID> getIgnored(@NotNull Player ignorer) {
Set<UUID> set = ignoredPlayers.get(ignorer.getUniqueId());
return set != null ? Collections.unmodifiableSet(set) : Collections.emptySet();
}
}
20 changes: 15 additions & 5 deletions src/main/java/com/oskarsmc/message/logic/MessageHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public final class MessageHandler {
private final MessageSettings messageSettings;
private final MiniMessage miniMessage = MiniMessage.miniMessage();
private final ConcurrentHashMap<Player, Player> conversations = new ConcurrentHashMap<>();
private final IgnoreManager ignoreManager;
/**
* Conversation Watchers
*/
Expand All @@ -38,8 +39,9 @@ public final class MessageHandler {
*
* @param messageSettings Message Settings
*/
public MessageHandler(MessageSettings messageSettings) {
public MessageHandler(MessageSettings messageSettings, IgnoreManager ignoreManager) {
this.messageSettings = messageSettings;
this.ignoreManager = ignoreManager;
}

/**
Expand Down Expand Up @@ -102,11 +104,19 @@ public void handleMessageEvent(@NotNull MessageEvent event) {
Component receiverMessage = miniMessage.deserialize(messageSettings.messageReceivedMiniMessage(), placeholders);

event.sender().sendMessage(senderMessage);
event.recipient().sendMessage(receiverMessage);

if (event.sender() instanceof Player player) {
conversations.remove(event.recipient());
conversations.put(event.recipient(), player);
boolean isIgnored = false;
if (event.sender() instanceof Player senderPlayer) {
isIgnored = ignoreManager.isIgnored(event.recipient(), senderPlayer);
}

if (!isIgnored) {
event.recipient().sendMessage(receiverMessage);

if (event.sender() instanceof Player player) {
conversations.remove(event.recipient());
conversations.put(event.recipient(), player);
}
}

Component socialSpyComponent = miniMessage.deserialize(messageSettings.messageSocialSpyMiniMessage(), placeholders);
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/com/oskarsmc/message/util/MessageModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.oskarsmc.message.command.IgnoreCommand;
import com.oskarsmc.message.command.MessageCommand;
import com.oskarsmc.message.command.ReplyCommand;
import com.oskarsmc.message.command.SocialSpyCommand;
import com.oskarsmc.message.configuration.MessageSettings;
import com.oskarsmc.message.logic.IgnoreManager;
import com.oskarsmc.message.logic.MessageHandler;
import com.oskarsmc.message.logic.MessageMetrics;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.nio.file.Path;

/**
* The message guice module.
*/
Expand All @@ -34,12 +39,20 @@ protected void configure() {
bind(ReplyCommand.class).in(Singleton.class);
bind(SocialSpyCommand.class).in(Singleton.class);
bind(MessageMetrics.class).in(Singleton.class);
bind(IgnoreCommand.class).in(Singleton.class);
}

@Contract(" -> new")
@Singleton
@Provides
private @NotNull MessageHandler provideHandler() {
return new MessageHandler(messageSettings);
private @NotNull MessageHandler provideHandler(IgnoreManager ignoreManager) {
return new MessageHandler(messageSettings, ignoreManager);
}

@Singleton
@Provides
private @NotNull IgnoreManager provideIgnoreManager(@DataDirectory Path dataDirectory) {
return new IgnoreManager(dataDirectory);
}

}
1 change: 1 addition & 0 deletions src/main/resources/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ message-socialspy = "<gradient:aqua:blue>[SocialSpy] </gradient>[<white><sender>
message = ["msg", "tell"]
reply = ["r"]
socialspy = ["ss"]
ignore = ["ignore", "ig"]

# Please don't touch this
[developer-info]
Expand Down
Loading