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 create_pull_request: true
pull_request_title: "New Crowdin translations" pull_request_title: "New Crowdin translations"
pull_request_body: "" pull_request_body: ""
source: "source.yml" source: "source.yaml"
translation: "/%original_path%/src/main/resources/translations/%three_letters_code%.yml" translation: "/%original_path%/src/main/resources/translations/%three_letters_code%.yaml"
project_id: ${{ secrets.CROWDIN_PROJECT_ID }} project_id: ${{ secrets.CROWDIN_PROJECT_ID }}
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
env: env:

View File

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

View File

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

View File

@ -429,7 +429,7 @@ public class DiscordMessageEmbed {
@NotNull @NotNull
public Builder setFooter(@Nullable CharSequence footer, @Nullable CharSequence footerImageUrl) { public Builder setFooter(@Nullable CharSequence footer, @Nullable CharSequence footerImageUrl) {
this.footer = footer != null ? footer.toString() : null; 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; return this;
} }

View File

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

View File

@ -21,7 +21,7 @@
* SOFTWARE. * 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.DiscordDMChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel; 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 com.discordsrv.api.event.events.Event;
import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.events.GenericEvent;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
@ -52,6 +53,7 @@ public interface EventBus {
* *
* @param event the event * @param event the event
*/ */
@Blocking
void publish(@NotNull Event event); void publish(@NotNull Event event);
/** /**
@ -59,6 +61,7 @@ public interface EventBus {
* *
* @param event the event * @param event the event
*/ */
@Blocking
void publish(@NotNull GenericEvent event); 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 com.discordsrv.api.event.events.Event;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class DiscordMessageForwardedEvent implements Event { public class DiscordChatMessageForwardedEvent implements Event {
private final MinecraftComponent message; private final MinecraftComponent message;
private final GameChannel channel; private final GameChannel channel;
public DiscordMessageForwardedEvent(@NotNull MinecraftComponent message, @NotNull GameChannel channel) { public DiscordChatMessageForwardedEvent(@NotNull MinecraftComponent message, @NotNull GameChannel channel) {
this.message = message; this.message = message;
this.channel = channel; this.channel = channel;
} }

View File

@ -21,4 +21,14 @@
* SOFTWARE. * 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. * 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 com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessageCluster;
import org.jetbrains.annotations.NotNull; 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); super(discordMessage);
} }
} }

View File

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

View File

