mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2025-01-22 21:41:30 +01:00
Merge branch 'main' into execute-command
This commit is contained in:
commit
bb24dc7b32
@ -37,7 +37,8 @@ java {
|
||||
}
|
||||
|
||||
jar {
|
||||
from sourceSets.ap.output
|
||||
from sourceSets.ap.output.classesDirs
|
||||
from sourceSets.ap.output.resourcesDir
|
||||
}
|
||||
|
||||
//license {
|
||||
|
@ -70,6 +70,14 @@ public interface DiscordAPI {
|
||||
@Nullable
|
||||
DiscordTextChannel getTextChannelById(long id);
|
||||
|
||||
/**
|
||||
* Gets a Discord forum channel by id, the provided entity should not be stored for long periods of time.
|
||||
* @param id the id for the text channel
|
||||
* @return the forum channel
|
||||
*/
|
||||
@Nullable
|
||||
DiscordForumChannel getForumChannelById(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
|
||||
|
@ -56,6 +56,14 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
|
||||
@NotNull
|
||||
String getUsername();
|
||||
|
||||
/**
|
||||
* Gets the effective display name of the Discord user.
|
||||
* @return the user's effective display name
|
||||
*/
|
||||
@Placeholder("user_effective_name")
|
||||
@NotNull
|
||||
String getEffectiveName();
|
||||
|
||||
/**
|
||||
* Gets the Discord user's discriminator.
|
||||
* @return the user's discriminator
|
||||
@ -82,12 +90,17 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
|
||||
String getEffectiveAvatarUrl();
|
||||
|
||||
/**
|
||||
* Gets the Discord user's username followed by a {@code #} and their discriminator.
|
||||
* @return the Discord user's username and discriminator in the following format {@code Username#1234}
|
||||
* Gets the Discord user's username, including discriminator if any.
|
||||
* @return the Discord user's username
|
||||
*/
|
||||
@Placeholder("user_tag")
|
||||
default String getAsTag() {
|
||||
return getUsername() + "#" + getDiscriminator();
|
||||
String username = getUsername();
|
||||
String discriminator = getDiscriminator();
|
||||
if (!discriminator.replace("0", "").isEmpty()) {
|
||||
username = username + "#" + discriminator;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.discordsrv.api.discord.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.Snowflake;
|
||||
|
||||
public interface DiscordChannel extends Snowflake {
|
||||
|
||||
/**
|
||||
* Returns the type of channel this is.
|
||||
* @return the type of the channel
|
||||
*/
|
||||
DiscordChannelType getType();
|
||||
}
|
@ -36,6 +36,7 @@ public enum DiscordChannelType implements JDAEntity<ChannelType> {
|
||||
VOICE(ChannelType.VOICE),
|
||||
GROUP(ChannelType.GROUP),
|
||||
CATEGORY(ChannelType.CATEGORY),
|
||||
FORUM(ChannelType.FORUM),
|
||||
NEWS(ChannelType.NEWS),
|
||||
STAGE(ChannelType.STAGE),
|
||||
GUILD_NEWS_THREAD(ChannelType.GUILD_NEWS_THREAD),
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.discordsrv.api.discord.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.JDAEntity;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
|
||||
|
||||
public interface DiscordForumChannel extends DiscordChannel, DiscordThreadContainer, JDAEntity<ForumChannel> {
|
||||
}
|
@ -24,27 +24,17 @@
|
||||
package com.discordsrv.api.discord.entity.channel;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.api.discord.entity.Snowflake;
|
||||
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A Discord channel that can send/receive messages.
|
||||
*/
|
||||
public interface DiscordMessageChannel extends Snowflake {
|
||||
|
||||
/**
|
||||
* Returns the type of channel this is.
|
||||
* @return the type of the channel
|
||||
*/
|
||||
DiscordChannelType getType();
|
||||
public interface DiscordMessageChannel extends DiscordChannel {
|
||||
|
||||
/**
|
||||
* Sends the provided message to the channel.
|
||||
@ -53,21 +43,7 @@ public interface DiscordMessageChannel extends Snowflake {
|
||||
* @return a future returning the message after being sent
|
||||
*/
|
||||
@NotNull
|
||||
default CompletableFuture<ReceivedDiscordMessage> sendMessage(@NotNull SendableDiscordMessage message) {
|
||||
return sendMessage(message, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the provided message to the channel with the provided attachments.
|
||||
*
|
||||
* @param message the message to send to the channel
|
||||
* @param attachments the attachments (in a map of file name and input stream pairs) to include in the message, the streams will be closed upon execution
|
||||
* @return a future returning the message after being sent
|
||||
*/
|
||||
CompletableFuture<ReceivedDiscordMessage> sendMessage(
|
||||
@NotNull SendableDiscordMessage message,
|
||||
@NotNull Map<String, InputStream> attachments
|
||||
);
|
||||
CompletableFuture<ReceivedDiscordMessage> sendMessage(@NotNull SendableDiscordMessage message);
|
||||
|
||||
/**
|
||||
* Deletes the message identified by the id.
|
||||
|
@ -54,6 +54,13 @@ public interface DiscordGuild extends JDAEntity<Guild>, Snowflake {
|
||||
@Placeholder("server_member_count")
|
||||
int getMemberCount();
|
||||
|
||||
/**
|
||||
* Gets the bot's membership in the server.
|
||||
* @return the connected bot's member
|
||||
*/
|
||||
@NotNull
|
||||
DiscordGuildMember getSelfMember();
|
||||
|
||||
/**
|
||||
* Retrieves a Discord guild member from Discord by id.
|
||||
* @param id the id for the Discord guild member
|
||||
|
@ -76,6 +76,13 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
|
||||
*/
|
||||
boolean hasRole(@NotNull DiscordRole role);
|
||||
|
||||
/**
|
||||
* If this member can interact (edit, add/take from members) with the specified role.
|
||||
* @param role the role
|
||||
* @return {@code true} if the member has a role above the specified role or is the server owner
|
||||
*/
|
||||
boolean canInteract(@NotNull DiscordRole role);
|
||||
|
||||
/**
|
||||
* Gives the given role to this member.
|
||||
* @param role the role to give
|
||||
@ -94,18 +101,18 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
|
||||
* Gets the effective name of this Discord server member.
|
||||
* @return the Discord server member's effective name
|
||||
*/
|
||||
@Placeholder("user_effective_name")
|
||||
@Placeholder("user_effective_server_name")
|
||||
@NotNull
|
||||
default String getEffectiveName() {
|
||||
default String getEffectiveServerName() {
|
||||
String nickname = getNickname();
|
||||
return nickname != null ? nickname : getUser().getUsername();
|
||||
return nickname != null ? nickname : getUser().getEffectiveName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the avatar url that is active for this user in this server.
|
||||
* @return the user's avatar url in this server
|
||||
*/
|
||||
@Placeholder("user_effective_avatar_url")
|
||||
@Placeholder("user_effective_server_avatar_url")
|
||||
@NotNull
|
||||
String getEffectiveServerAvatarUrl();
|
||||
|
||||
|
@ -30,6 +30,7 @@ import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.placeholder.annotation.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
@ -69,6 +70,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
|
||||
* @return the jump url
|
||||
*/
|
||||
@NotNull
|
||||
@Placeholder("message_jump_url")
|
||||
String getJumpUrl();
|
||||
|
||||
/**
|
||||
@ -155,8 +157,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
|
||||
* @throws IllegalArgumentException if the message is not a webhook message,
|
||||
* but the provided {@link SendableDiscordMessage} specifies a webhook username.
|
||||
*/
|
||||
@NotNull
|
||||
CompletableFuture<ReceivedDiscordMessage> edit(SendableDiscordMessage message);
|
||||
CompletableFuture<ReceivedDiscordMessage> edit(@NotNull SendableDiscordMessage message);
|
||||
|
||||
class Attachment {
|
||||
|
||||
|
@ -31,8 +31,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@ -107,6 +109,8 @@ public interface SendableDiscordMessage {
|
||||
return getWebhookUsername() != null;
|
||||
}
|
||||
|
||||
Map<InputStream, String> getAttachments();
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue") // API
|
||||
interface Builder {
|
||||
|
||||
@ -238,6 +242,20 @@ public interface SendableDiscordMessage {
|
||||
@NotNull
|
||||
Builder setWebhookAvatarUrl(String webhookAvatarUrl);
|
||||
|
||||
/**
|
||||
* Adds an attachment to this builder.
|
||||
* @param inputStream an input stream containing the file contents
|
||||
* @param fileName the name of the file
|
||||
* @return the builder, useful for chaining
|
||||
*/
|
||||
Builder addAttachment(InputStream inputStream, String fileName);
|
||||
|
||||
/**
|
||||
* Checks if this builder has any sendable content.
|
||||
* @return {@code true} if there is no sendable content
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Builds a {@link SendableDiscordMessage} from this builder.
|
||||
* @return the new {@link SendableDiscordMessage}
|
||||
|
@ -37,6 +37,7 @@ import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
@ -50,6 +51,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
|
||||
private final Set<AllowedMention> allowedMentions;
|
||||
private final String webhookUsername;
|
||||
private final String webhookAvatarUrl;
|
||||
private final Map<InputStream, String> attachments;
|
||||
|
||||
protected SendableDiscordMessageImpl(
|
||||
String content,
|
||||
@ -57,7 +59,8 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
|
||||
List<MessageActionRow> actionRows,
|
||||
Set<AllowedMention> allowedMentions,
|
||||
String webhookUsername,
|
||||
String webhookAvatarUrl
|
||||
String webhookAvatarUrl,
|
||||
Map<InputStream, String> attachments
|
||||
) {
|
||||
this.content = content;
|
||||
this.embeds = Collections.unmodifiableList(embeds);
|
||||
@ -65,6 +68,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
|
||||
this.allowedMentions = Collections.unmodifiableSet(allowedMentions);
|
||||
this.webhookUsername = webhookUsername;
|
||||
this.webhookAvatarUrl = webhookAvatarUrl;
|
||||
this.attachments = Collections.unmodifiableMap(attachments);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -98,6 +102,11 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
|
||||
return webhookAvatarUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<InputStream, String> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements SendableDiscordMessage.Builder {
|
||||
|
||||
private String content;
|
||||
@ -106,6 +115,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
|
||||
private final Set<AllowedMention> allowedMentions = new LinkedHashSet<>();
|
||||
private String webhookUsername;
|
||||
private String webhookAvatarUrl;
|
||||
private final Map<InputStream, String> attachments = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public String getContent() {
|
||||
@ -205,9 +215,20 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addAttachment(InputStream inputStream, String fileName) {
|
||||
this.attachments.put(inputStream, fileName);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return (content == null || content.isEmpty()) && embeds.isEmpty() && attachments.isEmpty() && actionRows.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull SendableDiscordMessage build() {
|
||||
return new SendableDiscordMessageImpl(content, embeds, actionRows, allowedMentions, webhookUsername, webhookAvatarUrl);
|
||||
return new SendableDiscordMessageImpl(content, embeds, actionRows, allowedMentions, webhookUsername, webhookAvatarUrl, attachments);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,7 +53,8 @@ subprojects {
|
||||
includeGroup 'me.scarsz'
|
||||
includeGroup 'me.minecraftauth'
|
||||
|
||||
includeGroup 'org.spongepowered' // Configurate yamlbranch
|
||||
includeGroup 'org.spongepowered' // SpongePowered/Configurate feature/yaml-improvements branch
|
||||
includeGroup 'net.dv8tion' // DiscordSRV/JDA v5-webhooks branch
|
||||
}
|
||||
}
|
||||
maven {
|
||||
|
@ -52,11 +52,11 @@ public class BukkitAdvancementListener extends AbstractBukkitAwardListener {
|
||||
|| version.startsWith("v1_2")) {
|
||||
// 1.19.4+
|
||||
nms = new NMS("org.bukkit.craftbukkit." + version + ".advancement.CraftAdvancement",
|
||||
"k", "d", "i", "a");
|
||||
"d", "i", "a");
|
||||
} else {
|
||||
// <1.19.4
|
||||
nms = new NMS("org.bukkit.craftbukkit." + version + ".advancement.CraftAdvancement",
|
||||
"j", "c", "i", "a");
|
||||
"c", "i", "a");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error("Could not get NMS methods for advancements.");
|
||||
@ -81,7 +81,7 @@ public class BukkitAdvancementListener extends AbstractBukkitAwardListener {
|
||||
event,
|
||||
event.getPlayer(),
|
||||
data.titleJson != null ? ComponentUtil.toAPI(BukkitComponentSerializer.gson().deserialize(data.titleJson)) : null,
|
||||
data.nameJson != null ? ComponentUtil.toAPI(BukkitComponentSerializer.gson().deserialize(data.nameJson)) : null,
|
||||
null,
|
||||
false);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
logger.debug("Failed to get advancement data", e);
|
||||
@ -91,7 +91,6 @@ public class BukkitAdvancementListener extends AbstractBukkitAwardListener {
|
||||
private static class NMS {
|
||||
|
||||
private final Method handleMethod;
|
||||
private final Method advancementNameMethod;
|
||||
private final Method advancementDisplayMethod;
|
||||
private final Method broadcastToChatMethod;
|
||||
private final Method titleMethod;
|
||||
@ -99,7 +98,6 @@ public class BukkitAdvancementListener extends AbstractBukkitAwardListener {
|
||||
|
||||
public NMS(
|
||||
String craftAdvancementClassName,
|
||||
String nameMethodName,
|
||||
String displayMethodName,
|
||||
String broadcastToChatMethodName,
|
||||
String titleMethodName
|
||||
@ -107,7 +105,6 @@ public class BukkitAdvancementListener extends AbstractBukkitAwardListener {
|
||||
Class<?> clazz = Class.forName(craftAdvancementClassName);
|
||||
handleMethod = clazz.getDeclaredMethod("getHandle");
|
||||
Class<?> nmsClass = handleMethod.getReturnType();
|
||||
advancementNameMethod = nmsClass.getDeclaredMethod(nameMethodName);
|
||||
advancementDisplayMethod = nmsClass.getDeclaredMethod(displayMethodName);
|
||||
Class<?> displayClass = advancementDisplayMethod.getReturnType();
|
||||
broadcastToChatMethod = displayClass.getDeclaredMethod(broadcastToChatMethodName);
|
||||
@ -133,27 +130,22 @@ public class BukkitAdvancementListener extends AbstractBukkitAwardListener {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object nameChat = advancementNameMethod.invoke(nms);
|
||||
Object titleChat = titleMethod.invoke(display);
|
||||
|
||||
return new ReturnData(
|
||||
toJson(nameChat),
|
||||
toJson(titleChat)
|
||||
);
|
||||
}
|
||||
|
||||
private String toJson(Object chat) throws ReflectiveOperationException {
|
||||
return (String) toJsonMethod.invoke(chat);
|
||||
return (String) toJsonMethod.invoke(null, chat);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReturnData {
|
||||
|
||||
private final String nameJson;
|
||||
private final String titleJson;
|
||||
|
||||
public ReturnData(String nameJson, String titleJson) {
|
||||
this.nameJson = nameJson;
|
||||
public ReturnData(String titleJson) {
|
||||
this.titleJson = titleJson;
|
||||
}
|
||||
}
|
||||
|
@ -183,12 +183,6 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
// Command handler
|
||||
commandHandler = AbstractBukkitCommandHandler.get(this);
|
||||
|
||||
// Register listeners
|
||||
server().getPluginManager().registerEvents(BukkitAwardForwarder.get(this), plugin());
|
||||
server().getPluginManager().registerEvents(BukkitChatForwarder.get(this), plugin());
|
||||
server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin());
|
||||
server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin());
|
||||
|
||||
// Modules
|
||||
registerModule(MinecraftToDiscordChatModule::new);
|
||||
registerModule(BukkitRequiredLinkingModule::new);
|
||||
@ -207,12 +201,18 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
|
||||
|
||||
super.enable();
|
||||
|
||||
// Register listeners
|
||||
server().getPluginManager().registerEvents(BukkitAwardForwarder.get(this), plugin());
|
||||
server().getPluginManager().registerEvents(BukkitChatForwarder.get(this), plugin());
|
||||
server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin());
|
||||
server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin());
|
||||
|
||||
// Connection listener
|
||||
server().getPluginManager().registerEvents(new BukkitConnectionListener(this), plugin());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReloadResult> reload(Set<ReloadFlag> flags, boolean initial) throws Throwable {
|
||||
public List<ReloadResult> reload(Set<ReloadFlag> flags, boolean initial) throws Throwable {
|
||||
List<ReloadResult> results = super.reload(flags, initial);
|
||||
|
||||
if (flags.contains(ReloadFlag.TRANSLATIONS)) {
|
||||
|
@ -92,11 +92,12 @@ public abstract class AbstractBukkitCommandHandler implements ICommandHandler {
|
||||
String label = gameCommand.getLabel();
|
||||
PluginCommand pluginCommand = discordSRV.plugin().getCommand(label);
|
||||
if (pluginCommand != null) {
|
||||
logger.debug("PluginCommand available for \"" + label + "\"");
|
||||
return pluginCommand;
|
||||
}
|
||||
|
||||
if (COMMAND_MAP_HANDLE == null) {
|
||||
// CommandMap unusable, can't get the command from it
|
||||
logger.debug("Unable to get command from command map");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -107,8 +108,16 @@ public abstract class AbstractBukkitCommandHandler implements ICommandHandler {
|
||||
command = (PluginCommand) constructor.newInstance(label, discordSRV.plugin());
|
||||
|
||||
CommandMap commandMap = (CommandMap) COMMAND_MAP_HANDLE.invokeExact(discordSRV.server());
|
||||
commandMap.register(label, discordSRV.plugin().getName().toLowerCase(Locale.ROOT), command);
|
||||
} catch (Throwable ignored) {}
|
||||
boolean result = commandMap.register(label, discordSRV.plugin().getName().toLowerCase(Locale.ROOT), command);
|
||||
|
||||
if (result) {
|
||||
logger.debug("Registered command \"" + label + "\" in CommandMap successfully");
|
||||
} else {
|
||||
logger.debug("Registered command \"" + label + "\" into CommandMap but with fallback prefix");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.debug("Failed to register command \"" + label + "\" to CommandMap", t);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ public class BukkitBasicCommandHandler extends AbstractBukkitCommandExecutor imp
|
||||
|
||||
@Override
|
||||
public void registerCommand(GameCommand command) {
|
||||
discordSRV.scheduler().runOnMainThread(() -> {
|
||||
PluginCommand pluginCommand = command(command);
|
||||
if (pluginCommand == null) {
|
||||
logger.error("Failed to create command " + command.getLabel());
|
||||
@ -59,5 +60,6 @@ public class BukkitBasicCommandHandler extends AbstractBukkitCommandExecutor imp
|
||||
handler.registerCommand(command);
|
||||
pluginCommand.setExecutor(this);
|
||||
pluginCommand.setTabCompleter(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -21,29 +21,21 @@ package com.discordsrv.bukkit.command.game.handler;
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.handler.util.BrigadierUtil;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import me.lucko.commodore.Commodore;
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* No avoiding basic handler on bukkit. Unfortunately it isn't possible to use brigadier for executing.
|
||||
* No avoiding basic handler on Bukkit. Commodore only sends the command tree to the client, nothing else.
|
||||
*/
|
||||
public class CommodoreHandler extends AbstractBukkitCommandExecutor {
|
||||
public class CommodoreHandler extends BukkitBasicCommandHandler {
|
||||
|
||||
private final Commodore commodore;
|
||||
private final Function<?, ICommandSender> senderFunction;
|
||||
|
||||
public CommodoreHandler(BukkitDiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
this.commodore = CommodoreProvider.getCommodore(discordSRV.plugin());
|
||||
this.senderFunction = wrapper -> sender(commodore.getBukkitSender(wrapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -58,38 +50,11 @@ public class CommodoreHandler extends AbstractBukkitCommandExecutor {
|
||||
|
||||
handler.registerCommand(command);
|
||||
pluginCommand.setExecutor(this);
|
||||
pluginCommand.setTabCompleter(this);
|
||||
|
||||
List<LiteralCommandNode<?>> nodes = getAliases(command, pluginCommand);
|
||||
discordSRV.scheduler().runOnMainThread(() -> {
|
||||
for (LiteralCommandNode<?> node : nodes) {
|
||||
commodore.register(node);
|
||||
}
|
||||
LiteralCommandNode<?> commandNode = BrigadierUtil.convertToBrigadier(command, null);
|
||||
commodore.register(pluginCommand, commandNode);
|
||||
});
|
||||
}
|
||||
|
||||
private List<LiteralCommandNode<?>> getAliases(GameCommand command, PluginCommand pluginCommand) {
|
||||
String commandName = pluginCommand.getName();
|
||||
String pluginName = pluginCommand.getPlugin().getName().toLowerCase(Locale.ROOT);
|
||||
|
||||
List<String> allAliases = new ArrayList<>();
|
||||
allAliases.add(commandName);
|
||||
allAliases.addAll(pluginCommand.getAliases());
|
||||
|
||||
List<LiteralCommandNode<?>> nodes = new ArrayList<>();
|
||||
for (String alias : allAliases) {
|
||||
if (alias.equals(commandName)) {
|
||||
LiteralCommandNode<?> node = BrigadierUtil.convertToBrigadier(command, senderFunction);
|
||||
if (node.getRedirect() != null) {
|
||||
throw new IllegalStateException("Cannot register a redirected node!");
|
||||
}
|
||||
nodes.add(node);
|
||||
} else {
|
||||
nodes.add(BrigadierUtil.convertToBrigadier(GameCommand.literal(alias).redirect(command), senderFunction));
|
||||
}
|
||||
|
||||
// plugin:command
|
||||
nodes.add(BrigadierUtil.convertToBrigadier(GameCommand.literal(pluginName + ":" + alias).redirect(command), senderFunction));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
package com.discordsrv.bukkit.scheduler;
|
||||
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.bukkit.DiscordSRVBukkitBootstrap;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.scheduler.ServerScheduler;
|
||||
import com.discordsrv.common.scheduler.StandardScheduler;
|
||||
import org.bukkit.Server;
|
||||
@ -35,6 +37,16 @@ public abstract class AbstractBukkitScheduler extends StandardScheduler implemen
|
||||
this.discordSRV = discordSRV;
|
||||
}
|
||||
|
||||
protected void checkDisable(Runnable task, BiConsumer<Server, Plugin> runNormal) {
|
||||
// Can't run tasks when disabling, so we'll push those to the bootstrap to run after disable
|
||||
if (!discordSRV.plugin().isEnabled() && discordSRV.status() == DiscordSRV.Status.SHUTTING_DOWN) {
|
||||
((DiscordSRVBukkitBootstrap) discordSRV.bootstrap()).mainThreadTasksForDisable().add(task);
|
||||
return;
|
||||
}
|
||||
|
||||
runWithArgs(runNormal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runWithArgs(BiConsumer<Server, Plugin> runNormal) {
|
||||
runNormal.accept(discordSRV.server(), discordSRV.plugin());
|
||||
|
@ -19,12 +19,6 @@
|
||||
package com.discordsrv.bukkit.scheduler;
|
||||
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.bukkit.DiscordSRVBukkitBootstrap;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class BukkitScheduler extends AbstractBukkitScheduler {
|
||||
|
||||
@ -32,16 +26,6 @@ public class BukkitScheduler extends AbstractBukkitScheduler {
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
protected void checkDisable(Runnable task, BiConsumer<Server, Plugin> runNormal) {
|
||||
// Can't run tasks when disabling, so we'll push those to the bootstrap to run after disable
|
||||
if (!discordSRV.plugin().isEnabled() && discordSRV.status() == DiscordSRV.Status.SHUTTING_DOWN) {
|
||||
((DiscordSRVBukkitBootstrap) discordSRV.bootstrap()).mainThreadTasksForDisable().add(task);
|
||||
return;
|
||||
}
|
||||
|
||||
runWithArgs(runNormal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnMainThread(Runnable task) {
|
||||
checkDisable(task, (server, plugin) -> server.getScheduler().runTask(plugin, task));
|
||||
|
@ -25,4 +25,19 @@ public class FoliaScheduler extends AbstractBukkitScheduler implements IFoliaSch
|
||||
public FoliaScheduler(BukkitDiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnMainThread(Runnable task) {
|
||||
checkDisable(task, (server, plugin) -> IFoliaScheduler.super.runOnMainThread(task));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnMainThreadLaterInTicks(Runnable task, int ticks) {
|
||||
checkDisable(task, (server, plugin) -> IFoliaScheduler.super.runOnMainThreadLaterInTicks(task, ticks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnMainThreadAtFixedRateInTicks(Runnable task, int initialTicks, int rateTicks) {
|
||||
checkDisable(task, (server, plugin) -> IFoliaScheduler.super.runOnMainThreadAtFixedRateInTicks(task, initialTicks, rateTicks));
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +42,6 @@ dependencies {
|
||||
// DependencyDownload
|
||||
api(libs.dependencydownload.runtime)
|
||||
|
||||
// Discord Webhooks
|
||||
runtimeDownloadApi(libs.webhooks) {
|
||||
// okhttp is already included
|
||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||
}
|
||||
|
||||
// Apache Commons
|
||||
runtimeDownloadApi(libs.commons.lang)
|
||||
runtimeDownloadApi(libs.commons.io)
|
||||
|
@ -27,8 +27,8 @@ import com.discordsrv.common.api.util.ApiInstanceUtil;
|
||||
import com.discordsrv.common.bootstrap.IBootstrap;
|
||||
import com.discordsrv.common.channel.ChannelConfigHelper;
|
||||
import com.discordsrv.common.channel.ChannelLockingModule;
|
||||
import com.discordsrv.common.channel.ChannelUpdaterModule;
|
||||
import com.discordsrv.common.channel.GlobalChannelLookupModule;
|
||||
import com.discordsrv.common.channel.TimedUpdaterModule;
|
||||
import com.discordsrv.common.command.discord.DiscordCommandModule;
|
||||
import com.discordsrv.common.command.game.GameCommandModule;
|
||||
import com.discordsrv.common.command.game.commands.subcommand.reload.ReloadResults;
|
||||
@ -48,7 +48,6 @@ import com.discordsrv.common.discord.connection.jda.JDAConnectionManager;
|
||||
import com.discordsrv.common.event.bus.EventBusImpl;
|
||||
import com.discordsrv.common.exception.StorageException;
|
||||
import com.discordsrv.common.function.CheckedFunction;
|
||||
import com.discordsrv.common.function.CheckedRunnable;
|
||||
import com.discordsrv.common.groupsync.GroupSyncModule;
|
||||
import com.discordsrv.common.invite.DiscordInviteModule;
|
||||
import com.discordsrv.common.linking.LinkProvider;
|
||||
@ -100,7 +99,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
@ -114,7 +112,7 @@ import java.util.jar.Manifest;
|
||||
public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainConfig, CC extends ConnectionConfig> implements DiscordSRV {
|
||||
|
||||
private final AtomicReference<Status> status = new AtomicReference<>(Status.INITIALIZED);
|
||||
private CompletableFuture<Void> enableFuture;
|
||||
private final AtomicReference<Boolean> beenReady = new AtomicReference<>(false);
|
||||
|
||||
// DiscordSRVApi
|
||||
private EventBusImpl eventBus;
|
||||
@ -147,9 +145,6 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
// Internal
|
||||
private final ReentrantLock lifecycleLock = new ReentrantLock();
|
||||
|
||||
public AbstractDiscordSRV(B bootstrap) {
|
||||
ApiInstanceUtil.setInstance(this);
|
||||
this.bootstrap = bootstrap;
|
||||
@ -449,6 +444,12 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
}
|
||||
if (status == Status.CONNECTED) {
|
||||
eventBus().publish(new DiscordSRVConnectedEvent());
|
||||
synchronized (beenReady) {
|
||||
if (!beenReady.get()) {
|
||||
eventBus.publish(new DiscordSRVReadyEvent());
|
||||
beenReady.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,63 +484,33 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
|
||||
// Lifecycle
|
||||
|
||||
protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable<?> runnable) {
|
||||
return invokeLifecycle(() -> {
|
||||
try {
|
||||
lifecycleLock.lock();
|
||||
runnable.run();
|
||||
} finally {
|
||||
lifecycleLock.unlock();
|
||||
}
|
||||
return null;
|
||||
}, "Failed to enable", true);
|
||||
}
|
||||
|
||||
protected <T> CompletableFuture<T> invokeLifecycle(CheckedRunnable<T> runnable, String message, boolean enable) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (status().isShutdown()) {
|
||||
// Already shutdown/shutting down, don't bother
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return runnable.run();
|
||||
} catch (Throwable t) {
|
||||
if (status().isShutdown() && t instanceof NoClassDefFoundError) {
|
||||
// Already shutdown, ignore errors for classes that already got unloaded
|
||||
return null;
|
||||
}
|
||||
if (enable) {
|
||||
setStatus(Status.FAILED_TO_START);
|
||||
disable();
|
||||
}
|
||||
logger().error(message, t);
|
||||
}
|
||||
return null;
|
||||
}, scheduler().executorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final CompletableFuture<Void> invokeEnable() {
|
||||
return enableFuture = invokeLifecycle(() -> {
|
||||
public final void runEnable() {
|
||||
try {
|
||||
this.enable();
|
||||
waitForStatus(Status.CONNECTED);
|
||||
eventBus().publish(new DiscordSRVReadyEvent());
|
||||
return null;
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to enable", t);
|
||||
setStatus(Status.FAILED_TO_START);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final CompletableFuture<Void> invokeDisable() {
|
||||
if (enableFuture != null && !enableFuture.isDone()) {
|
||||
logger().warning("Start cancelled");
|
||||
enableFuture.cancel(true);
|
||||
}
|
||||
return CompletableFuture.runAsync(this::disable, scheduler().executorService());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final CompletableFuture<List<ReloadResult>> invokeReload(Set<ReloadFlag> flags, boolean silent) {
|
||||
return invokeLifecycle(() -> reload(flags, silent), "Failed to reload", false);
|
||||
public final List<ReloadResult> runReload(Set<ReloadFlag> flags, boolean silent) {
|
||||
try {
|
||||
return reload(flags, silent);
|
||||
} catch (Throwable e) {
|
||||
if (silent) {
|
||||
throw new RuntimeException(e);
|
||||
} else {
|
||||
logger.error("Failed to reload", e);
|
||||
}
|
||||
return Collections.singletonList(ReloadResults.FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
@ -553,16 +524,13 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
// Logging
|
||||
DependencyLoggerAdapter.setAppender(new DependencyLoggingHandler(this));
|
||||
|
||||
// Register PlayerProvider listeners
|
||||
playerProvider().subscribe();
|
||||
|
||||
// Placeholder result stringifiers & global contexts
|
||||
placeholderService().addResultMapper(new ComponentResultStringifier(this));
|
||||
placeholderService().addGlobalContext(new GlobalTextHandlingContext(this));
|
||||
|
||||
// Modules
|
||||
registerModule(ChannelLockingModule::new);
|
||||
registerModule(ChannelUpdaterModule::new);
|
||||
registerModule(TimedUpdaterModule::new);
|
||||
registerModule(DiscordCommandModule::new);
|
||||
registerModule(GameCommandModule::new);
|
||||
registerModule(GlobalChannelLookupModule::new);
|
||||
@ -580,10 +548,13 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
|
||||
// Initial load
|
||||
try {
|
||||
invokeReload(ReloadFlag.ALL, true).get();
|
||||
} catch (ExecutionException e) {
|
||||
runReload(ReloadFlag.ALL, true);
|
||||
} catch (RuntimeException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
|
||||
// Register PlayerProvider listeners
|
||||
playerProvider().subscribe();
|
||||
}
|
||||
|
||||
protected final void startedMessage() {
|
||||
@ -610,11 +581,18 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
this.status.set(Status.SHUTTING_DOWN);
|
||||
eventBus().publish(new DiscordSRVShuttingDownEvent());
|
||||
eventBus().shutdown();
|
||||
try {
|
||||
if (storage != null) {
|
||||
storage.close();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger().error("Failed to close storage connection", t);
|
||||
}
|
||||
this.status.set(Status.SHUTDOWN);
|
||||
}
|
||||
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
protected List<ReloadResult> reload(Set<ReloadFlag> flags, boolean initial) throws Throwable {
|
||||
public List<ReloadResult> reload(Set<ReloadFlag> flags, boolean initial) throws Throwable {
|
||||
if (!initial) {
|
||||
logger().info("Reloading DiscordSRV...");
|
||||
}
|
||||
@ -626,7 +604,9 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
|
||||
channelConfig().reload();
|
||||
} catch (Throwable t) {
|
||||
if (initial) {
|
||||
setStatus(Status.FAILED_TO_LOAD_CONFIG);
|
||||
}
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
@ -658,7 +638,7 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
String provider = linkedAccountConfig.provider;
|
||||
boolean permitMinecraftAuth = connectionConfig().minecraftAuth.allow;
|
||||
if (provider.equals("auto")) {
|
||||
provider = permitMinecraftAuth ? "minecraftauth" : "storage";
|
||||
provider = permitMinecraftAuth && onlineMode().isOnline() ? "minecraftauth" : "storage";
|
||||
}
|
||||
switch (provider) {
|
||||
case "minecraftauth":
|
||||
@ -719,7 +699,9 @@ public abstract class AbstractDiscordSRV<B extends IBootstrap, C extends MainCon
|
||||
} catch (StorageException e) {
|
||||
e.log(this);
|
||||
logger().error("Failed to connect to storage");
|
||||
if (initial) {
|
||||
setStatus(Status.FAILED_TO_START);
|
||||
}
|
||||
return Collections.singletonList(ReloadResults.STORAGE_CONNECTION_FAILED);
|
||||
}
|
||||
}
|
||||
|
@ -149,9 +149,9 @@ public interface DiscordSRV extends DiscordSRVApi {
|
||||
ObjectMapper json();
|
||||
|
||||
// Lifecycle
|
||||
CompletableFuture<Void> invokeEnable();
|
||||
void runEnable();
|
||||
List<ReloadResult> runReload(Set<ReloadFlag> flags, boolean silent);
|
||||
CompletableFuture<Void> invokeDisable();
|
||||
CompletableFuture<List<ReloadResult>> invokeReload(Set<ReloadFlag> flags, boolean silent);
|
||||
|
||||
default ExecuteCommand.AutoCompleteHelper autoCompleteHelper() {
|
||||
return null;
|
||||
|
@ -51,13 +51,24 @@ public abstract class ServerDiscordSRV<B extends IBootstrap, C extends MainConfi
|
||||
}
|
||||
|
||||
public final CompletableFuture<Void> invokeServerStarted() {
|
||||
return invokeLifecycle(() -> {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (status().isShutdown()) {
|
||||
// Already shutdown/shutting down, don't bother
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
this.serverStarted();
|
||||
} catch (Throwable t) {
|
||||
if (status().isShutdown() && t instanceof NoClassDefFoundError) {
|
||||
// Already shutdown, ignore errors for classes that already got unloaded
|
||||
return null;
|
||||
});
|
||||
}
|
||||
setStatus(Status.FAILED_TO_START);
|
||||
disable();
|
||||
logger().error("Failed to start", t);
|
||||
}
|
||||
return null;
|
||||
}, scheduler().executorService());
|
||||
}
|
||||
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
|
@ -90,14 +90,14 @@ public class LifecycleManager {
|
||||
if (!completableFuture.isDone()) {
|
||||
return;
|
||||
}
|
||||
discordSRVSupplier.get().invokeEnable();
|
||||
discordSRVSupplier.get().runEnable();
|
||||
}
|
||||
|
||||
public void reload(DiscordSRV discordSRV) {
|
||||
if (discordSRV == null) {
|
||||
return;
|
||||
}
|
||||
discordSRV.invokeReload(DiscordSRVApi.ReloadFlag.DEFAULT_FLAGS, false);
|
||||
discordSRV.runReload(DiscordSRVApi.ReloadFlag.DEFAULT_FLAGS, false);
|
||||
}
|
||||
|
||||
public void disable(DiscordSRV discordSRV) {
|
||||
|
@ -27,7 +27,8 @@ import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.ThreadConfig;
|
||||
import com.discordsrv.common.config.main.generic.ThreadConfig;
|
||||
import com.discordsrv.common.config.main.generic.DestinationConfig;
|
||||
import com.discordsrv.common.config.manager.MainConfigManager;
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
@ -126,9 +127,9 @@ public class ChannelConfigHelper {
|
||||
String channelName = entry.getKey();
|
||||
BaseChannelConfig value = entry.getValue();
|
||||
if (value instanceof IChannelConfig) {
|
||||
IChannelConfig channelConfig = (IChannelConfig) value;
|
||||
DestinationConfig destination = ((IChannelConfig) value).destination();
|
||||
|
||||
List<Long> channelIds = channelConfig.channelIds();
|
||||
List<Long> channelIds = destination.channelIds;
|
||||
if (channelIds != null) {
|
||||
for (long channelId : channelIds) {
|
||||
text.computeIfAbsent(channelId, key -> new LinkedHashMap<>())
|
||||
@ -136,7 +137,7 @@ public class ChannelConfigHelper {
|
||||
}
|
||||
}
|
||||
|
||||
List<ThreadConfig> threads = channelConfig.threads();
|
||||
List<ThreadConfig> threads = destination.threads;
|
||||
if (threads != null) {
|
||||
for (ThreadConfig threadConfig : threads) {
|
||||
Pair<Long, String> pair = Pair.of(
|
||||
|
@ -18,18 +18,19 @@
|
||||
|
||||
package com.discordsrv.common.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.channels.ChannelLockingConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.IPermissionHolder;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.attribute.IPermissionContainer;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
|
||||
import net.dv8tion.jda.api.requests.restaction.PermissionOverrideAction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -54,42 +55,57 @@ public class ChannelLockingModule extends AbstractModule<DiscordSRV> {
|
||||
ChannelLockingConfig.Channels channels = shutdownConfig.channels;
|
||||
ChannelLockingConfig.Threads threads = shutdownConfig.threads;
|
||||
|
||||
if (threads.unarchive) {
|
||||
discordSRV.discordAPI().findOrCreateThreads(config, channelConfig, __ -> {}, new ArrayList<>(), false);
|
||||
discordSRV.discordAPI()
|
||||
.findOrCreateDestinations((BaseChannelConfig & IChannelConfig) config, threads.unarchive, true)
|
||||
.whenComplete((destinations, t) -> {
|
||||
if (channels.everyone || !channels.roleIds.isEmpty()) {
|
||||
for (DiscordGuildMessageChannel destination : destinations) {
|
||||
channelPermissions(channels, destination, true);
|
||||
}
|
||||
channelPermissions(channelConfig, channels, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
doForAllChannels((config, channelConfig) -> {
|
||||
if (!(config instanceof IChannelConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelLockingConfig shutdownConfig = config.channelLocking;
|
||||
ChannelLockingConfig.Channels channels = shutdownConfig.channels;
|
||||
ChannelLockingConfig.Threads threads = shutdownConfig.threads;
|
||||
|
||||
if (threads.archive) {
|
||||
for (DiscordThreadChannel thread : discordSRV.discordAPI().findThreads(config, channelConfig)) {
|
||||
thread.asJDA().getManager()
|
||||
boolean archive = threads.archive;
|
||||
boolean isChannels = channels.everyone || !channels.roleIds.isEmpty();
|
||||
if (!threads.archive && !isChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<DiscordGuildMessageChannel> destinations = discordSRV.discordAPI()
|
||||
.findDestinations((BaseChannelConfig & IChannelConfig) config, true);
|
||||
|
||||
for (DiscordGuildMessageChannel destination : destinations) {
|
||||
if (archive && destination instanceof DiscordThreadChannel) {
|
||||
((DiscordThreadChannel) destination).asJDA().getManager()
|
||||
.setArchived(true)
|
||||
.reason("DiscordSRV channel locking")
|
||||
.queue();
|
||||
}
|
||||
if (isChannels) {
|
||||
channelPermissions(channels, destination, false);
|
||||
}
|
||||
}
|
||||
channelPermissions(channelConfig, channels, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void channelPermissions(
|
||||
IChannelConfig channelConfig,
|
||||
ChannelLockingConfig.Channels shutdownConfig,
|
||||
DiscordGuildMessageChannel channel,
|
||||
boolean state
|
||||
) {
|
||||
JDA jda = discordSRV.jda();
|
||||
if (jda == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean everyone = shutdownConfig.everyone;
|
||||
List<Long> roleIds = shutdownConfig.roleIds;
|
||||
if (!everyone && roleIds.isEmpty()) {
|
||||
@ -107,33 +123,31 @@ public class ChannelLockingModule extends AbstractModule<DiscordSRV> {
|
||||
permissions.add(Permission.MESSAGE_ADD_REACTION);
|
||||
}
|
||||
|
||||
for (Long channelId : channelConfig.channelIds()) {
|
||||
TextChannel channel = jda.getTextChannelById(channelId);
|
||||
if (channel == null) {
|
||||
continue;
|
||||
GuildMessageChannel messageChannel = (GuildMessageChannel) channel.getAsJDAMessageChannel();
|
||||
if (!(messageChannel instanceof IPermissionContainer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Guild guild = channel.getGuild();
|
||||
if (!guild.getSelfMember().hasPermission(channel, Permission.MANAGE_PERMISSIONS)) {
|
||||
Guild guild = messageChannel.getGuild();
|
||||
if (!guild.getSelfMember().hasPermission(messageChannel, Permission.MANAGE_PERMISSIONS)) {
|
||||
logger().error("Cannot change permissions of " + channel + ": lacking \"Manage Permissions\" permission");
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (everyone) {
|
||||
setPermission(channel, guild.getPublicRole(), permissions, state);
|
||||
setPermission((IPermissionContainer) messageChannel, guild.getPublicRole(), permissions, state);
|
||||
}
|
||||
for (Long roleId : roleIds) {
|
||||
Role role = channel.getGuild().getRoleById(roleId);
|
||||
Role role = guild.getRoleById(roleId);
|
||||
if (role == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setPermission(channel, role, permissions, state);
|
||||
}
|
||||
setPermission((IPermissionContainer) messageChannel, role, permissions, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void setPermission(TextChannel channel, IPermissionHolder holder, List<Permission> permissions, boolean state) {
|
||||
private void setPermission(IPermissionContainer channel, IPermissionHolder holder, List<Permission> permissions, boolean state) {
|
||||
PermissionOverrideAction action = channel.upsertPermissionOverride(holder);
|
||||
if ((state ? action.getAllowedPermissions() : action.getDeniedPermissions()).containsAll(permissions)) {
|
||||
// Already correct
|
||||
|
@ -20,7 +20,7 @@ package com.discordsrv.common.channel;
|
||||
|
||||
import com.discordsrv.api.discord.connection.jda.errorresponse.ErrorCallbackContext;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.ChannelUpdaterConfig;
|
||||
import com.discordsrv.common.config.main.TimedUpdaterConfig;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
@ -31,24 +31,27 @@ import net.dv8tion.jda.api.managers.channel.concrete.TextChannelManager;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChannelUpdaterModule extends AbstractModule<DiscordSRV> {
|
||||
public class TimedUpdaterModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
private final Set<ScheduledFuture<?>> futures = new LinkedHashSet<>();
|
||||
private boolean firstReload = true;
|
||||
|
||||
public ChannelUpdaterModule(DiscordSRV discordSRV) {
|
||||
public TimedUpdaterModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV, new NamedLogger(discordSRV, "CHANNEL_UPDATER"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
boolean any = false;
|
||||
for (ChannelUpdaterConfig channelUpdater : discordSRV.config().channelUpdaters) {
|
||||
if (!channelUpdater.channelIds.isEmpty()) {
|
||||
|
||||
TimedUpdaterConfig config = discordSRV.config().timedUpdater;
|
||||
for (TimedUpdaterConfig.UpdaterConfig updaterConfig : config.getConfigs()) {
|
||||
if (updaterConfig.any()) {
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
@ -65,18 +68,21 @@ public class ChannelUpdaterModule extends AbstractModule<DiscordSRV> {
|
||||
futures.forEach(future -> future.cancel(false));
|
||||
futures.clear();
|
||||
|
||||
for (ChannelUpdaterConfig config : discordSRV.config().channelUpdaters) {
|
||||
TimedUpdaterConfig config = discordSRV.config().timedUpdater;
|
||||
for (TimedUpdaterConfig.UpdaterConfig updaterConfig : config.getConfigs()) {
|
||||
long time = Math.max(updaterConfig.timeSeconds(), updaterConfig.minimumSeconds());
|
||||
|
||||
futures.add(discordSRV.scheduler().runAtFixedRate(
|
||||
() -> update(config),
|
||||
firstReload ? 0 : config.timeMinutes,
|
||||
config.timeMinutes,
|
||||
TimeUnit.MINUTES
|
||||
() -> update(updaterConfig),
|
||||
firstReload ? 0 : time,
|
||||
time,
|
||||
TimeUnit.SECONDS
|
||||
));
|
||||
}
|
||||
firstReload = false;
|
||||
}
|
||||
|
||||
public void update(ChannelUpdaterConfig config) {
|
||||
public void update(TimedUpdaterConfig.UpdaterConfig config) {
|
||||
try {
|
||||
// Wait a moment in case we're (re)connecting at the time
|
||||
discordSRV.waitForStatus(DiscordSRV.Status.CONNECTED, 15, TimeUnit.SECONDS);
|
||||
@ -89,9 +95,26 @@ public class ChannelUpdaterModule extends AbstractModule<DiscordSRV> {
|
||||
return;
|
||||
}
|
||||
|
||||
String topicFormat = config.topicFormat;
|
||||
String nameFormat = config.nameFormat;
|
||||
if (config instanceof TimedUpdaterConfig.VoiceChannelConfig) {
|
||||
updateChannel(
|
||||
jda,
|
||||
((TimedUpdaterConfig.VoiceChannelConfig) config).channelIds,
|
||||
((TimedUpdaterConfig.VoiceChannelConfig) config).nameFormat,
|
||||
null
|
||||
);
|
||||
} else if (config instanceof TimedUpdaterConfig.TextChannelConfig) {
|
||||
updateChannel(
|
||||
jda,
|
||||
((TimedUpdaterConfig.TextChannelConfig) config).channelIds,
|
||||
((TimedUpdaterConfig.TextChannelConfig) config).nameFormat,
|
||||
((TimedUpdaterConfig.TextChannelConfig) config).topicFormat
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void updateChannel(JDA jda, List<Long> channelIds, String nameFormat, String topicFormat) {
|
||||
if (topicFormat != null) {
|
||||
topicFormat = discordSRV.placeholderService().replacePlaceholders(topicFormat);
|
||||
}
|
||||
@ -99,7 +122,7 @@ public class ChannelUpdaterModule extends AbstractModule<DiscordSRV> {
|
||||
nameFormat = discordSRV.placeholderService().replacePlaceholders(nameFormat);
|
||||
}
|
||||
|
||||
for (Long channelId : config.channelIds) {
|
||||
for (Long channelId : channelIds) {
|
||||
GuildChannel channel = jda.getGuildChannelById(channelId);
|
||||
if (channel == null) {
|
||||
continue;
|
@ -5,12 +5,23 @@ import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class GameCommandExecution implements CommandExecution {
|
||||
|
||||
private static final TextReplacementConfig URL_REPLACEMENT = TextReplacementConfig.builder()
|
||||
.match(Pattern.compile("^https?://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"))
|
||||
.replacement((matchResult, builder) -> {
|
||||
String url = matchResult.group();
|
||||
return builder.clickEvent(ClickEvent.openUrl(url));
|
||||
})
|
||||
.build();
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final ICommandSender sender;
|
||||
private final GameCommandArguments arguments;
|
||||
@ -37,7 +48,7 @@ public class GameCommandExecution implements CommandExecution {
|
||||
if (!extra.isEmpty()) {
|
||||
builder.hoverEvent(HoverEvent.showText(render(extra)));
|
||||
}
|
||||
sender.sendMessage(builder);
|
||||
sender.sendMessage(builder.build().replaceText(URL_REPLACEMENT));
|
||||
}
|
||||
|
||||
private TextComponent.Builder render(Collection<Text> texts) {
|
||||
|
@ -23,7 +23,7 @@ import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.commands.DiscordSRVGameCommand;
|
||||
import com.discordsrv.common.command.game.commands.subcommand.LinkCommand;
|
||||
import com.discordsrv.common.command.game.handler.ICommandHandler;
|
||||
import com.discordsrv.common.config.main.CommandConfig;
|
||||
import com.discordsrv.common.config.main.GameCommandConfig;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
|
||||
import java.util.HashSet;
|
||||
@ -48,7 +48,7 @@ public class GameCommandModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
@Override
|
||||
public void reloadNoResult() {
|
||||
CommandConfig config = discordSRV.config().command;
|
||||
GameCommandConfig config = discordSRV.config().gameCommand;
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public class DiscordSRVGameCommand implements GameCommandExecutor {
|
||||
@Override
|
||||
public void execute(ICommandSender sender, GameCommandArguments arguments) {
|
||||
MinecraftComponent component = discordSRV.componentFactory()
|
||||
.textBuilder(discordSRV.config().command.discordFormat)
|
||||
.textBuilder(discordSRV.config().gameCommand.discordFormat)
|
||||
.addContext(sender)
|
||||
.applyPlaceholderService()
|
||||
.build();
|
||||
|
@ -19,9 +19,8 @@
|
||||
package com.discordsrv.common.command.game.commands.subcommand;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
@ -32,7 +31,6 @@ import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
@ -98,11 +96,16 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
||||
|
||||
@Override
|
||||
public void execute(ICommandSender sender, GameCommandArguments arguments) {
|
||||
doExecute(sender, arguments);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // Wacky generics
|
||||
private <CC extends BaseChannelConfig & IChannelConfig> void doExecute(ICommandSender sender, GameCommandArguments arguments) {
|
||||
String channel = arguments.getString("channel");
|
||||
String content = arguments.getString("content");
|
||||
|
||||
List<DiscordMessageChannel> channels = new ArrayList<>();
|
||||
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
|
||||
CompletableFuture<List<DiscordGuildMessageChannel>> future = null;
|
||||
try {
|
||||
long id = Long.parseUnsignedLong(channel);
|
||||
|
||||
@ -112,24 +115,17 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
BaseChannelConfig channelConfig = discordSRV.channelConfig().resolve(null, channel);
|
||||
IChannelConfig config = channelConfig instanceof IChannelConfig ? (IChannelConfig) channelConfig : null;
|
||||
CC config = channelConfig instanceof IChannelConfig ? (CC) channelConfig : null;
|
||||
|
||||
if (config != null) {
|
||||
for (Long channelId : config.channelIds()) {
|
||||
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId);
|
||||
if (textChannel != null) {
|
||||
channels.add(textChannel);
|
||||
future = discordSRV.discordAPI().findOrCreateDestinations(config, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
discordSRV.discordAPI().findOrCreateThreads(channelConfig, config, channels::add, futures, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!futures.isEmpty()) {
|
||||
CompletableFutureUtil.combine(futures).whenComplete((v, t) -> execute(sender, content, channel, channels));
|
||||
if (future != null) {
|
||||
future.whenComplete((messageChannels, t) -> doBroadcast(sender, content, channel, messageChannels));
|
||||
} else {
|
||||
execute(sender, content, channel, channels);
|
||||
doBroadcast(sender, content, channel, channels);
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +141,7 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void execute(ICommandSender sender, String content, String channel, List<DiscordMessageChannel> channels) {
|
||||
private void doBroadcast(ICommandSender sender, String content, String channel, List<? extends DiscordMessageChannel> channels) {
|
||||
if (channels.isEmpty()) {
|
||||
sender.sendMessage(
|
||||
Component.text()
|
||||
|
@ -23,6 +23,7 @@ import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
public class LinkCommand implements GameCommandExecutor {
|
||||
|
||||
@ -46,6 +47,6 @@ public class LinkCommand implements GameCommandExecutor {
|
||||
|
||||
@Override
|
||||
public void execute(ICommandSender sender, GameCommandArguments arguments) {
|
||||
|
||||
sender.sendMessage(Component.text("Not currently implemented")); // TODO
|
||||
}
|
||||
}
|
||||
|
@ -79,20 +79,16 @@ public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester
|
||||
return;
|
||||
}
|
||||
|
||||
discordSRV.invokeReload(flags, false).whenComplete((results, t) -> {
|
||||
if (t != null) {
|
||||
discordSRV.logger().error("Failed to reload", t);
|
||||
List<DiscordSRVApi.ReloadResult> results = discordSRV.runReload(flags, false);
|
||||
for (DiscordSRV.ReloadResult result : results) {
|
||||
String res = result.name();
|
||||
if (res.equals(ReloadResults.FAILED.name())) {
|
||||
sender.sendMessage(
|
||||
Component.text()
|
||||
.append(Component.text("Reload failed.", NamedTextColor.DARK_RED, TextDecoration.BOLD))
|
||||
.append(Component.text("Please check the server console/log for more details."))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (DiscordSRV.ReloadResult result : results) {
|
||||
String res = result.name();
|
||||
if (res.equals(ReloadResults.SECURITY_FAILED.name())) {
|
||||
} else if (res.equals(ReloadResults.SECURITY_FAILED.name())) {
|
||||
sender.sendMessage(Component.text(
|
||||
"DiscordSRV is disabled due to a security check failure. "
|
||||
+ "Please check console for more details", NamedTextColor.DARK_RED));
|
||||
@ -124,7 +120,6 @@ public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Set<DiscordSRV.ReloadFlag> getFlagsFromArguments(ICommandSender sender, GameCommandArguments arguments, AtomicBoolean dangerousFlags) {
|
||||
|
@ -3,6 +3,7 @@ package com.discordsrv.common.command.game.commands.subcommand.reload;
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
|
||||
public enum ReloadResults implements DiscordSRVApi.ReloadResult {
|
||||
FAILED,
|
||||
SUCCESS,
|
||||
SECURITY_FAILED,
|
||||
STORAGE_CONNECTION_FAILED,
|
||||
|
@ -23,7 +23,6 @@ import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
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.receive.discord.DiscordChatMessageProcessingEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
|
||||
@ -50,23 +49,23 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
}
|
||||
|
||||
public static void runInContext(
|
||||
DiscordChatMessageProcessingEvent event,
|
||||
DiscordGuild guild,
|
||||
DiscordToMinecraftChatConfig config,
|
||||
Runnable runnable
|
||||
) {
|
||||
getWithContext(event, config, () -> {
|
||||
getWithContext(guild, config, () -> {
|
||||
runnable.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T getWithContext(
|
||||
DiscordChatMessageProcessingEvent event,
|
||||
DiscordGuild guild,
|
||||
DiscordToMinecraftChatConfig config,
|
||||
Supplier<T> supplier
|
||||
) {
|
||||
Context oldValue = CONTEXT.get();
|
||||
CONTEXT.set(new Context(event, config));
|
||||
CONTEXT.set(new Context(guild, config));
|
||||
T output = supplier.get();
|
||||
CONTEXT.set(oldValue);
|
||||
return output;
|
||||
@ -135,9 +134,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
public @NotNull Component appendUserMention(@NotNull Component component, @NotNull String id) {
|
||||
Context context = CONTEXT.get();
|
||||
DiscordToMinecraftChatConfig.Mentions.Format format = context != null ? context.config.mentions.user : null;
|
||||
DiscordGuild guild = context != null
|
||||
? discordSRV.discordAPI().getGuildById(context.event.getGuild().getId())
|
||||
: null;
|
||||
DiscordGuild guild = context != null ? context.guild : null;
|
||||
if (format == null || guild == null) {
|
||||
return component.append(Component.text("<@" + id + ">"));
|
||||
}
|
||||
@ -186,11 +183,11 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
|
||||
|
||||
private static class Context {
|
||||
|
||||
private final DiscordChatMessageProcessingEvent event;
|
||||
private final DiscordGuild guild;
|
||||
private final DiscordToMinecraftChatConfig config;
|
||||
|
||||
public Context(DiscordChatMessageProcessingEvent event, DiscordToMinecraftChatConfig config) {
|
||||
this.event = event;
|
||||
public Context(DiscordGuild guild, DiscordToMinecraftChatConfig config) {
|
||||
this.guild = guild;
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ public class UpdateConfig {
|
||||
public boolean notificationInGame = true;
|
||||
|
||||
@Setting(value = "enable-first-party-api-for-notifications")
|
||||
@Comment("Weather the DiscordSRV download API should be used for update checks\n"
|
||||
@Comment("Whether the DiscordSRV download API should be used for update checks\n"
|
||||
+ "Requires a connection to: download.discordsrv.com")
|
||||
public boolean firstPartyNotification = true;
|
||||
|
||||
@ -49,7 +49,7 @@ public class UpdateConfig {
|
||||
public static class GitHub {
|
||||
|
||||
@Setting(value = "enabled")
|
||||
@Comment("Weather the GitHub API should be used for update checks\n"
|
||||
@Comment("Whether the GitHub API should be used for update checks\n"
|
||||
+ "This will be the secondary API if both first party and GitHub sources are enabled\n"
|
||||
+ "Requires a connection to: api.github.com")
|
||||
public boolean enabled = true;
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.discordsrv.common.config.documentation;
|
||||
|
||||
public class DocumentationURLs {
|
||||
public static final String CREATE_TOKEN = "https://docs.discordsrv.com/installation/initial-setup/#setting-up-the-bot";
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.discordsrv.common.config.main;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class AvatarProviderConfig {
|
||||
|
||||
@Comment("Whether to let DiscordSRV decide an appropriate avatar URL automatically\n" +
|
||||
"This will result in appropriate head renders being provided for Bedrock players (when using Floodgate) and Offline Mode players (via username).")
|
||||
public boolean autoDecideAvatarUrl = true;
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
@Comment("The template for URLs of player avatars\n" +
|
||||
"This will be used for offical Java players only if auto-decide-avatar-url is set to true\n" +
|
||||
"This will be used ALWAYS if auto-decide-avatar-url is set to false")
|
||||
public String avatarUrlTemplate = "https://crafatar.com/avatars/%player_uuid_nodashes%.png?size=128&overlay#%player_texture%";
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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.config.main;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ChannelUpdaterConfig {
|
||||
|
||||
@Comment("The channel IDs.\n"
|
||||
+ "The bot will need the \"View Channel\" and \"Manage Channels\" permissions for the provided channels, "
|
||||
+ "additionally \"Connect\" is required for voice channels")
|
||||
public List<Long> channelIds = new ArrayList<>();
|
||||
|
||||
@Comment("If this is blank, the name will not be updated")
|
||||
public String nameFormat = "";
|
||||
|
||||
@Comment("If this is blank, the topic will not be updated. Unavailable for voice channels")
|
||||
public String topicFormat = "";
|
||||
|
||||
@Comment("The time between updates in minutes. The minimum time is 10 minutes.")
|
||||
public int timeMinutes = 10;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.discordsrv.common.config.main;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ConfigSerializable
|
||||
public class DebugConfig {
|
||||
|
||||
@Comment("If debug messages should be logged into the config")
|
||||
public boolean logToConsole = false;
|
||||
|
||||
@Comment("Additional levels to log\nExample value: {\"AWARD_LISTENER\":[\"TRACE\"]}")
|
||||
public Map<String, List<String>> additionalLevels = new HashMap<>();
|
||||
|
||||
}
|
@ -27,12 +27,12 @@ public class DiscordInviteConfig {
|
||||
@Comment("Manually enter a invite url here, if this isn't set this is ignored and the options below will take effect")
|
||||
public String inviteUrl = "";
|
||||
|
||||
@Comment("If the bot is only in one Discord server, it will attempt to get it's vanity url")
|
||||
@Comment("If the bot is only in one Discord server, it will attempt to get its vanity url")
|
||||
public boolean attemptToUseVanityUrl = true;
|
||||
|
||||
@Comment("If the bot is only in one Discord server, it will attempt to automatically create a invite for it.\n"
|
||||
+ "The bot will only attempt to do so if it has permission to \"Create Invite\"\n"
|
||||
+ "The server must also have a rules channel (available for community servers) or default channel (automatically determined by Discord)")
|
||||
public boolean autoCreateInvite = true;
|
||||
public boolean autoCreateInvite = false;
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class CommandConfig {
|
||||
public class GameCommandConfig {
|
||||
|
||||
@Comment("If the /discord command should be set by DiscordSRV")
|
||||
public boolean useDiscordCommand = true;
|
||||
@ -30,6 +30,6 @@ public class CommandConfig {
|
||||
@Comment("If /link should be used as a alias for /discord link")
|
||||
public boolean useLinkAlias = false;
|
||||
|
||||
@Comment("Discord command format, player placeholders may be used")
|
||||
public String discordFormat = "[click:open_url:%discord_invite%]&b&lClick here &r&ato join our Discord server!";
|
||||
@Comment("The Discord command response format (/discord), player placeholders may be used")
|
||||
public String discordFormat = "[click:open_url:%discord_invite%][color:aqua][bold:on]Click here [color][bold][color:green]to join our Discord server!";
|
||||
}
|
@ -21,6 +21,8 @@ package com.discordsrv.common.config.main;
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
import com.discordsrv.common.config.Config;
|
||||
import com.discordsrv.common.config.annotation.DefaultOnly;
|
||||
import com.discordsrv.common.config.annotation.Order;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
|
||||
import com.discordsrv.common.config.main.linking.LinkedAccountConfig;
|
||||
@ -34,6 +36,14 @@ public abstract class MainConfig implements Config {
|
||||
|
||||
public static final String FILE_NAME = "config.yaml";
|
||||
|
||||
public static final String HEADER = String.join("\n", Arrays.asList(
|
||||
"Welcome to the DiscordSRV configuration file",
|
||||
"",
|
||||
"Looking for the \"BotToken\" option? It has been moved into the " + ConnectionConfig.FILE_NAME,
|
||||
"Need help with the format for Minecraft messages? https://github.com/Vankka/EnhancedLegacyText/wiki/Format",
|
||||
"Need help with Discord markdown? https://support.discord.com/hc/en-us/articles/210298617"
|
||||
));
|
||||
|
||||
@Override
|
||||
public final String getFileName() {
|
||||
return FILE_NAME;
|
||||
@ -48,6 +58,12 @@ public abstract class MainConfig implements Config {
|
||||
}
|
||||
|
||||
@DefaultOnly(ChannelConfig.DEFAULT_KEY)
|
||||
@Comment("Channels configuration\n\n"
|
||||
+ "This is where everything related to in-game chat channels is configured.\n"
|
||||
+ "The key of this option is the in-game channel name (the default keys are \"global\" and \"default\")\n"
|
||||
+ "channel-ids and threads can be configured for all channels except \"default\"\n"
|
||||
+ "\"default\" is a special section which has the default values for all channels unless they are specified (overridden) under the channel's own section\n"
|
||||
+ "So if you don't specify a certain option under a channel's own section, the option will take its value from the \"default\" section")
|
||||
public Map<String, BaseChannelConfig> channels = new LinkedHashMap<String, BaseChannelConfig>() {{
|
||||
put(GameChannel.DEFAULT_NAME, createDefaultChannel());
|
||||
put(ChannelConfig.DEFAULT_KEY, createDefaultBaseChannel());
|
||||
@ -55,18 +71,27 @@ public abstract class MainConfig implements Config {
|
||||
|
||||
public LinkedAccountConfig linkedAccounts = new LinkedAccountConfig();
|
||||
|
||||
public MemberCachingConfig memberCaching = new MemberCachingConfig();
|
||||
|
||||
public List<ChannelUpdaterConfig> channelUpdaters = new ArrayList<>(Collections.singletonList(new ChannelUpdaterConfig()));
|
||||
public TimedUpdaterConfig timedUpdater = new TimedUpdaterConfig();
|
||||
|
||||
@Comment("Configuration options for group-role synchronization")
|
||||
public GroupSyncConfig groupSync = new GroupSyncConfig();
|
||||
|
||||
@Comment("Command configuration")
|
||||
public CommandConfig command = new CommandConfig();
|
||||
@Comment("In-game command configuration")
|
||||
public GameCommandConfig gameCommand = new GameCommandConfig();
|
||||
|
||||
@Comment("Configuration for the %discord_invite% placeholder. The below options will be attempted in the order they are in")
|
||||
public DiscordInviteConfig invite = new DiscordInviteConfig();
|
||||
|
||||
@Order(10) // To go below required linking config @ 5
|
||||
@Comment("Configuration for the %player_avatar_url% placeholder")
|
||||
public AvatarProviderConfig avatarProvider = new AvatarProviderConfig();
|
||||
|
||||
public abstract PluginIntegrationConfig integrations();
|
||||
|
||||
@Order(1000)
|
||||
public MemberCachingConfig memberCaching = new MemberCachingConfig();
|
||||
|
||||
@Order(5000)
|
||||
@Comment("Options for diagnosing DiscordSRV, you do not need to touch these options during normal operation")
|
||||
public DebugConfig debug = new DebugConfig();
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class MemberCachingConfig {
|
||||
public boolean all = false;
|
||||
|
||||
@Comment("If members should be cached at startup, this requires the \"Server Members Intent\"")
|
||||
public boolean chunk = false;
|
||||
public boolean chunk = true;
|
||||
|
||||
@Comment("Filter for which servers should be cached at startup")
|
||||
public GuildFilter chunkingServerFilter = new GuildFilter();
|
||||
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.config.main;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ConfigSerializable
|
||||
public class TimedUpdaterConfig {
|
||||
|
||||
public List<VoiceChannelConfig> voiceChannels = new ArrayList<>(Collections.singletonList(new VoiceChannelConfig()));
|
||||
public List<TextChannelConfig> textChannels = new ArrayList<>(Collections.singletonList(new TextChannelConfig()));
|
||||
|
||||
public List<UpdaterConfig> getConfigs() {
|
||||
List<UpdaterConfig> configs = new ArrayList<>();
|
||||
configs.addAll(voiceChannels);
|
||||
configs.addAll(textChannels);
|
||||
return configs;
|
||||
}
|
||||
|
||||
public interface UpdaterConfig {
|
||||
|
||||
boolean any();
|
||||
long timeSeconds();
|
||||
long minimumSeconds();
|
||||
|
||||
}
|
||||
|
||||
public static class VoiceChannelConfig implements UpdaterConfig {
|
||||
|
||||
@Comment("The channel IDs.\n"
|
||||
+ "The bot will need the \"View Channel\", \"Manage Channels\" and \"Connection\" permissions for the provided channels")
|
||||
public List<Long> channelIds = new ArrayList<>();
|
||||
|
||||
@Comment("The format for the channel name(s), placeholders are supported.")
|
||||
public String nameFormat = "";
|
||||
|
||||
@Comment("The time between updates in minutes. The minimum time is 10 minutes.")
|
||||
public int timeMinutes = 10;
|
||||
|
||||
@Override
|
||||
public boolean any() {
|
||||
return !channelIds.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long timeSeconds() {
|
||||
return TimeUnit.MINUTES.toSeconds(timeMinutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long minimumSeconds() {
|
||||
return TimeUnit.MINUTES.toSeconds(10);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextChannelConfig implements UpdaterConfig {
|
||||
|
||||
@Comment("The channel IDs.\n"
|
||||
+ "The bot will need the \"View Channel\" and \"Manage Channels\" permissions for the provided channels")
|
||||
public List<Long> channelIds = new ArrayList<>();
|
||||
|
||||
@Comment("The format for the channel name(s), placeholders are supported.\n"
|
||||
+ "If this is blank, the name will not be updated")
|
||||
public String nameFormat = "";
|
||||
|
||||
@Comment("The format for the channel topic(s), placeholders are supported.\n"
|
||||
+ "If this is blank, the topic will not be updated")
|
||||
public String topicFormat = "";
|
||||
|
||||
@Comment("The time between updates in minutes. The minimum time is 10 minutes.")
|
||||
public int timeMinutes = 10;
|
||||
|
||||
@Override
|
||||
public boolean any() {
|
||||
return !channelIds.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long timeSeconds() {
|
||||
return TimeUnit.MINUTES.toSeconds(timeMinutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long minimumSeconds() {
|
||||
return TimeUnit.MINUTES.toSeconds(10);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -39,8 +39,11 @@ public class ChannelLockingConfig {
|
||||
@Comment("Role ids for roles that should have the permissions taken while the server is offline")
|
||||
public List<Long> roleIds = new ArrayList<>();
|
||||
|
||||
@Comment("If the \"View Channel\" permission should be taken from the specified roles")
|
||||
public boolean read = false;
|
||||
@Comment("If the \"Send Messages\" permission should be taken from the specified roles")
|
||||
public boolean write = true;
|
||||
@Comment("If the \"Add Reactions\" permission should be taken from the specified roles")
|
||||
public boolean addReactions = true;
|
||||
|
||||
}
|
||||
@ -51,7 +54,7 @@ public class ChannelLockingConfig {
|
||||
@Comment("If the configured threads should be archived while the server is shutdown")
|
||||
public boolean archive = true;
|
||||
|
||||
@Comment("If the bot will attempt to unarchive threads rather than make new threads")
|
||||
@Comment("If the bot should attempt to unarchive threads rather than make new threads")
|
||||
public boolean unarchive = true;
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.DiscordIgnoresConfig;
|
||||
import com.discordsrv.common.config.main.generic.DiscordIgnoresConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ -30,27 +30,30 @@ import java.util.regex.Pattern;
|
||||
@ConfigSerializable
|
||||
public class DiscordToMinecraftChatConfig {
|
||||
|
||||
@Comment("Is Discord to Minecraft chat forwarding enabled")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Comment("The Discord to Minecraft message format for regular users and bots")
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
public String format = "[ᛩF2Discord&r] [hover:show_text:Tag: %user_tag%&r\nRoles: %user_roles:', '|text:'&7&oNone'%]%user_color%%user_effective_name%&r » %message%%message_attachments%";
|
||||
public String format = "[[color:#5865F2]Discord[color]] [hover:show_text:Username: @%user_tag%\nRoles: %user_roles:', '|text:'[color:gray][italics:on]None[color][italics]'%]%user_color%%user_effective_server_name%[color][hover]%message_reply% » %message%%message_attachments%";
|
||||
|
||||
@Comment("The Discord to Minecraft message format for webhook messages (if enabled)")
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
public String webhookFormat = "[ᛩF2Discord&r] [hover:show_text:Webhook message]%user_name%&r » %message%%message_attachments%";
|
||||
public String webhookFormat = "[[color:#5865F2]Discord[color]] [hover:show_text:Bot message]%user_effective_name%[hover] » %message%%message_attachments%";
|
||||
|
||||
@Comment("Attachment format")
|
||||
@Comment("Format for a single attachment in the %message_attachments% placeholder")
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
public String attachmentFormat = " [hover:show_text:Open %file_name% in browser][click:open_url:%file_url%]&a[&f%file_name%&a]&r";
|
||||
public String attachmentFormat = " [hover:show_text:Open %file_name% in browser][click:open_url:%file_url%][color:green][[color:white]%file_name%[color:green]][color][click][hover]";
|
||||
|
||||
@Comment("Format for the %message_reply% placeholder, when the message is a reply to another message")
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
public String replyFormat = " [hover:show_text:%message%][click:open_url:%message_jump_url%]replying to %user_color|text:''%%user_effective_server_name|user_effective_name%[color][click][hover]";
|
||||
|
||||
// TODO: more info on regex pairs (String#replaceAll)
|
||||
@Comment("Regex filters for Discord message contents (this is the %message% part of the \"format\" option)")
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
public Map<Pattern, String> contentRegexFilters = new LinkedHashMap<>();
|
||||
|
||||
@Comment("Users, bots and webhooks to ignore")
|
||||
@Comment("Users, bots, roles and webhooks to ignore")
|
||||
public DiscordIgnoresConfig ignores = new DiscordIgnoresConfig();
|
||||
|
||||
@Comment("The representations of Discord mentions in-game")
|
||||
@ -59,11 +62,11 @@ public class DiscordToMinecraftChatConfig {
|
||||
@ConfigSerializable
|
||||
public static class Mentions {
|
||||
|
||||
public Format role = new Format("ᛩf2@%role_name%", "ᛩf2@deleted-role");
|
||||
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 Format role = new Format("%role_color%@%role_name%", "[color:#5865F2]@deleted-role");
|
||||
public Format channel = new Format("[hover:show_text:Click to go to channel][click:open_url:%channel_jump_url%][color:#5865F2]#%channel_name%", "[color:#5865F2]#Unknown");
|
||||
public Format user = new Format("[hover:show_text:Username: @%user_tag%\nRoles: %user_roles:', '|text:'[color:gray][italics:on]None[color][italics]'%][color:#5865F2]@%user_effective_server_name|user_effective_name%", "[color:#5865F2]@Unknown user");
|
||||
|
||||
public String messageUrl = "[hover:show_text:Click to go to message][click:open_url:%jump_url%]ᛩf2#%channel_name% > ...";
|
||||
public String messageUrl = "[hover:show_text:Click to go to message][click:open_url:%jump_url%][color:#5865F2]#%channel_name% > ...";
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Format {
|
||||
|
@ -22,6 +22,7 @@ import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.api.event.events.message.receive.game.JoinMessageReceiveEvent;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
@ -21,6 +21,7 @@ package com.discordsrv.common.config.main.channels;
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
@ConfigSerializable
|
||||
|
@ -21,6 +21,7 @@ package com.discordsrv.common.config.main.channels;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.annotation.DefaultOnly;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ -31,7 +32,6 @@ import java.util.regex.Pattern;
|
||||
@ConfigSerializable
|
||||
public class MinecraftToDiscordChatConfig implements IMessageConfig {
|
||||
|
||||
@Comment("Is Minecraft to Discord chat forwarding enabled")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
|
||||
import com.discordsrv.common.config.main.DiscordIgnoresConfig;
|
||||
import com.discordsrv.common.config.main.generic.DiscordIgnoresConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ -27,16 +27,18 @@ public class MirroringConfig {
|
||||
|
||||
public boolean enabled = true;
|
||||
|
||||
@Comment("Users, bots and webhooks to ignore when mirroring")
|
||||
@Comment("Users, bots, roles and webhooks to ignore when mirroring")
|
||||
public DiscordIgnoresConfig ignores = new DiscordIgnoresConfig();
|
||||
|
||||
@Comment("The format of the username of mirrored messages\n"
|
||||
+ "It's recommended to include some special character if in-game messages use webhooks,\n"
|
||||
+ "in order to prevent Discord users and in-game players with the same name being grouped together")
|
||||
public String usernameFormat = "%user_effective_name% \uD83D\uDD03";
|
||||
public String usernameFormat = "%user_effective_server_name|user_effective_name% \uD83D\uDD03";
|
||||
|
||||
@Comment("Content to append to the beginning of a message if the message is replying to another")
|
||||
public String replyFormat = "[In reply to %user_effective_name|user_name%](%message_jump_url%)\n";
|
||||
@Comment("The format when a message is a reply.\n"
|
||||
+ "%message% will be replaced with the message content\n"
|
||||
+ "%message_jump_url% will be replaced with the url to the replied message in the channel the message is sent in")
|
||||
public String replyFormat = "[In reply to %user_effective_server_name|user_effective_name%](%message_jump_url%)\n%message%";
|
||||
|
||||
@Comment("Attachment related options")
|
||||
public AttachmentConfig attachments = new AttachmentConfig();
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.common.config.main.channels;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
@ConfigSerializable
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.common.config.main.channels;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
@ConfigSerializable
|
||||
|
@ -39,10 +39,6 @@ public class BaseChannelConfig {
|
||||
@Order(2)
|
||||
public LeaveMessageConfig leaveMessages = new LeaveMessageConfig();
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
@Order(10)
|
||||
public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay";
|
||||
|
||||
@Order(20)
|
||||
public StartMessageConfig startMessage = new StartMessageConfig();
|
||||
@Order(20)
|
||||
|
@ -18,12 +18,10 @@
|
||||
|
||||
package com.discordsrv.common.config.main.channels.base;
|
||||
|
||||
import com.discordsrv.common.config.main.generic.DestinationConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ChannelConfig extends BaseChannelConfig implements IChannelConfig {
|
||||
|
||||
@ -31,22 +29,11 @@ public class ChannelConfig extends BaseChannelConfig implements IChannelConfig {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Setting(CHANNEL_IDS_OPTION_NAME)
|
||||
@Comment(CHANNEL_IDS_COMMENT)
|
||||
public List<Long> channelIds = CHANNEL_IDS_VALUE;
|
||||
@Setting(nodeFromParent = true)
|
||||
public DestinationConfig destination = new DestinationConfig();
|
||||
|
||||
@Override
|
||||
public List<Long> channelIds() {
|
||||
return channelIds;
|
||||
public DestinationConfig destination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
@Setting(THREADS_OPTION_NAME)
|
||||
@Comment(THREADS_COMMENT)
|
||||
public List<ThreadConfig> threads = THREADS_VALUE;
|
||||
|
||||
@Override
|
||||
public List<ThreadConfig> threads() {
|
||||
return threads;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,38 +18,22 @@
|
||||
|
||||
package com.discordsrv.common.config.main.channels.base;
|
||||
|
||||
import com.discordsrv.common.config.main.generic.DestinationConfig;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public interface IChannelConfig {
|
||||
|
||||
String DEFAULT_KEY = "default";
|
||||
|
||||
String CHANNEL_IDS_OPTION_NAME = "channel-ids";
|
||||
String CHANNEL_IDS_COMMENT = "The channels this in-game channel will forward to in Discord";
|
||||
List<Long> CHANNEL_IDS_VALUE = new ArrayList<>();
|
||||
|
||||
List<Long> channelIds();
|
||||
|
||||
String THREADS_OPTION_NAME = "threads";
|
||||
String THREADS_COMMENT = "The threads that this in-game channel will forward to in Discord (this can be used instead of or with the channel-ids option)";
|
||||
List<ThreadConfig> THREADS_VALUE = new ArrayList<>(Collections.singletonList(new ThreadConfig()));
|
||||
|
||||
List<String> VALUES = Arrays.asList(CHANNEL_IDS_OPTION_NAME, THREADS_OPTION_NAME);
|
||||
|
||||
List<ThreadConfig> threads();
|
||||
DestinationConfig destination();
|
||||
|
||||
default void initialize() {
|
||||
// Clear everything besides channelIds by default (these will be filled back in by Configurate if they are in the config itself)
|
||||
@ -61,11 +45,6 @@ public interface IChannelConfig {
|
||||
continue;
|
||||
}
|
||||
|
||||
Setting setting = field.getAnnotation(Setting.class);
|
||||
if (setting != null && VALUES.contains(setting.value())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
field.set(this, null);
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
|
@ -20,7 +20,7 @@ package com.discordsrv.common.config.main.channels.base.proxy;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Order;
|
||||
import com.discordsrv.common.config.main.channels.JoinMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.ServerSwitchMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.proxy.ServerSwitchMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
|
@ -19,13 +19,10 @@
|
||||
package com.discordsrv.common.config.main.channels.base.proxy;
|
||||
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.ThreadConfig;
|
||||
import com.discordsrv.common.config.main.generic.DestinationConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ProxyChannelConfig extends ProxyBaseChannelConfig implements IChannelConfig {
|
||||
|
||||
@ -33,21 +30,11 @@ public class ProxyChannelConfig extends ProxyBaseChannelConfig implements IChann
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Setting(CHANNEL_IDS_OPTION_NAME)
|
||||
@Comment(CHANNEL_IDS_COMMENT)
|
||||
public List<Long> channelIds = CHANNEL_IDS_VALUE;
|
||||
@Setting(nodeFromParent = true)
|
||||
public DestinationConfig destination = new DestinationConfig();
|
||||
|
||||
@Override
|
||||
public List<Long> channelIds() {
|
||||
return channelIds;
|
||||
}
|
||||
|
||||
@Setting(THREADS_OPTION_NAME)
|
||||
@Comment(THREADS_COMMENT)
|
||||
public List<ThreadConfig> threads = THREADS_VALUE;
|
||||
|
||||
@Override
|
||||
public List<ThreadConfig> threads() {
|
||||
return threads;
|
||||
public DestinationConfig destination() {
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,12 @@
|
||||
package com.discordsrv.common.config.main.channels.base.server;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Order;
|
||||
import com.discordsrv.common.config.main.channels.AwardMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.DeathMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.server.AwardMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.server.DeathMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.server.ServerJoinMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ServerBaseChannelConfig extends BaseChannelConfig {
|
||||
@ -31,6 +33,7 @@ public class ServerBaseChannelConfig extends BaseChannelConfig {
|
||||
public ServerJoinMessageConfig joinMessages = new ServerJoinMessageConfig();
|
||||
|
||||
@Order(3)
|
||||
@Comment("Advancement/Achievement message configuration")
|
||||
public AwardMessageConfig awardMessages = new AwardMessageConfig();
|
||||
|
||||
@Order(3)
|
||||
|
@ -19,13 +19,10 @@
|
||||
package com.discordsrv.common.config.main.channels.base.server;
|
||||
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.ThreadConfig;
|
||||
import com.discordsrv.common.config.main.generic.DestinationConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class ServerChannelConfig extends ServerBaseChannelConfig implements IChannelConfig {
|
||||
|
||||
@ -33,21 +30,11 @@ public class ServerChannelConfig extends ServerBaseChannelConfig implements ICha
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Setting(CHANNEL_IDS_OPTION_NAME)
|
||||
@Comment(CHANNEL_IDS_COMMENT)
|
||||
public List<Long> channelIds = CHANNEL_IDS_VALUE;
|
||||
@Setting(nodeFromParent = true)
|
||||
public DestinationConfig destination = new DestinationConfig();
|
||||
|
||||
@Override
|
||||
public List<Long> channelIds() {
|
||||
return channelIds;
|
||||
}
|
||||
|
||||
@Setting(THREADS_OPTION_NAME)
|
||||
@Comment(THREADS_COMMENT)
|
||||
public List<ThreadConfig> threads = THREADS_VALUE;
|
||||
|
||||
@Override
|
||||
public List<ThreadConfig> threads() {
|
||||
return threads;
|
||||
public DestinationConfig destination() {
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
package com.discordsrv.common.config.main.channels.proxy;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
||||
@ConfigSerializable
|
@ -16,18 +16,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
package com.discordsrv.common.config.main.channels.server;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class AwardMessageConfig implements IMessageConfig {
|
||||
|
||||
@Comment("Enable achievement/advancement message forwarding")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
||||
@ -35,7 +34,7 @@ public class AwardMessageConfig implements IMessageConfig {
|
||||
.addEmbed(
|
||||
DiscordMessageEmbed.builder()
|
||||
.setAuthor(
|
||||
"%award_title|text:'{player_name} made the achievement {award_name}'%",
|
||||
"%award_title|text:'{player_name} made the advancement {award_name}'%",
|
||||
null,
|
||||
"%player_avatar_url%"
|
||||
)
|
@ -16,18 +16,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
package com.discordsrv.common.config.main.channels.server;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.generic.IMessageConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class DeathMessageConfig implements IMessageConfig {
|
||||
|
||||
@Comment("Enable death message forwarding")
|
||||
public boolean enabled = true;
|
||||
|
||||
@Untranslated(Untranslated.Type.VALUE)
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main.channels.base.server;
|
||||
package com.discordsrv.common.config.main.channels.server;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Order;
|
||||
import com.discordsrv.common.config.main.channels.JoinMessageConfig;
|
@ -0,0 +1,22 @@
|
||||
package com.discordsrv.common.config.main.generic;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigSerializable
|
||||
public class DestinationConfig {
|
||||
|
||||
@Setting("channel-ids")
|
||||
@Comment("The channels this in-game channel will forward to in Discord")
|
||||
public List<Long> channelIds = new ArrayList<>();
|
||||
|
||||
|
||||
@Setting("threads")
|
||||
@Comment("The threads that this in-game channel will forward to in Discord (this can be used instead of or with the channel-ids option)")
|
||||
public List<ThreadConfig> threads = new ArrayList<>(Collections.singletonList(new ThreadConfig()));
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main;
|
||||
package com.discordsrv.common.config.main.generic;
|
||||
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
@ -31,7 +31,7 @@ import java.util.Optional;
|
||||
public class DiscordIgnoresConfig {
|
||||
|
||||
@Comment("User, bot and webhook ids to ignore")
|
||||
public IDs usersAndWebhookIds = new IDs();
|
||||
public IDs userBotAndWebhookIds = new IDs();
|
||||
|
||||
@Comment("Role ids for users and bots to ignore")
|
||||
public IDs roleIds = new IDs();
|
||||
@ -58,7 +58,7 @@ public class DiscordIgnoresConfig {
|
||||
return true;
|
||||
}
|
||||
|
||||
DiscordIgnoresConfig.IDs users = usersAndWebhookIds;
|
||||
DiscordIgnoresConfig.IDs users = userBotAndWebhookIds;
|
||||
if (users != null && users.ids.contains(author.getId()) != users.whitelist) {
|
||||
return true;
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
package com.discordsrv.common.config.main.generic;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.config.main.channels.base;
|
||||
package com.discordsrv.common.config.main.generic;
|
||||
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
|
@ -30,7 +30,7 @@ public class LinkedAccountConfig {
|
||||
|
||||
@Comment("The linked account provider\n"
|
||||
+ "\n"
|
||||
+ " - auto: Defaults to using \"minecraftauth\" (if the " + ConnectionConfig.FILE_NAME + " permits it) otherwise \"storage\"\n"
|
||||
+ " - auto: Uses \"minecraftauth\" if the " + ConnectionConfig.FILE_NAME + " permits it and the server is in online mode, otherwise \"storage\"\n"
|
||||
+ " - minecraftauth: Uses minecraftauth.me as the linked account provider\n"
|
||||
+ " - storage: Use the configured database for linked accounts")
|
||||
public String provider = "auto";
|
||||
|
@ -20,12 +20,10 @@ package com.discordsrv.common.config.main.linking;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Order;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public abstract class RequiredLinkingConfig {
|
||||
|
||||
@Comment("If required linking is enabled")
|
||||
@Order(-10)
|
||||
public boolean enabled = false;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
package com.discordsrv.common.config.main.linking;
|
||||
|
||||
import com.discordsrv.common.config.annotation.DefaultOnly;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
@ -43,7 +44,8 @@ public class RequirementsConfig {
|
||||
+ "DiscordServer(Server ID)\n"
|
||||
+ "DiscordBoosting(Server ID)\n"
|
||||
+ "DiscordRole(Role ID)\n"
|
||||
+ "The following are available if you're using MinecraftAuth.me for linked accounts:\n"
|
||||
+ "\n"
|
||||
+ "The following are available if you're using MinecraftAuth.me for linked accounts and a MinecraftAuth.me token is specified in the " + ConnectionConfig.FILE_NAME + ":\n"
|
||||
+ "PatreonSubscriber() or PatreonSubscriber(Tier Title)\n"
|
||||
+ "GlimpseSubscriber() or GlimpseSubscriber(Level Name)\n"
|
||||
+ "TwitchFollower()\n"
|
||||
|
@ -22,6 +22,8 @@ import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.MainConfig;
|
||||
import com.discordsrv.common.config.manager.loader.YamlConfigLoaderProvider;
|
||||
import com.discordsrv.common.config.manager.manager.TranslatedConfigManager;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
public abstract class MainConfigManager<C extends MainConfig>
|
||||
@ -32,6 +34,12 @@ public abstract class MainConfigManager<C extends MainConfig>
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationOptions defaultOptions(ObjectMapper.Factory objectMapper) {
|
||||
return super.defaultOptions(objectMapper)
|
||||
.header(MainConfig.HEADER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fileName() {
|
||||
return MainConfig.FILE_NAME;
|
||||
|
@ -60,6 +60,8 @@ import java.util.regex.Pattern;
|
||||
public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurationLoader<CommentedConfigurationNode>>
|
||||
implements ConfigManager<T>, ConfigLoaderProvider<LT> {
|
||||
|
||||
public static ThreadLocal<Boolean> CLEAN_MAPPER = ThreadLocal.withInitial(() -> false);
|
||||
|
||||
public static NamingScheme NAMING_SCHEME = in -> {
|
||||
in = Character.toLowerCase(in.charAt(0)) + in.substring(1);
|
||||
in = NamingSchemes.LOWER_CASE_DASHED.coerce(in);
|
||||
@ -226,7 +228,16 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
|
||||
}
|
||||
|
||||
private CommentedConfigurationNode getDefault(T defaultConfig, boolean cleanMapper) throws SerializationException {
|
||||
try {
|
||||
if (cleanMapper) {
|
||||
CLEAN_MAPPER.set(true);
|
||||
}
|
||||
return getDefault(defaultConfig, cleanMapper ? defaultObjectMapper() : configObjectMapper());
|
||||
} finally {
|
||||
if (cleanMapper) {
|
||||
CLEAN_MAPPER.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.common.config.serializer;
|
||||
|
||||
import com.discordsrv.api.color.Color;
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.common.config.manager.manager.ConfigurateConfigManager;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
@ -45,6 +46,9 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
|
||||
|
||||
@Override
|
||||
public DiscordMessageEmbed.Builder deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
||||
if (ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
return null;
|
||||
}
|
||||
if (!node.node(map("Enabled")).getBoolean(node.node(map("Enable")).getBoolean(true))) {
|
||||
return null;
|
||||
}
|
||||
@ -86,7 +90,7 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
|
||||
@Override
|
||||
public void serialize(Type type, DiscordMessageEmbed.@Nullable Builder obj, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
if (obj == null) {
|
||||
if (obj == null || ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
node.set(null);
|
||||
return;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.common.config.serializer;
|
||||
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.common.config.manager.manager.ConfigurateConfigManager;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
@ -47,7 +48,7 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
||||
public SendableDiscordMessage.Builder deserialize(Type type, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
String contentOnly = node.getString();
|
||||
if (contentOnly != null) {
|
||||
if (contentOnly != null || ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
return SendableDiscordMessage.builder()
|
||||
.setContent(contentOnly);
|
||||
}
|
||||
@ -81,7 +82,7 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
||||
@Override
|
||||
public void serialize(Type type, SendableDiscordMessage.@Nullable Builder obj, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
if (obj == null) {
|
||||
if (obj == null || ConfigurateConfigManager.CLEAN_MAPPER.get()) {
|
||||
node.set(null);
|
||||
return;
|
||||
}
|
||||
|
@ -20,10 +20,20 @@ package com.discordsrv.common.debug.data;
|
||||
|
||||
public enum OnlineMode {
|
||||
|
||||
ONLINE,
|
||||
OFFLINE,
|
||||
BUNGEE,
|
||||
VELOCITY;
|
||||
ONLINE(true),
|
||||
OFFLINE(false),
|
||||
BUNGEE(true),
|
||||
VELOCITY(true);
|
||||
|
||||
private final boolean online;
|
||||
|
||||
OnlineMode(boolean online) {
|
||||
this.online = online;
|
||||
}
|
||||
|
||||
public boolean isOnline() {
|
||||
return online;
|
||||
}
|
||||
|
||||
public static OnlineMode of(boolean onlineMode) {
|
||||
return onlineMode ? OnlineMode.ONLINE : OnlineMode.OFFLINE;
|
||||
|
@ -18,8 +18,6 @@
|
||||
|
||||
package com.discordsrv.common.discord.api;
|
||||
|
||||
import club.minnced.discord.webhook.WebhookClient;
|
||||
import club.minnced.discord.webhook.WebhookClientBuilder;
|
||||
import com.discordsrv.api.discord.DiscordAPI;
|
||||
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
|
||||
import com.discordsrv.api.discord.connection.jda.errorresponse.ErrorCallbackContext;
|
||||
@ -34,7 +32,8 @@ import com.discordsrv.api.discord.exception.RestErrorResponseException;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.ThreadConfig;
|
||||
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.DiscordGuildImpl;
|
||||
@ -47,6 +46,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.Channel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.*;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
||||
@ -58,6 +58,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -68,7 +69,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final DiscordCommandRegistry commandRegistry;
|
||||
private final AsyncLoadingCache<Long, WebhookClient> cachedClients;
|
||||
private final AsyncLoadingCache<Long, WebhookClient<Message>> cachedClients;
|
||||
private final List<ThreadChannelLookup> threadLookups = new CopyOnWriteArrayList<>();
|
||||
|
||||
public DiscordAPIImpl(DiscordSRV discordSRV) {
|
||||
@ -79,46 +80,46 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
.buildAsync(new WebhookCacheLoader());
|
||||
}
|
||||
|
||||
public CompletableFuture<WebhookClient> queryWebhookClient(long channelId) {
|
||||
public CompletableFuture<WebhookClient<Message>> queryWebhookClient(long channelId) {
|
||||
return cachedClients.get(channelId);
|
||||
}
|
||||
|
||||
public AsyncLoadingCache<Long, WebhookClient> getCachedClients() {
|
||||
public AsyncLoadingCache<Long, WebhookClient<Message>> getCachedClients() {
|
||||
return cachedClients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds active threads based for the provided {@link IChannelConfig}.
|
||||
* @param config the config that specified the threads
|
||||
* @return the list of active threads
|
||||
*/
|
||||
public List<DiscordThreadChannel> findThreads(BaseChannelConfig config, IChannelConfig channelConfig) {
|
||||
List<DiscordThreadChannel> channels = new ArrayList<>();
|
||||
findOrCreateThreads(config, channelConfig, channels::add, null, false);
|
||||
return channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds or potentially unarchives or creates threads based on the provided {@link IChannelConfig}.
|
||||
* @param config the config
|
||||
* @param channelConsumer the consumer that will take the channels as they are gathered
|
||||
* @param futures a possibly null list of {@link CompletableFuture} for tasks that need to be completed to get all threads
|
||||
*/
|
||||
public void findOrCreateThreads(
|
||||
BaseChannelConfig config,
|
||||
IChannelConfig channelConfig,
|
||||
Consumer<DiscordThreadChannel> channelConsumer,
|
||||
@Nullable List<CompletableFuture<DiscordThreadChannel>> futures,
|
||||
public <T extends BaseChannelConfig & IChannelConfig> List<DiscordGuildMessageChannel> findDestinations(
|
||||
T config,
|
||||
boolean log
|
||||
) {
|
||||
List<ThreadConfig> threads = channelConfig.threads();
|
||||
if (threads == null) {
|
||||
return;
|
||||
return findOrCreateDestinations(config, false, log).join();
|
||||
}
|
||||
|
||||
for (ThreadConfig threadConfig : threads) {
|
||||
public <T extends BaseChannelConfig & IChannelConfig> CompletableFuture<List<DiscordGuildMessageChannel>> findOrCreateDestinations(
|
||||
T config,
|
||||
boolean create,
|
||||
boolean log
|
||||
) {
|
||||
DestinationConfig destination = config.destination();
|
||||
|
||||
List<DiscordGuildMessageChannel> channels = new CopyOnWriteArrayList<>();
|
||||
for (Long channelId : destination.channelIds) {
|
||||
DiscordMessageChannel channel = getMessageChannelById(channelId);
|
||||
if (!(channel instanceof DiscordGuildMessageChannel)) {
|
||||
continue;
|
||||
}
|
||||
channels.add((DiscordGuildMessageChannel) channel);
|
||||
}
|
||||
|
||||
List<CompletableFuture<Void>> threadFutures = new ArrayList<>();
|
||||
List<ThreadConfig> threadConfigs = destination.threads;
|
||||
if (threadConfigs != null && !threadConfigs.isEmpty()) {
|
||||
for (ThreadConfig threadConfig : threadConfigs) {
|
||||
long channelId = threadConfig.channelId;
|
||||
DiscordTextChannel channel = getTextChannelById(channelId);
|
||||
DiscordThreadContainer channel = getTextChannelById(channelId);
|
||||
if (channel == null) {
|
||||
channel = getForumChannelById(channelId);
|
||||
}
|
||||
if (channel == null) {
|
||||
if (channelId > 0 && log) {
|
||||
discordSRV.logger().error("Unable to find channel with ID " + Long.toUnsignedString(channelId));
|
||||
@ -131,13 +132,12 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
if (thread != null) {
|
||||
ThreadChannel jdaChannel = thread.asJDA();
|
||||
if (!jdaChannel.isArchived()) {
|
||||
channelConsumer.accept(thread);
|
||||
channels.add(getThreadChannel(jdaChannel));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (futures == null) {
|
||||
// Futures not specified, don't try to unarchive or create threads
|
||||
if (!create) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -151,23 +151,27 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
future = findOrCreateThread(config, threadConfig, channel);
|
||||
}
|
||||
|
||||
futures.add(future.handle((threadChannel, t) -> {
|
||||
DiscordThreadContainer container = channel;
|
||||
threadFutures.add(future.handle((threadChannel, t) -> {
|
||||
if (t != null) {
|
||||
ErrorCallbackContext.context(
|
||||
"Failed to deliver message to thread \""
|
||||
+ threadConfig.threadName + "\" in channel " + channel
|
||||
+ threadConfig.threadName + "\" in channel " + container
|
||||
).accept(t);
|
||||
throw new RuntimeException(); // Just here to fail the future
|
||||
}
|
||||
|
||||
if (threadChannel != null) {
|
||||
channelConsumer.accept(threadChannel);
|
||||
channels.add(threadChannel);
|
||||
}
|
||||
return threadChannel;
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return CompletableFutureUtil.combine(threadFutures).thenApply(v -> channels);
|
||||
}
|
||||
|
||||
private DiscordThreadChannel findThread(ThreadConfig config, List<DiscordThreadChannel> threads) {
|
||||
for (DiscordThreadChannel thread : threads) {
|
||||
if (thread.getName().equals(config.threadName)) {
|
||||
@ -180,17 +184,17 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
private CompletableFuture<DiscordThreadChannel> findOrCreateThread(
|
||||
BaseChannelConfig config,
|
||||
ThreadConfig threadConfig,
|
||||
DiscordTextChannel textChannel
|
||||
DiscordThreadContainer container
|
||||
) {
|
||||
if (!config.channelLocking.threads.unarchive) {
|
||||
return textChannel.createThread(threadConfig.threadName, threadConfig.privateThread);
|
||||
return container.createThread(threadConfig.threadName, threadConfig.privateThread);
|
||||
}
|
||||
|
||||
CompletableFuture<DiscordThreadChannel> completableFuture = new CompletableFuture<>();
|
||||
lookupThreads(
|
||||
textChannel,
|
||||
container,
|
||||
threadConfig.privateThread,
|
||||
lookup -> findOrCreateThread(threadConfig, textChannel, lookup, completableFuture),
|
||||
lookup -> findOrCreateThread(threadConfig, container, lookup, completableFuture),
|
||||
(thread, throwable) -> {
|
||||
if (throwable != null) {
|
||||
completableFuture.completeExceptionally(throwable);
|
||||
@ -203,7 +207,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
|
||||
private void findOrCreateThread(
|
||||
ThreadConfig config,
|
||||
DiscordTextChannel textChannel,
|
||||
DiscordThreadContainer container,
|
||||
ThreadChannelLookup lookup,
|
||||
CompletableFuture<DiscordThreadChannel> completableFuture
|
||||
) {
|
||||
@ -222,7 +226,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
}
|
||||
|
||||
DiscordThreadChannel thread = findThread(config, channels);
|
||||
unarchiveOrCreateThread(config, textChannel, thread, completableFuture);
|
||||
unarchiveOrCreateThread(config, container, thread, completableFuture);
|
||||
}).exceptionally(t -> {
|
||||
if (t instanceof CompletionException) {
|
||||
completableFuture.completeExceptionally(t.getCause());
|
||||
@ -235,7 +239,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
|
||||
private void unarchiveOrCreateThread(
|
||||
ThreadConfig config,
|
||||
DiscordTextChannel textChannel,
|
||||
DiscordThreadContainer container,
|
||||
DiscordThreadChannel thread,
|
||||
CompletableFuture<DiscordThreadChannel> future
|
||||
) {
|
||||
@ -256,7 +260,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return;
|
||||
}
|
||||
|
||||
textChannel.createThread(config.threadName, config.privateThread).whenComplete(((threadChannel, t) -> {
|
||||
container.createThread(config.threadName, config.privateThread).whenComplete(((threadChannel, t) -> {
|
||||
if (t != null) {
|
||||
future.completeExceptionally(t);
|
||||
} else {
|
||||
@ -266,7 +270,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
}
|
||||
|
||||
public void lookupThreads(
|
||||
DiscordTextChannel textChannel,
|
||||
DiscordThreadContainer container,
|
||||
boolean privateThreads,
|
||||
Consumer<ThreadChannelLookup> lookupConsumer,
|
||||
BiConsumer<DiscordThreadChannel, Throwable> channelConsumer
|
||||
@ -275,7 +279,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
synchronized (threadLookups) {
|
||||
for (ThreadChannelLookup threadLookup : threadLookups) {
|
||||
if (threadLookup.isPrivateThreads() != privateThreads
|
||||
|| threadLookup.getChannelId() != textChannel.getId()) {
|
||||
|| threadLookup.getChannelId() != container.getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -284,10 +288,10 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
}
|
||||
|
||||
lookup = new ThreadChannelLookup(
|
||||
textChannel.getId(), privateThreads,
|
||||
container.getId(), privateThreads,
|
||||
privateThreads
|
||||
? textChannel.retrieveArchivedPrivateThreads()
|
||||
: textChannel.retrieveArchivedPublicThreads()
|
||||
? container.retrieveArchivedPrivateThreads()
|
||||
: container.retrieveArchivedPublicThreads()
|
||||
);
|
||||
threadLookups.add(lookup);
|
||||
}
|
||||
@ -338,6 +342,16 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return getDirectMessageChannelById(id);
|
||||
}
|
||||
|
||||
public DiscordChannel getChannel(Channel jda) {
|
||||
if (jda instanceof ForumChannel) {
|
||||
return getForumChannel((ForumChannel) jda);
|
||||
} else if (jda instanceof MessageChannel) {
|
||||
return getMessageChannel((MessageChannel) jda);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unmappable Channel type: " + jda.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractDiscordMessageChannel<?> getMessageChannel(MessageChannel jda) {
|
||||
if (jda instanceof TextChannel) {
|
||||
return getTextChannel((TextChannel) jda);
|
||||
@ -393,6 +407,15 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return new DiscordTextChannelImpl(discordSRV, jda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordForumChannel getForumChannelById(long id) {
|
||||
return mapJDAEntity(jda -> jda.getForumChannelById(id), this::getForumChannel);
|
||||
}
|
||||
|
||||
public DiscordForumChannelImpl getForumChannel(ForumChannel jda) {
|
||||
return new DiscordForumChannelImpl(discordSRV, jda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscordVoiceChannel getVoiceChannelById(long id) {
|
||||
return mapJDAEntity(jda -> jda.getVoiceChannelById(id), this::getVoiceChannel);
|
||||
@ -489,10 +512,10 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
return commandRegistry;
|
||||
}
|
||||
|
||||
private class WebhookCacheLoader implements AsyncCacheLoader<Long, WebhookClient> {
|
||||
private class WebhookCacheLoader implements AsyncCacheLoader<Long, WebhookClient<Message>> {
|
||||
|
||||
@Override
|
||||
public @NonNull CompletableFuture<WebhookClient> asyncLoad(@NonNull Long channelId, @NonNull Executor executor) {
|
||||
public @NonNull CompletableFuture<WebhookClient<Message>> asyncLoad(@NonNull Long channelId, @NonNull Executor executor) {
|
||||
JDA jda = discordSRV.jda();
|
||||
if (jda == null) {
|
||||
return notReady();
|
||||
@ -525,22 +548,32 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
|
||||
return textChannel.createWebhook("DSRV").submit();
|
||||
}).thenApply(webhook ->
|
||||
WebhookClientBuilder.fromJDA(webhook)
|
||||
.setHttpClient(jda.getHttpClient())
|
||||
.setExecutorService(discordSRV.scheduler().scheduledExecutorService())
|
||||
.build()
|
||||
WebhookClient.createClient(
|
||||
webhook.getJDA(),
|
||||
webhook.getId(),
|
||||
Objects.requireNonNull(webhook.getToken())
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private class WebhookCacheExpiry implements Expiry<Long, WebhookClient> {
|
||||
private class WebhookCacheExpiry implements Expiry<Long, WebhookClient<Message>> {
|
||||
|
||||
private boolean isConfiguredChannel(Long channelId) {
|
||||
for (BaseChannelConfig config : discordSRV.config().channels.values()) {
|
||||
if (config instanceof IChannelConfig
|
||||
&& ((IChannelConfig) config).channelIds().contains(channelId)) {
|
||||
DestinationConfig destination = config instanceof IChannelConfig ? ((IChannelConfig) config).destination() : null;
|
||||
if (destination == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (destination.channelIds.contains(channelId)) {
|
||||
return true;
|
||||
}
|
||||
for (ThreadConfig thread : destination.threads) {
|
||||
if (Objects.equals(thread.channelId, channelId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -61,6 +61,11 @@ public class DiscordUserImpl implements DiscordUser {
|
||||
return user.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getEffectiveName() {
|
||||
return user.getEffectiveName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getDiscriminator() {
|
||||
return user.getDiscriminator();
|
||||
|
@ -18,9 +18,6 @@
|
||||
|
||||
package com.discordsrv.common.discord.api.entity.channel;
|
||||
|
||||
import club.minnced.discord.webhook.WebhookClient;
|
||||
import club.minnced.discord.webhook.receive.ReadonlyMessage;
|
||||
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
|
||||
@ -29,16 +26,16 @@ import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageImpl;
|
||||
import com.discordsrv.common.discord.api.entity.message.util.SendableDiscordMessageUtil;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.WebhookClient;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
|
||||
import net.dv8tion.jda.api.requests.FluentRestAction;
|
||||
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
|
||||
import net.dv8tion.jda.api.utils.FileUpload;
|
||||
import net.dv8tion.jda.api.requests.RestAction;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageCreateRequest;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageEditData;
|
||||
import net.dv8tion.jda.api.utils.messages.MessageEditRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageChannel>
|
||||
extends AbstractDiscordMessageChannel<T>
|
||||
@ -51,7 +48,7 @@ public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageC
|
||||
this.guild = discordSRV.discordAPI().getGuild(channel.getGuild());
|
||||
}
|
||||
|
||||
public CompletableFuture<WebhookClient> queryWebhookClient() {
|
||||
public CompletableFuture<WebhookClient<Message>> queryWebhookClient() {
|
||||
return discordSRV.discordAPI().queryWebhookClient(getId());
|
||||
}
|
||||
|
||||
@ -76,51 +73,55 @@ public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageC
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ReceivedDiscordMessage> sendMessage(
|
||||
@NotNull SendableDiscordMessage message, @NotNull Map<String, InputStream> attachments
|
||||
) {
|
||||
return message(message, (webhookClient, webhookMessage) -> {
|
||||
for (Map.Entry<String, InputStream> entry : attachments.entrySet()) {
|
||||
webhookMessage.addFile(entry.getKey(), entry.getValue());
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> sendMessage(@NotNull SendableDiscordMessage message) {
|
||||
return sendInternal(message);
|
||||
}
|
||||
return webhookClient.send(webhookMessage.build());
|
||||
}, (channel, msg) -> {
|
||||
MessageCreateAction action = channel.sendMessage(SendableDiscordMessageUtil.toJDASend(msg));
|
||||
for (Map.Entry<String, InputStream> entry : attachments.entrySet()) {
|
||||
action = action.addFiles(FileUpload.fromData(entry.getValue(), entry.getKey()));
|
||||
|
||||
@SuppressWarnings("unchecked") // Generics
|
||||
private <R extends MessageCreateRequest<? extends MessageCreateRequest<?>> & RestAction<Message>> CompletableFuture<ReceivedDiscordMessage> sendInternal(SendableDiscordMessage message) {
|
||||
MessageCreateData createData = SendableDiscordMessageUtil.toJDASend(message);
|
||||
|
||||
CompletableFuture<R> createRequest;
|
||||
if (message.isWebhookMessage()) {
|
||||
createRequest = queryWebhookClient()
|
||||
.thenApply(client -> (R) client.sendMessage(createData)
|
||||
.setUsername(message.getWebhookUsername())
|
||||
.setAvatarUrl(message.getWebhookAvatarUrl())
|
||||
);
|
||||
} else {
|
||||
createRequest = CompletableFuture.completedFuture(((R) channel.sendMessage(createData)));
|
||||
}
|
||||
return action;
|
||||
});
|
||||
|
||||
return createRequest
|
||||
.thenCompose(RestAction::submit)
|
||||
.thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, @NotNull SendableDiscordMessage message) {
|
||||
return message(
|
||||
message,
|
||||
(client, msg) -> client.edit(id, msg.build()),
|
||||
(textChannel, msg) -> textChannel.editMessageById(id, SendableDiscordMessageUtil.toJDAEdit(msg))
|
||||
);
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(
|
||||
long id,
|
||||
@NotNull SendableDiscordMessage message
|
||||
) {
|
||||
return editInternal(id, message);
|
||||
}
|
||||
|
||||
private CompletableFuture<ReceivedDiscordMessage> message(
|
||||
SendableDiscordMessage message,
|
||||
BiFunction<WebhookClient, WebhookMessageBuilder, CompletableFuture<ReadonlyMessage>> webhookFunction,
|
||||
BiFunction<T, SendableDiscordMessage, FluentRestAction<? extends Message, ?>> jdaFunction) {
|
||||
return discordSRV.discordAPI().mapExceptions(() -> {
|
||||
CompletableFuture<ReceivedDiscordMessage> future;
|
||||
@SuppressWarnings("unchecked") // Generics
|
||||
private <R extends MessageEditRequest<? extends MessageEditRequest<?>> & RestAction<Message>> CompletableFuture<ReceivedDiscordMessage> editInternal(
|
||||
long id,
|
||||
SendableDiscordMessage message
|
||||
) {
|
||||
MessageEditData editData = SendableDiscordMessageUtil.toJDAEdit(message);
|
||||
|
||||
CompletableFuture<R> editRequest;
|
||||
if (message.isWebhookMessage()) {
|
||||
future = queryWebhookClient()
|
||||
.thenCompose(client -> webhookFunction.apply(
|
||||
client, SendableDiscordMessageUtil.toWebhook(message)))
|
||||
.thenApply(msg -> ReceivedDiscordMessageImpl.fromWebhook(discordSRV, msg));
|
||||
editRequest = queryWebhookClient().thenApply(client -> (R) client.editMessageById(id, editData));
|
||||
} else {
|
||||
future = jdaFunction
|
||||
.apply(channel, message)
|
||||
.submit()
|
||||
.thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg));
|
||||
editRequest = CompletableFuture.completedFuture(((R) channel.editMessageById(id, editData)));
|
||||
}
|
||||
return future;
|
||||
});
|
||||
|
||||
return editRequest
|
||||
.thenCompose(RestAction::submit)
|
||||
.thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -131,7 +132,7 @@ public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageC
|
||||
} else {
|
||||
future = discordSRV.discordAPI()
|
||||
.queryWebhookClient(channel.getIdLong())
|
||||
.thenCompose(client -> client.delete(id));
|
||||
.thenCompose(client -> client.deleteMessageById(id).submit());
|
||||
}
|
||||
return discordSRV.discordAPI().mapExceptions(future);
|
||||
}
|
||||
|
@ -28,11 +28,8 @@ import com.discordsrv.common.discord.api.entity.message.util.SendableDiscordMess
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
|
||||
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
|
||||
import net.dv8tion.jda.api.utils.FileUpload;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@ -52,18 +49,12 @@ public class DiscordDMChannelImpl extends AbstractDiscordMessageChannel<PrivateC
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ReceivedDiscordMessage> sendMessage(
|
||||
@NotNull SendableDiscordMessage message,
|
||||
@NotNull Map<String, InputStream> attachments
|
||||
) {
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> sendMessage(@NotNull SendableDiscordMessage message) {
|
||||
if (message.isWebhookMessage()) {
|
||||
throw new IllegalArgumentException("Cannot send webhook messages to DMChannels");
|
||||
}
|
||||
|
||||
MessageCreateAction action = channel.sendMessage(SendableDiscordMessageUtil.toJDASend(message));
|
||||
for (Map.Entry<String, InputStream> entry : attachments.entrySet()) {
|
||||
action = action.addFiles(FileUpload.fromData(entry.getValue(), entry.getKey()));
|
||||
}
|
||||
|
||||
CompletableFuture<ReceivedDiscordMessage> future = action.submit()
|
||||
.thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg));
|
||||
@ -80,7 +71,10 @@ public class DiscordDMChannelImpl extends AbstractDiscordMessageChannel<PrivateC
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, @NotNull SendableDiscordMessage message) {
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(
|
||||
long id,
|
||||
@NotNull SendableDiscordMessage message
|
||||
) {
|
||||
if (message.isWebhookMessage()) {
|
||||
throw new IllegalArgumentException("Cannot send webhook messages to DMChannels");
|
||||
}
|
||||
|
@ -0,0 +1,119 @@
|
||||
package com.discordsrv.common.discord.api.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordChannelType;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordForumChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
|
||||
import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction;
|
||||
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DiscordForumChannelImpl implements DiscordForumChannel {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final ForumChannel channel;
|
||||
private final DiscordGuild guild;
|
||||
|
||||
public DiscordForumChannelImpl(DiscordSRV discordSRV, ForumChannel channel) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.channel = channel;
|
||||
this.guild = discordSRV.discordAPI().getGuild(channel.getGuild());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForumChannel asJDA() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return channel.getIdLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return channel.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DiscordGuild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getJumpUrl() {
|
||||
return channel.getJumpUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<DiscordThreadChannel> getActiveThreads() {
|
||||
List<ThreadChannel> threads = channel.getThreadChannels();
|
||||
List<DiscordThreadChannel> threadChannels = new ArrayList<>(threads.size());
|
||||
for (ThreadChannel thread : threads) {
|
||||
threadChannels.add(discordSRV.discordAPI().getThreadChannel(thread));
|
||||
}
|
||||
return threadChannels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<DiscordThreadChannel>> retrieveArchivedPrivateThreads() {
|
||||
return threads(IThreadContainer::retrieveArchivedPrivateThreadChannels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<DiscordThreadChannel>> retrieveArchivedJoinedPrivateThreads() {
|
||||
return threads(IThreadContainer::retrieveArchivedPrivateJoinedThreadChannels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<DiscordThreadChannel>> retrieveArchivedPublicThreads() {
|
||||
return threads(IThreadContainer::retrieveArchivedPublicThreadChannels);
|
||||
}
|
||||
|
||||
@SuppressWarnings("CodeBlock2Expr")
|
||||
private CompletableFuture<List<DiscordThreadChannel>> threads(
|
||||
Function<IThreadContainer, ThreadChannelPaginationAction> action) {
|
||||
return discordSRV.discordAPI().mapExceptions(() -> {
|
||||
return action.apply(channel)
|
||||
.submit()
|
||||
.thenApply(channels -> channels.stream()
|
||||
.map(channel -> discordSRV.discordAPI().getThreadChannel(channel))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DiscordThreadChannel> createThread(String name, boolean privateThread) {
|
||||
return thread(channel -> channel.createThreadChannel(name, privateThread));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DiscordThreadChannel> createThread(String name, long messageId) {
|
||||
return thread(channel -> channel.createThreadChannel(name, messageId));
|
||||
}
|
||||
|
||||
@SuppressWarnings("CodeBlock2Expr")
|
||||
private CompletableFuture<DiscordThreadChannel> thread(Function<ForumChannel, ThreadChannelAction> action) {
|
||||
return discordSRV.discordAPI().mapExceptions(() -> {
|
||||
return action.apply(channel)
|
||||
.submit()
|
||||
.thenApply(channel -> discordSRV.discordAPI().getThreadChannel(channel));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordChannelType getType() {
|
||||
return DiscordChannelType.FORUM;
|
||||
}
|
||||
}
|
@ -18,16 +18,17 @@
|
||||
|
||||
package com.discordsrv.common.discord.api.entity.channel;
|
||||
|
||||
import club.minnced.discord.webhook.WebhookClient;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordChannelType;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordThreadContainer;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.WebhookClient;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.internal.requests.IncomingWebhookClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@ -41,22 +42,15 @@ public class DiscordThreadChannelImpl extends AbstractDiscordGuildMessageChannel
|
||||
super(discordSRV, thread);
|
||||
|
||||
IThreadContainer container = thread.getParentChannel();
|
||||
this.threadContainer = container instanceof MessageChannel
|
||||
? (DiscordThreadContainer) discordSRV.discordAPI().getMessageChannel((MessageChannel) container)
|
||||
: null;
|
||||
this.threadContainer = (DiscordThreadContainer) discordSRV.discordAPI().getChannel(container);
|
||||
this.guild = discordSRV.discordAPI().getGuild(thread.getGuild());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WebhookClient> queryWebhookClient() {
|
||||
public CompletableFuture<WebhookClient<Message>> queryWebhookClient() {
|
||||
return discordSRV.discordAPI()
|
||||
.queryWebhookClient(getParentChannel().getId())
|
||||
.thenApply(client -> client.onThread(getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getName() {
|
||||
return channel.getName();
|
||||
.thenApply(client -> ((IncomingWebhookClient) client).withThreadId(Long.toUnsignedString(getId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -56,6 +56,11 @@ public class DiscordGuildImpl implements DiscordGuild {
|
||||
return guild.getMemberCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull DiscordGuildMember getSelfMember() {
|
||||
return discordSRV.discordAPI().getGuildMember(guild.getSelfMember());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<DiscordGuildMember> retrieveMemberById(long id) {
|
||||
return discordSRV.discordAPI().mapExceptions(() -> guild.retrieveMemberById(id)
|
||||
|
@ -87,6 +87,11 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember {
|
||||
return roles.stream().anyMatch(role::equals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInteract(@NotNull DiscordRole role) {
|
||||
return member.canInteract(role.asJDA());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> addRole(@NotNull DiscordRole role) {
|
||||
return discordSRV.discordAPI().mapExceptions(() ->
|
||||
|
@ -18,12 +18,6 @@
|
||||
|
||||
package com.discordsrv.common.discord.api.entity.message;
|
||||
|
||||
import club.minnced.discord.webhook.WebhookClient;
|
||||
import club.minnced.discord.webhook.receive.ReadonlyAttachment;
|
||||
import club.minnced.discord.webhook.receive.ReadonlyEmbed;
|
||||
import club.minnced.discord.webhook.receive.ReadonlyMessage;
|
||||
import club.minnced.discord.webhook.send.WebhookEmbed;
|
||||
import com.discordsrv.api.color.Color;
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordDMChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
@ -44,6 +38,7 @@ import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.entities.WebhookClient;
|
||||
import net.dv8tion.jda.api.requests.ErrorResponse;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -71,7 +66,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
||||
|
||||
boolean self = false;
|
||||
if (webhookMessage) {
|
||||
CompletableFuture<WebhookClient> clientFuture = discordSRV.discordAPI()
|
||||
CompletableFuture<WebhookClient<Message>> clientFuture = discordSRV.discordAPI()
|
||||
.getCachedClients()
|
||||
.getIfPresent(channel instanceof DiscordThreadChannel
|
||||
? ((DiscordThreadChannel) channel).getParentChannel().getId()
|
||||
@ -79,7 +74,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
||||
);
|
||||
|
||||
if (clientFuture != null) {
|
||||
long clientId = clientFuture.join().getId();
|
||||
long clientId = clientFuture.join().getIdLong();
|
||||
self = clientId == user.getId();
|
||||
}
|
||||
} else {
|
||||
@ -113,71 +108,6 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
||||
);
|
||||
}
|
||||
|
||||
public static ReceivedDiscordMessage fromWebhook(DiscordSRV discordSRV, ReadonlyMessage webhookMessage) {
|
||||
List<DiscordMessageEmbed> mappedEmbeds = new ArrayList<>();
|
||||
for (ReadonlyEmbed embed : webhookMessage.getEmbeds()) {
|
||||
List<DiscordMessageEmbed.Field> fields = new ArrayList<>();
|
||||
for (WebhookEmbed.EmbedField field : embed.getFields()) {
|
||||
fields.add(new DiscordMessageEmbed.Field(field.getName(), field.getValue(), field.isInline()));
|
||||
}
|
||||
|
||||
Integer color = embed.getColor();
|
||||
WebhookEmbed.EmbedAuthor author = embed.getAuthor();
|
||||
WebhookEmbed.EmbedTitle title = embed.getTitle();
|
||||
ReadonlyEmbed.EmbedImage thumbnail = embed.getThumbnail();
|
||||
ReadonlyEmbed.EmbedImage image = embed.getImage();
|
||||
WebhookEmbed.EmbedFooter footer = embed.getFooter();
|
||||
|
||||
mappedEmbeds.add(new DiscordMessageEmbed(
|
||||
color != null ? new Color(color) : null,
|
||||
author != null ? author.getName() : null,
|
||||
author != null ? author.getUrl() : null,
|
||||
author != null ? author.getIconUrl() : null,
|
||||
title != null ? title.getText() : null,
|
||||
title != null ? title.getUrl() : null,
|
||||
embed.getDescription(),
|
||||
fields,
|
||||
thumbnail != null ? thumbnail.getUrl() : null,
|
||||
image != null ? image.getUrl() : null,
|
||||
embed.getTimestamp(),
|
||||
footer != null ? footer.getText() : null,
|
||||
footer != null ? footer.getIconUrl() : null
|
||||
));
|
||||
}
|
||||
|
||||
DiscordMessageChannel channel = discordSRV.discordAPI().getMessageChannelById(
|
||||
webhookMessage.getChannelId());
|
||||
DiscordUser user = discordSRV.discordAPI().getUserById(
|
||||
webhookMessage.getAuthor().getId());
|
||||
DiscordGuildMember member = channel instanceof DiscordTextChannel && user != null
|
||||
? ((DiscordTextChannel) channel).getGuild().getMemberById(user.getId()) : null;
|
||||
|
||||
List<Attachment> attachments = new ArrayList<>();
|
||||
for (ReadonlyAttachment attachment : webhookMessage.getAttachments()) {
|
||||
attachments.add(new Attachment(
|
||||
attachment.getFileName(),
|
||||
attachment.getUrl(),
|
||||
attachment.getProxyUrl(),
|
||||
attachment.getSize()
|
||||
));
|
||||
}
|
||||
|
||||
return new ReceivedDiscordMessageImpl(
|
||||
discordSRV,
|
||||
attachments,
|
||||
true, // These are always from rest responses
|
||||
channel,
|
||||
null,
|
||||
member,
|
||||
user,
|
||||
webhookMessage.getChannelId(),
|
||||
webhookMessage.getId(),
|
||||
webhookMessage.getContent(),
|
||||
mappedEmbeds,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final List<Attachment> attachments;
|
||||
private final boolean fromSelf;
|
||||
@ -305,7 +235,9 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> edit(SendableDiscordMessage message) {
|
||||
public @NotNull CompletableFuture<ReceivedDiscordMessage> edit(
|
||||
@NotNull SendableDiscordMessage message
|
||||
) {
|
||||
if (!webhookMessage && message.isWebhookMessage()) {
|
||||
throw new IllegalArgumentException("Cannot edit a non-webhook message into a webhook message");
|
||||
}
|
||||
@ -322,6 +254,29 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
|
||||
// Placeholders
|
||||
//
|
||||
|
||||
@Placeholder("message_reply")
|
||||
public Component _reply(BaseChannelConfig config, @PlaceholderRemainder String suffix) {
|
||||
if (replyingTo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String content = replyingTo.getContent();
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Component component = discordSRV.componentFactory().minecraftSerializer().serialize(content);
|
||||
|
||||
String replyFormat = config.discordToMinecraft.replyFormat;
|
||||
return ComponentUtil.fromAPI(
|
||||
discordSRV.componentFactory().textBuilder(replyFormat)
|
||||
.applyPlaceholderService()
|
||||
.addPlaceholder("message", component)
|
||||
.addContext(replyingTo.getMember(), replyingTo.getAuthor(), replyingTo)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Placeholder("message_attachments")
|
||||
public Component _attachments(BaseChannelConfig config, @PlaceholderRemainder String suffix) {
|
||||
String attachmentFormat = config.discordToMinecraft.attachmentFormat;
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package com.discordsrv.common.discord.api.entity.message.util;
|
||||
|
||||
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.actionrow.MessageActionRow;
|
||||
import com.discordsrv.api.discord.entity.message.AllowedMention;
|
||||
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
|
||||
@ -26,11 +25,14 @@ import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.interactions.components.ActionRow;
|
||||
import net.dv8tion.jda.api.utils.FileUpload;
|
||||
import net.dv8tion.jda.api.utils.messages.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class SendableDiscordMessageUtil {
|
||||
@ -62,12 +64,18 @@ public final class SendableDiscordMessageUtil {
|
||||
embeds.add(embed.toJDA());
|
||||
}
|
||||
|
||||
List<FileUpload> uploads = new ArrayList<>();
|
||||
for (Map.Entry<InputStream, String> attachment : message.getAttachments().entrySet()) {
|
||||
uploads.add(FileUpload.fromData(attachment.getKey(), attachment.getValue()));
|
||||
}
|
||||
|
||||
return (T) builder
|
||||
.setContent(message.getContent())
|
||||
.setEmbeds(embeds)
|
||||
.setAllowedMentions(allowedTypes)
|
||||
.mentionUsers(allowedUsers.stream().mapToLong(l -> l).toArray())
|
||||
.mentionRoles(allowedRoles.stream().mapToLong(l -> l).toArray());
|
||||
.mentionRoles(allowedRoles.stream().mapToLong(l -> l).toArray())
|
||||
.setFiles(uploads);
|
||||
}
|
||||
|
||||
public static MessageCreateData toJDASend(@NotNull SendableDiscordMessage message) {
|
||||
@ -91,10 +99,4 @@ public final class SendableDiscordMessageUtil {
|
||||
.setComponents(actionRows)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static WebhookMessageBuilder toWebhook(@NotNull SendableDiscordMessage message) {
|
||||
return WebhookMessageBuilder.fromJDA(null/*toJDA(message)*/) // TODO: lib update? lib replacement?
|
||||
.setUsername(message.getWebhookUsername())
|
||||
.setAvatarUrl(message.getWebhookAvatarUrl());
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.connection.BotConfig;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.config.documentation.DocumentationURLs;
|
||||
import com.discordsrv.common.config.main.MemberCachingConfig;
|
||||
import com.discordsrv.common.debug.DebugGenerateEvent;
|
||||
import com.discordsrv.common.debug.file.TextDebugFile;
|
||||
@ -310,10 +311,8 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
MemberCachingConfig memberCachingConfig = discordSRV.config().memberCaching;
|
||||
DiscordConnectionDetailsImpl connectionDetails = discordSRV.discordConnectionDetails();
|
||||
|
||||
Set<GatewayIntent> intents = new LinkedHashSet<>();
|
||||
this.intents.clear();
|
||||
this.intents.addAll(connectionDetails.getGatewayIntents());
|
||||
this.intents.forEach(intent -> intents.add(intent.asJDA()));
|
||||
|
||||
Set<CacheFlag> cacheFlags = new LinkedHashSet<>();
|
||||
this.cacheFlags.clear();
|
||||
@ -322,7 +321,7 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
cacheFlags.add(flag.asJDA());
|
||||
DiscordGatewayIntent intent = flag.requiredIntent();
|
||||
if (intent != null) {
|
||||
intents.add(intent.asJDA());
|
||||
this.intents.add(intent);
|
||||
}
|
||||
});
|
||||
|
||||
@ -354,6 +353,9 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
chunkingFilter = ChunkingFilter.NONE;
|
||||
}
|
||||
|
||||
Set<GatewayIntent> intents = new LinkedHashSet<>();
|
||||
this.intents.forEach(intent -> intents.add(intent.asJDA()));
|
||||
|
||||
// Start with everything disabled & enable stuff that we actually need
|
||||
JDABuilder jdaBuilder = JDABuilder.createLight(token, intents);
|
||||
jdaBuilder.enableCache(cacheFlags);
|
||||
@ -562,7 +564,10 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
discordSRV.logger().error("| The token provided in the");
|
||||
discordSRV.logger().error("| " + ConnectionConfig.FILE_NAME + " is invalid");
|
||||
discordSRV.logger().error("|");
|
||||
discordSRV.logger().error("| You can get the token for your bot from:");
|
||||
discordSRV.logger().error("| Haven't created a bot yet? Installing the plugin for the first time?");
|
||||
discordSRV.logger().error("| See " + DocumentationURLs.CREATE_TOKEN);
|
||||
discordSRV.logger().error("|");
|
||||
discordSRV.logger().error("| Already have a bot? You can get the token for your bot from:");
|
||||
discordSRV.logger().error("| https://discord.com/developers/applications");
|
||||
discordSRV.logger().error("| by selecting the application, going to the \"Bot\" tab");
|
||||
discordSRV.logger().error("| and clicking on \"Reset Token\"");
|
||||
@ -638,7 +643,7 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
discordSRV.logger().error("| server requiring 2FA for moderation actions");
|
||||
if (user != null) {
|
||||
discordSRV.logger().error("|");
|
||||
discordSRV.logger().error("| The Discord bot's owner is " + user.getAsTag() + " (" + user.getId() + ")");
|
||||
discordSRV.logger().error("| The Discord bot's owner is " + user.getUsername() + " (" + user.getId() + ")");
|
||||
}
|
||||
discordSRV.logger().error("|");
|
||||
discordSRV.logger().error("| You can view instructions for enabling 2FA here:");
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package com.discordsrv.common.groupsync;
|
||||
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.entity.guild.DiscordRole;
|
||||
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleAddEvent;
|
||||
import com.discordsrv.api.discord.events.member.role.DiscordMemberRoleRemoveEvent;
|
||||
@ -316,7 +315,11 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST);
|
||||
}
|
||||
|
||||
DiscordGuildMember member = role.getGuild().getMemberById(userId);
|
||||
if (!role.getGuild().getSelfMember().canInteract(role)) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_CANNOT_INTERACT);
|
||||
}
|
||||
|
||||
return role.getGuild().retrieveMemberById(userId).thenCompose(member -> {
|
||||
if (member == null) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
|
||||
}
|
||||
@ -395,6 +398,7 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
|
||||
});
|
||||
|
||||
return resultFuture;
|
||||
});
|
||||
}
|
||||
|
||||
// Listeners & methods to indicate something changed
|
||||
@ -636,7 +640,11 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_DOESNT_EXIST);
|
||||
}
|
||||
|
||||
DiscordGuildMember member = role.getGuild().getMemberById(userId);
|
||||
if (!role.getGuild().getSelfMember().canInteract(role)) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.ROLE_CANNOT_INTERACT);
|
||||
}
|
||||
|
||||
return role.getGuild().retrieveMemberById(userId).thenCompose(member -> {
|
||||
if (member == null) {
|
||||
return CompletableFuture.completedFuture(GroupSyncResult.NOT_A_GUILD_MEMBER);
|
||||
}
|
||||
@ -675,5 +683,6 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
|
||||
resultFuture.complete(result);
|
||||
});
|
||||
return resultFuture;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ public enum GroupSyncResult {
|
||||
|
||||
// Errors
|
||||
ROLE_DOESNT_EXIST("Role doesn't exist"),
|
||||
ROLE_CANNOT_INTERACT("Bot doesn't have a role above the synced role (cannot interact)"),
|
||||
NOT_A_GUILD_MEMBER("User is not part of the server the role is in"),
|
||||
PERMISSION_BACKEND_FAIL_CHECK("Failed to check group status, error printed"),
|
||||
UPDATE_FAILED("Failed to modify role/group, error printed"),
|
||||
|
@ -21,6 +21,9 @@ package com.discordsrv.common.logging;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
public interface Logger {
|
||||
|
||||
default void info(String message) {
|
||||
@ -69,4 +72,11 @@ public interface Logger {
|
||||
|
||||
void log(@Nullable String loggerName, @NotNull LogLevel logLevel, @Nullable String message, @Nullable Throwable throwable);
|
||||
|
||||
default String getStackTrace(Throwable throwable) {
|
||||
final StringWriter stringWriter = new StringWriter();
|
||||
final PrintWriter printWriter = new PrintWriter(stringWriter, true);
|
||||
throwable.printStackTrace(printWriter);
|
||||
return stringWriter.getBuffer().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,8 +23,6 @@ import com.discordsrv.common.logging.LogLevel;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.backend.LogFilter;
|
||||
import com.discordsrv.common.logging.backend.LoggingBackend;
|
||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -39,12 +37,18 @@ import java.util.logging.LogRecord;
|
||||
|
||||
public class JavaLoggerImpl implements Logger, LoggingBackend {
|
||||
|
||||
private static final DualHashBidiMap<Level, LogLevel> LEVELS = new DualHashBidiMap<>();
|
||||
private static final Map<Level, LogLevel> LEVELS = new HashMap<>();
|
||||
private static final Map<LogLevel, Level> LEVELS_REVERSE = new HashMap<>();
|
||||
|
||||
private static void put(Level level, LogLevel logLevel) {
|
||||
LEVELS.put(level, logLevel);
|
||||
LEVELS_REVERSE.put(logLevel, level);
|
||||
}
|
||||
|
||||
static {
|
||||
LEVELS.put(Level.INFO, LogLevel.INFO);
|
||||
LEVELS.put(Level.WARNING, LogLevel.WARNING);
|
||||
LEVELS.put(Level.SEVERE, LogLevel.ERROR);
|
||||
put(Level.INFO, LogLevel.INFO);
|
||||
put(Level.WARNING, LogLevel.WARNING);
|
||||
put(Level.SEVERE, LogLevel.ERROR);
|
||||
}
|
||||
|
||||
private final java.util.logging.Logger logger;
|
||||
@ -61,7 +65,7 @@ public class JavaLoggerImpl implements Logger, LoggingBackend {
|
||||
|
||||
@Override
|
||||
public void log(@Nullable String loggerName, @NotNull LogLevel level, @Nullable String message, @Nullable Throwable throwable) {
|
||||
Level logLevel = LEVELS.getKey(level);
|
||||
Level logLevel = LEVELS_REVERSE.get(level);
|
||||
if (logLevel != null) {
|
||||
List<String> contents = new ArrayList<>(2);
|
||||
if (message != null) {
|
||||
@ -69,7 +73,7 @@ public class JavaLoggerImpl implements Logger, LoggingBackend {
|
||||
}
|
||||
if (throwable != null) {
|
||||
// Exceptions aren't always logged correctly by the logger itself
|
||||
contents.add(ExceptionUtils.getStackTrace(throwable));
|
||||
contents.add(getStackTrace(throwable));
|
||||
}
|
||||
logger.log(logLevel, String.join("\n", contents));
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import com.discordsrv.common.logging.LogLevel;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.backend.LogFilter;
|
||||
import com.discordsrv.common.logging.backend.LoggingBackend;
|
||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
@ -41,14 +40,20 @@ import java.util.Map;
|
||||
|
||||
public class Log4JLoggerImpl implements Logger, LoggingBackend {
|
||||
|
||||
private static final DualHashBidiMap<Level, LogLevel> LEVELS = new DualHashBidiMap<>();
|
||||
private static final Map<Level, LogLevel> LEVELS = new HashMap<>();
|
||||
private static final Map<LogLevel, Level> LEVELS_REVERSE = new HashMap<>();
|
||||
|
||||
private static void put(Level level, LogLevel logLevel) {
|
||||
LEVELS.put(level, logLevel);
|
||||
LEVELS_REVERSE.put(logLevel, level);
|
||||
}
|
||||
|
||||
static {
|
||||
LEVELS.put(Level.INFO, LogLevel.INFO);
|
||||
LEVELS.put(Level.WARN, LogLevel.WARNING);
|
||||
LEVELS.put(Level.ERROR, LogLevel.ERROR);
|
||||
LEVELS.put(Level.DEBUG, LogLevel.DEBUG);
|
||||
LEVELS.put(Level.TRACE, LogLevel.TRACE);
|
||||
put(Level.INFO, LogLevel.INFO);
|
||||
put(Level.WARN, LogLevel.WARNING);
|
||||
put(Level.ERROR, LogLevel.ERROR);
|
||||
put(Level.DEBUG, LogLevel.DEBUG);
|
||||
put(Level.TRACE, LogLevel.TRACE);
|
||||
}
|
||||
|
||||
private final org.apache.logging.log4j.Logger logger;
|
||||
@ -65,7 +70,7 @@ public class Log4JLoggerImpl implements Logger, LoggingBackend {
|
||||
|
||||
@Override
|
||||
public void log(@Nullable String loggerName, @NotNull LogLevel level, @Nullable String message, @Nullable Throwable throwable) {
|
||||
Level logLevel = LEVELS.getKey(level);
|
||||
Level logLevel = LEVELS_REVERSE.get(level);
|
||||
logger.log(logLevel, message, throwable);
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@
|
||||
package com.discordsrv.common.logging.impl;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.DebugConfig;
|
||||
import com.discordsrv.common.config.main.MainConfig;
|
||||
import com.discordsrv.common.logging.LogLevel;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
@ -36,6 +38,7 @@ import java.nio.file.StandardOpenOption;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@ -47,6 +50,8 @@ public class DiscordSRVLogger implements Logger {
|
||||
private static final String LOG_LINE_FORMAT = "[%s] [%s] %s";
|
||||
private static final String LOG_FILE_NAME_FORMAT = "%s-%s.log";
|
||||
|
||||
private static final List<String> DISABLE_DEBUG_BY_DEFAULT = Collections.singletonList("Hikari");
|
||||
|
||||
private final Queue<LogEntry> linesToAdd = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
@ -123,13 +128,34 @@ public class DiscordSRVLogger implements Logger {
|
||||
}
|
||||
|
||||
private void doLog(String loggerName, LogLevel logLevel, String message, Throwable throwable) {
|
||||
if (logLevel != LogLevel.DEBUG && logLevel != LogLevel.TRACE) {
|
||||
discordSRV.platformLogger().log(null, logLevel, message, throwable);
|
||||
MainConfig config = discordSRV.config();
|
||||
DebugConfig debugConfig = config != null ? config.debug : null;
|
||||
|
||||
if (logLevel == LogLevel.TRACE || (loggerName != null && logLevel == LogLevel.DEBUG && DISABLE_DEBUG_BY_DEFAULT.contains(loggerName))) {
|
||||
if (loggerName == null
|
||||
|| debugConfig == null
|
||||
|| debugConfig.additionalLevels == null
|
||||
|| !debugConfig.additionalLevels.getOrDefault(loggerName, Collections.emptyList()).contains(logLevel.name())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean debugOrTrace = logLevel == LogLevel.DEBUG || logLevel == LogLevel.TRACE;
|
||||
boolean logToConsole = debugConfig != null && debugConfig.logToConsole;
|
||||
|
||||
if (!debugOrTrace || logToConsole) {
|
||||
String consoleMessage = message;
|
||||
LogLevel consoleLevel = logLevel;
|
||||
if (debugOrTrace) {
|
||||
// Normally DEBUG/TRACE logging isn't enabled, so we convert it to INFO and add the level
|
||||
consoleMessage = "[" + logLevel.name() + "]" + (message != null ? " " + message : "");
|
||||
consoleLevel = LogLevel.INFO;
|
||||
}
|
||||
discordSRV.platformLogger().log(null, consoleLevel, consoleMessage, throwable);
|
||||
}
|
||||
|
||||
// TODO: handle trace & hikari
|
||||
Path debugLog = debugLogs.isEmpty() ? null : debugLogs.get(0);
|
||||
if (debugLog == null || logLevel == LogLevel.TRACE/* || loggerName.equals("Hikari")*/) {
|
||||
if (debugLog == null) {
|
||||
return;
|
||||
}
|
||||
long time = System.currentTimeMillis();
|
||||
|
@ -33,7 +33,7 @@ import com.discordsrv.api.placeholder.util.Placeholders;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.renderer.DiscordSRVMinecraftRenderer;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.main.DiscordIgnoresConfig;
|
||||
import com.discordsrv.common.config.main.generic.DiscordIgnoresConfig;
|
||||
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
@ -120,7 +120,7 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
|
||||
Placeholders message = new Placeholders(event.getMessageContent());
|
||||
chatConfig.contentRegexFilters.forEach(message::replaceAll);
|
||||
|
||||
Component messageComponent = DiscordSRVMinecraftRenderer.getWithContext(event, chatConfig, () ->
|
||||
Component messageComponent = DiscordSRVMinecraftRenderer.getWithContext(event.getGuild(), chatConfig, () ->
|
||||
discordSRV.componentFactory().minecraftSerializer().serialize(message.toString()));
|
||||
|
||||
GameTextBuilder componentBuilder = discordSRV.componentFactory()
|
||||
@ -133,7 +133,7 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
componentBuilder.applyPlaceholderService();
|
||||
|
||||
MinecraftComponent component = componentBuilder.build();
|
||||
MinecraftComponent component = DiscordSRVMinecraftRenderer.getWithContext(event.getGuild(), chatConfig, componentBuilder::build);
|
||||
if (ComponentUtil.isEmpty(component)) {
|
||||
// Empty
|
||||
return;
|
||||
|
@ -32,12 +32,14 @@ import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageDeleteEvent;
|
||||
import com.discordsrv.api.discord.events.message.DiscordMessageUpdateEvent;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.api.event.events.message.forward.game.AbstractGameMessageForwardedEvent;
|
||||
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessingEvent;
|
||||
import com.discordsrv.api.placeholder.provider.SinglePlaceholder;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.DiscordIgnoresConfig;
|
||||
import com.discordsrv.common.config.main.channels.MirroringConfig;
|
||||
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.DiscordIgnoresConfig;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
@ -46,8 +48,8 @@ import net.dv8tion.jda.api.entities.Message;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
@ -58,13 +60,13 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
private final Cache<String, Mirror> mapping;
|
||||
private final Cache<Long, Cache<Long, Sync>> mapping;
|
||||
|
||||
public DiscordMessageMirroringModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV, new NamedLogger(discordSRV, "DISCORD_MIRRORING"));
|
||||
this.mapping = discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.MINUTES)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.expireAfterWrite(60, TimeUnit.MINUTES)
|
||||
.expireAfterAccess(30, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -83,8 +85,9 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
return Arrays.asList(DiscordGatewayIntent.GUILD_MESSAGES, DiscordGatewayIntent.MESSAGE_CONTENT);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // Wacky generics
|
||||
@Subscribe
|
||||
public void onDiscordChatMessageProcessing(DiscordChatMessageProcessingEvent event) {
|
||||
public <CC extends BaseChannelConfig & IChannelConfig> void onDiscordChatMessageProcessing(DiscordChatMessageProcessingEvent event) {
|
||||
if (checkCancellation(event)) {
|
||||
return;
|
||||
}
|
||||
@ -95,16 +98,14 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
}
|
||||
|
||||
ReceivedDiscordMessage message = event.getDiscordMessage();
|
||||
DiscordMessageChannel channel = event.getChannel();
|
||||
|
||||
List<Pair<DiscordGuildMessageChannel, MirroringConfig>> mirrorChannels = new ArrayList<>();
|
||||
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
|
||||
List<CompletableFuture<MirrorOperation>> futures = new ArrayList<>();
|
||||
Map<ReceivedDiscordMessage.Attachment, byte[]> attachments = new LinkedHashMap<>();
|
||||
DiscordMessageEmbed.Builder attachmentEmbed = DiscordMessageEmbed.builder().setDescription("Attachments");
|
||||
|
||||
for (Map.Entry<GameChannel, BaseChannelConfig> entry : channels.entrySet()) {
|
||||
BaseChannelConfig channelConfig = entry.getValue();
|
||||
MirroringConfig config = channelConfig.mirroring;
|
||||
BaseChannelConfig baseChannelConfig = entry.getValue();
|
||||
MirroringConfig config = baseChannelConfig.mirroring;
|
||||
if (!config.enabled) {
|
||||
continue;
|
||||
}
|
||||
@ -114,13 +115,13 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
continue;
|
||||
}
|
||||
|
||||
IChannelConfig iChannelConfig = channelConfig instanceof IChannelConfig ? (IChannelConfig) channelConfig : null;
|
||||
if (iChannelConfig == null) {
|
||||
CC channelConfig = baseChannelConfig instanceof IChannelConfig ? (CC) baseChannelConfig : null;
|
||||
if (channelConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MirroringConfig.AttachmentConfig attachmentConfig = config.attachments;
|
||||
int maxSize = attachmentConfig.maximumSizeKb;
|
||||
int maxSize = attachmentConfig.maximumSizeKb * 1000;
|
||||
boolean embedAttachments = attachmentConfig.embedAttachments;
|
||||
if (maxSize >= 0 || embedAttachments) {
|
||||
for (ReceivedDiscordMessage.Attachment attachment : message.getAttachments()) {
|
||||
@ -128,10 +129,11 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (maxSize == 0 || attachment.sizeBytes() <= (maxSize * 1000)) {
|
||||
if (maxSize == 0 || attachment.sizeBytes() <= maxSize) {
|
||||
Request request = new Request.Builder()
|
||||
.url(attachment.proxyUrl())
|
||||
.url(attachment.url())
|
||||
.get()
|
||||
.addHeader("Accept", "*/*")
|
||||
.build();
|
||||
|
||||
byte[] bytes = null;
|
||||
@ -141,7 +143,7 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
bytes = body.bytes();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
discordSRV.logger().error("Failed to download attachment for mirroring", e);
|
||||
logger().error("Failed to download attachment for mirroring", e);
|
||||
}
|
||||
attachments.put(attachment, bytes);
|
||||
continue;
|
||||
@ -156,28 +158,29 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
}
|
||||
}
|
||||
|
||||
List<Long> channelIds = iChannelConfig.channelIds();
|
||||
if (channelIds != null) {
|
||||
for (Long channelId : channelIds) {
|
||||
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId);
|
||||
if (textChannel != null && textChannel.getId() != channel.getId()) {
|
||||
mirrorChannels.add(Pair.of(textChannel, config));
|
||||
}
|
||||
futures.add(
|
||||
discordSRV.discordAPI().findOrCreateDestinations(channelConfig, true, true)
|
||||
.thenApply(messageChannels -> {
|
||||
List<MirrorTarget> targets = new ArrayList<>();
|
||||
for (DiscordGuildMessageChannel messageChannel : messageChannels) {
|
||||
targets.add(new MirrorTarget(messageChannel, config));
|
||||
}
|
||||
return new MirrorOperation(message, config, targets);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
discordSRV.discordAPI().findOrCreateThreads(channelConfig, iChannelConfig, threadChannel -> {
|
||||
if (threadChannel.getId() != channel.getId()) {
|
||||
mirrorChannels.add(Pair.of(threadChannel, config));
|
||||
}
|
||||
}, futures, false);
|
||||
CompletableFutureUtil.combine(futures).whenComplete((lists, t) -> {
|
||||
for (MirrorOperation operation : lists) {
|
||||
List<CompletableFuture<MirroredMessage>> mirrorFutures = new ArrayList<>();
|
||||
|
||||
for (MirrorTarget target : operation.targets) {
|
||||
DiscordGuildMessageChannel mirrorChannel = target.targetChannel;
|
||||
if (mirrorChannel.getId() == event.getChannel().getId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CompletableFutureUtil.combine(futures).whenComplete((v, t) -> {
|
||||
List<CompletableFuture<Pair<ReceivedDiscordMessage, MirroringConfig>>> messageFutures = new ArrayList<>();
|
||||
for (Pair<DiscordGuildMessageChannel, MirroringConfig> pair : mirrorChannels) {
|
||||
DiscordGuildMessageChannel mirrorChannel = pair.getKey();
|
||||
MirroringConfig config = pair.getValue();
|
||||
MirroringConfig config = target.config;
|
||||
MirroringConfig.AttachmentConfig attachmentConfig = config.attachments;
|
||||
|
||||
SendableDiscordMessage.Builder messageBuilder = convert(event.getDiscordMessage(), mirrorChannel, config);
|
||||
@ -185,85 +188,164 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
messageBuilder.addEmbed(attachmentEmbed.build());
|
||||
}
|
||||
|
||||
int maxSize = attachmentConfig.maximumSizeKb;
|
||||
Map<String, InputStream> currentAttachments;
|
||||
if (!attachments.isEmpty() && maxSize > 0) {
|
||||
currentAttachments = new LinkedHashMap<>();
|
||||
int maxSize = attachmentConfig.maximumSizeKb * 1000;
|
||||
List<InputStream> streams = new ArrayList<>();
|
||||
if (!attachments.isEmpty() && maxSize >= 0) {
|
||||
attachments.forEach((attachment, bytes) -> {
|
||||
if (bytes != null && attachment.sizeBytes() <= maxSize) {
|
||||
currentAttachments.put(attachment.fileName(), new ByteArrayInputStream(bytes));
|
||||
if (bytes != null && (maxSize == 0 || attachment.sizeBytes() <= maxSize)) {
|
||||
InputStream stream = new ByteArrayInputStream(bytes);
|
||||
streams.add(stream);
|
||||
messageBuilder.addAttachment(stream, attachment.fileName());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
currentAttachments = Collections.emptyMap();
|
||||
}
|
||||
|
||||
CompletableFuture<Pair<ReceivedDiscordMessage, MirroringConfig>> future =
|
||||
mirrorChannel.sendMessage(messageBuilder.build(), currentAttachments)
|
||||
.thenApply(msg -> Pair.of(msg, config));
|
||||
if (messageBuilder.isEmpty()) {
|
||||
logger().debug("Nothing to mirror to " + mirrorChannel + ", skipping");
|
||||
for (InputStream stream : streams) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
messageFutures.add(future);
|
||||
CompletableFuture<MirroredMessage> future =
|
||||
mirrorChannel.sendMessage(messageBuilder.build())
|
||||
.thenApply(msg -> new MirroredMessage(msg, config));
|
||||
|
||||
mirrorFutures.add(future);
|
||||
future.exceptionally(t2 -> {
|
||||
discordSRV.logger().error("Failed to mirror message to " + mirrorChannel, t2);
|
||||
logger().error("Failed to mirror message to " + mirrorChannel, t2);
|
||||
for (InputStream stream : streams) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
CompletableFutureUtil.combine(messageFutures).whenComplete((messages, t2) -> {
|
||||
Map<Long, MessageReference> references = new LinkedHashMap<>();
|
||||
for (Pair<ReceivedDiscordMessage, MirroringConfig> pair : messages) {
|
||||
ReceivedDiscordMessage msg = pair.getKey();
|
||||
references.put(msg.getChannel().getId(), getReference(msg, pair.getValue()));
|
||||
CompletableFutureUtil.combine(mirrorFutures).whenComplete((messages, t2) -> {
|
||||
MessageReference reference = getReference(operation.originalMessage, operation.configForOriginalMessage);
|
||||
|
||||
Map<ReceivedDiscordMessage, MessageReference> references = new LinkedHashMap<>();
|
||||
references.put(message, reference);
|
||||
for (MirroredMessage mirroredMessage : messages) {
|
||||
references.put(mirroredMessage.message, getReference(mirroredMessage));
|
||||
}
|
||||
mapping.put(getCacheKey(message), new Mirror(getReference(message, null), references));
|
||||
|
||||
putIntoCache(reference, references);
|
||||
});
|
||||
}
|
||||
}).exceptionally(t -> {
|
||||
logger().error("Failed to mirror message", t);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordMessageUpdate(DiscordMessageUpdateEvent event) {
|
||||
ReceivedDiscordMessage message = event.getMessage();
|
||||
Mirror mirror = mapping.get(getCacheKey(message), k -> null);
|
||||
if (mirror == null) {
|
||||
Cache<Long, Sync> syncs = mapping.getIfPresent(event.getChannel().getId());
|
||||
if (syncs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (MessageReference reference : mirror.mirrors.values()) {
|
||||
ReceivedDiscordMessage message = event.getMessage();
|
||||
Sync sync = syncs.getIfPresent(message.getId());
|
||||
if (sync == null || sync.original == null || !sync.original.isMatching(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (MessageReference reference : sync.mirrors) {
|
||||
DiscordGuildMessageChannel channel = reference.getMessageChannel(discordSRV);
|
||||
if (channel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SendableDiscordMessage sendableMessage = convert(message, channel, reference.config).build();
|
||||
channel.editMessageById(reference.messageId, sendableMessage).whenComplete((v, t) -> {
|
||||
if (t != null) {
|
||||
discordSRV.logger().error("Failed to update mirrored message in " + channel);
|
||||
}
|
||||
channel.editMessageById(reference.messageId, sendableMessage).exceptionally(t -> {
|
||||
logger().error("Failed to update mirrored message in " + channel);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordMessageDelete(DiscordMessageDeleteEvent event) {
|
||||
Mirror mirror = mapping.get(getCacheKey(event.getChannel(), event.getMessageId()), k -> null);
|
||||
if (mirror == null) {
|
||||
Cache<Long, Sync> syncs = mapping.getIfPresent(event.getChannel().getId());
|
||||
if (syncs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (MessageReference reference : mirror.mirrors.values()) {
|
||||
Sync sync = syncs.getIfPresent(event.getMessageId());
|
||||
if (sync == null || sync.original == null || !sync.original.isMatching(event.getChannel())
|
||||
|| sync.original.messageId != event.getMessageId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (MessageReference reference : sync.mirrors) {
|
||||
DiscordMessageChannel channel = reference.getMessageChannel(discordSRV);
|
||||
if (channel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
channel.deleteMessageById(reference.messageId, reference.webhookMessage).whenComplete((v, t) -> {
|
||||
if (t != null) {
|
||||
discordSRV.logger().error("Failed to delete mirrored message in " + channel);
|
||||
}
|
||||
channel.deleteMessageById(reference.messageId, reference.webhookMessage).exceptionally(t -> {
|
||||
logger().error("Failed to delete mirrored message in " + channel);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGameMessageForwarded(AbstractGameMessageForwardedEvent event) {
|
||||
Set<? extends ReceivedDiscordMessage> messages = event.getDiscordMessage().getMessages();
|
||||
|
||||
Map<ReceivedDiscordMessage, MessageReference> references = new LinkedHashMap<>();
|
||||
for (ReceivedDiscordMessage message : messages) {
|
||||
DiscordMessageChannel channel = message.getChannel();
|
||||
|
||||
MirroringConfig config = discordSRV.channelConfig().resolve(channel).values().iterator().next().mirroring; // TODO: add channel to event
|
||||
MessageReference reference = getReference(message, config);
|
||||
references.put(message, reference);
|
||||
}
|
||||
|
||||
putIntoCache(null, references);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue") // Supplier always returns a non-null value
|
||||
@NotNull
|
||||
private Cache<Long, Sync> getCache(long channelId) {
|
||||
return mapping.get(
|
||||
channelId,
|
||||
k -> discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.MINUTES)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private void putIntoCache(@Nullable MessageReference original, Map<ReceivedDiscordMessage, MessageReference> references) {
|
||||
if (original == null && references.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<ReceivedDiscordMessage, MessageReference> entry : references.entrySet()) {
|
||||
ReceivedDiscordMessage message = entry.getKey();
|
||||
MessageReference reference = entry.getValue();
|
||||
|
||||
List<MessageReference> refs = new ArrayList<>();
|
||||
for (MessageReference ref : references.values()) {
|
||||
if (ref == reference) {
|
||||
continue;
|
||||
}
|
||||
refs.add(ref);
|
||||
}
|
||||
|
||||
getCache(message.getChannel().getId()).put(message.getId(), new Sync(original, refs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given received message to a sendable message.
|
||||
*/
|
||||
@ -274,10 +356,7 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
) {
|
||||
DiscordGuildMember member = message.getMember();
|
||||
DiscordUser user = message.getAuthor();
|
||||
String username = discordSRV.placeholderService().replacePlaceholders(
|
||||
config.usernameFormat, "%user_effective_name% [M]",
|
||||
member, user
|
||||
);
|
||||
String username = discordSRV.placeholderService().replacePlaceholders(config.usernameFormat, member, user);
|
||||
if (username.length() > 32) {
|
||||
username = username.substring(0, 32);
|
||||
}
|
||||
@ -288,15 +367,12 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
if (replyMessage != null) {
|
||||
MessageReference matchingReference = null;
|
||||
for (Mirror mirror : mapping.asMap().values()) {
|
||||
if (!mirror.hasMessage(replyMessage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MessageReference ref = mirror.getForChannel(destinationChannel);
|
||||
if (ref != null) {
|
||||
matchingReference = ref;
|
||||
break;
|
||||
Cache<Long, Sync> syncs = mapping.getIfPresent(replyMessage.getChannel().getId());
|
||||
if (syncs != null) {
|
||||
Sync sync = syncs.getIfPresent(replyMessage.getId());
|
||||
if (sync != null) {
|
||||
matchingReference = sync.getForChannel(destinationChannel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,8 +384,13 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
) : replyMessage.getJumpUrl();
|
||||
|
||||
content = discordSRV.placeholderService()
|
||||
.replacePlaceholders(config.replyFormat, replyMessage.getMember(), replyMessage.getAuthor())
|
||||
.replace("%message_jump_url%", jumpUrl) + content;
|
||||
.replacePlaceholders(
|
||||
config.replyFormat,
|
||||
replyMessage.getMember(),
|
||||
replyMessage.getAuthor(),
|
||||
new SinglePlaceholder("message_jump_url", jumpUrl),
|
||||
new SinglePlaceholder("message", content)
|
||||
);
|
||||
}
|
||||
|
||||
SendableDiscordMessage.Builder builder = SendableDiscordMessage.builder()
|
||||
@ -321,13 +402,16 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
? member.getEffectiveServerAvatarUrl()
|
||||
: user.getEffectiveAvatarUrl()
|
||||
);
|
||||
builder.getAllowedMentions().clear();
|
||||
for (DiscordMessageEmbed embed : message.getEmbeds()) {
|
||||
builder.addEmbed(embed);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private MessageReference getReference(MirroredMessage message) {
|
||||
return getReference(message.message, message.config);
|
||||
}
|
||||
|
||||
private MessageReference getReference(ReceivedDiscordMessage message, MirroringConfig config) {
|
||||
return getReference(message.getChannel(), message.getId(), message.isWebhookMessage(), config);
|
||||
}
|
||||
@ -348,24 +432,61 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
throw new IllegalStateException("Unexpected channel type: " + channel.getClass().getName());
|
||||
}
|
||||
|
||||
private static String getCacheKey(ReceivedDiscordMessage message) {
|
||||
return getCacheKey(message.getChannel(), message.getId());
|
||||
private static class MirrorOperation {
|
||||
|
||||
private final ReceivedDiscordMessage originalMessage;
|
||||
private final MirroringConfig configForOriginalMessage;
|
||||
private final List<MirrorTarget> targets;
|
||||
|
||||
public MirrorOperation(ReceivedDiscordMessage originalMessage, MirroringConfig configForOriginalMessage, List<MirrorTarget> targets) {
|
||||
this.originalMessage = originalMessage;
|
||||
this.configForOriginalMessage = configForOriginalMessage;
|
||||
this.targets = targets;
|
||||
}
|
||||
|
||||
private static String getCacheKey(DiscordMessageChannel channel, long messageId) {
|
||||
if (channel instanceof DiscordTextChannel) {
|
||||
return getCacheKey(channel.getId(), 0L, messageId);
|
||||
} else if (channel instanceof DiscordThreadChannel) {
|
||||
long parentId = ((DiscordThreadChannel) channel).getParentChannel().getId();
|
||||
return getCacheKey(parentId, channel.getId(), messageId);
|
||||
}
|
||||
throw new IllegalStateException("Unexpected channel type: " + channel.getClass().getName());
|
||||
}
|
||||
|
||||
private static String getCacheKey(long channelId, long threadId, long messageId) {
|
||||
return Long.toUnsignedString(channelId)
|
||||
+ (threadId > 0 ? ":" + Long.toUnsignedString(threadId) : "")
|
||||
+ ":" + Long.toUnsignedString(messageId);
|
||||
private static class MirrorTarget {
|
||||
|
||||
private final DiscordGuildMessageChannel targetChannel;
|
||||
private final MirroringConfig config;
|
||||
|
||||
public MirrorTarget(DiscordGuildMessageChannel targetChannel, MirroringConfig config) {
|
||||
this.targetChannel = targetChannel;
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MirroredMessage {
|
||||
|
||||
private final ReceivedDiscordMessage message;
|
||||
private final MirroringConfig config;
|
||||
|
||||
public MirroredMessage(ReceivedDiscordMessage message, MirroringConfig config) {
|
||||
this.message = message;
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Sync {
|
||||
|
||||
private final MessageReference original;
|
||||
private final List<MessageReference> mirrors;
|
||||
|
||||
public Sync(MessageReference original, List<MessageReference> mirrors) {
|
||||
this.original = original;
|
||||
this.mirrors = mirrors;
|
||||
}
|
||||
|
||||
public MessageReference getForChannel(DiscordGuildMessageChannel channel) {
|
||||
for (MessageReference mirror : mirrors) {
|
||||
if (mirror.isMatching(channel)) {
|
||||
return mirror;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MessageReference {
|
||||
@ -424,44 +545,15 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isMatching(ReceivedDiscordMessage message) {
|
||||
return isMatching((DiscordGuildMessageChannel) message.getChannel())
|
||||
&& message.getId() == messageId;
|
||||
}
|
||||
|
||||
public boolean isMatching(DiscordGuildMessageChannel channel) {
|
||||
public boolean isMatching(DiscordMessageChannel channel) {
|
||||
return channel instanceof DiscordThreadChannel
|
||||
? channel.getId() == threadId
|
||||
&& ((DiscordThreadChannel) channel).getParentChannel().getId() == channelId
|
||||
: channel.getId() == channelId;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Mirror {
|
||||
|
||||
private final MessageReference original;
|
||||
private final Map<Long, MessageReference> mirrors; // thread/channel id -> reference
|
||||
|
||||
public Mirror(MessageReference original, Map<Long, MessageReference> mirrors) {
|
||||
this.original = original;
|
||||
this.mirrors = mirrors;
|
||||
}
|
||||
|
||||
public boolean hasMessage(ReceivedDiscordMessage message) {
|
||||
if (original.isMatching(message)) {
|
||||
return true;
|
||||
}
|
||||
MessageReference reference = mirrors.get(message.getChannel().getId());
|
||||
return reference != null && reference.isMatching(message);
|
||||
}
|
||||
|
||||
public MessageReference getForChannel(DiscordGuildMessageChannel channel) {
|
||||
long id = channel.getId();
|
||||
if (original.isMatching(channel)) {
|
||||
return original;
|
||||
} else {
|
||||
return mirrors.get(id);
|
||||
}
|
||||
public boolean isMatching(ReceivedDiscordMessage message) {
|
||||
return isMatching(message.getChannel()) && messageId == message.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,18 +20,16 @@ package com.discordsrv.common.messageforwarding.game;
|
||||
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
import com.discordsrv.api.discord.connection.jda.errorresponse.ErrorCallbackContext;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
|
||||
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessageCluster;
|
||||
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.api.event.events.message.receive.game.AbstractGameMessageReceiveEvent;
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.channels.IMessageConfig;
|
||||
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.IMessageConfig;
|
||||
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageClusterImpl;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
@ -44,7 +42,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public abstract class AbstractGameMessageModule<T extends IMessageConfig, E extends AbstractGameMessageReceiveEvent> extends AbstractModule<DiscordSRV> {
|
||||
|
||||
@ -92,7 +89,8 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig, E exte
|
||||
return forwardToChannel(event, srvPlayer, channelConfig);
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> forwardToChannel(
|
||||
@SuppressWarnings("unchecked") // Wacky generis
|
||||
private <CC extends BaseChannelConfig & IChannelConfig> CompletableFuture<Void> forwardToChannel(
|
||||
@Nullable E event,
|
||||
@Nullable IPlayer player,
|
||||
@NotNull BaseChannelConfig config
|
||||
@ -102,37 +100,18 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig, E exte
|
||||
return null;
|
||||
}
|
||||
|
||||
IChannelConfig channelConfig = config instanceof IChannelConfig ? (IChannelConfig) config : null;
|
||||
CC channelConfig = config instanceof IChannelConfig ? (CC) config : null;
|
||||
if (channelConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<DiscordMessageChannel> messageChannels = new CopyOnWriteArrayList<>();
|
||||
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
|
||||
|
||||
List<Long> channelIds = channelConfig.channelIds();
|
||||
if (channelIds != null) {
|
||||
for (Long channelId : channelConfig.channelIds()) {
|
||||
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId);
|
||||
if (textChannel != null) {
|
||||
messageChannels.add(textChannel);
|
||||
} else if (channelId > 0) {
|
||||
discordSRV.logger().error("Unable to find channel with ID "
|
||||
+ Long.toUnsignedString(channelId)
|
||||
+ ", unable to forward message to Discord");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
discordSRV.discordAPI().findOrCreateThreads(config, channelConfig, messageChannels::add, futures, true);
|
||||
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenCompose((v) -> {
|
||||
return discordSRV.discordAPI().findOrCreateDestinations(channelConfig, true, true).thenCompose(messageChannels -> {
|
||||
SendableDiscordMessage.Builder format = moduleConfig.format();
|
||||
if (format == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
Map<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> messageFutures;
|
||||
Map<CompletableFuture<ReceivedDiscordMessage>, DiscordGuildMessageChannel> messageFutures;
|
||||
messageFutures = sendMessageToChannels(
|
||||
moduleConfig, player, format, messageChannels, event,
|
||||
// Context
|
||||
@ -142,7 +121,7 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig, E exte
|
||||
return CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0]))
|
||||
.whenComplete((vo, t2) -> {
|
||||
Set<ReceivedDiscordMessage> messages = new LinkedHashSet<>();
|
||||
for (Map.Entry<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> entry : messageFutures.entrySet()) {
|
||||
for (Map.Entry<CompletableFuture<ReceivedDiscordMessage>, DiscordGuildMessageChannel> entry : messageFutures.entrySet()) {
|
||||
CompletableFuture<ReceivedDiscordMessage> future = entry.getKey();
|
||||
if (future.isCompletedExceptionally()) {
|
||||
future.exceptionally(t -> {
|
||||
@ -184,11 +163,11 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig, E exte
|
||||
return discordSRV.componentFactory().discordSerializer().serialize(component);
|
||||
}
|
||||
|
||||
public Map<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> sendMessageToChannels(
|
||||
public Map<CompletableFuture<ReceivedDiscordMessage>, DiscordGuildMessageChannel> sendMessageToChannels(
|
||||
T config,
|
||||
IPlayer player,
|
||||
SendableDiscordMessage.Builder format,
|
||||
List<DiscordMessageChannel> channels,
|
||||
List<DiscordGuildMessageChannel> channels,
|
||||
E event,
|
||||
Object... context
|
||||
) {
|
||||
@ -201,8 +180,8 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig, E exte
|
||||
SendableDiscordMessage discordMessage = formatter
|
||||
.build();
|
||||
|
||||
Map<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> futures = new LinkedHashMap<>();
|
||||
for (DiscordMessageChannel channel : channels) {
|
||||
Map<CompletableFuture<ReceivedDiscordMessage>, DiscordGuildMessageChannel> futures = new LinkedHashMap<>();
|
||||
for (DiscordGuildMessageChannel channel : channels) {
|
||||
futures.put(channel.sendMessage(discordMessage), channel);
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import com.discordsrv.api.event.events.message.receive.game.AwardMessageReceiveE
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.main.channels.AwardMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.server.AwardMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.server.ServerBaseChannelConfig;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
@ -68,7 +68,7 @@ public class AwardMessageModule extends AbstractGameMessageModule<AwardMessageCo
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkIfShouldPermit(event.getPlayer())) {
|
||||
if (checkIfShouldPermit(event.getPlayer())) {
|
||||
process(event, event.getPlayer(), event.getGameChannel());
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import com.discordsrv.api.event.events.message.receive.game.DeathMessageReceiveE
|
||||
import com.discordsrv.api.placeholder.FormattedText;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.main.channels.DeathMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.server.DeathMessageConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.server.ServerBaseChannelConfig;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user