Add options for how to handle emojis from Discord, add event for rendering custom emoji to Minecraft components to API

This commit is contained in:
Vankka 2023-10-27 20:12:24 +03:00
parent ac84845d67
commit 39036e2b21
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
10 changed files with 189 additions and 11 deletions

View File

@ -25,6 +25,7 @@ package com.discordsrv.api.discord;
import com.discordsrv.api.discord.entity.DiscordUser;
import com.discordsrv.api.discord.entity.channel.*;
import com.discordsrv.api.discord.entity.guild.DiscordCustomEmoji;
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.entity.guild.DiscordRole;
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
@ -143,6 +144,13 @@ public interface DiscordAPI {
@Nullable
DiscordRole getRoleById(long id);
/**
* Gets a custom emoji for a Discord server by id, the provided entity should not be stored for long periods of time.
* @param id the id for the custom emoji
* @return the Discord custom emoji
*/
DiscordCustomEmoji getEmojiById(long id);
/**
* Registers a Discord command.
* @param command the command to register

View File

@ -0,0 +1,54 @@
package com.discordsrv.api.event.events.message.process.discord;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.discord.entity.guild.DiscordCustomEmoji;
import com.discordsrv.api.event.events.Event;
import com.discordsrv.api.event.events.Processable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Renders a given {@link DiscordCustomEmoji} into a {@link MinecraftComponent} that can be displayed in Minecraft.
* @see #process(MinecraftComponent)
*/
public class DiscordChatMessageCustomEmojiRenderEvent implements Event, Processable.Argument<MinecraftComponent> {
private final DiscordCustomEmoji emoji;
private MinecraftComponent rendered = null;
public DiscordChatMessageCustomEmojiRenderEvent(@NotNull DiscordCustomEmoji emoji) {
this.emoji = emoji;
}
@NotNull
public DiscordCustomEmoji getEmoji() {
return emoji;
}
/**
* Gets the rendered representation of the emoji.
* @return the rendered representation of the emoji if this event has been processed otherwise {@code null}
*/
@Nullable
public MinecraftComponent getRenderedEmojiFromProcessing() {
return rendered;
}
@Override
public boolean isProcessed() {
return rendered != null;
}
/**
* Marks this event as processed, with the given {@link MinecraftComponent} being the representation of {@link DiscordCustomEmoji} in game.
* @param renderedEmote the rendered emote
*/
@Override
public void process(@NotNull MinecraftComponent renderedEmote) {
if (rendered != null) {
throw new IllegalStateException("Cannot process an already processed event");
}
rendered = renderedEmote;
}
}

View File

@ -1,10 +1,11 @@
package com.discordsrv.api.event.events.message.receive.discord;
package com.discordsrv.api.event.events.message.process.discord;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.event.events.Cancellable;
import com.discordsrv.api.event.events.Processable;
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageReceiveEvent;
/**
* Indicates that a Discord message is about to be processed, this will run once per {@link GameChannel} destination,

View File

@ -29,11 +29,12 @@ import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.event.events.Cancellable;
import com.discordsrv.api.event.events.message.process.discord.DiscordChatMessageProcessEvent;
import org.jetbrains.annotations.NotNull;
/**
* Indicates that a Discord message has been received and will be processed unless cancelled.
* This runs once per Discord message, before {@link DiscordChatMessageProcessEvent}.
* This runs once per Discord message, before {@link DiscordChatMessageProcessEvent}(s).
*/
public class DiscordChatMessageReceiveEvent implements Cancellable {

View File

@ -40,7 +40,7 @@ public abstract class AbstractGameMessageReceiveEvent implements Cancellable, Pr
/**
* Gets the event that triggered this event to occur. This varies depending on platform and different plugin integrations.
* @return an event object, that isn't guaranteed to be of the same type every time or {@code null}
* @return an event object, that isn't guaranteed to be of the same type every time, or {@code null}
*/
@Nullable
public Object getTriggeringEvent() {

View File

@ -20,9 +20,11 @@ package com.discordsrv.common.component.renderer;
import com.discordsrv.api.component.GameTextBuilder;
import com.discordsrv.api.discord.entity.DiscordUser;
import com.discordsrv.api.discord.entity.guild.DiscordCustomEmoji;
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
import com.discordsrv.api.discord.entity.guild.DiscordRole;
import com.discordsrv.api.event.events.message.process.discord.DiscordChatMessageCustomEmojiRenderEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
@ -181,6 +183,42 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
));
}
@Override
public @NotNull Component appendEmoteMention(
@NotNull Component component,
@NotNull String name,
@NotNull String id
) {
Context context = CONTEXT.get();
DiscordToMinecraftChatConfig.EmoteBehaviour behaviour = context != null ? context.config.customEmojiBehaviour : null;
if (behaviour == null || behaviour == DiscordToMinecraftChatConfig.EmoteBehaviour.HIDE) {
return component;
}
System.out.println(name);
long emojiId = MiscUtil.parseLong(id);
DiscordCustomEmoji emoji = discordSRV.discordAPI().getEmojiById(emojiId);
if (emoji == null) {
return component;
}
DiscordChatMessageCustomEmojiRenderEvent event = new DiscordChatMessageCustomEmojiRenderEvent(emoji);
discordSRV.eventBus().publish(event);
if (event.isProcessed()) {
Component rendered = ComponentUtil.fromAPI(event.getRenderedEmojiFromProcessing());
return component.append(rendered);
}
switch (behaviour) {
case NAME:
return component.append(Component.text(":" + emoji.getName() + ":"));
case BLANK:
default:
return component;
}
}
private static class Context {
private final DiscordGuild guild;

View File

@ -18,7 +18,6 @@
package com.discordsrv.common.config.main.channels;
import com.discordsrv.common.config.configurate.annotation.Constants;
import com.discordsrv.common.config.configurate.annotation.Untranslated;
import com.discordsrv.common.config.main.generic.DiscordIgnoresConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ -58,10 +57,6 @@ public class DiscordToMinecraftChatConfig {
@Comment("The representations of Discord mentions in-game")
public Mentions mentions = new Mentions();
@Comment("The amount of milliseconds to delay processing Discord messages, if the message is deleted in that time it will not be processed.\n"
+ "This can be used together with Discord moderation bots, to filter forwarded messages")
public long delayMillis = 0L;
@ConfigSerializable
public static class Mentions {
@ -90,7 +85,35 @@ public class DiscordToMinecraftChatConfig {
this.unknownFormat = unknownFormat;
}
}
}
@Comment("How should unicode emoji be shown in-game:\n"
+ "- hide: hides emojis in-game\n"
+ "- show: shows emojis in-game as is (emojis may not be visible without resource packs)\n"
//+ "- name: shows the name of the emoji in-game (for example :smiley:)"
)
public EmojiBehaviour unicodeEmojiBehaviour = EmojiBehaviour.HIDE;
public enum EmojiBehaviour {
HIDE,
SHOW
// TODO: add and implement name
}
@Comment("How should custom emoji be shown in-game:\n"
+ "- hide: custom emoji will not be shown in-game\n"
+ "- blank: custom emoji will only be shown in-game if it is rendered by a 3rd party plugin\n"
+ "- name: shows the name of the custom emoji in-game (for example :discordsrv:), unless rendered by a 3rd party plugin")
public EmoteBehaviour customEmojiBehaviour = EmoteBehaviour.BLANK;
public enum EmoteBehaviour {
HIDE,
BLANK,
NAME
}
@Comment("The amount of milliseconds to delay processing Discord messages, if the message is deleted in that time it will not be processed.\n"
+ "This can be used together with Discord moderation bots, to filter forwarded messages")
public long delayMillis = 0L;
}

