Skip to content

Commit

Permalink
RegEx implemented to select which messages are translatable
Browse files Browse the repository at this point in the history
  • Loading branch information
DoggySazHi committed Feb 16, 2024
1 parent 6da9657 commit 153a78a
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 27 deletions.
19 changes: 8 additions & 11 deletions src/main/java/net/gensokyoreimagined/motoori/Kosuzu.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,13 @@

package net.gensokyoreimagined.motoori;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Objects;

public final class Kosuzu extends JavaPlugin {
Expand Down Expand Up @@ -61,8 +53,13 @@ public void onEnable() {
config.addDefault("storage.mysql.username", "kosuzu");
config.addDefault("storage.mysql.password", "changeme");

var regexDefaults = new HashMap<String, Object>();
regexDefaults.put("match.include", "https?://[-a-zA-Z0-9@:%._\\+~#?&//=]+");
var regexDefaults = List.of(
"^<[^>]+> (.*)", // Vanilla
"^[^»]+» (.*)", // Discord
"^(?::build:|:dev_server:).+?: (.*)" // Chatty
);

config.addDefault("match.include", regexDefaults);

config.options().copyDefaults(true);
saveConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,16 @@
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Properties;
import java.util.logging.Logger;

public class KosuzuKnowsWhereYouLive {
public static @Nullable String getCountryCode(Player player) {
private final Logger logger;

public KosuzuKnowsWhereYouLive(Kosuzu kosuzu) {
logger = kosuzu.getLogger();
}

public @Nullable String getCountryCode(Player player) {
var address = player.getAddress();
if (address == null) {
return null;
Expand All @@ -43,13 +50,13 @@ public class KosuzuKnowsWhereYouLive {
* @param ip The IP address
* @return The country code, or null if the IP address is invalid or location is unknown
*/
public static @Nullable String getCountryCode(String ip) {
public @Nullable String getCountryCode(String ip) {
if (ip == null) {
return null;
}

if (ip.equals("127.0.0.1")) {
System.out.println("Localhost IP address detected from player; check forwarding on proxy?");
logger.warning("Localhost IP address detected from player; check forwarding on proxy?");
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,13 +348,37 @@ public Collection<Language> getLanguages() {
return null;
}

public @Nullable UUID addMessage(@NotNull String message) {
public @Nullable UUID addMessage(@NotNull String json, @NotNull String message, @NotNull UUID player) {
try (var connection = getConnection()) {
// First cache the message, if necessary
// Then add the JSON to the database separately (player-specific)

UUID messageUUID = null;

try (var statement = connection.prepareStatement("SELECT message_id FROM `message` WHERE `text` = ?")) {
statement.setString(1, message);
var data = statement.executeQuery();
if (data.next()) {
messageUUID = UUID.fromString(data.getString("message_id"));
}
}

if (messageUUID == null) {
messageUUID = UUID.randomUUID();
try (var statement = connection.prepareStatement("INSERT INTO `message` (`message_id`, `text`) VALUES (?, ?)")) {
statement.setString(1, messageUUID.toString());
statement.setString(2, message);
statement.execute();
}
}

var uuid = UUID.randomUUID();

try (var statement = connection.prepareStatement("INSERT INTO `message` (`message_id`, `text`) VALUES (?, ?)")) {
try (var statement = connection.prepareStatement("INSERT INTO `user_message` (`uuid`, `user_id`, `message_id`, `json_msg`) VALUES (?, ?, ?, ?)")) {
statement.setString(1, uuid.toString());
statement.setString(2, message);
statement.setString(2, player.toString());
statement.setString(3, messageUUID.toString());
statement.setString(4, json);
statement.execute();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.util.concurrent.RateLimiter;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
Expand All @@ -31,38 +30,54 @@
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
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.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;
import java.util.regex.Pattern;

public class KosuzuUnderstandsEverything implements Listener {

private final Logger logger;
private final KosuzuTranslatesEverything translator;
private final KosuzuRemembersEverything database;
private final KosuzuKnowsWhereYouLive geolocation;

private final ArrayList<Pattern> regexes = new ArrayList<>();
private final Map<String, Map<UUID, Pattern>> placeholderRegexes = new HashMap<>();

public KosuzuUnderstandsEverything(Kosuzu kosuzu) {
logger = kosuzu.getLogger();
translator = new KosuzuTranslatesEverything(kosuzu);
database = kosuzu.database;
geolocation = new KosuzuKnowsWhereYouLive(kosuzu);

ProtocolManager manager = ProtocolLibrary.getProtocolManager();
manager.addPacketListener(new PacketAdapter(kosuzu, ListenerPriority.NORMAL, PacketType.Play.Server.CHAT) {
@Override
public void onPacketSending(PacketEvent event) {
KosuzuUnderstandsEverything.this.onPacketSending(event);
KosuzuUnderstandsEverything.this.onPacketSending(event, false);
}
});

manager.addPacketListener(new PacketAdapter(kosuzu, ListenerPriority.NORMAL, PacketType.Play.Server.SYSTEM_CHAT) {
@Override
public void onPacketSending(PacketEvent event) {
KosuzuUnderstandsEverything.this.onPacketSending(event);
KosuzuUnderstandsEverything.this.onPacketSending(event, true);
}
});

prepareRegexes(kosuzu.getConfig());
}

// To be deprecated, replaced by ProtocolLib
Expand All @@ -88,12 +103,65 @@ public void onPlayerMessage(@NotNull AsyncChatEvent event) {
}

// Called by ProtocolLib
public void onPacketSending(PacketEvent event) {
private void onPacketSending(PacketEvent event, boolean isSystem) {
var player = event.getPlayer();
var packet = event.getPacket();
var message = packet.getChatComponents().read(0);
var component = JSONComponentSerializer.json().deserialize(message.getJson()); // Adventure API from raw JSON
// getLogger().info("SYSTEM CHAT EVENT TO " + player.getName() + " " + message.getJson());
// logger.info("A CHAT EVENT TO " + player.getName() + ": " + getTextMessage(component, isSystem, player));
}

private void prepareRegexes(@NotNull FileConfiguration config) {
var regexes = config.getStringList("match.include");

for (var regex : regexes) {
if (regex.contains("%username%")) {
placeholderRegexes.put(regex, new HashMap<>());
} else {
this.regexes.add(Pattern.compile(regex));
}
}

logger.info("Prepared " + this.regexes.size() + " regexes");
}

/**
* Extracts the text message from a chat component
* Also determines if we should translate the message
* @param component The chat component created from the message
* @param isSystem Whether the message is a system message
* @param player The player who sent the message
* @return The text message, or null if it could/should not be translated
*/
private @Nullable String getTextMessage(Component component, boolean isSystem, Player player) {
var text = PlainTextComponentSerializer.plainText().serialize(component);

logger.info("TEXT: " + text);

if (!isSystem) {
return text;
}

for (var pattern : regexes) {
var matcher = pattern.matcher(text);
if (matcher.matches()) {
return matcher.group(1);
}
}

for (var placeholder : placeholderRegexes.entrySet()) {
var regex = placeholder.getKey();
var cache = placeholder.getValue();

var pattern = cache.computeIfAbsent(player.getUniqueId(), (key) -> Pattern.compile(regex.replace("%username%", player.getName())));
var matcher = pattern.matcher(text);

if (matcher.matches()) {
return matcher.group(1);
}
}

return null;
}

@EventHandler(priority = EventPriority.HIGHEST)
Expand All @@ -107,7 +175,7 @@ public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
if (database.isNewUser(uuid, name)) {
String welcome;

var country = KosuzuKnowsWhereYouLive.getCountryCode(player);
var country = geolocation.getCountryCode(player);

if (country == null) {
welcome = database.getTranslation("welcome.new", null);
Expand All @@ -128,6 +196,14 @@ public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
}
}

@EventHandler
public void onPlayerDisconnect(@NotNull PlayerQuitEvent event) {
var player = event.getPlayer();

// Remove from cache
placeholderRegexes.values().forEach(map -> map.remove(player.getUniqueId()));
}

private void translateCallback(AsyncChatEvent event, Audience player) {
// var ratelimit = RateLimiter.create(2000);

Expand Down
7 changes: 4 additions & 3 deletions src/main/resources/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ CREATE TABLE IF NOT EXISTS `multilingual`
CREATE TABLE IF NOT EXISTS `message`
(
`message_id` VARCHAR(36) NOT NULL,
`text` VARCHAR(2048) NOT NULL, -- We don't explicitly know the language of the message, so we store it here
`text` VARCHAR(256) NOT NULL, -- We don't explicitly know the language of the message, so we store it here
`language` VARCHAR(8), -- ISO 639-1 code of the message, NULL if the language is unknown
PRIMARY KEY (`message_id`),
UNIQUE (`text`)
);
Expand All @@ -50,9 +51,9 @@ CREATE TABLE IF NOT EXISTS `user_message`
`uuid` VARCHAR(36) NOT NULL, -- Surrogate key
`user_id` VARCHAR(36) NOT NULL, -- To which user this message belongs
`message_id` VARCHAR(36) NOT NULL, -- To which message this translation belongs
`json_msg` JSON NOT NULL, -- The JSON message from Minecraft
PRIMARY KEY (`uuid`),

FOREIGN KEY (`uuid`) REFERENCES `user` (`uuid`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `user` (`uuid`) ON DELETE CASCADE,
FOREIGN KEY (`message_id`) REFERENCES `message` (`message_id`) ON DELETE CASCADE
);

Expand Down

0 comments on commit 153a78a

Please sign in to comment.