@ -23,7 +23,6 @@
package com.discordsrv.api.event.events.message.receive.game; 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.component.MinecraftComponent;
import com.discordsrv.api.event.events.Cancellable; import com.discordsrv.api.event.events.Cancellable;
import com.discordsrv.api.event.events.Processable; import com.discordsrv.api.event.events.Processable;
@ -32,30 +31,22 @@ import org.jetbrains.annotations.NotNull;
public abstract class AbstractGameMessageReceiveEvent implements Processable, Cancellable { public abstract class AbstractGameMessageReceiveEvent implements Processable, Cancellable {
private final MinecraftComponent message; private final MinecraftComponent message;
private GameChannel gameChannel;
private boolean cancelled; private boolean cancelled;
private boolean processed; private boolean processed;
public AbstractGameMessageReceiveEvent(@NotNull MinecraftComponent message, @NotNull GameChannel gameChannel, boolean cancelled) { public AbstractGameMessageReceiveEvent(
@NotNull MinecraftComponent message,
boolean cancelled
) {
this.message = message; this.message = message;
this.gameChannel = gameChannel;
this.cancelled = cancelled; this.cancelled = cancelled;
} }
@NotNull @NotNull
public MinecraftComponent message() { public MinecraftComponent getMessage() {
return message; return message;
} }
@NotNull
public GameChannel getGameChannel() {
return gameChannel;
}
public void setGameChannel(@NotNull GameChannel gameChannel) {
this.gameChannel = gameChannel;
}
@Override @Override
public boolean isCancelled() { public boolean isCancelled() {
return cancelled; return cancelled;
@ -73,6 +64,9 @@ public abstract class AbstractGameMessageReceiveEvent implements Processable, Ca
@Override @Override
public void markAsProcessed() { public void markAsProcessed() {
if (isCancelled()) {
throw new IllegalStateException("Cannot process cancelled event");
}
this.processed = true; 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.component.MinecraftComponent;
import com.discordsrv.api.event.events.PlayerEvent; import com.discordsrv.api.event.events.PlayerEvent;
import com.discordsrv.api.player.DiscordSRVPlayer; 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 final DiscordSRVPlayer player;
private GameChannel gameChannel;
public ChatMessageProcessingEvent(DiscordSRVPlayer player, MinecraftComponent message, GameChannel gameChannel, boolean cancelled) { public GameChatMessageReceiveEvent(
super(message, gameChannel, cancelled); @NotNull DiscordSRVPlayer player,
@NotNull GameChannel gameChannel,
@NotNull MinecraftComponent message,
boolean cancelled) {
super(message, cancelled);
this.player = player; this.player = player;
this.gameChannel = gameChannel;
}
public GameChannel getGameChannel() {
return gameChannel;
}
public void setGameChannel(GameChannel gameChannel) {
this.gameChannel = gameChannel;
} }
@Override @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 { 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() {} private ResultMappers() {}
public static boolean isPlainComponentContext() { public static boolean isPlainContext() {
return PLAIN_COMPONENTS.get(); return PLAIN.get();
} }
/** /**
* Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s * Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s
* will replace {@link com.discordsrv.api.component.MinecraftComponent}s * will use plain text without Discord formatting (instead of converting to Discord formatting).
* as plain without formatting (instead of converting to Discord formatting).
* @param runnable a task that will be executed immediately * @param runnable a task that will be executed immediately
*/ */
public static void runInPlainComponentContext(Runnable runnable) { public static void runInPlainContext(Runnable runnable) {
getInPlainComponentContext(() -> { getInPlainContext(() -> {
runnable.run(); runnable.run();
return null; return null;
}); });
@ -52,15 +51,14 @@ public final class ResultMappers {
/** /**
* Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s * Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s
* will replace {@link com.discordsrv.api.component.MinecraftComponent}s * will use plain text without Discord formatting (instead of converting to Discord formatting).
* as plain without formatting (instead of converting to Discord formatting).
* @param supplier a supplier that will be executed immediately * @param supplier a supplier that will be executed immediately
* @return the output of the supplier provided as parameter * @return the output of the supplier provided as parameter
*/ */
public static <T> T getInPlainComponentContext(Supplier<T> supplier) { public static <T> T getInPlainContext(Supplier<T> supplier) {
PLAIN_COMPONENTS.set(true); PLAIN.set(true);
T output = supplier.get(); T output = supplier.get();
PLAIN_COMPONENTS.set(false); PLAIN.set(false);
return output; return output;
} }
} }

View File

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

View File

@ -1,5 +1,5 @@
plugins { 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 'org.cadixdev.licenser' version '0.6.0' apply false
id 'io.freefair.lombok' version '5.3.3.3' apply false id 'io.freefair.lombok' version '5.3.3.3' apply false
id 'net.kyori.blossom' version '1.2.0' apply false id 'net.kyori.blossom' version '1.2.0' apply false

View File

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

View File

@ -25,11 +25,13 @@ import com.discordsrv.bukkit.config.manager.BukkitConfigManager;
import com.discordsrv.bukkit.config.manager.BukkitConnectionConfigManager; import com.discordsrv.bukkit.config.manager.BukkitConnectionConfigManager;
import com.discordsrv.bukkit.console.BukkitConsole; import com.discordsrv.bukkit.console.BukkitConsole;
import com.discordsrv.bukkit.listener.BukkitChatListener; 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.player.BukkitPlayerProvider;
import com.discordsrv.bukkit.scheduler.BukkitScheduler; import com.discordsrv.bukkit.scheduler.BukkitScheduler;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
import com.discordsrv.common.config.manager.MainConfigManager; 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 com.discordsrv.common.server.ServerDiscordSRV;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.Server; import org.bukkit.Server;
@ -133,5 +135,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
// Register listeners // Register listeners
server().getPluginManager().registerEvents(BukkitChatListener.get(this), plugin()); 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; package com.discordsrv.bukkit;
import com.discordsrv.common.dependency.InitialDependencyLoader; import com.discordsrv.common.dependency.InitialDependencyLoader;
import com.discordsrv.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.logging.impl.JavaLoggerImpl; import com.discordsrv.common.logging.impl.JavaLoggerImpl;
import dev.vankka.mcdependencydownload.bukkit.bootstrap.BukkitBootstrap; import dev.vankka.mcdependencydownload.bukkit.bootstrap.BukkitBootstrap;
import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader; import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader;
import org.bukkit.plugin.java.JavaPlugin; 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; package com.discordsrv.bukkit.config.main;
import com.discordsrv.common.config.main.MainConfig; 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; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable @ConfigSerializable
public class BukkitConfig extends MainConfig { 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.bukkit.config.main.BukkitConfig;
import com.discordsrv.common.DiscordSRV; 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) { public BukkitConfigManager(DiscordSRV discordSRV) {
super(discordSRV); super(discordSRV);

View File

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

View File

@ -19,8 +19,9 @@
package com.discordsrv.bukkit.listener; package com.discordsrv.bukkit.listener;
import com.discordsrv.api.component.MinecraftComponent; 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.BukkitDiscordSRV;
import com.discordsrv.bukkit.component.util.PaperComponentUtil;
import com.discordsrv.common.channel.DefaultGlobalChannel; import com.discordsrv.common.channel.DefaultGlobalChannel;
import com.discordsrv.common.component.util.ComponentUtil; import com.discordsrv.common.component.util.ComponentUtil;
import io.papermc.paper.event.player.AsyncChatEvent; 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.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public abstract class BukkitChatListener implements Listener { public abstract class BukkitChatListener implements Listener {
public static BukkitChatListener get(BukkitDiscordSRV discordSRV) { public static BukkitChatListener get(BukkitDiscordSRV discordSRV) {
// TODO: config option // TODO: config option
//noinspection ConstantConditions //noinspection ConstantConditions,PointlessBooleanExpression
if (1 == 2) { if (1 == 2 && PaperComponentUtil.IS_PAPER_ADVENTURE) {
try { return new Paper(discordSRV);
Class.forName("io.papermc.paper.event.player.AsyncChatEvent");
return new Paper(discordSRV);
} catch (ClassNotFoundException ignored) {}
} }
return new Bukkit(discordSRV); return new Bukkit(discordSRV);
@ -56,14 +50,14 @@ public abstract class BukkitChatListener implements Listener {
} }
protected void publishEvent(Player player, MinecraftComponent component, boolean cancelled) { protected void publishEvent(Player player, MinecraftComponent component, boolean cancelled) {
discordSRV.eventBus().publish( discordSRV.scheduler().run(() -> discordSRV.eventBus().publish(
new ChatMessageProcessingEvent( new GameChatMessageReceiveEvent(
discordSRV.playerProvider().player(player), discordSRV.playerProvider().player(player),
component,
new DefaultGlobalChannel(discordSRV), new DefaultGlobalChannel(discordSRV),
component,
cancelled cancelled
) )
); ));
} }
static class Bukkit extends BukkitChatListener { static class Bukkit extends BukkitChatListener {
@ -84,39 +78,13 @@ public abstract class BukkitChatListener implements Listener {
static class Paper extends BukkitChatListener { 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) { public Paper(BukkitDiscordSRV discordSRV) {
super(discordSRV); super(discordSRV);
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onAsyncChat(AsyncChatEvent event) { public void onAsyncChat(AsyncChatEvent event) {
MinecraftComponent component = discordSRV.componentFactory().empty(); MinecraftComponent component = PaperComponentUtil.getComponent(discordSRV, event, "message");
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);
publishEvent( publishEvent(
event.getPlayer(), event.getPlayer(),
component, 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") @SuppressWarnings("NullabilityProblems")
@Override @Override
public String getUsername() { public String username() {
return offlinePlayer.getName(); return offlinePlayer.getName();
} }

View File

@ -82,7 +82,7 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
@SuppressWarnings("deprecation") // Paper @SuppressWarnings("deprecation") // Paper
@Override @Override
public @NotNull Component getDisplayName() { public @NotNull Component displayName() {
if (DISPLAY_NAME_METHOD != null) { if (DISPLAY_NAME_METHOD != null) {
try { try {
return ComponentUtil.fromUnrelocated(DISPLAY_NAME_METHOD.invoke(player)); 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.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
import com.discordsrv.common.config.manager.MainConfigManager; import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.proxy.ProxyDiscordSRV; import com.discordsrv.proxy.ProxyDiscordSRV;
import com.discordsrv.common.scheduler.StandardScheduler; import com.discordsrv.common.scheduler.StandardScheduler;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences; import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;

View File

@ -19,8 +19,8 @@
package com.discordsrv.bungee; package com.discordsrv.bungee;
import com.discordsrv.common.dependency.InitialDependencyLoader; import com.discordsrv.common.dependency.InitialDependencyLoader;
import com.discordsrv.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.logging.impl.JavaLoggerImpl; import com.discordsrv.common.logging.impl.JavaLoggerImpl;
import dev.vankka.mcdependencydownload.bungee.bootstrap.BungeeBootstrap; import dev.vankka.mcdependencydownload.bungee.bootstrap.BungeeBootstrap;
import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader; import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader;
import net.md_5.bungee.api.plugin.Plugin; 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.bungee.BungeeDiscordSRV;
import com.discordsrv.common.console.Console; import com.discordsrv.common.console.Console;
import com.discordsrv.logging.backend.LoggingBackend; import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.logging.impl.JavaLoggerImpl; import com.discordsrv.common.logging.impl.JavaLoggerImpl;
import net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View File

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

View File

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

View File

@ -16,9 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;

View File

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

View File

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

View File

@ -18,11 +18,10 @@
package org.slf4j.impl; package org.slf4j.impl;
import com.discordsrv.logging.adapter.DependencyLoggerAdapter; import com.discordsrv.common.logging.adapter.DependencyLoggerAdapter;
import org.slf4j.ILoggerFactory; import org.slf4j.ILoggerFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -32,6 +31,6 @@ public class DiscordSRVLoggerFactory implements ILoggerFactory {
@Override @Override
public Logger getLogger(String s) { 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-yaml:' + rootProject.configurateVersion
runtimeDownloadApi 'org.spongepowered:configurate-hocon:' + 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 // Adventure, MCDiscordReserializer, EnhancedLegacyText
runtimeDownloadApi 'net.kyori:adventure-api:' + rootProject.adventureVersion runtimeDownloadApi 'net.kyori:adventure-api:' + rootProject.adventureVersion
runtimeDownloadApi 'net.kyori:adventure-text-serializer-plain:' + rootProject.adventureVersion runtimeDownloadApi 'net.kyori:adventure-text-serializer-plain:' + rootProject.adventureVersion
@ -51,6 +55,9 @@ dependencies {
// Database Drivers // Database Drivers
h2Driver 'com.h2database:h2:1.4.200' h2Driver 'com.h2database:h2:1.4.200'
mysqlDriver 'mysql:mysql-connector-java:8.0.25' mysqlDriver 'mysql:mysql-connector-java:8.0.25'
// Integrations
compileOnlyApi 'net.luckperms:api:5.3'
} }
processResources { 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.AbstractDiscordSRV;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig; 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.player.ServerPlayerProvider;
import com.discordsrv.common.server.scheduler.ServerScheduler; import com.discordsrv.common.server.scheduler.ServerScheduler;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -36,6 +38,19 @@ public abstract class ServerDiscordSRV<C extends MainConfig, CC extends Connecti
@Override @Override
public abstract @NotNull ServerPlayerProvider<?> playerProvider(); 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() { public final CompletableFuture<Void> invokeServerStarted() {
return invokeLifecycle(this::serverStarted, "Failed to enable", true); 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/>. * 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.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment; import org.spongepowered.configurate.objectmapping.meta.Comment;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ConfigSerializable @ConfigSerializable
public class ChannelConfig extends BaseChannelConfig { public class ServerChannelConfig extends ServerBaseChannelConfig implements IChannelConfig {
public static final String DEFAULT_KEY = "default"; public ServerChannelConfig() {
initialize();
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) {}
}
} }
@Comment("The channels this in-game channel will forward to in Discord") @Comment(CHANNEL_IDS_COMMENT)
public List<Long> channelIds = new ArrayList<>(); 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.discord.details.DiscordConnectionDetailsImpl;
import com.discordsrv.common.event.bus.EventBusImpl; import com.discordsrv.common.event.bus.EventBusImpl;
import com.discordsrv.common.function.CheckedRunnable; import com.discordsrv.common.function.CheckedRunnable;
import com.discordsrv.common.logging.DependencyLoggingHandler; import com.discordsrv.common.logging.dependency.DependencyLoggingHandler;
import com.discordsrv.common.module.Module; 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.ModuleManager;
import com.discordsrv.common.module.modules.DiscordAPIEventModule; 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.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.ComponentResultStringifier;
import com.discordsrv.common.placeholder.PlaceholderServiceImpl; import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
import com.discordsrv.common.placeholder.context.GlobalTextHandlingContext; 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 net.dv8tion.jda.api.JDA;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.OverridingMethodsMustInvokeSuper; import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -167,12 +171,12 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
} }
@Override @Override
public void registerModule(Module module) { public void registerModule(AbstractModule module) {
moduleManager.register(module); moduleManager.register(module);
} }
@Override @Override
public void unregisterModule(Module module) { public void unregisterModule(AbstractModule module) {
moduleManager.unregister(module); moduleManager.unregister(module);
} }
@ -264,13 +268,19 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
// Register modules // Register modules
moduleManager = new ModuleManager(this); moduleManager = new ModuleManager(this);
for (Module module : Arrays.asList( for (ModuleInitializationFunction function : new ModuleInitializationFunction[]{
new DiscordAPIEventModule(this), LuckPermsIntegration::new,
new DiscordToMinecraftModule(this),
new GlobalChannelLookupModule(this), DiscordToMinecraftChatModule::new,
new MinecraftToDiscordModule(this) JoinMessageModule::new,
)) { LeaveMessageModule::new,
registerModule(module); 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.console.Console;
import com.discordsrv.common.discord.api.DiscordAPIImpl; import com.discordsrv.common.discord.api.DiscordAPIImpl;
import com.discordsrv.common.discord.connection.DiscordConnectionManager; 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.placeholder.PlaceholderServiceImpl;
import com.discordsrv.common.player.provider.AbstractPlayerProvider; import com.discordsrv.common.player.provider.AbstractPlayerProvider;
import com.discordsrv.common.scheduler.Scheduler; 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 com.github.benmanes.caffeine.cache.Caffeine;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -80,8 +81,8 @@ public interface DiscordSRV extends DiscordSRVApi {
// Modules // Modules
<T extends Module> T getModule(Class<T> moduleType); <T extends Module> T getModule(Class<T> moduleType);
void registerModule(Module module); void registerModule(AbstractModule module);
void unregisterModule(Module module); void unregisterModule(AbstractModule module);
Locale locale(); Locale locale();
void setStatus(Status status); 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.channel.GameChannelLookupEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent; import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig; 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.discordsrv.common.function.OrDefault;
import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache; 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.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -42,7 +41,7 @@ public class ChannelConfigHelper {
private final DiscordSRV discordSRV; private final DiscordSRV discordSRV;
private final LoadingCache<String, GameChannel> nameToChannelCache; 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) { public ChannelConfigHelper(DiscordSRV discordSRV) {
this.discordSRV = discordSRV; this.discordSRV = discordSRV;
@ -74,14 +73,15 @@ public class ChannelConfigHelper {
return; 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()) { for (Map.Entry<String, BaseChannelConfig> entry : channels().entrySet()) {
String channelName = entry.getKey(); String channelName = entry.getKey();
BaseChannelConfig value = entry.getValue(); BaseChannelConfig value = entry.getValue();
if (value instanceof ChannelConfig) { if (value instanceof IChannelConfig) {
ChannelConfig channelConfig = (ChannelConfig) value; IChannelConfig channelConfig = (IChannelConfig) value;
for (long channelId : channelConfig.channelIds) { for (long channelId : channelConfig.ids()) {
newMap.put(channelId, Pair.of(channelName, channelConfig)); newMap.computeIfAbsent(channelId, key -> new LinkedHashMap<>())
.put(channelName, value);
} }
} }
} }
@ -96,22 +96,27 @@ public class ChannelConfigHelper {
return discordSRV.config().channels; 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) { public OrDefault<BaseChannelConfig> orDefault(GameChannel gameChannel) {
return orDefault(gameChannel.getOwnerName(), gameChannel.getChannelName()); 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) { public OrDefault<BaseChannelConfig> orDefault(String ownerName, String channelName) {
BaseChannelConfig defaultConfig = getDefault(); BaseChannelConfig defaultConfig = getDefault();
@ -144,21 +149,39 @@ public class ChannelConfigHelper {
return gameChannel != null ? get(gameChannel) : null; return gameChannel != null ? get(gameChannel) : null;
} }
public Pair<GameChannel, BaseChannelConfig> getDiscordResolved(DiscordTextChannel channel) { public Map<GameChannel, OrDefault<BaseChannelConfig>> orDefault(DiscordTextChannel discordTextChannel) {
Pair<String, ? extends BaseChannelConfig> pair = getDiscord(channel); BaseChannelConfig defaultConfig = getDefault();
if (pair == null) {
return null;
}
GameChannel gameChannel = nameToChannelCache.get(pair.getKey()); Map<GameChannel, OrDefault<BaseChannelConfig>> channels = new HashMap<>();
if (gameChannel == null) { for (Map.Entry<GameChannel, BaseChannelConfig> entry : getDiscordResolved(discordTextChannel).entrySet()) {
return null; channels.put(
entry.getKey(),
new OrDefault<>(entry.getValue(), defaultConfig)
);
} }
return channels;
return Pair.of(gameChannel, pair.getValue());
} }
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) { synchronized (discordToConfigMap) {
return discordToConfigMap.get(channel.getId()); 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.DiscordGuild;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember; import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
import com.discordsrv.api.discord.api.entity.guild.DiscordRole; 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.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil; import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig; import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
@ -47,7 +47,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
} }
public static void runInContext( public static void runInContext(
DiscordMessageProcessingEvent event, DiscordChatMessageProcessingEvent event,
OrDefault<DiscordToMinecraftChatConfig> config, OrDefault<DiscordToMinecraftChatConfig> config,
Runnable runnable Runnable runnable
) { ) {
@ -58,7 +58,7 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
} }
public static <T> T getWithContext( public static <T> T getWithContext(
DiscordMessageProcessingEvent event, DiscordChatMessageProcessingEvent event,
OrDefault<DiscordToMinecraftChatConfig> config, OrDefault<DiscordToMinecraftChatConfig> config,
Supplier<T> supplier Supplier<T> supplier
) { ) {
@ -149,10 +149,10 @@ public class DiscordSRVMinecraftRenderer extends DefaultMinecraftRenderer {
private static class Context { private static class Context {
private final DiscordMessageProcessingEvent event; private final DiscordChatMessageProcessingEvent event;
private final OrDefault<DiscordToMinecraftChatConfig> config; private final OrDefault<DiscordToMinecraftChatConfig> config;
public Context(DiscordMessageProcessingEvent event, OrDefault<DiscordToMinecraftChatConfig> config) { public Context(DiscordChatMessageProcessingEvent event, OrDefault<DiscordToMinecraftChatConfig> config) {
this.event = event; this.event = event;
this.config = config; this.config = config;
} }

View File

@ -22,9 +22,13 @@ import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentAdapter; import com.discordsrv.api.component.MinecraftComponentAdapter;
import com.discordsrv.common.component.MinecraftComponentImpl; import com.discordsrv.common.component.MinecraftComponentImpl;
import net.kyori.adventure.text.Component; 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.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import java.util.Collection;
/** /**
* An util class for {@link Component}s and {@link MinecraftComponent}s. * An util class for {@link Component}s and {@link MinecraftComponent}s.
*/ */
@ -65,4 +69,19 @@ public final class ComponentUtil {
.setComponent(unrelocatedAdventure); .setComponent(unrelocatedAdventure);
return fromAPI(component); 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.Config;
import com.discordsrv.common.config.annotation.DefaultOnly; import com.discordsrv.common.config.annotation.DefaultOnly;
import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig; import com.discordsrv.common.config.main.channels.base.ChannelConfig;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import java.util.LinkedHashMap; import java.util.*;
import java.util.Map;
@ConfigSerializable @ConfigSerializable
public class MainConfig implements Config { public class MainConfig implements Config {
@ -40,6 +39,8 @@ public class MainConfig implements Config {
@DefaultOnly("default") @DefaultOnly("default")
public Map<String, BaseChannelConfig> channels = new LinkedHashMap<String, BaseChannelConfig>() {{ public Map<String, BaseChannelConfig> channels = new LinkedHashMap<String, BaseChannelConfig>() {{
put("global", new ChannelConfig()); 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; 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.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment; import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -30,14 +31,24 @@ import java.util.regex.Pattern;
@ConfigSerializable @ConfigSerializable
public class DiscordToMinecraftChatConfig { public class DiscordToMinecraftChatConfig {
@Comment("The Discord to Minecraft message format for regular users") @Comment("Is Discord to Minecraft chat forwarding enabled")
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%"; 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)") @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) // TODO: more info on regex pairs (String#replaceAll)
@Comment("Regex filters for Discord message contents (this is the %message% part of the \"format\" option)") @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<>(); public Map<Pattern, String> contentRegexFilters = new LinkedHashMap<>();
@Comment("Users, bots and webhooks to ignore") @Comment("Users, bots and webhooks to ignore")
@ -56,7 +67,7 @@ public class DiscordToMinecraftChatConfig {
public boolean bots = false; public boolean bots = false;
@Comment("If webhooks should be ignored") @Comment("If webhooks should be ignored")
public boolean webhooks = false; public boolean webhooks = true;
@ConfigSerializable @ConfigSerializable
public static class IDs { public static class IDs {
@ -82,11 +93,14 @@ public class DiscordToMinecraftChatConfig {
public static class Format { public static class Format {
@Comment("The format shown in-game") @Comment("The format shown in-game")
@Untranslated(Untranslated.Type.VALUE)
public String format = ""; public String format = "";
@Comment("The format when the entity is deleted or can't be looked up") @Comment("The format when the entity is deleted or can't be looked up")
@Untranslated(Untranslated.Type.VALUE)
public String unknownFormat = ""; public String unknownFormat = "";
@SuppressWarnings("unused") // Configurate
public Format() {} public Format() {}
public Format(String format, String unknownFormat) { 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; package com.discordsrv.common.config.main.channels;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage; 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.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment; import org.spongepowered.configurate.objectmapping.meta.Comment;
@ -29,10 +30,14 @@ import java.util.regex.Pattern;
@ConfigSerializable @ConfigSerializable
public class MinecraftToDiscordChatConfig { 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() public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
.setWebhookUsername("%player_display_name%") .setWebhookUsername("%player_display_name%")
.setWebhookAvatarUrl("%player_avatar_url%") .setWebhookAvatarUrl("%player_avatar_url%")
.setContent("%message%");// TODO .setContent("%message%");
// TODO: more info on regex pairs (String#replaceAll) // TODO: more info on regex pairs (String#replaceAll)
@Comment("Regex filters for Minecraft message contents (this is the %message% part of the \"format\" option)") @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/>. * 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.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.ObjectMapper; import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer; import org.spongepowered.configurate.serialize.TypeSerializer;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
@ConfigSerializable @ConfigSerializable
public class BaseChannelConfig { public class ChannelConfig extends BaseChannelConfig implements IChannelConfig {
public MinecraftToDiscordChatConfig minecraftToDiscord = new MinecraftToDiscordChatConfig(); public ChannelConfig() {
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig(); 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> { public static class Serializer implements TypeSerializer<BaseChannelConfig> {
private final ObjectMapper.Factory mapperFactory; 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.mapperFactory = mapperFactory;
this.baseConfigClass = baseConfigClass;
this.configClass = configClass;
} }
@Override @Override
public BaseChannelConfig deserialize(Type type, ConfigurationNode node) throws SerializationException { public BaseChannelConfig deserialize(Type type, ConfigurationNode node) throws SerializationException {
return (BaseChannelConfig) mapperFactory.asTypeSerializer() return (BaseChannelConfig) mapperFactory.asTypeSerializer()
.deserialize( .deserialize(
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? BaseChannelConfig.class : ChannelConfig.class, ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
node node
); );
} }
@ -60,7 +74,7 @@ public class BaseChannelConfig {
} }
mapperFactory.asTypeSerializer().serialize( mapperFactory.asTypeSerializer().serialize(
ChannelConfig.DEFAULT_KEY.equals(node.key()) ? BaseChannelConfig.class : ChannelConfig.class, ChannelConfig.DEFAULT_KEY.equals(node.key()) ? baseConfigClass : configClass,
obj, obj,
node 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; package com.discordsrv.common.config.manager.loader;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.loader.AbstractConfigurationLoader; import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
import org.spongepowered.configurate.yaml.NodeStyle; import org.spongepowered.configurate.yaml.NodeStyle;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader; import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
public interface YamlConfigLoaderProvider extends ConfigLoaderProvider<YamlConfigurationLoader> { public interface YamlConfigLoaderProvider extends ConfigLoaderProvider<YamlConfigurationLoader> {
default ConfigurationOptions defaultOptions() {
return ConfigurationOptions.defaults();
}
default NodeStyle nodeStyle() { default NodeStyle nodeStyle() {
return NodeStyle.BLOCK; 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.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.annotation.DefaultOnly; 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.manager.loader.ConfigLoaderProvider;
import com.discordsrv.common.config.serializer.ColorSerializer; import com.discordsrv.common.config.serializer.ColorSerializer;
import com.discordsrv.common.config.serializer.DiscordMessageEmbedSerializer; 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.ConfigurationOptions;
import org.spongepowered.configurate.loader.AbstractConfigurationLoader; import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
import org.spongepowered.configurate.objectmapping.ObjectMapper; import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.util.NamingScheme; import org.spongepowered.configurate.util.NamingScheme;
@ -44,6 +48,7 @@ import org.spongepowered.configurate.util.NamingSchemes;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -88,18 +93,30 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
protected abstract String fileName(); protected abstract String fileName();
public ChannelConfig.Serializer getChannelConfigSerializer( ObjectMapper.Factory mapperFactory) {
return new ChannelConfig.Serializer(mapperFactory, BaseChannelConfig.class, ChannelConfig.class);
}
public ConfigurationOptions defaultOptions() { public ConfigurationOptions defaultOptions() {
return ConfigurationOptions.defaults() return ConfigurationOptions.defaults()
.shouldCopyDefaults(false) .shouldCopyDefaults(false)
.implicitInitialization(false) .implicitInitialization(false)
.serializers(builder -> { .serializers(builder -> {
ObjectMapper.Factory objectMapper = configObjectMapper(); ObjectMapper.Factory objectMapper = configObjectMapper();
builder.register(BaseChannelConfig.class, getChannelConfigSerializer(objectMapper));
builder.register(Color.class, new ColorSerializer()); builder.register(Color.class, new ColorSerializer());
builder.register(Pattern.class, new PatternSerializer()); 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.Builder.class, new DiscordMessageEmbedSerializer(NAMING_SCHEME));
builder.register(DiscordMessageEmbed.Field.class, new DiscordMessageEmbedSerializer.FieldSerializer(NAMING_SCHEME)); builder.register(DiscordMessageEmbed.Field.class, new DiscordMessageEmbedSerializer.FieldSerializer(NAMING_SCHEME));
builder.register(SendableDiscordMessage.Builder.class, new SendableDiscordMessageSerializer(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(); 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() 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(); return objectMapperBuilder();
} }
@ -164,9 +189,13 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
} }
private CommentedConfigurationNode getDefault(T defaultConfig, boolean cleanMapper) throws SerializationException { 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()); CommentedConfigurationNode node = CommentedConfigurationNode.root(defaultNodeOptions());
(cleanMapper ? defaultObjectMapper() : configObjectMapper()) mapperFactory.get((Class<T>) defaultConfig.getClass()).save(defaultConfig, node);
.get(defaultConfig.getClass()).load(node);
return node; return node;
} }
@ -175,6 +204,10 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
return null; return null;
} }
public CommentedConfigurationNode getDefaultNode(ObjectMapper.Factory mapperFactory) throws ConfigurateException {
return getDefault(createConfiguration(), mapperFactory);
}
@Override @Override
public void load() throws ConfigException { public void load() throws ConfigException {
reload(); reload();

View File

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

View File

@ -54,6 +54,6 @@ public class ColorSerializer implements TypeSerializer<Color> {
if (obj == null) { if (obj == null) {
return; 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.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.List;
public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMessageEmbed.Builder> { 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); Color color = node.node(map("Color")).get(Color.class);
builder.setColor(color != null ? color.rgb() : Role.DEFAULT_COLOR_RAW); builder.setColor(color != null ? color.rgb() : Role.DEFAULT_COLOR_RAW);
ConfigurationNode author = node.node("Author"); ConfigurationNode author = node.node(map("Author"));
builder.setAuthor( builder.setAuthor(
author.node(map("Name")).getString(), author.node(map("Name")).getString(),
author.node(map("Url")).getString(), author.node(map("Url")).getString(),
@ -65,7 +66,7 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
title.node(map("Url")).getString()); title.node(map("Url")).getString());
builder.setDescription(node.node(map("Description")).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); builder.addField(field);
} }
@ -77,7 +78,7 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
ConfigurationNode footer = node.node(map("Footer")); ConfigurationNode footer = node.node(map("Footer"));
builder.setFooter( builder.setFooter(
footer.node(map("Text")).getString(), 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; return builder;
} }
@ -102,7 +103,10 @@ public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMess
title.node(map("Url")).set(obj.getTitleUrl()); title.node(map("Url")).set(obj.getTitleUrl());
node.node(map("Description")).set(obj.getDescription()); 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("ThumbnailUrl")).set(obj.getThumbnailUrl());
node.node(map("ImageUrl")).set(obj.getImageUrl()); node.node(map("ImageUrl")).set(obj.getImageUrl());

View File

@ -19,7 +19,7 @@
package com.discordsrv.common.console; package com.discordsrv.common.console;
import com.discordsrv.common.command.game.sender.ICommandSender; 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 { public interface Console extends ICommandSender {

View File

@ -18,7 +18,7 @@
package com.discordsrv.common.dependency; package com.discordsrv.common.dependency;
import com.discordsrv.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.scheduler.threadfactory.CountingForkJoinWorkerThreadFactory; import com.discordsrv.common.scheduler.threadfactory.CountingForkJoinWorkerThreadFactory;
import dev.vankka.dependencydownload.classpath.ClasspathAppender; 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.NotReadyException;
import com.discordsrv.api.discord.api.exception.RestErrorResponseException; import com.discordsrv.api.discord.api.exception.RestErrorResponseException;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig; import com.discordsrv.common.config.main.channels.base.IChannelConfig;
import com.discordsrv.common.discord.api.channel.DiscordDMChannelImpl; import com.discordsrv.common.discord.api.channel.DiscordDMChannelImpl;
import com.discordsrv.common.discord.api.channel.DiscordTextChannelImpl; import com.discordsrv.common.discord.api.channel.DiscordTextChannelImpl;
import com.discordsrv.common.discord.api.guild.DiscordGuildImpl; import com.discordsrv.common.discord.api.guild.DiscordGuildImpl;
@ -208,7 +208,7 @@ public class DiscordAPIImpl implements DiscordAPI {
}).thenApply(webhook -> }).thenApply(webhook ->
WebhookClientBuilder.fromJDA(webhook) WebhookClientBuilder.fromJDA(webhook)
.setHttpClient(jda.getHttpClient()) .setHttpClient(jda.getHttpClient())
.setExecutorService(discordSRV.scheduler().executor()) .setExecutorService(discordSRV.scheduler().scheduledExecutor())
.build() .build()
); );
} }
@ -218,8 +218,8 @@ public class DiscordAPIImpl implements DiscordAPI {
private boolean isConfiguredChannel(Long channelId) { private boolean isConfiguredChannel(Long channelId) {
for (BaseChannelConfig config : discordSRV.config().channels.values()) { for (BaseChannelConfig config : discordSRV.config().channels.values()) {
if (config instanceof ChannelConfig if (config instanceof IChannelConfig
&& ((ChannelConfig) config).channelIds.contains(channelId)) { && ((IChannelConfig) config).ids().contains(channelId)) {
return true; 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.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder; import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.discord.api.DiscordUserImpl; import com.discordsrv.common.discord.api.DiscordUserImpl;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -107,20 +107,19 @@ public class DiscordGuildMemberImpl extends DiscordUserImpl implements DiscordGu
return null; return null;
} }
@Placeholder("user_roles_") @Placeholder("user_roles")
public Component _allRoles(@PlaceholderRemainder String suffix) { public Component _allRoles(@PlaceholderRemainder String suffix) {
if (suffix.startsWith("_")) {
suffix = suffix.substring(1);
} else if (!suffix.isEmpty()) {
return null;
}
List<Component> components = new ArrayList<>(); List<Component> components = new ArrayList<>();
for (DiscordRole role : getRoles()) { for (DiscordRole role : getRoles()) {
components.add(Component.text(role.getName()).color(TextColor.color(role.getColor().rgb()))); components.add(Component.text(role.getName()).color(TextColor.color(role.getColor().rgb())));
} }
TextComponent.Builder builder = Component.text(); return ComponentUtil.join(Component.text(suffix), components);
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();
} }
} }

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.entity.message.impl.SendableDiscordMessageImpl;
import com.discordsrv.api.discord.api.exception.RestErrorResponseException; import com.discordsrv.api.discord.api.exception.RestErrorResponseException;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder;
import com.discordsrv.common.DiscordSRV; 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.DiscordUserImpl;
import com.discordsrv.common.discord.api.channel.DiscordMessageChannelImpl; import com.discordsrv.common.discord.api.channel.DiscordMessageChannelImpl;
import com.discordsrv.common.discord.api.guild.DiscordGuildMemberImpl; 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.Member;
import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.requests.ErrorResponse;
import net.kyori.adventure.text.Component; 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 org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@ -287,18 +289,24 @@ public class ReceivedDiscordMessageImpl extends SendableDiscordMessageImpl imple
// //
@Placeholder("message_attachments") @Placeholder("message_attachments")
public Component _attachments() { public Component _attachments(OrDefault<BaseChannelConfig> config, @PlaceholderRemainder String suffix) {
// TODO: customizable if (suffix.startsWith("_")) {
suffix = suffix.substring(1);
TextComponent.Builder builder = Component.text(); } else if (!suffix.isEmpty()) {
for (Attachment attachment : attachments) { return null;
builder.append(
Component.text()
.content("[" + attachment.fileName() + "]")
.clickEvent(ClickEvent.openUrl(attachment.url()))
)
.append(Component.text(" "));
} }
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/>. * 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;

View File

@ -16,7 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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 { public interface LoggingBackend {

View File

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

View File

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

View File

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

View File

@ -16,18 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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.common.logging.LogLevel;
import com.discordsrv.logging.Logger; import com.discordsrv.common.logging.Logger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class SLF4JLoggerImpl implements Logger { 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; this.logger = logger;
} }

View File

@ -16,12 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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.DiscordSRV;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.config.main.MainConfig;
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.DiscordSRVReloadEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent; import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.common.DiscordSRV; 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.Map;
import java.util.Set; import java.util.Set;
@ -31,8 +33,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
public class ModuleManager { public class ModuleManager {
private final Set<Module> modules = new CopyOnWriteArraySet<>(); private final Set<AbstractModule> modules = new CopyOnWriteArraySet<>();
private final Map<String, Module> moduleLookupTable = new ConcurrentHashMap<>(); private final Map<String, AbstractModule> moduleLookupTable = new ConcurrentHashMap<>();
private final DiscordSRV discordSRV; private final DiscordSRV discordSRV;
public ModuleManager(DiscordSRV discordSRV) { public ModuleManager(DiscordSRV discordSRV) {
@ -42,23 +44,25 @@ public class ModuleManager {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends Module> T getModule(Class<T> moduleType) { public <T extends Module> T getModule(Class<T> moduleType) {
return (T) moduleLookupTable.computeIfAbsent(moduleType.getName(), key -> { return (T) moduleLookupTable.computeIfAbsent(moduleType.getName(), key -> {
for (Module module : modules) { AbstractModule bestCandidate = null;
if (moduleType.isAssignableFrom(module.getClass())) { for (AbstractModule module : modules) {
return module; 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.modules.add(module);
this.moduleLookupTable.put(module.getClass().getName(), module); this.moduleLookupTable.put(module.getClass().getName(), module);
enable(module); enable(module);
} }
private void enable(Module module) { private void enable(AbstractModule module) {
try { try {
module.enableModule(); module.enableModule();
} catch (Throwable t) { } 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.modules.remove(module);
this.moduleLookupTable.values().removeIf(mod -> mod == module); this.moduleLookupTable.values().removeIf(mod -> mod == module);
disable(module); disable(module);
} }
private void disable(Module module) { private void disable(AbstractModule module) {
try { try {
module.disable(); module.disable();
} catch (Throwable t) { } catch (Throwable t) {
@ -83,14 +87,14 @@ public class ModuleManager {
@Subscribe(priority = EventPriority.EARLY) @Subscribe(priority = EventPriority.EARLY)
public void onShuttingDown(DiscordSRVShuttingDownEvent event) { public void onShuttingDown(DiscordSRVShuttingDownEvent event) {
for (Module module : modules) { for (AbstractModule module : modules) {
unregister(module); unregister(module);
} }
} }
@Subscribe(priority = EventPriority.EARLY) @Subscribe(priority = EventPriority.EARLY)
public void onReload(DiscordSRVReloadEvent event) { public void onReload(DiscordSRVReloadEvent event) {
for (Module module : modules) { for (AbstractModule module : modules) {
// Check if the module needs to be enabled due to reload // Check if the module needs to be enabled due to reload
enable(module); enable(module);

View File

@ -19,14 +19,14 @@
package com.discordsrv.common.module.modules; package com.discordsrv.common.module.modules;
import com.discordsrv.api.event.bus.Subscribe; 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.DiscordSRV;
import com.discordsrv.common.discord.api.channel.DiscordMessageChannelImpl; import com.discordsrv.common.discord.api.channel.DiscordMessageChannelImpl;
import com.discordsrv.common.discord.api.message.ReceivedDiscordMessageImpl; 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; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
public class DiscordAPIEventModule extends Module { public class DiscordAPIEventModule extends AbstractModule {
public DiscordAPIEventModule(DiscordSRV discordSRV) { public DiscordAPIEventModule(DiscordSRV discordSRV) {
super(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.api.event.events.channel.GameChannelLookupEvent;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.channel.DefaultGlobalChannel; import com.discordsrv.common.channel.DefaultGlobalChannel;
import com.discordsrv.common.event.util.EventUtil; import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.module.Module;
public class GlobalChannelLookupModule extends Module { public class GlobalChannelLookupModule extends AbstractModule {
private final DefaultGlobalChannel defaultGlobalChannel; private final DefaultGlobalChannel defaultGlobalChannel;
@ -37,7 +36,7 @@ public class GlobalChannelLookupModule extends Module {
@Subscribe(priority = EventPriority.LATE) @Subscribe(priority = EventPriority.LATE)
public void onGameChannelLookup(GameChannelLookupEvent event) { public void onGameChannelLookup(GameChannelLookupEvent event) {
if (EventUtil.checkProcessor(discordSRV, event)) { if (checkProcessor(event)) {
return; 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/>. * 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.channel.GameChannel;
import com.discordsrv.api.component.EnhancedTextBuilder; 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.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember; import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage; 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.bus.Subscribe;
import com.discordsrv.api.event.events.discord.DiscordMessageReceivedEvent; import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessingEvent;
import com.discordsrv.api.event.events.message.receive.discord.DiscordMessageProcessingEvent;
import com.discordsrv.api.placeholder.util.Placeholders; import com.discordsrv.api.placeholder.util.Placeholders;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.renderer.DiscordSRVMinecraftRenderer; import com.discordsrv.common.component.renderer.DiscordSRVMinecraftRenderer;
import com.discordsrv.common.component.util.ComponentUtil; 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.config.main.channels.DiscordToMinecraftChatConfig;
import com.discordsrv.common.function.OrDefault; 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 net.kyori.adventure.text.Component;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class DiscordToMinecraftModule extends Module { public class DiscordToMinecraftChatModule extends AbstractModule {
public DiscordToMinecraftModule(DiscordSRV discordSRV) { public DiscordToMinecraftChatModule(DiscordSRV discordSRV) {
super(discordSRV); super(discordSRV);
} }
@ -54,30 +54,38 @@ public class DiscordToMinecraftModule extends Module {
return; return;
} }
discordSRV.eventBus().publish(new DiscordMessageProcessingEvent(event.getMessage(), channel)); discordSRV.eventBus().publish(new DiscordChatMessageProcessingEvent(event.getMessage(), channel));
} }
@Subscribe @Subscribe
public void onDiscordMessageReceive(DiscordMessageProcessingEvent event) { public void onDiscordMessageReceive(DiscordChatMessageProcessingEvent event) {
if (checkCancellation(event) || checkProcessor(event)) { if (checkCancellation(event) || checkProcessor(event)) {
return; 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(); DiscordTextChannel channel = event.getChannel();
ReceivedDiscordMessage discordMessage = event.getDiscordMessage(); ReceivedDiscordMessage discordMessage = event.getDiscordMessage();
DiscordUser author = discordMessage.getAuthor(); DiscordUser author = discordMessage.getAuthor();
Optional<DiscordGuildMember> member = discordMessage.getMember(); Optional<DiscordGuildMember> member = discordMessage.getMember();
boolean webhookMessage = discordMessage.isWebhookMessage(); 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); DiscordToMinecraftChatConfig.Ignores ignores = chatConfig.get(cfg -> cfg.ignores);
if (ignores != null) { if (ignores != null) {
if (ignores.webhooks && webhookMessage) { if (ignores.webhooks && webhookMessage) {
@ -116,7 +124,7 @@ public class DiscordToMinecraftModule extends Module {
EnhancedTextBuilder componentBuilder = discordSRV.componentFactory() EnhancedTextBuilder componentBuilder = discordSRV.componentFactory()
.enhancedBuilder(format) .enhancedBuilder(format)
.addContext(discordMessage, author) .addContext(discordMessage, author, channel, channelConfig)
.addReplacement("%message%", messageComponent); .addReplacement("%message%", messageComponent);
member.ifPresent(componentBuilder::addContext); 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