mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-22 11:55:54 +01:00
Add jump urls to channel mentions & rendering messages, handle translatable components when serializing to plain
This commit is contained in:
parent
2e2c1667eb
commit
2d4252ed6f
@ -23,6 +23,7 @@
|
||||
|
||||
package com.discordsrv.api.component;
|
||||
|
||||
import com.discordsrv.api.placeholder.provider.SinglePlaceholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
@ -38,6 +39,14 @@ public interface GameTextBuilder {
|
||||
@NotNull
|
||||
GameTextBuilder addContext(Object... context);
|
||||
|
||||
default GameTextBuilder addPlaceholder(String placeholder, Object replacement) {
|
||||
return addContext(new SinglePlaceholder(placeholder, replacement));
|
||||
}
|
||||
|
||||
default GameTextBuilder addPlaceholder(String placeholder, Supplier<Object> replacementSupplier) {
|
||||
return addContext(new SinglePlaceholder(placeholder, replacementSupplier));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
default GameTextBuilder addReplacement(String target, Object replacement) {
|
||||
return addReplacement(Pattern.compile(target, Pattern.LITERAL), replacement);
|
||||
|
@ -70,6 +70,23 @@ public interface DiscordAPI {
|
||||
@Nullable
|
||||
DiscordTextChannel getTextChannelById(long id);
|
||||
|
||||
/**
|
||||
* Gets a Discord voice channel by id, the provided entity should be stored for long periods of time.
|
||||
* @param id the id for the voice channel
|
||||
* @return the voice channel
|
||||
*/
|
||||
@Nullable
|
||||
DiscordVoiceChannel getVoiceChannelById(long id);
|
||||
|
||||
/**
|
||||
* Gets a Discord stage channel by id, the provided entity should be stored for long periods of time.
|
||||
* @param id the id for the voice channel
|
||||
* @return the voice channel
|
||||
*/
|
||||
@Nullable
|
||||
DiscordStageChannel getStageChannelById(long id);
|
||||
|
||||
|
||||
/**
|
||||
* Gets a Discord thread channel by id from the cache, the provided entity should not be stored for long periods of time.
|
||||
* @param id the id for the thread channel
|
||||
|
@ -25,6 +25,7 @@ package com.discordsrv.api.discord.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.Snowflake;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.placeholder.annotation.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface DiscordGuildChannel extends Snowflake {
|
||||
@ -34,6 +35,7 @@ public interface DiscordGuildChannel extends Snowflake {
|
||||
* @return the name of the channel
|
||||
*/
|
||||
@NotNull
|
||||
@Placeholder("channel_name")
|
||||
String getName();
|
||||
|
||||
/**
|
||||
@ -42,4 +44,12 @@ public interface DiscordGuildChannel extends Snowflake {
|
||||
*/
|
||||
@NotNull
|
||||
DiscordGuild getGuild();
|
||||
|
||||
/**
|
||||
* Gets the jump url for this channel
|
||||
* @return the https url to go to this channel
|
||||
*/
|
||||
@NotNull
|
||||
@Placeholder("channel_jump_url")
|
||||
String getJumpUrl();
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.discordsrv.api.discord.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.JDAEntity;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.StageChannel;
|
||||
|
||||
public interface DiscordStageChannel extends DiscordGuildMessageChannel, JDAEntity<StageChannel> {
|
||||
|
||||
@Override
|
||||
default DiscordChannelType getType() {
|
||||
return DiscordChannelType.STAGE;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.discordsrv.api.discord.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.JDAEntity;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
|
||||
public interface DiscordVoiceChannel extends DiscordGuildMessageChannel, JDAEntity<VoiceChannel> {
|
||||
|
||||
@Override
|
||||
default DiscordChannelType getType() {
|
||||
return DiscordChannelType.VOICE;
|
||||
}
|
||||
}
|
@ -29,6 +29,9 @@ import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
|
||||
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializerOptions;
|
||||
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializer;
|
||||
import dev.vankka.mcdiscordreserializer.minecraft.MinecraftSerializerOptions;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.flattener.ComponentFlattener;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
@ -48,6 +51,7 @@ public class ComponentFactory implements MinecraftComponentFactory {
|
||||
private final DiscordSRV discordSRV;
|
||||
private final MinecraftSerializer minecraftSerializer;
|
||||
private final DiscordSerializer discordSerializer;
|
||||
private final PlainTextComponentSerializer plainSerializer;
|
||||
|
||||
// Not the same as Adventure's TranslationRegistry
|
||||
private final TranslationRegistry translationRegistry = new TranslationRegistry();
|
||||
@ -61,20 +65,29 @@ public class ComponentFactory implements MinecraftComponentFactory {
|
||||
this.discordSerializer = new DiscordSerializer();
|
||||
discordSerializer.setDefaultOptions(
|
||||
DiscordSerializerOptions.defaults()
|
||||
.withTranslationProvider(translatableComponent -> {
|
||||
Translation translation = translationRegistry.lookup(Locale.US, translatableComponent.key());
|
||||
if (translation == null) {
|
||||
return null;
|
||||
}
|
||||
.withTranslationProvider(this::provideTranslation)
|
||||
);
|
||||
this.plainSerializer = PlainTextComponentSerializer.builder()
|
||||
.flattener(
|
||||
ComponentFlattener.basic().toBuilder()
|
||||
.mapper(TranslatableComponent.class, this::provideTranslation)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
return translation.translate(
|
||||
translatableComponent.args()
|
||||
.stream()
|
||||
.map(discordSerializer::serialize)
|
||||
.map(str -> (Object) str)
|
||||
.toArray(Object[]::new)
|
||||
);
|
||||
})
|
||||
private String provideTranslation(TranslatableComponent component) {
|
||||
Translation translation = translationRegistry.lookup(Locale.US, component.key());
|
||||
if (translation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return translation.translate(
|
||||
component.args()
|
||||
.stream()
|
||||
.map(discordSerializer::serialize)
|
||||
.map(str -> (Object) str)
|
||||
.toArray(Object[]::new)
|
||||
);
|
||||
}
|
||||
|
||||
@ -96,6 +109,10 @@ public class ComponentFactory implements MinecraftComponentFactory {
|
||||
return discordSerializer;
|
||||
}
|
||||
|
||||
public PlainTextComponentSerializer plainSerializer() {
|
||||
return plainSerializer;
|
||||
}
|
||||
|
||||
public TranslationRegistry translationRegistry() {
|
||||
return translationRegistry;
|
||||
}
|
||||
|
@ -33,12 +33,16 @@ import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
|
||||
import net.dv8tion.jda.api.utils.MiscUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
|
||||
private static final Pattern MESSAGE_URL_PATTERN = Pattern.compile("https://(?:(?:ptb|canary)\\.)?discord\\.com/channels/[0-9]{16,20}/([0-9]{16,20})/[0-9]{16,20}");
|
||||
private static final ThreadLocal<Context> CONTEXT = new ThreadLocal<>();
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
@ -69,6 +73,41 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component appendLink(@NotNull Component part, String link) {
|
||||
JDA jda = discordSRV.jda();
|
||||
|
||||
if (jda != null) {
|
||||
Matcher matcher = MESSAGE_URL_PATTERN.matcher(link);
|
||||
if (matcher.matches()) {
|
||||
String channel = matcher.group(1);
|
||||
GuildChannel guildChannel = jda.getGuildChannelById(channel);
|
||||
|
||||
Context context = CONTEXT.get();
|
||||
String format = context != null ? context.config.map(cfg -> cfg.mentions).get(cfg -> cfg.message) : null;
|
||||
if (format == null || guildChannel == null) {
|
||||
return super.appendLink(part, link);
|
||||
}
|
||||
|
||||
return Component.text()
|
||||
.clickEvent(ClickEvent.openUrl(link))
|
||||
.append(
|
||||
ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(format)
|
||||
.addContext(guildChannel)
|
||||
.addPlaceholder("jump_url", link)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
)
|
||||
)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
return super.appendLink(part, link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component appendChannelMention(@NotNull Component component, @NotNull String id) {
|
||||
Context context = CONTEXT.get();
|
||||
@ -88,7 +127,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
return component.append(ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory()
|
||||
.textBuilder(guildChannel != null ? format.format : format.unknownFormat)
|
||||
.addReplacement("%channel_name%", guildChannel != null ? guildChannel.getName() : null)
|
||||
.addContext(guildChannel)
|
||||
.applyPlaceholderService()
|
||||
.build()
|
||||
));
|
||||
|
@ -60,9 +60,11 @@ public class DiscordToMinecraftChatConfig {
|
||||
public static class Mentions {
|
||||
|
||||
public Format role = new Format("ᛩf2@%role_name%", "ᛩf2@deleted-role");
|
||||
public Format channel = new Format("ᛩf2#%channel_name%", "ᛩf2#deleted-channel");
|
||||
public Format channel = new Format("[hover:show_text:Click to go to channel][click:open_url:%channel_jump_url%]ᛩf2#%channel_name%", "ᛩf2#deleted-channel");
|
||||
public Format user = new Format("[hover:show_text:Tag: %user_tag%&r\nRoles: %user_roles_, |text_&7&oNone%]ᛩf2@%user_effective_name|user_name%", "ᛩf2@Unknown user");
|
||||
|
||||
public String message = "[hover:show_text:Click to go to message][click:open_url:%jump_url%]ᛩf2#%channel_name% > ...";
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Format {
|
||||
|
||||
|
@ -48,10 +48,7 @@ import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.github.benmanes.caffeine.cache.Expiry;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.*;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
||||
import net.dv8tion.jda.api.requests.ErrorResponse;
|
||||
@ -397,6 +394,24 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return new DiscordTextChannelImpl(discordSRV, jda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordVoiceChannel getVoiceChannelById(long id) {
|
||||
return mapJDAEntity(jda -> jda.getVoiceChannelById(id), this::getVoiceChannel);
|
||||
}
|
||||
|
||||
public DiscordVoiceChannelImpl getVoiceChannel(VoiceChannel jda) {
|
||||
return new DiscordVoiceChannelImpl(discordSRV, jda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordStageChannel getStageChannelById(long id) {
|
||||
return mapJDAEntity(jda -> jda.getStageChannelById(id), this::getStageChannel);
|
||||
}
|
||||
|
||||
public DiscordStageChannelImpl getStageChannel(StageChannel jda) {
|
||||
return new DiscordStageChannelImpl(discordSRV, jda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordThreadChannel getCachedThreadChannelById(long id) {
|
||||
return mapJDAEntity(jda -> jda.getThreadChannelById(id), this::getThreadChannel);
|
||||
|
@ -65,6 +65,11 @@ public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageC
|
||||
return channel.getAsMention();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getJumpUrl() {
|
||||
return channel.getJumpUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DiscordGuild getGuild() {
|
||||
return guild;
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.discord.api.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordStageChannel;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.StageChannel;
|
||||
|
||||
public class DiscordStageChannelImpl extends AbstractDiscordGuildMessageChannel<StageChannel> implements DiscordStageChannel {
|
||||
|
||||
public DiscordStageChannelImpl(DiscordSRV discordSRV, StageChannel stageChannel) {
|
||||
super(discordSRV, stageChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StageChannel asJDA() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StageChannel:" + getName() + "(" + Long.toUnsignedString(getId()) + ")";
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.discord.api.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordVoiceChannel;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
|
||||
public class DiscordVoiceChannelImpl extends AbstractDiscordGuildMessageChannel<VoiceChannel> implements DiscordVoiceChannel {
|
||||
|
||||
public DiscordVoiceChannelImpl(DiscordSRV discordSRV, VoiceChannel voiceChannel) {
|
||||
super(discordSRV, voiceChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoiceChannel asJDA() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VoiceChannel:" + getName() + "(" + Long.toUnsignedString(getId()) + ")";
|
||||
}
|
||||
}
|
@ -336,8 +336,8 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
||||
for (Attachment attachment : attachments) {
|
||||
components.add(ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory().textBuilder(attachmentFormat)
|
||||
.addReplacement("%file_name%", attachment.fileName())
|
||||
.addReplacement("%file_url%", attachment.url())
|
||||
.addPlaceholder("file_name", attachment.fileName())
|
||||
.addPlaceholder("file_url", attachment.url())
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
@ -50,8 +50,7 @@ import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.*;
|
||||
import net.dv8tion.jda.api.events.StatusChangeEvent;
|
||||
import net.dv8tion.jda.api.events.session.SessionDisconnectEvent;
|
||||
import net.dv8tion.jda.api.events.session.ShutdownEvent;
|
||||
@ -236,6 +235,12 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
converted = api().getDirectMessageChannel((PrivateChannel) o);
|
||||
} else if (o instanceof TextChannel) {
|
||||
converted = api().getTextChannel((TextChannel) o);
|
||||
} else if (o instanceof ThreadChannel) {
|
||||
converted = api().getThreadChannel((ThreadChannel) o);
|
||||
} else if (o instanceof VoiceChannel) {
|
||||
converted = api().getVoiceChannel((VoiceChannel) o);
|
||||
} else if (o instanceof StageChannel) {
|
||||
converted = api().getStageChannel((StageChannel) o);
|
||||
} else if (o instanceof Guild) {
|
||||
converted = api().getGuild((Guild) o);
|
||||
} else if (o instanceof Member) {
|
||||
|
@ -129,7 +129,7 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
|
||||
GameTextBuilder componentBuilder = discordSRV.componentFactory()
|
||||
.textBuilder(format)
|
||||
.addContext(discordMessage, author, channel, channelConfig)
|
||||
.addReplacement("%message%", messageComponent);
|
||||
.addPlaceholder("message", messageComponent);
|
||||
if (member != null) {
|
||||
componentBuilder.addContext(member);
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ComponentResultStringifier implements PlaceholderResultMapper {
|
||||
@ -47,7 +46,7 @@ public class ComponentResultStringifier implements PlaceholderResultMapper {
|
||||
switch (mappingState) {
|
||||
case ANSI: // TODO: ansi serializer (?)
|
||||
case PLAIN:
|
||||
return PlainTextComponentSerializer.plainText().serialize(component);
|
||||
return discordSRV.componentFactory().plainSerializer().serialize(component);
|
||||
default:
|
||||
case NORMAL:
|
||||
return new FormattedText(discordSRV.componentFactory().discordSerializer().serialize(component));
|
||||
|
@ -130,7 +130,7 @@ dependencyResolutionManagement {
|
||||
library('adventure-serializer-bungee', 'net.kyori', 'adventure-text-serializer-bungeecord').versionRef('adventure-platform')
|
||||
|
||||
// MCDiscordReserializer & EnhancedLegacyText
|
||||
library('mcdiscordreserializer', 'dev.vankka', 'mcdiscordreserializer').version('4.3.0')
|
||||
library('mcdiscordreserializer', 'dev.vankka', 'mcdiscordreserializer').version('4.4.0-SNAPSHOT')
|
||||
library('enhancedlegacytext', 'dev.vankka', 'enhancedlegacytext').version('1.0.0')
|
||||
|
||||
// JUnit
|
||||
|
Loading…
Reference in New Issue
Block a user