View File

@ -23,6 +23,7 @@ import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
import com.discordsrv.api.discord.connection.jda.errorresponse.ErrorCallbackContext;
import com.discordsrv.api.discord.entity.DiscordUser;
import com.discordsrv.api.discord.entity.channel.*;
import com.discordsrv.api.discord.entity.guild.DiscordCustomEmoji;
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.entity.guild.DiscordRole;
import com.discordsrv.api.discord.entity.interaction.command.CommandType;
@ -36,6 +37,7 @@ import com.discordsrv.common.config.main.generic.ThreadConfig;
import com.discordsrv.common.config.main.generic.DestinationConfig;
import com.discordsrv.common.discord.api.entity.DiscordUserImpl;
import com.discordsrv.common.discord.api.entity.channel.*;
import com.discordsrv.common.discord.api.entity.guild.DiscordCustomEmojiImpl;
import com.discordsrv.common.discord.api.entity.guild.DiscordGuildImpl;
import com.discordsrv.common.discord.api.entity.guild.DiscordGuildMemberImpl;
import com.discordsrv.common.discord.api.entity.guild.DiscordRoleImpl;
@ -49,6 +51,7 @@ import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.*;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
import net.dv8tion.jda.api.requests.ErrorResponse;
import org.checkerframework.checker.index.qual.NonNegative;
@ -498,6 +501,15 @@ public class DiscordAPIImpl implements DiscordAPI {
return new DiscordRoleImpl(discordSRV, jda);
}
@Override
public DiscordCustomEmoji getEmojiById(long id) {
return mapJDAEntity(jda -> jda.getEmojiById(id), this::getEmoji);
}
public DiscordCustomEmoji getEmoji(CustomEmoji jda) {
return new DiscordCustomEmojiImpl(jda);
}
@Override
public DiscordCommand.RegistrationResult registerCommand(DiscordCommand command) {
return commandRegistry.register(command, false);

View File

@ -0,0 +1,28 @@
package com.discordsrv.common.discord.api.entity.guild;
import com.discordsrv.api.discord.entity.guild.DiscordCustomEmoji;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
public class DiscordCustomEmojiImpl implements DiscordCustomEmoji {
private final CustomEmoji jda;
public DiscordCustomEmojiImpl(CustomEmoji jda) {
this.jda = jda;
}
@Override
public CustomEmoji asJDA() {
return jda;
}
@Override
public long getId() {
return jda.getIdLong();
}
@Override
public String getName() {
return jda.getName();
}
}

View File

@ -32,7 +32,7 @@ import com.discordsrv.api.discord.events.message.DiscordMessageReceiveEvent;
import com.discordsrv.api.discord.events.message.DiscordMessageUpdateEvent;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.message.forward.discord.DiscordChatMessageForwardedEvent;
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessEvent;
import com.discordsrv.api.event.events.message.process.discord.DiscordChatMessageProcessEvent;
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageReceiveEvent;
import com.discordsrv.api.placeholder.util.Placeholders;
import com.discordsrv.common.DiscordSRV;
@ -60,6 +60,10 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
// Notably this excludes, 0x09 HT (\t), 0x0A LF (\n), 0x0B VT (\v) and 0x0D CR (\r) (which may be used for text formatting)
private static final Pattern ASCII_CONTROL_FILTER = Pattern.compile("[\\u0000-\\u0008\\u000C\\u000E-\\u001F\\u007F]");
// A regex filter matching the unicode regular expression character category "Other Symbol"
// https://unicode.org/reports/tr18/#General_Category_Property
private static final Pattern EMOJI_FILTER = Pattern.compile("\\p{So}");
private final Map<String, MessageSend> sends = new ConcurrentHashMap<>();
public DiscordChatMessageModule(DiscordSRV discordSRV) {
@ -188,16 +192,25 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
Placeholders message = new Placeholders(event.getContent());
message.replaceAll(ASCII_CONTROL_FILTER, "");
if (chatConfig.unicodeEmojiBehaviour == DiscordToMinecraftChatConfig.EmojiBehaviour.HIDE) {
message.replaceAll(EMOJI_FILTER, "");
}
chatConfig.contentRegexFilters.forEach(message::replaceAll);
String finalMessage = message.toString();
if (StringUtils.isEmpty(finalMessage)) {
if (finalMessage.trim().isEmpty()) {
// No sending empty messages
return;
}
Component messageComponent = DiscordSRVMinecraftRenderer.getWithContext(guild, chatConfig, () ->
discordSRV.componentFactory().minecraftSerializer().serialize(finalMessage));
if (discordSRV.componentFactory().plainSerializer().serialize(messageComponent).trim().isEmpty()) {
// Check empty-ness again after rendering
return;
}
GameTextBuilder componentBuilder = discordSRV.componentFactory()
.textBuilder(format)
.addContext(discordMessage, author, channel, channelConfig)