Join, leave, server switch messages. Fixed dependency logging, added config option ordering annotation, fixed some issues with translation/configuration

This commit is contained in:
Vankka 2021-12-24 01:21:14 +02:00
parent 9ffce74061
commit 0e73f80d40
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
128 changed files with 2523 additions and 501 deletions

View File

@ -19,8 +19,8 @@ jobs:
create_pull_request: true
pull_request_title: "New Crowdin translations"
pull_request_body: ""
source: "source.yml"
translation: "/%original_path%/src/main/resources/translations/%three_letters_code%.yml"
source: "source.yaml"
translation: "/%original_path%/src/main/resources/translations/%three_letters_code%.yaml"
project_id: ${{ secrets.CROWDIN_PROJECT_ID }}
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
env:

View File

@ -27,8 +27,8 @@ jobs:
upload_translations: true
download_translations: false
localization_branch_name: i18n
source: "source.yml"
translation: "/%original_path%/src/main/resources/translations/%three_letters_code%.yml"
source: "source.yaml"
translation: "/%original_path%/src/main/resources/translations/%three_letters_code%.yaml"
project_id: ${{ secrets.CROWDIN_PROJECT_ID }}
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
env:

View File

@ -67,7 +67,7 @@ public class Color {
}
public String hex() {
return Integer.toHexString(rgb);
return Integer.toHexString(0xF000000 | rgb).substring(1);
}
public int red() {

View File

@ -429,7 +429,7 @@ public class DiscordMessageEmbed {
@NotNull
public Builder setFooter(@Nullable CharSequence footer, @Nullable CharSequence footerImageUrl) {
this.footer = footer != null ? footer.toString() : null;
this.footerImageUrl = footerImageUrl != null ? footerImageUrl.toString() : null;
this.footerImageUrl = footerImageUrl != null && footerImageUrl.length() != 0 ? footerImageUrl.toString() : null;
return this;
}

View File

@ -209,7 +209,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
@Override
public @NotNull Formatter applyPlaceholderService() {
DiscordSRVApi api = DiscordSRVApiProvider.optional().orElse(null);
DiscordSRVApi api = DiscordSRVApiProvider.get();
if (api == null) {
throw new IllegalStateException("DiscordSRVApi not available");
}
@ -221,7 +221,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
private Function<Matcher, Object> wrapFunction(Function<Matcher, Object> function) {
return matcher -> {
Object result = function.apply(matcher);
if (result instanceof FormattedText) {
if (result instanceof FormattedText || ResultMappers.isPlainContext()) {
// Process as regular text
return result.toString();
} else if (result instanceof CharSequence) {
@ -258,7 +258,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
DiscordMessageEmbed.Builder embedBuilder = embed.toBuilder();
// TODO: check which parts allow formatting more thoroughly
ResultMappers.runInPlainComponentContext(() -> {
ResultMappers.runInPlainContext(() -> {
embedBuilder.setAuthor(
placeholders.apply(
embedBuilder.getAuthorName()),
@ -307,7 +307,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
builder.addEmbed(embedBuilder.build());
}
ResultMappers.runInPlainComponentContext(() -> {
ResultMappers.runInPlainContext(() -> {
builder.setWebhookUsername(placeholders.apply(builder.getWebhookUsername()));
builder.setWebhookAvatarUrl(placeholders.apply(builder.getWebhookAvatarUrl()));
});

View File

@ -21,7 +21,7 @@
* SOFTWARE.
*/
package com.discordsrv.api.event.events.discord;
package com.discordsrv.api.discord.events;
import com.discordsrv.api.discord.api.entity.channel.DiscordDMChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;

View File

@ -25,6 +25,7 @@ package com.discordsrv.api.event.bus;
import com.discordsrv.api.event.events.Event;
import net.dv8tion.jda.api.events.GenericEvent;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.NotNull;
/**
@ -52,6 +53,7 @@ public interface EventBus {
*
* @param event the event
*/
@Blocking
void publish(@NotNull Event event);
/**
@ -59,6 +61,7 @@ public interface EventBus {
*
* @param event the event
*/
@Blocking
void publish(@NotNull GenericEvent event);
}

View File

@ -28,12 +28,12 @@ import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.Event;
import org.jetbrains.annotations.NotNull;
public class DiscordMessageForwardedEvent implements Event {
public class DiscordChatMessageForwardedEvent implements Event {
private final MinecraftComponent message;
private final GameChannel channel;
public DiscordMessageForwardedEvent(@NotNull MinecraftComponent message, @NotNull GameChannel channel) {
public DiscordChatMessageForwardedEvent(@NotNull MinecraftComponent message, @NotNull GameChannel channel) {
this.message = message;
this.channel = channel;
}

View File

@ -21,4 +21,14 @@
* SOFTWARE.
*/
package com.discordsrv.api.event.events.message.receive;
package com.discordsrv.api.event.events.message.forward.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import org.jetbrains.annotations.NotNull;
public class DeathMessageForwardedEvent extends AbstractGameMessageForwardedEvent {
public DeathMessageForwardedEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) {
super(discordMessage);
}
}

View File

@ -21,4 +21,14 @@
* SOFTWARE.
*/
package com.discordsrv.api.event.events.message;
package com.discordsrv.api.event.events.message.forward.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import org.jetbrains.annotations.NotNull;
public class GameChatMessageForwardedEvent extends AbstractGameMessageForwardedEvent {
public GameChatMessageForwardedEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) {
super(discordMessage);
}
}

View File

@ -26,9 +26,9 @@ package com.discordsrv.api.event.events.message.forward.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import org.jetbrains.annotations.NotNull;
public class ChatMessageForwardedEvent extends AbstractGameMessageForwardedEvent {
public class JoinMessageForwardedEvent extends AbstractGameMessageForwardedEvent {
public ChatMessageForwardedEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) {
public JoinMessageForwardedEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) {
super(discordMessage);
}
}

View File

@ -21,4 +21,14 @@
* SOFTWARE.
*/
package com.discordsrv.api.event.events.message.forward;
package com.discordsrv.api.event.events.message.forward.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import org.jetbrains.annotations.NotNull;
public class LeaveMessageForwardedEvent extends AbstractGameMessageForwardedEvent {
public LeaveMessageForwardedEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) {
super(discordMessage);
}
}

View File

@ -0,0 +1,11 @@
package com.discordsrv.api.event.events.message.forward.game;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import org.jetbrains.annotations.NotNull;
public class ServerSwitchMessageForwardedEvent extends AbstractGameMessageForwardedEvent {
public ServerSwitchMessageForwardedEvent(@NotNull ReceivedDiscordMessageCluster discordMessage) {
super(discordMessage);
}
}

View File

@ -30,7 +30,7 @@ import com.discordsrv.api.event.events.Cancellable;
import com.discordsrv.api.event.events.Processable;
import org.jetbrains.annotations.NotNull;
public class DiscordMessageProcessingEvent implements Cancellable, Processable {
public class DiscordChatMessageProcessingEvent implements Cancellable, Processable {
private final ReceivedDiscordMessage discordMessage;
private String messageContent;
@ -38,7 +38,7 @@ public class DiscordMessageProcessingEvent implements Cancellable, Processable {
private boolean cancelled;
private boolean processed;
public DiscordMessageProcessingEvent(@NotNull ReceivedDiscordMessage discordMessage, @NotNull DiscordTextChannel channel) {
public DiscordChatMessageProcessingEvent(@NotNull ReceivedDiscordMessage discordMessage, @NotNull DiscordTextChannel channel) {
this.discordMessage = discordMessage;
this.messageContent = discordMessage.getContent().orElse(null);
this.channel = channel;
@ -81,6 +81,9 @@ public class DiscordMessageProcessingEvent implements Cancellable, Processable {
@Override
public void markAsProcessed() {
if (isCancelled()) {
throw new IllegalStateException("Cannot process cancelled event");
}
this.processed = true;
}

View File

@ -23,7 +23,6 @@
package com.discordsrv.api.event.events.message.receive.game;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.Cancellable;
import com.discordsrv.api.event.events.Processable;
@ -32,30 +31,22 @@ import org.jetbrains.annotations.NotNull;
public abstract class AbstractGameMessageReceiveEvent implements Processable, Cancellable {
private final MinecraftComponent message;
private GameChannel gameChannel;
private boolean cancelled;
private boolean processed;
public AbstractGameMessageReceiveEvent(@NotNull MinecraftComponent message, @NotNull GameChannel gameChannel, boolean cancelled) {
public AbstractGameMessageReceiveEvent(
@NotNull MinecraftComponent message,
boolean cancelled
) {
this.message = message;
this.gameChannel = gameChannel;
this.cancelled = cancelled;
}
@NotNull
public MinecraftComponent message() {
public MinecraftComponent getMessage() {
return message;
}
@NotNull
public GameChannel getGameChannel() {
return gameChannel;
}
public void setGameChannel(@NotNull GameChannel gameChannel) {
this.gameChannel = gameChannel;
}
@Override
public boolean isCancelled() {
return cancelled;
@ -73,6 +64,9 @@ public abstract class AbstractGameMessageReceiveEvent implements Processable, Ca
@Override
public void markAsProcessed() {
if (isCancelled()) {
throw new IllegalStateException("Cannot process cancelled event");
}
this.processed = true;
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.channel.GameChannel;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.player.DiscordSRVPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class DeathMessageReceiveEvent extends AbstractGameMessageReceiveEvent {
private final DiscordSRVPlayer player;
private GameChannel gameChannel;
public DeathMessageReceiveEvent(
@NotNull DiscordSRVPlayer player,
@Nullable GameChannel gameChannel,
@NotNull MinecraftComponent message,
boolean cancelled) {
super(message, cancelled);
this.player = player;
this.gameChannel = gameChannel;
}
@NotNull
public DiscordSRVPlayer getPlayer() {
return player;
}
@Nullable
public GameChannel getGameChannel() {
return gameChannel;
}
public void setGameChannel(@Nullable GameChannel gameChannel) {
this.gameChannel = gameChannel;
}
}

View File

@ -27,14 +27,29 @@ import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.PlayerEvent;
import com.discordsrv.api.player.DiscordSRVPlayer;
import org.jetbrains.annotations.NotNull;
public class ChatMessageProcessingEvent extends AbstractGameMessageReceiveEvent implements PlayerEvent {
public class GameChatMessageReceiveEvent extends AbstractGameMessageReceiveEvent implements PlayerEvent {
private final DiscordSRVPlayer player;
private GameChannel gameChannel;
public ChatMessageProcessingEvent(DiscordSRVPlayer player, MinecraftComponent message, GameChannel gameChannel, boolean cancelled) {
super(message, gameChannel, cancelled);
public GameChatMessageReceiveEvent(
@NotNull DiscordSRVPlayer player,
@NotNull GameChannel gameChannel,
@NotNull MinecraftComponent message,
boolean cancelled) {
super(message, cancelled);
this.player = player;
this.gameChannel = gameChannel;
}
public GameChannel getGameChannel() {
return gameChannel;
}
public void setGameChannel(GameChannel gameChannel) {
this.gameChannel = gameChannel;
}
@Override

View File

@ -0,0 +1,61 @@
/*
* 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.channel.GameChannel;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.player.DiscordSRVPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JoinMessageReceiveEvent extends AbstractGameMessageReceiveEvent {
private final DiscordSRVPlayer player;
private GameChannel gameChannel;
public JoinMessageReceiveEvent(
@NotNull DiscordSRVPlayer player,
@Nullable GameChannel gameChannel,
@NotNull MinecraftComponent message,
boolean cancelled) {
super(message, cancelled);
this.player = player;
this.gameChannel = gameChannel;
}
@NotNull
public DiscordSRVPlayer getPlayer() {
return player;
}
@Nullable
public GameChannel getGameChannel() {
return gameChannel;
}
public void setGameChannel(@Nullable GameChannel gameChannel) {
this.gameChannel = gameChannel;
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.channel.GameChannel;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.player.DiscordSRVPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class LeaveMessageReceiveEvent extends AbstractGameMessageReceiveEvent {
private final DiscordSRVPlayer player;
private GameChannel gameChannel;
public LeaveMessageReceiveEvent(
@NotNull DiscordSRVPlayer player,
@Nullable GameChannel gameChannel,
@NotNull MinecraftComponent message,
boolean cancelled) {
super(message, cancelled);
this.player = player;
this.gameChannel = gameChannel;
}
@NotNull
public DiscordSRVPlayer getPlayer() {
return player;
}
@Nullable
public GameChannel getGameChannel() {
return gameChannel;
}
public void setGameChannel(@Nullable GameChannel gameChannel) {
this.gameChannel = gameChannel;
}
}

View File

@ -0,0 +1,19 @@
package com.discordsrv.api.event.events.message.receive.game;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.player.DiscordSRVPlayer;
import org.jetbrains.annotations.NotNull;
public class ServerSwitchMessageReceiveEvent extends AbstractGameMessageReceiveEvent {
private final DiscordSRVPlayer player;
public ServerSwitchMessageReceiveEvent(DiscordSRVPlayer player, @NotNull MinecraftComponent message, boolean cancelled) {
super(message, cancelled);
this.player = player;
}
public DiscordSRVPlayer getPlayer() {
return player;
}
}

View File

@ -29,22 +29,21 @@ import java.util.function.Supplier;
public final class ResultMappers {
private static final ThreadLocal<Boolean> PLAIN_COMPONENTS = ThreadLocal.withInitial(() -> false);
private static final ThreadLocal<Boolean> PLAIN = ThreadLocal.withInitial(() -> false);
private ResultMappers() {}
public static boolean isPlainComponentContext() {
return PLAIN_COMPONENTS.get();
public static boolean isPlainContext() {
return PLAIN.get();
}
/**
* Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s
* will replace {@link com.discordsrv.api.component.MinecraftComponent}s
* as plain without formatting (instead of converting to Discord formatting).
* will use plain text without Discord formatting (instead of converting to Discord formatting).
* @param runnable a task that will be executed immediately
*/
public static void runInPlainComponentContext(Runnable runnable) {
getInPlainComponentContext(() -> {
public static void runInPlainContext(Runnable runnable) {
getInPlainContext(() -> {
runnable.run();
return null;
});
@ -52,15 +51,14 @@ public final class ResultMappers {
/**
* Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s
* will replace {@link com.discordsrv.api.component.MinecraftComponent}s
* as plain without formatting (instead of converting to Discord formatting).
* will use plain text without Discord formatting (instead of converting to Discord formatting).
* @param supplier a supplier that will be executed immediately
* @return the output of the supplier provided as parameter
*/
public static <T> T getInPlainComponentContext(Supplier<T> supplier) {
PLAIN_COMPONENTS.set(true);
public static <T> T getInPlainContext(Supplier<T> supplier) {
PLAIN.set(true);
T output = supplier.get();
PLAIN_COMPONENTS.set(false);
PLAIN.set(false);
return output;
}
}

View File

@ -39,7 +39,7 @@ public interface DiscordSRVPlayer {
*/
@Placeholder("player_name")
@NotNull
String getUsername();
String username();
/**
* The {@link UUID} of the player.
@ -47,6 +47,6 @@ public interface DiscordSRVPlayer {
*/
@Placeholder("player_uuid")
@NotNull
UUID getUniqueId();
UUID uniqueId();
}

View File

@ -1,5 +1,5 @@
plugins {
id 'com.github.johnrengelman.shadow' version '7.0.0' apply false
id 'com.github.johnrengelman.shadow' version '7.1.1' apply false
id 'org.cadixdev.licenser' version '0.6.0' apply false
id 'io.freefair.lombok' version '5.3.3.3' apply false
id 'net.kyori.blossom' version '1.2.0' apply false

View File

@ -40,6 +40,9 @@ shadowJar {
// Commons
'org.apache.commons',
// SLF4J
'org.slf4j',
// Checker Framework
'org.checkerframework',
@ -54,10 +57,6 @@ shadowJar {
relocate it, 'com.discordsrv.dependencies.' + it
}
// SLF4J
relocate('org.slf4j', 'com.discordsrv.dependencies.org.slf4j') {
include('*')
exclude('com.discordsrv.logging.impl.SLF4JLoggerImpl')
exclude('com.discordsrv.velocity.DiscordSRVVelocityBootstrap')
}
// SLF4J hack
relocate('com.discordsrv.x.slf4j', 'org.slf4j')
}

View File

@ -25,11 +25,13 @@ import com.discordsrv.bukkit.config.manager.BukkitConfigManager;
import com.discordsrv.bukkit.config.manager.BukkitConnectionConfigManager;
import com.discordsrv.bukkit.console.BukkitConsole;
import com.discordsrv.bukkit.listener.BukkitChatListener;
import com.discordsrv.bukkit.listener.BukkitDeathListener;
import com.discordsrv.bukkit.listener.BukkitStatusMessageListener;
import com.discordsrv.bukkit.player.BukkitPlayerProvider;
import com.discordsrv.bukkit.scheduler.BukkitScheduler;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.logging.Logger;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.server.ServerDiscordSRV;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.Server;
@ -133,5 +135,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
// Register listeners
server().getPluginManager().registerEvents(BukkitChatListener.get(this), plugin());
server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin());
server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin());
}
}

View File

@ -19,8 +19,8 @@
package com.discordsrv.bukkit;
import com.discordsrv.common.dependency.InitialDependencyLoader;
import com.discordsrv.logging.Logger;
import com.discordsrv.logging.impl.JavaLoggerImpl;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.impl.JavaLoggerImpl;
import dev.vankka.mcdependencydownload.bukkit.bootstrap.BukkitBootstrap;
import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader;
import org.bukkit.plugin.java.JavaPlugin;

View File

@ -0,0 +1,82 @@
/*
* 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.bukkit.component.util;
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;
import java.util.function.Function;
public final class PaperComponentUtil {
public static final boolean IS_PAPER_ADVENTURE;
static {
boolean isPaperAdventure = false;
try {
Class.forName("io.papermc.paper.event.player.AsyncChatEvent");
isPaperAdventure = true;
} catch (ClassNotFoundException ignored) {}
IS_PAPER_ADVENTURE = isPaperAdventure;
}
private PaperComponentUtil() {}
public static <T extends Event> MinecraftComponent getComponent(
DiscordSRV discordSRV, T source, String methodName, Function<T, String> legacy) {
if (!IS_PAPER_ADVENTURE) {
return getLegacy(legacy.apply(source));
}
return getComponent(discordSRV, source, methodName);
}
private static MinecraftComponent getLegacy(String legacy) {
return ComponentUtil.toAPI(BukkitComponentSerializer.legacy().deserialize(legacy));
}
public static MinecraftComponent getComponent(DiscordSRV discordSRV, Object source, String methodName) {
if (!IS_PAPER_ADVENTURE) {
return null;
}
Class<?> eventClass = source.getClass();
Object unrelocated;
try {
Method method = eventClass.getMethod(methodName);
unrelocated = method.invoke(source);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Failed to invoke method reflectively", e);
}
MinecraftComponent component = discordSRV.componentFactory().empty();
MinecraftComponent.Adapter adapter = component.unrelocatedAdapter().orElse(null);
if (adapter == null) {
throw new IllegalStateException("Unrelocated adventure unavailable");
}
adapter.setComponent(unrelocated);
return component;
}
}

View File

@ -19,8 +19,17 @@
package com.discordsrv.bukkit.config.main;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
import com.discordsrv.common.server.config.channels.base.ServerBaseChannelConfig;
import com.discordsrv.common.server.config.channels.base.ServerChannelConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class BukkitConfig extends MainConfig {
public BukkitConfig() {
channels.clear();
channels.put("global", new ServerChannelConfig());
channels.put(ChannelConfig.DEFAULT_KEY, new ServerBaseChannelConfig());
}
}

View File

@ -20,9 +20,9 @@ package com.discordsrv.bukkit.config.manager;
import com.discordsrv.bukkit.config.main.BukkitConfig;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.common.server.config.manager.ServerConfigManager;
public class BukkitConfigManager extends MainConfigManager<BukkitConfig> {
public class BukkitConfigManager extends ServerConfigManager<BukkitConfig> {
public BukkitConfigManager(DiscordSRV discordSRV) {
super(discordSRV);

View File

@ -20,9 +20,9 @@ package com.discordsrv.bukkit.console;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.console.Console;
import com.discordsrv.logging.backend.LoggingBackend;
import com.discordsrv.logging.impl.JavaLoggerImpl;
import com.discordsrv.logging.impl.Log4JLoggerImpl;
import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.impl.JavaLoggerImpl;
import com.discordsrv.common.logging.impl.Log4JLoggerImpl;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;

View File

@ -19,8 +19,9 @@
package com.discordsrv.bukkit.listener;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.message.receive.game.ChatMessageProcessingEvent;
import com.discordsrv.api.event.events.message.receive.game.GameChatMessageReceiveEvent;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.util.PaperComponentUtil;
import com.discordsrv.common.channel.DefaultGlobalChannel;
import com.discordsrv.common.component.util.ComponentUtil;
import io.papermc.paper.event.player.AsyncChatEvent;
@ -30,20 +31,13 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public abstract class BukkitChatListener implements Listener {
public static BukkitChatListener get(BukkitDiscordSRV discordSRV) {
// TODO: config option
//noinspection ConstantConditions
if (1 == 2) {
try {
Class.forName("io.papermc.paper.event.player.AsyncChatEvent");
return new Paper(discordSRV);
} catch (ClassNotFoundException ignored) {}
//noinspection ConstantConditions,PointlessBooleanExpression
if (1 == 2 && PaperComponentUtil.IS_PAPER_ADVENTURE) {
return new Paper(discordSRV);
}
return new Bukkit(discordSRV);
@ -56,14 +50,14 @@ public abstract class BukkitChatListener implements Listener {
}
protected void publishEvent(Player player, MinecraftComponent component, boolean cancelled) {
discordSRV.eventBus().publish(
new ChatMessageProcessingEvent(
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new GameChatMessageReceiveEvent(
discordSRV.playerProvider().player(player),
component,
new DefaultGlobalChannel(discordSRV),
component,
cancelled
)
);
));
}
static class Bukkit extends BukkitChatListener {
@ -84,39 +78,13 @@ public abstract class BukkitChatListener implements Listener {
static class Paper extends BukkitChatListener {
private static final Method MESSAGE_METHOD;
static {
try {
MESSAGE_METHOD = AsyncChatEvent.class.getMethod("message");
} catch (NoSuchMethodException e) {
throw new ExceptionInInitializerError(e);
}
}
public Paper(BukkitDiscordSRV discordSRV) {
super(discordSRV);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAsyncChat(AsyncChatEvent event) {
MinecraftComponent component = discordSRV.componentFactory().empty();
Object unrelocated;
try {
unrelocated = MESSAGE_METHOD.invoke(event);
} catch (IllegalAccessException | InvocationTargetException e) {
discordSRV.logger().error("Failed to get message from Paper AsyncChatEvent", e);
return;
}
MinecraftComponent.Adapter adapter = component.unrelocatedAdapter().orElse(null);
if (adapter == null) {
discordSRV.logger().error("Failed to get unrelocated adventure adapter for Paper AsyncChatEvent listener");
return;
}
adapter.setComponent(unrelocated);
MinecraftComponent component = PaperComponentUtil.getComponent(discordSRV, event, "message");
publishEvent(
event.getPlayer(),
component,

View File

@ -0,0 +1,48 @@
/*
* 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.bukkit.listener;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.message.receive.game.DeathMessageReceiveEvent;
import com.discordsrv.api.player.DiscordSRVPlayer;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.util.PaperComponentUtil;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
public class BukkitDeathListener implements Listener {
private final BukkitDiscordSRV discordSRV;
public BukkitDeathListener(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@SuppressWarnings("deprecation") // Paper
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerDeath(PlayerDeathEvent event) {
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getEntity());
MinecraftComponent component = PaperComponentUtil.getComponent(discordSRV, event, "deathMessage", PlayerDeathEvent::getDeathMessage);
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new DeathMessageReceiveEvent(player, null, component, event.isCancelled())));
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.bukkit.listener;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.event.events.message.receive.game.JoinMessageReceiveEvent;
import com.discordsrv.api.event.events.message.receive.game.LeaveMessageReceiveEvent;
import com.discordsrv.api.player.DiscordSRVPlayer;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.util.PaperComponentUtil;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class BukkitStatusMessageListener implements Listener {
private final BukkitDiscordSRV discordSRV;
public BukkitStatusMessageListener(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@SuppressWarnings("deprecation") // Paper
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer());
MinecraftComponent component = PaperComponentUtil.getComponent(
discordSRV, event, "joinMessage", PlayerJoinEvent::getJoinMessage);
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new JoinMessageReceiveEvent(player, null, component, false)
));
}
@SuppressWarnings("deprecation") // Paper
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerQuit(PlayerQuitEvent event) {
DiscordSRVPlayer player = discordSRV.playerProvider().player(event.getPlayer());
MinecraftComponent component = PaperComponentUtil.getComponent(
discordSRV, event, "quitMessage", PlayerQuitEvent::getQuitMessage);
discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new LeaveMessageReceiveEvent(player, null, component, false)
));
}
}

View File

@ -38,7 +38,7 @@ public class BukkitOfflinePlayer implements IOfflinePlayer {
@SuppressWarnings("NullabilityProblems")
@Override
public String getUsername() {
public String username() {
return offlinePlayer.getName();
}

View File

@ -82,7 +82,7 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
@SuppressWarnings("deprecation") // Paper
@Override
public @NotNull Component getDisplayName() {
public @NotNull Component displayName() {
if (DISPLAY_NAME_METHOD != null) {
try {
return ComponentUtil.fromUnrelocated(DISPLAY_NAME_METHOD.invoke(player));

View File

@ -24,8 +24,8 @@ import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager;
import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.logging.Logger;
import com.discordsrv.common.proxy.ProxyDiscordSRV;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.proxy.ProxyDiscordSRV;
import com.discordsrv.common.scheduler.StandardScheduler;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.md_5.bungee.api.ProxyServer;

View File

@ -19,8 +19,8 @@
package com.discordsrv.bungee;
import com.discordsrv.common.dependency.InitialDependencyLoader;
import com.discordsrv.logging.Logger;
import com.discordsrv.logging.impl.JavaLoggerImpl;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.impl.JavaLoggerImpl;
import dev.vankka.mcdependencydownload.bungee.bootstrap.BungeeBootstrap;
import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader;
import net.md_5.bungee.api.plugin.Plugin;

View File

@ -20,8 +20,8 @@ package com.discordsrv.bungee.console;
import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.common.console.Console;
import com.discordsrv.logging.backend.LoggingBackend;
import com.discordsrv.logging.impl.JavaLoggerImpl;
import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.impl.JavaLoggerImpl;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;

View File

@ -65,7 +65,7 @@ public class BungeePlayer implements IPlayer {
}
@Override
public @NotNull String getUsername() {
public @NotNull String username() {
return player.getName();
}
@ -75,7 +75,7 @@ public class BungeePlayer implements IPlayer {
}
@Override
public @NotNull Component getDisplayName() {
public @NotNull Component displayName() {
return BungeeComponentUtil.fromLegacy(player.getDisplayName());
}
}

View File

@ -3,6 +3,5 @@ dependencies {
api project(':api')
// Logging
api 'org.slf4j:slf4j-api:1.6.99'
compileOnly 'org.apache.logging.log4j:log4j-core:2.0-beta9'
api 'org.slf4j:slf4j-api:1.7.32'
}

View File

@ -16,9 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging.backend;
package com.discordsrv.common.logging;
import com.discordsrv.logging.LogLevel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging;
package com.discordsrv.common.logging;
public interface LogLevel {

View File

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging.adapter;
package com.discordsrv.common.logging.adapter;
import com.discordsrv.logging.LogLevel;
import com.discordsrv.logging.backend.LogAppender;
import com.discordsrv.common.logging.LogAppender;
import com.discordsrv.common.logging.LogLevel;
import org.slf4j.Marker;
import org.slf4j.helpers.MarkerIgnoringBase;
import org.slf4j.spi.LocationAwareLogger;

View File

@ -18,11 +18,10 @@
package org.slf4j.impl;
import com.discordsrv.logging.adapter.DependencyLoggerAdapter;
import com.discordsrv.common.logging.adapter.DependencyLoggerAdapter;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -32,6 +31,6 @@ public class DiscordSRVLoggerFactory implements ILoggerFactory {
@Override
public Logger getLogger(String s) {
return loggerMap.computeIfAbsent(s.toLowerCase(Locale.ROOT), DependencyLoggerAdapter::new);
return loggerMap.computeIfAbsent(s, DependencyLoggerAdapter::new);
}
}

View File

@ -40,6 +40,10 @@ dependencies {
runtimeDownloadApi 'org.spongepowered:configurate-yaml:' + rootProject.configurateVersion
runtimeDownloadApi 'org.spongepowered:configurate-hocon:' + rootProject.configurateVersion
// Logging
compileOnlyApi project(':common:common-slf4j-hack')
compileOnly 'org.apache.logging.log4j:log4j-core:2.0-beta9'
// Adventure, MCDiscordReserializer, EnhancedLegacyText
runtimeDownloadApi 'net.kyori:adventure-api:' + rootProject.adventureVersion
runtimeDownloadApi 'net.kyori:adventure-text-serializer-plain:' + rootProject.adventureVersion
@ -51,6 +55,9 @@ dependencies {
// Database Drivers
h2Driver 'com.h2database:h2:1.4.200'
mysqlDriver 'mysql:mysql-connector-java:8.0.25'
// Integrations
compileOnlyApi 'net.luckperms:api:5.3'
}
processResources {

View File

@ -0,0 +1,41 @@
/*
* 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;
import com.discordsrv.common.AbstractDiscordSRV;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.module.ModuleInitializationFunction;
import com.discordsrv.proxy.modules.ServerSwitchMessageModule;
public abstract class ProxyDiscordSRV<C extends MainConfig, CC extends ConnectionConfig> extends AbstractDiscordSRV<C, CC> {
@Override
protected void enable() throws Throwable {
super.enable();
for (ModuleInitializationFunction function : new ModuleInitializationFunction[]{
ServerSwitchMessageModule::new
}) {
try {
registerModule(function.initialize(this));
} catch (Throwable ignored) {}
}
}
}

View File

@ -0,0 +1,25 @@
package com.discordsrv.proxy.config.channels;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.config.annotation.Untranslated;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class ServerSwitchMessageConfig {
public boolean enabled = true;
@Untranslated(Untranslated.Type.VALUE)
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
.addEmbed(
DiscordMessageEmbed.builder()
.setAuthor(
"%player_display_name% switched from %from_server% to %to_server%",
null,
"%player_avatar_url%"
)
.setColor(0x5555FF)
.build()
);
}

View File

@ -0,0 +1,31 @@
/*
* 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.annotation.Order;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.proxy.config.channels.ServerSwitchMessageConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class ProxyBaseChannelConfig extends BaseChannelConfig {
@Order(1)
public ServerSwitchMessageConfig serverSwitchMessages = new ServerSwitchMessageConfig();
}

View File

@ -0,0 +1,22 @@
package com.discordsrv.proxy.config.channels.base;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import java.util.ArrayList;
import java.util.List;
public class ProxyChannelConfig extends ProxyBaseChannelConfig implements IChannelConfig {
public ProxyChannelConfig() {
initialize();
}
@Comment(CHANNEL_IDS_COMMENT)
public List<Long> channelIds = new ArrayList<>();
@Override
public List<Long> ids() {
return channelIds;
}
}

View File

@ -0,0 +1,21 @@
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.manager.MainConfigManager;
import com.discordsrv.proxy.config.channels.base.ProxyBaseChannelConfig;
import com.discordsrv.proxy.config.channels.base.ProxyChannelConfig;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
public abstract class ProxyConfigManager<T extends MainConfig> extends MainConfigManager<T> {
public ProxyConfigManager(DiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public ChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new ChannelConfig.Serializer(mapperFactory, ProxyBaseChannelConfig.class, ProxyChannelConfig.class);
}
}

View File

@ -0,0 +1 @@
package com.discordsrv.proxy.config;

View File

@ -0,0 +1,51 @@
package com.discordsrv.proxy.modules;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.event.bus.EventPriority;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.message.forward.game.ServerSwitchMessageForwardedEvent;
import com.discordsrv.api.event.events.message.receive.game.ServerSwitchMessageReceiveEvent;
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.proxy.config.channels.ServerSwitchMessageConfig;
import com.discordsrv.proxy.config.channels.base.ProxyBaseChannelConfig;
public class ServerSwitchMessageModule extends AbstractGameMessageModule<ServerSwitchMessageConfig> {
public ServerSwitchMessageModule(DiscordSRV discordSRV) {
super(discordSRV);
}
@Subscribe(priority = EventPriority.LAST)
public void onServerSwitchReceive(ServerSwitchMessageReceiveEvent event) {
if (checkCancellation(event) || checkProcessor(event)) {
return;
}
process(event, event.getPlayer(), null);
event.markAsProcessed();
}
@Override
public OrDefault<ServerSwitchMessageConfig> mapConfig(OrDefault<BaseChannelConfig> channelConfig) {
return channelConfig.map(cfg -> ((ProxyBaseChannelConfig) cfg).serverSwitchMessages);
}
@Override
public boolean isEnabled(OrDefault<ServerSwitchMessageConfig> config) {
return config.get(cfg -> cfg.enabled, false);
}
@Override
public SendableDiscordMessage.Builder getFormat(OrDefault<ServerSwitchMessageConfig> config) {
return config.get(cfg -> cfg.format);
}
@Override
public void postClusterToEventBus(ReceivedDiscordMessageCluster cluster) {
discordSRV.eventBus().publish(new ServerSwitchMessageForwardedEvent(cluster));
}
}

View File

@ -21,6 +21,8 @@ package com.discordsrv.common.server;
import com.discordsrv.common.AbstractDiscordSRV;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.module.ModuleInitializationFunction;
import com.discordsrv.common.server.modules.DeathMessageModule;
import com.discordsrv.common.server.player.ServerPlayerProvider;
import com.discordsrv.common.server.scheduler.ServerScheduler;
import org.jetbrains.annotations.NotNull;
@ -36,6 +38,19 @@ public abstract class ServerDiscordSRV<C extends MainConfig, CC extends Connecti
@Override
public abstract @NotNull ServerPlayerProvider<?> playerProvider();
@Override
protected void enable() throws Throwable {
super.enable();
for (ModuleInitializationFunction function : new ModuleInitializationFunction[]{
DeathMessageModule::new
}) {
try {
registerModule(function.initialize(this));
} catch (Throwable ignored) {}
}
}
public final CompletableFuture<Void> invokeServerStarted() {
return invokeLifecycle(this::serverStarted, "Failed to enable", true);
}

View File

@ -0,0 +1,41 @@
/*
* 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.server.config.channels;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.config.annotation.Untranslated;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class DeathMessageConfig {
@Comment("Enable death message forwarding")
public boolean enabled = true;
@Untranslated(Untranslated.Type.VALUE)
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
.addEmbed(
DiscordMessageEmbed.builder()
.setAuthor("%message%", null, "%player_avatar_url%")
.setColor(1)
.build()
);
}

View File

@ -0,0 +1,31 @@
/*
* 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.server.config.channels.base;
import com.discordsrv.common.config.annotation.Order;
import com.discordsrv.common.server.config.channels.DeathMessageConfig;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class ServerBaseChannelConfig extends BaseChannelConfig {
@Order(1)
public DeathMessageConfig deathMessages = new DeathMessageConfig();
}

View File

@ -16,39 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.config.main.channels;
package com.discordsrv.common.server.config.channels.base;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
@ConfigSerializable
public class ChannelConfig extends BaseChannelConfig {
public class ServerChannelConfig extends ServerBaseChannelConfig implements IChannelConfig {
public static final String DEFAULT_KEY = "default";
public ChannelConfig() {
// Clear everything besides channelIds by default (these will be filled back in by Configurate if they are in the config itself)
for (Field field : getClass().getFields()) {
int modifiers = field.getModifiers();
if (!Modifier.isPublic(modifiers) || Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
continue;
}
if (field.getName().equals("channelIds")) {
continue;
}
try {
field.set(this, null);
} catch (IllegalAccessException ignored) {}
}
public ServerChannelConfig() {
initialize();
}
@Comment("The channels this in-game channel will forward to in Discord")
@Comment(CHANNEL_IDS_COMMENT)
public List<Long> channelIds = new ArrayList<>();
@Override
public List<Long> ids() {
return channelIds;
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.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.manager.MainConfigManager;
import com.discordsrv.common.server.config.channels.base.ServerBaseChannelConfig;
import com.discordsrv.common.server.config.channels.base.ServerChannelConfig;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
public abstract class ServerConfigManager<T extends MainConfig> extends MainConfigManager<T> {
public ServerConfigManager(DiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public ChannelConfig.Serializer getChannelConfigSerializer(ObjectMapper.Factory mapperFactory) {
return new ChannelConfig.Serializer(mapperFactory, ServerBaseChannelConfig.class, ServerChannelConfig.class);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.server.modules;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.event.bus.EventPriority;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.message.forward.game.DeathMessageForwardedEvent;
import com.discordsrv.api.event.events.message.receive.game.DeathMessageReceiveEvent;
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.server.config.channels.base.ServerBaseChannelConfig;
public class DeathMessageModule extends AbstractGameMessageModule<DeathMessageConfig> {
public DeathMessageModule(DiscordSRV discordSRV) {
super(discordSRV);
}
@Subscribe(priority = EventPriority.LAST)
public void onDeathReceive(DeathMessageReceiveEvent event) {
if (checkCancellation(event) || checkProcessor(event)) {
return;
}
process(event, event.getPlayer(), event.getGameChannel());
event.markAsProcessed();
}
@Override
public OrDefault<DeathMessageConfig> mapConfig(OrDefault<BaseChannelConfig> channelConfig) {
return channelConfig.map(cfg -> ((ServerBaseChannelConfig) cfg).deathMessages);
}
@Override
public boolean isEnabled(OrDefault<DeathMessageConfig> config) {
return config.get(cfg -> cfg.enabled, true);
}
@Override
public SendableDiscordMessage.Builder getFormat(OrDefault<DeathMessageConfig> config) {
return config.get(cfg -> cfg.format);
}
@Override
public void postClusterToEventBus(ReceivedDiscordMessageCluster cluster) {
discordSRV.eventBus().publish(new DeathMessageForwardedEvent(cluster));
}
}

View File

@ -0,0 +1,3 @@
dependencies {
api 'org.slf4j:slf4j-api:1.7.32'
}

View File

@ -0,0 +1,28 @@
/*
* 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.x.slf4j;
/**
* A fake org.slf4j.Logger that is compileOnly scoped and relocated back to the real org.slf4j package.
* This is required as we want to relocate 'org.slf4j'
* but we also need the non-relocated version for Velocity's logging.
*
* Could be fixed by https://github.com/johnrengelman/shadow/issues/727
*/
public interface Logger extends org.slf4j.Logger {}

View File

@ -35,22 +35,26 @@ 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.logging.DependencyLoggingHandler;
import com.discordsrv.common.module.Module;
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.module.ModuleInitializationFunction;
import com.discordsrv.common.module.ModuleManager;
import com.discordsrv.common.module.modules.DiscordAPIEventModule;
import com.discordsrv.common.module.modules.DiscordToMinecraftModule;
import com.discordsrv.common.module.modules.message.DiscordToMinecraftChatModule;
import com.discordsrv.common.module.modules.GlobalChannelLookupModule;
import com.discordsrv.common.module.modules.MinecraftToDiscordModule;
import com.discordsrv.common.module.modules.message.MinecraftToDiscordChatModule;
import com.discordsrv.common.module.modules.integration.LuckPermsIntegration;
import com.discordsrv.common.placeholder.ComponentResultStringifier;
import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
import com.discordsrv.common.placeholder.context.GlobalTextHandlingContext;
import com.discordsrv.logging.adapter.DependencyLoggerAdapter;
import com.discordsrv.common.logging.adapter.DependencyLoggerAdapter;
import net.dv8tion.jda.api.JDA;
import org.jetbrains.annotations.NotNull;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@ -167,12 +171,12 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
}
@Override
public void registerModule(Module module) {
public void registerModule(AbstractModule module) {
moduleManager.register(module);
}
@Override
public void unregisterModule(Module module) {
public void unregisterModule(AbstractModule module) {
moduleManager.unregister(module);
}
@ -264,13 +268,19 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
// Register modules
moduleManager = new ModuleManager(this);
for (Module module : Arrays.asList(
new DiscordAPIEventModule(this),
new DiscordToMinecraftModule(this),
new GlobalChannelLookupModule(this),
new MinecraftToDiscordModule(this)
)) {
registerModule(module);
for (ModuleInitializationFunction function : new ModuleInitializationFunction[]{
LuckPermsIntegration::new,
DiscordToMinecraftChatModule::new,
JoinMessageModule::new,
LeaveMessageModule::new,
MinecraftToDiscordChatModule::new,
DiscordAPIEventModule::new,
GlobalChannelLookupModule::new
}) {
try {
registerModule(function.initialize(this));
} catch (Throwable ignored) {}
}
}

View File

@ -28,11 +28,12 @@ import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.common.console.Console;
import com.discordsrv.common.discord.api.DiscordAPIImpl;
import com.discordsrv.common.discord.connection.DiscordConnectionManager;
import com.discordsrv.common.module.Module;
import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.module.type.Module;
import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
import com.discordsrv.common.player.provider.AbstractPlayerProvider;
import com.discordsrv.common.scheduler.Scheduler;
import com.discordsrv.logging.Logger;
import com.discordsrv.common.logging.Logger;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ -80,8 +81,8 @@ public interface DiscordSRV extends DiscordSRVApi {
// Modules
<T extends Module> T getModule(Class<T> moduleType);
void registerModule(Module module);
void unregisterModule(Module module);
void registerModule(AbstractModule module);
void unregisterModule(AbstractModule module);
Locale locale();
void setStatus(Status status);

View File

@ -24,17 +24,16 @@ import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.channel.GameChannelLookupEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig;
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.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;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@ -42,7 +41,7 @@ public class ChannelConfigHelper {
private final DiscordSRV discordSRV;
private final LoadingCache<String, GameChannel> nameToChannelCache;
private final Map<Long, Pair<String, ChannelConfig>> discordToConfigMap;
private final Map<Long, Map<String, BaseChannelConfig>> discordToConfigMap;
public ChannelConfigHelper(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
@ -74,14 +73,15 @@ public class ChannelConfigHelper {
return;
}
Map<Long, Pair<String, ChannelConfig>> newMap = new HashMap<>();
Map<Long, Map<String, BaseChannelConfig>> newMap = new HashMap<>();
for (Map.Entry<String, BaseChannelConfig> entry : channels().entrySet()) {
String channelName = entry.getKey();
BaseChannelConfig value = entry.getValue();
if (value instanceof ChannelConfig) {
ChannelConfig channelConfig = (ChannelConfig) value;
for (long channelId : channelConfig.channelIds) {
newMap.put(channelId, Pair.of(channelName, channelConfig));
if (value instanceof IChannelConfig) {
IChannelConfig channelConfig = (IChannelConfig) value;
for (long channelId : channelConfig.ids()) {
newMap.computeIfAbsent(channelId, key -> new LinkedHashMap<>())
.put(channelName, value);
}
}
}
@ -96,22 +96,27 @@ public class ChannelConfigHelper {
return discordSRV.config().channels;
}
private BaseChannelConfig getDefault() {
return channels().computeIfAbsent(ChannelConfig.DEFAULT_KEY, key -> new BaseChannelConfig());
}
public Set<OrDefault<BaseChannelConfig>> getAllChannels() {
BaseChannelConfig defaultConfig = getDefault();
Set<OrDefault<BaseChannelConfig>> channelConfigs = new HashSet<>();
for (Map.Entry<String, BaseChannelConfig> entry : channels().entrySet()) {
if (entry.getKey().equals(ChannelConfig.DEFAULT_KEY)) {
continue;
}
channelConfigs.add(new OrDefault<>(entry.getValue(), defaultConfig));
}
return channelConfigs;
}
public OrDefault<BaseChannelConfig> orDefault(GameChannel gameChannel) {
return orDefault(gameChannel.getOwnerName(), gameChannel.getChannelName());
}
public OrDefault<Pair<GameChannel, BaseChannelConfig>> orDefault(DiscordTextChannel discordTextChannel) {
return new OrDefault<>(
getDiscordResolved(discordTextChannel),
Pair.of(null, getDefault())
);
}
private BaseChannelConfig getDefault() {
return channels().computeIfAbsent(
"default", key -> new BaseChannelConfig());
}
public OrDefault<BaseChannelConfig> orDefault(String ownerName, String channelName) {
BaseChannelConfig defaultConfig = getDefault();
@ -144,21 +149,39 @@ public class ChannelConfigHelper {
return gameChannel != null ? get(gameChannel) : null;
}
public Pair<GameChannel, BaseChannelConfig> getDiscordResolved(DiscordTextChannel channel) {
Pair<String, ? extends BaseChannelConfig> pair = getDiscord(channel);
if (pair == null) {
return null;
}
public Map<GameChannel, OrDefault<BaseChannelConfig>> orDefault(DiscordTextChannel discordTextChannel) {
BaseChannelConfig defaultConfig = getDefault();
GameChannel gameChannel = nameToChannelCache.get(pair.getKey());
if (gameChannel == null) {
return null;
Map<GameChannel, OrDefault<BaseChannelConfig>> channels = new HashMap<>();
for (Map.Entry<GameChannel, BaseChannelConfig> entry : getDiscordResolved(discordTextChannel).entrySet()) {
channels.put(
entry.getKey(),
new OrDefault<>(entry.getValue(), defaultConfig)
);
}
return Pair.of(gameChannel, pair.getValue());
return channels;
}
public Pair<String, ? extends BaseChannelConfig> getDiscord(DiscordTextChannel channel) {
public Map<GameChannel, BaseChannelConfig> getDiscordResolved(DiscordTextChannel channel) {
Map<String, BaseChannelConfig> pairs = getDiscord(channel);
if (pairs == null) {
return Collections.emptyMap();
}
Map<GameChannel, BaseChannelConfig> channels = new LinkedHashMap<>();
for (Map.Entry<String, BaseChannelConfig> entry : pairs.entrySet()) {
GameChannel gameChannel = nameToChannelCache.get(entry.getKey());
if (gameChannel == null) {
continue;
}
channels.put(gameChannel, entry.getValue());
}
return channels;
}
public Map<String, BaseChannelConfig> getDiscord(DiscordTextChannel channel) {
synchronized (discordToConfigMap) {
return discordToConfigMap.get(channel.getId());
}

View File

@ -23,7 +23,7 @@ import com.discordsrv.api.discord.api.entity.DiscordUser;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
import com.discordsrv.api.event.events.message.receive.discord.DiscordMessageProcessingEvent;
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;
@ -47,7 +47,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
}
public static void runInContext(
DiscordMessageProcessingEvent event,
DiscordChatMessageProcessingEvent event,
OrDefault<DiscordToMinecraftChatConfig> config,
Runnable runnable
) {
@ -58,7 +58,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
}
public static <T> T getWithContext(
DiscordMessageProcessingEvent event,
DiscordChatMessageProcessingEvent event,
OrDefault<DiscordToMinecraftChatConfig> config,
Supplier<T> supplier
) {
@ -149,10 +149,10 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
private static class Context {
private final DiscordMessageProcessingEvent event;
private final DiscordChatMessageProcessingEvent event;
private final OrDefault<DiscordToMinecraftChatConfig> config;
public Context(DiscordMessageProcessingEvent event, OrDefault<DiscordToMinecraftChatConfig> config) {
public Context(DiscordChatMessageProcessingEvent event, OrDefault<DiscordToMinecraftChatConfig> config) {
this.event = event;
this.config = config;
}

View File

@ -22,9 +22,13 @@ import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentAdapter;
import com.discordsrv.common.component.MinecraftComponentImpl;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import java.util.Collection;
/**
* An util class for {@link Component}s and {@link MinecraftComponent}s.
*/
@ -65,4 +69,19 @@ public final class ComponentUtil {
.setComponent(unrelocatedAdventure);
return fromAPI(component);
}
public static Component join(Component delimiter, Collection<? extends ComponentLike> components) {
return join(delimiter, components.toArray(new ComponentLike[0]));
}
public static Component join(Component delimiter, ComponentLike[] components) {
TextComponent.Builder builder = Component.text();
for (int i = 0; i < components.length; i++) {
builder.append(components[i]);
if (i < components.length - 1) {
builder.append(delimiter);
}
}
return builder.build();
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.config.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Manually determines the position of the option in the config, everything is ordered {@code 0} by default,
* and will go in the order they are defined.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Order {
int value();
}

View File

@ -0,0 +1,92 @@
/*
* 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.config.fielddiscoverer;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.objectmapping.FieldData;
import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.util.CheckedFunction;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class OrderedFieldDiscovererProxy<T> implements FieldDiscoverer<T> {
private final FieldDiscoverer<T> fieldDiscoverer;
private final Comparator<FieldCollectorData<T, ?>> order;
public OrderedFieldDiscovererProxy(FieldDiscoverer<T> fieldDiscoverer, Comparator<FieldCollectorData<T, ?>> order) {
this.fieldDiscoverer = fieldDiscoverer;
this.order = order;
}
@Override
public @Nullable <V> InstanceFactory<T> discover(AnnotatedType target, FieldCollector<T, V> collector) throws SerializationException {
List<FieldCollectorData<T, V>> data = new ArrayList<>();
FieldCollector<T, V> fieldCollector = (name, type, annotations, deserializer, serializer) ->
data.add(new FieldCollectorData<>(name, type, annotations, deserializer, serializer));
InstanceFactory<T> instanceFactory = fieldDiscoverer.discover(target, fieldCollector);
if (instanceFactory == null) {
return null;
}
data.sort(order);
for (FieldCollectorData<T, V> field : data) {
collector.accept(field.name, field.type, field.annotations, field.deserializer, field.serializer);
}
return instanceFactory;
}
public static class FieldCollectorData<T, V> {
private final String name;
private final AnnotatedType type;
private final AnnotatedElement annotations;
private final FieldData.Deserializer<T> deserializer;
private final CheckedFunction<V, Object, Exception> serializer;
public FieldCollectorData(String name, AnnotatedType type, AnnotatedElement annotations,
FieldData.Deserializer<T> deserializer,
CheckedFunction<V, Object, Exception> serializer) {
this.name = name;
this.type = type;
this.annotations = annotations;
this.deserializer = deserializer;
this.serializer = serializer;
}
public String name() {
return name;
}
public AnnotatedType type() {
return type;
}
public AnnotatedElement annotations() {
return annotations;
}
}
}

View File

@ -20,12 +20,11 @@ package com.discordsrv.common.config.main;
import com.discordsrv.common.config.Config;
import com.discordsrv.common.config.annotation.DefaultOnly;
import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.*;
@ConfigSerializable
public class MainConfig implements Config {
@ -40,6 +39,8 @@ public class MainConfig implements Config {
@DefaultOnly("default")
public Map<String, BaseChannelConfig> channels = new LinkedHashMap<String, BaseChannelConfig>() {{
put("global", new ChannelConfig());
put("default", new BaseChannelConfig());
put(ChannelConfig.DEFAULT_KEY, new BaseChannelConfig());
}};
public List<ChannelUpdaterConfig> channelUpdaters = new ArrayList<>(Collections.singletonList(new ChannelUpdaterConfig()));
}

View File

@ -18,6 +18,7 @@
package com.discordsrv.common.config.main.channels;
import com.discordsrv.common.config.annotation.Untranslated;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -30,14 +31,24 @@ import java.util.regex.Pattern;
@ConfigSerializable
public class DiscordToMinecraftChatConfig {
@Comment("The Discord to Minecraft message format for regular users")
public String format = "[&#5865F2Discord&r] [hover:show_text:Tag: %user_tag%&r\nRoles: %user_roles_, |text_&7&oNone%]%user_color%%user_effective_name%&r » %message% %message_attachments%";
@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 = "[&#5865F2Discord&r] [hover:show_text:Tag: %user_tag%&r\nRoles: %user_roles_, |text_&7&oNone%]%user_color%%user_effective_name%&r » %message%%message_attachments%";
@Comment("The Discord to Minecraft message format for webhook messages (if enabled)")
public String webhookFormat = "[&#5865F2Discord&r] [hover:show_text:Webhook message]%user_name%&r » %message% %message_attachments%";
@Untranslated(Untranslated.Type.VALUE)
public String webhookFormat = "[&#5865F2Discord&r] [hover:show_text:Webhook message]%user_name%&r » %message%%message_attachments_ %";
@Comment("Attachment format")
@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";
// 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")
@ -56,7 +67,7 @@ public class DiscordToMinecraftChatConfig {
public boolean bots = false;
@Comment("If webhooks should be ignored")
public boolean webhooks = false;
public boolean webhooks = true;
@ConfigSerializable
public static class IDs {
@ -82,11 +93,14 @@ public class DiscordToMinecraftChatConfig {
public static class Format {
@Comment("The format shown in-game")
@Untranslated(Untranslated.Type.VALUE)
public String format = "";
@Comment("The format when the entity is deleted or can't be looked up")
@Untranslated(Untranslated.Type.VALUE)
public String unknownFormat = "";
@SuppressWarnings("unused") // Configurate
public Format() {}
public Format(String format, String unknownFormat) {

View File

@ -0,0 +1,39 @@
/*
* 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.config.main.channels;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.config.annotation.Untranslated;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class JoinMessageConfig {
public boolean enabled = true;
@Untranslated(Untranslated.Type.VALUE)
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
.addEmbed(
DiscordMessageEmbed.builder()
.setAuthor("%player_display_name% joined", null, "%player_avatar_url%")
.setColor(0x55FF55)
.build()
);
}

View File

@ -0,0 +1,39 @@
/*
* 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.config.main.channels;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.config.annotation.Untranslated;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class LeaveMessageConfig {
public boolean enabled = true;
@Untranslated(Untranslated.Type.VALUE)
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
.addEmbed(
DiscordMessageEmbed.builder()
.setAuthor("%player_display_name% left", null, "%player_avatar_url%")
.setColor(0xFF5555)
.build()
);
}

View File

@ -19,6 +19,7 @@
package com.discordsrv.common.config.main.channels;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.config.annotation.Untranslated;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -29,10 +30,14 @@ import java.util.regex.Pattern;
@ConfigSerializable
public class MinecraftToDiscordChatConfig {
@Comment("Is Minecraft to Discord chat forwarding enabled")
public boolean enabled = true;
@Untranslated(Untranslated.Type.VALUE)
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
.setWebhookUsername("%player_display_name%")
.setWebhookAvatarUrl("%player_avatar_url%")
.setContent("%message%");// TODO
.setContent("%message%");
// TODO: more info on regex pairs (String#replaceAll)
@Comment("Regex filters for Minecraft message contents (this is the %message% part of the \"format\" option)")

View File

@ -0,0 +1,40 @@
/*
* 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.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 org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class BaseChannelConfig {
public MinecraftToDiscordChatConfig minecraftToDiscord = new MinecraftToDiscordChatConfig();
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
public JoinMessageConfig joinMessages = new JoinMessageConfig();
public LeaveMessageConfig leaveMessages = new LeaveMessageConfig();
@Untranslated(Untranslated.Type.VALUE)
@Order(5)
public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay";
}

View File

@ -16,38 +16,52 @@
* 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.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 java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
@ConfigSerializable
public class BaseChannelConfig {
public class ChannelConfig extends BaseChannelConfig implements IChannelConfig {
public MinecraftToDiscordChatConfig minecraftToDiscord = new MinecraftToDiscordChatConfig();
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
public ChannelConfig() {
initialize();
}
public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay";
@Comment(CHANNEL_IDS_COMMENT)
public List<Long> channelIds = new ArrayList<>();
@Override
public List<Long> ids() {
return channelIds;
}
public static class Serializer implements TypeSerializer<BaseChannelConfig> {
private final ObjectMapper.Factory mapperFactory;
private final Class<?> baseConfigClass;
private final Class<?> configClass;
public Serializer(ObjectMapper.Factory mapperFactory) {
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()) ? BaseChannelConfig.class : ChannelConfig.class,
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
node
);
}
@ -60,7 +74,7 @@ public class BaseChannelConfig {
}
mapperFactory.asTypeSerializer().serialize(
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? BaseChannelConfig.class : ChannelConfig.class,
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
obj,
node
);

View File

@ -0,0 +1,52 @@
/*
* 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.config.main.channels.base;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
public interface IChannelConfig {
String DEFAULT_KEY = "default";
String CHANNEL_IDS_COMMENT = "The channels this in-game channel will forward to in Discord";
default void initialize() {
// Clear everything besides channelIds by default (these will be filled back in by Configurate if they are in the config itself)
Class<?> clazz = getClass();
while (clazz != null) {
for (Field field : clazz.getFields()) {
int modifiers = field.getModifiers();
if (!Modifier.isPublic(modifiers) || Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
continue;
}
if (field.getName().equals("channelIds")) {
continue;
}
try {
field.set(this, null);
} catch (IllegalAccessException ignored) {}
}
clazz = clazz.getSuperclass();
}
}
List<Long> ids();
}

View File

@ -19,17 +19,12 @@
package com.discordsrv.common.config.manager.loader;
import org.jetbrains.annotations.ApiStatus;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
import org.spongepowered.configurate.yaml.NodeStyle;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
public interface YamlConfigLoaderProvider extends ConfigLoaderProvider<YamlConfigurationLoader> {
default ConfigurationOptions defaultOptions() {
return ConfigurationOptions.defaults();
}
default NodeStyle nodeStyle() {
return NodeStyle.BLOCK;
}

View File

@ -23,7 +23,10 @@ import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.annotation.DefaultOnly;
import com.discordsrv.common.config.main.channels.BaseChannelConfig;
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.manager.loader.ConfigLoaderProvider;
import com.discordsrv.common.config.serializer.ColorSerializer;
import com.discordsrv.common.config.serializer.DiscordMessageEmbedSerializer;
@ -37,6 +40,7 @@ import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.util.NamingScheme;
@ -44,6 +48,7 @@ import org.spongepowered.configurate.util.NamingSchemes;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@ -88,18 +93,30 @@ 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 ConfigurationOptions defaultOptions() {
return ConfigurationOptions.defaults()
.shouldCopyDefaults(false)
.implicitInitialization(false)
.serializers(builder -> {
ObjectMapper.Factory objectMapper = configObjectMapper();
builder.register(BaseChannelConfig.class, getChannelConfigSerializer(objectMapper));
builder.register(Color.class, new ColorSerializer());
builder.register(Pattern.class, new PatternSerializer());
builder.register(BaseChannelConfig.class, new BaseChannelConfig.Serializer(objectMapper));
builder.register(DiscordMessageEmbed.Builder.class, new DiscordMessageEmbedSerializer(NAMING_SCHEME));
builder.register(DiscordMessageEmbed.Field.class, new DiscordMessageEmbedSerializer.FieldSerializer(NAMING_SCHEME));
builder.register(SendableDiscordMessage.Builder.class, new SendableDiscordMessageSerializer(NAMING_SCHEME));
// give Configurate' serializers the ObjectMapper mapper
builder.register(type -> {
String typeName = type.getTypeName();
return typeName.startsWith("com.discordsrv")
&& !typeName.startsWith("com.discordsrv.dependencies")
&& !typeName.contains(".serializer");
}, objectMapper.asTypeSerializer());
});
}
@ -111,12 +128,20 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
return defaultOptions();
}
protected ObjectMapper.Factory.Builder objectMapperBuilder() {
@SuppressWarnings("unchecked")
public ObjectMapper.Factory.Builder objectMapperBuilder() {
Comparator<OrderedFieldDiscovererProxy.FieldCollectorData<Object, ?>> fieldOrder = Comparator.comparingInt(data -> {
Order order = data.annotations().getAnnotation(Order.class);
return order != null ? order.value() : 0;
});
return ObjectMapper.factoryBuilder()
.defaultNamingScheme(NAMING_SCHEME);
.defaultNamingScheme(NAMING_SCHEME)
.addDiscoverer(new OrderedFieldDiscovererProxy<>((FieldDiscoverer<Object>) FieldDiscoverer.emptyConstructorObject(), fieldOrder))
.addDiscoverer(new OrderedFieldDiscovererProxy<>((FieldDiscoverer<Object>) FieldDiscoverer.record(), fieldOrder));
}
protected ObjectMapper.Factory.Builder configObjectMapperBuilder() {
public ObjectMapper.Factory.Builder configObjectMapperBuilder() {
return objectMapperBuilder();
}
@ -164,9 +189,13 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
}
private CommentedConfigurationNode getDefault(T defaultConfig, boolean cleanMapper) throws SerializationException {
return getDefault(defaultConfig, cleanMapper ? defaultObjectMapper() : configObjectMapper());
}
@SuppressWarnings("unchecked")
private CommentedConfigurationNode getDefault(T defaultConfig, ObjectMapper.Factory mapperFactory) throws SerializationException {
CommentedConfigurationNode node = CommentedConfigurationNode.root(defaultNodeOptions());
(cleanMapper ? defaultObjectMapper() : configObjectMapper())
.get(defaultConfig.getClass()).load(node);
mapperFactory.get((Class<T>) defaultConfig.getClass()).save(defaultConfig, node);
return node;
}
@ -175,6 +204,10 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
return null;
}
public CommentedConfigurationNode getDefaultNode(ObjectMapper.Factory mapperFactory) throws ConfigurateException {
return getDefault(createConfiguration(), mapperFactory);
}
@Override
public void load() throws ConfigException {
reload();

View File

@ -30,6 +30,9 @@ import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class TranslatedConfigManager<T extends Config, LT extends AbstractConfigurationLoader<CommentedConfigurationNode>>
extends ConfigurateConfigManager<T, LT> {
@ -64,17 +67,18 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
}
try {
ConfigurationNode translation = getTranslationRoot();
if (translation == null) {
ConfigurationNode translationRoot = getTranslationRoot();
if (translationRoot == null) {
return;
}
translation = translation.node(config.getFileName());
String fileIdentifier = config.getFileName();
ConfigurationNode translation = translationRoot.node(fileIdentifier);
ConfigurationNode comments = translationRoot.node(fileIdentifier + "_comments");
CommentedConfigurationNode node = loader().createNode();
save(config, (Class<T>) config.getClass(), node);
//node.set(config);
translateNode(node, translation, translation.node("_comments"));
translateNode(node, translation, comments);
} catch (ConfigurateException e) {
throw new ConfigException(e);
}
@ -96,13 +100,14 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
ConfigurationNode translations,
ConfigurationNode commentTranslations
) throws SerializationException {
Object[] path = node.path().array();
List<Object> path = new ArrayList<>(Arrays.asList(node.path().array()));
String translation = translations.node(path).getString();
if (translation != null) {
node.set(translation);
}
path.add("_comment");
String commentTranslation = commentTranslations.node(path).getString();
if (commentTranslation != null) {
node.comment(commentTranslation);

View File

@ -54,6 +54,6 @@ public class ColorSerializer implements TypeSerializer<Color> {
if (obj == null) {
return;
}
node.set(obj.hex());
node.set("#" + obj.hex());
}
}

View File

@ -29,6 +29,7 @@ import org.spongepowered.configurate.util.NamingScheme;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMessageEmbed.Builder> {
@ -53,7 +54,7 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
Color color = node.node(map("Color")).get(Color.class);
builder.setColor(color != null ? color.rgb() : Role.DEFAULT_COLOR_RAW);
ConfigurationNode author = node.node("Author");
ConfigurationNode author = node.node(map("Author"));
builder.setAuthor(
author.node(map("Name")).getString(),
author.node(map("Url")).getString(),
@ -65,7 +66,7 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
title.node(map("Url")).getString());
builder.setDescription(node.node(map("Description")).getString());
for (DiscordMessageEmbed.Field field : node.getList(DiscordMessageEmbed.Field.class, Collections.emptyList())) {
for (DiscordMessageEmbed.Field field : node.node(map("Fields")).getList(DiscordMessageEmbed.Field.class, Collections.emptyList())) {
builder.addField(field);
}
@ -77,7 +78,7 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
ConfigurationNode footer = node.node(map("Footer"));
builder.setFooter(
footer.node(map("Text")).getString(),
footer.node(map("ImageUrl")).getString(footer.node(map("IconUrl")).getString()));
footer.node(map("ImageUrl")).getString(footer.node(map("IconUrl")).getString("")));
return builder;
}
@ -102,7 +103,10 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
title.node(map("Url")).set(obj.getTitleUrl());
node.node(map("Description")).set(obj.getDescription());
node.node(map("Fields")).setList(DiscordMessageEmbed.Field.class, obj.getFields());
List<DiscordMessageEmbed.Field> fields = obj.getFields();
ConfigurationNode fieldsNode = node.node(map("Fields"));
fieldsNode.setList(DiscordMessageEmbed.Field.class, fields.isEmpty() ? null : obj.getFields());
node.node(map("ThumbnailUrl")).set(obj.getThumbnailUrl());
node.node(map("ImageUrl")).set(obj.getImageUrl());

View File

@ -19,7 +19,7 @@
package com.discordsrv.common.console;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.LoggingBackend;
public interface Console extends ICommandSender {

View File

@ -18,7 +18,7 @@
package com.discordsrv.common.dependency;
import com.discordsrv.logging.Logger;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.scheduler.threadfactory.CountingForkJoinWorkerThreadFactory;
import dev.vankka.dependencydownload.classpath.ClasspathAppender;

View File

@ -30,8 +30,8 @@ import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
import com.discordsrv.api.discord.api.exception.NotReadyException;
import com.discordsrv.api.discord.api.exception.RestErrorResponseException;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.discord.api.channel.DiscordDMChannelImpl;
import com.discordsrv.common.discord.api.channel.DiscordTextChannelImpl;
import com.discordsrv.common.discord.api.guild.DiscordGuildImpl;
@ -208,7 +208,7 @@ public class DiscordAPIImpl implements DiscordAPI {
}).thenApply(webhook ->
WebhookClientBuilder.fromJDA(webhook)
.setHttpClient(jda.getHttpClient())
.setExecutorService(discordSRV.scheduler().executor())
.setExecutorService(discordSRV.scheduler().scheduledExecutor())
.build()
);
}
@ -218,8 +218,8 @@ public class DiscordAPIImpl implements DiscordAPI {
private boolean isConfiguredChannel(Long channelId) {
for (BaseChannelConfig config : discordSRV.config().channels.values()) {
if (config instanceof ChannelConfig
&& ((ChannelConfig) config).channelIds.contains(channelId)) {
if (config instanceof IChannelConfig
&& ((IChannelConfig) config).ids().contains(channelId)) {
return true;
}
}

View File

@ -25,12 +25,12 @@ import com.discordsrv.api.discord.api.entity.guild.DiscordRole;
import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.discord.api.DiscordUserImpl;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextColor;
import org.jetbrains.annotations.NotNull;
@ -107,20 +107,19 @@ public class DiscordGuildMemberImpl extends DiscordUserImpl implements DiscordGu
return null;
}
@Placeholder("user_roles_")
@Placeholder("user_roles")
public Component _allRoles(@PlaceholderRemainder String suffix) {
if (suffix.startsWith("_")) {
suffix = suffix.substring(1);
} else if (!suffix.isEmpty()) {
return null;
}
List<Component> components = new ArrayList<>();
for (DiscordRole role : getRoles()) {
components.add(Component.text(role.getName()).color(TextColor.color(role.getColor().rgb())));
}
TextComponent.Builder builder = Component.text();
for (int i = 0; i < components.size(); i++) {
builder.append(components.get(i));
if (i < components.size() - 1) {
builder.append(Component.text(suffix));
}
}
return builder.build();
return ComponentUtil.join(Component.text(suffix), components);
}
}

View File

@ -36,18 +36,20 @@ import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.api.entity.message.impl.SendableDiscordMessageImpl;
import com.discordsrv.api.discord.api.exception.RestErrorResponseException;
import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder;
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.discord.api.DiscordUserImpl;
import com.discordsrv.common.discord.api.channel.DiscordMessageChannelImpl;
import com.discordsrv.common.discord.api.guild.DiscordGuildMemberImpl;
import com.discordsrv.common.function.OrDefault;
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.User;
import net.dv8tion.jda.api.requests.ErrorResponse;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -287,18 +289,24 @@ public class ReceivedDiscordMessageImpl extends SendableDiscordMessageImpl imple
//
@Placeholder("message_attachments")
public Component _attachments() {
// TODO: customizable
TextComponent.Builder builder = Component.text();
for (Attachment attachment : attachments) {
builder.append(
Component.text()
.content("[" + attachment.fileName() + "]")
.clickEvent(ClickEvent.openUrl(attachment.url()))
)
.append(Component.text(" "));
public Component _attachments(OrDefault<BaseChannelConfig> config, @PlaceholderRemainder String suffix) {
if (suffix.startsWith("_")) {
suffix = suffix.substring(1);
} else if (!suffix.isEmpty()) {
return null;
}
return builder.build();
String attachmentFormat = config.map(cfg -> cfg.discordToMinecraft).get(cfg -> cfg.attachmentFormat);
List<Component> components = new ArrayList<>();
for (Attachment attachment : attachments) {
components.add(ComponentUtil.fromAPI(
discordSRV.componentFactory().enhancedBuilder(attachmentFormat)
.addReplacement("%file_name%", attachment.fileName())
.addReplacement("%file_url%", attachment.url())
.build()
));
}
return ComponentUtil.join(Component.text(suffix), components);
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging;
package com.discordsrv.common.logging;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging.backend;
package com.discordsrv.common.logging.backend;
import com.discordsrv.logging.LogLevel;
import com.discordsrv.common.logging.LogLevel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -16,7 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging.backend;
package com.discordsrv.common.logging.backend;
import com.discordsrv.common.logging.LogAppender;
public interface LoggingBackend {

View File

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.logging;
package com.discordsrv.common.logging.dependency;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.logging.LogLevel;
import com.discordsrv.logging.backend.LogAppender;
import com.discordsrv.common.logging.LogLevel;
import com.discordsrv.common.logging.LogAppender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -53,8 +53,8 @@ public class DependencyLoggingHandler implements LogAppender {
@Override
public void append(@Nullable String loggerName, @NotNull LogLevel logLevel, @Nullable String message,
@Nullable Throwable throwable) {
if (loggerName == null || !loggerName.startsWith("com.discordsrv.dependencies")) {
return;
if (loggerName == null) {
loggerName = "null";
}
if (message != null) {

View File

@ -16,13 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging.impl;
package com.discordsrv.common.logging.impl;
import com.discordsrv.logging.LogLevel;
import com.discordsrv.logging.Logger;
import com.discordsrv.logging.backend.LogAppender;
import com.discordsrv.logging.backend.LogFilter;
import com.discordsrv.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.LogLevel;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.LogAppender;
import com.discordsrv.common.logging.backend.LogFilter;
import com.discordsrv.common.logging.backend.LoggingBackend;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -16,13 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging.impl;
package com.discordsrv.common.logging.impl;
import com.discordsrv.logging.LogLevel;
import com.discordsrv.logging.backend.LogAppender;
import com.discordsrv.logging.backend.LogFilter;
import com.discordsrv.logging.backend.LoggingBackend;
import com.discordsrv.logging.Logger;
import com.discordsrv.common.logging.LogLevel;
import com.discordsrv.common.logging.LogAppender;
import com.discordsrv.common.logging.backend.LogFilter;
import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.Logger;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;

View File

@ -16,18 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.logging.impl;
package com.discordsrv.common.logging.impl;
import com.discordsrv.logging.LogLevel;
import com.discordsrv.logging.Logger;
import com.discordsrv.common.logging.LogLevel;
import com.discordsrv.common.logging.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SLF4JLoggerImpl implements Logger {
private final org.slf4j.Logger logger;
private final com.discordsrv.x.slf4j.Logger logger;
public SLF4JLoggerImpl(org.slf4j.Logger logger) {
public SLF4JLoggerImpl(com.discordsrv.x.slf4j.Logger logger) {
this.logger = logger;
}

View File

@ -16,12 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.proxy;
package com.discordsrv.common.module;
import com.discordsrv.common.AbstractDiscordSRV;
import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.module.type.AbstractModule;
public abstract class ProxyDiscordSRV<C extends MainConfig, CC extends ConnectionConfig> extends AbstractDiscordSRV<C, CC> {
@FunctionalInterface
public interface ModuleInitializationFunction {
AbstractModule initialize(DiscordSRV discordSRV) throws Throwable;
}

View File

@ -23,6 +23,8 @@ import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.module.type.Module;
import java.util.Map;
import java.util.Set;
@ -31,8 +33,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
public class ModuleManager {
private final Set<Module> modules = new CopyOnWriteArraySet<>();
private final Map<String, Module> moduleLookupTable = new ConcurrentHashMap<>();
private final Set<AbstractModule> modules = new CopyOnWriteArraySet<>();
private final Map<String, AbstractModule> moduleLookupTable = new ConcurrentHashMap<>();
private final DiscordSRV discordSRV;
public ModuleManager(DiscordSRV discordSRV) {
@ -42,23 +44,25 @@ public class ModuleManager {
@SuppressWarnings("unchecked")
public <T extends Module> T getModule(Class<T> moduleType) {
return (T) moduleLookupTable.computeIfAbsent(moduleType.getName(), key -> {
for (Module module : modules) {
if (moduleType.isAssignableFrom(module.getClass())) {
return module;
AbstractModule bestCandidate = null;
for (AbstractModule module : modules) {
if (moduleType.isAssignableFrom(module.getClass())
&& (bestCandidate == null || module.priority() > bestCandidate.priority())) {
bestCandidate = module;
}
}
return null;
return bestCandidate;
});
}
public void register(Module module) {
public void register(AbstractModule module) {
this.modules.add(module);
this.moduleLookupTable.put(module.getClass().getName(), module);
enable(module);
}
private void enable(Module module) {
private void enable(AbstractModule module) {
try {
module.enableModule();
} catch (Throwable t) {
@ -66,14 +70,14 @@ public class ModuleManager {
}
}
public void unregister(Module module) {
public void unregister(AbstractModule module) {
this.modules.remove(module);
this.moduleLookupTable.values().removeIf(mod -> mod == module);
disable(module);
}
private void disable(Module module) {
private void disable(AbstractModule module) {
try {
module.disable();
} catch (Throwable t) {
@ -83,14 +87,14 @@ public class ModuleManager {
@Subscribe(priority = EventPriority.EARLY)
public void onShuttingDown(DiscordSRVShuttingDownEvent event) {
for (Module module : modules) {
for (AbstractModule module : modules) {
unregister(module);
}
}
@Subscribe(priority = EventPriority.EARLY)
public void onReload(DiscordSRVReloadEvent event) {
for (Module module : modules) {
for (AbstractModule module : modules) {
// Check if the module needs to be enabled due to reload
enable(module);

View File

@ -19,14 +19,14 @@
package com.discordsrv.common.module.modules;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.discord.DiscordMessageReceivedEvent;
import com.discordsrv.api.discord.events.DiscordMessageReceivedEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.discord.api.channel.DiscordMessageChannelImpl;
import com.discordsrv.common.discord.api.message.ReceivedDiscordMessageImpl;
import com.discordsrv.common.module.Module;
import com.discordsrv.common.module.type.AbstractModule;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
public class DiscordAPIEventModule extends Module {
public class DiscordAPIEventModule extends AbstractModule {
public DiscordAPIEventModule(DiscordSRV discordSRV) {
super(discordSRV);

View File

@ -23,10 +23,9 @@ 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.event.util.EventUtil;
import com.discordsrv.common.module.Module;
import com.discordsrv.common.module.type.AbstractModule;
public class GlobalChannelLookupModule extends Module {
public class GlobalChannelLookupModule extends AbstractModule {
private final DefaultGlobalChannel defaultGlobalChannel;
@ -37,7 +36,7 @@ public class GlobalChannelLookupModule extends Module {
@Subscribe(priority = EventPriority.LATE)
public void onGameChannelLookup(GameChannelLookupEvent event) {
if (EventUtil.checkProcessor(discordSRV, event)) {
if (checkProcessor(event)) {
return;
}

View File

@ -0,0 +1,82 @@
/*
* 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.integration;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.module.type.PermissionDataProvider;
import com.discordsrv.common.module.type.PluginIntegration;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.group.Group;
import net.luckperms.api.model.user.User;
import net.luckperms.api.query.QueryOptions;
import java.util.Collection;
import java.util.UUID;
public class LuckPermsIntegration extends PluginIntegration implements PermissionDataProvider {
private LuckPerms luckPerms;
public LuckPermsIntegration(DiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public boolean isEnabled() {
try {
Class.forName("net.luckperms.api.LuckPerms");
} catch (ClassNotFoundException e) {
return false;
}
return true;
}
@Override
public void enable() {
luckPerms = LuckPermsProvider.get();
}
@Override
public void disable() {
luckPerms = null;
}
@Override
public boolean hasGroup(UUID player, String groupName) {
User user = luckPerms.getUserManager().getUser(player);
if (user == null) {
return false;
}
Collection<Group> groups = user.getInheritedGroups(QueryOptions.defaultContextualOptions());
return groups.stream().anyMatch(group -> group.getName().equalsIgnoreCase(groupName));
}
@Override
public void addGroup(UUID player, String groupName) {
}
@Override
public void removeGroup(UUID player, String groupName) {
}
}

View File

@ -0,0 +1,143 @@
/*
* 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;
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.modules;
package com.discordsrv.common.module.modules.message;
import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.component.EnhancedTextBuilder;
@ -25,25 +25,25 @@ import com.discordsrv.api.discord.api.entity.DiscordUser;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.discord.events.DiscordMessageReceivedEvent;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.discord.DiscordMessageReceivedEvent;
import com.discordsrv.api.event.events.message.receive.discord.DiscordMessageProcessingEvent;
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessingEvent;
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.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
import com.discordsrv.common.function.OrDefault;
import com.discordsrv.common.module.Module;
import com.discordsrv.common.module.type.AbstractModule;
import net.kyori.adventure.text.Component;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
import java.util.Optional;
public class DiscordToMinecraftModule extends Module {
public class DiscordToMinecraftChatModule extends AbstractModule {
public DiscordToMinecraftModule(DiscordSRV discordSRV) {
public DiscordToMinecraftChatModule(DiscordSRV discordSRV) {
super(discordSRV);
}
@ -54,30 +54,38 @@ public class DiscordToMinecraftModule extends Module {
return;
}
discordSRV.eventBus().publish(new DiscordMessageProcessingEvent(event.getMessage(), channel));
discordSRV.eventBus().publish(new DiscordChatMessageProcessingEvent(event.getMessage(), channel));
}
@Subscribe
public void onDiscordMessageReceive(DiscordMessageProcessingEvent event) {
public void onDiscordMessageReceive(DiscordChatMessageProcessingEvent event) {
if (checkCancellation(event) || checkProcessor(event)) {
return;
}
Map<GameChannel, OrDefault<BaseChannelConfig>> channels = discordSRV.channelConfig().orDefault(event.getChannel());
if (channels == null || channels.isEmpty()) {
return;
}
for (Map.Entry<GameChannel, OrDefault<BaseChannelConfig>> entry : channels.entrySet()) {
process(event, entry.getKey(), entry.getValue());
}
event.markAsProcessed();
}
private void process(DiscordChatMessageProcessingEvent event, GameChannel gameChannel, OrDefault<BaseChannelConfig> channelConfig) {
OrDefault<DiscordToMinecraftChatConfig> chatConfig = channelConfig.map(cfg -> cfg.discordToMinecraft);
if (!chatConfig.get(cfg -> cfg.enabled, true)) {
return;
}
DiscordTextChannel channel = event.getChannel();
ReceivedDiscordMessage discordMessage = event.getDiscordMessage();
DiscordUser author = discordMessage.getAuthor();
Optional<DiscordGuildMember> member = discordMessage.getMember();
boolean webhookMessage = discordMessage.isWebhookMessage();
OrDefault<Pair<GameChannel, BaseChannelConfig>> channelPair = discordSRV.channelConfig().orDefault(channel);
GameChannel gameChannel = channelPair.get(Pair::getKey);
if (gameChannel == null) {
return;
}
OrDefault<? extends BaseChannelConfig> channelConfig = channelPair.map(Pair::getValue);
OrDefault<DiscordToMinecraftChatConfig> chatConfig = channelConfig.map(cfg -> cfg.discordToMinecraft);
DiscordToMinecraftChatConfig.Ignores ignores = chatConfig.get(cfg -> cfg.ignores);
if (ignores != null) {
if (ignores.webhooks && webhookMessage) {
@ -116,7 +124,7 @@ public class DiscordToMinecraftModule extends Module {
EnhancedTextBuilder componentBuilder = discordSRV.componentFactory()
.enhancedBuilder(format)
.addContext(discordMessage, author)
.addContext(discordMessage, author, channel, channelConfig)
.addReplacement("%message%", messageComponent);
member.ifPresent(componentBuilder::addContext);

View File

@ -0,0 +1,66 @@
/*
* 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.discord.api.entity.message.ReceivedDiscordMessageCluster;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.event.bus.EventPriority;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.message.forward.game.JoinMessageForwardedEvent;
import com.discordsrv.api.event.events.message.receive.game.JoinMessageReceiveEvent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.JoinMessageConfig;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.function.OrDefault;
public class JoinMessageModule extends AbstractGameMessageModule<JoinMessageConfig> {
public JoinMessageModule(DiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public OrDefault<JoinMessageConfig> mapConfig(OrDefault<BaseChannelConfig> channelConfig) {
return channelConfig.map(cfg -> cfg.joinMessages);
}
@Override
public boolean isEnabled(OrDefault<JoinMessageConfig> config) {
return config.get(cfg -> cfg.enabled, true);
}
@Override
public SendableDiscordMessage.Builder getFormat(OrDefault<JoinMessageConfig> config) {
return config.get(cfg -> cfg.format);
}
@Override
public void postClusterToEventBus(ReceivedDiscordMessageCluster cluster) {
discordSRV.eventBus().publish(new JoinMessageForwardedEvent(cluster));
}
@Subscribe(priority = EventPriority.LAST)
public void onStatusMessageReceive(JoinMessageReceiveEvent event) {
if (checkCancellation(event) || checkProcessor(event)) {
return;
}
process(event, event.getPlayer(), event.getGameChannel());
}
}

Some files were not shown because too many files have changed in this diff Show More