mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-21 11:45:25 +01:00
Rendering mentions in-game
This commit is contained in:
parent
03cbd45993
commit
fb426b870c
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.events.message.render;
|
||||
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.api.events.Cancellable;
|
||||
import com.discordsrv.api.events.PlayerEvent;
|
||||
import com.discordsrv.api.events.Processable;
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class GameChatRenderEvent implements PlayerEvent, Processable.Argument<MinecraftComponent>, Cancellable {
|
||||
|
||||
private final Object triggeringEvent;
|
||||
private final DiscordSRVPlayer player;
|
||||
private final GameChannel channel;
|
||||
private final MinecraftComponent message;
|
||||
private MinecraftComponent annotatedMessage;
|
||||
private boolean cancelled = false;
|
||||
|
||||
public GameChatRenderEvent(
|
||||
@Nullable Object triggeringEvent,
|
||||
@NotNull DiscordSRVPlayer player,
|
||||
@NotNull GameChannel channel,
|
||||
@NotNull MinecraftComponent message
|
||||
) {
|
||||
this.triggeringEvent = triggeringEvent;
|
||||
this.player = player;
|
||||
this.channel = channel;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Object getTriggeringEvent() {
|
||||
return triggeringEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordSRVPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public GameChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public MinecraftComponent getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public MinecraftComponent getAnnotatedMessage() {
|
||||
return annotatedMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProcessed() {
|
||||
return annotatedMessage != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(MinecraftComponent annotatedMessage) {
|
||||
if (isProcessed()) {
|
||||
throw new IllegalStateException("Already processed");
|
||||
}
|
||||
this.annotatedMessage = annotatedMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
@ -24,5 +24,6 @@ import org.bukkit.event.Event;
|
||||
|
||||
public interface IBukkitChatForwarder {
|
||||
|
||||
void publishEvent(Event event, Player player, MinecraftComponent component, boolean cancelled);
|
||||
MinecraftComponent annotateChatMessage(Event event, Player player, MinecraftComponent component);
|
||||
void forwardMessage(Event event, Player player, MinecraftComponent component, boolean cancelled);
|
||||
}
|
||||
|
@ -20,32 +20,69 @@ package com.discordsrv.bukkit.listener.chat;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.bukkit.component.PaperComponentHandle;
|
||||
import com.discordsrv.common.core.logging.Logger;
|
||||
import com.discordsrv.unrelocate.net.kyori.adventure.text.Component;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
public class PaperChatListener implements Listener {
|
||||
|
||||
private static final PaperComponentHandle<AsyncChatEvent> COMPONENT_HANDLE;
|
||||
private static final PaperComponentHandle<AsyncChatEvent> GET_MESSAGE_HANDLE = makeGet();
|
||||
private static final MethodHandle SET_MESSAGE_HANDLE = makeSet();
|
||||
|
||||
static {
|
||||
COMPONENT_HANDLE = new PaperComponentHandle<>(
|
||||
private static PaperComponentHandle<AsyncChatEvent> makeGet() {
|
||||
return new PaperComponentHandle<>(
|
||||
AsyncChatEvent.class,
|
||||
"message",
|
||||
null
|
||||
);
|
||||
}
|
||||
@SuppressWarnings("JavaLangInvokeHandleSignature") // Unrelocate
|
||||
private static MethodHandle makeSet() {
|
||||
try {
|
||||
return MethodHandles.lookup().findVirtual(
|
||||
AsyncChatEvent.class,
|
||||
"message",
|
||||
MethodType.methodType(void.class, Component.class)
|
||||
);
|
||||
} catch (NoSuchMethodException | IllegalAccessException ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final IBukkitChatForwarder listener;
|
||||
private final Logger logger;
|
||||
|
||||
public PaperChatListener(IBukkitChatForwarder listener) {
|
||||
public PaperChatListener(IBukkitChatForwarder listener, Logger logger) {
|
||||
this.listener = listener;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onAsyncChatRender(AsyncChatEvent event) {
|
||||
if (SET_MESSAGE_HANDLE == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftComponent component = GET_MESSAGE_HANDLE.getComponent(event);
|
||||
MinecraftComponent annotated = listener.annotateChatMessage(event, event.getPlayer(), component);
|
||||
if (annotated != null) {
|
||||
try {
|
||||
SET_MESSAGE_HANDLE.invoke(event, annotated.asAdventure());
|
||||
} catch (Throwable t) {
|
||||
logger.debug("Failed to render Minecraft message", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onAsyncChat(AsyncChatEvent event) {
|
||||
MinecraftComponent component = COMPONENT_HANDLE.getComponent(event);
|
||||
listener.publishEvent(event, event.getPlayer(), component, event.isCancelled());
|
||||
public void onAsyncChatForward(AsyncChatEvent event) {
|
||||
MinecraftComponent component = GET_MESSAGE_HANDLE.getComponent(event);
|
||||
listener.forwardMessage(event, event.getPlayer(), component, event.isCancelled());
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ import com.discordsrv.common.config.configurate.manager.MainConfigManager;
|
||||
import com.discordsrv.common.config.configurate.manager.MessagesConfigManager;
|
||||
import com.discordsrv.common.config.messages.MessagesConfig;
|
||||
import com.discordsrv.common.feature.debug.data.OnlineMode;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.minecrafttodiscord.MinecraftToDiscordChatModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.MinecraftToDiscordChatModule;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
|
@ -19,10 +19,12 @@
|
||||
package com.discordsrv.bukkit.listener.chat;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.api.events.message.render.GameChatRenderEvent;
|
||||
import com.discordsrv.api.events.message.receive.game.GameChatMessageReceiveEvent;
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.bukkit.component.PaperComponentHandle;
|
||||
import com.discordsrv.common.abstraction.player.IPlayer;
|
||||
import com.discordsrv.common.core.logging.NamedLogger;
|
||||
import com.discordsrv.common.feature.channel.global.GlobalChannel;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
@ -33,8 +35,8 @@ public class BukkitChatForwarder implements IBukkitChatForwarder {
|
||||
public static Listener get(BukkitDiscordSRV discordSRV) {
|
||||
// TODO: config option
|
||||
//noinspection ConstantConditions,PointlessBooleanExpression
|
||||
if (1 == 2 && PaperComponentHandle.IS_PAPER_ADVENTURE) {
|
||||
return new PaperChatListener(new BukkitChatForwarder(discordSRV));
|
||||
if (1 == 1 && PaperComponentHandle.IS_PAPER_ADVENTURE) {
|
||||
return new PaperChatListener(new BukkitChatForwarder(discordSRV), new NamedLogger(discordSRV, "CHAT_LISTENER"));
|
||||
}
|
||||
|
||||
return new BukkitChatListener(new BukkitChatForwarder(discordSRV));
|
||||
@ -47,7 +49,21 @@ public class BukkitChatForwarder implements IBukkitChatForwarder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishEvent(Event event, Player player, MinecraftComponent component, boolean cancelled) {
|
||||
public MinecraftComponent annotateChatMessage(Event event, Player player, MinecraftComponent component) {
|
||||
IPlayer srvPlayer = discordSRV.playerProvider().player(player);
|
||||
GameChatRenderEvent annotateEvent = new GameChatRenderEvent(
|
||||
event,
|
||||
srvPlayer,
|
||||
new GlobalChannel(discordSRV),
|
||||
component
|
||||
);
|
||||
|
||||
discordSRV.eventBus().publish(annotateEvent);
|
||||
return annotateEvent.getAnnotatedMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forwardMessage(Event event, Player player, MinecraftComponent component, boolean cancelled) {
|
||||
IPlayer srvPlayer = discordSRV.playerProvider().player(player);
|
||||
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
|
||||
new GameChatMessageReceiveEvent(
|
||||
|
@ -24,6 +24,7 @@ import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
|
||||
public class BukkitChatListener implements Listener {
|
||||
|
||||
@ -33,11 +34,22 @@ public class BukkitChatListener implements Listener {
|
||||
this.forwarder = forwarder;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onAsyncPlayerChat(org.bukkit.event.player.AsyncPlayerChatEvent event) {
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void onAsyncPlayerChatAnnotate(AsyncPlayerChatEvent event) {
|
||||
MinecraftComponent component = ComponentUtil.toAPI(
|
||||
BukkitComponentSerializer.legacy().deserialize(event.getMessage()));
|
||||
|
||||
forwarder.publishEvent(event, event.getPlayer(), component, event.isCancelled());
|
||||
MinecraftComponent annotated = forwarder.annotateChatMessage(event, event.getPlayer(), component);
|
||||
if (annotated != null) {
|
||||
event.setMessage(BukkitComponentSerializer.legacy().serialize(ComponentUtil.fromAPI(annotated)));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onAsyncPlayerChatForward(AsyncPlayerChatEvent event) {
|
||||
MinecraftComponent component = ComponentUtil.toAPI(
|
||||
BukkitComponentSerializer.legacy().deserialize(event.getMessage()));
|
||||
|
||||
forwarder.forwardMessage(event, event.getPlayer(), component, event.isCancelled());
|
||||
}
|
||||
}
|
||||
|
@ -71,13 +71,14 @@ import com.discordsrv.common.feature.linking.LinkProvider;
|
||||
import com.discordsrv.common.feature.linking.LinkingModule;
|
||||
import com.discordsrv.common.feature.linking.impl.MinecraftAuthenticationLinker;
|
||||
import com.discordsrv.common.feature.linking.impl.StorageLinker;
|
||||
import com.discordsrv.common.feature.mention.MentionGameRenderingModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.discord.DiscordChatMessageModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.discord.DiscordMessageMirroringModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.JoinMessageModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.LeaveMessageModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.StartMessageModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.StopMessageModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.minecrafttodiscord.MentionCachingModule;
|
||||
import com.discordsrv.common.feature.mention.MentionCachingModule;
|
||||
import com.discordsrv.common.feature.profile.ProfileManager;
|
||||
import com.discordsrv.common.feature.update.UpdateChecker;
|
||||
import com.discordsrv.common.helper.ChannelConfigHelper;
|
||||
@ -593,6 +594,7 @@ public abstract class AbstractDiscordSRV<
|
||||
registerModule(MentionCachingModule::new);
|
||||
registerModule(LinkingModule::new);
|
||||
registerModule(PresenceUpdaterModule::new);
|
||||
registerModule(MentionGameRenderingModule::new);
|
||||
|
||||
// Integrations
|
||||
registerIntegration("com.discordsrv.common.integration.LuckPermsIntegration");
|
||||
|
@ -248,7 +248,7 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
|
||||
switch (outputMode) {
|
||||
default:
|
||||
case MARKDOWN:
|
||||
discord = discordSRV.componentFactory().discordSerializer().serialize(component);
|
||||
discord = discordSRV.componentFactory().discordSerialize(component);
|
||||
break;
|
||||
case ANSI:
|
||||
discord = discordSRV.componentFactory().ansiSerializer().serialize(component);
|
||||
|
@ -113,7 +113,7 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
BaseChannelConfig channelConfig = discordSRV.channelConfig().resolve(null, channel);
|
||||
CC config = channelConfig instanceof IChannelConfig ? (CC) channelConfig : null;
|
||||
CC config = channelConfig != null ? (CC) channelConfig : null;
|
||||
|
||||
if (config != null) {
|
||||
future = discordSRV.destinations().lookupDestination(config.destination(), true, false);
|
||||
@ -203,7 +203,7 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
||||
.applyPlaceholderService()
|
||||
.build();
|
||||
|
||||
return discordSRV.componentFactory().discordSerializer().serialize(ComponentUtil.fromAPI(component));
|
||||
return discordSRV.componentFactory().discordSerialize(ComponentUtil.fromAPI(component));
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,7 +216,7 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
||||
@Override
|
||||
public String getContent(String content) {
|
||||
Component component = GsonComponentSerializer.gson().deserialize(content);
|
||||
return discordSRV.componentFactory().discordSerializer().serialize(component);
|
||||
return discordSRV.componentFactory().discordSerialize(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ package com.discordsrv.common.config.main.channels;
|
||||
import com.discordsrv.common.config.configurate.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.configurate.manager.abstraction.ConfigurateConfigManager;
|
||||
import com.discordsrv.common.config.main.generic.DiscordIgnoresConfig;
|
||||
import com.discordsrv.common.config.main.generic.MentionsConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ -64,9 +63,6 @@ public class DiscordToMinecraftChatConfig {
|
||||
@Comment("Users, bots, roles and webhooks to ignore")
|
||||
public DiscordIgnoresConfig ignores = new DiscordIgnoresConfig();
|
||||
|
||||
@Comment("The representations of Discord mentions in-game")
|
||||
public MentionsConfig mentions = new MentionsConfig();
|
||||
|
||||
@Comment("How should unicode emoji be shown in-game:\n"
|
||||
+ "- hide: hides emojis in-game\n"
|
||||
+ "- show: shows emojis in-game as is (emojis may not be visible without resource packs)\n"
|
||||
|
@ -65,6 +65,9 @@ public class MinecraftToDiscordChatConfig implements IMessageConfig {
|
||||
@ConfigSerializable
|
||||
public static class Mentions {
|
||||
|
||||
@Comment("Should mentions be rendered in Minecraft when sent in Minecraft")
|
||||
public boolean renderMentionsInGame = true;
|
||||
|
||||
@Comment("If role mentions should be rendered on Discord\n\n"
|
||||
+ "The player needs one of the below permission to trigger notifications:\n"
|
||||
+ "- discordsrv.mention.roles.mentionable (for roles which have \"Allow anyone to @mention this role\" enabled)\n"
|
||||
@ -87,6 +90,9 @@ public class MinecraftToDiscordChatConfig implements IMessageConfig {
|
||||
+ "The player needs the discordsrv.mention.everyone permission to render the mention and trigger a notification")
|
||||
public boolean everyone = false;
|
||||
|
||||
public boolean anyCaching() {
|
||||
return roles || channels || users;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.common.config.main.channels.base;
|
||||
|
||||
import com.discordsrv.common.config.configurate.annotation.Order;
|
||||
import com.discordsrv.common.config.main.channels.*;
|
||||
import com.discordsrv.common.config.main.generic.MentionsConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ -32,6 +33,9 @@ public class BaseChannelConfig {
|
||||
@Order(0)
|
||||
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
|
||||
|
||||
@Comment("The representations of Discord mentions in-game")
|
||||
public MentionsConfig mentions = new MentionsConfig();
|
||||
|
||||
public JoinMessageConfig joinMessages() {
|
||||
return new JoinMessageConfig();
|
||||
}
|
||||
|
@ -33,9 +33,14 @@ public class MentionsConfig {
|
||||
"[hover:show_text:Click to go to channel][click:open_url:%channel_jump_url%][color:#5865F2]#%channel_name%",
|
||||
"[color:#5865F2]#Unknown"
|
||||
);
|
||||
public Format user = new Format(
|
||||
"[hover:show_text:Username: @%user_tag%\nRoles: %user_roles:', '|text:'[color:gray][italics:on]None[color][italics]'%][color:#5865F2]@%user_effective_server_name|user_effective_name%",
|
||||
"[color:#5865F2]@Unknown user"
|
||||
public FormatUser user = new FormatUser(
|
||||
"[hover:show_text:Username: @%user_tag% [italics:on][color:gray](Shift+Click to mention)[color][italics:off]\nRoles: %user_roles:', '|text:'[color:gray][italics:on]None[color][italics]'%]"
|
||||
+ "[insert:@%user_tag%][color:#5865F2]"
|
||||
+ "@%user_effective_server_name|user_effective_name%",
|
||||
"[color:#5865F2]@Unknown user",
|
||||
"[hover:show_text:Username: @%user_tag% [italics:on][color:gray](Shift+Click to mention)[color][italics:off]]"
|
||||
+ "[insert:@%user_tag%][color:#5865F2]"
|
||||
+ "@%user_effective_name%"
|
||||
);
|
||||
|
||||
public String messageUrl = "[hover:show_text:Click to go to message][click:open_url:%jump_url%][color:#5865F2]#%channel_name% > ...";
|
||||
@ -71,4 +76,19 @@ public class MentionsConfig {
|
||||
this.unknownFormat = unknownFormat;
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
public static class FormatUser extends Format {
|
||||
|
||||
@Comment("The format shown in-game for users that cannot be linked to a specific Discord server")
|
||||
public String formatGlobal = "";
|
||||
|
||||
@SuppressWarnings("unused") // Configurate
|
||||
public FormatUser() {}
|
||||
|
||||
public FormatUser(String format, String unknownFormat, String globalFormat) {
|
||||
super(format, unknownFormat);
|
||||
this.formatGlobal = globalFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,19 +22,28 @@ import com.discordsrv.api.component.GameTextBuilder;
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.api.component.MinecraftComponentAdapter;
|
||||
import com.discordsrv.api.component.MinecraftComponentFactory;
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordCustomEmoji;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.events.message.process.discord.DiscordChatMessageCustomEmojiRenderEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.generic.MentionsConfig;
|
||||
import com.discordsrv.common.core.component.renderer.DiscordSRVMinecraftRenderer;
|
||||
import com.discordsrv.common.core.component.translation.Translation;
|
||||
import com.discordsrv.common.core.component.translation.TranslationRegistry;
|
||||
import com.discordsrv.common.core.logging.Logger;
|
||||
import com.discordsrv.common.core.logging.NamedLogger;
|
||||
import com.discordsrv.common.util.ComponentUtil;
|
||||
import dev.vankka.enhancedlegacytext.EnhancedLegacyText;
|
||||
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
|
||||
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializerOptions;
|
||||
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializer;
|
||||
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializerOptions;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
@ -155,10 +164,86 @@ public class ComponentFactory implements MinecraftComponentFactory {
|
||||
return EnhancedLegacyText.get().parse(textInput);
|
||||
}
|
||||
|
||||
public Component minecraftSerialize(DiscordGuild guild, DiscordToMinecraftChatConfig config, String discordMessage) {
|
||||
// Mentions
|
||||
|
||||
@NotNull
|
||||
public Component makeChannelMention(long id, MentionsConfig.Format format) {
|
||||
JDA jda = discordSRV.jda();
|
||||
GuildChannel guildChannel = jda != null ? jda.getGuildChannelById(id) : null;
|
||||
|
||||
return DiscordMentionComponent.of("<#" + Long.toUnsignedString(id) + ">").append(ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(guildChannel != null ? format.format : format.unknownFormat)
|
||||
.addContext(guildChannel)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component makeUserMention(long id, MentionsConfig.FormatUser format, DiscordGuild guild) {
|
||||
DiscordUser user = discordSRV.discordAPI().getUserById(id);
|
||||
DiscordGuildMember member = guild.getMemberById(id);
|
||||
|
||||
return DiscordMentionComponent.of("<@" + Long.toUnsignedString(id) + ">").append(ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(user != null ? (member != null ? format.format : format.formatGlobal) : format.unknownFormat)
|
||||
.addContext(user, member)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
public Component makeRoleMention(long id, MentionsConfig.Format format) {
|
||||
DiscordRole role = discordSRV.discordAPI().getRoleById(id);
|
||||
|
||||
return DiscordMentionComponent.of("<@&" + Long.toUnsignedString(id) + ">").append(ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(role != null ? format.format : format.unknownFormat)
|
||||
.addContext(role)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue") // isProcessed = processed is not null
|
||||
public Component makeEmoteMention(long id, MentionsConfig.EmoteBehaviour behaviour) {
|
||||
DiscordCustomEmoji emoji = discordSRV.discordAPI().getEmojiById(id);
|
||||
if (emoji == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DiscordChatMessageCustomEmojiRenderEvent event = new DiscordChatMessageCustomEmojiRenderEvent(emoji);
|
||||
discordSRV.eventBus().publish(event);
|
||||
|
||||
if (event.isProcessed()) {
|
||||
return DiscordMentionComponent.of(emoji.asJDA().getAsMention())
|
||||
.append(ComponentUtil.fromAPI(event.getRenderedEmojiFromProcessing()));
|
||||
}
|
||||
|
||||
switch (behaviour) {
|
||||
case NAME:
|
||||
return DiscordMentionComponent.of(emoji.asJDA().getAsMention()).append(Component.text(":" + emoji.getName() + ":"));
|
||||
case BLANK:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Component minecraftSerialize(DiscordGuild guild, BaseChannelConfig config, String discordMessage) {
|
||||
return DiscordSRVMinecraftRenderer.getWithContext(guild, config, () -> minecraftSerializer().serialize(discordMessage));
|
||||
}
|
||||
|
||||
public String discordSerialize(Component component) {
|
||||
Component mapped = Component.text().append(component).mapChildrenDeep(comp -> {
|
||||
if (comp instanceof DiscordMentionComponent) {
|
||||
return Component.text(((DiscordMentionComponent) comp).mention());
|
||||
}
|
||||
return comp;
|
||||
}).children().get(0);
|
||||
return discordSerializer().serialize(mapped);
|
||||
}
|
||||
|
||||
public MinecraftSerializer minecraftSerializer() {
|
||||
return minecraftSerializer;
|
||||
}
|
||||
|
@ -0,0 +1,364 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.core.component;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.*;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEventSource;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* An Adventure {@link Component} that holds a Discord mention whilst being disguised as a {@link TextComponent}
|
||||
* for compatibility with serializers that don't know how to deal with custom Component types.
|
||||
* <p>
|
||||
* Possibly removable after <a href="https://github.com/KyoriPowered/adventure/pull/842">adventure #842</a>
|
||||
*
|
||||
* @see ComponentFactory#discordSerialize(Component)
|
||||
*/
|
||||
public class DiscordMentionComponent implements TextComponent {
|
||||
|
||||
@NotNull
|
||||
public static DiscordMentionComponent of(@NotNull String mention) {
|
||||
return new DiscordMentionComponent(new ArrayList<>(), Style.empty(), mention);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Builder builder(@NotNull String mention) {
|
||||
return new Builder(new ArrayList<>(), Style.empty(), mention);
|
||||
}
|
||||
|
||||
private final List<Component> children;
|
||||
private final Style style;
|
||||
private final String mention;
|
||||
|
||||
private DiscordMentionComponent(List<? extends ComponentLike> children, Style style, String mention) {
|
||||
this.children = ComponentLike.asComponents(children, IS_NOT_EMPTY);
|
||||
this.style = style;
|
||||
this.mention = mention;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // NOOP
|
||||
public @NotNull String content() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // NOOP
|
||||
public @NotNull DiscordMentionComponent content(@NotNull String content) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component asComponent() {
|
||||
return TextComponent.super.asComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Builder toBuilder() {
|
||||
return new Builder(children, style, mention);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Unmodifiable @NotNull List<Component> children() {
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DiscordMentionComponent children(@NotNull List<? extends ComponentLike> children) {
|
||||
return new DiscordMentionComponent(children, style, mention);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Style style() {
|
||||
return style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DiscordMentionComponent style(@NotNull Style style) {
|
||||
return new DiscordMentionComponent(children, style, mention);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String mention() {
|
||||
return mention;
|
||||
}
|
||||
|
||||
public @NotNull DiscordMentionComponent mention(@NotNull String mention) {
|
||||
return new DiscordMentionComponent(children, style, mention);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DiscordMentionComponent{" +
|
||||
"children=" + children +
|
||||
", style=" + style +
|
||||
", mention='" + mention + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static class Builder implements TextComponent.Builder {
|
||||
|
||||
private final List<Component> children;
|
||||
private Style.Builder styleBuilder;
|
||||
private String mention;
|
||||
|
||||
private Builder(List<Component> children, Style style, String mention) {
|
||||
this.children = children;
|
||||
this.styleBuilder = style.toBuilder();
|
||||
this.mention = mention;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated // NOOP
|
||||
public @NotNull String content() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@Deprecated // NOOP
|
||||
public DiscordMentionComponent.Builder content(@NotNull String content) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder append(@NotNull Component component) {
|
||||
if (component == Component.empty()) return this;
|
||||
this.children.add(component);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder append(@NotNull Component @NotNull ... components) {
|
||||
for (Component component : components) {
|
||||
append(component);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder append(@NotNull ComponentLike @NotNull ... components) {
|
||||
for (ComponentLike component : components) {
|
||||
append(component);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder append(@NotNull Iterable<? extends ComponentLike> components) {
|
||||
for (Component child : children) {
|
||||
append(child);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<Component> children() {
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder style(@NotNull Style style) {
|
||||
this.styleBuilder = style.toBuilder();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder style(@NotNull Consumer<Style.Builder> consumer) {
|
||||
consumer.accept(styleBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder font(@Nullable Key font) {
|
||||
styleBuilder.font(font);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder color(@Nullable TextColor color) {
|
||||
styleBuilder.color(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder colorIfAbsent(@Nullable TextColor color) {
|
||||
styleBuilder.color(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder decoration(@NotNull TextDecoration decoration, TextDecoration.State state) {
|
||||
styleBuilder.decoration(decoration, state);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder decorationIfAbsent(@NotNull TextDecoration decoration, TextDecoration.State state) {
|
||||
styleBuilder.decorationIfAbsent(decoration, state);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder clickEvent(@Nullable ClickEvent event) {
|
||||
styleBuilder.clickEvent(event);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder hoverEvent(@Nullable HoverEventSource<?> source) {
|
||||
styleBuilder.hoverEvent(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder insertion(@Nullable String insertion) {
|
||||
styleBuilder.insertion(insertion);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder mergeStyle(@NotNull Component that, @NotNull Set<Style.Merge> merges) {
|
||||
styleBuilder.merge(that.style(), merges);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder resetStyle() {
|
||||
styleBuilder = Style.style();
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String mention() {
|
||||
return mention;
|
||||
}
|
||||
|
||||
public DiscordMentionComponent.@NotNull Builder mention(@NotNull String mention) {
|
||||
this.mention = mention;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DiscordMentionComponent build() {
|
||||
return new DiscordMentionComponent(children, styleBuilder.build(), mention);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (c) 2017-2023 KyoriPowered
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder applyDeep(@NotNull Consumer<? super ComponentBuilder<?, ?>> action) {
|
||||
this.apply(action);
|
||||
if (this.children == Collections.<Component>emptyList()) {
|
||||
return this;
|
||||
}
|
||||
ListIterator<Component> it = this.children.listIterator();
|
||||
while (it.hasNext()) {
|
||||
final Component child = it.next();
|
||||
if (!(child instanceof BuildableComponent<?, ?>)) {
|
||||
continue;
|
||||
}
|
||||
final ComponentBuilder<?, ?> childBuilder = ((BuildableComponent<?, ?>) child).toBuilder();
|
||||
childBuilder.applyDeep(action);
|
||||
it.set(childBuilder.build());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder mapChildren(
|
||||
@NotNull Function<BuildableComponent<?, ?>, ? extends BuildableComponent<?, ?>> function) {
|
||||
if (this.children == Collections.<Component>emptyList()) {
|
||||
return this;
|
||||
}
|
||||
final ListIterator<Component> it = this.children.listIterator();
|
||||
while (it.hasNext()) {
|
||||
final Component child = it.next();
|
||||
if (!(child instanceof BuildableComponent<?, ?>)) {
|
||||
continue;
|
||||
}
|
||||
final BuildableComponent<?, ?> mappedChild = requireNonNull(function.apply((BuildableComponent<?, ?>) child), "mappedChild");
|
||||
if (child == mappedChild) {
|
||||
continue;
|
||||
}
|
||||
it.set(mappedChild);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordMentionComponent.@NotNull Builder mapChildrenDeep(
|
||||
@NotNull Function<BuildableComponent<?, ?>, ? extends BuildableComponent<?, ?>> function) {
|
||||
if (this.children == Collections.<Component>emptyList()) {
|
||||
return this;
|
||||
}
|
||||
final ListIterator<Component> it = this.children.listIterator();
|
||||
while (it.hasNext()) {
|
||||
final Component child = it.next();
|
||||
if (!(child instanceof BuildableComponent<?, ?>)) {
|
||||
continue;
|
||||
}
|
||||
final BuildableComponent<?, ?> mappedChild = requireNonNull(function.apply((BuildableComponent<?, ?>) child), "mappedChild");
|
||||
if (mappedChild.children().isEmpty()) {
|
||||
if (child == mappedChild) {
|
||||
continue;
|
||||
}
|
||||
it.set(mappedChild);
|
||||
} else {
|
||||
final ComponentBuilder<?, ?> builder = mappedChild.toBuilder();
|
||||
builder.mapChildrenDeep(function);
|
||||
it.set(builder.build());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,14 +18,9 @@
|
||||
|
||||
package com.discordsrv.common.core.component.renderer;
|
||||
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordCustomEmoji;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.events.message.process.discord.DiscordChatMessageCustomEmojiRenderEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.generic.MentionsConfig;
|
||||
import com.discordsrv.common.util.ComponentUtil;
|
||||
import dev.vankka.mcdiscordreserializer.renderer.implementation.DefaultMinecraftRenderer;
|
||||
@ -35,7 +30,6 @@ import net.dv8tion.jda.api.utils.MiscUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
@ -53,7 +47,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
|
||||
public static <T> T getWithContext(
|
||||
DiscordGuild guild,
|
||||
DiscordToMinecraftChatConfig config,
|
||||
BaseChannelConfig config,
|
||||
Supplier<T> supplier
|
||||
) {
|
||||
Context oldValue = CONTEXT.get();
|
||||
@ -116,57 +110,20 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
return component.append(Component.text("<#" + id + ">"));
|
||||
}
|
||||
|
||||
Component mention = makeChannelMention(MiscUtil.parseLong(id), format);
|
||||
if (mention == null) {
|
||||
return component;
|
||||
}
|
||||
|
||||
return component.append(mention);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Component makeChannelMention(long id, MentionsConfig.Format format) {
|
||||
JDA jda = discordSRV.jda();
|
||||
if (jda == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GuildChannel guildChannel = jda.getGuildChannelById(id);
|
||||
|
||||
return ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(guildChannel != null ? format.format : format.unknownFormat)
|
||||
.addContext(guildChannel)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
);
|
||||
return component.append(discordSRV.componentFactory().makeChannelMention(MiscUtil.parseLong(id), format));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component appendUserMention(@NotNull Component component, @NotNull String id) {
|
||||
Context context = CONTEXT.get();
|
||||
MentionsConfig.Format format = context != null ? context.config.mentions.user : null;
|
||||
MentionsConfig.FormatUser format = context != null ? context.config.mentions.user : null;
|
||||
DiscordGuild guild = context != null ? context.guild : null;
|
||||
if (format == null || guild == null) {
|
||||
return component.append(Component.text("<@" + id + ">"));
|
||||
}
|
||||
|
||||
long userId = MiscUtil.parseLong(id);
|
||||
return component.append(makeUserMention(userId, format, guild));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component makeUserMention(long id, MentionsConfig.Format format, DiscordGuild guild) {
|
||||
DiscordUser user = discordSRV.discordAPI().getUserById(id);
|
||||
DiscordGuildMember member = guild.getMemberById(id);
|
||||
|
||||
return ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(user != null ? format.format : format.unknownFormat)
|
||||
.addContext(user, member)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
);
|
||||
return component.append(discordSRV.componentFactory().makeUserMention(userId, format, guild));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -178,19 +135,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
}
|
||||
|
||||
long roleId = MiscUtil.parseLong(id);
|
||||
return component.append(makeRoleMention(roleId, format));
|
||||
}
|
||||
|
||||
public Component makeRoleMention(long id, MentionsConfig.Format format) {
|
||||
DiscordRole role = discordSRV.discordAPI().getRoleById(id);
|
||||
|
||||
return ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(role != null ? format.format : format.unknownFormat)
|
||||
.addContext(role)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
);
|
||||
return component.append(discordSRV.componentFactory().makeRoleMention(roleId, format));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -206,7 +151,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
}
|
||||
|
||||
long emojiId = MiscUtil.parseLong(id);
|
||||
Component emoteMention = makeEmoteMention(emojiId, behaviour);
|
||||
Component emoteMention = discordSRV.componentFactory().makeEmoteMention(emojiId, behaviour);
|
||||
if (emoteMention == null) {
|
||||
return component;
|
||||
}
|
||||
@ -214,34 +159,12 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
return component.append(emoteMention);
|
||||
}
|
||||
|
||||
public Component makeEmoteMention(long id, MentionsConfig.EmoteBehaviour behaviour) {
|
||||
DiscordCustomEmoji emoji = discordSRV.discordAPI().getEmojiById(id);
|
||||
if (emoji == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DiscordChatMessageCustomEmojiRenderEvent event = new DiscordChatMessageCustomEmojiRenderEvent(emoji);
|
||||
discordSRV.eventBus().publish(event);
|
||||
|
||||
if (event.isProcessed()) {
|
||||
return ComponentUtil.fromAPI(event.getRenderedEmojiFromProcessing());
|
||||
}
|
||||
|
||||
switch (behaviour) {
|
||||
case NAME:
|
||||
return Component.text(":" + emoji.getName() + ":");
|
||||
case BLANK:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Context {
|
||||
|
||||
private final DiscordGuild guild;
|
||||
private final DiscordToMinecraftChatConfig config;
|
||||
private final BaseChannelConfig config;
|
||||
|
||||
public Context(DiscordGuild guild, DiscordToMinecraftChatConfig config) {
|
||||
public Context(DiscordGuild guild, BaseChannelConfig config) {
|
||||
this.guild = guild;
|
||||
this.config = config;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public class ComponentResultStringifier implements PlaceholderResultMapper {
|
||||
case PLAIN:
|
||||
return discordSRV.componentFactory().plainSerializer().serialize(component);
|
||||
case DISCORD:
|
||||
return new FormattedText(discordSRV.componentFactory().discordSerializer().serialize(component));
|
||||
return new FormattedText(discordSRV.componentFactory().discordSerialize(component));
|
||||
case ANSI:
|
||||
return discordSRV.componentFactory().ansiSerializer().serialize(component);
|
||||
case LEGACY:
|
||||
|
@ -282,7 +282,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
||||
return null;
|
||||
}
|
||||
|
||||
Component component = discordSRV.componentFactory().minecraftSerialize(getGuild(), config.discordToMinecraft, content);
|
||||
Component component = discordSRV.componentFactory().minecraftSerialize(getGuild(), config, content);
|
||||
|
||||
String replyFormat = config.discordToMinecraft.replyFormat;
|
||||
return ComponentUtil.fromAPI(
|
||||
|
@ -71,7 +71,7 @@ public class ConsoleMessage {
|
||||
|
||||
public String asMarkdown() {
|
||||
Component component = builder.build();
|
||||
return discordSRV.componentFactory().discordSerializer().serialize(component);
|
||||
return discordSRV.componentFactory().discordSerialize(component);
|
||||
}
|
||||
|
||||
public String asAnsi() {
|
||||
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.feature.mention;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class CachedMention {
|
||||
|
||||
private final Pattern search;
|
||||
private final int searchLength;
|
||||
private final String mention;
|
||||
private final Type type;
|
||||
private final long id;
|
||||
|
||||
public CachedMention(String search, String mention, Type type, long id) {
|
||||
this.search = Pattern.compile(search, Pattern.LITERAL);
|
||||
this.searchLength = search.length();
|
||||
this.mention = mention;
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String plain() {
|
||||
return search.pattern();
|
||||
}
|
||||
|
||||
public Pattern search() {
|
||||
return search;
|
||||
}
|
||||
|
||||
public int searchLength() {
|
||||
return searchLength;
|
||||
}
|
||||
|
||||
public String mention() {
|
||||
return mention;
|
||||
}
|
||||
|
||||
public Type type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public long id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CachedMention that = (CachedMention) o;
|
||||
return type == that.type && id == that.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CachedMention{pattern=" + search.pattern() + ",mention=" + mention + "}";
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
USER,
|
||||
CHANNEL,
|
||||
ROLE
|
||||
}
|
||||
}
|
@ -16,14 +16,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.feature.messageforwarding.game.minecrafttodiscord;
|
||||
package com.discordsrv.common.feature.mention;
|
||||
|
||||
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
|
||||
import com.discordsrv.api.eventbus.Subscribe;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.abstraction.player.IPlayer;
|
||||
import com.discordsrv.common.config.main.channels.MinecraftToDiscordChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.core.module.type.AbstractModule;
|
||||
import com.discordsrv.common.permission.game.Permission;
|
||||
import com.discordsrv.common.util.CompletableFutureUtil;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
@ -40,18 +43,23 @@ import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameE
|
||||
import net.dv8tion.jda.api.events.role.RoleCreateEvent;
|
||||
import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
|
||||
import net.dv8tion.jda.api.events.role.update.RoleUpdateNameEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
private static final Pattern USER_MENTION_PATTERN = Pattern.compile("@[a-z0-9_.]{2,32}");
|
||||
|
||||
private final Map<Long, Map<Long, CachedMention>> memberMentions = new ConcurrentHashMap<>();
|
||||
private final Map<Long, Cache<Long, CachedMention>> memberMentionsCache = new ConcurrentHashMap<>();
|
||||
private final Map<Long, Cache<String, CachedMention>> memberMentionsCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<Long, Map<Long, CachedMention>> roleMentions = new ConcurrentHashMap<>();
|
||||
private final Map<Long, Map<Long, CachedMention>> channelMentions = new ConcurrentHashMap<>();
|
||||
@ -91,8 +99,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
continue;
|
||||
}
|
||||
|
||||
MinecraftToDiscordChatConfig.Mentions mentions = config.mentions;
|
||||
if (mentions.roles || mentions.users || mentions.channels) {
|
||||
if (config.mentions.anyCaching()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -106,10 +113,58 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
channelMentions.clear();
|
||||
}
|
||||
|
||||
public CompletableFuture<List<CachedMention>> lookup(
|
||||
MinecraftToDiscordChatConfig.Mentions config,
|
||||
Guild guild,
|
||||
IPlayer player,
|
||||
Component message
|
||||
) {
|
||||
List<CachedMention> mentions = new ArrayList<>();
|
||||
if (config.users) {
|
||||
mentions.addAll(getMemberMentions(guild).values());
|
||||
}
|
||||
|
||||
List<CompletableFuture<List<CachedMention>>> futures = new ArrayList<>();
|
||||
if (config.users && config.uncachedUsers && player.hasPermission(Permission.MENTION_USER_LOOKUP)) {
|
||||
String messageContent = discordSRV.componentFactory().plainSerializer().serialize(message);
|
||||
Matcher matcher = USER_MENTION_PATTERN.matcher(messageContent);
|
||||
while (matcher.find()) {
|
||||
String mention = matcher.group();
|
||||
boolean perfectMatch = false;
|
||||
for (CachedMention cachedMention : mentions) {
|
||||
if (cachedMention.search().matcher(mention).matches()) {
|
||||
perfectMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!perfectMatch) {
|
||||
futures.add(lookupMemberMentions(guild, mention));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.roles) {
|
||||
mentions.addAll(getRoleMentions(guild).values());
|
||||
}
|
||||
if (config.channels) {
|
||||
mentions.addAll(getChannelMentions(guild).values());
|
||||
}
|
||||
|
||||
return CompletableFutureUtil.combine(futures).thenApply(lists -> {
|
||||
lists.forEach(mentions::addAll);
|
||||
|
||||
// From longest to shortest
|
||||
return mentions.stream()
|
||||
.sorted(Comparator.comparingInt(mention -> ((CachedMention) mention).searchLength()).reversed())
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGuildDelete(GuildLeaveEvent event) {
|
||||
long guildId = event.getGuild().getIdLong();
|
||||
memberMentions.remove(guildId);
|
||||
memberMentionsCache.remove(guildId);
|
||||
roleMentions.remove(guildId);
|
||||
channelMentions.remove(guildId);
|
||||
}
|
||||
@ -118,27 +173,31 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
// Member
|
||||
//
|
||||
|
||||
public CompletableFuture<List<CachedMention>> lookupMemberMentions(Guild guild, String mention) {
|
||||
private CompletableFuture<List<CachedMention>> lookupMemberMentions(Guild guild, String mention) {
|
||||
Cache<String, CachedMention> cache = memberMentionsCache.computeIfAbsent(guild.getIdLong(), key -> discordSRV.caffeineBuilder()
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build()
|
||||
);
|
||||
CachedMention cached = cache.getIfPresent(mention);
|
||||
if (cached != null) {
|
||||
return CompletableFuture.completedFuture(Collections.singletonList(cached));
|
||||
}
|
||||
|
||||
CompletableFuture<List<Member>> memberFuture = new CompletableFuture<>();
|
||||
guild.retrieveMembersByPrefix(mention.substring(1), 100)
|
||||
.onSuccess(memberFuture::complete).onError(memberFuture::completeExceptionally);
|
||||
|
||||
Cache<Long, CachedMention> cache = memberMentionsCache.computeIfAbsent(guild.getIdLong(), key -> discordSRV.caffeineBuilder()
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build()
|
||||
);
|
||||
|
||||
return memberFuture.thenApply(members -> {
|
||||
List<CachedMention> cachedMentions = new ArrayList<>();
|
||||
for (Member member : members) {
|
||||
CachedMention cachedMention = cache.get(member.getIdLong(), k -> convertMember(member));
|
||||
CachedMention cachedMention = cache.get(member.getUser().getName(), k -> convertMember(member));
|
||||
cachedMentions.add(cachedMention);
|
||||
}
|
||||
return cachedMentions;
|
||||
});
|
||||
}
|
||||
|
||||
public Map<Long, CachedMention> getMemberMentions(Guild guild) {
|
||||
private Map<Long, CachedMention> getMemberMentions(Guild guild) {
|
||||
return memberMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
||||
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
||||
for (Member member : guild.getMembers()) {
|
||||
@ -152,6 +211,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
return new CachedMention(
|
||||
"@" + member.getUser().getName(),
|
||||
member.getAsMention(),
|
||||
CachedMention.Type.USER,
|
||||
member.getIdLong()
|
||||
);
|
||||
}
|
||||
@ -187,7 +247,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
// Role
|
||||
//
|
||||
|
||||
public Map<Long, CachedMention> getRoleMentions(Guild guild) {
|
||||
private Map<Long, CachedMention> getRoleMentions(Guild guild) {
|
||||
return roleMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
||||
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
||||
for (Role role : guild.getRoles()) {
|
||||
@ -201,6 +261,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
return new CachedMention(
|
||||
"@" + role.getName(),
|
||||
role.getAsMention(),
|
||||
CachedMention.Type.ROLE,
|
||||
role.getIdLong()
|
||||
);
|
||||
}
|
||||
@ -227,7 +288,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
// Channel
|
||||
//
|
||||
|
||||
public Map<Long, CachedMention> getChannelMentions(Guild guild) {
|
||||
private Map<Long, CachedMention> getChannelMentions(Guild guild) {
|
||||
return channelMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
||||
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
||||
for (GuildChannel channel : guild.getChannels()) {
|
||||
@ -246,6 +307,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
return new CachedMention(
|
||||
"#" + channel.getName(),
|
||||
channel.getAsMention(),
|
||||
CachedMention.Type.CHANNEL,
|
||||
channel.getIdLong()
|
||||
);
|
||||
}
|
||||
@ -279,53 +341,4 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
||||
GuildChannel channel = (GuildChannel) event.getChannel();
|
||||
getChannelMentions(event.getGuild()).remove(channel.getIdLong());
|
||||
}
|
||||
|
||||
public static class CachedMention {
|
||||
|
||||
private final Pattern search;
|
||||
private final int searchLength;
|
||||
private final String mention;
|
||||
private final long id;
|
||||
|
||||
public CachedMention(String search, String mention, long id) {
|
||||
this.search = Pattern.compile(search, Pattern.LITERAL);
|
||||
this.searchLength = search.length();
|
||||
this.mention = mention;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Pattern search() {
|
||||
return search;
|
||||
}
|
||||
|
||||
public int searchLength() {
|
||||
return searchLength;
|
||||
}
|
||||
|
||||
public String mention() {
|
||||
return mention;
|
||||
}
|
||||
|
||||
public long id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CachedMention that = (CachedMention) o;
|
||||
return id == that.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CachedMention{pattern=" + search.pattern() + ",mention=" + mention + "}";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.feature.mention;
|
||||
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.eventbus.Subscribe;
|
||||
import com.discordsrv.api.events.message.render.GameChatRenderEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.abstraction.player.IPlayer;
|
||||
import com.discordsrv.common.config.main.channels.MinecraftToDiscordChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.config.main.generic.MentionsConfig;
|
||||
import com.discordsrv.common.core.logging.NamedLogger;
|
||||
import com.discordsrv.common.core.module.type.AbstractModule;
|
||||
import com.discordsrv.common.util.ComponentUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class MentionGameRenderingModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
public MentionGameRenderingModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV, new NamedLogger(discordSRV, "MENTION_ANNOTATION"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
for (BaseChannelConfig channelConfig : discordSRV.channelConfig().getAllChannels()) {
|
||||
MinecraftToDiscordChatConfig config = channelConfig.minecraftToDiscord;
|
||||
if (!config.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MinecraftToDiscordChatConfig.Mentions mentions = config.mentions;
|
||||
if (mentions.renderMentionsInGame && mentions.anyCaching()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameChatAnnotate(GameChatRenderEvent event) {
|
||||
if (checkCancellation(event) || checkProcessor(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BaseChannelConfig config = discordSRV.channelConfig().get(event.getChannel());
|
||||
if (!(config instanceof IChannelConfig) || !config.minecraftToDiscord.mentions.renderMentionsInGame) {
|
||||
return;
|
||||
}
|
||||
|
||||
MentionCachingModule module = discordSRV.getModule(MentionCachingModule.class);
|
||||
if (module == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<DiscordGuildMessageChannel> channels = discordSRV.destinations()
|
||||
.lookupDestination(((IChannelConfig) config).destination(), true, true)
|
||||
.join();
|
||||
Set<DiscordGuild> guilds = new LinkedHashSet<>();
|
||||
for (DiscordGuildMessageChannel channel : channels) {
|
||||
guilds.add(channel.getGuild());
|
||||
}
|
||||
|
||||
Component component = ComponentUtil.fromAPI(event.getMessage());
|
||||
List<CachedMention> cachedMentions = new ArrayList<>();
|
||||
for (DiscordGuild guild : guilds) {
|
||||
cachedMentions.addAll(
|
||||
module.lookup(
|
||||
config.minecraftToDiscord.mentions,
|
||||
guild.asJDA(),
|
||||
(IPlayer) event.getPlayer(),
|
||||
component
|
||||
).join()
|
||||
);
|
||||
}
|
||||
|
||||
MentionsConfig mentionsConfig = config.mentions;
|
||||
DiscordGuild guild = guilds.size() == 1 ? guilds.iterator().next() : null;
|
||||
|
||||
for (CachedMention cachedMention : cachedMentions) {
|
||||
component = component.replaceText(
|
||||
TextReplacementConfig.builder().match(cachedMention.search())
|
||||
.replacement(() -> replacement(cachedMention, mentionsConfig, guild))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
event.process(ComponentUtil.toAPI(component));
|
||||
}
|
||||
|
||||
private Component replacement(CachedMention mention, MentionsConfig config, DiscordGuild guild) {
|
||||
switch (mention.type()) {
|
||||
case ROLE:
|
||||
return discordSRV.componentFactory().makeRoleMention(mention.id(), config.role);
|
||||
case USER:
|
||||
return discordSRV.componentFactory().makeUserMention(mention.id(), config.user, guild);
|
||||
case CHANNEL:
|
||||
return discordSRV.componentFactory().makeChannelMention(mention.id(), config.channel);
|
||||
}
|
||||
return Component.text(mention.plain());
|
||||
}
|
||||
}
|
@ -204,7 +204,7 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
|
||||
return;
|
||||
}
|
||||
|
||||
Component messageComponent = discordSRV.componentFactory().minecraftSerialize(guild, chatConfig, finalMessage);
|
||||
Component messageComponent = discordSRV.componentFactory().minecraftSerialize(guild, channelConfig, finalMessage);
|
||||
if (ComponentUtil.isEmpty(messageComponent) && !attachments) {
|
||||
// Check empty-ness again after rendering
|
||||
return;
|
||||
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.feature.messageforwarding.game.minecrafttodiscord;
|
||||
package com.discordsrv.common.feature.messageforwarding.game;
|
||||
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
||||
@ -31,14 +31,15 @@ import com.discordsrv.api.eventbus.Subscribe;
|
||||
import com.discordsrv.api.events.message.forward.game.GameChatMessageForwardedEvent;
|
||||
import com.discordsrv.api.events.message.receive.game.GameChatMessageReceiveEvent;
|
||||
import com.discordsrv.api.placeholder.format.FormattedText;
|
||||
import com.discordsrv.api.placeholder.format.PlainPlaceholderFormat;
|
||||
import com.discordsrv.api.placeholder.util.Placeholders;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.abstraction.player.IPlayer;
|
||||
import com.discordsrv.common.config.main.channels.MinecraftToDiscordChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.AbstractGameMessageModule;
|
||||
import com.discordsrv.common.feature.mention.CachedMention;
|
||||
import com.discordsrv.common.feature.mention.MentionCachingModule;
|
||||
import com.discordsrv.common.permission.game.Permission;
|
||||
import com.discordsrv.common.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.util.ComponentUtil;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
@ -47,9 +48,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<MinecraftToDiscordChatConfig, GameChatMessageReceiveEvent> {
|
||||
|
||||
@ -114,8 +112,6 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
||||
@Override
|
||||
public void setPlaceholders(MinecraftToDiscordChatConfig config, GameChatMessageReceiveEvent event, SendableDiscordMessage.Formatter formatter) {}
|
||||
|
||||
private final Pattern MENTION_PATTERN = Pattern.compile("@\\S+");
|
||||
|
||||
private CompletableFuture<SendableDiscordMessage> getMessageForGuild(
|
||||
MinecraftToDiscordChatConfig config,
|
||||
SendableDiscordMessage.Builder format,
|
||||
@ -124,29 +120,10 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
||||
IPlayer player,
|
||||
Object[] context
|
||||
) {
|
||||
MinecraftToDiscordChatConfig.Mentions mentionConfig = config.mentions;
|
||||
MentionCachingModule mentionCaching = discordSRV.getModule(MentionCachingModule.class);
|
||||
|
||||
if (mentionCaching != null && mentionConfig.users && mentionConfig.uncachedUsers
|
||||
&& player.hasPermission(Permission.MENTION_USER_LOOKUP)) {
|
||||
List<CompletableFuture<List<MentionCachingModule.CachedMention>>> futures = new ArrayList<>();
|
||||
|
||||
String messageContent = discordSRV.componentFactory().plainSerializer().serialize(message);
|
||||
Matcher matcher = MENTION_PATTERN.matcher(messageContent);
|
||||
while (matcher.find()) {
|
||||
futures.add(mentionCaching.lookupMemberMentions(guild, matcher.group()));
|
||||
}
|
||||
|
||||
if (!futures.isEmpty()) {
|
||||
return CompletableFutureUtil.combine(futures).thenApply(values -> {
|
||||
Set<MentionCachingModule.CachedMention> mentions = new LinkedHashSet<>();
|
||||
for (List<MentionCachingModule.CachedMention> value : values) {
|
||||
mentions.addAll(value);
|
||||
}
|
||||
|
||||
return getMessageForGuildWithMentions(config, format, guild, message, player, context, mentions);
|
||||
});
|
||||
}
|
||||
if (mentionCaching != null) {
|
||||
return mentionCaching.lookup(config.mentions, guild, player, message)
|
||||
.thenApply(mentions -> getMessageForGuildWithMentions(config, format, guild, message, player, context, mentions));
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(getMessageForGuildWithMentions(config, format, guild, message, player, context, null));
|
||||
@ -159,31 +136,9 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
||||
Component message,
|
||||
IPlayer player,
|
||||
Object[] context,
|
||||
Set<MentionCachingModule.CachedMention> memberMentions
|
||||
List<CachedMention> mentions
|
||||
) {
|
||||
MinecraftToDiscordChatConfig.Mentions mentionConfig = config.mentions;
|
||||
Set<MentionCachingModule.CachedMention> mentions = new LinkedHashSet<>();
|
||||
|
||||
if (memberMentions != null) {
|
||||
mentions.addAll(memberMentions);
|
||||
}
|
||||
|
||||
MentionCachingModule mentionCaching = discordSRV.getModule(MentionCachingModule.class);
|
||||
if (mentionCaching != null) {
|
||||
if (mentionConfig.roles) {
|
||||
mentions.addAll(mentionCaching.getRoleMentions(guild).values());
|
||||
}
|
||||
if (mentionConfig.channels) {
|
||||
mentions.addAll(mentionCaching.getChannelMentions(guild).values());
|
||||
}
|
||||
if (mentionConfig.users) {
|
||||
mentions.addAll(mentionCaching.getMemberMentions(guild).values());
|
||||
}
|
||||
}
|
||||
|
||||
List<MentionCachingModule.CachedMention> orderedMentions = mentions.stream()
|
||||
.sorted(Comparator.comparingInt(mention -> ((MentionCachingModule.CachedMention) mention).searchLength()).reversed())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<AllowedMention> allowedMentions = new ArrayList<>();
|
||||
if (mentionConfig.users && player.hasPermission(Permission.MENTION_USER)) {
|
||||
@ -211,29 +166,24 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
||||
.toFormatter()
|
||||
.addContext(context)
|
||||
.addPlaceholder("message", () -> {
|
||||
String convertedComponent = convertComponent(config, message);
|
||||
Placeholders channelMessagePlaceholders = new Placeholders(
|
||||
DiscordFormattingUtil.escapeMentions(convertedComponent));
|
||||
String content = PlainPlaceholderFormat.supplyWith(
|
||||
PlainPlaceholderFormat.Formatting.DISCORD,
|
||||
() -> discordSRV.placeholderService().getResultAsCharSequence(message).toString()
|
||||
);
|
||||
Placeholders messagePlaceholders = new Placeholders(DiscordFormattingUtil.escapeMentions(content));
|
||||
config.contentRegexFilters.forEach(messagePlaceholders::replaceAll);
|
||||
|
||||
// From longest to shortest
|
||||
orderedMentions.forEach(mention -> channelMessagePlaceholders.replaceAll(mention.search(), mention.mention()));
|
||||
if (mentions != null) {
|
||||
mentions.forEach(mention -> messagePlaceholders.replaceAll(mention.search(), mention.mention()));
|
||||
}
|
||||
|
||||
String finalMessage = channelMessagePlaceholders.toString();
|
||||
String finalMessage = messagePlaceholders.toString();
|
||||
return new FormattedText(preventEveryoneMentions(everyone, finalMessage));
|
||||
})
|
||||
.applyPlaceholderService()
|
||||
.build();
|
||||
}
|
||||
|
||||
public String convertComponent(MinecraftToDiscordChatConfig config, Component component) {
|
||||
String content = discordSRV.placeholderService().getResultAsCharSequence(component).toString();
|
||||
|
||||
Placeholders messagePlaceholders = new Placeholders(content);
|
||||
config.contentRegexFilters.forEach(messagePlaceholders::replaceAll);
|
||||
|
||||
return messagePlaceholders.toString();
|
||||
}
|
||||
|
||||
private String preventEveryoneMentions(boolean everyoneAllowed, String message) {
|
||||
if (everyoneAllowed) {
|
||||
// Nothing to do
|
@ -45,7 +45,7 @@ import com.discordsrv.common.core.storage.impl.MemoryStorage;
|
||||
import com.discordsrv.common.feature.console.Console;
|
||||
import com.discordsrv.common.feature.debug.data.OnlineMode;
|
||||
import com.discordsrv.common.feature.debug.data.VersionInfo;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.minecrafttodiscord.MinecraftToDiscordChatModule;
|
||||
import com.discordsrv.common.feature.messageforwarding.game.MinecraftToDiscordChatModule;
|
||||
import dev.vankka.dependencydownload.classpath.ClasspathAppender;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
Loading…
Reference in New Issue
Block a user