Package reorganizing, and other minor changes

This commit is contained in:
Vankka 2022-01-13 19:03:23 +02:00
parent 694a6191cd
commit 6c2c0c5134
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
34 changed files with 693 additions and 294 deletions

View File

@ -188,6 +188,9 @@ public interface SendableDiscordMessage {
@NotNull
Builder setWebhookAvatarUrl(String webhookAvatarUrl);
@NotNull
Builder convertToNonWebhook();
/**
* Builds a {@link SendableDiscordMessage} from this builder.
* @return the new {@link SendableDiscordMessage}
@ -252,6 +255,9 @@ public interface SendableDiscordMessage {
@NotNull
Formatter applyPlaceholderService();
@NotNull
Formatter convertToNonWebhook();
@NotNull
SendableDiscordMessage build();
}

View File

@ -30,7 +30,7 @@ public final class DiscordFormattingUtil {
private DiscordFormattingUtil() {}
public static String escapeContent(String content) {
content = escapeChars(content, '*', '_', '|', '`', '~');
content = escapeChars(content, '*', '_', '|', '`', '~', ':');
content = escapeQuote(content);
content = escapeMentions(content);
return content;

View File

@ -0,0 +1,41 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.discordsrv.api.discord.events;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
public class DiscordMessageReceiveEvent extends AbstractDiscordMessageEvent {
private final ReceivedDiscordMessage message;
public DiscordMessageReceiveEvent(DiscordMessageChannel channel, ReceivedDiscordMessage message) {
super(channel);
this.message = message;
}
public ReceivedDiscordMessage getMessage() {
return message;
}
}

View File

@ -1,3 +1,26 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.discordsrv.api.event.events.message.forward.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;

View File

@ -1,3 +1,26 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.discordsrv.api.event.events.message.receive.game;
import com.discordsrv.api.component.MinecraftComponent;

View File

@ -22,7 +22,6 @@ import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import net.kyori.adventure.platform.bukkit.BukkitComponentSerializer;
import org.bukkit.event.Event;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -43,7 +42,7 @@ public final class PaperComponentUtil {
private PaperComponentUtil() {}
public static <T extends Event> MinecraftComponent getComponent(
public static <T> MinecraftComponent getComponent(
DiscordSRV discordSRV, T source, String methodName, Function<T, String> legacy) {
if (!IS_PAPER_ADVENTURE) {
return getLegacy(legacy.apply(source));

View File

@ -19,6 +19,7 @@
package com.discordsrv.bukkit.player;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.util.PaperComponentUtil;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.player.IPlayer;
@ -29,21 +30,9 @@ import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Method;
@SuppressWarnings("NullableProblems") // BukkitOfflinePlayer nullability
public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
private static final Method DISPLAY_NAME_METHOD; // Paper 1.16+
static {
Method displayNameMethod = null;
try {
displayNameMethod = Player.class.getMethod("displayName");
} catch (Throwable ignored) {}
DISPLAY_NAME_METHOD = displayNameMethod;
}
private final Player player;
private final Audience audience;
@ -83,13 +72,8 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
@SuppressWarnings("deprecation") // Paper
@Override
public @NotNull Component displayName() {
if (DISPLAY_NAME_METHOD != null) {
try {
return ComponentUtil.fromUnrelocated(DISPLAY_NAME_METHOD.invoke(player));
} catch (Throwable ignored) {}
}
// Use the legacy method
return BukkitComponentSerializer.legacy().deserialize(player.getDisplayName());
return ComponentUtil.fromAPI(
PaperComponentUtil.getComponent(discordSRV, player, "displayName", Player::getDisplayName)
);
}
}

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.proxy.config.channels;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;

View File

@ -1,22 +1,53 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.proxy.config.channels.base;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.config.main.channels.base.ThreadConfig;
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.List;
@ConfigSerializable
public class ProxyChannelConfig extends ProxyBaseChannelConfig implements IChannelConfig {
public ProxyChannelConfig() {
initialize();
}
@Setting(CHANNEL_IDS_OPTION_NAME)
@Comment(CHANNEL_IDS_COMMENT)
public List<Long> channelIds = new ArrayList<>();
public List<Long> channelIds = CHANNEL_IDS_VALUE;
@Override
public List<Long> ids() {
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;
}
}

View File

@ -1,8 +1,26 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.proxy.config.manager;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.proxy.config.channels.base.ProxyBaseChannelConfig;
import com.discordsrv.proxy.config.channels.base.ProxyChannelConfig;
@ -15,7 +33,7 @@ public abstract class ProxyConfigManager<T extends MainConfig> extends MainConfi
}
@Override
public ChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new ChannelConfig.Serializer(mapperFactory, ProxyBaseChannelConfig.class, ProxyChannelConfig.class);
public IChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new IChannelConfig.Serializer(mapperFactory, ProxyBaseChannelConfig.class, ProxyChannelConfig.class);
}
}

View File

@ -1 +1,19 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.proxy.config;

View File

@ -1,3 +1,21 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.proxy.modules;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
@ -9,7 +27,7 @@ import com.discordsrv.api.event.events.message.receive.game.ServerSwitchMessageR
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.function.OrDefault;
import com.discordsrv.common.module.modules.message.AbstractGameMessageModule;
import com.discordsrv.common.messageforwarding.game.AbstractGameMessageModule;
import com.discordsrv.proxy.config.channels.ServerSwitchMessageConfig;
import com.discordsrv.proxy.config.channels.base.ProxyBaseChannelConfig;

View File

@ -19,10 +19,11 @@
package com.discordsrv.common.server.config.channels.base;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.config.main.channels.base.ThreadConfig;
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.List;
@ConfigSerializable
@ -32,11 +33,21 @@ public class ServerChannelConfig extends ServerBaseChannelConfig implements ICha
initialize();
}
@Setting(CHANNEL_IDS_OPTION_NAME)
@Comment(CHANNEL_IDS_COMMENT)
public List<Long> channelIds = new ArrayList<>();
public List<Long> channelIds = CHANNEL_IDS_VALUE;
@Override
public List<Long> ids() {
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;
}
}

View File

@ -20,7 +20,7 @@ package com.discordsrv.common.server.config.manager;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.common.server.config.channels.base.ServerBaseChannelConfig;
import com.discordsrv.common.server.config.channels.base.ServerChannelConfig;
@ -33,7 +33,7 @@ public abstract class ServerConfigManager<T extends MainConfig> extends MainConf
}
@Override
public ChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new ChannelConfig.Serializer(mapperFactory, ServerBaseChannelConfig.class, ServerChannelConfig.class);
public IChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new IChannelConfig.Serializer(mapperFactory, ServerBaseChannelConfig.class, ServerChannelConfig.class);
}
}

View File

@ -28,7 +28,7 @@ import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.server.config.channels.DeathMessageConfig;
import com.discordsrv.common.function.OrDefault;
import com.discordsrv.common.module.modules.message.AbstractGameMessageModule;
import com.discordsrv.common.messageforwarding.game.AbstractGameMessageModule;
import com.discordsrv.common.server.config.channels.base.ServerBaseChannelConfig;
public class DeathMessageModule extends AbstractGameMessageModule<DeathMessageConfig> {

View File

@ -22,8 +22,11 @@ import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
import com.discordsrv.api.event.bus.EventBus;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.common.discord.api.DiscordAPIEventModule;
import com.discordsrv.common.api.util.ApiInstanceUtil;
import com.discordsrv.common.channel.ChannelConfigHelper;
import com.discordsrv.common.channel.ChannelUpdaterModule;
import com.discordsrv.common.channel.GlobalChannelLookupModule;
import com.discordsrv.common.component.ComponentFactory;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
@ -35,22 +38,21 @@ import com.discordsrv.common.discord.connection.jda.JDAConnectionManager;
import com.discordsrv.common.discord.details.DiscordConnectionDetailsImpl;
import com.discordsrv.common.event.bus.EventBusImpl;
import com.discordsrv.common.function.CheckedRunnable;
import com.discordsrv.common.integration.LuckPermsIntegration;
import com.discordsrv.common.logging.adapter.DependencyLoggerAdapter;
import com.discordsrv.common.logging.dependency.DependencyLoggingHandler;
import com.discordsrv.common.module.modules.message.JoinMessageModule;
import com.discordsrv.common.module.modules.message.LeaveMessageModule;
import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.module.type.Module;
import com.discordsrv.common.messageforwarding.discord.DiscordChatMessageModule;
import com.discordsrv.common.messageforwarding.discord.DiscordMessageMirroringModule;
import com.discordsrv.common.messageforwarding.game.JoinMessageModule;
import com.discordsrv.common.messageforwarding.game.LeaveMessageModule;
import com.discordsrv.common.messageforwarding.game.MinecraftToDiscordChatModule;
import com.discordsrv.common.module.ModuleInitializationFunction;
import com.discordsrv.common.module.ModuleManager;
import com.discordsrv.common.module.modules.DiscordAPIEventModule;
import com.discordsrv.common.module.modules.message.DiscordToMinecraftChatModule;
import com.discordsrv.common.module.modules.GlobalChannelLookupModule;
import com.discordsrv.common.module.modules.message.MinecraftToDiscordChatModule;
import com.discordsrv.common.module.modules.integration.LuckPermsIntegration;
import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.module.type.Module;
import com.discordsrv.common.placeholder.ComponentResultStringifier;
import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
import com.discordsrv.common.placeholder.context.GlobalTextHandlingContext;
import com.discordsrv.common.logging.adapter.DependencyLoggerAdapter;
import net.dv8tion.jda.api.JDA;
import org.jetbrains.annotations.NotNull;
@ -269,14 +271,19 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
// Register modules
moduleManager = new ModuleManager(this);
for (ModuleInitializationFunction function : new ModuleInitializationFunction[]{
ChannelUpdaterModule::new,
GlobalChannelLookupModule::new,
DiscordAPIEventModule::new,
LuckPermsIntegration::new,
DiscordToMinecraftChatModule::new,
DiscordChatMessageModule::new,
DiscordMessageMirroringModule::new,
JoinMessageModule::new,
LeaveMessageModule::new,
MinecraftToDiscordChatModule::new,
DiscordAPIEventModule::new,
GlobalChannelLookupModule::new
}) {
try {
registerModule(function.initialize(this));

View File

@ -19,7 +19,9 @@
package com.discordsrv.common.channel;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.channel.GameChannelLookupEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
@ -27,9 +29,11 @@ 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.function.OrDefault;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.apache.commons.lang3.tuple.Pair;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -41,7 +45,8 @@ public class ChannelConfigHelper {
private final DiscordSRV discordSRV;
private final LoadingCache<String, GameChannel> nameToChannelCache;
private final Map<Long, Map<String, BaseChannelConfig>> discordToConfigMap;
private final Map<Long, Map<String, BaseChannelConfig>> textChannelToConfigMap;
private final LoadingCache<Pair<Long, String>, Map<String, BaseChannelConfig>> threadToConfigCache;
public ChannelConfigHelper(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
@ -62,7 +67,33 @@ public class ChannelConfigHelper {
return event.getChannelFromProcessing();
}
});
this.discordToConfigMap = new ConcurrentHashMap<>();
this.textChannelToConfigMap = new ConcurrentHashMap<>();
this.threadToConfigCache = discordSRV.caffeineBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.expireAfterAccess(30, TimeUnit.SECONDS)
.refreshAfterWrite(10, TimeUnit.SECONDS)
.build(key -> {
Map<String, BaseChannelConfig> map = new HashMap<>();
for (Map.Entry<String, BaseChannelConfig> entry : channels().entrySet()) {
String channelName = entry.getKey();
BaseChannelConfig value = entry.getValue();
if (value instanceof IChannelConfig) {
IChannelConfig channelConfig = (IChannelConfig) value;
List<ThreadConfig> threads = channelConfig.threads();
if (threads == null) {
continue;
}
for (ThreadConfig thread : threads) {
if (Objects.equals(thread.channelId, key.getKey())
&& Objects.equals(thread.threadName, key.getValue())) {
map.put(channelName, value);
}
}
}
}
return map;
});
discordSRV.eventBus().subscribe(this);
}
@ -79,16 +110,21 @@ public class ChannelConfigHelper {
BaseChannelConfig value = entry.getValue();
if (value instanceof IChannelConfig) {
IChannelConfig channelConfig = (IChannelConfig) value;
for (long channelId : channelConfig.ids()) {
List<Long> channelIds = channelConfig.channelIds();
if (channelIds == null) {
continue;
}
for (long channelId : channelIds) {
newMap.computeIfAbsent(channelId, key -> new LinkedHashMap<>())
.put(channelName, value);
}
}
}
synchronized (discordToConfigMap) {
discordToConfigMap.clear();
discordToConfigMap.putAll(newMap);
synchronized (textChannelToConfigMap) {
textChannelToConfigMap.clear();
textChannelToConfigMap.putAll(newMap);
}
}
@ -149,11 +185,11 @@ public class ChannelConfigHelper {
return gameChannel != null ? get(gameChannel) : null;
}
public Map<GameChannel, OrDefault<BaseChannelConfig>> orDefault(DiscordTextChannel discordTextChannel) {
public Map<GameChannel, OrDefault<BaseChannelConfig>> orDefault(DiscordMessageChannel messageChannel) {
BaseChannelConfig defaultConfig = getDefault();
Map<GameChannel, OrDefault<BaseChannelConfig>> channels = new HashMap<>();
for (Map.Entry<GameChannel, BaseChannelConfig> entry : getDiscordResolved(discordTextChannel).entrySet()) {
for (Map.Entry<GameChannel, BaseChannelConfig> entry : getDiscordResolved(messageChannel).entrySet()) {
channels.put(
entry.getKey(),
new OrDefault<>(entry.getValue(), defaultConfig)
@ -162,9 +198,14 @@ public class ChannelConfigHelper {
return channels;
}
public Map<GameChannel, BaseChannelConfig> getDiscordResolved(DiscordTextChannel channel) {
Map<String, BaseChannelConfig> pairs = getDiscord(channel);
if (pairs == null) {
public Map<GameChannel, BaseChannelConfig> getDiscordResolved(DiscordMessageChannel channel) {
Map<String, BaseChannelConfig> pairs = null;
if (channel instanceof DiscordTextChannel) {
pairs = getText((DiscordTextChannel) channel);
} else if (channel instanceof DiscordThreadChannel) {
pairs = getThread((DiscordThreadChannel) channel);
}
if (pairs == null || pairs.isEmpty()) {
return Collections.emptyMap();
}
@ -181,9 +222,11 @@ public class ChannelConfigHelper {
return channels;
}
public Map<String, BaseChannelConfig> getDiscord(DiscordTextChannel channel) {
synchronized (discordToConfigMap) {
return discordToConfigMap.get(channel.getId());
}
public Map<String, BaseChannelConfig> getText(DiscordTextChannel channel) {
return textChannelToConfigMap.get(channel.getId());
}
public Map<String, BaseChannelConfig> getThread(DiscordThreadChannel channel) {
return threadToConfigCache.get(Pair.of(channel.getParentChannel().getId(), channel.getName()));
}
}

View File

@ -16,13 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.modules;
package com.discordsrv.common.channel;
import com.discordsrv.api.event.bus.EventPriority;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.channel.GameChannelLookupEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.channel.DefaultGlobalChannel;
import com.discordsrv.common.module.type.AbstractModule;
public class GlobalChannelLookupModule extends AbstractModule {

View File

@ -29,7 +29,6 @@ import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
import com.discordsrv.common.function.OrDefault;
import dev.vankka.mcdiscordreserializer.renderer.implementation.DefaultMinecraftRenderer;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.GuildChannel;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.kyori.adventure.text.Component;
@ -62,14 +61,15 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
OrDefault<DiscordToMinecraftChatConfig> config,
Supplier<T> supplier
) {
Context oldValue = CONTEXT.get();
CONTEXT.set(new Context(event, config));
T output = supplier.get();
CONTEXT.remove();
CONTEXT.set(oldValue);
return output;
}
@Override
public @NotNull Component appendChannelMention(@NonNull Component component, @NonNull String id) {
public @NotNull Component appendChannelMention(@NotNull Component component, @NotNull String id) {
Context context = CONTEXT.get();
DiscordToMinecraftChatConfig.Mentions.Format format =
context != null ? context.config.map(cfg -> cfg.mentions).get(cfg -> cfg.channel) : null;
@ -91,7 +91,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
}
@Override
public @NotNull Component appendUserMention(@NonNull Component component, @NonNull String id) {
public @NotNull Component appendUserMention(@NotNull Component component, @NotNull String id) {
Context context = CONTEXT.get();
DiscordToMinecraftChatConfig.Mentions.Format format =
context != null ? context.config.map(cfg -> cfg.mentions).get(cfg -> cfg.user) : null;
@ -124,7 +124,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
}
@Override
public @NotNull Component appendRoleMention(@NonNull Component component, @NonNull String id) {
public @NotNull Component appendRoleMention(@NotNull Component component, @NotNull String id) {
Context context = CONTEXT.get();
DiscordToMinecraftChatConfig.Mentions.Format format =
context != null ? context.config.map(cfg -> cfg.mentions).get(cfg -> cfg.role) : null;

View File

@ -20,11 +20,9 @@ package com.discordsrv.common.config.main.channels.base;
import com.discordsrv.common.config.annotation.Order;
import com.discordsrv.common.config.annotation.Untranslated;
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
import com.discordsrv.common.config.main.channels.JoinMessageConfig;
import com.discordsrv.common.config.main.channels.LeaveMessageConfig;
import com.discordsrv.common.config.main.channels.MinecraftToDiscordChatConfig;
import com.discordsrv.common.config.main.channels.*;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class BaseChannelConfig {
@ -34,7 +32,11 @@ public class BaseChannelConfig {
public JoinMessageConfig joinMessages = new JoinMessageConfig();
public LeaveMessageConfig leaveMessages = new LeaveMessageConfig();
@Order(10)
@Comment("Settings for synchronizing messages between the defined Discord channels and threads")
public MirroringConfig mirroring = new MirroringConfig();
@Untranslated(Untranslated.Type.VALUE)
@Order(5)
@Order(50)
public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay";
}

View File

@ -18,16 +18,10 @@
package com.discordsrv.common.config.main.channels.base;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import org.spongepowered.configurate.objectmapping.meta.Setting;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
@ConfigSerializable
@ -37,47 +31,22 @@ public class ChannelConfig extends BaseChannelConfig implements IChannelConfig {
initialize();
}
@Setting(CHANNEL_IDS_OPTION_NAME)
@Comment(CHANNEL_IDS_COMMENT)
public List<Long> channelIds = new ArrayList<>();
public List<Long> channelIds = CHANNEL_IDS_VALUE;
@Override
public List<Long> ids() {
public List<Long> channelIds() {
return channelIds;
}
public static class Serializer implements TypeSerializer<BaseChannelConfig> {
@Setting(THREADS_OPTION_NAME)
@Comment(THREADS_COMMENT)
public List<ThreadConfig> threads = THREADS_VALUE;
private final ObjectMapper.Factory mapperFactory;
private final Class<?> baseConfigClass;
private final Class<?> configClass;
public Serializer(ObjectMapper.Factory mapperFactory, Class<?> baseConfigClass, Class<?> configClass) {
this.mapperFactory = mapperFactory;
this.baseConfigClass = baseConfigClass;
this.configClass = configClass;
}
@Override
public BaseChannelConfig deserialize(Type type, ConfigurationNode node) throws SerializationException {
return (BaseChannelConfig) mapperFactory.asTypeSerializer()
.deserialize(
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
node
);
}
@Override
public void serialize(Type type, @Nullable BaseChannelConfig obj, ConfigurationNode node) throws SerializationException {
if (obj == null) {
node.set(null);
return;
}
mapperFactory.asTypeSerializer().serialize(
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
obj,
node
);
}
@Override
public List<ThreadConfig> threads() {
return threads;
}
}

View File

@ -18,14 +18,38 @@
package com.discordsrv.common.config.main.channels.base;
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 = "channelIds";
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();
default void initialize() {
// Clear everything besides channelIds by default (these will be filled back in by Configurate if they are in the config itself)
@ -36,7 +60,9 @@ public interface IChannelConfig {
if (!Modifier.isPublic(modifiers) || Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
continue;
}
if (field.getName().equals("channelIds")) {
Setting setting = field.getAnnotation(Setting.class);
if (setting != null && VALUES.contains(setting.value())) {
continue;
}
@ -48,5 +74,39 @@ public interface IChannelConfig {
}
}
List<Long> ids();
class Serializer implements TypeSerializer<BaseChannelConfig> {
private final ObjectMapper.Factory mapperFactory;
private final Class<?> baseConfigClass;
private final Class<?> configClass;
public Serializer(ObjectMapper.Factory mapperFactory, Class<?> baseConfigClass, Class<?> configClass) {
this.mapperFactory = mapperFactory;
this.baseConfigClass = baseConfigClass;
this.configClass = configClass;
}
@Override
public BaseChannelConfig deserialize(Type type, ConfigurationNode node) throws SerializationException {
return (BaseChannelConfig) mapperFactory.asTypeSerializer()
.deserialize(
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
node
);
}
@Override
public void serialize(Type type, @Nullable BaseChannelConfig obj, ConfigurationNode node) throws SerializationException {
if (obj == null) {
node.set(null);
return;
}
mapperFactory.asTypeSerializer().serialize(
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
obj,
node
);
}
}
}

View File

@ -27,6 +27,7 @@ import com.discordsrv.common.config.annotation.Order;
import com.discordsrv.common.config.fielddiscoverer.OrderedFieldDiscovererProxy;
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.manager.loader.ConfigLoaderProvider;
import com.discordsrv.common.config.serializer.ColorSerializer;
import com.discordsrv.common.config.serializer.DiscordMessageEmbedSerializer;
@ -93,8 +94,8 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
protected abstract String fileName();
public ChannelConfig.Serializer getChannelConfigSerializer( ObjectMapper.Factory mapperFactory) {
return new ChannelConfig.Serializer(mapperFactory, BaseChannelConfig.class, ChannelConfig.class);
public IChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new IChannelConfig.Serializer(mapperFactory, BaseChannelConfig.class, ChannelConfig.class);
}
public ConfigurationOptions defaultOptions() {

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.discord.api.message.util;
package com.discordsrv.common.discord.api.entity.message.util;
import club.minnced.discord.webhook.send.WebhookMessage;
import club.minnced.discord.webhook.send.WebhookMessageBuilder;

View File

@ -27,13 +27,13 @@ import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent;
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.discord.api.channel.DiscordDMChannelImpl;
import com.discordsrv.common.discord.api.channel.DiscordTextChannelImpl;
import com.discordsrv.common.discord.api.guild.DiscordGuildImpl;
import com.discordsrv.common.discord.api.guild.DiscordGuildMemberImpl;
import com.discordsrv.common.discord.api.guild.DiscordRoleImpl;
import com.discordsrv.common.discord.api.message.ReceivedDiscordMessageImpl;
import com.discordsrv.common.discord.api.DiscordUserImpl;
import com.discordsrv.common.discord.api.entity.channel.DiscordDMChannelImpl;
import com.discordsrv.common.discord.api.entity.channel.DiscordTextChannelImpl;
import com.discordsrv.common.discord.api.entity.guild.DiscordGuildImpl;
import com.discordsrv.common.discord.api.entity.guild.DiscordGuildMemberImpl;
import com.discordsrv.common.discord.api.entity.guild.DiscordRoleImpl;
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageImpl;
import com.discordsrv.common.discord.api.entity.DiscordUserImpl;
import com.discordsrv.common.discord.connection.DiscordConnectionManager;
import com.discordsrv.common.scheduler.Scheduler;
import com.discordsrv.common.scheduler.threadfactory.CountingThreadFactory;
@ -239,9 +239,9 @@ public class JDAConnectionManager implements DiscordConnectionManager {
jdaBuilder.setMemberCachePolicy(membersIntent ? MemberCachePolicy.ALL : MemberCachePolicy.OWNER);
jdaBuilder.setChunkingFilter(membersIntent ? ChunkingFilter.ALL : ChunkingFilter.NONE);
jdaBuilder.setEventManager(new EventManagerProxy(new JDAEventManager(discordSRV), discordSRV.scheduler().forkExecutor()));
jdaBuilder.setEventManager(new EventManagerProxy(new JDAEventManager(discordSRV), discordSRV.scheduler().forkJoinPool()));
jdaBuilder.setCallbackPool(discordSRV.scheduler().forkExecutor());
jdaBuilder.setCallbackPool(discordSRV.scheduler().forkJoinPool());
jdaBuilder.setGatewayPool(gatewayPool);
jdaBuilder.setRateLimitPool(rateLimitPool);

View File

@ -195,7 +195,7 @@ public class EventBusImpl implements EventBus {
} catch (IllegalAccessException e) {
discordSRV.logger().error("Failed to access API listener method: " + eventListener.methodName(), e);
} catch (InvocationTargetException e) {
discordSRV.logger().error("Failed to pass " + event.getClass().getSimpleName() + " to " + eventListener, e);
discordSRV.logger().error("Failed to pass " + event.getClass().getSimpleName() + " to " + eventListener, e.getCause());
}
long timeTaken = System.currentTimeMillis() - startTime;
discordSRV.logger().trace(eventListener + " took " + timeTaken + "ms to execute");

View File

@ -0,0 +1,25 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.function;
@FunctionalInterface
public interface CheckedFunction<I, O> {
O apply(I input) throws Throwable;
}

View File

@ -0,0 +1,25 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.function;
@FunctionalInterface
public interface CheckedSupplier<O> {
O get() throws Throwable;
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.modules.integration;
package com.discordsrv.common.integration;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.module.type.PermissionDataProvider;

View File

@ -0,0 +1,194 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.messageforwarding.game;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.api.util.DiscordFormattingUtil;
import com.discordsrv.api.event.events.message.receive.game.AbstractGameMessageReceiveEvent;
import com.discordsrv.api.placeholder.FormattedText;
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.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageClusterImpl;
import com.discordsrv.common.function.OrDefault;
import com.discordsrv.common.module.type.AbstractModule;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CopyOnWriteArrayList;
public abstract class AbstractGameMessageModule<T> extends AbstractModule {
public AbstractGameMessageModule(DiscordSRV discordSRV) {
super(discordSRV);
}
public abstract OrDefault<T> mapConfig(OrDefault<BaseChannelConfig> channelConfig);
public abstract boolean isEnabled(OrDefault<T> config);
public abstract SendableDiscordMessage.Builder getFormat(OrDefault<T> config);
public abstract void postClusterToEventBus(ReceivedDiscordMessageCluster cluster);
public final void process(
@NotNull AbstractGameMessageReceiveEvent event,
@NotNull DiscordSRVPlayer player,
@Nullable GameChannel channel
) {
if (channel == null) {
// Send to all channels due to lack of specified channel
for (OrDefault<BaseChannelConfig> channelConfig : discordSRV.channelConfig().getAllChannels()) {
forwardToChannel(event, player, channelConfig);
}
return;
}
OrDefault<BaseChannelConfig> channelConfig = discordSRV.channelConfig().orDefault(channel);
forwardToChannel(event, player, channelConfig);
}
private void forwardToChannel(
@NotNull AbstractGameMessageReceiveEvent event,
@NotNull DiscordSRVPlayer player,
@NotNull OrDefault<BaseChannelConfig> channelConfig
) {
OrDefault<T> config = mapConfig(channelConfig);
if (!isEnabled(config)) {
return;
}
IChannelConfig iChannelConfig = channelConfig.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null);
if (iChannelConfig == null) {
return;
}
List<DiscordMessageChannel> messageChannels = new CopyOnWriteArrayList<>();
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
List<Long> channelIds = iChannelConfig.channelIds();
if (channelIds != null) {
for (Long channelId : iChannelConfig.channelIds()) {
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null);
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(iChannelConfig, messageChannels::add, futures);
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).whenComplete((v, t1) -> {
SendableDiscordMessage.Builder format = getFormat(config);
if (format == null) {
return;
}
String message = convertMessage(config, ComponentUtil.fromAPI(event.getMessage()));
List<CompletableFuture<ReceivedDiscordMessage>> messageFutures;
messageFutures = sendMessageToChannels(
config, format, messageChannels, message,
// Context
channelConfig, player
);
CompletableFuture.allOf(messageFutures.toArray(new CompletableFuture[0]))
.whenComplete((vo, t2) -> {
List<ReceivedDiscordMessage> messages = new ArrayList<>();
for (CompletableFuture<ReceivedDiscordMessage> future : messageFutures) {
if (future.isCompletedExceptionally()) {
future.exceptionally(t -> {
if (t instanceof InsufficientPermissionException) {
discordSRV.logger().error(
"Unable to send message to a Discord channel"
+ " because the bot is lacking the "
+ ((InsufficientPermissionException) t).getPermission().getName()
+ " permission");
} else {
discordSRV.logger().error("Failed to deliver a message to Discord", t);
}
return null;
});
// Ignore ones that failed
continue;
}
// They are all done, so joining will return the result instantly
messages.add(future.join());
}
if (message.isEmpty()) {
// Nothing was delivered
return;
}
postClusterToEventBus(new ReceivedDiscordMessageClusterImpl(messages));
})
.exceptionally(t -> {
if (t instanceof CompletionException) {
return null;
}
discordSRV.logger().error("Failed to publish to event bus", t);
return null;
});
});
}
public String convertMessage(OrDefault<T> config, Component component) {
return DiscordFormattingUtil.escapeContent(
discordSRV.componentFactory().discordSerializer().serialize(component)
);
}
public List<CompletableFuture<ReceivedDiscordMessage>> sendMessageToChannels(
OrDefault<T> config,
SendableDiscordMessage.Builder format,
List<DiscordMessageChannel> channels,
String message,
Object... context
) {
SendableDiscordMessage discordMessage = format.toFormatter()
.addContext(context)
.addReplacement("%message%", new FormattedText(message))
.applyPlaceholderService()
.build();
List<CompletableFuture<ReceivedDiscordMessage>> futures = new ArrayList<>();
for (DiscordMessageChannel channel : channels) {
futures.add(channel.sendMessage(discordMessage));
}
return futures;
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.modules.message;
package com.discordsrv.common.messageforwarding.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.modules.message;
package com.discordsrv.common.messageforwarding.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;

View File

@ -16,10 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.modules.message;
package com.discordsrv.common.messageforwarding.game;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
@ -116,23 +118,30 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
public List<CompletableFuture<ReceivedDiscordMessage>> sendMessageToChannels(
OrDefault<MinecraftToDiscordChatConfig> config,
SendableDiscordMessage.Builder format,
List<Long> channelIds,
List<DiscordMessageChannel> channels,
String message,
Object... context
) {
Map<DiscordGuild, Set<DiscordTextChannel>> channels = new LinkedHashMap<>();
for (Long channelId : channelIds) {
discordSRV.discordAPI().getTextChannelById(channelId)
.ifPresent(textChannel -> channels
.computeIfAbsent(textChannel.getGuild(), key -> new LinkedHashSet<>())
.add(textChannel));
Map<DiscordGuild, Set<DiscordMessageChannel>> channelMap = new LinkedHashMap<>();
for (DiscordMessageChannel channel : channels) {
DiscordGuild guild;
if (channel instanceof DiscordTextChannel) {
guild = ((DiscordTextChannel) channel).getGuild();
} else if (channel instanceof DiscordThreadChannel) {
guild = ((DiscordThreadChannel) channel).getParentChannel().getGuild();
} else {
continue;
}
channelMap.computeIfAbsent(guild, key -> new LinkedHashSet<>())
.add(channel);
}
List<CompletableFuture<ReceivedDiscordMessage>> futures = new ArrayList<>();
OrDefault<MinecraftToDiscordChatConfig.Mentions> mentionConfig = config.map(cfg -> cfg.mentions);
// Format messages per-Guild
for (Map.Entry<DiscordGuild, Set<DiscordTextChannel>> entry : channels.entrySet()) {
for (Map.Entry<DiscordGuild, Set<DiscordMessageChannel>> entry : channelMap.entrySet()) {
Guild guild = entry.getKey().getAsJDAGuild();
Placeholders channelMessagePlaceholders = new Placeholders(message);
@ -152,14 +161,32 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
.sorted(Comparator.comparingInt(mention -> ((CachedMention) mention).searchLength).reversed())
.forEachOrdered(mention -> channelMessagePlaceholders.replaceAll(mention.search, mention.mention));
SendableDiscordMessage discordMessage = format.toFormatter()
SendableDiscordMessage.Formatter discordMessage = format.toFormatter()
.addContext(context)
.addReplacement("%message%", new FormattedText(channelMessagePlaceholders.toString()))
.applyPlaceholderService()
.build();
.applyPlaceholderService();
for (DiscordTextChannel textChannel : entry.getValue()) {
futures.add(textChannel.sendMessage(discordMessage));
List<DiscordMessageChannel> text = new ArrayList<>();
List<DiscordMessageChannel> thread = new ArrayList<>();
for (DiscordMessageChannel channel : entry.getValue()) {
if (channel instanceof DiscordTextChannel) {
text.add(channel);
} else if (channel instanceof DiscordThreadChannel) {
thread.add(channel);
}
}
if (!text.isEmpty()) {
SendableDiscordMessage finalMessage = discordMessage.build();
for (DiscordMessageChannel channel : text) {
futures.add(channel.sendMessage(finalMessage));
}
}
if (!thread.isEmpty()) {
SendableDiscordMessage finalMessage = discordMessage.convertToNonWebhook().build();
for (DiscordMessageChannel channel : thread) {
futures.add(channel.sendMessage(finalMessage));
}
}
}

View File

@ -1,143 +0,0 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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.module.modules.message;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.api.util.DiscordFormattingUtil;
import com.discordsrv.api.event.events.message.receive.game.AbstractGameMessageReceiveEvent;
import com.discordsrv.api.placeholder.FormattedText;
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.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.discord.api.message.ReceivedDiscordMessageClusterImpl;
import com.discordsrv.common.function.OrDefault;
import com.discordsrv.common.module.type.AbstractModule;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractGameMessageModule<T> extends AbstractModule {
public AbstractGameMessageModule(DiscordSRV discordSRV) {
super(discordSRV);
}
public abstract OrDefault<T> mapConfig(OrDefault<BaseChannelConfig> channelConfig);
public abstract boolean isEnabled(OrDefault<T> config);
public abstract SendableDiscordMessage.Builder getFormat(OrDefault<T> config);
public abstract void postClusterToEventBus(ReceivedDiscordMessageCluster cluster);
public final void process(
@NotNull AbstractGameMessageReceiveEvent event,
@NotNull DiscordSRVPlayer player,
@Nullable GameChannel channel
) {
if (channel == null) {
// Send to all channels due to lack of specified channel
for (OrDefault<BaseChannelConfig> channelConfig : discordSRV.channelConfig().getAllChannels()) {
forwardToChannel(event, player, channelConfig);
}
return;
}
OrDefault<BaseChannelConfig> channelConfig = discordSRV.channelConfig().orDefault(channel);
forwardToChannel(event, player, channelConfig);
}
private void forwardToChannel(
@NotNull AbstractGameMessageReceiveEvent event,
@NotNull DiscordSRVPlayer player,
@NotNull OrDefault<BaseChannelConfig> channelConfig
) {
OrDefault<T> config = mapConfig(channelConfig);
if (!isEnabled(config)) {
return;
}
List<Long> channelIds = channelConfig.get(cfg -> cfg instanceof IChannelConfig ? ((IChannelConfig) cfg).ids() : null);
if (channelIds == null || channelIds.isEmpty()) {
return;
}
SendableDiscordMessage.Builder format = getFormat(config);
if (format == null) {
return;
}
String message = convertMessage(config, ComponentUtil.fromAPI(event.getMessage()));
List<CompletableFuture<ReceivedDiscordMessage>> futures = sendMessageToChannels(
config, format, channelIds, message,
// Context
channelConfig, player
);
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((v, t) -> {
if (t != null) {
discordSRV.logger().error("Failed to deliver message to Discord", t);
return;
}
List<ReceivedDiscordMessage> messages = new ArrayList<>();
for (CompletableFuture<ReceivedDiscordMessage> future : futures) {
// They are all done
messages.add(future.join());
}
postClusterToEventBus(new ReceivedDiscordMessageClusterImpl(messages));
});
}
public String convertMessage(OrDefault<T> config, Component component) {
return DiscordFormattingUtil.escapeContent(
discordSRV.componentFactory().discordSerializer().serialize(component)
);
}
public List<CompletableFuture<ReceivedDiscordMessage>> sendMessageToChannels(
OrDefault<T> config,
SendableDiscordMessage.Builder format,
List<Long> channelIds,
String message,
Object... context
) {
SendableDiscordMessage discordMessage = format.toFormatter()
.addContext(context)
.addReplacement("%message%", new FormattedText(message))
.applyPlaceholderService()
.build();
List<CompletableFuture<ReceivedDiscordMessage>> futures = new ArrayList<>();
for (Long channelId : channelIds) {
discordSRV.discordAPI().getTextChannelById(channelId)
.ifPresent(channel -> futures.add(channel.sendMessage(discordMessage)));
}
return futures;
}
}