mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-25 12:25:15 +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 {
|
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.api.component.MinecraftComponent;
|
||||||
import com.discordsrv.bukkit.component.PaperComponentHandle;
|
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 io.papermc.paper.event.player.AsyncChatEvent;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
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 {
|
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 {
|
private static PaperComponentHandle<AsyncChatEvent> makeGet() {
|
||||||
COMPONENT_HANDLE = new PaperComponentHandle<>(
|
return new PaperComponentHandle<>(
|
||||||
AsyncChatEvent.class,
|
AsyncChatEvent.class,
|
||||||
"message",
|
"message",
|
||||||
null
|
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 IBukkitChatForwarder listener;
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
public PaperChatListener(IBukkitChatForwarder listener) {
|
public PaperChatListener(IBukkitChatForwarder listener, Logger logger) {
|
||||||
this.listener = listener;
|
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)
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
public void onAsyncChat(AsyncChatEvent event) {
|
public void onAsyncChatForward(AsyncChatEvent event) {
|
||||||
MinecraftComponent component = COMPONENT_HANDLE.getComponent(event);
|
MinecraftComponent component = GET_MESSAGE_HANDLE.getComponent(event);
|
||||||
listener.publishEvent(event, event.getPlayer(), component, event.isCancelled());
|
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.configurate.manager.MessagesConfigManager;
|
||||||
import com.discordsrv.common.config.messages.MessagesConfig;
|
import com.discordsrv.common.config.messages.MessagesConfig;
|
||||||
import com.discordsrv.common.feature.debug.data.OnlineMode;
|
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 net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.plugin.ServicePriority;
|
import org.bukkit.plugin.ServicePriority;
|
||||||
|
@ -19,10 +19,12 @@
|
|||||||
package com.discordsrv.bukkit.listener.chat;
|
package com.discordsrv.bukkit.listener.chat;
|
||||||
|
|
||||||
import com.discordsrv.api.component.MinecraftComponent;
|
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.api.events.message.receive.game.GameChatMessageReceiveEvent;
|
||||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||||
import com.discordsrv.bukkit.component.PaperComponentHandle;
|
import com.discordsrv.bukkit.component.PaperComponentHandle;
|
||||||
import com.discordsrv.common.abstraction.player.IPlayer;
|
import com.discordsrv.common.abstraction.player.IPlayer;
|
||||||
|
import com.discordsrv.common.core.logging.NamedLogger;
|
||||||
import com.discordsrv.common.feature.channel.global.GlobalChannel;
|
import com.discordsrv.common.feature.channel.global.GlobalChannel;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
@ -33,8 +35,8 @@ public class BukkitChatForwarder implements IBukkitChatForwarder {
|
|||||||
public static Listener get(BukkitDiscordSRV discordSRV) {
|
public static Listener get(BukkitDiscordSRV discordSRV) {
|
||||||
// TODO: config option
|
// TODO: config option
|
||||||
//noinspection ConstantConditions,PointlessBooleanExpression
|
//noinspection ConstantConditions,PointlessBooleanExpression
|
||||||
if (1 == 2 && PaperComponentHandle.IS_PAPER_ADVENTURE) {
|
if (1 == 1 && PaperComponentHandle.IS_PAPER_ADVENTURE) {
|
||||||
return new PaperChatListener(new BukkitChatForwarder(discordSRV));
|
return new PaperChatListener(new BukkitChatForwarder(discordSRV), new NamedLogger(discordSRV, "CHAT_LISTENER"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BukkitChatListener(new BukkitChatForwarder(discordSRV));
|
return new BukkitChatListener(new BukkitChatForwarder(discordSRV));
|
||||||
@ -47,7 +49,21 @@ public class BukkitChatForwarder implements IBukkitChatForwarder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
IPlayer srvPlayer = discordSRV.playerProvider().player(player);
|
||||||
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
|
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
|
||||||
new GameChatMessageReceiveEvent(
|
new GameChatMessageReceiveEvent(
|
||||||
|
@ -24,6 +24,7 @@ import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
|
|||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||||
|
|
||||||
public class BukkitChatListener implements Listener {
|
public class BukkitChatListener implements Listener {
|
||||||
|
|
||||||
@ -33,11 +34,22 @@ public class BukkitChatListener implements Listener {
|
|||||||
this.forwarder = forwarder;
|
this.forwarder = forwarder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR)
|
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||||
public void onAsyncPlayerChat(org.bukkit.event.player.AsyncPlayerChatEvent event) {
|
public void onAsyncPlayerChatAnnotate(AsyncPlayerChatEvent event) {
|
||||||
MinecraftComponent component = ComponentUtil.toAPI(
|
MinecraftComponent component = ComponentUtil.toAPI(
|
||||||
BukkitComponentSerializer.legacy().deserialize(event.getMessage()));
|
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.LinkingModule;
|
||||||
import com.discordsrv.common.feature.linking.impl.MinecraftAuthenticationLinker;
|
import com.discordsrv.common.feature.linking.impl.MinecraftAuthenticationLinker;
|
||||||
import com.discordsrv.common.feature.linking.impl.StorageLinker;
|
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.DiscordChatMessageModule;
|
||||||
import com.discordsrv.common.feature.messageforwarding.discord.DiscordMessageMirroringModule;
|
import com.discordsrv.common.feature.messageforwarding.discord.DiscordMessageMirroringModule;
|
||||||
import com.discordsrv.common.feature.messageforwarding.game.JoinMessageModule;
|
import com.discordsrv.common.feature.messageforwarding.game.JoinMessageModule;
|
||||||
import com.discordsrv.common.feature.messageforwarding.game.LeaveMessageModule;
|
import com.discordsrv.common.feature.messageforwarding.game.LeaveMessageModule;
|
||||||
import com.discordsrv.common.feature.messageforwarding.game.StartMessageModule;
|
import com.discordsrv.common.feature.messageforwarding.game.StartMessageModule;
|
||||||
import com.discordsrv.common.feature.messageforwarding.game.StopMessageModule;
|
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.profile.ProfileManager;
|
||||||
import com.discordsrv.common.feature.update.UpdateChecker;
|
import com.discordsrv.common.feature.update.UpdateChecker;
|
||||||
import com.discordsrv.common.helper.ChannelConfigHelper;
|
import com.discordsrv.common.helper.ChannelConfigHelper;
|
||||||
@ -593,6 +594,7 @@ public abstract class AbstractDiscordSRV<
|
|||||||
registerModule(MentionCachingModule::new);
|
registerModule(MentionCachingModule::new);
|
||||||
registerModule(LinkingModule::new);
|
registerModule(LinkingModule::new);
|
||||||
registerModule(PresenceUpdaterModule::new);
|
registerModule(PresenceUpdaterModule::new);
|
||||||
|
registerModule(MentionGameRenderingModule::new);
|
||||||
|
|
||||||
// Integrations
|
// Integrations
|
||||||
registerIntegration("com.discordsrv.common.integration.LuckPermsIntegration");
|
registerIntegration("com.discordsrv.common.integration.LuckPermsIntegration");
|
||||||
|
@ -248,7 +248,7 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
|
|||||||
switch (outputMode) {
|
switch (outputMode) {
|
||||||
default:
|
default:
|
||||||
case MARKDOWN:
|
case MARKDOWN:
|
||||||
discord = discordSRV.componentFactory().discordSerializer().serialize(component);
|
discord = discordSRV.componentFactory().discordSerialize(component);
|
||||||
break;
|
break;
|
||||||
case ANSI:
|
case ANSI:
|
||||||
discord = discordSRV.componentFactory().ansiSerializer().serialize(component);
|
discord = discordSRV.componentFactory().ansiSerializer().serialize(component);
|
||||||
|
@ -113,7 +113,7 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
|||||||
}
|
}
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
BaseChannelConfig channelConfig = discordSRV.channelConfig().resolve(null, channel);
|
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) {
|
if (config != null) {
|
||||||
future = discordSRV.destinations().lookupDestination(config.destination(), true, false);
|
future = discordSRV.destinations().lookupDestination(config.destination(), true, false);
|
||||||
@ -203,7 +203,7 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
|||||||
.applyPlaceholderService()
|
.applyPlaceholderService()
|
||||||
.build();
|
.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
|
@Override
|
||||||
public String getContent(String content) {
|
public String getContent(String content) {
|
||||||
Component component = GsonComponentSerializer.gson().deserialize(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.annotation.Untranslated;
|
||||||
import com.discordsrv.common.config.configurate.manager.abstraction.ConfigurateConfigManager;
|
import com.discordsrv.common.config.configurate.manager.abstraction.ConfigurateConfigManager;
|
||||||
import com.discordsrv.common.config.main.generic.DiscordIgnoresConfig;
|
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.ConfigSerializable;
|
||||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||||
|
|
||||||
@ -64,9 +63,6 @@ public class DiscordToMinecraftChatConfig {
|
|||||||
@Comment("Users, bots, roles and webhooks to ignore")
|
@Comment("Users, bots, roles and webhooks to ignore")
|
||||||
public DiscordIgnoresConfig ignores = new DiscordIgnoresConfig();
|
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"
|
@Comment("How should unicode emoji be shown in-game:\n"
|
||||||
+ "- hide: hides emojis 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"
|
+ "- 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
|
@ConfigSerializable
|
||||||
public static class Mentions {
|
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"
|
@Comment("If role mentions should be rendered on Discord\n\n"
|
||||||
+ "The player needs one of the below permission to trigger notifications:\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"
|
+ "- 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")
|
+ "The player needs the discordsrv.mention.everyone permission to render the mention and trigger a notification")
|
||||||
public boolean everyone = false;
|
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.configurate.annotation.Order;
|
||||||
import com.discordsrv.common.config.main.channels.*;
|
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.ConfigSerializable;
|
||||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||||
|
|
||||||
@ -32,6 +33,9 @@ public class BaseChannelConfig {
|
|||||||
@Order(0)
|
@Order(0)
|
||||||
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
|
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
|
||||||
|
|
||||||
|
@Comment("The representations of Discord mentions in-game")
|
||||||
|
public MentionsConfig mentions = new MentionsConfig();
|
||||||
|
|
||||||
public JoinMessageConfig joinMessages() {
|
public JoinMessageConfig joinMessages() {
|
||||||
return new JoinMessageConfig();
|
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%",
|
"[hover:show_text:Click to go to channel][click:open_url:%channel_jump_url%][color:#5865F2]#%channel_name%",
|
||||||
"[color:#5865F2]#Unknown"
|
"[color:#5865F2]#Unknown"
|
||||||
);
|
);
|
||||||
public Format user = new Format(
|
public FormatUser user = new FormatUser(
|
||||||
"[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%",
|
"[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]'%]"
|
||||||
"[color:#5865F2]@Unknown user"
|
+ "[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% > ...";
|
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;
|
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.MinecraftComponent;
|
||||||
import com.discordsrv.api.component.MinecraftComponentAdapter;
|
import com.discordsrv.api.component.MinecraftComponentAdapter;
|
||||||
import com.discordsrv.api.component.MinecraftComponentFactory;
|
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.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.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.renderer.DiscordSRVMinecraftRenderer;
|
||||||
import com.discordsrv.common.core.component.translation.Translation;
|
import com.discordsrv.common.core.component.translation.Translation;
|
||||||
import com.discordsrv.common.core.component.translation.TranslationRegistry;
|
import com.discordsrv.common.core.component.translation.TranslationRegistry;
|
||||||
import com.discordsrv.common.core.logging.Logger;
|
import com.discordsrv.common.core.logging.Logger;
|
||||||
import com.discordsrv.common.core.logging.NamedLogger;
|
import com.discordsrv.common.core.logging.NamedLogger;
|
||||||
|
import com.discordsrv.common.util.ComponentUtil;
|
||||||
import dev.vankka.enhancedlegacytext.EnhancedLegacyText;
|
import dev.vankka.enhancedlegacytext.EnhancedLegacyText;
|
||||||
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
|
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
|
||||||
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializerOptions;
|
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializerOptions;
|
||||||
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializer;
|
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializer;
|
||||||
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializerOptions;
|
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.Component;
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
import net.kyori.adventure.text.TranslatableComponent;
|
||||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||||
@ -155,10 +164,86 @@ public class ComponentFactory implements MinecraftComponentFactory {
|
|||||||
return EnhancedLegacyText.get().parse(textInput);
|
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));
|
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() {
|
public MinecraftSerializer minecraftSerializer() {
|
||||||
return 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;
|
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.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.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.config.main.generic.MentionsConfig;
|
||||||
import com.discordsrv.common.util.ComponentUtil;
|
import com.discordsrv.common.util.ComponentUtil;
|
||||||
import dev.vankka.mcdiscordreserializer.renderer.implementation.DefaultMinecraftRenderer;
|
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.Component;
|
||||||
import net.kyori.adventure.text.event.ClickEvent;
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@ -53,7 +47,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
|||||||
|
|
||||||
public static <T> T getWithContext(
|
public static <T> T getWithContext(
|
||||||
DiscordGuild guild,
|
DiscordGuild guild,
|
||||||
DiscordToMinecraftChatConfig config,
|
BaseChannelConfig config,
|
||||||
Supplier<T> supplier
|
Supplier<T> supplier
|
||||||
) {
|
) {
|
||||||
Context oldValue = CONTEXT.get();
|
Context oldValue = CONTEXT.get();
|
||||||
@ -116,57 +110,20 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
|||||||
return component.append(Component.text("<#" + id + ">"));
|
return component.append(Component.text("<#" + id + ">"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Component mention = makeChannelMention(MiscUtil.parseLong(id), format);
|
return component.append(discordSRV.componentFactory().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()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Component appendUserMention(@NotNull Component component, @NotNull String id) {
|
public @NotNull Component appendUserMention(@NotNull Component component, @NotNull String id) {
|
||||||
Context context = CONTEXT.get();
|
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;
|
DiscordGuild guild = context != null ? context.guild : null;
|
||||||
if (format == null || guild == null) {
|
if (format == null || guild == null) {
|
||||||
return component.append(Component.text("<@" + id + ">"));
|
return component.append(Component.text("<@" + id + ">"));
|
||||||
}
|
}
|
||||||
|
|
||||||
long userId = MiscUtil.parseLong(id);
|
long userId = MiscUtil.parseLong(id);
|
||||||
return component.append(makeUserMention(userId, format, guild));
|
return component.append(discordSRV.componentFactory().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()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -178,19 +135,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long roleId = MiscUtil.parseLong(id);
|
long roleId = MiscUtil.parseLong(id);
|
||||||
return component.append(makeRoleMention(roleId, format));
|
return component.append(discordSRV.componentFactory().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()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -206,7 +151,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long emojiId = MiscUtil.parseLong(id);
|
long emojiId = MiscUtil.parseLong(id);
|
||||||
Component emoteMention = makeEmoteMention(emojiId, behaviour);
|
Component emoteMention = discordSRV.componentFactory().makeEmoteMention(emojiId, behaviour);
|
||||||
if (emoteMention == null) {
|
if (emoteMention == null) {
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
@ -214,34 +159,12 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
|||||||
return component.append(emoteMention);
|
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 static class Context {
|
||||||
|
|
||||||
private final DiscordGuild guild;
|
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.guild = guild;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ public class ComponentResultStringifier implements PlaceholderResultMapper {
|
|||||||
case PLAIN:
|
case PLAIN:
|
||||||
return discordSRV.componentFactory().plainSerializer().serialize(component);
|
return discordSRV.componentFactory().plainSerializer().serialize(component);
|
||||||
case DISCORD:
|
case DISCORD:
|
||||||
return new FormattedText(discordSRV.componentFactory().discordSerializer().serialize(component));
|
return new FormattedText(discordSRV.componentFactory().discordSerialize(component));
|
||||||
case ANSI:
|
case ANSI:
|
||||||
return discordSRV.componentFactory().ansiSerializer().serialize(component);
|
return discordSRV.componentFactory().ansiSerializer().serialize(component);
|
||||||
case LEGACY:
|
case LEGACY:
|
||||||
|
@ -282,7 +282,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component component = discordSRV.componentFactory().minecraftSerialize(getGuild(), config.discordToMinecraft, content);
|
Component component = discordSRV.componentFactory().minecraftSerialize(getGuild(), config, content);
|
||||||
|
|
||||||
String replyFormat = config.discordToMinecraft.replyFormat;
|
String replyFormat = config.discordToMinecraft.replyFormat;
|
||||||
return ComponentUtil.fromAPI(
|
return ComponentUtil.fromAPI(
|
||||||
|
@ -71,7 +71,7 @@ public class ConsoleMessage {
|
|||||||
|
|
||||||
public String asMarkdown() {
|
public String asMarkdown() {
|
||||||
Component component = builder.build();
|
Component component = builder.build();
|
||||||
return discordSRV.componentFactory().discordSerializer().serialize(component);
|
return discordSRV.componentFactory().discordSerialize(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String asAnsi() {
|
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/>.
|
* 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.discord.connection.details.DiscordGatewayIntent;
|
||||||
import com.discordsrv.api.eventbus.Subscribe;
|
import com.discordsrv.api.eventbus.Subscribe;
|
||||||
import com.discordsrv.common.DiscordSRV;
|
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.MinecraftToDiscordChatConfig;
|
||||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||||
import com.discordsrv.common.core.module.type.AbstractModule;
|
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 com.github.benmanes.caffeine.cache.Cache;
|
||||||
import net.dv8tion.jda.api.entities.Guild;
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
import net.dv8tion.jda.api.entities.Member;
|
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.RoleCreateEvent;
|
||||||
import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
|
import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
|
||||||
import net.dv8tion.jda.api.events.role.update.RoleUpdateNameEvent;
|
import net.dv8tion.jda.api.events.role.update.RoleUpdateNameEvent;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
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, 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>> roleMentions = new ConcurrentHashMap<>();
|
||||||
private final Map<Long, Map<Long, CachedMention>> channelMentions = new ConcurrentHashMap<>();
|
private final Map<Long, Map<Long, CachedMention>> channelMentions = new ConcurrentHashMap<>();
|
||||||
@ -91,8 +99,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftToDiscordChatConfig.Mentions mentions = config.mentions;
|
if (config.mentions.anyCaching()) {
|
||||||
if (mentions.roles || mentions.users || mentions.channels) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,10 +113,58 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
channelMentions.clear();
|
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
|
@Subscribe
|
||||||
public void onGuildDelete(GuildLeaveEvent event) {
|
public void onGuildDelete(GuildLeaveEvent event) {
|
||||||
long guildId = event.getGuild().getIdLong();
|
long guildId = event.getGuild().getIdLong();
|
||||||
memberMentions.remove(guildId);
|
memberMentions.remove(guildId);
|
||||||
|
memberMentionsCache.remove(guildId);
|
||||||
roleMentions.remove(guildId);
|
roleMentions.remove(guildId);
|
||||||
channelMentions.remove(guildId);
|
channelMentions.remove(guildId);
|
||||||
}
|
}
|
||||||
@ -118,27 +173,31 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
// Member
|
// 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<>();
|
CompletableFuture<List<Member>> memberFuture = new CompletableFuture<>();
|
||||||
guild.retrieveMembersByPrefix(mention.substring(1), 100)
|
guild.retrieveMembersByPrefix(mention.substring(1), 100)
|
||||||
.onSuccess(memberFuture::complete).onError(memberFuture::completeExceptionally);
|
.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 -> {
|
return memberFuture.thenApply(members -> {
|
||||||
List<CachedMention> cachedMentions = new ArrayList<>();
|
List<CachedMention> cachedMentions = new ArrayList<>();
|
||||||
for (Member member : members) {
|
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);
|
cachedMentions.add(cachedMention);
|
||||||
}
|
}
|
||||||
return cachedMentions;
|
return cachedMentions;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Long, CachedMention> getMemberMentions(Guild guild) {
|
private Map<Long, CachedMention> getMemberMentions(Guild guild) {
|
||||||
return memberMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
return memberMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
||||||
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
||||||
for (Member member : guild.getMembers()) {
|
for (Member member : guild.getMembers()) {
|
||||||
@ -152,6 +211,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
return new CachedMention(
|
return new CachedMention(
|
||||||
"@" + member.getUser().getName(),
|
"@" + member.getUser().getName(),
|
||||||
member.getAsMention(),
|
member.getAsMention(),
|
||||||
|
CachedMention.Type.USER,
|
||||||
member.getIdLong()
|
member.getIdLong()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -187,7 +247,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
// Role
|
// Role
|
||||||
//
|
//
|
||||||
|
|
||||||
public Map<Long, CachedMention> getRoleMentions(Guild guild) {
|
private Map<Long, CachedMention> getRoleMentions(Guild guild) {
|
||||||
return roleMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
return roleMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
||||||
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
||||||
for (Role role : guild.getRoles()) {
|
for (Role role : guild.getRoles()) {
|
||||||
@ -201,6 +261,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
return new CachedMention(
|
return new CachedMention(
|
||||||
"@" + role.getName(),
|
"@" + role.getName(),
|
||||||
role.getAsMention(),
|
role.getAsMention(),
|
||||||
|
CachedMention.Type.ROLE,
|
||||||
role.getIdLong()
|
role.getIdLong()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -227,7 +288,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
// Channel
|
// Channel
|
||||||
//
|
//
|
||||||
|
|
||||||
public Map<Long, CachedMention> getChannelMentions(Guild guild) {
|
private Map<Long, CachedMention> getChannelMentions(Guild guild) {
|
||||||
return channelMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
return channelMentions.computeIfAbsent(guild.getIdLong(), key -> {
|
||||||
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
Map<Long, CachedMention> mentions = new LinkedHashMap<>();
|
||||||
for (GuildChannel channel : guild.getChannels()) {
|
for (GuildChannel channel : guild.getChannels()) {
|
||||||
@ -246,6 +307,7 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
return new CachedMention(
|
return new CachedMention(
|
||||||
"#" + channel.getName(),
|
"#" + channel.getName(),
|
||||||
channel.getAsMention(),
|
channel.getAsMention(),
|
||||||
|
CachedMention.Type.CHANNEL,
|
||||||
channel.getIdLong()
|
channel.getIdLong()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -279,53 +341,4 @@ public class MentionCachingModule extends AbstractModule<DiscordSRV> {
|
|||||||
GuildChannel channel = (GuildChannel) event.getChannel();
|
GuildChannel channel = (GuildChannel) event.getChannel();
|
||||||
getChannelMentions(event.getGuild()).remove(channel.getIdLong());
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component messageComponent = discordSRV.componentFactory().minecraftSerialize(guild, chatConfig, finalMessage);
|
Component messageComponent = discordSRV.componentFactory().minecraftSerialize(guild, channelConfig, finalMessage);
|
||||||
if (ComponentUtil.isEmpty(messageComponent) && !attachments) {
|
if (ComponentUtil.isEmpty(messageComponent) && !attachments) {
|
||||||
// Check empty-ness again after rendering
|
// Check empty-ness again after rendering
|
||||||
return;
|
return;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.channel.GameChannel;
|
||||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
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.forward.game.GameChatMessageForwardedEvent;
|
||||||
import com.discordsrv.api.events.message.receive.game.GameChatMessageReceiveEvent;
|
import com.discordsrv.api.events.message.receive.game.GameChatMessageReceiveEvent;
|
||||||
import com.discordsrv.api.placeholder.format.FormattedText;
|
import com.discordsrv.api.placeholder.format.FormattedText;
|
||||||
|
import com.discordsrv.api.placeholder.format.PlainPlaceholderFormat;
|
||||||
import com.discordsrv.api.placeholder.util.Placeholders;
|
import com.discordsrv.api.placeholder.util.Placeholders;
|
||||||
import com.discordsrv.common.DiscordSRV;
|
import com.discordsrv.common.DiscordSRV;
|
||||||
import com.discordsrv.common.abstraction.player.IPlayer;
|
import com.discordsrv.common.abstraction.player.IPlayer;
|
||||||
import com.discordsrv.common.config.main.channels.MinecraftToDiscordChatConfig;
|
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.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.permission.game.Permission;
|
||||||
import com.discordsrv.common.util.CompletableFutureUtil;
|
|
||||||
import com.discordsrv.common.util.ComponentUtil;
|
import com.discordsrv.common.util.ComponentUtil;
|
||||||
import net.dv8tion.jda.api.entities.Guild;
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
import net.dv8tion.jda.api.entities.Role;
|
import net.dv8tion.jda.api.entities.Role;
|
||||||
@ -47,9 +48,6 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
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> {
|
public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<MinecraftToDiscordChatConfig, GameChatMessageReceiveEvent> {
|
||||||
|
|
||||||
@ -114,8 +112,6 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
|||||||
@Override
|
@Override
|
||||||
public void setPlaceholders(MinecraftToDiscordChatConfig config, GameChatMessageReceiveEvent event, SendableDiscordMessage.Formatter formatter) {}
|
public void setPlaceholders(MinecraftToDiscordChatConfig config, GameChatMessageReceiveEvent event, SendableDiscordMessage.Formatter formatter) {}
|
||||||
|
|
||||||
private final Pattern MENTION_PATTERN = Pattern.compile("@\\S+");
|
|
||||||
|
|
||||||
private CompletableFuture<SendableDiscordMessage> getMessageForGuild(
|
private CompletableFuture<SendableDiscordMessage> getMessageForGuild(
|
||||||
MinecraftToDiscordChatConfig config,
|
MinecraftToDiscordChatConfig config,
|
||||||
SendableDiscordMessage.Builder format,
|
SendableDiscordMessage.Builder format,
|
||||||
@ -124,29 +120,10 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
|||||||
IPlayer player,
|
IPlayer player,
|
||||||
Object[] context
|
Object[] context
|
||||||
) {
|
) {
|
||||||
MinecraftToDiscordChatConfig.Mentions mentionConfig = config.mentions;
|
|
||||||
MentionCachingModule mentionCaching = discordSRV.getModule(MentionCachingModule.class);
|
MentionCachingModule mentionCaching = discordSRV.getModule(MentionCachingModule.class);
|
||||||
|
if (mentionCaching != null) {
|
||||||
if (mentionCaching != null && mentionConfig.users && mentionConfig.uncachedUsers
|
return mentionCaching.lookup(config.mentions, guild, player, message)
|
||||||
&& player.hasPermission(Permission.MENTION_USER_LOOKUP)) {
|
.thenApply(mentions -> getMessageForGuildWithMentions(config, format, guild, message, player, context, mentions));
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletableFuture.completedFuture(getMessageForGuildWithMentions(config, format, guild, message, player, context, null));
|
return CompletableFuture.completedFuture(getMessageForGuildWithMentions(config, format, guild, message, player, context, null));
|
||||||
@ -159,31 +136,9 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
|||||||
Component message,
|
Component message,
|
||||||
IPlayer player,
|
IPlayer player,
|
||||||
Object[] context,
|
Object[] context,
|
||||||
Set<MentionCachingModule.CachedMention> memberMentions
|
List<CachedMention> mentions
|
||||||
) {
|
) {
|
||||||
MinecraftToDiscordChatConfig.Mentions mentionConfig = config.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<>();
|
List<AllowedMention> allowedMentions = new ArrayList<>();
|
||||||
if (mentionConfig.users && player.hasPermission(Permission.MENTION_USER)) {
|
if (mentionConfig.users && player.hasPermission(Permission.MENTION_USER)) {
|
||||||
@ -211,29 +166,24 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
|||||||
.toFormatter()
|
.toFormatter()
|
||||||
.addContext(context)
|
.addContext(context)
|
||||||
.addPlaceholder("message", () -> {
|
.addPlaceholder("message", () -> {
|
||||||
String convertedComponent = convertComponent(config, message);
|
String content = PlainPlaceholderFormat.supplyWith(
|
||||||
Placeholders channelMessagePlaceholders = new Placeholders(
|
PlainPlaceholderFormat.Formatting.DISCORD,
|
||||||
DiscordFormattingUtil.escapeMentions(convertedComponent));
|
() -> discordSRV.placeholderService().getResultAsCharSequence(message).toString()
|
||||||
|
);
|
||||||
|
Placeholders messagePlaceholders = new Placeholders(DiscordFormattingUtil.escapeMentions(content));
|
||||||
|
config.contentRegexFilters.forEach(messagePlaceholders::replaceAll);
|
||||||
|
|
||||||
// From longest to shortest
|
if (mentions != null) {
|
||||||
orderedMentions.forEach(mention -> channelMessagePlaceholders.replaceAll(mention.search(), mention.mention()));
|
mentions.forEach(mention -> messagePlaceholders.replaceAll(mention.search(), mention.mention()));
|
||||||
|
}
|
||||||
|
|
||||||
String finalMessage = channelMessagePlaceholders.toString();
|
String finalMessage = messagePlaceholders.toString();
|
||||||
return new FormattedText(preventEveryoneMentions(everyone, finalMessage));
|
return new FormattedText(preventEveryoneMentions(everyone, finalMessage));
|
||||||
})
|
})
|
||||||
.applyPlaceholderService()
|
.applyPlaceholderService()
|
||||||
.build();
|
.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) {
|
private String preventEveryoneMentions(boolean everyoneAllowed, String message) {
|
||||||
if (everyoneAllowed) {
|
if (everyoneAllowed) {
|
||||||
// Nothing to do
|
// 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.console.Console;
|
||||||
import com.discordsrv.common.feature.debug.data.OnlineMode;
|
import com.discordsrv.common.feature.debug.data.OnlineMode;
|
||||||
import com.discordsrv.common.feature.debug.data.VersionInfo;
|
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 dev.vankka.dependencydownload.classpath.ClasspathAppender;
|
||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
Loading…
Reference in New Issue
Block a user