From f8f6fd7a65bd60d54b5d05f439d073f0e39a4966 Mon Sep 17 00:00:00 2001 From: Ilya Andreev Date: Fri, 22 Dec 2023 18:19:46 +0300 Subject: [PATCH] Implement cross-proxy messaging --- .gitignore | 3 +- .../ru/brikster/chatty/api/chat/Chat.java | 6 +- spigot/build.gradle | 8 + .../main/java/ru/brikster/chatty/Chatty.java | 6 +- .../ru/brikster/chatty/chat/ChatImpl.java | 32 +-- .../context/SinglePlayerTransformContext.java | 4 +- .../impl/LinkParserComponentTransformer.java | 2 +- .../ReplacementsComponentTransformer.java | 2 +- .../chat/executor/LegacyEventExecutor.java | 181 ++++++++------- .../stage/early/CooldownStrategy.java | 4 +- .../stage/early/SpyModeStrategy.java | 2 +- .../AdModerationStrategyModeration.java | 14 +- .../moderation/CapsModerationStrategy.java | 6 +- .../SwearModerationStrategyModeration.java | 14 +- .../middle/LinkParserTransformStrategy.java | 2 +- .../stage/post/MentionsTransformStrategy.java | 4 +- .../post/RelationalPlaceholdersStrategy.java | 4 +- .../chat/style/ChatStylePlayerGrouper.java | 27 +++ .../style/ChatStylePlayerGrouperImpl.java | 59 +++++ .../config/{type => file}/ChatsConfig.java | 2 +- .../config/{type => file}/MessagesConfig.java | 5 +- .../{type => file}/ModerationConfig.java | 2 +- .../{type => file}/NotificationsConfig.java | 2 +- .../config/{type => file}/PmConfig.java | 2 +- .../chatty/config/file/ProxyConfig.java | 71 ++++++ .../{type => file}/ReplacementsConfig.java | 2 +- .../config/{type => file}/SettingsConfig.java | 2 +- .../config/{type => file}/VanillaConfig.java | 65 +++--- .../chatty/config/serdes/SerdesChatty.java | 2 +- .../brikster/chatty/guice/ConfigsLoader.java | 4 +- .../chatty/guice/GeneralGuiceModule.java | 88 ++++++- .../brikster/chatty/misc/VanillaListener.java | 11 +- .../papi/ChattyPlaceholderApiExpansion.java | 1 + .../brikster/chatty/pm/MsgCommandHandler.java | 5 +- .../brikster/chatty/pm/PmMessageService.java | 120 +++++++--- .../pm/PrivateMessageCommandHandler.java | 51 +++-- .../pm/PrivateMessageSuggestionsProvider.java | 15 +- .../chatty/pm/ReplyCommandHandler.java | 5 +- .../pm/ignore/AddIgnoreCommandHandler.java | 16 +- .../pm/ignore/IgnoreListCommandHandler.java | 2 +- .../pm/ignore/RemoveIgnoreCommandHandler.java | 11 +- .../targets/CommandSenderPmMessageTarget.java | 48 ++++ .../chatty/pm/targets/PmMessageTarget.java | 22 ++ .../pm/targets/RemotePmMessageTarget.java | 46 ++++ .../prefix/LuckpermsPrefixProvider.java | 15 +- .../chatty/prefix/NullPrefixProvider.java | 6 +- .../chatty/prefix/PrefixProvider.java | 6 +- .../chatty/prefix/VaultPrefixProvider.java | 13 +- .../chatty/proxy/DummyProxyService.java | 49 ++++ .../brikster/chatty/proxy/ProxyService.java | 44 ++++ .../chatty/proxy/ProxyServiceImpl.java | 209 +++++++++++++++++ .../chatty/proxy/data/ChatMessage.java | 16 ++ .../brikster/chatty/proxy/data/ChatStyle.java | 9 + .../chatty/proxy/data/PrivateMessage.java | 16 ++ .../chatty/proxy/data/ProxyPlayer.java | 11 + .../player/MysqlPlayerDataRepository.java | 215 +++++++++++++++++ .../player/PlayerDataRepository.java | 1 + .../player/PostgresPlayerDataRepository.java | 216 ++++++++++++++++++ .../player/SqlitePlayerDataRepository.java | 3 +- .../ru/brikster/chatty/util/GraphUtil.java | 2 +- .../resources/db/migration/mysql/V1__init.sql | 17 ++ .../db/migration/postgres/V1__init.sql | 17 ++ .../db/migration/{ => sqlite}/V1__init.sql | 0 63 files changed, 1568 insertions(+), 277 deletions(-) create mode 100644 spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouper.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouperImpl.java rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/ChatsConfig.java (99%) rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/MessagesConfig.java (94%) rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/ModerationConfig.java (99%) rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/NotificationsConfig.java (99%) rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/PmConfig.java (98%) create mode 100644 spigot/src/main/java/ru/brikster/chatty/config/file/ProxyConfig.java rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/ReplacementsConfig.java (96%) rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/SettingsConfig.java (99%) rename spigot/src/main/java/ru/brikster/chatty/config/{type => file}/VanillaConfig.java (72%) create mode 100644 spigot/src/main/java/ru/brikster/chatty/pm/targets/CommandSenderPmMessageTarget.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/pm/targets/PmMessageTarget.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/pm/targets/RemotePmMessageTarget.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/proxy/DummyProxyService.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/proxy/ProxyService.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/proxy/ProxyServiceImpl.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatMessage.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatStyle.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/proxy/data/PrivateMessage.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/proxy/data/ProxyPlayer.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/repository/player/MysqlPlayerDataRepository.java create mode 100644 spigot/src/main/java/ru/brikster/chatty/repository/player/PostgresPlayerDataRepository.java create mode 100644 spigot/src/main/resources/db/migration/mysql/V1__init.sql create mode 100644 spigot/src/main/resources/db/migration/postgres/V1__init.sql rename spigot/src/main/resources/db/migration/{ => sqlite}/V1__init.sql (100%) diff --git a/.gitignore b/.gitignore index 0bb6d19f..0bc06482 100644 --- a/.gitignore +++ b/.gitignore @@ -106,4 +106,5 @@ build/ !gradle-wrapper.jar -spigot/run/ \ No newline at end of file +spigot/run/ +compose.yaml \ No newline at end of file diff --git a/api/src/main/java/ru/brikster/chatty/api/chat/Chat.java b/api/src/main/java/ru/brikster/chatty/api/chat/Chat.java index cdc19524..0cc93900 100644 --- a/api/src/main/java/ru/brikster/chatty/api/chat/Chat.java +++ b/api/src/main/java/ru/brikster/chatty/api/chat/Chat.java @@ -18,12 +18,12 @@ public interface Chat { /** - * Name of chat from plugin configuration + * ID of chat from plugin configuration * - * @return name of chat + * @return ID of chat */ @NotNull - String getName(); + String getId(); @NotNull String getDisplayName(); diff --git a/spigot/build.gradle b/spigot/build.gradle index 2f747f6f..a5163e64 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -19,6 +19,9 @@ tasks { relocations.forEach { relocate it, "ru.brikster.chatty.shaded.${it}" } + mergeServiceFiles { + setPath("META-INF/services/org.flywaydb.core.extensibility.Plugin") + } } build { @@ -66,6 +69,11 @@ dependencies { implementation 'com.zaxxer:HikariCP:5.1.0' implementation 'org.flywaydb:flyway-core:9.22.3' + implementation 'org.flywaydb:flyway-mysql:9.22.3' + implementation 'org.postgresql:postgresql:42.7.1' + implementation('org.redisson:redisson:3.25.1') { + exclude group: 'org.yaml' + } compileOnly 'net.milkbowl.vault:VaultAPI:1.7' compileOnly 'me.clip:placeholderapi:2.10.6' diff --git a/spigot/src/main/java/ru/brikster/chatty/Chatty.java b/spigot/src/main/java/ru/brikster/chatty/Chatty.java index 587a5460..ed9cbcbc 100644 --- a/spigot/src/main/java/ru/brikster/chatty/Chatty.java +++ b/spigot/src/main/java/ru/brikster/chatty/Chatty.java @@ -36,9 +36,9 @@ import ru.brikster.chatty.command.CommandSuggestionsProvider; import ru.brikster.chatty.command.ProxyingCommandHandler; import ru.brikster.chatty.command.ProxyingCommandSuggestionsProvider; -import ru.brikster.chatty.config.type.MessagesConfig; -import ru.brikster.chatty.config.type.PmConfig; -import ru.brikster.chatty.config.type.SettingsConfig; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.config.file.PmConfig; +import ru.brikster.chatty.config.file.SettingsConfig; import ru.brikster.chatty.guice.ConfigsLoader; import ru.brikster.chatty.guice.GeneralGuiceModule; import ru.brikster.chatty.misc.VanillaListener; diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/ChatImpl.java b/spigot/src/main/java/ru/brikster/chatty/chat/ChatImpl.java index a0e7fdff..c0d5c498 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/ChatImpl.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/ChatImpl.java @@ -28,7 +28,7 @@ public final class ChatImpl implements Chat { @Getter - private final @NotNull String name; + private final @NotNull String id; private final @Nullable String displayName; @Getter @@ -74,7 +74,7 @@ public final class ChatImpl implements Chat { @Override public @NotNull String getDisplayName() { return displayName == null - ? name + ? id : displayName; } @@ -95,33 +95,33 @@ public boolean removeStrategy(@NotNull MessageTransformStrategy strategy) { @Override public boolean hasSymbolWritePermission(Player sender) { - return sender.hasPermission("chatty.chat." + getName()) - || sender.hasPermission("chatty.chat." + getName() + ".write") - || sender.hasPermission("chatty.chat." + getName() + ".send") - || sender.hasPermission("chatty.chat." + getName() + ".write.symbol") - || sender.hasPermission("chatty.chat." + getName() + ".send.symbol"); + return sender.hasPermission("chatty.chat." + getId()) + || sender.hasPermission("chatty.chat." + getId() + ".write") + || sender.hasPermission("chatty.chat." + getId() + ".send") + || sender.hasPermission("chatty.chat." + getId() + ".write.symbol") + || sender.hasPermission("chatty.chat." + getId() + ".send.symbol"); } @Override public boolean hasCommandWritePermission(Player sender) { - return sender.hasPermission("chatty.chat." + getName()) - || sender.hasPermission("chatty.chat." + getName() + ".write") - || sender.hasPermission("chatty.chat." + getName() + ".send") - || sender.hasPermission("chatty.chat." + getName() + ".write.command") - || sender.hasPermission("chatty.chat." + getName() + ".send.command"); + return sender.hasPermission("chatty.chat." + getId()) + || sender.hasPermission("chatty.chat." + getId() + ".write") + || sender.hasPermission("chatty.chat." + getId() + ".send") + || sender.hasPermission("chatty.chat." + getId() + ".write.command") + || sender.hasPermission("chatty.chat." + getId() + ".send.command"); } @Override public boolean hasReadPermission(Player sender) { - return sender.hasPermission("chatty.chat." + getName()) - || sender.hasPermission("chatty.chat." + getName() + ".read") - || sender.hasPermission("chatty.chat." + getName() + ".see"); + return sender.hasPermission("chatty.chat." + getId()) + || sender.hasPermission("chatty.chat." + getId() + ".read") + || sender.hasPermission("chatty.chat." + getId() + ".see"); } @Override public @NotNull Predicate getRecipientPredicate(@Nullable Player sender) { return player -> { - if (player.equals(sender)) { + if (player == sender || player.equals(sender)) { return true; } diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/component/context/SinglePlayerTransformContext.java b/spigot/src/main/java/ru/brikster/chatty/chat/component/context/SinglePlayerTransformContext.java index 353a510c..3eb8ecbb 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/component/context/SinglePlayerTransformContext.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/component/context/SinglePlayerTransformContext.java @@ -2,12 +2,12 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.bukkit.entity.Player; +import org.bukkit.OfflinePlayer; @Getter @RequiredArgsConstructor(staticName = "of") public final class SinglePlayerTransformContext implements TransformContext { - private final Player player; + private final OfflinePlayer player; } diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/LinkParserComponentTransformer.java b/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/LinkParserComponentTransformer.java index ca3beb91..966ad15c 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/LinkParserComponentTransformer.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/LinkParserComponentTransformer.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; import ru.brikster.chatty.chat.component.ComponentTransformer; import ru.brikster.chatty.chat.component.context.SinglePlayerTransformContext; -import ru.brikster.chatty.config.type.SettingsConfig; +import ru.brikster.chatty.config.file.SettingsConfig; import ru.brikster.chatty.convert.component.ComponentStringConverter; import ru.brikster.chatty.util.AdventureUtil; diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/ReplacementsComponentTransformer.java b/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/ReplacementsComponentTransformer.java index 82916c4a..cf09afac 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/ReplacementsComponentTransformer.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/component/impl/ReplacementsComponentTransformer.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; import ru.brikster.chatty.Constants; import ru.brikster.chatty.chat.component.context.SinglePlayerTransformContext; -import ru.brikster.chatty.config.type.ReplacementsConfig; +import ru.brikster.chatty.config.file.ReplacementsConfig; import ru.brikster.chatty.convert.component.ComponentStringConverter; import ru.brikster.chatty.util.AdventureUtil; diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/executor/LegacyEventExecutor.java b/spigot/src/main/java/ru/brikster/chatty/chat/executor/LegacyEventExecutor.java index bbbf58f1..5728ab98 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/executor/LegacyEventExecutor.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/executor/LegacyEventExecutor.java @@ -3,6 +3,7 @@ import net.kyori.adventure.identity.Identity; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; @@ -24,12 +25,13 @@ import ru.brikster.chatty.chat.message.transform.intermediary.IntermediateMessageTransformer; import ru.brikster.chatty.chat.message.transform.processor.MessageTransformStrategiesProcessor; import ru.brikster.chatty.chat.selection.ChatSelector; -import ru.brikster.chatty.config.type.MessagesConfig; -import ru.brikster.chatty.config.type.SettingsConfig; +import ru.brikster.chatty.chat.style.ChatStylePlayerGrouper; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.config.file.SettingsConfig; +import ru.brikster.chatty.proxy.ProxyService; import javax.inject.Inject; import java.util.*; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.logging.Level; @@ -50,6 +52,8 @@ public final class LegacyEventExecutor implements Listener, EventExecutor { @Inject private MessageTransformStrategiesProcessor processor; @Inject private IntermediateMessageTransformer intermediateMessageTransformer; @Inject private Logger logger; + @Inject private ProxyService proxyService; + @Inject private ChatStylePlayerGrouper chatStylePlayerGrouper; @Override public void execute(@NotNull Listener listener, @NotNull Event event) { @@ -63,6 +67,30 @@ public void execute(@NotNull Listener listener, @NotNull Event event) { } private void onChat(AsyncPlayerChatEvent event) { + boolean processed = false; + + try { + MessageContext unhandledEarlyContext = createEarlyContext(event); + if (unhandledEarlyContext == null) return; + + MessageContext earlyContext = processor.handle(unhandledEarlyContext, Stage.EARLY).getNewContext(); + + event.getRecipients().clear(); + event.getRecipients().addAll(earlyContext.getRecipients()); + event.setMessage(earlyContext.getMessage()); + pendingMessages.put(System.identityHashCode(event), earlyContext); + + processed = true; + } catch (Throwable t) { + logger.log(Level.SEVERE, "Cannot handle chat event", t); + } finally { + if (!processed) { + event.setCancelled(true); + } + } + } + + private MessageContext createEarlyContext(AsyncPlayerChatEvent event) { Chat chat = selector.selectChat(event.getMessage(), chatCandidate -> !chatCandidate.isPermissionRequired() || chatCandidate.hasSymbolWritePermission(event.getPlayer())); @@ -71,7 +99,7 @@ private void onChat(AsyncPlayerChatEvent event) { audiences.player(event.getPlayer().getUniqueId()) .sendMessage(messages.getChatNotFound()); event.setCancelled(true); - return; + return null; } List recipients; @@ -84,7 +112,7 @@ private void onChat(AsyncPlayerChatEvent event) { recipients = new ArrayList<>(chat.calculateRecipients(event.getPlayer())); } - MessageContext context = new MessageContextImpl<>( + return new MessageContextImpl<>( chat, event.getPlayer(), new HashMap<>(), @@ -93,25 +121,6 @@ private void onChat(AsyncPlayerChatEvent event) { recipients, event.getMessage(), null); - - boolean processed = false; - - try { - MessageContext earlyContext = processor.handle(context, Stage.EARLY).getNewContext(); - - event.getRecipients().clear(); - event.getRecipients().addAll(earlyContext.getRecipients()); - event.setMessage(earlyContext.getMessage()); - pendingMessages.put(System.identityHashCode(event), earlyContext); - - processed = true; - } catch (Throwable t) { - logger.log(Level.SEVERE, "Cannot handle chat event", t); - } finally { - if (!processed) { - event.setCancelled(true); - } - } } @EventHandler(priority = EventPriority.MONITOR) @@ -138,6 +147,12 @@ public void handleFinishedEarlyContextEvent(AsyncPlayerChatEvent event) { try { MessageContext earlyComponentContext = intermediateMessageTransformer.handle(earlyContext).getNewContext(); + + if (PlainTextComponentSerializer.plainText().serialize(earlyComponentContext.getMessage()).isBlank()) { + // will be cancelled in finally block + return; + } + MessageContext middleContext = processor.handle(earlyComponentContext, Stage.MIDDLE).getNewContext(); ChattyMessageEvent messageEvent = new ChattyMessageEvent( @@ -157,6 +172,10 @@ public void handleFinishedEarlyContextEvent(AsyncPlayerChatEvent event) { } } + if (middleContext.getChat().getRange() <= -3) { + sendProxyMessage(middleContext); + } + List> groupedByStyle = groupedByStyle(middleContext); for (int groupIndex = 0; groupIndex < groupedByStyle.size(); groupIndex++) { MessageContext groupContext = groupedByStyle.get(groupIndex); @@ -177,24 +196,7 @@ public void handleFinishedEarlyContextEvent(AsyncPlayerChatEvent event) { } } - if (middleContext.getChat().isSendNobodyHeardYou()) { - Set allowedRecipients = new HashSet<>(); - allowedRecipients.add(event.getPlayer()); - - if (middleContext.getChat().isEnableSpy() && middleContext.getMetadata().containsKey("spy-recipients")) { - //noinspection unchecked - allowedRecipients.addAll((List) middleContext.getMetadata().get("spy-recipients")); - } - - if (settings.isHideVanishedRecipients()) { - allowedRecipients.removeIf(player -> player != event.getPlayer() - && !event.getPlayer().canSee(player)); - } - - if (allowedRecipients.containsAll(middleContext.getRecipients())) { - audiences.player(event.getPlayer()).sendMessage(messages.getNobodyHeard()); - } - } + sendNobodyHeardYou(event, middleContext); processed = true; } catch (Throwable t) { @@ -206,53 +208,74 @@ public void handleFinishedEarlyContextEvent(AsyncPlayerChatEvent event) { } } - private List> groupedByStyle(MessageContext context) { - Map playerStyleMap = new HashMap<>(); - - for (ChatStyle style : context.getChat().getStyles()) { - for (Player recipient : context.getRecipients()) { - ChatStyle currentStyle = playerStyleMap.get(recipient); - if (currentStyle == null || style.priority() > currentStyle.priority()) { - if (recipient.hasPermission("chatty.style." + style.id())) { - playerStyleMap.put(recipient, style); - } - } - } + private void sendProxyMessage(MessageContext middleContext) { + Chat chat = middleContext.getChat(); + + Component noStyleProxyMessage; + Map proxyStyles = new HashMap<>(); + + MessageContext proxyNoStyleContext = new MessageContextImpl<>(middleContext); + proxyNoStyleContext.setFormat(chat.getFormat()); + proxyNoStyleContext.setMessage(middleContext.getMessage()); + proxyNoStyleContext.setRecipients(Collections.emptyList()); + MessageContext proxyNoStyleLateContext = processor.handle(proxyNoStyleContext, Stage.LATE).getNewContext(); + noStyleProxyMessage = componentFromContextConstructor.construct(proxyNoStyleLateContext).compact(); + + for (var style : chat.getStyles()) { + MessageContext proxyStyleContext = new MessageContextImpl<>(middleContext); + proxyStyleContext.setFormat(style.format()); + proxyStyleContext.setMessage(middleContext.getMessage()); + proxyStyleContext.setRecipients(Collections.emptyList()); + MessageContext proxyLateContext = processor.handle(proxyStyleContext, Stage.LATE).getNewContext(); + Component message = componentFromContextConstructor.construct(proxyLateContext).compact(); + + proxyStyles.put(style.id(), new ru.brikster.chatty.proxy.data.ChatStyle( + style.priority(), + GsonComponentSerializer.gson().serialize(message) + )); } - if (context.getChat().isEnableSpy() && context.getMetadata().containsKey("spy-recipients")) { - @SuppressWarnings("unchecked") - List players = (List) context.getMetadata().get("spy-recipients"); + proxyService.sendChatMessage(chat, noStyleProxyMessage, proxyStyles, chat.getSound()); + } - ChatStyle spyStyle = new ChatStyle( - null, - context.getChat().getSpyFormat(), - Integer.MAX_VALUE); + private void sendNobodyHeardYou(AsyncPlayerChatEvent event, MessageContext middleContext) { + if (middleContext.getChat().isSendNobodyHeardYou()) { + Set allowedRecipients = new HashSet<>(); + allowedRecipients.add(event.getPlayer()); - players.forEach(spyPlayer -> playerStyleMap.put(spyPlayer, spyStyle)); - } + if (middleContext.getChat().isEnableSpy() && middleContext.getMetadata().containsKey("spy-recipients")) { + //noinspection unchecked + allowedRecipients.addAll((List) middleContext.getMetadata().get("spy-recipients")); + } - Map> stylePlayersMap = new HashMap<>(); + if (settings.isHideVanishedRecipients()) { + allowedRecipients.removeIf(player -> player != event.getPlayer() + && !event.getPlayer().canSee(player)); + } - for (Entry entry : playerStyleMap.entrySet()) { - stylePlayersMap.compute(entry.getValue(), (k, v) -> { - List players = v; - if (players == null) { - players = new ArrayList<>(); - } - players.add(entry.getKey()); - return players; - }); + if (allowedRecipients.containsAll(middleContext.getRecipients())) { + audiences.player(event.getPlayer()).sendMessage(messages.getNobodyHeard()); + } } + } + + private List> groupedByStyle(MessageContext context) { + Chat chat = context.getChat(); + boolean useSpy = chat.isEnableSpy() && context.getMetadata().containsKey("spy-recipients"); + + //noinspection unchecked + var grouping = chatStylePlayerGrouper.makeGrouping(context.getRecipients(), chat.getStyles(), + useSpy ? (List) context.getMetadata().get("spy-recipients") : null, + useSpy ? new ChatStyle( + "internal-spy-style", + chat.getSpyFormat(), + Integer.MAX_VALUE) : null); + + Map> stylePlayersMap = grouping.getStylesMap(); List> contexts = new ArrayList<>(); - List noStyleRecipients = new ArrayList<>(); - for (Player recipient : context.getRecipients()) { - if (!playerStyleMap.containsKey(recipient)) { - noStyleRecipients.add(recipient); - } - } + List noStyleRecipients = grouping.getNoStylePlayers(); MessageContext noStyleContext = new MessageContextImpl<>(context); noStyleContext.setMessage(context.getMessage()); diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/CooldownStrategy.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/CooldownStrategy.java index edbe5829..5c1e6373 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/CooldownStrategy.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/CooldownStrategy.java @@ -10,7 +10,7 @@ import ru.brikster.chatty.api.chat.message.strategy.MessageTransformStrategy; import ru.brikster.chatty.api.chat.message.strategy.result.MessageTransformResult; import ru.brikster.chatty.chat.message.transform.result.MessageTransformResultBuilder; -import ru.brikster.chatty.config.type.MessagesConfig; +import ru.brikster.chatty.config.file.MessagesConfig; import ru.brikster.chatty.util.AdventureUtil; import javax.inject.Inject; @@ -28,7 +28,7 @@ public final class CooldownStrategy implements MessageTransformStrategy @SneakyThrows @Override public @NotNull MessageTransformResult handle(MessageContext context) { - String chatName = context.getChat().getName(); + String chatName = context.getChat().getId(); if (context.getChat().getCooldown() > 0 && !context.getSender().hasPermission("chatty.cooldown." + chatName)) { diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/SpyModeStrategy.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/SpyModeStrategy.java index fafe4c61..96bec8a9 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/SpyModeStrategy.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/SpyModeStrategy.java @@ -30,7 +30,7 @@ public final class SpyModeStrategy implements MessageTransformStrategy { List spies = new ArrayList<>(); if (context.getChat().isEnableSpy()) { for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - if (onlinePlayer.hasPermission("chatty.spy." + context.getChat().getName()) + if (onlinePlayer.hasPermission("chatty.spy." + context.getChat().getId()) && !recipients.contains(onlinePlayer)) { recipients.add(onlinePlayer); spies.add(onlinePlayer); diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/AdModerationStrategyModeration.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/AdModerationStrategyModeration.java index 61a9c148..db7d8d21 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/AdModerationStrategyModeration.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/AdModerationStrategyModeration.java @@ -5,9 +5,9 @@ import org.jetbrains.annotations.NotNull; import ru.brikster.chatty.api.chat.message.context.MessageContext; import ru.brikster.chatty.api.chat.message.strategy.result.MessageTransformResult; -import ru.brikster.chatty.config.type.MessagesConfig; -import ru.brikster.chatty.config.type.ModerationConfig; -import ru.brikster.chatty.config.type.ModerationConfig.AdvertisementModerationConfig; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.config.file.ModerationConfig; +import ru.brikster.chatty.config.file.ModerationConfig.AdvertisementModerationConfig; import javax.inject.Inject; import java.util.Set; @@ -61,15 +61,15 @@ public AdModerationStrategyModeration(BukkitAudiences audiences, MessagesConfig private @NotNull String match(@NotNull String message, @NotNull Pattern pattern) { Matcher matcher = pattern.matcher(message); - StringBuffer buffer = new StringBuffer(); + StringBuilder builder = new StringBuilder(); while (matcher.find()) { if (!this.whitelist.contains(matcher.group().trim())) { - matcher.appendReplacement(buffer, replacement); + matcher.appendReplacement(builder, replacement); } } - matcher.appendTail(buffer); + matcher.appendTail(builder); - return buffer.toString(); + return builder.toString(); } } diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/CapsModerationStrategy.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/CapsModerationStrategy.java index 1ef15a12..3ee0d8e0 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/CapsModerationStrategy.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/CapsModerationStrategy.java @@ -7,9 +7,9 @@ import ru.brikster.chatty.api.chat.message.strategy.MessageTransformStrategy; import ru.brikster.chatty.api.chat.message.strategy.result.MessageTransformResult; import ru.brikster.chatty.chat.message.transform.result.MessageTransformResultBuilder; -import ru.brikster.chatty.config.type.MessagesConfig; -import ru.brikster.chatty.config.type.ModerationConfig; -import ru.brikster.chatty.config.type.ModerationConfig.CapsModerationConfig; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.config.file.ModerationConfig; +import ru.brikster.chatty.config.file.ModerationConfig.CapsModerationConfig; import javax.inject.Inject; diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/SwearModerationStrategyModeration.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/SwearModerationStrategyModeration.java index 998d8266..0d5fa784 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/SwearModerationStrategyModeration.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/early/moderation/SwearModerationStrategyModeration.java @@ -6,9 +6,9 @@ import org.jetbrains.annotations.Nullable; import ru.brikster.chatty.api.chat.message.context.MessageContext; import ru.brikster.chatty.api.chat.message.strategy.result.MessageTransformResult; -import ru.brikster.chatty.config.type.MessagesConfig; -import ru.brikster.chatty.config.type.ModerationConfig; -import ru.brikster.chatty.config.type.ModerationConfig.SwearModerationConfig; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.config.file.ModerationConfig; +import ru.brikster.chatty.config.file.ModerationConfig.SwearModerationConfig; import ru.brikster.chatty.repository.swear.SwearRepository; import javax.inject.Inject; @@ -72,15 +72,15 @@ public SwearModerationStrategyModeration(BukkitAudiences audiences, MessagesConf Matcher matcher = pattern.matcher(message); - StringBuffer buffer = new StringBuffer(); + StringBuilder builder = new StringBuilder(); while (matcher.find()) { if (!swearRepository.getWhitelist().contains(matcher.group().trim())) { - matcher.appendReplacement(buffer, replacement); + matcher.appendReplacement(builder, replacement); } } - matcher.appendTail(buffer); + matcher.appendTail(builder); - return buffer.toString(); + return builder.toString(); } } diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/middle/LinkParserTransformStrategy.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/middle/LinkParserTransformStrategy.java index b1617c2a..52659882 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/middle/LinkParserTransformStrategy.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/middle/LinkParserTransformStrategy.java @@ -8,7 +8,7 @@ import ru.brikster.chatty.chat.component.context.SinglePlayerTransformContext; import ru.brikster.chatty.chat.component.impl.LinkParserComponentTransformer; import ru.brikster.chatty.chat.message.transform.result.MessageTransformResultBuilder; -import ru.brikster.chatty.config.type.SettingsConfig; +import ru.brikster.chatty.config.file.SettingsConfig; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/MentionsTransformStrategy.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/MentionsTransformStrategy.java index afd03727..b0027ec6 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/MentionsTransformStrategy.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/MentionsTransformStrategy.java @@ -16,8 +16,8 @@ import ru.brikster.chatty.chat.component.impl.PlaceholdersComponentTransformer; import ru.brikster.chatty.chat.component.impl.RelationalPlaceholdersComponentTransformer; import ru.brikster.chatty.chat.message.transform.result.MessageTransformResultBuilder; -import ru.brikster.chatty.config.type.SettingsConfig; -import ru.brikster.chatty.config.type.SettingsConfig.RelationalPlaceholdersOrder; +import ru.brikster.chatty.config.file.SettingsConfig; +import ru.brikster.chatty.config.file.SettingsConfig.RelationalPlaceholdersOrder; import ru.brikster.chatty.convert.component.ComponentStringConverter; import javax.inject.Inject; diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/RelationalPlaceholdersStrategy.java b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/RelationalPlaceholdersStrategy.java index 8fc0e9b0..0d6c0b53 100644 --- a/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/RelationalPlaceholdersStrategy.java +++ b/spigot/src/main/java/ru/brikster/chatty/chat/message/transform/stage/post/RelationalPlaceholdersStrategy.java @@ -4,8 +4,8 @@ import ru.brikster.chatty.chat.component.context.TwoPlayersTransformContext; import ru.brikster.chatty.chat.component.impl.RelationalPlaceholdersComponentTransformer; import ru.brikster.chatty.chat.message.transform.AbstractComponentTransformerStrategy; -import ru.brikster.chatty.config.type.SettingsConfig; -import ru.brikster.chatty.config.type.SettingsConfig.RelationalPlaceholdersOrder; +import ru.brikster.chatty.config.file.SettingsConfig; +import ru.brikster.chatty.config.file.SettingsConfig.RelationalPlaceholdersOrder; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouper.java b/spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouper.java new file mode 100644 index 00000000..9c3dd07f --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouper.java @@ -0,0 +1,27 @@ +package ru.brikster.chatty.chat.style; + +import lombok.Value; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.brikster.chatty.api.chat.ChatStyle; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface ChatStylePlayerGrouper { + + @Value + class Groping { + List noStylePlayers; + Map> stylesMap; + } + + @NotNull ChatStylePlayerGrouper.Groping makeGrouping(@NotNull Collection recipients, + @NotNull Set<@NotNull ChatStyle> styles, + @Nullable Collection spies, + @Nullable ChatStyle spyStyle); + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouperImpl.java b/spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouperImpl.java new file mode 100644 index 00000000..b581dc1c --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/chat/style/ChatStylePlayerGrouperImpl.java @@ -0,0 +1,59 @@ +package ru.brikster.chatty.chat.style; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.brikster.chatty.api.chat.ChatStyle; + +import javax.inject.Singleton; +import java.util.*; + +@Singleton +public final class ChatStylePlayerGrouperImpl implements ChatStylePlayerGrouper { + + @Override + public @NotNull ChatStylePlayerGrouper.Groping makeGrouping(@NotNull Collection recipients, + @NotNull Set<@NotNull ChatStyle> styles, + @Nullable Collection spies, + @Nullable ChatStyle spyStyle) { + Map playerStyleMap = new HashMap<>(); + + for (ChatStyle style : styles) { + for (Player recipient : recipients) { + ChatStyle currentStyle = playerStyleMap.get(recipient); + if (currentStyle == null || style.priority() > currentStyle.priority()) { + if (recipient.hasPermission("chatty.style." + style.id())) { + playerStyleMap.put(recipient, style); + } + } + } + } + + if (spies != null) { + spies.forEach(spyPlayer -> playerStyleMap.put(spyPlayer, spyStyle)); + } + + Map> stylePlayersMap = new HashMap<>(); + + for (var entry : playerStyleMap.entrySet()) { + stylePlayersMap.compute(entry.getValue(), (k, v) -> { + List players = v; + if (players == null) { + players = new ArrayList<>(); + } + players.add(entry.getKey()); + return players; + }); + } + + List noStyleRecipients = new ArrayList<>(); + for (Player recipient : recipients) { + if (!playerStyleMap.containsKey(recipient)) { + noStyleRecipients.add(recipient); + } + } + + return new Groping(noStyleRecipients, stylePlayersMap); + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/ChatsConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/ChatsConfig.java similarity index 99% rename from spigot/src/main/java/ru/brikster/chatty/config/type/ChatsConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/ChatsConfig.java index 580d05e9..a8d1b558 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/ChatsConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/ChatsConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.*; diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/MessagesConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/MessagesConfig.java similarity index 94% rename from spigot/src/main/java/ru/brikster/chatty/config/type/MessagesConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/MessagesConfig.java index dd04b728..d3176de4 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/MessagesConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/MessagesConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.*; @@ -36,8 +36,9 @@ public class MessagesConfig extends OkaeriConfig { @Comment({"", "Messages for PM"}) private Component pmPlayerNotFound = MINI_MESSAGE.deserialize("Player not found."); private Component pmNobodyToReply = MINI_MESSAGE.deserialize("Nobody to reply."); - private Component pmCannotPmYourself = MINI_MESSAGE.deserialize("You cannot pm yourself."); + private Component pmCannotPmYourself = MINI_MESSAGE.deserialize("You cannot PM yourself."); private Component pmYouNowIgnore = MINI_MESSAGE.deserialize("You now ignore this player."); + private Component pmCannotIgnoreYourself = MINI_MESSAGE.deserialize("You cannot ignore yourself."); private Component pmYouAlreadyIgnore = MINI_MESSAGE.deserialize("You already ignore this player."); private Component pmYouDontNowIgnore = MINI_MESSAGE.deserialize("You now don't ignore this player."); private Component pmYouDontIgnore = MINI_MESSAGE.deserialize("You don't ignore this player."); diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/ModerationConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/ModerationConfig.java similarity index 99% rename from spigot/src/main/java/ru/brikster/chatty/config/type/ModerationConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/ModerationConfig.java index f1b8e966..c92a6bc5 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/ModerationConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/ModerationConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import com.google.common.collect.Sets; import eu.okaeri.configs.OkaeriConfig; diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/NotificationsConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/NotificationsConfig.java similarity index 99% rename from spigot/src/main/java/ru/brikster/chatty/config/type/NotificationsConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/NotificationsConfig.java index 29698b03..b3130a35 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/NotificationsConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/NotificationsConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import com.google.common.collect.Lists; import eu.okaeri.configs.OkaeriConfig; diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/PmConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/PmConfig.java similarity index 98% rename from spigot/src/main/java/ru/brikster/chatty/config/type/PmConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/PmConfig.java index 871488bd..8a5853a2 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/PmConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/PmConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.*; diff --git a/spigot/src/main/java/ru/brikster/chatty/config/file/ProxyConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/ProxyConfig.java new file mode 100644 index 00000000..7a4379ad --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/ProxyConfig.java @@ -0,0 +1,71 @@ +package ru.brikster.chatty.config.file; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import ru.brikster.chatty.BuildConstants; + +@Getter +@SuppressWarnings("FieldMayBeFinal") +@Header("################################################################") +@Header("#") +@Header("# Chatty (version " + BuildConstants.VERSION + ")") +@Header("# Author: Brikster") +@Header("#") +@Header("################################################################") +@Names(strategy = NameStrategy.HYPHEN_CASE, modifier = NameModifier.TO_LOWER_CASE) +public class ProxyConfig extends OkaeriConfig { + + @Comment({"", "Enable support for cross-server messaging?"}) + @Comment("This feature supports any proxy (BungeeCord, Velocity), ") + @Comment("including multi-proxy networks.") + @Comment("You need to setup Redis and shared database to use it") + private boolean enable = false; + + @Comment({"", "Basic Redis configuration."}) + private RedisConfig redisConfig = new RedisConfig(); + + @Comment({"", "Enable external Redis configuration for advanced setup."}) + @Comment("File with name \"redis_config.json\" will be created.") + @Comment("See \"Redisson\" library documentation for details.") + private boolean useExternalRedisConfig = false; + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Names(strategy = NameStrategy.HYPHEN_CASE, modifier = NameModifier.TO_LOWER_CASE) + public static final class RedisConfig extends OkaeriConfig { + + private String address = "redis://localhost:6379"; + private String username = ""; + private String password = ""; + + } + + @Comment({"", "Shared database configuration."}) + @Comment("Possible types: POSTGRESQL, MYSQL.") + @Comment("Default port for PostgreSQL: 5432, for MySQL: 3306.") + private DatabaseConfig databaseConfig = new DatabaseConfig(); + + @Getter + @AllArgsConstructor + @NoArgsConstructor + @Names(strategy = NameStrategy.HYPHEN_CASE, modifier = NameModifier.TO_LOWER_CASE) + public static final class DatabaseConfig extends OkaeriConfig { + + public enum DatasourceType { + MYSQL, POSTGRESQL + } + + private DatasourceType type = DatasourceType.MYSQL; + private String hostname = "localhost"; + private int port = 3306; + private String database = "app"; + private String username = "app"; + private String password = "12345"; + + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/ReplacementsConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/ReplacementsConfig.java similarity index 96% rename from spigot/src/main/java/ru/brikster/chatty/config/type/ReplacementsConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/ReplacementsConfig.java index 947bf719..8def88a4 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/ReplacementsConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/ReplacementsConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.*; diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/SettingsConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/SettingsConfig.java similarity index 99% rename from spigot/src/main/java/ru/brikster/chatty/config/type/SettingsConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/SettingsConfig.java index e3284faa..ac8752ca 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/SettingsConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/SettingsConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.*; diff --git a/spigot/src/main/java/ru/brikster/chatty/config/type/VanillaConfig.java b/spigot/src/main/java/ru/brikster/chatty/config/file/VanillaConfig.java similarity index 72% rename from spigot/src/main/java/ru/brikster/chatty/config/type/VanillaConfig.java rename to spigot/src/main/java/ru/brikster/chatty/config/file/VanillaConfig.java index 8c66b31d..84e9d2fc 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/type/VanillaConfig.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/file/VanillaConfig.java @@ -1,4 +1,4 @@ -package ru.brikster.chatty.config.type; +package ru.brikster.chatty.config.file; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.*; @@ -8,7 +8,6 @@ import net.kyori.adventure.sound.Sound.Source; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import ru.brikster.chatty.BuildConstants; import java.util.HashMap; @@ -128,37 +127,37 @@ public static class DeathVanillaConfig extends OkaeriConfig { @Comment("See keys for \"causes\" section here: ") @Comment("https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html") private String fallbackCause = "killed by something strange"; - private Map causes = new HashMap<>() {{ - put(DamageCause.BLOCK_EXPLOSION, "caught in block explosion"); - put(DamageCause.CONTACT, "pricked by cactus, stalagmite, or berry bush"); - put(DamageCause.CRAMMING, "crammed by too many entities"); - put(DamageCause.DRAGON_BREATH, "harmed by dragon's breath"); - put(DamageCause.DROWNING, "drowned underwater"); - put(DamageCause.DRYOUT, "dried out outside water"); - put(DamageCause.ENTITY_ATTACK, "attacked by an entity"); - put(DamageCause.ENTITY_EXPLOSION, "caught in entity explosion"); - put(DamageCause.ENTITY_SWEEP_ATTACK, "hit by sweep attack"); - put(DamageCause.FALL, "fell from a height"); - put(DamageCause.FALLING_BLOCK, "hit by a falling block"); - put(DamageCause.FIRE, "burned in fire"); - put(DamageCause.FIRE_TICK, "suffered from fire burns"); - put(DamageCause.FLY_INTO_WALL, "flew into a wall"); - put(DamageCause.FREEZE, "froze to death"); - put(DamageCause.HOT_FLOOR, "stepped on a hot floor"); - put(DamageCause.LAVA, "swam in lava"); - put(DamageCause.LIGHTNING, "struck by lightning"); - put(DamageCause.MAGIC, "hit by a magic potion or spell"); - put(DamageCause.MELTING, "melted away"); - put(DamageCause.POISON, "poisoned"); - put(DamageCause.PROJECTILE, "hit by a projectile"); - put(DamageCause.SONIC_BOOM, "hit by Warden's sonic boom"); - put(DamageCause.STARVATION, "starved to death"); - put(DamageCause.SUFFOCATION, "suffocated in a block"); - put(DamageCause.SUICIDE, "committed suicide"); - put(DamageCause.THORNS, "harmed by Thorns enchantment"); - put(DamageCause.VOID, "fell into the void"); - put(DamageCause.WITHER, "withered away"); - put(DamageCause.CUSTOM, "killed by something strange"); + private Map causes = new HashMap<>() {{ + put("BLOCK_EXPLOSION", "caught in block explosion"); + put("CONTACT", "pricked by cactus, stalagmite, or berry bush"); + put("CRAMMING", "crammed by too many entities"); + put("DRAGON_BREATH", "harmed by dragon's breath"); + put("DROWNING", "drowned underwater"); + put("DRYOUT", "dried out outside water"); + put("ENTITY_ATTACK", "attacked by an entity"); + put("ENTITY_EXPLOSION", "caught in entity explosion"); + put("ENTITY_SWEEP_ATTACK", "hit by sweep attack"); + put("FALL", "fell from a height"); + put("FALLING_BLOCK", "hit by a falling block"); + put("FIRE", "burned in fire"); + put("FIRE_TICK", "suffered from fire burns"); + put("FLY_INTO_WALL", "flew into a wall"); + put("FREEZE", "froze to death"); + put("HOT_FLOOR", "stepped on a hot floor"); + put("LAVA", "swam in lava"); + put("LIGHTNING", "struck by lightning"); + put("MAGIC", "hit by a magic potion or spell"); + put("MELTING", "melted away"); + put("POISON", "poisoned"); + put("PROJECTILE", "hit by a projectile"); + put("SONIC_BOOM", "hit by Warden's sonic boom"); + put("STARVATION", "starved to death"); + put("SUFFOCATION", "suffocated in a block"); + put("SUICIDE", "committed suicide"); + put("THORNS", "harmed by Thorns enchantment"); + put("VOID", "fell into the void"); + put("WITHER", "withered away"); + put("CUSTOM", "killed by something strange"); }}; @Comment diff --git a/spigot/src/main/java/ru/brikster/chatty/config/serdes/SerdesChatty.java b/spigot/src/main/java/ru/brikster/chatty/config/serdes/SerdesChatty.java index 42556e26..1d6b4c30 100644 --- a/spigot/src/main/java/ru/brikster/chatty/config/serdes/SerdesChatty.java +++ b/spigot/src/main/java/ru/brikster/chatty/config/serdes/SerdesChatty.java @@ -8,7 +8,7 @@ import ru.brikster.chatty.config.serdes.serializer.adventure.SoundSerializer; import ru.brikster.chatty.convert.component.ComponentStringConverter; -public class SerdesChatty implements OkaeriSerdesPack { +public final class SerdesChatty implements OkaeriSerdesPack { private final ComponentStringConverter converter; diff --git a/spigot/src/main/java/ru/brikster/chatty/guice/ConfigsLoader.java b/spigot/src/main/java/ru/brikster/chatty/guice/ConfigsLoader.java index 8c03d237..94c8ed6c 100644 --- a/spigot/src/main/java/ru/brikster/chatty/guice/ConfigsLoader.java +++ b/spigot/src/main/java/ru/brikster/chatty/guice/ConfigsLoader.java @@ -7,8 +7,8 @@ import ru.brikster.chatty.chat.ChatImpl; import ru.brikster.chatty.chat.component.impl.PlaceholdersComponentTransformer; import ru.brikster.chatty.chat.registry.ChatRegistry; -import ru.brikster.chatty.config.type.ChatsConfig; -import ru.brikster.chatty.config.type.NotificationsConfig; +import ru.brikster.chatty.config.file.ChatsConfig; +import ru.brikster.chatty.config.file.NotificationsConfig; import ru.brikster.chatty.convert.component.ComponentStringConverter; import ru.brikster.chatty.notification.ActionbarNotification; import ru.brikster.chatty.notification.ChatNotification; diff --git a/spigot/src/main/java/ru/brikster/chatty/guice/GeneralGuiceModule.java b/spigot/src/main/java/ru/brikster/chatty/guice/GeneralGuiceModule.java index 3eb2826a..9dbf6862 100644 --- a/spigot/src/main/java/ru/brikster/chatty/guice/GeneralGuiceModule.java +++ b/spigot/src/main/java/ru/brikster/chatty/guice/GeneralGuiceModule.java @@ -13,6 +13,8 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; +import org.redisson.config.Config; +import org.redisson.config.SingleServerConfig; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions.ScalarStyle; import org.yaml.snakeyaml.LoaderOptions; @@ -59,8 +61,11 @@ import ru.brikster.chatty.chat.registry.MemoryChatRegistry; import ru.brikster.chatty.chat.selection.ChatSelector; import ru.brikster.chatty.chat.selection.ChatSelectorImpl; +import ru.brikster.chatty.chat.style.ChatStylePlayerGrouper; +import ru.brikster.chatty.chat.style.ChatStylePlayerGrouperImpl; +import ru.brikster.chatty.config.file.*; +import ru.brikster.chatty.config.file.ProxyConfig.DatabaseConfig.DatasourceType; import ru.brikster.chatty.config.serdes.SerdesChatty; -import ru.brikster.chatty.config.type.*; import ru.brikster.chatty.convert.component.ComponentStringConverter; import ru.brikster.chatty.convert.component.InternalMiniMessageStringConverter; import ru.brikster.chatty.convert.message.LegacyToMiniMessageConverter; @@ -71,7 +76,12 @@ import ru.brikster.chatty.prefix.NullPrefixProvider; import ru.brikster.chatty.prefix.PrefixProvider; import ru.brikster.chatty.prefix.VaultPrefixProvider; +import ru.brikster.chatty.proxy.DummyProxyService; +import ru.brikster.chatty.proxy.ProxyService; +import ru.brikster.chatty.proxy.ProxyServiceImpl; +import ru.brikster.chatty.repository.player.MysqlPlayerDataRepository; import ru.brikster.chatty.repository.player.PlayerDataRepository; +import ru.brikster.chatty.repository.player.PostgresPlayerDataRepository; import ru.brikster.chatty.repository.player.SqlitePlayerDataRepository; import ru.brikster.chatty.repository.swear.FileSwearRepository; import ru.brikster.chatty.repository.swear.SwearRepository; @@ -79,8 +89,11 @@ import ru.brikster.chatty.util.GraphUtil.CycleAnalysisResult; import javax.inject.Singleton; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; @@ -116,8 +129,6 @@ protected void configure() { bind(Plugin.class).toInstance(plugin); bind(ChatRegistry.class).toInstance(chatRegistry); - bind(PlayerDataRepository.class).toInstance(new SqlitePlayerDataRepository(dataFolderPath)); - bind(MessageTransformStrategiesProcessor.class).to(MessageTransformStrategiesProcessorImpl.class); bind(ComponentStringConverter.class).toInstance(internalMiniMessageStringConverter); bind(MessageConverter.class).to(LegacyToMiniMessageConverter.class); @@ -138,8 +149,20 @@ protected void configure() { bind(ModerationConfig.class).toInstance(moderationConfig); bind(NotificationsConfig.class).toInstance(createConfig(NotificationsConfig.class, "notifications.yml")); bind(ReplacementsConfig.class).toInstance(createConfig(ReplacementsConfig.class, "replacements.yml")); + ProxyConfig proxyConfig = createConfig(ProxyConfig.class, "proxy.yml"); + bind(ProxyConfig.class).toInstance(proxyConfig); + bind(ChatStylePlayerGrouper.class).to(ChatStylePlayerGrouperImpl.class); + + if (proxyConfig.isEnable()) { + setupRedis(proxyConfig); + setupSharedDatabase(proxyConfig); + bind(ProxyService.class).to(ProxyServiceImpl.class); + } else { + bind(PlayerDataRepository.class).toInstance(new SqlitePlayerDataRepository(dataFolderPath)); + bind(ProxyService.class).to(DummyProxyService.class); + } - Multibinder> strategyMultibinder = Multibinder.newSetBinder(binder(), new TypeLiteral>() {}); + Multibinder> strategyMultibinder = Multibinder.newSetBinder(binder(), new TypeLiteral<>() {}); // Early strategyMultibinder.addBinding().to(RemoveChatSymbolStrategy.class); strategyMultibinder.addBinding().to(SpyModeStrategy.class); @@ -173,14 +196,65 @@ protected void configure() { bind(IntermediateMessageTransformer.class).to(IntermediateMessageTransformerImpl.class); } + private void setupRedis(ProxyConfig proxyConfig) { + Config redisConfig; + if (proxyConfig.isUseExternalRedisConfig()) { + Path redisConfigPath = dataFolderPath.resolve("redis_config.json"); + if (Files.exists(redisConfigPath)) { + try { + //noinspection deprecation + redisConfig = Config.fromJSON(Files.newBufferedReader(redisConfigPath)); + } catch (IOException e) { + throw new IllegalStateException("Cannot read redis_config.json", e); + } + } else { + redisConfig = new Config(); + redisConfig.useSingleServer() + .setAddress("redis://localhost:6379"); + try { + //noinspection deprecation + Files.writeString(redisConfigPath, redisConfig.toJSON(), StandardCharsets.UTF_8, + StandardOpenOption.WRITE, StandardOpenOption.CREATE); + } catch (IOException e) { + throw new IllegalStateException("Cannot write redis_config.json", e); + } + } + } else { + redisConfig = new Config(); + SingleServerConfig singleServerConfig = redisConfig.useSingleServer(); + singleServerConfig.setAddress(proxyConfig.getRedisConfig().getAddress()); + if (proxyConfig.getRedisConfig().getUsername() != null && !proxyConfig.getRedisConfig().getUsername().isBlank()) { + singleServerConfig + .setUsername(proxyConfig.getRedisConfig().getUsername()) + .setPassword(proxyConfig.getRedisConfig().getPassword()); + } + } + + bind(Config.class).toInstance(redisConfig); + } + + private void setupSharedDatabase(ProxyConfig proxyConfig) { + if (proxyConfig.getDatabaseConfig().getType() == DatasourceType.POSTGRESQL) { + bind(PlayerDataRepository.class) + .toInstance(new PostgresPlayerDataRepository(proxyConfig.getDatabaseConfig())); + } else if (proxyConfig.getDatabaseConfig().getType() == DatasourceType.MYSQL) { + bind(PlayerDataRepository.class) + .toInstance(new MysqlPlayerDataRepository(proxyConfig.getDatabaseConfig())); + } else { + throw new IllegalArgumentException(proxyConfig.getDatabaseConfig().getType() + " database is not implemented yet"); + } + } + @Provides @Singleton - public PrefixProvider prefixProvider() { - if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) { + public PrefixProvider prefixProvider(ProxyConfig proxyConfig) { + boolean hasLuckPerms = Bukkit.getPluginManager().isPluginEnabled("LuckPerms"); + boolean hasVault = Bukkit.getPluginManager().isPluginEnabled("Vault"); + if (hasLuckPerms && (!hasVault || !proxyConfig.isEnable())) { plugin.getLogger().log(Level.INFO, "Using LuckPerms as prefix provider"); return new LuckpermsPrefixProvider(); } - if (Bukkit.getPluginManager().isPluginEnabled("Vault")) { + if (hasVault) { plugin.getLogger().log(Level.INFO, "Using Vault as prefix provider"); return new VaultPrefixProvider(); } diff --git a/spigot/src/main/java/ru/brikster/chatty/misc/VanillaListener.java b/spigot/src/main/java/ru/brikster/chatty/misc/VanillaListener.java index b585dbd9..c0d29ea1 100644 --- a/spigot/src/main/java/ru/brikster/chatty/misc/VanillaListener.java +++ b/spigot/src/main/java/ru/brikster/chatty/misc/VanillaListener.java @@ -14,10 +14,10 @@ import ru.brikster.chatty.chat.component.context.SinglePlayerTransformContext; import ru.brikster.chatty.chat.component.impl.PlaceholdersComponentTransformer; import ru.brikster.chatty.chat.component.impl.prefix.PrefixComponentTransformer; -import ru.brikster.chatty.config.type.VanillaConfig; -import ru.brikster.chatty.config.type.VanillaConfig.DeathVanillaConfig; -import ru.brikster.chatty.config.type.VanillaConfig.JoinVanillaConfig; -import ru.brikster.chatty.config.type.VanillaConfig.QuitVanillaConfig; +import ru.brikster.chatty.config.file.VanillaConfig; +import ru.brikster.chatty.config.file.VanillaConfig.DeathVanillaConfig; +import ru.brikster.chatty.config.file.VanillaConfig.JoinVanillaConfig; +import ru.brikster.chatty.config.file.VanillaConfig.QuitVanillaConfig; import ru.brikster.chatty.util.AdventureUtil; import javax.inject.Inject; @@ -27,7 +27,6 @@ public final class VanillaListener implements Listener { @Inject private PlaceholdersComponentTransformer placeholdersComponentTransformer; @Inject private PrefixComponentTransformer prefixComponentTransformer; @Inject private VanillaConfig vanillaConfig; - @Inject private BukkitAudiences audiences; @EventHandler(priority = EventPriority.HIGHEST) @@ -130,7 +129,7 @@ public void onPlayerDeath(PlayerDeathEvent event) { deathCause = deathConfig.getFallbackCause(); } else { DamageCause damageCause = damageEvent.getCause(); - deathCause = deathConfig.getCauses().getOrDefault(damageCause, deathConfig.getFallbackCause()); + deathCause = deathConfig.getCauses().getOrDefault(damageCause.name(), deathConfig.getFallbackCause()); } deathMessage = deathMessage.replaceText(AdventureUtil.createReplacement("{cause}", deathCause)); diff --git a/spigot/src/main/java/ru/brikster/chatty/papi/ChattyPlaceholderApiExpansion.java b/spigot/src/main/java/ru/brikster/chatty/papi/ChattyPlaceholderApiExpansion.java index 18f82800..76e961b4 100644 --- a/spigot/src/main/java/ru/brikster/chatty/papi/ChattyPlaceholderApiExpansion.java +++ b/spigot/src/main/java/ru/brikster/chatty/papi/ChattyPlaceholderApiExpansion.java @@ -31,6 +31,7 @@ public String getVersion() { @Override public String onPlaceholderRequest(Player one, Player two, String params) { + // TODO finish placeholders String[] args = params.split(Pattern.quote("_")); if (args.length == 0) { return null; diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/MsgCommandHandler.java b/spigot/src/main/java/ru/brikster/chatty/pm/MsgCommandHandler.java index 2044dcad..c80265a8 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/MsgCommandHandler.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/MsgCommandHandler.java @@ -5,7 +5,8 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; -import ru.brikster.chatty.config.type.MessagesConfig; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.pm.targets.PmMessageTarget; import javax.inject.Inject; import javax.inject.Singleton; @@ -23,7 +24,7 @@ public void execute(@NotNull CommandContext commandContext) { CommandSender sender = commandContext.getSender(); String targetName = commandContext.get("target"); - CommandSender target = pmMessageService.resolveTarget(targetName, true); + PmMessageTarget target = pmMessageService.resolveTarget(targetName, true); if (target == null) { audiences.sender(sender).sendMessage(messagesConfig.getPmPlayerNotFound()); return; diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/PmMessageService.java b/spigot/src/main/java/ru/brikster/chatty/pm/PmMessageService.java index 10ba3695..80ceb727 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/PmMessageService.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/PmMessageService.java @@ -2,10 +2,10 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextReplacementConfig; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; @@ -20,9 +20,13 @@ import ru.brikster.chatty.chat.component.impl.pm.prefix.PmFromPrefixComponentTransformer; import ru.brikster.chatty.chat.component.impl.pm.prefix.PmToPrefixComponentTransformer; import ru.brikster.chatty.chat.message.transform.decorations.PlayerDecorationsFormatter; -import ru.brikster.chatty.config.type.MessagesConfig; -import ru.brikster.chatty.config.type.PmConfig; +import ru.brikster.chatty.config.file.PmConfig; +import ru.brikster.chatty.config.file.ProxyConfig; import ru.brikster.chatty.convert.component.ComponentStringConverter; +import ru.brikster.chatty.pm.targets.CommandSenderPmMessageTarget; +import ru.brikster.chatty.pm.targets.PmMessageTarget; +import ru.brikster.chatty.pm.targets.RemotePmMessageTarget; +import ru.brikster.chatty.proxy.ProxyService; import ru.brikster.chatty.util.AdventureUtil; import javax.inject.Inject; @@ -41,42 +45,54 @@ public class PmMessageService { @Inject private PlayerDecorationsFormatter decorationsFormatter; @Inject private LinkParserComponentTransformer linkParserComponentTransformer; @Inject private ComponentStringConverter componentStringConverter; - @Inject private MessagesConfig messagesConfig; - @Inject private BukkitAudiences audiences; + @Inject private ProxyConfig proxyConfig; + @Inject private ProxyService proxyService; - private final Cache lastConversations = CacheBuilder.newBuilder() + private final Cache lastConversations = CacheBuilder.newBuilder() .expireAfterAccess(Duration.ofMinutes(10)) - .weakKeys() - .weakValues() .build(); - public @Nullable CommandSender resolveTarget(String targetName, boolean allowConsole) { - CommandSender target; + public @Nullable PmMessageTarget resolveTarget(String targetName, boolean allowConsole) { + PmMessageTarget target = null; if (allowConsole && pmConfig.isAllowConsole() && targetName.equalsIgnoreCase("Console")) { - return Bukkit.getConsoleSender(); + target = new CommandSenderPmMessageTarget(Bukkit.getConsoleSender()); } else { - target = Bukkit.getPlayer(targetName); - return target; + Player targetPlayer = Bukkit.getPlayer(targetName); + if (targetPlayer != null) { + target = new CommandSenderPmMessageTarget(targetPlayer); + } + } + + if (target == null) { + for (String onlinePlayerName : proxyService.getOnlinePlayers()) { + if (onlinePlayerName.equalsIgnoreCase(targetName)) { + return new RemotePmMessageTarget(onlinePlayerName, proxyService.getUuidByUsername(onlinePlayerName)); + } + } } + + return target; } public @NotNull Component transformFormat(@NotNull String formatString, - @NotNull CommandSender fromSender, - @NotNull CommandSender toSender, + @NotNull CommandSender sender, + @NotNull PmMessageTarget target, @NotNull String message) { Component formatComponent = componentStringConverter.stringToComponent(formatString); - formatComponent = formatFromPlaceholders(formatComponent, fromSender); - formatComponent = fromToPlaceholders(formatComponent, toSender); - formatComponent = formatRelationalPlaceholders(formatComponent, fromSender, toSender); - formatComponent = formatWithMessage(formatComponent, fromSender, message); + formatComponent = formatFromPlaceholders(formatComponent, sender); + formatComponent = formatToPlaceholders(formatComponent, target); + if (target.isOnline()) { + formatComponent = formatRelationalPlaceholders(formatComponent, sender, target.asCommandSender()); + } + formatComponent = formatWithMessage(formatComponent, sender, message); return formatComponent; } - public @NotNull Component formatWithMessage(@NotNull Component component, @NotNull CommandSender fromSender, @NotNull String message) { - Component messageComponent = decorationsFormatter.formatMessageWithDecorations(fromSender, message); + public @NotNull Component formatWithMessage(@NotNull Component component, @NotNull CommandSender sender, @NotNull String message) { + Component messageComponent = decorationsFormatter.formatMessageWithDecorations(sender, message); if (pmConfig.isParseLinks()) { messageComponent = linkParserComponentTransformer.transform(messageComponent, - SinglePlayerTransformContext.of((Player) fromSender)); + SinglePlayerTransformContext.of((Player) sender)); } return component.replaceText(TextReplacementConfig.builder() .matchLiteral("{message}") @@ -84,34 +100,34 @@ public class PmMessageService { .build()); } - public @NotNull Component formatFromPlaceholders(@NotNull Component component, @NotNull CommandSender fromSender) { + public @NotNull Component formatFromPlaceholders(@NotNull Component component, @NotNull CommandSender sender) { Component updatedComponent; - if (fromSender instanceof ConsoleCommandSender) { + if (sender instanceof ConsoleCommandSender) { updatedComponent = component .replaceText(AdventureUtil.createReplacement("{from-prefix}", "")) .replaceText(AdventureUtil.createReplacement("{from-suffix}", "")) - .replaceText(AdventureUtil.createReplacement("{from-name}", fromSender.getName())); + .replaceText(AdventureUtil.createReplacement("{from-name}", sender.getName())); } else { - updatedComponent = pmFromPrefixComponentTransformer.transform(component, SinglePlayerTransformContext.of((Player) fromSender)); - updatedComponent = pmFromPlaceholdersTransformer.transform(updatedComponent, SinglePlayerTransformContext.of((Player) fromSender)); + updatedComponent = pmFromPrefixComponentTransformer.transform(component, SinglePlayerTransformContext.of((OfflinePlayer) sender)); + updatedComponent = pmFromPlaceholdersTransformer.transform(updatedComponent, SinglePlayerTransformContext.of((OfflinePlayer) sender)); updatedComponent = updatedComponent - .replaceText(AdventureUtil.createReplacement("{from-name}", ((Player) fromSender).getDisplayName())); + .replaceText(AdventureUtil.createReplacement("{from-name}", sender.getName())); } return updatedComponent; } - public @NotNull Component fromToPlaceholders(@NotNull Component component, @NotNull CommandSender fromSender) { + public @NotNull Component formatToPlaceholders(@NotNull Component component, @NotNull PmMessageTarget messageTarget) { Component updatedComponent; - if (fromSender instanceof ConsoleCommandSender) { + if (messageTarget.isConsole()) { updatedComponent = component .replaceText(AdventureUtil.createReplacement("{to-prefix}", "")) .replaceText(AdventureUtil.createReplacement("{to-suffix}", "")) - .replaceText(AdventureUtil.createReplacement("{to-name}", fromSender.getName())); + .replaceText(AdventureUtil.createReplacement("{to-name}", messageTarget.getName())); } else { - updatedComponent = pmToPrefixComponentTransformer.transform(component, SinglePlayerTransformContext.of((Player) fromSender)); - updatedComponent = pmToPlaceholdersTransformer.transform(updatedComponent, SinglePlayerTransformContext.of((Player) fromSender)); + updatedComponent = pmToPrefixComponentTransformer.transform(component, SinglePlayerTransformContext.of(messageTarget.asOfflinePlayer())); + updatedComponent = pmToPlaceholdersTransformer.transform(updatedComponent, SinglePlayerTransformContext.of(messageTarget.asOfflinePlayer())); updatedComponent = updatedComponent - .replaceText(AdventureUtil.createReplacement("{to-name}", ((Player) fromSender).getDisplayName())); + .replaceText(AdventureUtil.createReplacement("{to-name}", messageTarget.getName())); } return updatedComponent; } @@ -127,12 +143,40 @@ public class PmMessageService { } } - public @Nullable CommandSender getLastConversation(@NotNull CommandSender sender) { - return lastConversations.getIfPresent(sender); + public @Nullable PmMessageTarget getLastConversation(@NotNull CommandSender sender) { + String targetName; + if (proxyConfig.isEnable()) { + targetName = null; + } else { + targetName = lastConversations.getIfPresent(sender.getName()); + } + + if (targetName == null) { + return null; + } + + CommandSender target; + if (targetName.equals("Console")) { + target = Bukkit.getConsoleSender(); + } else { + target = Bukkit.getPlayerExact(targetName); + } + + if (target == null && proxyConfig.isEnable()) { + if (proxyService.isOnline(targetName)) { + return new RemotePmMessageTarget(targetName, proxyService.getUuidByUsername(targetName)); + } + } + + return target == null ? null : new CommandSenderPmMessageTarget(target); } - public void addConversation(@NotNull CommandSender firstSender, @NotNull CommandSender secondSender) { - lastConversations.put(firstSender, secondSender); + public void addConversation(@NotNull String firstSender, @NotNull String secondSender) { + if (proxyConfig.isEnable()) { + proxyService.addConversation(firstSender, secondSender); + } else { + lastConversations.put(firstSender, secondSender); + } } } diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageCommandHandler.java b/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageCommandHandler.java index e36406b4..72cbb2a3 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageCommandHandler.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageCommandHandler.java @@ -8,8 +8,10 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; -import ru.brikster.chatty.config.type.MessagesConfig; -import ru.brikster.chatty.config.type.PmConfig; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.config.file.PmConfig; +import ru.brikster.chatty.pm.targets.PmMessageTarget; +import ru.brikster.chatty.proxy.ProxyService; import ru.brikster.chatty.repository.player.PlayerDataRepository; import javax.inject.Inject; @@ -24,11 +26,12 @@ public final class PrivateMessageCommandHandler { @Inject private MessagesConfig messagesConfig; @Inject private BukkitAudiences audiences; @Inject private PlayerDataRepository playerDataRepository; + @Inject private ProxyService proxyService; public void handleCommand(@NotNull CommandContext<@NotNull CommandSender> commandContext, @NotNull CommandSender sender, - @NotNull CommandSender target) { - if (sender == target) { + @NotNull PmMessageTarget target) { + if (target.isOnline() && sender == target.asCommandSender()) { audiences.sender(sender) .sendMessage(messagesConfig.getPmCannotPmYourself()); return; @@ -45,34 +48,48 @@ public void handleCommand(@NotNull CommandContext<@NotNull CommandSender> comman sender, target, message); audiences.sender(sender).sendMessage(fromComponentFormat); - pmMessageService.addConversation(sender, target); + pmMessageService.addConversation(sender.getName(), + target instanceof ConsoleCommandSender ? "Console" : target.getName()); boolean ignored = sender instanceof Player && target instanceof Player && playerDataRepository.isIgnoredPlayer((Player) target, ((Player) sender).getUniqueId()); - if (!ignored) { - var targetAudience = audiences.sender(target); - targetAudience.sendMessage(toComponentFormat); - if (pmConfig.isPlaySound()) { - targetAudience.playSound(pmConfig.getSound()); - } - pmMessageService.addConversation(target, sender); - } + Component spyComponentFormat = null; if (pmConfig.getSpy().isEnable()) { - Component spyComponentFormat = pmMessageService.transformFormat( + spyComponentFormat = pmMessageService.transformFormat( pmConfig.getSpy().getFormat(), sender, target, message); audiences.filter(spyCandidate -> spyCandidate.hasPermission("chatty.spy.pm") - && spyCandidate != sender - && (spyCandidate != target || ignored)) + && !(spyCandidate instanceof ConsoleCommandSender) + && spyCandidate != sender + && (!target.isOnline() || spyCandidate != target.asCommandSender())) .sendMessage(spyComponentFormat); } + String logMessage = "[PM] " + sender.getName() + " -> " + target.getName() + ": " + message; + + if (!ignored) { + if (target.isOnline()) { + var targetAudience = audiences.sender(target.asCommandSender()); + targetAudience.sendMessage(toComponentFormat); + if (pmConfig.isPlaySound()) { + targetAudience.playSound(pmConfig.getSound()); + } + pmMessageService.addConversation(target instanceof ConsoleCommandSender ? "Console" : target.getName(), + sender.getName()); + } else { + proxyService.sendPrivateMessage(target.getName(), toComponentFormat, + spyComponentFormat, + logMessage, + pmConfig.isPlaySound() ? pmConfig.getSound() : null); + } + } + boolean consoleIsInConversation = sender instanceof ConsoleCommandSender || target instanceof ConsoleCommandSender; if (!consoleIsInConversation) { - plugin.getLogger().info("[PM] " + sender.getName() + " -> " + target.getName() + ": " + message); + plugin.getLogger().info(logMessage); } } diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageSuggestionsProvider.java b/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageSuggestionsProvider.java index 14d7ca6b..e596a132 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageSuggestionsProvider.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/PrivateMessageSuggestionsProvider.java @@ -6,30 +6,39 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import ru.brikster.chatty.command.CommandSuggestionsProvider; -import ru.brikster.chatty.config.type.PmConfig; +import ru.brikster.chatty.config.file.PmConfig; +import ru.brikster.chatty.proxy.ProxyService; import javax.inject.Inject; import javax.inject.Singleton; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; @Singleton public final class PrivateMessageSuggestionsProvider implements CommandSuggestionsProvider { @Inject private PmConfig pmConfig; + @Inject private ProxyService proxyService; @Override public @NotNull List<@NotNull String> provideSuggestions(@NotNull CommandContext<@NotNull CommandSender> commandContext, @NotNull String arg) { - List suggestions = new ArrayList<>(); + Set suggestions = new HashSet<>(); for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { if (onlinePlayer.getName().toLowerCase().startsWith(arg.toLowerCase())) { suggestions.add(onlinePlayer.getName()); } } + for (String proxyPlayerName : proxyService.getOnlinePlayers()) { + if (proxyPlayerName.toLowerCase().startsWith(arg.toLowerCase())) { + suggestions.add(proxyPlayerName); + } + } if (pmConfig.isAllowConsole()) { suggestions.add("Console"); } - return suggestions; + return new ArrayList<>(suggestions); } } diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/ReplyCommandHandler.java b/spigot/src/main/java/ru/brikster/chatty/pm/ReplyCommandHandler.java index 62a64a63..efebcc6f 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/ReplyCommandHandler.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/ReplyCommandHandler.java @@ -5,7 +5,8 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; -import ru.brikster.chatty.config.type.MessagesConfig; +import ru.brikster.chatty.config.file.MessagesConfig; +import ru.brikster.chatty.pm.targets.PmMessageTarget; import javax.inject.Inject; import javax.inject.Singleton; @@ -21,7 +22,7 @@ public final class ReplyCommandHandler implements CommandExecutionHandler commandContext) { CommandSender sender = commandContext.getSender(); - CommandSender target = pmMessageService.getLastConversation(sender); + PmMessageTarget target = pmMessageService.getLastConversation(sender); if (target == null) { audiences.sender(sender).sendMessage(messagesConfig.getPmNobodyToReply()); diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/ignore/AddIgnoreCommandHandler.java b/spigot/src/main/java/ru/brikster/chatty/pm/ignore/AddIgnoreCommandHandler.java index 6dc9017a..036b247f 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/ignore/AddIgnoreCommandHandler.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/ignore/AddIgnoreCommandHandler.java @@ -6,8 +6,9 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import ru.brikster.chatty.config.type.MessagesConfig; +import ru.brikster.chatty.config.file.MessagesConfig; import ru.brikster.chatty.pm.PmMessageService; +import ru.brikster.chatty.pm.targets.PmMessageTarget; import ru.brikster.chatty.repository.player.PlayerDataRepository; import javax.inject.Inject; @@ -30,11 +31,11 @@ public void execute(@NotNull CommandContext commandContext) { UUID targetUuid; - Player target = (Player) pmMessageService.resolveTarget(targetName, false); + PmMessageTarget target = pmMessageService.resolveTarget(targetName, false); if (target == null) { targetUuid = repository.getCachedUuid(targetName); } else { - targetUuid = target.getUniqueId(); + targetUuid = target.getUuid(); } if (targetUuid == null) { @@ -42,12 +43,17 @@ public void execute(@NotNull CommandContext commandContext) { return; } + if (targetUuid.equals(sender.getUniqueId())) { + audiences.sender(sender).sendMessage(messagesConfig.getPmCannotIgnoreYourself()); + return; + } + if (repository.isIgnoredPlayer(sender, targetUuid)) { audiences.sender(sender).sendMessage(messagesConfig.getPmYouAlreadyIgnore()); } else { - repository.createOrUpdateUser(sender.getUniqueId(), sender.getDisplayName()); + repository.createOrUpdateUser(sender.getUniqueId(), sender.getName()); if (target != null) { - repository.createOrUpdateUser(target.getUniqueId(), target.getDisplayName()); + repository.createOrUpdateUser(target.getUuid(), target.getName()); } repository.addIgnoredPlayer(sender, targetUuid); diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/ignore/IgnoreListCommandHandler.java b/spigot/src/main/java/ru/brikster/chatty/pm/ignore/IgnoreListCommandHandler.java index 42cc2dfd..4f811493 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/ignore/IgnoreListCommandHandler.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/ignore/IgnoreListCommandHandler.java @@ -6,7 +6,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import ru.brikster.chatty.config.type.MessagesConfig; +import ru.brikster.chatty.config.file.MessagesConfig; import ru.brikster.chatty.repository.player.PlayerDataRepository; import ru.brikster.chatty.util.AdventureUtil; diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/ignore/RemoveIgnoreCommandHandler.java b/spigot/src/main/java/ru/brikster/chatty/pm/ignore/RemoveIgnoreCommandHandler.java index 41adcc3f..a55f0d2c 100644 --- a/spigot/src/main/java/ru/brikster/chatty/pm/ignore/RemoveIgnoreCommandHandler.java +++ b/spigot/src/main/java/ru/brikster/chatty/pm/ignore/RemoveIgnoreCommandHandler.java @@ -6,8 +6,9 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import ru.brikster.chatty.config.type.MessagesConfig; +import ru.brikster.chatty.config.file.MessagesConfig; import ru.brikster.chatty.pm.PmMessageService; +import ru.brikster.chatty.pm.targets.PmMessageTarget; import ru.brikster.chatty.repository.player.PlayerDataRepository; import javax.inject.Inject; @@ -30,11 +31,11 @@ public void execute(@NotNull CommandContext commandContext) { UUID targetUuid; - Player target = (Player) pmMessageService.resolveTarget(targetName, false); + PmMessageTarget target = pmMessageService.resolveTarget(targetName, false); if (target == null) { targetUuid = repository.getCachedUuid(targetName); } else { - targetUuid = target.getUniqueId(); + targetUuid = target.getUuid(); } if (targetUuid == null) { @@ -43,9 +44,9 @@ public void execute(@NotNull CommandContext commandContext) { } if (repository.isIgnoredPlayer(sender, targetUuid)) { - repository.createOrUpdateUser(sender.getUniqueId(), sender.getDisplayName()); + repository.createOrUpdateUser(sender.getUniqueId(), sender.getName()); if (target != null) { - repository.createOrUpdateUser(target.getUniqueId(), target.getDisplayName()); + repository.createOrUpdateUser(target.getUuid(), target.getName()); } repository.removeIgnoredPlayer(sender, targetUuid); diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/targets/CommandSenderPmMessageTarget.java b/spigot/src/main/java/ru/brikster/chatty/pm/targets/CommandSenderPmMessageTarget.java new file mode 100644 index 00000000..d21e8897 --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/pm/targets/CommandSenderPmMessageTarget.java @@ -0,0 +1,48 @@ +package ru.brikster.chatty.pm.targets; + +import lombok.RequiredArgsConstructor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +@RequiredArgsConstructor +public final class CommandSenderPmMessageTarget implements PmMessageTarget { + + private final CommandSender commandSender; + + @Override + public UUID getUuid() { + return commandSender instanceof Player + ? ((Player) commandSender).getUniqueId() + : null; + } + + @Override + public boolean isOnline() { + return true; + } + + @Override + public boolean isConsole() { + return commandSender instanceof ConsoleCommandSender; + } + + @Override + public String getName() { + return commandSender.getName(); + } + + @Override + public OfflinePlayer asOfflinePlayer() { + return (OfflinePlayer) commandSender; + } + + @Override + public CommandSender asCommandSender() { + return commandSender; + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/targets/PmMessageTarget.java b/spigot/src/main/java/ru/brikster/chatty/pm/targets/PmMessageTarget.java new file mode 100644 index 00000000..f86beb40 --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/pm/targets/PmMessageTarget.java @@ -0,0 +1,22 @@ +package ru.brikster.chatty.pm.targets; + +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; + +import java.util.UUID; + +public interface PmMessageTarget { + + UUID getUuid(); + + boolean isOnline(); + + boolean isConsole(); + + String getName(); + + OfflinePlayer asOfflinePlayer(); + + CommandSender asCommandSender(); + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/pm/targets/RemotePmMessageTarget.java b/spigot/src/main/java/ru/brikster/chatty/pm/targets/RemotePmMessageTarget.java new file mode 100644 index 00000000..0bca5b3a --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/pm/targets/RemotePmMessageTarget.java @@ -0,0 +1,46 @@ +package ru.brikster.chatty.pm.targets; + +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; + +import java.util.UUID; + +@RequiredArgsConstructor +public final class RemotePmMessageTarget implements PmMessageTarget { + + private final String targetName; + private final UUID targetUuid; + + @Override + public UUID getUuid() { + return targetUuid; + } + + @Override + public boolean isOnline() { + return false; + } + + @Override + public boolean isConsole() { + return false; + } + + @Override + public String getName() { + return targetName; + } + + @Override + public OfflinePlayer asOfflinePlayer() { + return Bukkit.getOfflinePlayer(targetUuid); + } + + @Override + public CommandSender asCommandSender() { + throw new IllegalStateException("Cannot cast remote target to CommandSender"); + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/prefix/LuckpermsPrefixProvider.java b/spigot/src/main/java/ru/brikster/chatty/prefix/LuckpermsPrefixProvider.java index 28b9ad84..73751259 100644 --- a/spigot/src/main/java/ru/brikster/chatty/prefix/LuckpermsPrefixProvider.java +++ b/spigot/src/main/java/ru/brikster/chatty/prefix/LuckpermsPrefixProvider.java @@ -4,6 +4,7 @@ import net.luckperms.api.model.user.User; import net.luckperms.api.platform.PlayerAdapter; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import javax.inject.Singleton; @@ -17,14 +18,20 @@ public final class LuckpermsPrefixProvider implements PrefixProvider { private final PlayerAdapter playerAdapter = luckPerms.getPlayerAdapter(Player.class); @Override - public String getPrefix(Player player) { - User user = playerAdapter.getUser(player); + public String getPrefix(OfflinePlayer player) { + if (!(player instanceof Player)) { + return null; + } + User user = playerAdapter.getUser((Player) player); return user.getCachedData().getMetaData().getPrefix(); } @Override - public String getSuffix(Player player) { - User user = playerAdapter.getUser(player); + public String getSuffix(OfflinePlayer player) { + if (!(player instanceof Player)) { + return null; + } + User user = playerAdapter.getUser((Player) player); return user.getCachedData().getMetaData().getSuffix(); } diff --git a/spigot/src/main/java/ru/brikster/chatty/prefix/NullPrefixProvider.java b/spigot/src/main/java/ru/brikster/chatty/prefix/NullPrefixProvider.java index ca9c1306..8bf9cfb8 100644 --- a/spigot/src/main/java/ru/brikster/chatty/prefix/NullPrefixProvider.java +++ b/spigot/src/main/java/ru/brikster/chatty/prefix/NullPrefixProvider.java @@ -1,6 +1,6 @@ package ru.brikster.chatty.prefix; -import org.bukkit.entity.Player; +import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.Nullable; import javax.inject.Singleton; @@ -9,12 +9,12 @@ public final class NullPrefixProvider implements PrefixProvider { @Override - public @Nullable String getPrefix(Player player) { + public @Nullable String getPrefix(OfflinePlayer player) { return null; } @Override - public @Nullable String getSuffix(Player player) { + public @Nullable String getSuffix(OfflinePlayer player) { return null; } diff --git a/spigot/src/main/java/ru/brikster/chatty/prefix/PrefixProvider.java b/spigot/src/main/java/ru/brikster/chatty/prefix/PrefixProvider.java index 7bd5f67b..134985db 100644 --- a/spigot/src/main/java/ru/brikster/chatty/prefix/PrefixProvider.java +++ b/spigot/src/main/java/ru/brikster/chatty/prefix/PrefixProvider.java @@ -1,12 +1,12 @@ package ru.brikster.chatty.prefix; -import org.bukkit.entity.Player; +import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.Nullable; public interface PrefixProvider { - @Nullable String getPrefix(Player player); + @Nullable String getPrefix(OfflinePlayer player); - @Nullable String getSuffix(Player player); + @Nullable String getSuffix(OfflinePlayer player); } diff --git a/spigot/src/main/java/ru/brikster/chatty/prefix/VaultPrefixProvider.java b/spigot/src/main/java/ru/brikster/chatty/prefix/VaultPrefixProvider.java index c306787e..7ba451db 100644 --- a/spigot/src/main/java/ru/brikster/chatty/prefix/VaultPrefixProvider.java +++ b/spigot/src/main/java/ru/brikster/chatty/prefix/VaultPrefixProvider.java @@ -2,6 +2,7 @@ import net.milkbowl.vault.chat.Chat; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import javax.inject.Singleton; @@ -14,13 +15,17 @@ public final class VaultPrefixProvider implements PrefixProvider { Objects.requireNonNull(Bukkit.getServicesManager().getRegistration(Chat.class)).getProvider(); @Override - public String getPrefix(Player player) { - return vaultChatModule.getPlayerPrefix(player); + public String getPrefix(OfflinePlayer player) { + return vaultChatModule.getPlayerPrefix(player instanceof Player + ? ((Player) player).getWorld().getName() + : Bukkit.getWorlds().get(0).getName(), player); } @Override - public String getSuffix(Player player) { - return vaultChatModule.getPlayerSuffix(player); + public String getSuffix(OfflinePlayer player) { + return vaultChatModule.getPlayerSuffix(player instanceof Player + ? ((Player) player).getWorld().getName() + : Bukkit.getWorlds().get(0).getName(), player); } } diff --git a/spigot/src/main/java/ru/brikster/chatty/proxy/DummyProxyService.java b/spigot/src/main/java/ru/brikster/chatty/proxy/DummyProxyService.java new file mode 100644 index 00000000..0d1bd220 --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/proxy/DummyProxyService.java @@ -0,0 +1,49 @@ +package ru.brikster.chatty.proxy; + +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.brikster.chatty.api.chat.Chat; +import ru.brikster.chatty.proxy.data.ChatStyle; + +import javax.inject.Singleton; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +@Singleton +public final class DummyProxyService implements ProxyService { + + @Override + public @NotNull Collection getOnlinePlayers() { + return Collections.emptyList(); + } + + @Override + public @Nullable UUID getUuidByUsername(@NotNull String username) { + return null; + } + + @Override + public void addConversation(@NotNull String firstSender, @NotNull String secondSender) { + + } + + @Override + public @Nullable String getLastConversation(@NotNull String sender) { + return null; + } + + @Override + public void sendChatMessage(@NotNull Chat chat, @NotNull Component noStyleMessage, @NotNull Map stylesMessages, @Nullable Sound sound) { + + } + + @Override + public void sendPrivateMessage(@NotNull String targetName, @NotNull Component message, @Nullable Component spyMessage, @NotNull String logMessage, @Nullable Sound sound) { + + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/proxy/ProxyService.java b/spigot/src/main/java/ru/brikster/chatty/proxy/ProxyService.java new file mode 100644 index 00000000..b88bb6e7 --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/proxy/ProxyService.java @@ -0,0 +1,44 @@ +package ru.brikster.chatty.proxy; + +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.brikster.chatty.api.chat.Chat; +import ru.brikster.chatty.proxy.data.ChatStyle; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +public interface ProxyService { + + @NotNull Collection getOnlinePlayers(); + + @Nullable UUID getUuidByUsername(@NotNull String username); + + void addConversation(@NotNull String firstSender, @NotNull String secondSender); + + @Nullable String getLastConversation(@NotNull String sender); + + default boolean isOnline(@NotNull String playerName) { + return getOnlinePlayers() + .stream() + .map(String::toLowerCase) + .collect(Collectors.toSet()) + .contains(playerName.toLowerCase()); + } + + void sendChatMessage(@NotNull Chat chat, + @NotNull Component noStyleMessage, + @NotNull Map stylesMessages, + @Nullable Sound sound); + + void sendPrivateMessage(@NotNull String targetName, + @NotNull Component message, + @Nullable Component spyMessage, + @NotNull String logMessage, + @Nullable Sound sound); + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/proxy/ProxyServiceImpl.java b/spigot/src/main/java/ru/brikster/chatty/proxy/ProxyServiceImpl.java new file mode 100644 index 00000000..c134a6c3 --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/proxy/ProxyServiceImpl.java @@ -0,0 +1,209 @@ +package ru.brikster.chatty.proxy; + +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.redisson.Redisson; +import org.redisson.api.RMapCache; +import org.redisson.api.RTopic; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import ru.brikster.chatty.api.chat.Chat; +import ru.brikster.chatty.api.chat.ChatStyle; +import ru.brikster.chatty.chat.registry.ChatRegistry; +import ru.brikster.chatty.chat.style.ChatStylePlayerGrouper; +import ru.brikster.chatty.chat.style.ChatStylePlayerGrouper.Groping; +import ru.brikster.chatty.config.file.PmConfig; +import ru.brikster.chatty.proxy.data.ChatMessage; +import ru.brikster.chatty.proxy.data.PrivateMessage; +import ru.brikster.chatty.proxy.data.ProxyPlayer; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.Closeable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Singleton +public final class ProxyServiceImpl implements ProxyService, Closeable { + + private static final GsonComponentSerializer GSON_COMPONENT_SERIALIZER = GsonComponentSerializer.gson(); + + private final UUID clientId = UUID.randomUUID(); + + private final RMapCache playersCache; + private final RMapCache pmReplyCache; + private final RTopic chatTopic; + private final RTopic pmTopic; + private final ScheduledExecutorService scheduledExecutor; + + @Inject + public ProxyServiceImpl(Config redissonConfig, + BukkitAudiences audiences, + ChatRegistry chatRegistry, + PmConfig pmConfig, + ChatStylePlayerGrouper stylePlayerGrouper, + Plugin plugin) { + RedissonClient redissonClient = Redisson.create(redissonConfig); + this.playersCache = redissonClient.getMapCache("chatty_players"); + this.pmReplyCache = redissonClient.getMapCache("chatty_pm_reply"); + this.chatTopic = redissonClient.getTopic("chatty_chat"); + this.pmTopic = redissonClient.getTopic("chatty_pm"); + + chatTopic.addListener(ChatMessage.class, (channel, redisMessage) -> { + if (redisMessage.getClientId().equals(clientId)) return; + + Chat chat = chatRegistry.getChats().get(redisMessage.getChatId()); + if (chat.getRange() > -3) return; // not cross-proxy chat + + Component noStyleComponent = GSON_COMPONENT_SERIALIZER.deserialize(redisMessage.getNoStyleComponentJson()); + + var recipients = chat.calculateRecipients(null); + + Set styles = redisMessage.getStyleComponentJsonMap() + .entrySet() + .stream() + .map(entry -> new ChatStyle(entry.getKey(), + GSON_COMPONENT_SERIALIZER.deserialize(entry.getValue().getComponentJson()), + entry.getValue().getPriority())) + .collect(Collectors.toSet()); + + Groping grouping = stylePlayerGrouper.makeGrouping(recipients, styles, null, null); + + Sound sound = redisMessage.getSound(); + + for (Player noStylePlayer : grouping.getNoStylePlayers()) { + var playerAudience = audiences.player(noStylePlayer); + playerAudience.sendMessage(noStyleComponent); + if (sound != null) { + playerAudience.playSound(sound); + } + } + + grouping.getStylesMap().forEach((style, players) -> { + for (Player player : players) { + var playerAudience = audiences.player(player); + playerAudience.sendMessage(style.format()); + if (sound != null) { + playerAudience.playSound(sound); + } + } + }); + + audiences.console().sendMessage(noStyleComponent); + }); + + if (pmConfig.isEnable()) { + pmTopic.addListener(PrivateMessage.class, (channel, redisMessage) -> { + if (redisMessage.getClientId().equals(clientId)) return; + + if (redisMessage.getSpyComponentJson() != null) { + Component spyMessage = GSON_COMPONENT_SERIALIZER.deserialize(redisMessage.getSpyComponentJson()); + audiences.filter(spyCandidate -> + spyCandidate.hasPermission("chatty.spy.pm") + && !(spyCandidate instanceof ConsoleCommandSender) + && !spyCandidate.getName().equalsIgnoreCase(redisMessage.getTargetName())) + .sendMessage(spyMessage); + } + + Player targetPlayer = Bukkit.getPlayerExact(redisMessage.getTargetName()); + if (targetPlayer == null) return; + + Component message = GSON_COMPONENT_SERIALIZER.deserialize(redisMessage.getComponentJson()); + + var targetPlayerAudience = audiences.player(targetPlayer); + + targetPlayerAudience.sendMessage(message); + if (pmConfig.isPlaySound()) { + targetPlayerAudience.playSound(pmConfig.getSound()); + } + + plugin.getLogger().info(redisMessage.getLogMessage()); + }); + } + + this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); + this.scheduledExecutor.scheduleWithFixedDelay(() -> { + for (Player player : Bukkit.getOnlinePlayers()) { + playersCache.put(player.getName().toLowerCase(), new ProxyPlayer(player.getName(), player.getUniqueId()), 8, TimeUnit.SECONDS); + } + }, 0, 5, TimeUnit.SECONDS); + } + + @Override + public @NotNull Collection getOnlinePlayers() { + return playersCache + .values() + .stream() + .map(ProxyPlayer::getUsername) + .collect(Collectors.toSet()); + } + + @Override + public @Nullable UUID getUuidByUsername(@NotNull String username) { + ProxyPlayer player = playersCache.get(username.toLowerCase()); + if (player != null) return player.getUuid(); + return null; + } + + @Override + public void addConversation(@NotNull String firstSender, @NotNull String secondSender) { + pmReplyCache.put(firstSender, secondSender, 10, TimeUnit.MINUTES); + } + + @Override + public @Nullable String getLastConversation(@NotNull String sender) { + return pmReplyCache.get(sender); + } + + @Override + public boolean isOnline(@NotNull String playerName) { + return playersCache.containsKey(playerName.toLowerCase()); + } + + @Override + public void sendChatMessage(@NotNull Chat chat, + @NotNull Component noStyleMessage, + @NotNull Map stylesMessages, + @Nullable Sound sound) { + chatTopic.publish(new ChatMessage(clientId, chat.getId(), + GSON_COMPONENT_SERIALIZER.serialize(noStyleMessage), + stylesMessages, + sound)); + } + + @Override + public void sendPrivateMessage(@NotNull String targetName, + @NotNull Component message, + @Nullable Component spyMessage, + @NotNull String logMessage, + @Nullable Sound sound) { + pmTopic.publish(new PrivateMessage(clientId, + targetName, + GSON_COMPONENT_SERIALIZER.serialize(message), + spyMessage == null ? null : GSON_COMPONENT_SERIALIZER.serialize(spyMessage), + logMessage, + sound)); + } + + @Override + public void close() { + this.scheduledExecutor.shutdown(); + this.chatTopic.removeAllListeners(); + this.pmTopic.removeAllListeners(); + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatMessage.java b/spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatMessage.java new file mode 100644 index 00000000..185344cd --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatMessage.java @@ -0,0 +1,16 @@ +package ru.brikster.chatty.proxy.data; + +import lombok.Value; +import net.kyori.adventure.sound.Sound; + +import java.util.Map; +import java.util.UUID; + +@Value +public class ChatMessage { + UUID clientId; + String chatId; + String noStyleComponentJson; + Map styleComponentJsonMap; + Sound sound; +} diff --git a/spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatStyle.java b/spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatStyle.java new file mode 100644 index 00000000..d25bc15f --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/proxy/data/ChatStyle.java @@ -0,0 +1,9 @@ +package ru.brikster.chatty.proxy.data; + +import lombok.Value; + +@Value +public class ChatStyle { + int priority; + String componentJson; +} diff --git a/spigot/src/main/java/ru/brikster/chatty/proxy/data/PrivateMessage.java b/spigot/src/main/java/ru/brikster/chatty/proxy/data/PrivateMessage.java new file mode 100644 index 00000000..efda842e --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/proxy/data/PrivateMessage.java @@ -0,0 +1,16 @@ +package ru.brikster.chatty.proxy.data; + +import lombok.Value; +import net.kyori.adventure.sound.Sound; + +import java.util.UUID; + +@Value +public class PrivateMessage { + UUID clientId; + String targetName; + String componentJson; + String spyComponentJson; + String logMessage; + Sound sound; +} diff --git a/spigot/src/main/java/ru/brikster/chatty/proxy/data/ProxyPlayer.java b/spigot/src/main/java/ru/brikster/chatty/proxy/data/ProxyPlayer.java new file mode 100644 index 00000000..ae602de4 --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/proxy/data/ProxyPlayer.java @@ -0,0 +1,11 @@ +package ru.brikster.chatty.proxy.data; + +import lombok.Value; + +import java.util.UUID; + +@Value +public class ProxyPlayer { + String username; + UUID uuid; +} diff --git a/spigot/src/main/java/ru/brikster/chatty/repository/player/MysqlPlayerDataRepository.java b/spigot/src/main/java/ru/brikster/chatty/repository/player/MysqlPlayerDataRepository.java new file mode 100644 index 00000000..1aa9723a --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/repository/player/MysqlPlayerDataRepository.java @@ -0,0 +1,215 @@ +package ru.brikster.chatty.repository.player; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.bukkit.entity.Player; +import org.flywaydb.core.Flyway; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.brikster.chatty.Chatty; +import ru.brikster.chatty.config.file.ProxyConfig.DatabaseConfig; + +import javax.inject.Singleton; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Singleton +public final class MysqlPlayerDataRepository implements PlayerDataRepository { + + private final HikariDataSource dataSource; + + public MysqlPlayerDataRepository(DatabaseConfig databaseConfig) { + HikariConfig config = new HikariConfig(); + config.setDriverClassName("com.mysql.jdbc.Driver"); + config.setJdbcUrl(String.format("jdbc:mysql://%s:%d/%s", + databaseConfig.getHostname(), databaseConfig.getPort(), databaseConfig.getDatabase())); + config.addDataSourceProperty("user", databaseConfig.getUsername()); + config.addDataSourceProperty("password", databaseConfig.getPassword()); + config.setPoolName("Chatty"); + config.setMaximumPoolSize(8); + + this.dataSource = new HikariDataSource(config); + + Flyway flyway = Flyway.configure(Chatty.class.getClassLoader()) + .locations("db/migration/mysql") + .dataSource(dataSource) + .load(); + flyway.migrate(); + } + + @Override + public @NotNull Set<@NotNull UUID> getWhoIgnoreUuids(@NotNull Player player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT player_uuid " + + "FROM chatty_ignored_users " + + "WHERE ignored_uuid = ?")) { + statement.setString(1, player.getUniqueId().toString()); + + ResultSet resultSet = statement.executeQuery(); + + Set ignoredPlayers = new HashSet<>(); + while (resultSet.next()) { + ignoredPlayers.add(UUID.fromString(resultSet.getString(1))); + } + + return ignoredPlayers; + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve ignored players", sqlException); + } + } + + @Override + public @NotNull Set<@NotNull UUID> getIgnoredPlayersByUuids(@NotNull Player player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT ignored_uuid " + + "FROM chatty_ignored_users " + + "WHERE player_uuid = ?")) { + statement.setString(1, player.getUniqueId().toString()); + + ResultSet resultSet = statement.executeQuery(); + + Set ignoredPlayers = new HashSet<>(); + while (resultSet.next()) { + ignoredPlayers.add(UUID.fromString(resultSet.getString(1))); + } + + return ignoredPlayers; + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve ignored players", sqlException); + } + } + + @Override + public @NotNull Set<@NotNull String> getIgnoredPlayersByUsernames(@NotNull Player player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT username " + + "FROM chatty_ignored_users iu JOIN chatty_users u ON iu.ignored_uuid = u.uuid " + + "WHERE player_uuid = ?")) { + statement.setString(1, player.getUniqueId().toString()); + + ResultSet resultSet = statement.executeQuery(); + + Set ignoredPlayers = new HashSet<>(); + while (resultSet.next()) { + ignoredPlayers.add(resultSet.getString(1)); + } + + return ignoredPlayers; + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve ignored players", sqlException); + } + } + + @Override + public void createOrUpdateUser(@NotNull UUID uuid, @NotNull String username) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO chatty_users (uuid, username) VALUES (?, ?) " + + "ON DUPLICATE KEY UPDATE username = ?")) { + statement.setString(1, uuid.toString()); + statement.setString(2, username); + statement.setString(3, username); + statement.executeUpdate(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot create or update", sqlException); + } + } + + @Override + public @Nullable UUID getCachedUuid(@NotNull String playerName) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT uuid " + + "FROM chatty_users " + + "WHERE lower(username) = lower(?)")) { + statement.setString(1, playerName); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return UUID.fromString(resultSet.getString(1)); + } else { + return null; + } + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve cached uuid", sqlException); + } + } + + @Override + public @Nullable String getCachedUsername(@NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT username " + + "FROM chatty_users " + + "WHERE uuid = ?")) { + statement.setString(1, uuid.toString()); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return resultSet.getString(1); + } else { + return null; + } + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve cached username", sqlException); + } + } + + @Override + public void addIgnoredPlayer(@NotNull Player player, @NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO chatty_ignored_users (player_uuid, ignored_uuid) VALUES (?, ?)")) { + statement.setString(1, player.getUniqueId().toString()); + statement.setString(2, uuid.toString()); + statement.executeUpdate(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot add ignored player", sqlException); + } + } + + @Override + public void removeIgnoredPlayer(@NotNull Player player, @NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "DELETE FROM chatty_ignored_users" + + " WHERE player_uuid = ? AND ignored_uuid = ?")) { + statement.setString(1, player.getUniqueId().toString()); + statement.setString(2, uuid.toString()); + statement.executeUpdate(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot remove ignored player", sqlException); + } + } + + @Override + public boolean isIgnoredPlayer(@NotNull Player player, @NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT ignored_uuid " + + "FROM chatty_ignored_users " + + "WHERE player_uuid = ? AND ignored_uuid = ?")) { + statement.setString(1, player.getUniqueId().toString()); + statement.setString(2, uuid.toString()); + + ResultSet resultSet = statement.executeQuery(); + return resultSet.next(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot check ignored player", sqlException); + } + } + + @Override + public void close() { + dataSource.close(); + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/repository/player/PlayerDataRepository.java b/spigot/src/main/java/ru/brikster/chatty/repository/player/PlayerDataRepository.java index 77bbc397..4e3212eb 100644 --- a/spigot/src/main/java/ru/brikster/chatty/repository/player/PlayerDataRepository.java +++ b/spigot/src/main/java/ru/brikster/chatty/repository/player/PlayerDataRepository.java @@ -12,6 +12,7 @@ public interface PlayerDataRepository extends AutoCloseable { @NotNull Set<@NotNull UUID> getWhoIgnoreUuids(@NotNull Player player); @NotNull Set<@NotNull UUID> getIgnoredPlayersByUuids(@NotNull Player player); + @NotNull Set<@NotNull String> getIgnoredPlayersByUsernames(@NotNull Player player); void createOrUpdateUser(@NotNull UUID uuid, @NotNull String username); diff --git a/spigot/src/main/java/ru/brikster/chatty/repository/player/PostgresPlayerDataRepository.java b/spigot/src/main/java/ru/brikster/chatty/repository/player/PostgresPlayerDataRepository.java new file mode 100644 index 00000000..2d6e0702 --- /dev/null +++ b/spigot/src/main/java/ru/brikster/chatty/repository/player/PostgresPlayerDataRepository.java @@ -0,0 +1,216 @@ +package ru.brikster.chatty.repository.player; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.bukkit.entity.Player; +import org.flywaydb.core.Flyway; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.brikster.chatty.Chatty; +import ru.brikster.chatty.config.file.ProxyConfig.DatabaseConfig; + +import javax.inject.Singleton; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Singleton +public final class PostgresPlayerDataRepository implements PlayerDataRepository { + + private final HikariDataSource dataSource; + + public PostgresPlayerDataRepository(DatabaseConfig databaseConfig) { + HikariConfig config = new HikariConfig(); + config.setDataSourceClassName("org.postgresql.ds.PGSimpleDataSource"); + config.addDataSourceProperty("serverName", databaseConfig.getHostname()); + config.addDataSourceProperty("portNumber", databaseConfig.getPort()); + config.addDataSourceProperty("databaseName", databaseConfig.getDatabase()); + config.addDataSourceProperty("user", databaseConfig.getUsername()); + config.addDataSourceProperty("password", databaseConfig.getPassword()); + config.setPoolName("Chatty"); + config.setMaximumPoolSize(8); + + this.dataSource = new HikariDataSource(config); + + Flyway flyway = Flyway.configure(Chatty.class.getClassLoader()) + .locations("db/migration/postgres") + .dataSource(dataSource) + .load(); + flyway.migrate(); + } + + @Override + public @NotNull Set<@NotNull UUID> getWhoIgnoreUuids(@NotNull Player player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT player_uuid " + + "FROM chatty_ignored_users " + + "WHERE ignored_uuid = ?")) { + statement.setObject(1, player.getUniqueId()); + + ResultSet resultSet = statement.executeQuery(); + + Set ignoredPlayers = new HashSet<>(); + while (resultSet.next()) { + ignoredPlayers.add((UUID) resultSet.getObject(1)); + } + + return ignoredPlayers; + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve ignored players", sqlException); + } + } + + @Override + public @NotNull Set<@NotNull UUID> getIgnoredPlayersByUuids(@NotNull Player player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT ignored_uuid " + + "FROM chatty_ignored_users " + + "WHERE player_uuid = ?")) { + statement.setObject(1, player.getUniqueId()); + + ResultSet resultSet = statement.executeQuery(); + + Set ignoredPlayers = new HashSet<>(); + while (resultSet.next()) { + ignoredPlayers.add((UUID) resultSet.getObject(1)); + } + + return ignoredPlayers; + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve ignored players", sqlException); + } + } + + @Override + public @NotNull Set<@NotNull String> getIgnoredPlayersByUsernames(@NotNull Player player) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT username " + + "FROM chatty_ignored_users iu JOIN chatty_users u ON iu.ignored_uuid = u.uuid " + + "WHERE player_uuid = ?")) { + statement.setObject(1, player.getUniqueId()); + + ResultSet resultSet = statement.executeQuery(); + + Set ignoredPlayers = new HashSet<>(); + while (resultSet.next()) { + ignoredPlayers.add(resultSet.getString(1)); + } + + return ignoredPlayers; + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve ignored players", sqlException); + } + } + + @Override + public void createOrUpdateUser(@NotNull UUID uuid, @NotNull String username) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO chatty_users (uuid, username) VALUES (?, ?) " + + "ON CONFLICT (uuid) DO UPDATE SET username = ?")) { + statement.setObject(1, uuid); + statement.setString(2, username); + statement.setString(3, username); + statement.executeUpdate(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot create or update", sqlException); + } + } + + @Override + public @Nullable UUID getCachedUuid(@NotNull String playerName) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT uuid " + + "FROM chatty_users " + + "WHERE lower(username) = lower(?)")) { + statement.setString(1, playerName); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return (UUID) resultSet.getObject(1); + } else { + return null; + } + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve cached uuid", sqlException); + } + } + + @Override + public @Nullable String getCachedUsername(@NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT username " + + "FROM chatty_users " + + "WHERE uuid = ?")) { + statement.setObject(1, uuid); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return resultSet.getString(1); + } else { + return null; + } + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot retrieve cached username", sqlException); + } + } + + @Override + public void addIgnoredPlayer(@NotNull Player player, @NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO chatty_ignored_users (player_uuid, ignored_uuid) VALUES (?, ?)")) { + statement.setObject(1, player.getUniqueId()); + statement.setObject(2, uuid); + statement.executeUpdate(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot add ignored player", sqlException); + } + } + + @Override + public void removeIgnoredPlayer(@NotNull Player player, @NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "DELETE FROM chatty_ignored_users" + + " WHERE player_uuid = ? AND ignored_uuid = ?")) { + statement.setObject(1, player.getUniqueId()); + statement.setObject(2, uuid); + statement.executeUpdate(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot remove ignored player", sqlException); + } + } + + @Override + public boolean isIgnoredPlayer(@NotNull Player player, @NotNull UUID uuid) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT ignored_uuid " + + "FROM chatty_ignored_users " + + "WHERE player_uuid = ? AND ignored_uuid = ?")) { + statement.setObject(1, player.getUniqueId()); + statement.setObject(2, uuid); + + ResultSet resultSet = statement.executeQuery(); + return resultSet.next(); + } catch (SQLException sqlException) { + throw new IllegalStateException("Cannot check ignored player", sqlException); + } + } + + @Override + public void close() { + dataSource.close(); + } + +} diff --git a/spigot/src/main/java/ru/brikster/chatty/repository/player/SqlitePlayerDataRepository.java b/spigot/src/main/java/ru/brikster/chatty/repository/player/SqlitePlayerDataRepository.java index 510232a8..cbc70da5 100644 --- a/spigot/src/main/java/ru/brikster/chatty/repository/player/SqlitePlayerDataRepository.java +++ b/spigot/src/main/java/ru/brikster/chatty/repository/player/SqlitePlayerDataRepository.java @@ -41,6 +41,7 @@ public SqlitePlayerDataRepository(Path dataFolder) { this.dataSource = new HikariDataSource(config); Flyway flyway = Flyway.configure(Chatty.class.getClassLoader()) + .locations("db/migration/sqlite") .dataSource(dataSource) .load(); flyway.migrate(); @@ -116,7 +117,7 @@ public SqlitePlayerDataRepository(Path dataFolder) { public void createOrUpdateUser(@NotNull UUID uuid, @NotNull String username) { try (Connection connection = dataSource.getConnection(); PreparedStatement statement = connection.prepareStatement( - "INSERT INTO users (uuid, username) VALUES (?, ?) ON CONFLICT (uuid) DO UPDATE SET username = ?")) { + "INSERT INTO users (uuid, username) VALUES (?, ?) ON CONFLICT (uuid, username) DO UPDATE SET username = ?")) { statement.setBytes(1, SqliteUtil.fromUUID(uuid)); statement.setString(2, username); statement.setString(3, username); diff --git a/spigot/src/main/java/ru/brikster/chatty/util/GraphUtil.java b/spigot/src/main/java/ru/brikster/chatty/util/GraphUtil.java index 79ce6d24..02ec2aaa 100644 --- a/spigot/src/main/java/ru/brikster/chatty/util/GraphUtil.java +++ b/spigot/src/main/java/ru/brikster/chatty/util/GraphUtil.java @@ -3,7 +3,7 @@ import lombok.Value; import lombok.experimental.UtilityClass; import ru.brikster.chatty.Constants; -import ru.brikster.chatty.config.type.ReplacementsConfig; +import ru.brikster.chatty.config.file.ReplacementsConfig; import java.util.*; diff --git a/spigot/src/main/resources/db/migration/mysql/V1__init.sql b/spigot/src/main/resources/db/migration/mysql/V1__init.sql new file mode 100644 index 00000000..7ccf4507 --- /dev/null +++ b/spigot/src/main/resources/db/migration/mysql/V1__init.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS chatty_users ( + uuid VARCHAR(36) PRIMARY KEY, + username VARCHAR(32) NOT NULL +); + +CREATE TABLE IF NOT EXISTS chatty_ignored_users ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + player_uuid VARCHAR(36) NOT NULL + REFERENCES chatty_users (uuid), + ignored_uuid VARCHAR(36) NOT NULL + REFERENCES chatty_users (uuid), + CONSTRAINT chatty_ignored_users_uq UNIQUE (player_uuid, ignored_uuid) +); + +CREATE INDEX chatty_users_username_idx ON chatty_users (username); +CREATE INDEX chatty_ignored_users_player_uuid_idx ON chatty_ignored_users (player_uuid); +CREATE INDEX chatty_ignored_users_ignored_uuid_idx ON chatty_ignored_users (ignored_uuid); \ No newline at end of file diff --git a/spigot/src/main/resources/db/migration/postgres/V1__init.sql b/spigot/src/main/resources/db/migration/postgres/V1__init.sql new file mode 100644 index 00000000..0fe927b5 --- /dev/null +++ b/spigot/src/main/resources/db/migration/postgres/V1__init.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS chatty_users ( + uuid UUID PRIMARY KEY, + username VARCHAR(32) NOT NULL +); + +CREATE TABLE IF NOT EXISTS chatty_ignored_users ( + id BIGSERIAL PRIMARY KEY, + player_uuid UUID NOT NULL + REFERENCES chatty_users (uuid), + ignored_uuid UUID NOT NULL + REFERENCES chatty_users (uuid), + CONSTRAINT chatty_ignored_users_uq UNIQUE (player_uuid, ignored_uuid) +); + +CREATE INDEX chatty_users_username_idx ON chatty_users (username); +CREATE INDEX chatty_ignored_users_player_uuid_idx ON chatty_ignored_users (player_uuid); +CREATE INDEX chatty_ignored_users_ignored_uuid_idx ON chatty_ignored_users (ignored_uuid); \ No newline at end of file diff --git a/spigot/src/main/resources/db/migration/V1__init.sql b/spigot/src/main/resources/db/migration/sqlite/V1__init.sql similarity index 100% rename from spigot/src/main/resources/db/migration/V1__init.sql rename to spigot/src/main/resources/db/migration/sqlite/V1__init.sql