mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2025-02-17 01:51:32 +01:00
Add message mirroring
This commit is contained in:
parent
047580d9cf
commit
0c9732fe11
@ -45,15 +45,16 @@ public interface DiscordMessageChannel extends Snowflake {
|
||||
* @throws com.discordsrv.api.discord.api.exception.NotReadyException if DiscordSRV is not ready, {@link com.discordsrv.api.DiscordSRVApi#isReady()}
|
||||
*/
|
||||
@NotNull
|
||||
CompletableFuture<ReceivedDiscordMessage> sendMessage(SendableDiscordMessage message);
|
||||
CompletableFuture<ReceivedDiscordMessage> sendMessage(@NotNull SendableDiscordMessage message);
|
||||
|
||||
/**
|
||||
* Deletes the message identified by the id.
|
||||
*
|
||||
* @param id the id of the message to delete
|
||||
* @param webhookMessage if the message is a webhook message or not
|
||||
* @return a future that will fail if the request fails
|
||||
*/
|
||||
CompletableFuture<Void> deleteMessageById(long id);
|
||||
CompletableFuture<Void> deleteMessageById(long id, boolean webhookMessage);
|
||||
|
||||
/**
|
||||
* Edits the message identified by the id.
|
||||
@ -64,7 +65,7 @@ public interface DiscordMessageChannel extends Snowflake {
|
||||
* @throws com.discordsrv.api.discord.api.exception.NotReadyException if DiscordSRV is not ready, {@link com.discordsrv.api.DiscordSRVApi#isReady()}
|
||||
*/
|
||||
@NotNull
|
||||
CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, SendableDiscordMessage message);
|
||||
CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, @NotNull SendableDiscordMessage message);
|
||||
|
||||
/**
|
||||
* Returns the JDA representation of this object. This should not be used if it can be avoided.
|
||||
|
@ -26,46 +26,53 @@ package com.discordsrv.api.discord.events;
|
||||
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.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.event.events.Event;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class DiscordMessageReceivedEvent implements Event {
|
||||
public abstract class AbstractDiscordMessageEvent implements Event {
|
||||
|
||||
private final ReceivedDiscordMessage message;
|
||||
private final DiscordMessageChannel channel;
|
||||
|
||||
public DiscordMessageReceivedEvent(ReceivedDiscordMessage message, DiscordMessageChannel channel) {
|
||||
this.message = message;
|
||||
public AbstractDiscordMessageEvent(DiscordMessageChannel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public boolean isGuildMessage() {
|
||||
return getTextChannel().isPresent();
|
||||
return !getDMChannel().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Discord text channel if this event originated from a message sent in a text channel.
|
||||
* This will not be present on messages from threads (see {@link #getThreadChannel()}).
|
||||
* @return an optional potentially containing a {@link DiscordTextChannel}
|
||||
*/
|
||||
@NotNull
|
||||
public Optional<DiscordTextChannel> getTextChannel() {
|
||||
return channel instanceof DiscordTextChannel
|
||||
? Optional.of((DiscordTextChannel) channel)
|
||||
: Optional.empty();
|
||||
? Optional.of((DiscordTextChannel) channel)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<DiscordThreadChannel> getThreadChannel() {
|
||||
return channel instanceof DiscordThreadChannel
|
||||
? Optional.of((DiscordThreadChannel) channel)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<DiscordDMChannel> getDMChannel() {
|
||||
return channel instanceof DiscordDMChannel
|
||||
? Optional.of((DiscordDMChannel) channel)
|
||||
: Optional.empty();
|
||||
? Optional.of((DiscordDMChannel) channel)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DiscordMessageChannel getChannel() {
|
||||
return channel ;
|
||||
return channel;
|
||||
}
|
||||
|
||||
public ReceivedDiscordMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
|
||||
public class DiscordMessageDeleteEvent extends AbstractDiscordMessageEvent {
|
||||
|
||||
private final long messageId;
|
||||
|
||||
public DiscordMessageDeleteEvent(DiscordMessageChannel channel, long messageId) {
|
||||
super(channel);
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public long getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of the DiscordSRV API, licensed under the MIT License
|
||||
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.discordsrv.api.discord.events;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
||||
|
||||
public class DiscordMessageUpdateEvent extends AbstractDiscordMessageEvent {
|
||||
|
||||
private final ReceivedDiscordMessage message;
|
||||
|
||||
public DiscordMessageUpdateEvent(DiscordMessageChannel channel, ReceivedDiscordMessage message) {
|
||||
super(channel);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ReceivedDiscordMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,9 @@
|
||||
|
||||
package com.discordsrv.api.event.events.message.receive.discord;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuild;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.event.events.Cancellable;
|
||||
@ -34,14 +36,17 @@ public class DiscordChatMessageProcessingEvent implements Cancellable, Processab
|
||||
|
||||
private final ReceivedDiscordMessage discordMessage;
|
||||
private String messageContent;
|
||||
private final DiscordTextChannel channel;
|
||||
private final DiscordMessageChannel channel;
|
||||
private boolean cancelled;
|
||||
private boolean processed;
|
||||
|
||||
public DiscordChatMessageProcessingEvent(@NotNull ReceivedDiscordMessage discordMessage, @NotNull DiscordTextChannel channel) {
|
||||
public DiscordChatMessageProcessingEvent(@NotNull ReceivedDiscordMessage discordMessage, @NotNull DiscordMessageChannel channel) {
|
||||
this.discordMessage = discordMessage;
|
||||
this.messageContent = discordMessage.getContent().orElse(null);
|
||||
this.channel = channel;
|
||||
if (!(channel instanceof DiscordTextChannel) && !(channel instanceof DiscordThreadChannel)) {
|
||||
throw new IllegalStateException("Cannot process messages that aren't from a text channel or thread");
|
||||
}
|
||||
}
|
||||
|
||||
public ReceivedDiscordMessage getDiscordMessage() {
|
||||
@ -56,12 +61,18 @@ public class DiscordChatMessageProcessingEvent implements Cancellable, Processab
|
||||
this.messageContent = messageContent;
|
||||
}
|
||||
|
||||
public DiscordTextChannel getChannel() {
|
||||
public DiscordMessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public DiscordGuild getGuild() {
|
||||
return channel.getGuild();
|
||||
if (channel instanceof DiscordTextChannel) {
|
||||
return ((DiscordTextChannel) channel).getGuild();
|
||||
} else if (channel instanceof DiscordThreadChannel) {
|
||||
return ((DiscordThreadChannel) channel).getParentChannel().getGuild();
|
||||
} else {
|
||||
throw new IllegalStateException("Message isn't from a text channel or thread");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ConfigSerializable
|
||||
public class DiscordIgnores {
|
||||
|
||||
@Comment("User, bot and webhook ids to ignore")
|
||||
public IDs usersAndWebhookIds = new IDs();
|
||||
|
||||
@Comment("Role ids for users/bots to ignore")
|
||||
public IDs roleIds = new IDs();
|
||||
|
||||
@Comment("If bots (webhooks not included) should be ignored")
|
||||
public boolean bots = false;
|
||||
|
||||
@Comment("If webhooks should be ignored")
|
||||
public boolean webhooks = true;
|
||||
|
||||
@ConfigSerializable
|
||||
public static class IDs {
|
||||
|
||||
public List<Long> ids = new ArrayList<>();
|
||||
|
||||
@Comment("true for whitelisting the provided ids, false for blacklisting them")
|
||||
public boolean whitelist = false;
|
||||
}
|
||||
|
||||
public boolean shouldBeIgnored(boolean webhookMessage, DiscordUser author, DiscordGuildMember member) {
|
||||
if (webhooks && webhookMessage) {
|
||||
return true;
|
||||
} else if (bots && (author.isBot() && !webhookMessage)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DiscordIgnores.IDs users = usersAndWebhookIds;
|
||||
if (users != null && users.ids.contains(author.getId()) != users.whitelist) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DiscordIgnores.IDs roles = roleIds;
|
||||
return roles != null && Optional.ofNullable(member)
|
||||
.map(m -> m.getRoles().stream().anyMatch(role -> roles.ids.contains(role.getId())))
|
||||
.map(hasRole -> hasRole != roles.whitelist)
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
@ -19,12 +19,11 @@
|
||||
package com.discordsrv.common.config.main.channels;
|
||||
|
||||
import com.discordsrv.common.config.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.main.DiscordIgnores;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -44,7 +43,7 @@ public class DiscordToMinecraftChatConfig {
|
||||
|
||||
@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";
|
||||
public String attachmentFormat = " [hover:show_text:Open %file_name% in browser][click:open_url:%file_url%]&a[&f%file_name%&a]&r";
|
||||
|
||||
// TODO: more info on regex pairs (String#replaceAll)
|
||||
@Comment("Regex filters for Discord message contents (this is the %message% part of the \"format\" option)")
|
||||
@ -52,32 +51,7 @@ public class DiscordToMinecraftChatConfig {
|
||||
public Map<Pattern, String> contentRegexFilters = new LinkedHashMap<>();
|
||||
|
||||
@Comment("Users, bots and webhooks to ignore")
|
||||
public Ignores ignores = new Ignores();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Ignores {
|
||||
|
||||
@Comment("User, bot and webhook ids to ignore")
|
||||
public IDs usersAndWebhookIds = new IDs();
|
||||
|
||||
@Comment("Role ids for users/bots to ignore")
|
||||
public IDs roleIds = new IDs();
|
||||
|
||||
@Comment("If bots (webhooks not included) should be ignored")
|
||||
public boolean bots = false;
|
||||
|
||||
@Comment("If webhooks should be ignored")
|
||||
public boolean webhooks = true;
|
||||
|
||||
@ConfigSerializable
|
||||
public static class IDs {
|
||||
|
||||
public List<Long> ids = new ArrayList<>();
|
||||
|
||||
@Comment("true for whitelisting the provided ids, false for blacklisting them")
|
||||
public boolean whitelist = false;
|
||||
}
|
||||
}
|
||||
public DiscordIgnores ignores = new DiscordIgnores();
|
||||
|
||||
@Comment("The representations of Discord mentions in-game")
|
||||
public Mentions mentions = new Mentions();
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.common.config.main.DiscordIgnores;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class MirroringConfig {
|
||||
|
||||
public boolean enabled = true;
|
||||
|
||||
@Comment("Users, bots and webhooks to ignore when mirroring")
|
||||
public DiscordIgnores ignores = new DiscordIgnores();
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.discord.api.entity.channel;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
|
||||
public abstract class DiscordMessageChannelImpl<T extends MessageChannel>
|
||||
implements DiscordMessageChannel {
|
||||
|
||||
public static DiscordMessageChannelImpl<?> get(DiscordSRV discordSRV, MessageChannel messageChannel) {
|
||||
if (messageChannel instanceof TextChannel) {
|
||||
return new DiscordTextChannelImpl(discordSRV, (TextChannel) messageChannel);
|
||||
} else if (messageChannel instanceof ThreadChannel) {
|
||||
return new DiscordThreadChannelImpl(discordSRV, (ThreadChannel) messageChannel);
|
||||
} else if (messageChannel instanceof PrivateChannel) {
|
||||
return new DiscordDMChannelImpl(discordSRV, (PrivateChannel) messageChannel);
|
||||
} else if (messageChannel instanceof NewsChannel) {
|
||||
return new DiscordNewsChannelImpl(discordSRV, (NewsChannel) messageChannel);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unmappable MessageChannel type: " + messageChannel.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected final DiscordSRV discordSRV;
|
||||
protected final T channel;
|
||||
|
||||
public DiscordMessageChannelImpl(DiscordSRV discordSRV, T channel) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return channel.getIdLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageChannel getAsJDAMessageChannel() {
|
||||
return channel;
|
||||
}
|
||||
}
|
@ -16,49 +16,49 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.module.modules.message;
|
||||
package com.discordsrv.common.messageforwarding.discord;
|
||||
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
import com.discordsrv.api.component.EnhancedTextBuilder;
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
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.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageReceivedEvent;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageReceiveEvent;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessingEvent;
|
||||
import com.discordsrv.api.placeholder.util.Placeholders;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.renderer.DiscordSRVMinecraftRenderer;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.DiscordIgnores;
|
||||
import com.discordsrv.common.config.main.channels.DiscordToMinecraftChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.function.OrDefault;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DiscordToMinecraftChatModule extends AbstractModule {
|
||||
public class DiscordChatMessageModule extends AbstractModule {
|
||||
|
||||
public DiscordToMinecraftChatModule(DiscordSRV discordSRV) {
|
||||
public DiscordChatMessageModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onGuildMessageReceived(DiscordMessageReceivedEvent event) {
|
||||
DiscordTextChannel channel = event.getTextChannel().orElse(null);
|
||||
if (channel == null || !discordSRV.isReady() || event.getMessage().isFromSelf()) {
|
||||
public void onDiscordMessageReceived(DiscordMessageReceiveEvent event) {
|
||||
if (!discordSRV.isReady() || event.getMessage().isFromSelf()
|
||||
|| !(event.getTextChannel().isPresent() || event.getThreadChannel().isPresent())) {
|
||||
return;
|
||||
}
|
||||
|
||||
discordSRV.eventBus().publish(new DiscordChatMessageProcessingEvent(event.getMessage(), channel));
|
||||
discordSRV.eventBus().publish(new DiscordChatMessageProcessingEvent(event.getMessage(), event.getChannel()));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordMessageReceive(DiscordChatMessageProcessingEvent event) {
|
||||
public void onDiscordChatMessageProcessing(DiscordChatMessageProcessingEvent event) {
|
||||
if (checkCancellation(event) || checkProcessor(event)) {
|
||||
return;
|
||||
}
|
||||
@ -80,32 +80,16 @@ public class DiscordToMinecraftChatModule extends AbstractModule {
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordTextChannel channel = event.getChannel();
|
||||
DiscordMessageChannel channel = event.getChannel();
|
||||
ReceivedDiscordMessage discordMessage = event.getDiscordMessage();
|
||||
DiscordUser author = discordMessage.getAuthor();
|
||||
Optional<DiscordGuildMember> member = discordMessage.getMember();
|
||||
DiscordGuildMember member = discordMessage.getMember().orElse(null);
|
||||
boolean webhookMessage = discordMessage.isWebhookMessage();
|
||||
|
||||
DiscordToMinecraftChatConfig.Ignores ignores = chatConfig.get(cfg -> cfg.ignores);
|
||||
if (ignores != null) {
|
||||
if (ignores.webhooks && webhookMessage) {
|
||||
return;
|
||||
} else if (ignores.bots && (author.isBot() && !webhookMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordToMinecraftChatConfig.Ignores.IDs users = ignores.usersAndWebhookIds;
|
||||
if (users != null && users.ids.contains(author.getId()) != users.whitelist) {
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordToMinecraftChatConfig.Ignores.IDs roles = ignores.roleIds;
|
||||
if (roles != null && member
|
||||
.map(m -> m.getRoles().stream().anyMatch(role -> roles.ids.contains(role.getId())))
|
||||
.map(hasRole -> hasRole != roles.whitelist)
|
||||
.orElse(false)) {
|
||||
return;
|
||||
}
|
||||
DiscordIgnores ignores = chatConfig.get(cfg -> cfg.ignores);
|
||||
if (ignores != null && ignores.shouldBeIgnored(webhookMessage, author, member)) {
|
||||
// TODO: response for humans
|
||||
return;
|
||||
}
|
||||
|
||||
String format = chatConfig.opt(cfg -> webhookMessage ? cfg.webhookFormat : cfg.format)
|
||||
@ -126,7 +110,9 @@ public class DiscordToMinecraftChatModule extends AbstractModule {
|
||||
.enhancedBuilder(format)
|
||||
.addContext(discordMessage, author, channel, channelConfig)
|
||||
.addReplacement("%message%", messageComponent);
|
||||
member.ifPresent(componentBuilder::addContext);
|
||||
if (member != null) {
|
||||
componentBuilder.addContext(member);
|
||||
}
|
||||
|
||||
componentBuilder.applyPlaceholderService();
|
||||
|
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.discordsrv.common.messageforwarding.discord;
|
||||
|
||||
import com.discordsrv.api.channel.GameChannel;
|
||||
import com.discordsrv.api.discord.api.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
|
||||
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
|
||||
import com.discordsrv.api.discord.api.entity.guild.DiscordGuildMember;
|
||||
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
|
||||
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
|
||||
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageDeleteEvent;
|
||||
import com.discordsrv.api.discord.events.DiscordMessageUpdateEvent;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.api.event.events.message.receive.discord.DiscordChatMessageProcessingEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.main.DiscordIgnores;
|
||||
import com.discordsrv.common.config.main.channels.MirroringConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.function.OrDefault;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DiscordMessageMirroringModule extends AbstractModule {
|
||||
|
||||
private final Cache<MessageReference, Set<MessageReference>> mapping;
|
||||
|
||||
public DiscordMessageMirroringModule(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
this.mapping = discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.MINUTES)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordChatMessageProcessing(DiscordChatMessageProcessingEvent event) {
|
||||
if (checkCancellation(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<GameChannel, OrDefault<BaseChannelConfig>> channels = discordSRV.channelConfig().orDefault(event.getChannel());
|
||||
if (channels == null || channels.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReceivedDiscordMessage message = event.getDiscordMessage();
|
||||
DiscordMessageChannel channel = event.getChannel();
|
||||
|
||||
List<DiscordMessageChannel> mirrorChannels = new ArrayList<>();
|
||||
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<GameChannel, OrDefault<BaseChannelConfig>> entry : channels.entrySet()) {
|
||||
OrDefault<BaseChannelConfig> channelConfig = entry.getValue();
|
||||
OrDefault<MirroringConfig> config = channelConfig.map(cfg -> cfg.mirroring);
|
||||
if (!config.get(cfg -> cfg.enabled, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DiscordIgnores ignores = config.get(cfg -> cfg.ignores);
|
||||
if (ignores != null && ignores.shouldBeIgnored(message.isWebhookMessage(), message.getAuthor(), message.getMember().orElse(null))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IChannelConfig iChannelConfig = channelConfig.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null);
|
||||
if (iChannelConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Long> channelIds = iChannelConfig.channelIds();
|
||||
if (channelIds != null) {
|
||||
for (Long channelId : channelIds) {
|
||||
discordSRV.discordAPI().getTextChannelById(channelId).ifPresent(textChannel -> {
|
||||
if (textChannel.getId() != channel.getId()) {
|
||||
mirrorChannels.add(textChannel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
discordSRV.discordAPI().findOrCreateThreads(iChannelConfig, threadChannel -> {
|
||||
if (threadChannel.getId() != channel.getId()) {
|
||||
mirrorChannels.add(threadChannel);
|
||||
}
|
||||
}, futures);
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).whenComplete((v, t) -> {
|
||||
List<DiscordTextChannel> text = new ArrayList<>();
|
||||
List<DiscordThreadChannel> thread = new ArrayList<>();
|
||||
for (DiscordMessageChannel mirrorChannel : mirrorChannels) {
|
||||
if (mirrorChannel instanceof DiscordTextChannel) {
|
||||
text.add((DiscordTextChannel) mirrorChannel);
|
||||
} else if (mirrorChannel instanceof DiscordThreadChannel) {
|
||||
thread.add((DiscordThreadChannel) mirrorChannel);
|
||||
}
|
||||
}
|
||||
|
||||
SendableDiscordMessage.Builder builder = convert(event.getDiscordMessage());
|
||||
List<CompletableFuture<ReceivedDiscordMessage>> messageFutures = new ArrayList<>();
|
||||
if (!text.isEmpty()) {
|
||||
SendableDiscordMessage finalMessage = builder.build();
|
||||
for (DiscordTextChannel textChannel : text) {
|
||||
messageFutures.add(textChannel.sendMessage(finalMessage));
|
||||
}
|
||||
}
|
||||
if (!thread.isEmpty()) {
|
||||
SendableDiscordMessage finalMessage = builder.convertToNonWebhook().build();
|
||||
for (DiscordThreadChannel threadChannel : thread) {
|
||||
messageFutures.add(threadChannel.sendMessage(finalMessage));
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(messageFutures.toArray(new CompletableFuture[0])).whenComplete((v2, t2) -> {
|
||||
Set<MessageReference> messages = new HashSet<>();
|
||||
for (CompletableFuture<ReceivedDiscordMessage> messageFuture : messageFutures) {
|
||||
if (messageFuture.isCompletedExceptionally()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
messages.add(getReference(messageFuture.join()));
|
||||
}
|
||||
|
||||
mapping.put(getReference(message), messages);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordMessageUpdate(DiscordMessageUpdateEvent event) {
|
||||
ReceivedDiscordMessage message = event.getMessage();
|
||||
Set<MessageReference> references = mapping.get(getReference(message), k -> null);
|
||||
if (references == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<DiscordTextChannel, MessageReference> text = new LinkedHashMap<>();
|
||||
Map<DiscordThreadChannel, MessageReference> thread = new LinkedHashMap<>();
|
||||
for (MessageReference reference : references) {
|
||||
DiscordMessageChannel channel = reference.getMessageChannel(discordSRV);
|
||||
if (channel instanceof DiscordTextChannel) {
|
||||
text.put((DiscordTextChannel) channel, reference);
|
||||
} else if (channel instanceof DiscordThreadChannel) {
|
||||
thread.put((DiscordThreadChannel) channel, reference);
|
||||
}
|
||||
}
|
||||
SendableDiscordMessage.Builder builder = convert(message);
|
||||
if (!text.isEmpty()) {
|
||||
SendableDiscordMessage finalMessage = builder.build();
|
||||
for (Map.Entry<DiscordTextChannel, MessageReference> entry : text.entrySet()) {
|
||||
entry.getKey().editMessageById(entry.getValue().messageId, finalMessage).whenComplete((v, t) -> {
|
||||
if (t != null) {
|
||||
discordSRV.logger().error("Failed to update mirrored message in " + entry.getKey());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!thread.isEmpty()) {
|
||||
SendableDiscordMessage finalMessage = builder.convertToNonWebhook().build();
|
||||
for (Map.Entry<DiscordThreadChannel, MessageReference> entry : thread.entrySet()) {
|
||||
entry.getKey().editMessageById(entry.getValue().messageId, finalMessage).whenComplete((v, t) -> {
|
||||
if (t != null) {
|
||||
discordSRV.logger().error("Failed to update mirrored message in " + entry.getKey());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onDiscordMessageDelete(DiscordMessageDeleteEvent event) {
|
||||
Set<MessageReference> references = mapping.get(getReference(event.getChannel(), event.getMessageId(), false), k -> null);
|
||||
if (references == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (MessageReference reference : references) {
|
||||
DiscordMessageChannel channel = reference.getMessageChannel(discordSRV);
|
||||
if (channel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
channel.deleteMessageById(reference.messageId, reference.webhookMessage).whenComplete((v, t) -> {
|
||||
if (t != null) {
|
||||
discordSRV.logger().error("Failed to delete mirrored message in " + channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private SendableDiscordMessage.Builder convert(ReceivedDiscordMessage message) {
|
||||
DiscordGuildMember member = message.getMember().orElse(null);
|
||||
DiscordUser user = message.getAuthor();
|
||||
|
||||
SendableDiscordMessage.Builder builder = SendableDiscordMessage.builder()
|
||||
.setContent(message.getContent().orElse(null))
|
||||
.setWebhookUsername(member != null ? member.getEffectiveName() : user.getUsername())
|
||||
.setWebhookAvatarUrl(member != null
|
||||
? member.getEffectiveServerAvatarUrl()
|
||||
: user.getEffectiveAvatarUrl());
|
||||
for (DiscordMessageEmbed embed : message.getEmbeds()) {
|
||||
builder.addEmbed(embed);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private MessageReference getReference(ReceivedDiscordMessage message) {
|
||||
return getReference(message.getChannel(), message.getId(), message.isWebhookMessage());
|
||||
}
|
||||
|
||||
private MessageReference getReference(DiscordMessageChannel channel, long messageId, boolean webhookMessage) {
|
||||
if (channel instanceof DiscordTextChannel) {
|
||||
DiscordTextChannel textChannel = (DiscordTextChannel) channel;
|
||||
return new MessageReference(textChannel, messageId, webhookMessage);
|
||||
} else if (channel instanceof DiscordThreadChannel) {
|
||||
DiscordThreadChannel threadChannel = (DiscordThreadChannel) channel;
|
||||
return new MessageReference(threadChannel, messageId, webhookMessage);
|
||||
}
|
||||
throw new IllegalStateException("Unexpected channel type: " + channel.getClass().getName());
|
||||
}
|
||||
|
||||
public static class MessageReference {
|
||||
|
||||
private final long channelId;
|
||||
private final long threadId;
|
||||
private final long messageId;
|
||||
private final boolean webhookMessage;
|
||||
|
||||
public MessageReference(DiscordTextChannel textChannel, long messageId, boolean webhookMessage) {
|
||||
this(textChannel.getId(), -1L, messageId, webhookMessage);
|
||||
}
|
||||
|
||||
public MessageReference(DiscordThreadChannel threadChannel, long messageId, boolean webhookMessage) {
|
||||
this(threadChannel.getParentChannel().getId(), threadChannel.getId(), messageId, webhookMessage);
|
||||
}
|
||||
|
||||
public MessageReference(long channelId, long threadId, long messageId, boolean webhookMessage) {
|
||||
this.channelId = channelId;
|
||||
this.threadId = threadId;
|
||||
this.messageId = messageId;
|
||||
this.webhookMessage = webhookMessage;
|
||||
}
|
||||
|
||||
public DiscordMessageChannel getMessageChannel(DiscordSRV discordSRV) {
|
||||
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null);
|
||||
if (textChannel == null) {
|
||||
return null;
|
||||
} else if (threadId == -1) {
|
||||
return textChannel;
|
||||
}
|
||||
|
||||
for (DiscordThreadChannel activeThread : textChannel.getActiveThreads()) {
|
||||
if (activeThread.getId() == threadId) {
|
||||
return activeThread;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
MessageReference that = (MessageReference) o;
|
||||
// Intentionally ignores webhookMessage
|
||||
return channelId == that.channelId && threadId == that.threadId && messageId == that.messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Intentionally ignores webhookMessage
|
||||
return Objects.hash(channelId, threadId, messageId);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user