Merge pull request #99 from TBMCPlugins/dev

Updated to Discord4J v3, permission injection, improvements
This commit is contained in:
Norbi Peti 2019-06-06 22:45:21 +02:00 committed by GitHub
commit bc160a21b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 2750 additions and 2422 deletions

43
pom.xml
View file

@ -40,6 +40,14 @@
<configuration> <configuration>
<source>1.8</source> <source>1.8</source>
<target>1.8</target> <target>1.8</target>
<!-- <compilerArgs>
<arg>-processor</arg>
<arg>buttondevteam.buttonproc.ButtonProcessor, lombok.core.AnnotationProcessor</arg>
</compilerArgs> -->
<!-- <annotationProcessors>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
</annotationProcessors> -->
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@ -60,6 +68,15 @@
<exclude>net.ess3:Essentials</exclude> <exclude>net.ess3:Essentials</exclude>
</excludes> <!-- http://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies --> </excludes> <!-- http://stackoverflow.com/questions/28458058/maven-shade-plugin-exclude-a-dependency-and-all-its-transitive-dependencies -->
</artifactSet> </artifactSet>
<minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>io.netty</pattern>
<shadedPattern>btndvtm.dp.io.netty</shadedPattern>
<excludes>
</excludes>
</relocation>
</relocations>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
@ -96,6 +113,7 @@
</executions> </plugin> --> </executions> </plugin> -->
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration> <configuration>
<useSystemClassLoader>false <useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 --> </useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
@ -140,6 +158,10 @@
<id>pex-repo</id> <id>pex-repo</id>
<url>http://pex-repo.aoeu.xyz</url> <url>http://pex-repo.aoeu.xyz</url>
</repository> --> </repository> -->
<!-- <repository>
<id>Reactor-Tools</id>
<url>https://repo.spring.io/milestone</url>
</repository> -->
</repositories> </repositories>
<dependencies> <dependencies>
@ -164,8 +186,8 @@
<!-- https://mvnrepository.com/artifact/com.discord4j/Discord4J --> <!-- https://mvnrepository.com/artifact/com.discord4j/Discord4J -->
<dependency> <dependency>
<groupId>com.discord4j</groupId> <groupId>com.discord4j</groupId>
<artifactId>Discord4J</artifactId> <artifactId>discord4j-core</artifactId>
<version>2.10.1</version> <version>3.0.6</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-jdk14 --> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-jdk14 -->
<dependency> <dependency>
@ -191,11 +213,6 @@
<version>2.13.1</version> <version>2.13.1</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.github.xaanit</groupId>
<artifactId>D4J-OAuth</artifactId>
<version>master-SNAPSHOT</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
@ -227,6 +244,18 @@
<artifactId>emoji-java</artifactId> <artifactId>emoji-java</artifactId>
<version>4.0.0</version> <version>4.0.0</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/io.projectreactor.tools/blockhound -->
<!-- <dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound</artifactId>
<version>1.0.0.M3</version>
</dependency> -->
<dependency>
<groupId>com.github.lucko</groupId>
<artifactId>LuckPerms</artifactId>
<version>v4.4</version>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>

View file

@ -1,27 +0,0 @@
package buttondevteam.discordplugin;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
@RequiredArgsConstructor
public class AsyncDiscordEvent<T extends sx.blah.discord.api.events.Event> extends Event implements Cancellable {
private final @Getter T event;
@Getter
@Setter
private boolean cancelled;
private static final HandlerList handlers = new HandlerList();
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -1,15 +1,14 @@
package buttondevteam.discordplugin; package buttondevteam.discordplugin;
import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.discordplugin.mcchat.MCChatUtils;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.MessageChannel;
import lombok.Getter; import lombok.Getter;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import sx.blah.discord.api.internal.json.objects.EmbedObject; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.util.EmbedBuilder;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.awt.*; import java.util.function.Function;
public class ChromaBot { public class ChromaBot {
/** /**
@ -33,111 +32,24 @@ public class ChromaBot {
instance = null; instance = null;
} }
/**
* Send a message to the chat channel and private chats.
*
* @param message
* The message to send, duh
*/
public void sendMessage(String message) {
MCChatUtils.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message));
}
/** /**
* Send a message to the chat channels and private chats. * Send a message to the chat channels and private chats.
* *
* @param message * @param message
* The message to send, duh * The message to send, duh (use {@link MessageChannel#createMessage(String)})
* @param embed
* Custom fancy stuff, use {@link EmbedBuilder} to create one
*/ */
public void sendMessage(String message, EmbedObject embed) { public void sendMessage(Function<Mono<MessageChannel>, Mono<Message>> message) {
MCChatUtils.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message, embed)); MCChatUtils.forAllMCChat(ch -> message.apply(ch).subscribe());
} }
/** /**
* Send a message to the chat channels, private chats and custom chats. * Send a message to the chat channels, private chats and custom chats.
* *
* @param message The message to send, duh * @param message The message to send, duh
* @param embed Custom fancy stuff, use {@link EmbedBuilder} to create one
* @param toggle The toggle type for channelcon * @param toggle The toggle type for channelcon
*/ */
public void sendMessageCustomAsWell(String message, EmbedObject embed, @Nullable ChannelconBroadcast toggle) { public void sendMessageCustomAsWell(Function<Mono<MessageChannel>, Mono<Message>> message, @Nullable ChannelconBroadcast toggle) {
MCChatUtils.forCustomAndAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message, embed), toggle, false); MCChatUtils.forCustomAndAllMCChat(ch -> message.apply(ch).subscribe(), toggle, false);
}
/**
* Send a message to an arbitrary channel. This will not send it to the private chats.
*
* @param channel
* The channel to send to, use the channel variables in {@link DiscordPlugin}
* @param message
* The message to send, duh
* @param embed
* Custom fancy stuff, use {@link EmbedBuilder} to create one
*/
public void sendMessage(IChannel channel, String message, EmbedObject embed) {
DiscordPlugin.sendMessageToChannel(channel, message, embed);
}
/**
* Send a fancy message to the chat channels. This will show a bold text with a colored line.
*
* @param message
* The message to send, duh
* @param color
* The color of the line before the text
*/
public void sendMessage(String message, Color color) {
MCChatUtils.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message,
new EmbedBuilder().withTitle(message).withColor(color).build()));
}
/**
* Send a fancy message to the chat channels. This will show a bold text with a colored line.
*
* @param message
* The message to send, duh
* @param color
* The color of the line before the text
* @param mcauthor
* The name of the Minecraft player who is the author of this message
*/
public void sendMessage(String message, Color color, String mcauthor) {
MCChatUtils.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message,
DPUtils.embedWithHead(new EmbedBuilder().withTitle(message).withColor(color), mcauthor).build()));
}
/**
* Send a fancy message to the chat channels. This will show a bold text with a colored line.
*
* @param message
* The message to send, duh
* @param color
* The color of the line before the text
* @param authorname
* The name of the author of this message
* @param authorimg
* The URL of the avatar image for this message's author
*/
public void sendMessage(String message, Color color, String authorname, String authorimg) {
MCChatUtils.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message, new EmbedBuilder()
.withTitle(message).withColor(color).withAuthorName(authorname).withAuthorIcon(authorimg).build()));
}
/**
* Send a message to the chat channels. This will show a bold text with a colored line.
*
* @param message
* The message to send, duh
* @param color
* The color of the line before the text
* @param sender
* The player who sends this message
*/
public void sendMessage(String message, Color color, Player sender) {
MCChatUtils.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannel(ch, message, DPUtils
.embedWithHead(new EmbedBuilder().withTitle(message).withColor(color), sender.getName()).build()));
} }
public void updatePlayerList() { public void updatePlayerList() {

View file

@ -4,27 +4,24 @@ import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig; import buttondevteam.lib.architecture.IHaveConfig;
import buttondevteam.lib.architecture.ReadOnlyConfigData;
import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.Role;
import discord4j.core.object.util.Snowflake;
import discord4j.core.spec.EmbedCreateSpec;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IGuild;
import sx.blah.discord.handle.obj.IIDLinkedObject;
import sx.blah.discord.handle.obj.IRole;
import sx.blah.discord.util.EmbedBuilder;
import sx.blah.discord.util.RequestBuffer;
import sx.blah.discord.util.RequestBuffer.IRequest;
import sx.blah.discord.util.RequestBuffer.IVoidRequest;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
public final class DPUtils { public final class DPUtils {
public static EmbedBuilder embedWithHead(EmbedBuilder builder, String playername) { public static EmbedCreateSpec embedWithHead(EmbedCreateSpec ecs, String displayname, String playername, String profileUrl) {
return builder.withAuthorIcon("https://minotar.net/avatar/" + playername + "/32.png"); return ecs.setAuthor(displayname, profileUrl, "https://minotar.net/avatar/" + playername + "/32.png");
} }
/** /**
@ -39,7 +36,7 @@ public final class DPUtils {
* Removes §[char] colour codes from strings * Removes §[char] colour codes from strings
*/ */
public static String sanitizeStringNoEscape(String string) { public static String sanitizeStringNoEscape(String string) {
String sanitizedString = ""; StringBuilder sanitizedString = new StringBuilder();
boolean random = false; boolean random = false;
for (int i = 0; i < string.length(); i++) { for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) == '§') { if (string.charAt(i) == '§') {
@ -47,62 +44,13 @@ public final class DPUtils {
random = string.charAt(i) == 'k'; random = string.charAt(i) == 'k';
} else { } else {
if (!random) // Skip random/obfuscated characters if (!random) // Skip random/obfuscated characters
sanitizedString += string.charAt(i); sanitizedString.append(string.charAt(i));
} }
} }
return sanitizedString; return sanitizedString.toString();
} }
/** private static String escape(String message) {
* Performs Discord actions, retrying when ratelimited. May return null if action fails too many times or in safe mode.
*/
@Nullable
public static <T> T perform(IRequest<T> action, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException {
if (DiscordPlugin.SafeMode)
return null;
if (Bukkit.isPrimaryThread()) // TODO: Ignore shutdown message <--
// throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
getLogger().warning("Waiting for a Discord request on the main thread!");
return RequestBuffer.request(action).get(timeout, unit); // Let the pros handle this
}
/**
* Performs Discord actions, retrying when ratelimited. May return null if action fails too many times or in safe mode.
*/
@Nullable
public static <T> T perform(IRequest<T> action) {
if (DiscordPlugin.SafeMode)
return null;
if (Bukkit.isPrimaryThread()) // TODO: Ignore shutdown message <--
// throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
getLogger().warning("Waiting for a Discord request on the main thread!");
return RequestBuffer.request(action).get(); // Let the pros handle this
}
/**
* Performs Discord actions, retrying when ratelimited.
*/
public static Void perform(IVoidRequest action) {
if (DiscordPlugin.SafeMode)
return null;
if (Bukkit.isPrimaryThread())
throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
return RequestBuffer.request(action).get(); // Let the pros handle this
}
public static void performNoWait(IVoidRequest action) {
if (DiscordPlugin.SafeMode)
return;
RequestBuffer.request(action);
}
public static <T> void performNoWait(IRequest<T> action) {
if (DiscordPlugin.SafeMode)
return;
RequestBuffer.request(action);
}
public static String escape(String message) {
return message.replaceAll("([*_~])", Matcher.quoteReplacement("\\") + "$1"); return message.replaceAll("([*_~])", Matcher.quoteReplacement("\\") + "$1");
} }
@ -112,20 +60,26 @@ public final class DPUtils {
return DiscordPlugin.plugin.getLogger(); return DiscordPlugin.plugin.getLogger();
} }
public static ConfigData<IChannel> channelData(IHaveConfig config, String key, long defID) { public static ReadOnlyConfigData<Mono<MessageChannel>> channelData(IHaveConfig config, String key, long defID) {
return config.getDataPrimDef(key, defID, id -> DiscordPlugin.dc.getChannelByID((long) id), IIDLinkedObject::getLongID); //We can afford to search for the channel in the cache once (instead of using mainServer) return config.getReadOnlyDataPrimDef(key, defID, id -> getMessageChannel(key, Snowflake.of((Long) id)), ch -> defID); //We can afford to search for the channel in the cache once (instead of using mainServer)
} }
public static ConfigData<IRole> roleData(IHaveConfig config, String key, String defName) { public static ReadOnlyConfigData<Mono<Role>> roleData(IHaveConfig config, String key, String defName) {
return roleData(config, key, defName, DiscordPlugin.mainServer); return roleData(config, key, defName, Mono.just(DiscordPlugin.mainServer));
} }
public static ConfigData<IRole> roleData(IHaveConfig config, String key, String defName, IGuild guild) { /**
return config.getDataPrimDef(key, defName, name -> { * Needs to be a {@link ConfigData} for checking if it's set
if (!(name instanceof String)) return null; */
val roles = guild.getRolesByName((String) name); public static ReadOnlyConfigData<Mono<Role>> roleData(IHaveConfig config, String key, String defName, Mono<Guild> guild) {
return roles.size() > 0 ? roles.get(0) : null; return config.getReadOnlyDataPrimDef(key, defName, name -> {
}, IIDLinkedObject::getLongID); if (!(name instanceof String)) return Mono.empty();
return guild.flatMapMany(Guild::getRoles).filter(r -> r.getName().equals(name)).next();
}, r -> defName);
}
public static ConfigData<Snowflake> snowflakeData(IHaveConfig config, String key, long defID) {
return config.getDataPrimDef(key, defID, id -> Snowflake.of((long) id), Snowflake::asLong);
} }
/** /**
@ -134,10 +88,8 @@ public final class DPUtils {
* @return The string for mentioning the channel * @return The string for mentioning the channel
*/ */
public static String botmention() { public static String botmention() {
IChannel channel; if (DiscordPlugin.plugin == null) return "#bot";
if (DiscordPlugin.plugin == null return channelMention(DiscordPlugin.plugin.commandChannel().get());
|| (channel = DiscordPlugin.plugin.CommandChannel().get()) == null) return "#bot";
return channel.mention();
} }
/** /**
@ -149,14 +101,29 @@ public final class DPUtils {
*/ */
public static boolean disableIfConfigError(@Nullable Component<DiscordPlugin> component, ConfigData<?>... configs) { public static boolean disableIfConfigError(@Nullable Component<DiscordPlugin> component, ConfigData<?>... configs) {
for (val config : configs) { for (val config : configs) {
if (config.get() == null) { Object v = config.get();
if (disableIfConfigErrorRes(component, config, v))
return true;
}
return false;
}
/**
* Disables the component if one of the given configs return null. Useful for channel/role configs.
*
* @param component The component to disable if needed
* @param config The (snowflake) config to check for null
* @param result The result of getting the value
* @return Whether the component got disabled and a warning logged
*/
public static boolean disableIfConfigErrorRes(@Nullable Component<DiscordPlugin> component, ConfigData<?> config, Object result) {
//noinspection ConstantConditions
if (result == null || (result instanceof Mono<?> && !((Mono<?>) result).hasElement().block())) {
String path = null; String path = null;
try { try {
if (component != null) if (component != null)
Component.setComponentEnabled(component, false); Component.setComponentEnabled(component, false);
val f = ConfigData.class.getDeclaredField("path"); path = config.getPath();
f.setAccessible(true); //Hacking my own plugin
path = (String) f.get(config);
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Failed to disable component after config error!", e); TBMCCoreAPI.SendException("Failed to disable component after config error!", e);
} }
@ -164,8 +131,36 @@ public final class DPUtils {
getLogger().warning("Set the correct ID in the config" + (component == null ? "" : " or disable this component") + " to remove this message."); getLogger().warning("Set the correct ID in the config" + (component == null ? "" : " or disable this component") + " to remove this message.");
return true; return true;
} }
}
return false; return false;
} }
public static Mono<Message> reply(Message original, @Nullable MessageChannel channel, String message) {
Mono<MessageChannel> ch;
if (channel == null)
ch = original.getChannel();
else
ch = Mono.just(channel);
return ch.flatMap(chan -> chan.createMessage((original.getAuthor().isPresent()
? original.getAuthor().get().getMention() + ", " : "") + message));
}
public static String nickMention(Snowflake userId) {
return "<@!" + userId.asString() + ">";
}
public static String channelMention(Snowflake channelId) {
return "<#" + channelId.asString() + ">";
}
public static Mono<MessageChannel> getMessageChannel(String key, Snowflake id) {
return DiscordPlugin.dc.getChannelById(id).onErrorResume(e -> {
getLogger().warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage());
return Mono.empty();
}).filter(ch -> ch instanceof MessageChannel).cast(MessageChannel.class);
}
public static Mono<MessageChannel> getMessageChannel(ConfigData<Snowflake> config) {
return getMessageChannel(config.getPath(), config.get());
}
} }

View file

@ -1,19 +1,24 @@
package buttondevteam.discordplugin; package buttondevteam.discordplugin;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer; import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
import sx.blah.discord.handle.obj.IChannel; import lombok.Setter;
import sx.blah.discord.handle.obj.IUser;
import java.util.UUID; import java.util.UUID;
public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer<DiscordConnectedPlayer> { public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer<DiscordConnectedPlayer> {
private static int nextEntityId = 10000; private static int nextEntityId = 10000;
private @Getter VanillaCommandListener<DiscordConnectedPlayer> vanillaCmdListener; private @Getter VanillaCommandListener<DiscordConnectedPlayer> vanillaCmdListener;
@Getter
@Setter
private boolean loggedIn = false;
public DiscordConnectedPlayer(IUser user, IChannel channel, UUID uuid, String mcname) { public DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, MinecraftChatModule module) {
super(user, channel, nextEntityId++, uuid, mcname); super(user, channel, nextEntityId++, uuid, mcname, module);
vanillaCmdListener = new VanillaCommandListener<>(this); vanillaCmdListener = new VanillaCommandListener<>(this);
} }

View file

@ -20,7 +20,7 @@ public class DiscordPlayer extends ChromaGamerBase {
/** /**
* Returns true if player has the private Minecraft chat enabled. For setting the value, see * Returns true if player has the private Minecraft chat enabled. For setting the value, see
* {@link MCChatPrivate#privateMCChat(sx.blah.discord.handle.obj.IChannel, boolean, sx.blah.discord.handle.obj.IUser, DiscordPlayer)} * {@link MCChatPrivate#privateMCChat(sx.blah.discord.handle.obj.MessageChannel, boolean, sx.blah.discord.handle.obj.User, DiscordPlayer)}
*/ */
public boolean isMinecraftChatEnabled() { public boolean isMinecraftChatEnabled() {
return MCChatPrivate.isMinecraftChatEnabled(this); return MCChatPrivate.isMinecraftChatEnabled(this);

View file

@ -1,6 +1,8 @@
package buttondevteam.discordplugin; package buttondevteam.discordplugin;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.advancement.Advancement; import org.bukkit.advancement.Advancement;
@ -26,8 +28,6 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.*; import java.util.*;
@ -38,7 +38,7 @@ public class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer<
protected Player player; protected Player player;
private @Getter VanillaCommandListener<DiscordPlayerSender> vanillaCmdListener; private @Getter VanillaCommandListener<DiscordPlayerSender> vanillaCmdListener;
public DiscordPlayerSender(IUser user, IChannel channel, Player player) { public DiscordPlayerSender(User user, MessageChannel channel, Player player) {
super(user, channel); super(user, channel);
this.player = player; this.player = player;
vanillaCmdListener = new VanillaCommandListener<DiscordPlayerSender>(this); vanillaCmdListener = new VanillaCommandListener<DiscordPlayerSender>(this);

View file

@ -10,16 +10,27 @@ import buttondevteam.discordplugin.listeners.MCListener;
import buttondevteam.discordplugin.mcchat.MCChatPrivate; import buttondevteam.discordplugin.mcchat.MCChatPrivate;
import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.mccommands.DiscordMCCommandBase; import buttondevteam.discordplugin.mccommands.DiscordMCCommand;
import buttondevteam.discordplugin.mccommands.ResetMCCommand;
import buttondevteam.discordplugin.role.GameRoleModule; import buttondevteam.discordplugin.role.GameRoleModule;
import buttondevteam.discordplugin.util.Timings;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.architecture.IHaveConfig;
import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.ChromaGamerBase;
import com.google.common.io.Files; import com.google.common.io.Files;
import discord4j.core.DiscordClient;
import discord4j.core.DiscordClientBuilder;
import discord4j.core.event.domain.guild.GuildCreateEvent;
import discord4j.core.event.domain.lifecycle.ReadyEvent;
import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.Role;
import discord4j.core.object.presence.Activity;
import discord4j.core.object.presence.Presence;
import discord4j.core.object.reaction.ReactionEmoji;
import discord4j.core.object.util.Snowflake;
import discord4j.store.jdk.JdkStoreService;
import lombok.Getter; import lombok.Getter;
import lombok.val; import lombok.val;
import net.milkbowl.vault.permission.Permission; import net.milkbowl.vault.permission.Permission;
@ -27,125 +38,123 @@ import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.scheduler.BukkitTask; import reactor.core.publisher.Mono;
import sx.blah.discord.api.ClientBuilder;
import sx.blah.discord.api.IDiscordClient;
import sx.blah.discord.api.events.IListener;
import sx.blah.discord.api.internal.json.objects.EmbedObject;
import sx.blah.discord.handle.impl.events.ReadyEvent;
import sx.blah.discord.handle.impl.obj.ReactionEmoji;
import sx.blah.discord.handle.obj.*;
import sx.blah.discord.util.EmbedBuilder;
import sx.blah.discord.util.RequestBuffer;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class DiscordPlugin extends ButtonPlugin implements IListener<ReadyEvent> { @ButtonPlugin.ConfigOpts(disableConfigGen = true)
public static IDiscordClient dc; public class DiscordPlugin extends ButtonPlugin {
public static DiscordClient dc;
public static DiscordPlugin plugin; public static DiscordPlugin plugin;
public static boolean SafeMode = true; public static boolean SafeMode = true;
@Getter @Getter
private Command2DC manager; private Command2DC manager;
public ConfigData<Character> Prefix() { private ConfigData<Character> prefix() {
return getIConfig().getData("prefix", '/', str -> ((String) str).charAt(0), Object::toString); return getIConfig().getData("prefix", '/', str -> ((String) str).charAt(0), Object::toString);
} }
public static char getPrefix() { public static char getPrefix() {
if (plugin == null) return '/'; if (plugin == null) return '/';
return plugin.Prefix().get(); return plugin.prefix().get();
} }
public ConfigData<IGuild> MainServer() { private ConfigData<Optional<Guild>> mainServer() {
return getIConfig().getDataPrimDef("mainServer", 219529124321034241L, id -> dc.getGuildByID((long) id), IIDLinkedObject::getLongID); return getIConfig().getDataPrimDef("mainServer", 0L,
id -> {
//It attempts to get the default as well
if ((long) id == 0L)
return Optional.empty(); //Hack?
return dc.getGuildById(Snowflake.of((long) id))
.onErrorResume(t -> Mono.fromRunnable(() -> getLogger().warning("Failed to get guild: " + t.getMessage()))).blockOptional();
},
g -> g.map(gg -> gg.getId().asLong()).orElse(0L));
} }
public ConfigData<IChannel> CommandChannel() { public ConfigData<Snowflake> commandChannel() {
return DPUtils.channelData(getIConfig(), "commandChannel", 239519012529111040L); return DPUtils.snowflakeData(getIConfig(), "commandChannel", 239519012529111040L);
} }
public ConfigData<IRole> ModRole() { /**
* If the role doesn't exist, then it will only allow for the owner.
*/
public ConfigData<Mono<Role>> modRole() {
return DPUtils.roleData(getIConfig(), "modRole", "Moderator"); return DPUtils.roleData(getIConfig(), "modRole", "Moderator");
} }
/**
* The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access.
*/
public ConfigData<String> inviteLink() {
return getIConfig().getData("inviteLink", "");
}
@Override @Override
public void pluginEnable() { public void pluginEnable() {
try { try {
getLogger().info("Initializing..."); getLogger().info("Initializing...");
plugin = this; plugin = this;
manager = new Command2DC(); manager = new Command2DC();
ClientBuilder cb = new ClientBuilder(); String token;
File tokenFile = new File("TBMC", "Token.txt"); File tokenFile = new File("TBMC", "Token.txt");
if (tokenFile.exists()) //Legacy support if (tokenFile.exists()) //Legacy support
//noinspection UnstableApiUsage //noinspection UnstableApiUsage
cb.withToken(Files.readFirstLine(tokenFile, StandardCharsets.UTF_8)); token = Files.readFirstLine(tokenFile, StandardCharsets.UTF_8);
else { else {
File privateFile = new File(getDataFolder(), "private.yml"); File privateFile = new File(getDataFolder(), "private.yml");
val conf = YamlConfiguration.loadConfiguration(privateFile); val conf = YamlConfiguration.loadConfiguration(privateFile);
String token = conf.getString("token"); token = conf.getString("token");
if (token == null) { if (token == null || token.equalsIgnoreCase("Token goes here")) {
conf.set("token", "Token goes here"); conf.set("token", "Token goes here");
conf.save(privateFile); conf.save(privateFile);
getLogger().severe("Token not found! Set it in private.yml"); getLogger().severe("Token not found! Set it in private.yml");
Bukkit.getPluginManager().disablePlugin(this); Bukkit.getPluginManager().disablePlugin(this);
return; return;
} else
cb.withToken(token);
} }
dc = cb.login(); }
dc.getDispatcher().registerListener(this); val cb = new DiscordClientBuilder(token);
cb.setInitialPresence(Presence.doNotDisturb(Activity.playing("booting")));
cb.setStoreService(new JdkStoreService()); //The default doesn't work for some reason - it's waaay faster now
dc = cb.build();
dc.getEventDispatcher().on(ReadyEvent.class) // Listen for ReadyEvent(s)
.map(event -> event.getGuilds().size()) // Get how many guilds the bot is in
.flatMap(size -> dc.getEventDispatcher()
.on(GuildCreateEvent.class) // Listen for GuildCreateEvent(s)
.take(size) // Take only the first `size` GuildCreateEvent(s) to be received
.collectList()) // Take all received GuildCreateEvents and make it a List
.subscribe(this::handleReady); /* All guilds have been received, client is fully connected */
dc.login().subscribe();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Bukkit.getPluginManager().disablePlugin(this); Bukkit.getPluginManager().disablePlugin(this);
} }
} }
public static IGuild mainServer; public static Guild mainServer;
private static volatile BukkitTask task; private void handleReady(List<GuildCreateEvent> event) {
private static volatile boolean sent = false;
@Override
public void handle(ReadyEvent event) {
try { try {
dc.changePresence(StatusType.DND, ActivityType.PLAYING, "booting"); mainServer = mainServer().get().orElse(null); //Shouldn't change afterwards
val tries = new AtomicInteger();
task = Bukkit.getScheduler().runTaskTimerAsynchronously(this, () -> {
tries.incrementAndGet();
if (tries.get() > 10) { //5 seconds
task.cancel();
getLogger().severe("Main server not found! Invite the bot and do /discord reset");
//getIConfig().getConfig().set("mainServer", 219529124321034241L); //Needed because it won't save as long as it's null - made it save
saveConfig(); //Put default there
return;
}
mainServer = MainServer().get(); //Shouldn't change afterwards
if (mainServer == null) { if (mainServer == null) {
val guilds = dc.getGuilds(); if (event.size() == 0) {
if (guilds.size() == 0) getLogger().severe("Main server not found! Invite the bot and do /discord reset");
return; //If there are no guilds in cache, retry saveConfig(); //Put default there
mainServer = guilds.get(0); return; //We should have all guilds by now, no need to retry
getLogger().warning("Main server set to first one: " + mainServer.getName());
MainServer().set(mainServer); //Save in config
} }
if (!TBMCCoreAPI.IsTestServer()) { //Don't change conditions here, see mainServer=devServer=null in onDisable() mainServer = event.get(0).getGuild();
dc.changePresence(StatusType.ONLINE, ActivityType.PLAYING, "Minecraft"); getLogger().warning("Main server set to first one: " + mainServer.getName());
} else { mainServer().set(Optional.of(mainServer)); //Save in config
dc.changePresence(StatusType.ONLINE, ActivityType.PLAYING, "testing");
} }
SafeMode = false; SafeMode = false;
if (task != null) DPUtils.disableIfConfigErrorRes(null, commandChannel(), DPUtils.getMessageChannel(commandChannel()));
task.cancel(); DPUtils.disableIfConfigError(null, modRole()); //Won't disable, just prints the warning here
if (!sent) {
DPUtils.disableIfConfigError(null, CommandChannel(), ModRole()); //Won't disable, just prints the warning here
Component.registerComponent(this, new GeneralEventBroadcasterModule()); Component.registerComponent(this, new GeneralEventBroadcasterModule());
Component.registerComponent(this, new MinecraftChatModule()); Component.registerComponent(this, new MinecraftChatModule());
@ -160,46 +169,51 @@ public class DiscordPlugin extends ButtonPlugin implements IListener<ReadyEvent>
getManager().registerCommand(new HelpCommand()); getManager().registerCommand(new HelpCommand());
getManager().registerCommand(new DebugCommand()); getManager().registerCommand(new DebugCommand());
getManager().registerCommand(new ConnectCommand()); getManager().registerCommand(new ConnectCommand());
if (ResetMCCommand.resetting) //These will only execute if the chat is enabled if (DiscordMCCommand.resetting) //These will only execute if the chat is enabled
ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.CYAN) ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.CYAN)
.withTitle("Discord plugin restarted - chat connected.").build(), ChannelconBroadcast.RESTART); //Really important to note the chat, hmm .setTitle("Discord plugin restarted - chat connected."))), ChannelconBroadcast.RESTART); //Really important to note the chat, hmm
else if (getConfig().getBoolean("serverup", false)) { else if (getConfig().getBoolean("serverup", false)) {
ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.YELLOW) ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.YELLOW)
.withTitle("Server recovered from a crash - chat connected.").build(), ChannelconBroadcast.RESTART); .setTitle("Server recovered from a crash - chat connected."))), ChannelconBroadcast.RESTART);
val thr = new Throwable( val thr = new Throwable(
"The server shut down unexpectedly. See the log of the previous run for more details."); "The server shut down unexpectedly. See the log of the previous run for more details.");
thr.setStackTrace(new StackTraceElement[0]); thr.setStackTrace(new StackTraceElement[0]);
TBMCCoreAPI.SendException("The server crashed!", thr); TBMCCoreAPI.SendException("The server crashed!", thr);
} else } else
ChromaBot.getInstance().sendMessageCustomAsWell("", new EmbedBuilder().withColor(Color.GREEN) ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.GREEN)
.withTitle("Server started - chat connected.").build(), ChannelconBroadcast.RESTART); .setTitle("Server started - chat connected."))), ChannelconBroadcast.RESTART);
ResetMCCommand.resetting = false; //This is the last event handling this flag DiscordMCCommand.resetting = false; //This is the last event handling this flag
getConfig().set("serverup", true); getConfig().set("serverup", true);
saveConfig(); saveConfig();
sent = true; if (TBMCCoreAPI.IsTestServer() && !Objects.requireNonNull(dc.getSelf().block()).getUsername().toLowerCase().contains("test")) {
if (TBMCCoreAPI.IsTestServer() && !dc.getOurUser().getName().toLowerCase().contains("test")) {
TBMCCoreAPI.SendException( TBMCCoreAPI.SendException(
"Won't load because we're in testing mode and not using a separate account.", "Won't load because we're in testing mode and not using a separate account.",
new Exception( new Exception(
"The plugin refuses to load until you change the token to a testing account. (The account needs to have \"test\" in it's name.)")); "The plugin refuses to load until you change the token to a testing account. (The account needs to have \"test\" in its name.)"
+ "\nYou can disable test mode in ThorpeCore config."));
Bukkit.getPluginManager().disablePlugin(this); Bukkit.getPluginManager().disablePlugin(this);
} }
TBMCCoreAPI.SendUnsentExceptions(); TBMCCoreAPI.SendUnsentExceptions();
TBMCCoreAPI.SendUnsentDebugMessages(); TBMCCoreAPI.SendUnsentDebugMessages();
}
}, 0, 10); CommonListeners.register(dc.getEventDispatcher());
for (IListener<?> listener : CommonListeners.getListeners())
dc.getDispatcher().registerListener(listener);
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this); TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this);
TBMCChatAPI.AddCommands(this, DiscordMCCommandBase.class); getCommand2MC().registerCommand(new DiscordMCCommand());
TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class); TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class);
ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof DiscordSenderBase ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof DiscordSenderBase
? ((DiscordSenderBase) sender).getChromaUser() : null)); ? ((DiscordSenderBase) sender).getChromaUser() : null));
setupProviders(); setupProviders();
IHaveConfig.pregenConfig(this, null);
if (!TBMCCoreAPI.IsTestServer()) {
dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe();
} else {
dc.updatePresence(Presence.online(Activity.playing("testing"))).subscribe();
}
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while enabling DiscordPlugin!", e); TBMCCoreAPI.SendException("An error occurred while enabling DiscordPlugin!", e);
} }
} }
@ -211,35 +225,35 @@ public class DiscordPlugin extends ButtonPlugin implements IListener<ReadyEvent>
@Override @Override
public void pluginPreDisable() { public void pluginPreDisable() {
if (ChromaBot.getInstance() == null) return; //Failed to load if (ChromaBot.getInstance() == null) return; //Failed to load
EmbedObject embed; Timings timings = new Timings();
if (ResetMCCommand.resetting) timings.printElapsed("Disable start");
embed = new EmbedBuilder().withColor(Color.ORANGE).withTitle("Discord plugin restarting").build(); MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> {
timings.printElapsed("Sending message to " + ch.getMention());
if (DiscordMCCommand.resetting)
ecs.setColor(Color.ORANGE).setTitle("Discord plugin restarting");
else else
embed = new EmbedBuilder().withColor(Restart ? Color.ORANGE : Color.RED) ecs.setColor(Restart ? Color.ORANGE : Color.RED)
.withTitle(Restart ? "Server restarting" : "Server stopping") .setTitle(Restart ? "Server restarting" : "Server stopping")
.withDescription( .setDescription(
Bukkit.getOnlinePlayers().size() > 0 Bukkit.getOnlinePlayers().size() > 0
? (DPUtils ? (DPUtils
.sanitizeString(Bukkit.getOnlinePlayers().stream() .sanitizeString(Bukkit.getOnlinePlayers().stream()
.map(Player::getDisplayName).collect(Collectors.joining(", "))) .map(Player::getDisplayName).collect(Collectors.joining(", ")))
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ") + (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
+ "kicked the hell out.") //TODO: Make configurable + "kicked the hell out.") //TODO: Make configurable
: "") //If 'restart' is disabled then this isn't shown even if joinleave is enabled : ""); //If 'restart' is disabled then this isn't shown even if joinleave is enabled
.build(); })).subscribe(), ChannelconBroadcast.RESTART, false);
MCChatUtils.forCustomAndAllMCChat(ch -> { timings.printElapsed("Updating player list");
try {
DiscordPlugin.sendMessageToChannelWait(ch, "",
embed, 5, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException e) {
e.printStackTrace();
}
}, ChannelconBroadcast.RESTART, false);
ChromaBot.getInstance().updatePlayerList(); ChromaBot.getInstance().updatePlayerList();
timings.printElapsed("Done");
} }
@Override @Override
public void pluginDisable() { public void pluginDisable() {
Timings timings = new Timings();
timings.printElapsed("Actual disable start (logout)");
MCChatPrivate.logoutAll(); MCChatPrivate.logoutAll();
timings.printElapsed("Config setup");
getConfig().set("serverup", false); getConfig().set("serverup", false);
if (ChromaBot.getInstance() == null) return; //Failed to load if (ChromaBot.getInstance() == null) return; //Failed to load
@ -247,80 +261,21 @@ public class DiscordPlugin extends ButtonPlugin implements IListener<ReadyEvent>
try { try {
SafeMode = true; // Stop interacting with Discord SafeMode = true; // Stop interacting with Discord
ChromaBot.delete(); ChromaBot.delete();
dc.changePresence(StatusType.IDLE, ActivityType.PLAYING, "Chromacraft"); //No longer using the same account for testing timings.printElapsed("Updating presence...");
dc.logout(); dc.updatePresence(Presence.idle(Activity.playing("Chromacraft"))).block(); //No longer using the same account for testing
timings.printElapsed("Logging out...");
dc.logout().block();
//Configs are emptied so channels and servers are fetched again //Configs are emptied so channels and servers are fetched again
sent = false;
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e); TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e);
} }
} }
public static final ReactionEmoji DELIVERED_REACTION = ReactionEmoji.of(""); public static final ReactionEmoji DELIVERED_REACTION = ReactionEmoji.unicode("");
public static void sendMessageToChannel(IChannel channel, String message) {
sendMessageToChannel(channel, message, null);
}
public static void sendMessageToChannel(IChannel channel, String message, EmbedObject embed) {
try {
sendMessageToChannel(channel, message, embed, false);
} catch (TimeoutException | InterruptedException e) {
e.printStackTrace(); //Shouldn't happen, as we're not waiting on the result
}
}
public static IMessage sendMessageToChannelWait(IChannel channel, String message) throws TimeoutException, InterruptedException {
return sendMessageToChannelWait(channel, message, null);
}
public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed) throws TimeoutException, InterruptedException {
return sendMessageToChannel(channel, message, embed, true);
}
public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException {
return sendMessageToChannel(channel, message, embed, true, timeout, unit);
}
private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait) throws TimeoutException, InterruptedException {
return sendMessageToChannel(channel, message, embed, wait, -1, null);
}
private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException {
if (message.length() > 1980) {
message = message.substring(0, 1980);
DPUtils.getLogger()
.warning("Message was too long to send to discord and got truncated. In " + channel.getName());
}
try {
MCChatUtils.resetLastMessage(channel); // If this is a chat message, it'll be set again
final String content = message;
RequestBuffer.IRequest<IMessage> r = () -> embed == null ? channel.sendMessage(content)
: channel.sendMessage(content, embed, false);
if (wait) {
if (unit != null)
return DPUtils.perform(r, timeout, unit);
else
return DPUtils.perform(r);
} else {
if (unit != null)
plugin.getLogger().warning("Tried to set timeout for non-waiting call.");
else
DPUtils.performNoWait(r);
return null;
}
} catch (TimeoutException | InterruptedException e) {
throw e;
} catch (Exception e) {
DPUtils.getLogger().warning(
"Failed to deliver message to Discord! Channel: " + channel.getName() + " Message: " + message);
throw new RuntimeException(e);
}
}
public static Permission perms; public static Permission perms;
public boolean setupProviders() { private boolean setupProviders() {
try { try {
Class.forName("net.milkbowl.vault.permission.Permission"); Class.forName("net.milkbowl.vault.permission.Permission");
Class.forName("net.milkbowl.vault.chat.Chat"); Class.forName("net.milkbowl.vault.chat.Chat");

View file

@ -1,10 +0,0 @@
package buttondevteam.discordplugin;
import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.MissingPermissionsException;
import sx.blah.discord.util.RateLimitException;
@FunctionalInterface
public interface DiscordRunnable {
public abstract void run() throws DiscordException, RateLimitException, MissingPermissionsException;
}

View file

@ -1,5 +1,9 @@
package buttondevteam.discordplugin; package buttondevteam.discordplugin;
import discord4j.core.object.entity.Member;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -8,8 +12,6 @@ import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
import java.util.Set; import java.util.Set;
@ -18,12 +20,13 @@ public class DiscordSender extends DiscordSenderBase implements CommandSender {
private String name; private String name;
public DiscordSender(IUser user, IChannel channel) { public DiscordSender(User user, MessageChannel channel) {
super(user, channel); super(user, channel);
name = user == null ? "Discord user" : user.getDisplayName(DiscordPlugin.mainServer); val def = "Discord user";
name = user == null ? def : user.asMember(DiscordPlugin.mainServer.getId()).blockOptional().map(Member::getDisplayName).orElse(def);
} }
public DiscordSender(IUser user, IChannel channel, String name) { public DiscordSender(User user, MessageChannel channel, String name) {
super(user, channel); super(user, channel);
this.name = name; this.name = name;
} }

View file

@ -1,20 +1,20 @@
package buttondevteam.discordplugin; package buttondevteam.discordplugin;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
public abstract class DiscordSenderBase implements CommandSender { public abstract class DiscordSenderBase implements CommandSender {
/** /**
* May be null. * May be null.
*/ */
protected IUser user; protected User user;
protected IChannel channel; protected MessageChannel channel;
protected DiscordSenderBase(IUser user, IChannel channel) { protected DiscordSenderBase(User user, MessageChannel channel) {
this.user = user; this.user = user;
this.channel = channel; this.channel = channel;
} }
@ -27,11 +27,11 @@ public abstract class DiscordSenderBase implements CommandSender {
* *
* @return The user or null. * @return The user or null.
*/ */
public IUser getUser() { public User getUser() {
return user; return user;
} }
public IChannel getChannel() { public MessageChannel getChannel() {
return channel; return channel;
} }
@ -43,7 +43,7 @@ public abstract class DiscordSenderBase implements CommandSender {
* @return A Chroma user of Discord or a Discord user of Chroma * @return A Chroma user of Discord or a Discord user of Chroma
*/ */
public DiscordPlayer getChromaUser() { public DiscordPlayer getChromaUser() {
if (chromaUser == null) chromaUser = DiscordPlayer.getUser(user.getStringID(), DiscordPlayer.class); if (chromaUser == null) chromaUser = DiscordPlayer.getUser(user.getId().asString(), DiscordPlayer.class);
return chromaUser; return chromaUser;
} }
@ -58,8 +58,7 @@ public abstract class DiscordSenderBase implements CommandSender {
msgtosend += "\n" + sendmsg; msgtosend += "\n" + sendmsg;
if (sendtask == null) if (sendtask == null)
sendtask = Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> { sendtask = Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> {
DiscordPlugin.sendMessageToChannel(channel, channel.createMessage((!broadcast && user != null ? user.getMention() + "\n" : "") + msgtosend.trim()).subscribe();
(!broadcast && user != null ? user.mention() + "\n" : "") + msgtosend.trim());
sendtask = null; sendtask = null;
msgtosend = ""; msgtosend = "";
}, 4); // Waits a 0.2 second to gather all/most of the different messages }, 4); // Waits a 0.2 second to gather all/most of the different messages

View file

@ -1,11 +0,0 @@
package buttondevteam.discordplugin;
import sx.blah.discord.handle.obj.IDiscordObject;
import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.MissingPermissionsException;
import sx.blah.discord.util.RateLimitException;
@FunctionalInterface
public interface DiscordSupplier<T extends IDiscordObject<T>> {
public abstract T get() throws DiscordException, RateLimitException, MissingPermissionsException;
}

View file

@ -6,40 +6,48 @@ import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.ReadOnlyConfigData;
import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.ChromaGamerBase;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.MessageChannel;
import lombok.val; import lombok.val;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import sx.blah.discord.handle.obj.IChannel; import reactor.core.publisher.Flux;
import sx.blah.discord.handle.obj.IMessage; import reactor.core.publisher.Mono;
import java.io.File; import java.io.File;
import java.util.List;
public class AnnouncerModule extends Component<DiscordPlugin> { public class AnnouncerModule extends Component<DiscordPlugin> {
public ConfigData<IChannel> channel() { /**
* Channel to post new posts.
*/
public ReadOnlyConfigData<Mono<MessageChannel>> channel() {
return DPUtils.channelData(getConfig(), "channel", 239519012529111040L); return DPUtils.channelData(getConfig(), "channel", 239519012529111040L);
} }
public ConfigData<IChannel> modChannel() { /**
* Channel where distinguished (moderator) posts go.
*/
public ReadOnlyConfigData<Mono<MessageChannel>> modChannel() {
return DPUtils.channelData(getConfig(), "modChannel", 239519012529111040L); return DPUtils.channelData(getConfig(), "modChannel", 239519012529111040L);
} }
/** /**
* Set to 0 or >50 to disable * Automatically unpins all messages except the last few. Set to 0 or >50 to disable
*/ */
public ConfigData<Short> keepPinned() { public ConfigData<Short> keepPinned() {
return getConfig().getData("keepPinned", (short) 40); return getConfig().getData("keepPinned", (short) 40);
} }
private ConfigData<Long> lastannouncementtime() { private ConfigData<Long> lastAnnouncementTime() {
return getConfig().getData("lastAnnouncementTime", 0L); return getConfig().getData("lastAnnouncementTime", 0L);
} }
private ConfigData<Long> lastseentime() { private ConfigData<Long> lastSeenTime() {
return getConfig().getData("lastSeenTime", 0L); return getConfig().getData("lastSeenTime", 0L);
} }
@ -50,24 +58,15 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
protected void enable() { protected void enable() {
if (DPUtils.disableIfConfigError(this, channel(), modChannel())) return; if (DPUtils.disableIfConfigError(this, channel(), modChannel())) return;
stop = false; //If not the first time stop = false; //If not the first time
DPUtils.performNoWait(() -> {
try {
val keepPinned = keepPinned().get(); val keepPinned = keepPinned().get();
if (keepPinned == 0) return; if (keepPinned == 0) return;
val channel = channel().get(); Flux<Message> msgs = channel().get().flatMapMany(MessageChannel::getPinnedMessages);
List<IMessage> msgs = channel.getPinnedMessages(); msgs.subscribe(Message::unpin);
for (int i = msgs.size() - 1; i >= keepPinned; i--) { // Unpin all pinned messages except the newest 10
channel.unpin(msgs.get(i));
Thread.sleep(10);
}
} catch (InterruptedException ignore) {
}
});
val yc = YamlConfiguration.loadConfiguration(new File("plugins/DiscordPlugin", "config.yml")); //Name change val yc = YamlConfiguration.loadConfiguration(new File("plugins/DiscordPlugin", "config.yml")); //Name change
if (lastannouncementtime().get() == 0) //Load old data if (lastAnnouncementTime().get() == 0) //Load old data
lastannouncementtime().set(yc.getLong("lastannouncementtime")); lastAnnouncementTime().set(yc.getLong("lastannouncementtime"));
if (lastseentime().get() == 0) if (lastSeenTime().get() == 0)
lastseentime().set(yc.getLong("lastseentime")); lastSeenTime().set(yc.getLong("lastseentime"));
new Thread(this::AnnouncementGetterThreadMethod).start(); new Thread(this::AnnouncementGetterThreadMethod).start();
} }
@ -88,7 +87,7 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
.get("children").getAsJsonArray(); .get("children").getAsJsonArray();
StringBuilder msgsb = new StringBuilder(); StringBuilder msgsb = new StringBuilder();
StringBuilder modmsgsb = new StringBuilder(); StringBuilder modmsgsb = new StringBuilder();
long lastanntime = lastannouncementtime().get(); long lastanntime = lastAnnouncementTime().get();
for (int i = json.size() - 1; i >= 0; i--) { for (int i = json.size() - 1; i >= 0; i--) {
JsonObject item = json.get(i).getAsJsonObject(); JsonObject item = json.get(i).getAsJsonObject();
final JsonObject data = item.get("data").getAsJsonObject(); final JsonObject data = item.get("data").getAsJsonObject();
@ -101,9 +100,9 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
distinguished = distinguishedjson.getAsString(); distinguished = distinguishedjson.getAsString();
String permalink = "https://www.reddit.com" + data.get("permalink").getAsString(); String permalink = "https://www.reddit.com" + data.get("permalink").getAsString();
long date = data.get("created_utc").getAsLong(); long date = data.get("created_utc").getAsLong();
if (date > lastseentime().get()) if (date > lastSeenTime().get())
lastseentime().set(date); lastSeenTime().set(date);
else if (date > lastannouncementtime().get()) { else if (date > lastAnnouncementTime().get()) {
do { do {
val reddituserclass = ChromaGamerBase.getTypeForFolder("reddit"); val reddituserclass = ChromaGamerBase.getTypeForFolder("reddit");
if (reddituserclass == null) if (reddituserclass == null)
@ -122,13 +121,13 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
} }
} }
if (msgsb.length() > 0) if (msgsb.length() > 0)
channel().get().pin(DiscordPlugin.sendMessageToChannelWait(channel().get(), msgsb.toString())); channel().get().flatMap(ch -> ch.createMessage(msgsb.toString()))
.flatMap(Message::pin).subscribe();
if (modmsgsb.length() > 0) if (modmsgsb.length() > 0)
DiscordPlugin.sendMessageToChannel(modChannel().get(), modmsgsb.toString()); modChannel().get().flatMap(ch -> ch.createMessage(modmsgsb.toString()))
if (lastannouncementtime().get() != lastanntime) { .flatMap(Message::pin).subscribe();
lastannouncementtime().set(lastanntime); // If sending succeeded if (lastAnnouncementTime().get() != lastanntime)
getPlugin().saveConfig(); //TODO: Won't be needed if I implement auto-saving lastAnnouncementTime().set(lastanntime); // If sending succeeded
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -3,6 +3,8 @@ package buttondevteam.discordplugin.commands;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import java.lang.reflect.Method;
public class Command2DC extends Command2<ICommand2DC, Command2DCSender> { public class Command2DC extends Command2<ICommand2DC, Command2DCSender> {
@Override @Override
public void registerCommand(ICommand2DC command) { public void registerCommand(ICommand2DC command) {
@ -10,8 +12,8 @@ public class Command2DC extends Command2<ICommand2DC, Command2DCSender> {
} }
@Override @Override
public boolean hasPermission(Command2DCSender sender, ICommand2DC command) { public boolean hasPermission(Command2DCSender sender, ICommand2DC command, Method method) {
//return !command.isModOnly() || sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.ModRole().get()); //TODO: ModRole may be null; more customisable way? //return !command.isModOnly() || sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.modRole().get()); //TODO: modRole may be null; more customisable way?
return true; return true;
} }
} }

View file

@ -2,20 +2,28 @@ package buttondevteam.discordplugin.commands;
import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DPUtils;
import buttondevteam.lib.chat.Command2Sender; import buttondevteam.lib.chat.Command2Sender;
import discord4j.core.object.entity.Message;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import sx.blah.discord.handle.obj.IMessage; import lombok.val;
@RequiredArgsConstructor @RequiredArgsConstructor
public class Command2DCSender implements Command2Sender { public class Command2DCSender implements Command2Sender {
private final @Getter IMessage message; private final @Getter
Message message;
@Override @Override
public void sendMessage(String message) { public void sendMessage(String message) {
if (message.length() == 0) return; if (message.length() == 0) return;
message = DPUtils.sanitizeString(message); message = DPUtils.sanitizeString(message);
message = Character.toLowerCase(message.charAt(0)) + message.substring(1); message = Character.toLowerCase(message.charAt(0)) + message.substring(1);
this.message.reply(message); val msg = message;
/*this.message.getAuthorAsMember().flatMap(author ->
this.message.getChannel().flatMap(ch ->
ch.createMessage(author.getNicknameMention() + ", " + msg))).subscribe();*/
this.message.getChannel().flatMap(ch ->
ch.createMessage(this.message.getAuthor().map(u -> DPUtils.nickMention(u.getId()) + ", ").orElse("")
+ msg)).subscribe();
} }
@Override @Override

View file

@ -1,7 +1,6 @@
package buttondevteam.discordplugin.commands; package buttondevteam.discordplugin.commands;
import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
@ -28,34 +27,37 @@ public class ConnectCommand extends ICommand2DC {
@Command2.Subcommand @Command2.Subcommand
public boolean def(Command2DCSender sender, String Minecraftname) { public boolean def(Command2DCSender sender, String Minecraftname) {
val message = sender.getMessage(); val message = sender.getMessage();
if (WaitingToConnect.inverse().containsKey(message.getAuthor().getStringID())) { val channel = message.getChannel().block();
DiscordPlugin.sendMessageToChannel(message.getChannel(), val author = message.getAuthor().orElse(null);
"Replacing " + WaitingToConnect.inverse().get(message.getAuthor().getStringID()) + " with " + Minecraftname); if (author == null || channel == null) return true;
WaitingToConnect.inverse().remove(message.getAuthor().getStringID()); if (WaitingToConnect.inverse().containsKey(author.getId().asString())) {
channel.createMessage(
"Replacing " + WaitingToConnect.inverse().get(author.getId().asString()) + " with " + Minecraftname).subscribe();
WaitingToConnect.inverse().remove(author.getId().asString());
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
OfflinePlayer p = Bukkit.getOfflinePlayer(Minecraftname); OfflinePlayer p = Bukkit.getOfflinePlayer(Minecraftname);
if (p == null) { if (p == null) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), "The specified Minecraft player cannot be found"); channel.createMessage("The specified Minecraft player cannot be found").subscribe();
return true; return true;
} }
try (TBMCPlayer pl = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class)) { try (TBMCPlayer pl = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class)) {
DiscordPlayer dp = pl.getAs(DiscordPlayer.class); DiscordPlayer dp = pl.getAs(DiscordPlayer.class);
if (dp != null && message.getAuthor().getStringID().equals(dp.getDiscordID())) { if (dp != null && author.getId().asString().equals(dp.getDiscordID())) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), "You already have this account connected."); channel.createMessage("You already have this account connected.").subscribe();
return true; return true;
} }
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while connecting a Discord account!", e); TBMCCoreAPI.SendException("An error occured while connecting a Discord account!", e);
DiscordPlugin.sendMessageToChannel(message.getChannel(), "An internal error occured!\n" + e); channel.createMessage("An internal error occured!\n" + e).subscribe();
} }
WaitingToConnect.put(p.getName(), message.getAuthor().getStringID()); WaitingToConnect.put(p.getName(), author.getId().asString());
DiscordPlugin.sendMessageToChannel(message.getChannel(), channel.createMessage(
"Alright! Now accept the connection in Minecraft from the account " + Minecraftname "Alright! Now accept the connection in Minecraft from the account " + Minecraftname
+ " before the next server restart. You can also adjust the Minecraft name you want to connect to with the same command."); + " before the next server restart. You can also adjust the Minecraft name you want to connect to with the same command.").subscribe();
if (p.isOnline()) if (p.isOnline())
((Player) p).sendMessage("§bTo connect with the Discord account " + message.getAuthor().getName() + "#" ((Player) p).sendMessage("§bTo connect with the Discord account " + author.getUsername() + "#"
+ message.getAuthor().getDiscriminator() + " do /discord accept"); + author.getDiscriminator() + " do /discord accept");
return true; return true;
} }

View file

@ -4,17 +4,27 @@ import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.listeners.CommonListeners; import buttondevteam.discordplugin.listeners.CommonListeners;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import reactor.core.publisher.Mono;
@CommandClass(helpText = { @CommandClass(helpText = {
"Switches debug mode." "Switches debug mode."
}) })
public class DebugCommand extends ICommand2DC { public class DebugCommand extends ICommand2DC {
@Command2.Subcommand @Command2.Subcommand
public boolean def(Command2DCSender sender, String args) { public boolean def(Command2DCSender sender) {
if (sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.ModRole().get())) sender.getMessage().getAuthorAsMember()
.switchIfEmpty(sender.getMessage().getAuthor() //Support DMs
.map(u -> u.asMember(DiscordPlugin.mainServer.getId()))
.orElse(Mono.empty()))
.flatMap(m -> DiscordPlugin.plugin.modRole().get()
.map(mr -> m.getRoleIds().stream().anyMatch(r -> r.equals(mr.getId())))
.switchIfEmpty(Mono.fromSupplier(() -> DiscordPlugin.mainServer.getOwnerId().asLong() == m.getId().asLong()))) //Role not found
.subscribe(success -> {
if (success)
sender.sendMessage("debug " + (CommonListeners.debug() ? "enabled" : "disabled")); sender.sendMessage("debug " + (CommonListeners.debug() ? "enabled" : "disabled"));
else else
sender.sendMessage("you need to be a moderator to use this command."); sender.sendMessage("you need to be a moderator to use this command.");
});
return true; return true;
} }
} }

View file

@ -1,5 +1,6 @@
package buttondevteam.discordplugin.commands; package buttondevteam.discordplugin.commands;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
@CommandClass(helpText = { @CommandClass(helpText = {
@ -7,12 +8,17 @@ import buttondevteam.lib.chat.CommandClass;
"Shows some info about a command or lists the available commands.", // "Shows some info about a command or lists the available commands.", //
}) })
public class HelpCommand extends ICommand2DC { public class HelpCommand extends ICommand2DC {
@Override @Command2.Subcommand
public boolean def(Command2DCSender sender, String args) { public boolean def(Command2DCSender sender, @Command2.TextArg @Command2.OptionalArg String args) {
if (args.length() == 0) if (args == null || args.length() == 0)
sender.sendMessage(getManager().getCommandsText()); sender.sendMessage(getManager().getCommandsText());
else {
String[] ht = getManager().getHelpText(args);
if (ht == null)
sender.sendMessage("Command not found: " + args);
else else
sender.sendMessage("Soon:tm:"); //TODO sender.sendMessage(ht);
}
return true; return true;
} }
} }

View file

@ -7,13 +7,11 @@ import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.ChromaGamerBase.InfoTarget; import buttondevteam.lib.player.ChromaGamerBase.InfoTarget;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.User;
import lombok.val; import lombok.val;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.IUser;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@CommandClass(helpText = { @CommandClass(helpText = {
"User information", // "User information", //
@ -24,67 +22,71 @@ public class UserinfoCommand extends ICommand2DC {
@Command2.Subcommand @Command2.Subcommand
public boolean def(Command2DCSender sender, @Command2.OptionalArg @Command2.TextArg String user) { public boolean def(Command2DCSender sender, @Command2.OptionalArg @Command2.TextArg String user) {
val message = sender.getMessage(); val message = sender.getMessage();
IUser target = null; User target = null;
val channel = message.getChannel().block();
assert channel != null;
if (user == null || user.length() == 0) if (user == null || user.length() == 0)
target = message.getAuthor(); target = message.getAuthor().orElse(null);
else { else {
final Optional<IUser> firstmention = message.getMentions().stream() @SuppressWarnings("OptionalGetWithoutIsPresent") final User firstmention = message.getUserMentions()
.filter(m -> !m.getStringID().equals(DiscordPlugin.dc.getOurUser().getStringID())).findFirst(); .filter(m -> !m.getId().asString().equals(DiscordPlugin.dc.getSelfId().get().asString())).blockFirst();
if (firstmention.isPresent()) if (firstmention != null)
target = firstmention.get(); target = firstmention;
else if (user.contains("#")) { else if (user.contains("#")) {
String[] targettag = user.split("#"); String[] targettag = user.split("#");
final List<IUser> targets = getUsers(message, targettag[0]); final List<User> targets = getUsers(message, targettag[0]);
if (targets.size() == 0) { if (targets.size() == 0) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), channel.createMessage("The user cannot be found (by name): " + user).subscribe();
"The user cannot be found (by name): " + user);
return true; return true;
} }
for (IUser ptarget : targets) { for (User ptarget : targets) {
if (ptarget.getDiscriminator().equalsIgnoreCase(targettag[1])) { if (ptarget.getDiscriminator().equalsIgnoreCase(targettag[1])) {
target = ptarget; target = ptarget;
break; break;
} }
} }
if (target == null) { if (target == null) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), channel.createMessage("The user cannot be found (by discriminator): " + user + "(Found " + targets.size()
"The user cannot be found (by discriminator): " + user + "(Found " + targets.size() + " users with the name.)").subscribe();
+ " users with the name.)");
return true; return true;
} }
} else { } else {
final List<IUser> targets = getUsers(message, user); final List<User> targets = getUsers(message, user);
if (targets.size() == 0) { if (targets.size() == 0) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), channel.createMessage("The user cannot be found on Discord: " + user).subscribe();
"The user cannot be found on Discord: " + user);
return true; return true;
} }
if (targets.size() > 1) { if (targets.size() > 1) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), channel.createMessage("Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping.").subscribe();
"Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping.");
return true; return true;
} }
target = targets.get(0); target = targets.get(0);
} }
} }
try (DiscordPlayer dp = ChromaGamerBase.getUser(target.getStringID(), DiscordPlayer.class)) { if (target == null) {
StringBuilder uinfo = new StringBuilder("User info for ").append(target.getName()).append(":\n"); sender.sendMessage("An error occurred.");
return true;
}
try (DiscordPlayer dp = ChromaGamerBase.getUser(target.getId().asString(), DiscordPlayer.class)) {
StringBuilder uinfo = new StringBuilder("User info for ").append(target.getUsername()).append(":\n");
uinfo.append(dp.getInfo(InfoTarget.Discord)); uinfo.append(dp.getInfo(InfoTarget.Discord));
DiscordPlugin.sendMessageToChannel(message.getChannel(), uinfo.toString()); channel.createMessage(uinfo.toString()).subscribe();
} catch (Exception e) { } catch (Exception e) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), "An error occured while getting the user!"); channel.createMessage("An error occured while getting the user!").subscribe();
TBMCCoreAPI.SendException("Error while getting info about " + target.getName() + "!", e); TBMCCoreAPI.SendException("Error while getting info about " + target.getUsername() + "!", e);
} }
return true; return true;
} }
private List<IUser> getUsers(IMessage message, String args) { private List<User> getUsers(Message message, String args) {
final List<IUser> targets; final List<User> targets;
if (message.getChannel().isPrivate()) val guild = message.getGuild().block();
targets = DiscordPlugin.dc.getUsers().stream().filter(u -> u.getName().equalsIgnoreCase(args)) if (guild == null) //Private channel
.collect(Collectors.toList()); targets = DiscordPlugin.dc.getUsers().filter(u -> u.getUsername().equalsIgnoreCase(args))
.collectList().block();
else else
targets = message.getGuild().getUsersByName(args, true); targets = guild.getMembers().filter(m -> m.getUsername().equalsIgnoreCase(args))
.map(m -> (User) m).collectList().block();
return targets; return targets;
} }

View file

@ -3,8 +3,10 @@ package buttondevteam.discordplugin.exceptions;
import buttondevteam.core.ComponentManager; import buttondevteam.core.ComponentManager;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.TBMCDebugMessageEvent; import buttondevteam.lib.TBMCDebugMessageEvent;
import discord4j.core.object.entity.MessageChannel;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import reactor.core.publisher.Mono;
public class DebugMessageListener implements Listener { public class DebugMessageListener implements Listener {
@EventHandler @EventHandler
@ -17,13 +19,15 @@ public class DebugMessageListener implements Listener{
if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(ExceptionListenerModule.class)) if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(ExceptionListenerModule.class))
return; return;
try { try {
Mono<MessageChannel> mc = ExceptionListenerModule.getChannel();
if (mc == null) return;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("```").append("\n"); sb.append("```").append("\n");
if (message.length() > 2000) if (message.length() > 2000)
message = message.substring(0, 2000); message = message.substring(0, 2000);
sb.append(message).append("\n"); sb.append(message).append("\n");
sb.append("```"); sb.append("```");
DiscordPlugin.sendMessageToChannel(ExceptionListenerModule.getChannel(), sb.toString()); mc.flatMap(ch -> ch.createMessage(sb.toString())).subscribe();
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
} }

View file

@ -7,13 +7,16 @@ import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCExceptionEvent; import buttondevteam.lib.TBMCExceptionEvent;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.ReadOnlyConfigData;
import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.GuildChannel;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.Role;
import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import sx.blah.discord.handle.obj.IChannel; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.obj.IGuild;
import sx.blah.discord.handle.obj.IRole;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -47,21 +50,28 @@ public class ExceptionListenerModule extends Component<DiscordPlugin> implements
private static void SendException(Throwable e, String sourcemessage) { private static void SendException(Throwable e, String sourcemessage) {
if (instance == null) return; if (instance == null) return;
try { try {
IChannel channel = getChannel(); Mono<MessageChannel> channel = getChannel();
assert channel != null; assert channel != null;
IRole coderRole = instance.pingRole(channel.getGuild()).get(); Mono<Role> coderRole;
StringBuilder sb = TBMCCoreAPI.IsTestServer() ? new StringBuilder() if (channel instanceof GuildChannel)
: new StringBuilder(coderRole == null ? "" : coderRole.mention()).append("\n"); coderRole = instance.pingRole(((GuildChannel) channel).getGuild()).get();
else
coderRole = Mono.empty();
coderRole.map(role -> TBMCCoreAPI.IsTestServer() ? new StringBuilder()
: new StringBuilder(role.getMention()).append("\n"))
.defaultIfEmpty(new StringBuilder())
.flatMap(sb -> {
sb.append(sourcemessage).append("\n"); sb.append(sourcemessage).append("\n");
sb.append("```").append("\n"); sb.append("```").append("\n");
String stackTrace = Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")) String stackTrace = Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n"))
.filter(s -> !s.contains("\tat ") || s.contains("\tat buttondevteam.")) .filter(s -> !s.contains("\tat ") || s.contains("\tat buttondevteam."))
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
if (stackTrace.length() > 1800) if (sb.length() + stackTrace.length() >= 1980)
stackTrace = stackTrace.substring(0, 1800); stackTrace = stackTrace.substring(0, 1980 - sb.length());
sb.append(stackTrace).append("\n"); sb.append(stackTrace).append("\n");
sb.append("```"); sb.append("```");
DiscordPlugin.sendMessageToChannel(channel, sb.toString()); //Instance isn't null here return channel.flatMap(ch -> ch.createMessage(sb.toString()));
}).subscribe();
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
@ -69,16 +79,16 @@ public class ExceptionListenerModule extends Component<DiscordPlugin> implements
private static ExceptionListenerModule instance; private static ExceptionListenerModule instance;
public static IChannel getChannel() { public static Mono<MessageChannel> getChannel() {
if (instance != null) return instance.channel().get(); if (instance != null) return instance.channel().get();
return null; return Mono.empty();
} }
private ConfigData<IChannel> channel() { private ReadOnlyConfigData<Mono<MessageChannel>> channel() {
return DPUtils.channelData(getConfig(), "channel", 239519012529111040L); return DPUtils.channelData(getConfig(), "channel", 239519012529111040L);
} }
private ConfigData<IRole> pingRole(IGuild guild) { private ConfigData<Mono<Role>> pingRole(Mono<Guild> guild) {
return DPUtils.roleData(getConfig(), "pingRole", "Coder", guild); return DPUtils.roleData(getConfig(), "pingRole", "Coder", guild);
} }

View file

@ -6,15 +6,17 @@ import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.ReadOnlyConfigData;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import discord4j.core.event.domain.PresenceUpdateEvent;
import discord4j.core.object.entity.*;
import discord4j.core.object.presence.Status;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import sx.blah.discord.handle.impl.events.user.PresenceUpdateEvent; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.obj.*;
import sx.blah.discord.util.EmbedBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -23,6 +25,9 @@ import java.util.Random;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream; import java.util.stream.IntStream;
/**
* The YEEHAW event uses an emoji named :YEEHAW: if available
*/
public class FunModule extends Component<DiscordPlugin> implements Listener { public class FunModule extends Component<DiscordPlugin> implements Listener {
private static final String[] serverReadyStrings = new String[]{"In one week from now", // Ali private static final String[] serverReadyStrings = new String[]{"In one week from now", // Ali
"Between now and the heat-death of the universe.", // Ghostise "Between now and the heat-death of the universe.", // Ghostise
@ -40,19 +45,23 @@ public class FunModule extends Component<DiscordPlugin> implements Listener {
"When will *you* be open?" // Ali "When will *you* be open?" // Ali
}; };
private ConfigData<Boolean> serverReady() { /**
return getConfig().getData("serverReady", true); * Questions that the bot will choose a random answer to give to.
*/
private ConfigData<String[]> serverReady() {
return getConfig().getData("serverReady", () -> new String[]{"when will the server be open",
"when will the server be ready", "when will the server be done", "when will the server be complete",
"when will the server be finished", "when's the server ready", "when's the server open",
"Vhen vill ze server be open?"});
} }
/**
* Answers for a recognized question. Selected randomly.
*/
private ConfigData<ArrayList<String>> serverReadyAnswers() { private ConfigData<ArrayList<String>> serverReadyAnswers() {
return getConfig().getData("serverReadyAnswers", () -> Lists.newArrayList(serverReadyStrings)); //TODO: Test return getConfig().getData("serverReadyAnswers", () -> Lists.newArrayList(serverReadyStrings)); //TODO: Test
} }
private static final String[] serverReadyQuestions = new String[]{"when will the server be open",
"when will the server be ready", "when will the server be done", "when will the server be complete",
"when will the server be finished", "when's the server ready", "when's the server open",
"Vhen vill ze server be open?"};
private static final Random serverReadyRandom = new Random(); private static final Random serverReadyRandom = new Random();
private static final ArrayList<Short> usableServerReadyStrings = new ArrayList<>(0); private static final ArrayList<Short> usableServerReadyStrings = new ArrayList<>(0);
@ -76,10 +85,10 @@ public class FunModule extends Component<DiscordPlugin> implements Listener {
private static short ListC = 0; private static short ListC = 0;
public static boolean executeMemes(IMessage message) { public static boolean executeMemes(Message message) {
val fm = ComponentManager.getIfEnabled(FunModule.class); val fm = ComponentManager.getIfEnabled(FunModule.class);
if (fm == null) return false; if (fm == null) return false;
String msglowercased = message.getContent().toLowerCase(); String msglowercased = message.getContent().orElse("").toLowerCase();
lastlist++; lastlist++;
if (lastlist > 5) { if (lastlist > 5) {
ListC = 0; ListC = 0;
@ -87,23 +96,21 @@ public class FunModule extends Component<DiscordPlugin> implements Listener {
} }
if (msglowercased.equals("list") && Bukkit.getOnlinePlayers().size() == lastlistp && ListC++ > 2) // Lowered already if (msglowercased.equals("list") && Bukkit.getOnlinePlayers().size() == lastlistp && ListC++ > 2) // Lowered already
{ {
message.reply("Stop it. You know the answer."); DPUtils.reply(message, null, "Stop it. You know the answer.").subscribe();
lastlist = 0; lastlist = 0;
lastlistp = (short) Bukkit.getOnlinePlayers().size(); lastlistp = (short) Bukkit.getOnlinePlayers().size();
return true; //Handled return true; //Handled
} }
lastlistp = (short) Bukkit.getOnlinePlayers().size(); //Didn't handle lastlistp = (short) Bukkit.getOnlinePlayers().size(); //Didn't handle
if (fm.serverReady().get()) {
if (!TBMCCoreAPI.IsTestServer() if (!TBMCCoreAPI.IsTestServer()
&& Arrays.stream(serverReadyQuestions).anyMatch(msglowercased::contains)) { && Arrays.stream(fm.serverReady().get()).anyMatch(msglowercased::contains)) {
int next; int next;
if (usableServerReadyStrings.size() == 0) if (usableServerReadyStrings.size() == 0)
fm.createUsableServerReadyStrings(); fm.createUsableServerReadyStrings();
next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size())); next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size()));
DiscordPlugin.sendMessageToChannel(message.getChannel(), serverReadyStrings[next]); DPUtils.reply(message, null, fm.serverReadyAnswers().get().get(next)).subscribe();
return false; //Still process it as a command/mcchat if needed return false; //Still process it as a command/mcchat if needed
} }
}
return false; return false;
} }
@ -112,12 +119,12 @@ public class FunModule extends Component<DiscordPlugin> implements Listener {
ListC = 0; ListC = 0;
} }
private ConfigData<IRole> fullHouseDevRole(IGuild guild) { private ConfigData<Mono<Role>> fullHouseDevRole(Mono<Guild> guild) {
return DPUtils.roleData(getConfig(), "fullHouseDevRole", "Developer", guild); return DPUtils.roleData(getConfig(), "fullHouseDevRole", "Developer", guild);
} }
private ConfigData<IChannel> fullHouseChannel() { private ReadOnlyConfigData<Mono<MessageChannel>> fullHouseChannel() {
return DPUtils.channelData(getConfig(), "fullHouseChannel", 219626707458457603L); return DPUtils.channelData(getConfig(), "fullHouseChannel", 219626707458457603L);
} }
@ -126,24 +133,24 @@ public class FunModule extends Component<DiscordPlugin> implements Listener {
public static void handleFullHouse(PresenceUpdateEvent event) { public static void handleFullHouse(PresenceUpdateEvent event) {
val fm = ComponentManager.getIfEnabled(FunModule.class); val fm = ComponentManager.getIfEnabled(FunModule.class);
if (fm == null) return; if (fm == null) return;
val channel = fm.fullHouseChannel().get(); if (Calendar.getInstance().get(Calendar.DAY_OF_MONTH) % 5 != 0) return;
if (channel == null) return; fm.fullHouseChannel().get()
val devrole = fm.fullHouseDevRole(channel.getGuild()).get(); .filter(ch -> ch instanceof GuildChannel)
if (devrole == null) return; .flatMap(channel -> fm.fullHouseDevRole(((GuildChannel) channel).getGuild()).get()
if (event.getOldPresence().getStatus().equals(StatusType.OFFLINE) .filter(role -> event.getOld().map(p -> p.getStatus().equals(Status.OFFLINE)).orElse(false))
&& !event.getNewPresence().getStatus().equals(StatusType.OFFLINE) .filter(role -> !event.getCurrent().getStatus().equals(Status.OFFLINE))
&& event.getUser().getRolesForGuild(channel.getGuild()).stream() .filterWhen(devrole -> event.getMember().flatMap(m -> m.getRoles()
.anyMatch(r -> r.getLongID() == devrole.getLongID()) .any(r -> r.getId().asLong() == devrole.getId().asLong())))
&& channel.getGuild().getUsersByRole(devrole).stream() .filterWhen(devrole ->
.noneMatch(u -> u.getPresence().getStatus().equals(StatusType.OFFLINE)) event.getGuild().flatMapMany(g -> g.getMembers().filter(m -> m.getRoleIds().stream().anyMatch(s -> s.equals(devrole.getId()))))
&& lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime()) .flatMap(Member::getPresence).all(pr -> !pr.getStatus().equals(Status.OFFLINE)))
&& Calendar.getInstance().get(Calendar.DAY_OF_MONTH) % 5 == 0) { .filter(devrole -> lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime())) //This should stay so it checks this last
DiscordPlugin.sendMessageToChannel(channel, "Full house!", .flatMap(devrole -> {
new EmbedBuilder()
.withImage(
"https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png")
.build());
lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime()); lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime());
} return channel.createMessage(mcs -> mcs.setContent("Full house!").setEmbed(ecs ->
ecs.setImage(
"https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png")
));
})).subscribe();
} }
} }

View file

@ -1,11 +1,18 @@
package buttondevteam.discordplugin.listeners; package buttondevteam.discordplugin.listeners;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.commands.Command2DCSender; import buttondevteam.discordplugin.commands.Command2DCSender;
import buttondevteam.discordplugin.util.Timings;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import sx.blah.discord.handle.obj.IChannel; import discord4j.core.object.entity.Message;
import sx.blah.discord.handle.obj.IMessage; import discord4j.core.object.entity.MessageChannel;
import sx.blah.discord.handle.obj.IRole; import discord4j.core.object.entity.PrivateChannel;
import discord4j.core.object.entity.Role;
import lombok.val;
import reactor.core.publisher.Mono;
import java.util.concurrent.atomic.AtomicBoolean;
public class CommandListener { public class CommandListener {
/** /**
@ -13,44 +20,61 @@ public class CommandListener {
* *
* @param message The Discord message * @param message The Discord message
* @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message * @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message
* @return Whether it ran the command * @return Whether it <b>did not run</b> the command
*/ */
public static boolean runCommand(IMessage message, boolean mentionedonly) { public static Mono<Boolean> runCommand(Message message, MessageChannel commandChannel, boolean mentionedonly) {
if (message.getContent().length() == 0) Timings timings = CommonListeners.timings;
return false; //Pin messages and such, let the mcchat listener deal with it Mono<Boolean> ret = Mono.just(true);
final IChannel channel = message.getChannel(); if (!message.getContent().isPresent())
return ret; //Pin messages and such, let the mcchat listener deal with it
val content = message.getContent().get();
timings.printElapsed("A");
return message.getChannel().flatMap(channel -> {
Mono<?> tmp = ret;
if (!mentionedonly) { //mentionedonly conditions are in CommonListeners if (!mentionedonly) { //mentionedonly conditions are in CommonListeners
if (!message.getChannel().isPrivate() timings.printElapsed("B");
&& !(message.getContent().charAt(0) == DiscordPlugin.getPrefix() if (!(channel instanceof PrivateChannel)
&& channel.getStringID().equals(DiscordPlugin.plugin.CommandChannel().get().getStringID()))) // && !(content.charAt(0) == DiscordPlugin.getPrefix()
return false; && channel.getId().asLong() == commandChannel.getId().asLong())) //
message.getChannel().setTypingStatus(true); // Fun return ret;
timings.printElapsed("C");
tmp = ret.then(channel.type()).thenReturn(true); // Fun (this true is ignored - x)
} }
final StringBuilder cmdwithargs = new StringBuilder(message.getContent()); final StringBuilder cmdwithargs = new StringBuilder(content);
final String mention = DiscordPlugin.dc.getOurUser().mention(false); val gotmention = new AtomicBoolean();
final String mentionNick = DiscordPlugin.dc.getOurUser().mention(true); timings.printElapsed("Before self");
boolean gotmention = checkanddeletemention(cmdwithargs, mention, message); return tmp.flatMapMany(x ->
gotmention = checkanddeletemention(cmdwithargs, mentionNick, message) || gotmention; DiscordPlugin.dc.getSelf().flatMap(self -> self.asMember(DiscordPlugin.mainServer.getId()))
for (String mentionRole : (Iterable<String>) message.getRoleMentions().stream().filter(r -> DiscordPlugin.dc.getOurUser().hasRole(r)).map(IRole::mention)::iterator) .flatMapMany(self -> {
gotmention = checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention; // Delete all mentions timings.printElapsed("D");
if (mentionedonly && !gotmention) { gotmention.set(checkanddeletemention(cmdwithargs, self.getMention(), message));
message.getChannel().setTypingStatus(false); gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention(), message) || gotmention.get());
return false; val mentions = message.getRoleMentions();
} return self.getRoles().filterWhen(r -> mentions.any(rr -> rr.getName().equals(r.getName())))
message.getChannel().setTypingStatus(true); .map(Role::getMention);
}).map(mentionRole -> {
timings.printElapsed("E");
gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get()); // Delete all mentions
return !mentionedonly || gotmention.get(); //Stops here if false
}).switchIfEmpty(Mono.fromSupplier(() -> !mentionedonly || gotmention.get())))
.filter(b -> b).last(false).filter(b -> b).doOnNext(b -> channel.type().subscribe()).flatMap(b -> {
String cmdwithargsString = cmdwithargs.toString(); String cmdwithargsString = cmdwithargs.toString();
try { try {
timings.printElapsed("F");
if (!DiscordPlugin.plugin.getManager().handleCommand(new Command2DCSender(message), cmdwithargsString)) if (!DiscordPlugin.plugin.getManager().handleCommand(new Command2DCSender(message), cmdwithargsString))
message.reply("Unknown command. Do " + DiscordPlugin.getPrefix() + "help for help.\n" + cmdwithargsString); return DPUtils.reply(message, channel, "Unknown command. Do " + DiscordPlugin.getPrefix() + "help for help.\n" + cmdwithargsString)
.map(m -> false);
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e); TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e);
} }
message.getChannel().setTypingStatus(false); return Mono.just(false); //If the command succeeded or there was an error, return false
return true; }).defaultIfEmpty(true);
});
} }
private static boolean checkanddeletemention(StringBuilder cmdwithargs, String mention, IMessage message) { private static boolean checkanddeletemention(StringBuilder cmdwithargs, String mention, Message message) {
if (message.getContent().startsWith(mention)) // TODO: Resolve mentions: Compound arguments, either a mention or text final char prefix = DiscordPlugin.getPrefix();
if (message.getContent().orElse("").startsWith(mention)) // TODO: Resolve mentions: Compound arguments, either a mention or text
if (cmdwithargs.length() > mention.length() + 1) { if (cmdwithargs.length() > mention.length() + 1) {
int i = cmdwithargs.indexOf(" ", mention.length()); int i = cmdwithargs.indexOf(" ", mention.length());
if (i == -1) if (i == -1)
@ -60,14 +84,16 @@ public class CommandListener {
for (; i < cmdwithargs.length() && cmdwithargs.charAt(i) == ' '; i++) for (; i < cmdwithargs.length() && cmdwithargs.charAt(i) == ' '; i++)
; //Removes any space before the command ; //Removes any space before the command
cmdwithargs.delete(0, i); cmdwithargs.delete(0, i);
cmdwithargs.insert(0, DiscordPlugin.getPrefix()); //Always use the prefix for processing cmdwithargs.insert(0, prefix); //Always use the prefix for processing
} else } else
cmdwithargs.replace(0, cmdwithargs.length(), DiscordPlugin.getPrefix() + "help"); cmdwithargs.replace(0, cmdwithargs.length(), prefix + "help");
else { else {
if (cmdwithargs.length() == 0)
cmdwithargs.replace(0, cmdwithargs.length(), prefix + "help");
else if (cmdwithargs.charAt(0) != prefix)
cmdwithargs.insert(0, prefix);
return false; //Don't treat / as mention, mentions can be used in public mcchat return false; //Don't treat / as mention, mentions can be used in public mcchat
} }
if (cmdwithargs.length() == 0)
cmdwithargs.replace(0, cmdwithargs.length(), DiscordPlugin.getPrefix() + "help");
return true; return true;
} }
} }

View file

@ -5,18 +5,23 @@ import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.fun.FunModule; import buttondevteam.discordplugin.fun.FunModule;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.role.GameRoleModule; import buttondevteam.discordplugin.role.GameRoleModule;
import buttondevteam.discordplugin.util.Timings;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import discord4j.core.event.EventDispatcher;
import discord4j.core.event.domain.PresenceUpdateEvent;
import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.event.domain.role.RoleCreateEvent;
import discord4j.core.event.domain.role.RoleDeleteEvent;
import discord4j.core.event.domain.role.RoleUpdateEvent;
import discord4j.core.object.entity.PrivateChannel;
import lombok.val; import lombok.val;
import sx.blah.discord.api.events.IListener; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;
import sx.blah.discord.handle.impl.events.guild.role.RoleCreateEvent;
import sx.blah.discord.handle.impl.events.guild.role.RoleDeleteEvent;
import sx.blah.discord.handle.impl.events.guild.role.RoleUpdateEvent;
import sx.blah.discord.handle.impl.events.user.PresenceUpdateEvent;
public class CommonListeners { public class CommonListeners {
public static final Timings timings = new Timings();
/* /*
MentionEvent: MentionEvent:
- CommandListener (starts with mention, only 'channelcon' and not in #bot) - CommandListener (starts with mention, only 'channelcon' and not in #bot)
@ -26,42 +31,49 @@ public class CommonListeners {
- Minecraft chat (is enabled in the channel and message isn't [/]mcchat) - Minecraft chat (is enabled in the channel and message isn't [/]mcchat)
- CommandListener (with the correct prefix in #bot, or in private) - CommandListener (with the correct prefix in #bot, or in private)
*/ */
public static IListener<?>[] getListeners() { public static void register(EventDispatcher dispatcher) {
return new IListener[]{new IListener<MessageReceivedEvent>() { dispatcher.on(MessageCreateEvent.class).flatMap(event -> {
@Override timings.printElapsed("Message received");
public void handle(MessageReceivedEvent event) { val def = Mono.empty();
if (DiscordPlugin.SafeMode) if (DiscordPlugin.SafeMode)
return; return def;
if (event.getMessage().getAuthor().isBot()) val author = event.getMessage().getAuthor();
return; if (!author.isPresent() || author.get().isBot())
return def;
if (FunModule.executeMemes(event.getMessage())) if (FunModule.executeMemes(event.getMessage()))
return; return def;
try { val commandChannel = DiscordPlugin.plugin.commandChannel().get();
boolean handled = false; val commandCh = DPUtils.getMessageChannel(DiscordPlugin.plugin.commandChannel());
val commandChannel = DiscordPlugin.plugin.CommandChannel().get(); return commandCh.filterWhen(ch -> event.getMessage().getChannel().map(mch ->
if ((commandChannel != null && event.getChannel().getLongID() == commandChannel.getLongID()) //If mentioned, that's higher than chat (commandChannel != null && mch.getId().asLong() == commandChannel.asLong()) //If mentioned, that's higher than chat
|| event.getMessage().getContent().contains("channelcon")) //Only 'channelcon' is allowed in other channels || mch instanceof PrivateChannel
handled = CommandListener.runCommand(event.getMessage(), true); //#bot is handled here || event.getMessage().getContent().orElse("").contains("channelcon")) //Only 'channelcon' is allowed in other channels
if (handled) return; .flatMap(shouldRun -> { //Only continue if this doesn't handle the event
if (!shouldRun)
return Mono.just(true); //The condition is only for the first command execution, not mcchat
timings.printElapsed("Run command 1");
return CommandListener.runCommand(event.getMessage(), ch, true); //#bot is handled here
})).filterWhen(ch -> {
timings.printElapsed("mcchat");
val mcchat = Component.getComponents().get(MinecraftChatModule.class); val mcchat = Component.getComponents().get(MinecraftChatModule.class);
if (mcchat != null && mcchat.isEnabled()) //ComponentManager.isEnabled() searches the component again if (mcchat != null && mcchat.isEnabled()) //ComponentManager.isEnabled() searches the component again
handled = ((MinecraftChatModule) mcchat).getListener().handleDiscord(event); //Also runs Discord commands in chat channels return ((MinecraftChatModule) mcchat).getListener().handleDiscord(event); //Also runs Discord commands in chat channels
if (!handled) return Mono.empty(); //Wasn't handled, continue
handled = CommandListener.runCommand(event.getMessage(), false); }).filterWhen(ch -> {
} catch (Exception e) { timings.printElapsed("Run command 2");
TBMCCoreAPI.SendException("An error occured while handling a message!", e); return CommandListener.runCommand(event.getMessage(), ch, false);
} });
} }).onErrorContinue((err, obj) -> TBMCCoreAPI.SendException("An error occured while handling a message!", err))
}, new IListener<sx.blah.discord.handle.impl.events.user.PresenceUpdateEvent>() { .subscribe();
@Override dispatcher.on(PresenceUpdateEvent.class).subscribe(event -> {
public void handle(PresenceUpdateEvent event) {
if (DiscordPlugin.SafeMode) if (DiscordPlugin.SafeMode)
return; return;
FunModule.handleFullHouse(event); FunModule.handleFullHouse(event);
} });
}, (IListener<RoleCreateEvent>) GameRoleModule::handleRoleEvent, // dispatcher.on(RoleCreateEvent.class).subscribe(GameRoleModule::handleRoleEvent);
(IListener<RoleDeleteEvent>) GameRoleModule::handleRoleEvent, // dispatcher.on(RoleDeleteEvent.class).subscribe(GameRoleModule::handleRoleEvent);
(IListener<RoleUpdateEvent>) GameRoleModule::handleRoleEvent}; dispatcher.on(RoleUpdateEvent.class).subscribe(GameRoleModule::handleRoleEvent);
} }
private static boolean debug = false; private static boolean debug = false;

View file

@ -5,18 +5,22 @@ import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.commands.ConnectCommand; import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.lib.player.TBMCPlayerGetInfoEvent; import buttondevteam.lib.player.TBMCPlayerGetInfoEvent;
import buttondevteam.lib.player.TBMCPlayerJoinEvent; import buttondevteam.lib.player.TBMCPlayerJoinEvent;
import discord4j.core.object.entity.Member;
import discord4j.core.object.entity.User;
import discord4j.core.object.util.Snowflake;
import lombok.val;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.event.server.ServerCommandEvent;
import sx.blah.discord.handle.obj.IUser;
public class MCListener implements Listener { public class MCListener implements Listener {
@EventHandler @EventHandler
public void onPlayerJoin(TBMCPlayerJoinEvent e) { public void onPlayerJoin(TBMCPlayerJoinEvent e) {
if (ConnectCommand.WaitingToConnect.containsKey(e.GetPlayer().PlayerName().get())) { if (ConnectCommand.WaitingToConnect.containsKey(e.GetPlayer().PlayerName().get())) {
@SuppressWarnings("ConstantConditions") IUser user = DiscordPlugin.dc @SuppressWarnings("ConstantConditions") User user = DiscordPlugin.dc
.getUserByID(Long.parseLong(ConnectCommand.WaitingToConnect.get(e.GetPlayer().PlayerName().get()))); .getUserById(Snowflake.of(ConnectCommand.WaitingToConnect.get(e.GetPlayer().PlayerName().get()))).block();
e.getPlayer().sendMessage("§bTo connect with the Discord account @" + user.getName() + "#" + user.getDiscriminator() if (user == null) return;
e.getPlayer().sendMessage("§bTo connect with the Discord account @" + user.getUsername() + "#" + user.getDiscriminator()
+ " do /discord accept"); + " do /discord accept");
e.getPlayer().sendMessage("§bIf it wasn't you, do /discord decline"); e.getPlayer().sendMessage("§bIf it wasn't you, do /discord decline");
} }
@ -29,11 +33,18 @@ public class MCListener implements Listener {
DiscordPlayer dp = e.getPlayer().getAs(DiscordPlayer.class); DiscordPlayer dp = e.getPlayer().getAs(DiscordPlayer.class);
if (dp == null || dp.getDiscordID() == null || dp.getDiscordID().equals("")) if (dp == null || dp.getDiscordID() == null || dp.getDiscordID().equals(""))
return; return;
IUser user = DiscordPlugin.dc.getUserByID(Long.parseLong(dp.getDiscordID())); User user = DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID())).block();
e.addInfo("Discord tag: " + user.getName() + "#" + user.getDiscriminator()); if (user == null) return;
e.addInfo(user.getPresence().getStatus().toString()); e.addInfo("Discord tag: " + user.getUsername() + "#" + user.getDiscriminator());
if (user.getPresence().getActivity().isPresent() && user.getPresence().getText().isPresent()) Member member = user.asMember(DiscordPlugin.mainServer.getId()).block();
e.addInfo(user.getPresence().getActivity().get() + ": " + user.getPresence().getText().get()); if (member == null) return;
val pr = member.getPresence().block();
if (pr == null) return;
e.addInfo(pr.getStatus().toString());
if (pr.getActivity().isPresent()) {
val activity = pr.getActivity().get();
e.addInfo(activity.getType() + ": " + activity.getName());
}
} }
@EventHandler @EventHandler

View file

@ -9,15 +9,16 @@ import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer;
import discord4j.core.object.entity.Message;
import discord4j.core.object.util.Permission;
import lombok.RequiredArgsConstructor;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.Permissions;
import sx.blah.discord.util.PermissionUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -32,15 +33,17 @@ import java.util.stream.Collectors;
"Mentioning the bot is needed in this case because the / prefix only works in #bot.", // "Mentioning the bot is needed in this case because the / prefix only works in #bot.", //
"Invite link: <https://discordapp.com/oauth2/authorize?client_id=226443037893591041&scope=bot&permissions=268509264>" // "Invite link: <https://discordapp.com/oauth2/authorize?client_id=226443037893591041&scope=bot&permissions=268509264>" //
}) })
@RequiredArgsConstructor
public class ChannelconCommand extends ICommand2DC { public class ChannelconCommand extends ICommand2DC {
private final MinecraftChatModule module;
@Command2.Subcommand @Command2.Subcommand
public boolean remove(Command2DCSender sender) { public boolean remove(Command2DCSender sender) {
val message = sender.getMessage(); val message = sender.getMessage();
if (checkPerms(message)) return true; if (checkPerms(message)) return true;
if (MCChatCustom.removeCustomChat(message.getChannel())) if (MCChatCustom.removeCustomChat(message.getChannelId()))
message.reply("channel connection removed."); DPUtils.reply(message, null, "channel connection removed.").subscribe();
else else
message.reply("this channel isn't connected."); DPUtils.reply(message, null, "this channel isn't connected.").subscribe();
return true; return true;
} }
@ -48,13 +51,13 @@ public class ChannelconCommand extends ICommand2DC {
public boolean toggle(Command2DCSender sender, @Command2.OptionalArg String toggle) { public boolean toggle(Command2DCSender sender, @Command2.OptionalArg String toggle) {
val message = sender.getMessage(); val message = sender.getMessage();
if (checkPerms(message)) return true; if (checkPerms(message)) return true;
val cc = MCChatCustom.getCustomChat(message.getChannel()); val cc = MCChatCustom.getCustomChat(message.getChannelId());
if (cc == null) if (cc == null)
return respond(sender, "this channel isn't connected."); return respond(sender, "this channel isn't connected.");
Supplier<String> togglesString = () -> Arrays.stream(ChannelconBroadcast.values()).map(t -> t.toString().toLowerCase() + ": " + ((cc.toggles & t.flag) == 0 ? "disabled" : "enabled")).collect(Collectors.joining("\n")) Supplier<String> togglesString = () -> Arrays.stream(ChannelconBroadcast.values()).map(t -> t.toString().toLowerCase() + ": " + ((cc.toggles & t.flag) == 0 ? "disabled" : "enabled")).collect(Collectors.joining("\n"))
+ "\n\n" + TBMCSystemChatEvent.BroadcastTarget.stream().map(target -> target.getName() + ": " + (cc.brtoggles.contains(target) ? "enabled" : "disabled")).collect(Collectors.joining("\n")); + "\n\n" + TBMCSystemChatEvent.BroadcastTarget.stream().map(target -> target.getName() + ": " + (cc.brtoggles.contains(target) ? "enabled" : "disabled")).collect(Collectors.joining("\n"));
if (toggle == null) { if (toggle == null) {
message.reply("toggles:\n" + togglesString.get()); DPUtils.reply(message, null, "toggles:\n" + togglesString.get()).subscribe();
return true; return true;
} }
String arg = toggle.toUpperCase(); String arg = toggle.toUpperCase();
@ -62,7 +65,7 @@ public class ChannelconCommand extends ICommand2DC {
if (!b.isPresent()) { if (!b.isPresent()) {
val bt = TBMCSystemChatEvent.BroadcastTarget.get(arg); val bt = TBMCSystemChatEvent.BroadcastTarget.get(arg);
if (bt == null) { if (bt == null) {
message.reply("cannot find toggle. Toggles:\n" + togglesString.get()); DPUtils.reply(message, null, "cannot find toggle. Toggles:\n" + togglesString.get()).subscribe();
return true; return true;
} }
final boolean add; final boolean add;
@ -80,7 +83,7 @@ public class ChannelconCommand extends ICommand2DC {
//1 1 | 0 //1 1 | 0
// XOR // XOR
cc.toggles ^= b.get().flag; cc.toggles ^= b.get().flag;
message.reply("'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")); DPUtils.reply(message, null, "'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")).subscribe();
return true; return true;
} }
@ -88,45 +91,49 @@ public class ChannelconCommand extends ICommand2DC {
public boolean def(Command2DCSender sender, String channelID) { public boolean def(Command2DCSender sender, String channelID) {
val message = sender.getMessage(); val message = sender.getMessage();
if (checkPerms(message)) return true; if (checkPerms(message)) return true;
if (MCChatCustom.hasCustomChat(message.getChannel())) if (MCChatCustom.hasCustomChat(message.getChannelId()))
return respond(sender, "this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it."); return respond(sender, "this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it.");
val chan = Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase(channelID) || (Arrays.stream(ch.IDs().get()).anyMatch(cid -> cid.equalsIgnoreCase(channelID)))).findAny(); val chan = Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase(channelID) || (Arrays.stream(ch.IDs().get()).anyMatch(cid -> cid.equalsIgnoreCase(channelID)))).findAny();
if (!chan.isPresent()) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW) if (!chan.isPresent()) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW)
message.reply("MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /."); DPUtils.reply(message, null, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe();
return true; return true;
} }
val dp = DiscordPlayer.getUser(message.getAuthor().getStringID(), DiscordPlayer.class); if (!message.getAuthor().isPresent()) return true;
val author = message.getAuthor().get();
val dp = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class);
val chp = dp.getAs(TBMCPlayer.class); val chp = dp.getAs(TBMCPlayer.class);
if (chp == null) { if (chp == null) {
message.reply("you need to connect your Minecraft account. On our server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect <MCname>"); DPUtils.reply(message, null, "you need to connect your Minecraft account. On our server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect <MCname>").subscribe();
return true; return true;
} }
DiscordConnectedPlayer dcp = new DiscordConnectedPlayer(message.getAuthor(), message.getChannel(), chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName()); val channel = message.getChannel().block();
DiscordConnectedPlayer dcp = new DiscordConnectedPlayer(message.getAuthor().get(), channel, chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName(), module);
//Using a fake player with no login/logout, should be fine for this event //Using a fake player with no login/logout, should be fine for this event
String groupid = chan.get().getGroupID(dcp); String groupid = chan.get().getGroupID(dcp);
if (groupid == null && !(chan.get() instanceof ChatRoom)) { //ChatRooms don't allow it unless the user joins, which happens later if (groupid == null && !(chan.get() instanceof ChatRoom)) { //ChatRooms don't allow it unless the user joins, which happens later
message.reply("sorry, you cannot use that Minecraft channel."); DPUtils.reply(message, null, "sorry, you cannot use that Minecraft channel.").subscribe();
return true; return true;
} }
if (chan.get() instanceof ChatRoom) { //ChatRooms don't work well if (chan.get() instanceof ChatRoom) { //ChatRooms don't work well
message.reply("chat rooms are not supported yet."); DPUtils.reply(message, null, "chat rooms are not supported yet.").subscribe();
return true; return true;
} }
/*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) { /*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) {
message.reply("sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm."); DPUtils.reply(message, null, "sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm.");
return true; return true;
}*/ //TODO: "Channel admins" that can connect channels? }*/ //TODO: "Channel admins" that can connect channels?
MCChatCustom.addCustomChat(message.getChannel(), groupid, chan.get(), message.getAuthor(), dcp, 0, new HashSet<>()); MCChatCustom.addCustomChat(channel, groupid, chan.get(), author, dcp, 0, new HashSet<>());
if (chan.get() instanceof ChatRoom) if (chan.get() instanceof ChatRoom)
message.reply("alright, connection made to the room!"); DPUtils.reply(message, null, "alright, connection made to the room!").subscribe();
else else
message.reply("alright, connection made to group `" + groupid + "`!"); DPUtils.reply(message, null, "alright, connection made to group `" + groupid + "`!").subscribe();
return true; return true;
} }
private boolean checkPerms(IMessage message) { @SuppressWarnings("ConstantConditions")
if (!PermissionUtils.hasPermissions(message.getChannel(), message.getAuthor(), Permissions.MANAGE_CHANNEL)) { private boolean checkPerms(Message message) {
message.reply("you need to have manage permissions for this channel!"); if (!message.getAuthorAsMember().block().getBasePermissions().block().contains(Permission.MANAGE_CHANNELS)) {
DPUtils.reply(message, null, "you need to have manage permissions for this channel!").subscribe();
return true; return true;
} }
return false; return false;
@ -140,7 +147,7 @@ public class ChannelconCommand extends ICommand2DC {
"You need to have access to the MC channel and have manage permissions on the Discord channel.", // "You need to have access to the MC channel and have manage permissions on the Discord channel.", //
"You also need to have your Minecraft account connected. In " + DPUtils.botmention() + " use " + DiscordPlugin.getPrefix() + "connect <mcname>.", // "You also need to have your Minecraft account connected. In " + DPUtils.botmention() + " use " + DiscordPlugin.getPrefix() + "connect <mcname>.", //
"Call this command from the channel you want to use.", // "Call this command from the channel you want to use.", //
"Usage: @" + DiscordPlugin.dc.getOurUser().getName() + " channelcon <mcchannel>", // "Usage: " + Objects.requireNonNull(DiscordPlugin.dc.getSelf().block()).getMention() + " channelcon <mcchannel>", //
"Use the ID (command) of the channel, for example `g` for the global chat.", // "Use the ID (command) of the channel, for example `g` for the global chat.", //
"To remove a connection use @ChromaBot channelcon remove in the channel.", // "To remove a connection use @ChromaBot channelcon remove in the channel.", //
"Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix() + " prefix only works in " + DPUtils.botmention() + ".", // "Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix() + " prefix only works in " + DPUtils.botmention() + ".", //

View file

@ -1,5 +1,6 @@
package buttondevteam.discordplugin.mcchat; package buttondevteam.discordplugin.mcchat;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.commands.Command2DCSender; import buttondevteam.discordplugin.commands.Command2DCSender;
@ -7,6 +8,7 @@ import buttondevteam.discordplugin.commands.ICommand2DC;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import discord4j.core.object.entity.PrivateChannel;
import lombok.val; import lombok.val;
@CommandClass(helpText = { @CommandClass(helpText = {
@ -20,18 +22,20 @@ public class MCChatCommand extends ICommand2DC {
@Command2.Subcommand @Command2.Subcommand
public boolean def(Command2DCSender sender) { public boolean def(Command2DCSender sender) {
val message = sender.getMessage(); val message = sender.getMessage();
if (!message.getChannel().isPrivate()) { val channel = message.getChannel().block();
message.reply("this command can only be issued in a direct message with the bot."); @SuppressWarnings("OptionalGetWithoutIsPresent") val author = message.getAuthor().get();
if (!(channel instanceof PrivateChannel)) {
DPUtils.reply(message, null, "this command can only be issued in a direct message with the bot.").subscribe();
return true; return true;
} }
try (final DiscordPlayer user = DiscordPlayer.getUser(message.getAuthor().getStringID(), DiscordPlayer.class)) { try (final DiscordPlayer user = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class)) {
boolean mcchat = !user.isMinecraftChatEnabled(); boolean mcchat = !user.isMinecraftChatEnabled();
MCChatPrivate.privateMCChat(message.getChannel(), mcchat, message.getAuthor(), user); MCChatPrivate.privateMCChat(channel, mcchat, author, user);
message.reply("Minecraft chat " + (mcchat // DPUtils.reply(message, null, "Minecraft chat " + (mcchat //
? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." // ? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." //
: "disabled.")); : "disabled.")).subscribe();
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Error while setting mcchat for user" + message.getAuthor().getName(), e); TBMCCoreAPI.SendException("Error while setting mcchat for user " + author.getUsername() + "#" + author.getDiscriminator(), e);
} }
return true; return true;
} // TODO: Pin channel switching to indicate the current channel } // TODO: Pin channel switching to indicate the current channel

View file

@ -4,10 +4,11 @@ import buttondevteam.core.component.channel.Channel;
import buttondevteam.core.component.channel.ChatRoom; import buttondevteam.core.component.channel.ChatRoom;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import discord4j.core.object.util.Snowflake;
import lombok.NonNull; import lombok.NonNull;
import lombok.val; import lombok.val;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,7 +22,7 @@ public class MCChatCustom {
*/ */
static ArrayList<CustomLMD> lastmsgCustom = new ArrayList<>(); static ArrayList<CustomLMD> lastmsgCustom = new ArrayList<>();
public static void addCustomChat(IChannel channel, String groupid, Channel mcchannel, IUser user, DiscordConnectedPlayer dcp, int toggles, Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles) { public static void addCustomChat(MessageChannel channel, String groupid, Channel mcchannel, User user, DiscordConnectedPlayer dcp, int toggles, Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles) {
if (mcchannel instanceof ChatRoom) { if (mcchannel instanceof ChatRoom) {
((ChatRoom) mcchannel).joinRoom(dcp); ((ChatRoom) mcchannel).joinRoom(dcp);
if (groupid == null) groupid = mcchannel.getGroupID(dcp); if (groupid == null) groupid = mcchannel.getGroupID(dcp);
@ -30,19 +31,19 @@ public class MCChatCustom {
lastmsgCustom.add(lmd); lastmsgCustom.add(lmd);
} }
public static boolean hasCustomChat(IChannel channel) { public static boolean hasCustomChat(Snowflake channel) {
return lastmsgCustom.stream().anyMatch(lmd -> lmd.channel.getLongID() == channel.getLongID()); return lastmsgCustom.stream().anyMatch(lmd -> lmd.channel.getId().asLong() == channel.asLong());
} }
@Nullable @Nullable
public static CustomLMD getCustomChat(IChannel channel) { public static CustomLMD getCustomChat(Snowflake channel) {
return lastmsgCustom.stream().filter(lmd -> lmd.channel.getLongID() == channel.getLongID()).findAny().orElse(null); return lastmsgCustom.stream().filter(lmd -> lmd.channel.getId().asLong() == channel.asLong()).findAny().orElse(null);
} }
public static boolean removeCustomChat(IChannel channel) { public static boolean removeCustomChat(Snowflake channel) {
MCChatUtils.lastmsgfromd.remove(channel.getLongID()); MCChatUtils.lastmsgfromd.remove(channel.asLong());
return lastmsgCustom.removeIf(lmd -> { return lastmsgCustom.removeIf(lmd -> {
if (lmd.channel.getLongID() != channel.getLongID()) if (lmd.channel.getId().asLong() != channel.asLong())
return false; return false;
if (lmd.mcchannel instanceof ChatRoom) if (lmd.mcchannel instanceof ChatRoom)
((ChatRoom) lmd.mcchannel).leaveRoom(lmd.dcp); ((ChatRoom) lmd.mcchannel).leaveRoom(lmd.dcp);
@ -61,7 +62,7 @@ public class MCChatCustom {
public int toggles; public int toggles;
public Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles; public Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles;
private CustomLMD(@NonNull IChannel channel, @NonNull IUser user, private CustomLMD(@NonNull MessageChannel channel, @NonNull User user,
@NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles, Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles) { @NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles, Set<TBMCSystemChatEvent.BroadcastTarget> brtoggles) {
super(channel, user); super(channel, user);
groupID = groupid; groupID = groupid;

View file

@ -8,26 +8,26 @@ import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.DiscordSender; import buttondevteam.discordplugin.DiscordSender;
import buttondevteam.discordplugin.DiscordSenderBase; import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.listeners.CommandListener; import buttondevteam.discordplugin.listeners.CommandListener;
import buttondevteam.discordplugin.listeners.CommonListeners;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import buttondevteam.discordplugin.util.Timings;
import buttondevteam.lib.*; import buttondevteam.lib.*;
import buttondevteam.lib.chat.ChatMessage; import buttondevteam.lib.chat.ChatMessage;
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer;
import com.vdurmont.emoji.EmojiParser; import com.vdurmont.emoji.EmojiParser;
import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.object.Embed;
import discord4j.core.object.entity.*;
import discord4j.core.object.util.Snowflake;
import discord4j.core.spec.EmbedCreateSpec;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
import sx.blah.discord.api.internal.json.objects.EmbedObject; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.IUser;
import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.EmbedBuilder;
import sx.blah.discord.util.MissingPermissionsException;
import java.awt.*; import java.awt.*;
import java.time.Instant; import java.time.Instant;
@ -36,6 +36,7 @@ import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -77,76 +78,68 @@ public class MCChatListener implements Listener {
final String authorPlayer = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel().DisplayName().get()) + "] " // final String authorPlayer = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel().DisplayName().get()) + "] " //
+ ("Minecraft".equals(e.getOrigin()) ? "" : "[" + e.getOrigin().substring(0, 1) + "]") // + ("Minecraft".equals(e.getOrigin()) ? "" : "[" + e.getOrigin().substring(0, 1) + "]") //
+ (DPUtils.sanitizeStringNoEscape(e.getSender() instanceof Player // + (DPUtils.sanitizeStringNoEscape(ThorpeUtils.getDisplayName(e.getSender())));
? ((Player) e.getSender()).getDisplayName() //
: e.getSender().getName()));
val color = e.getChannel().Color().get(); val color = e.getChannel().Color().get();
final EmbedBuilder embed = new EmbedBuilder().withAuthorName(authorPlayer) final Consumer<EmbedCreateSpec> embed = ecs -> {
.withDescription(e.getMessage()).withColor(new Color(color.getRed(), ecs.setDescription(e.getMessage()).setColor(new Color(color.getRed(),
color.getGreen(), color.getBlue())); color.getGreen(), color.getBlue()));
// embed.appendField("Channel", ((e.getSender() instanceof DiscordSenderBase ? "d|" : "")
// + DiscordPlugin.sanitizeString(e.getChannel().DisplayName)), false);
if (e.getSender() instanceof Player) if (e.getSender() instanceof Player)
DPUtils.embedWithHead( DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(),
embed.withAuthorUrl("https://tbmcplugins.github.io/profile.html?type=minecraft&id=" "https://tbmcplugins.github.io/profile.html?type=minecraft&id="
+ ((Player) e.getSender()).getUniqueId()), + ((Player) e.getSender()).getUniqueId());
e.getSender().getName());
else if (e.getSender() instanceof DiscordSenderBase) else if (e.getSender() instanceof DiscordSenderBase)
embed.withAuthorIcon(((DiscordSenderBase) e.getSender()).getUser().getAvatarURL()) ecs.setAuthor(authorPlayer, "https://tbmcplugins.github.io/profile.html?type=discord&id=" // TODO: Constant/method to get URLs like this
.withAuthorUrl("https://tbmcplugins.github.io/profile.html?type=discord&id=" + ((DiscordSenderBase) e.getSender()).getUser().getId().asString(),
+ ((DiscordSenderBase) e.getSender()).getUser().getStringID()); // TODO: Constant/method to get URLs like this ((DiscordSenderBase) e.getSender()).getUser().getAvatarUrl());
// embed.withFooterText(e.getChannel().DisplayName); else
embed.withTimestamp(time); DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(), null);
ecs.setTimestamp(time);
};
final long nanoTime = System.nanoTime(); final long nanoTime = System.nanoTime();
InterruptibleConsumer<MCChatUtils.LastMsgData> doit = lastmsgdata -> { InterruptibleConsumer<MCChatUtils.LastMsgData> doit = lastmsgdata -> {
final EmbedObject embedObject = embed.build(); if (lastmsgdata.message == null
if (lastmsgdata.message == null || lastmsgdata.message.isDeleted() || !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().map(Embed.Author::getName).orElse(null))
|| !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().getName())
|| lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120
|| !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID)) { || !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID)) {
lastmsgdata.message = DiscordPlugin.sendMessageToChannelWait(lastmsgdata.channel, "", lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block();
embedObject); // TODO Use ChromaBot API
lastmsgdata.time = nanoTime; lastmsgdata.time = nanoTime;
lastmsgdata.mcchannel = e.getChannel(); lastmsgdata.mcchannel = e.getChannel();
lastmsgdata.content = embedObject.description; lastmsgdata.content = e.getMessage();
} else } else {
try { lastmsgdata.content = lastmsgdata.content + "\n"
lastmsgdata.content = embedObject.description = lastmsgdata.content + "\n" + e.getMessage(); // The message object doesn't get updated
+ embedObject.description;// The message object doesn't get updated lastmsgdata.message.edit(mes -> mes.setEmbed(embed.andThen(ecs ->
final MCChatUtils.LastMsgData _lastmsgdata = lastmsgdata; ecs.setDescription(lastmsgdata.content)))).block();
DPUtils.perform(() -> _lastmsgdata.message.edit("", embedObject));
} catch (MissingPermissionsException | DiscordException e1) {
TBMCCoreAPI.SendException("An error occurred while editing chat message!", e1);
} }
}; };
// Checks if the given channel is different than where the message was sent from // Checks if the given channel is different than where the message was sent from
// Or if it was from MC // Or if it was from MC
Predicate<IChannel> isdifferentchannel = ch -> !(e.getSender() instanceof DiscordSenderBase) Predicate<Snowflake> isdifferentchannel = id -> !(e.getSender() instanceof DiscordSenderBase)
|| ((DiscordSenderBase) e.getSender()).getChannel().getLongID() != ch.getLongID(); || ((DiscordSenderBase) e.getSender()).getChannel().getId().asLong() != id.asLong();
if (e.getChannel().isGlobal() if (e.getChannel().isGlobal()
&& (e.isFromCommand() || isdifferentchannel.test(module.chatChannel().get()))) && (e.isFromCommand() || isdifferentchannel.test(module.chatChannel().get())))
doit.accept(MCChatUtils.lastmsgdata == null doit.accept(MCChatUtils.lastmsgdata == null
? MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannel().get(), null) ? MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono().block(), null)
: MCChatUtils.lastmsgdata); : MCChatUtils.lastmsgdata);
for (MCChatUtils.LastMsgData data : MCChatPrivate.lastmsgPerUser) { for (MCChatUtils.LastMsgData data : MCChatPrivate.lastmsgPerUser) {
if ((e.isFromCommand() || isdifferentchannel.test(data.channel)) if ((e.isFromCommand() || isdifferentchannel.test(data.channel.getId()))
&& e.shouldSendTo(MCChatUtils.getSender(data.channel, data.user))) && e.shouldSendTo(MCChatUtils.getSender(data.channel.getId(), data.user)))
doit.accept(data); doit.accept(data);
} }
val iterator = MCChatCustom.lastmsgCustom.iterator(); val iterator = MCChatCustom.lastmsgCustom.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
val lmd = iterator.next(); val lmd = iterator.next();
if ((e.isFromCommand() || isdifferentchannel.test(lmd.channel)) //Test if msg is from Discord if ((e.isFromCommand() || isdifferentchannel.test(lmd.channel.getId())) //Test if msg is from Discord
&& e.getChannel().ID.equals(lmd.mcchannel.ID) //If it's from a command, the command msg has been deleted, so we need to send it && e.getChannel().ID.equals(lmd.mcchannel.ID) //If it's from a command, the command msg has been deleted, so we need to send it
&& e.getGroupID().equals(lmd.groupID)) { //Check if this is the group we want to test - #58 && e.getGroupID().equals(lmd.groupID)) { //Check if this is the group we want to test - #58
if (e.shouldSendTo(lmd.dcp)) //Check original user's permissions if (e.shouldSendTo(lmd.dcp)) //Check original user's permissions
doit.accept(lmd); doit.accept(lmd);
else { else {
iterator.remove(); //If the user no longer has permission, remove the connection iterator.remove(); //If the user no longer has permission, remove the connection
DiscordPlugin.sendMessageToChannel(lmd.channel, "The user no longer has permission to view the channel, connection removed."); lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe();
} }
} }
} }
@ -170,10 +163,11 @@ public class MCChatListener implements Listener {
end_ = event.getMessage().length(); end_ = event.getMessage().length();
final int end = end_; final int end = end_;
final int startF = start; final int startF = start;
DiscordPlugin.dc.getUsersByName(event.getMessage().substring(start + 1, mid)).stream() val user = DiscordPlugin.dc.getUsers().filter(u -> u.getUsername().equals(event.getMessage().substring(startF + 1, mid)))
.filter(u -> u.getDiscriminator().equals(event.getMessage().substring(mid + 1, end))).findAny() .filter(u -> u.getDiscriminator().equals(event.getMessage().substring(mid + 1, end))).blockFirst();
.ifPresent(user -> event.setMessage(event.getMessage().substring(0, startF) + "@" + user.getName() if (user != null) //TODO: Nicknames
+ (event.getMessage().length() > end ? event.getMessage().substring(end) : ""))); // TODO: Add formatting event.setMessage(event.getMessage().substring(0, startF) + "@" + user.getUsername()
+ (event.getMessage().length() > end ? event.getMessage().substring(end) : "")); // TODO: Add formatting
start = end; // Skip any @s inside the mention start = end; // Skip any @s inside the mention
} }
} }
@ -221,29 +215,39 @@ public class MCChatListener implements Listener {
} }
private BukkitTask rectask; private BukkitTask rectask;
private LinkedBlockingQueue<MessageReceivedEvent> recevents = new LinkedBlockingQueue<>(); private LinkedBlockingQueue<MessageCreateEvent> recevents = new LinkedBlockingQueue<>();
private Runnable recrun; private Runnable recrun;
private static Thread recthread; private static Thread recthread;
// Discord // Discord
public boolean handleDiscord(MessageReceivedEvent ev) { public Mono<Boolean> handleDiscord(MessageCreateEvent ev) {
val ret = Mono.just(true);
if (!ComponentManager.isEnabled(MinecraftChatModule.class)) if (!ComponentManager.isEnabled(MinecraftChatModule.class))
return false; return ret;
Timings timings = CommonListeners.timings;
timings.printElapsed("Chat event");
val author = ev.getMessage().getAuthor(); val author = ev.getMessage().getAuthor();
final boolean hasCustomChat = MCChatCustom.hasCustomChat(ev.getChannel()); final boolean hasCustomChat = MCChatCustom.hasCustomChat(ev.getMessage().getChannelId());
if (ev.getMessage().getChannel().getLongID() != module.chatChannel().get().getLongID() return ev.getMessage().getChannel().filter(channel -> {
&& !(ev.getMessage().getChannel().isPrivate() && MCChatPrivate.isMinecraftChatEnabled(author.getStringID())) timings.printElapsed("Filter 1");
&& !hasCustomChat) return !(ev.getMessage().getChannelId().asLong() != module.chatChannel().get().asLong()
return false; //Chat isn't enabled on this channel && !(channel instanceof PrivateChannel
if (ev.getMessage().getChannel().isPrivate() //Only in private chat && author.map(u -> MCChatPrivate.isMinecraftChatEnabled(u.getId().asString())).orElse(false)
&& ev.getMessage().getContent().length() < "/mcchat<>".length() && !hasCustomChat)); //Chat isn't enabled on this channel
&& ev.getMessage().getContent().replace("/", "") }).filter(channel -> {
.equalsIgnoreCase("mcchat")) //Either mcchat or /mcchat timings.printElapsed("Filter 2");
return false; //Allow disabling the chat if needed return !(channel instanceof PrivateChannel //Only in private chat
if (CommandListener.runCommand(ev.getMessage(), true)) && ev.getMessage().getContent().isPresent()
return true; //Allow running commands in chat channels && ev.getMessage().getContent().get().length() < "/mcchat<>".length()
MCChatUtils.resetLastMessage(ev.getChannel()); && ev.getMessage().getContent().get().replace("/", "")
.equalsIgnoreCase("mcchat")); //Either mcchat or /mcchat
//Allow disabling the chat if needed
}).filterWhen(channel -> CommandListener.runCommand(ev.getMessage(), channel, true))
//Allow running commands in chat channels
.filter(channel -> {
MCChatUtils.resetLastMessage(channel);
recevents.add(ev); recevents.add(ev);
timings.printElapsed("Message event added");
if (rectask != null) if (rectask != null)
return true; return true;
recrun = () -> { //Don't return in a while loop next time recrun = () -> { //Don't return in a while loop next time
@ -254,30 +258,33 @@ public class MCChatListener implements Listener {
}; };
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing
return true; return true;
}).map(b -> false).defaultIfEmpty(true);
} }
private void processDiscordToMC() { private void processDiscordToMC() {
@val MessageCreateEvent event;
sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent event;
try { try {
event = recevents.take(); event = recevents.take();
} catch (InterruptedException e1) { } catch (InterruptedException e1) {
rectask.cancel(); rectask.cancel();
return; return;
} }
val sender = event.getMessage().getAuthor(); val sender = event.getMessage().getAuthor().orElse(null);
String dmessage = event.getMessage().getContent(); String dmessage = event.getMessage().getContent().orElse("");
try { try {
final DiscordSenderBase dsender = MCChatUtils.getSender(event.getMessage().getChannel(), sender); final DiscordSenderBase dsender = MCChatUtils.getSender(event.getMessage().getChannelId(), sender);
val user = dsender.getChromaUser(); val user = dsender.getChromaUser();
for (IUser u : event.getMessage().getMentions()) { for (User u : event.getMessage().getUserMentions().toIterable()) { //TODO: Role mentions
dmessage = dmessage.replace(u.mention(false), "@" + u.getName()); // TODO: IG Formatting dmessage = dmessage.replace(u.getMention(), "@" + u.getUsername()); // TODO: IG Formatting
final String nick = u.getNicknameForGuild(DiscordPlugin.mainServer); val m = u.asMember(DiscordPlugin.mainServer.getId()).block();
dmessage = dmessage.replace(u.mention(true), "@" + (nick != null ? nick : u.getName())); if (m != null) {
final String nick = m.getDisplayName();
dmessage = dmessage.replace(m.getNicknameMention(), "@" + nick);
} }
for (IChannel ch : event.getMessage().getChannelMentions()) { }
dmessage = dmessage.replace(ch.mention(), "#" + ch.getName()); // TODO: IG Formatting for (GuildChannel ch : event.getGuild().flux().flatMap(Guild::getChannels).toIterable()) {
dmessage = dmessage.replace(ch.getMention(), "#" + ch.getName()); // TODO: IG Formatting
} }
dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE); //Converts emoji to text- TODO: Add option to disable (resource pack?) dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE); //Converts emoji to text- TODO: Add option to disable (resource pack?)
@ -285,18 +292,18 @@ public class MCChatListener implements Listener {
Function<String, String> getChatMessage = msg -> // Function<String, String> getChatMessage = msg -> //
msg + (event.getMessage().getAttachments().size() > 0 ? "\n" + event.getMessage() msg + (event.getMessage().getAttachments().size() > 0 ? "\n" + event.getMessage()
.getAttachments().stream().map(IMessage.Attachment::getUrl).collect(Collectors.joining("\n")) .getAttachments().stream().map(Attachment::getUrl).collect(Collectors.joining("\n"))
: ""); : "");
MCChatCustom.CustomLMD clmd = MCChatCustom.getCustomChat(event.getChannel()); MCChatCustom.CustomLMD clmd = MCChatCustom.getCustomChat(event.getMessage().getChannelId());
boolean react = false; boolean react = false;
val sendChannel = event.getMessage().getChannel().block();
boolean isPrivate = sendChannel instanceof PrivateChannel;
if (dmessage.startsWith("/")) { // Ingame command if (dmessage.startsWith("/")) { // Ingame command
DPUtils.perform(() -> { if (!isPrivate)
if (!event.getMessage().isDeleted() && !event.getChannel().isPrivate()) event.getMessage().delete().subscribe();
event.getMessage().delete();
});
final String cmd = dmessage.substring(1); final String cmd = dmessage.substring(1);
final String cmdlowercased = cmd.toLowerCase(); final String cmdlowercased = cmd.toLowerCase();
if (dsender instanceof DiscordSender && module.whitelistedCommands().get().stream() if (dsender instanceof DiscordSender && module.whitelistedCommands().get().stream()
@ -338,7 +345,7 @@ public class MCChatListener implements Listener {
}); });
else { else {
Channel chc = ch.get(); Channel chc = ch.get();
if (!chc.isGlobal() && !event.getMessage().getChannel().isPrivate()) if (!chc.isGlobal() && !isPrivate)
dsender.sendMessage( dsender.sendMessage(
"You can only talk in a public chat here. DM `mcchat` to enable private chat to talk in the other channels."); "You can only talk in a public chat here. DM `mcchat` to enable private chat to talk in the other channels.");
else { else {
@ -369,14 +376,13 @@ public class MCChatListener implements Listener {
} }
} else {// Not a command } else {// Not a command
if (dmessage.length() == 0 && event.getMessage().getAttachments().size() == 0 if (dmessage.length() == 0 && event.getMessage().getAttachments().size() == 0
&& !event.getChannel().isPrivate() && event.getMessage().isSystemMessage()) { && !isPrivate && event.getMessage().getType() == Message.Type.CHANNEL_PINNED_MESSAGE) {
val rtr = clmd != null ? clmd.mcchannel.getRTR(clmd.dcp) val rtr = clmd != null ? clmd.mcchannel.getRTR(clmd.dcp)
: dsender.getChromaUser().channel().get().getRTR(dsender); : dsender.getChromaUser().channel().get().getRTR(dsender);
TBMCChatAPI.SendSystemMessage(clmd != null ? clmd.mcchannel : dsender.getChromaUser().channel().get(), rtr, TBMCChatAPI.SendSystemMessage(clmd != null ? clmd.mcchannel : dsender.getChromaUser().channel().get(), rtr,
(dsender instanceof Player ? ((Player) dsender).getDisplayName() (dsender instanceof Player ? ((Player) dsender).getDisplayName()
: dsender.getName()) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL); : dsender.getName()) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL);
} } else {
else {
val cmb = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false); val cmb = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false);
if (clmd != null) if (clmd != null)
TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build(), clmd.mcchannel); TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build(), clmd.mcchannel);
@ -387,16 +393,15 @@ public class MCChatListener implements Listener {
} }
if (react) { if (react) {
try { try {
val lmfd = MCChatUtils.lastmsgfromd.get(event.getChannel().getLongID()); val lmfd = MCChatUtils.lastmsgfromd.get(event.getMessage().getChannelId().asLong());
if (lmfd != null) { if (lmfd != null) {
DPUtils.perform(() -> lmfd.removeReaction(DiscordPlugin.dc.getOurUser(), lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe(); // Remove it no matter what, we know it's there 99.99% of the time
DiscordPlugin.DELIVERED_REACTION)); // Remove it no matter what, we know it's there 99.99% of the time
} }
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e); TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e);
} }
MCChatUtils.lastmsgfromd.put(event.getChannel().getLongID(), event.getMessage()); MCChatUtils.lastmsgfromd.put(event.getMessage().getChannelId().asLong(), event.getMessage());
DPUtils.perform(() -> event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION)); event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe();
} }
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e); TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e);

View file

@ -1,17 +1,14 @@
package buttondevteam.discordplugin.mcchat; package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.ComponentManager;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.PrivateChannel;
import discord4j.core.object.entity.User;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IPrivateChannel;
import sx.blah.discord.handle.obj.IUser;
import java.util.ArrayList; import java.util.ArrayList;
@ -22,27 +19,31 @@ public class MCChatPrivate {
*/ */
static ArrayList<MCChatUtils.LastMsgData> lastmsgPerUser = new ArrayList<>(); static ArrayList<MCChatUtils.LastMsgData> lastmsgPerUser = new ArrayList<>();
public static boolean privateMCChat(IChannel channel, boolean start, IUser user, DiscordPlayer dp) { public static boolean privateMCChat(MessageChannel channel, boolean start, User user, DiscordPlayer dp) {
TBMCPlayer mcp = dp.getAs(TBMCPlayer.class); TBMCPlayer mcp = dp.getAs(TBMCPlayer.class);
if (mcp != null) { // If the accounts aren't connected, can't make a connected sender if (mcp != null) { // If the accounts aren't connected, can't make a connected sender
val p = Bukkit.getPlayer(mcp.getUUID()); val p = Bukkit.getPlayer(mcp.getUUID());
val op = Bukkit.getOfflinePlayer(mcp.getUUID()); val op = Bukkit.getOfflinePlayer(mcp.getUUID());
val mcm = ComponentManager.getIfEnabled(MinecraftChatModule.class);
if (start) { if (start) {
val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName()); val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName(), mcm);
MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender); MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender);
if (p == null)// Player is offline - If the player is online, that takes precedence if (p == null)// Player is offline - If the player is online, that takes precedence
callEventSync(new PlayerJoinEvent(sender, "")); MCChatUtils.callLoginEvents(sender);
} else { } else {
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel, user); val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
if (p == null)// Player is offline - If the player is online, that takes precedence assert sender != null;
callEventSync(new PlayerQuitEvent(sender, "")); if (p == null // Player is offline - If the player is online, that takes precedence
&& sender.isLoggedIn()) //Don't call the quit event if login failed
MCChatUtils.callLogoutEvent(sender, true);
sender.setLoggedIn(false);
} }
} // ---- PermissionsEx warning is normal on logout ---- } // ---- PermissionsEx warning is normal on logout ----
if (!start) if (!start)
MCChatUtils.lastmsgfromd.remove(channel.getLongID()); MCChatUtils.lastmsgfromd.remove(channel.getId().asLong());
return start // return start //
? lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs ? lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs
: lastmsgPerUser.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID()); : lastmsgPerUser.removeIf(lmd -> lmd.channel.getId().asLong() == channel.getId().asLong());
} }
public static boolean isMinecraftChatEnabled(DiscordPlayer dp) { public static boolean isMinecraftChatEnabled(DiscordPlayer dp) {
@ -51,18 +52,16 @@ public class MCChatPrivate {
public static boolean isMinecraftChatEnabled(String did) { // Don't load the player data just for this public static boolean isMinecraftChatEnabled(String did) { // Don't load the player data just for this
return lastmsgPerUser.stream() return lastmsgPerUser.stream()
.anyMatch(lmd -> ((IPrivateChannel) lmd.channel).getRecipient().getStringID().equals(did)); .anyMatch(lmd -> ((PrivateChannel) lmd.channel)
.getRecipientIds().stream().anyMatch(u -> u.asString().equals(did)));
} }
public static void logoutAll() { public static void logoutAll() {
for (val entry : MCChatUtils.ConnectedSenders.entrySet()) for (val entry : MCChatUtils.ConnectedSenders.entrySet())
for (val valueEntry : entry.getValue().entrySet()) for (val valueEntry : entry.getValue().entrySet())
if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out
MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync MCChatUtils.callLogoutEvent(valueEntry.getValue(), false); //This is sync
MCChatUtils.ConnectedSenders.clear(); MCChatUtils.ConnectedSenders.clear();
} }
private static void callEventSync(Event event) {
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> MCChatUtils.callEventExcludingSome(event));
}
} }

View file

@ -1,10 +1,13 @@
package buttondevteam.discordplugin.mcchat; package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.ComponentManager; import buttondevteam.core.ComponentManager;
import buttondevteam.core.component.channel.Channel;
import buttondevteam.discordplugin.*; import buttondevteam.discordplugin.*;
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import com.google.common.collect.Sets;
import discord4j.core.object.entity.*;
import discord4j.core.object.util.Snowflake;
import io.netty.util.collection.LongObjectHashMap; import io.netty.util.collection.LongObjectHashMap;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.var; import lombok.experimental.var;
@ -13,16 +16,20 @@ import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.AuthorNagException; import org.bukkit.plugin.AuthorNagException;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.RegisteredListener;
import sx.blah.discord.handle.obj.IChannel; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.obj.IMessage;
import sx.blah.discord.handle.obj.IUser;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.net.InetAddress;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -34,23 +41,22 @@ public class MCChatUtils {
/** /**
* May contain P&lt;DiscordID&gt; as key for public chat * May contain P&lt;DiscordID&gt; as key for public chat
*/ */
public static final HashMap<String, HashMap<IChannel, DiscordSender>> UnconnectedSenders = new HashMap<>(); public static final HashMap<String, HashMap<Snowflake, DiscordSender>> UnconnectedSenders = new HashMap<>();
public static final HashMap<String, HashMap<IChannel, DiscordConnectedPlayer>> ConnectedSenders = new HashMap<>(); public static final HashMap<String, HashMap<Snowflake, DiscordConnectedPlayer>> ConnectedSenders = new HashMap<>();
/** /**
* May contain P&lt;DiscordID&gt; as key for public chat * May contain P&lt;DiscordID&gt; as key for public chat
*/ */
public static final HashMap<String, HashMap<IChannel, DiscordPlayerSender>> OnlineSenders = new HashMap<>(); public static final HashMap<String, HashMap<Snowflake, DiscordPlayerSender>> OnlineSenders = new HashMap<>();
static @Nullable LastMsgData lastmsgdata; static @Nullable LastMsgData lastmsgdata;
static LongObjectHashMap<IMessage> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
private static MinecraftChatModule module; private static MinecraftChatModule module;
private static HashMap<Class<? extends Event>, HashSet<String>> staticExcludedPlugins = new HashMap<>();
public static void updatePlayerList() { public static void updatePlayerList() {
if (notEnabled()) return; if (notEnabled()) return;
DPUtils.performNoWait(() -> {
if (lastmsgdata != null) if (lastmsgdata != null)
updatePL(lastmsgdata); updatePL(lastmsgdata);
MCChatCustom.lastmsgCustom.forEach(MCChatUtils::updatePL); MCChatCustom.lastmsgCustom.forEach(MCChatUtils::updatePL);
});
} }
private static boolean notEnabled() { private static boolean notEnabled() {
@ -64,8 +70,13 @@ public class MCChatUtils {
} }
private static void updatePL(LastMsgData lmd) { private static void updatePL(LastMsgData lmd) {
String topic = lmd.channel.getTopic(); if (!(lmd.channel instanceof TextChannel)) {
if (topic == null || topic.length() == 0) TBMCCoreAPI.SendException("Failed to update player list for channel " + lmd.channel.getId(),
new Exception("The channel isn't a (guild) text channel."));
return;
}
String topic = ((TextChannel) lmd.channel).getTopic().orElse("");
if (topic.length() == 0)
topic = ".\n----\nMinecraft chat\n----\n."; topic = ".\n----\nMinecraft chat\n----\n.";
String[] s = topic.split("\\n----\\n"); String[] s = topic.split("\\n----\\n");
if (s.length < 3) if (s.length < 3)
@ -74,45 +85,45 @@ public class MCChatUtils {
+ " online"; + " online";
s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream() s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream()
.map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", ")); .map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", "));
lmd.channel.changeTopic(String.join("\n----\n", s)); ((TextChannel) lmd.channel).edit(tce -> tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe(); //Don't wait
} }
public static <T extends DiscordSenderBase> T addSender(HashMap<String, HashMap<IChannel, T>> senders, public static <T extends DiscordSenderBase> T addSender(HashMap<String, HashMap<Snowflake, T>> senders,
IUser user, T sender) { User user, T sender) {
return addSender(senders, user.getStringID(), sender); return addSender(senders, user.getId().asString(), sender);
} }
public static <T extends DiscordSenderBase> T addSender(HashMap<String, HashMap<IChannel, T>> senders, public static <T extends DiscordSenderBase> T addSender(HashMap<String, HashMap<Snowflake, T>> senders,
String did, T sender) { String did, T sender) {
var map = senders.get(did); var map = senders.get(did);
if (map == null) if (map == null)
map = new HashMap<>(); map = new HashMap<>();
map.put(sender.getChannel(), sender); map.put(sender.getChannel().getId(), sender);
senders.put(did, map); senders.put(did, map);
return sender; return sender;
} }
public static <T extends DiscordSenderBase> T getSender(HashMap<String, HashMap<IChannel, T>> senders, public static <T extends DiscordSenderBase> T getSender(HashMap<String, HashMap<Snowflake, T>> senders,
IChannel channel, IUser user) { Snowflake channel, User user) {
var map = senders.get(user.getStringID()); var map = senders.get(user.getId().asString());
if (map != null) if (map != null)
return map.get(channel); return map.get(channel);
return null; return null;
} }
public static <T extends DiscordSenderBase> T removeSender(HashMap<String, HashMap<IChannel, T>> senders, public static <T extends DiscordSenderBase> T removeSender(HashMap<String, HashMap<Snowflake, T>> senders,
IChannel channel, IUser user) { Snowflake channel, User user) {
var map = senders.get(user.getStringID()); var map = senders.get(user.getId().asString());
if (map != null) if (map != null)
return map.remove(channel); return map.remove(channel);
return null; return null;
} }
public static void forAllMCChat(Consumer<IChannel> action) { public static void forAllMCChat(Consumer<Mono<MessageChannel>> action) {
if (notEnabled()) return; if (notEnabled()) return;
action.accept(module.chatChannel().get()); action.accept(module.chatChannelMono());
for (LastMsgData data : MCChatPrivate.lastmsgPerUser) for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
action.accept(data.channel); action.accept(Mono.just(data.channel));
// lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat
} }
@ -123,11 +134,11 @@ public class MCChatUtils {
* @param toggle The toggle to check * @param toggle The toggle to check
* @param hookmsg Whether the message is also sent from the hook * @param hookmsg Whether the message is also sent from the hook
*/ */
public static void forCustomAndAllMCChat(Consumer<IChannel> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) { public static void forCustomAndAllMCChat(Consumer<Mono<MessageChannel>> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return; if (notEnabled()) return;
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg) if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
forAllMCChat(action); forAllMCChat(action);
final Consumer<MCChatCustom.CustomLMD> customLMDConsumer = cc -> action.accept(cc.channel); final Consumer<MCChatCustom.CustomLMD> customLMDConsumer = cc -> action.accept(Mono.just(cc.channel));
if (toggle == null) if (toggle == null)
MCChatCustom.lastmsgCustom.forEach(customLMDConsumer); MCChatCustom.lastmsgCustom.forEach(customLMDConsumer);
else else
@ -141,7 +152,7 @@ public class MCChatUtils {
* @param sender The sender to check perms of or null to send to all that has it toggled * @param sender The sender to check perms of or null to send to all that has it toggled
* @param toggle The toggle to check or null to send to all allowed * @param toggle The toggle to check or null to send to all allowed
*/ */
public static void forAllowedCustomMCChat(Consumer<IChannel> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) { public static void forAllowedCustomMCChat(Consumer<Mono<MessageChannel>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) {
if (notEnabled()) return; if (notEnabled()) return;
MCChatCustom.lastmsgCustom.stream().filter(clmd -> { MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
//new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple //new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
@ -150,7 +161,7 @@ public class MCChatUtils {
if (sender == null) if (sender == null)
return true; return true;
return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)); return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender));
}).forEach(cc -> action.accept(cc.channel)); //TODO: Send error messages on channel connect }).forEach(cc -> action.accept(Mono.just(cc.channel))); //TODO: Send error messages on channel connect
} }
/** /**
@ -161,42 +172,42 @@ public class MCChatUtils {
* @param toggle The toggle to check or null to send to all allowed * @param toggle The toggle to check or null to send to all allowed
* @param hookmsg Whether the message is also sent from the hook * @param hookmsg Whether the message is also sent from the hook
*/ */
public static void forAllowedCustomAndAllMCChat(Consumer<IChannel> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) { public static void forAllowedCustomAndAllMCChat(Consumer<Mono<MessageChannel>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return; if (notEnabled()) return;
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg) if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
forAllMCChat(action); forAllMCChat(action);
forAllowedCustomMCChat(action, sender, toggle); forAllowedCustomMCChat(action, sender, toggle);
} }
public static Consumer<IChannel> send(String message) { public static Consumer<Mono<MessageChannel>> send(String message) {
return ch -> DiscordPlugin.sendMessageToChannel(ch, DPUtils.sanitizeString(message)); return ch -> ch.flatMap(mc -> mc.createMessage(DPUtils.sanitizeString(message))).subscribe();
} }
public static void forAllowedMCChat(Consumer<IChannel> action, TBMCSystemChatEvent event) { public static void forAllowedMCChat(Consumer<Mono<MessageChannel>> action, TBMCSystemChatEvent event) {
if (notEnabled()) return; if (notEnabled()) return;
if (event.getChannel().isGlobal()) if (event.getChannel().isGlobal())
action.accept(module.chatChannel().get()); action.accept(module.chatChannelMono());
for (LastMsgData data : MCChatPrivate.lastmsgPerUser) for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
if (event.shouldSendTo(getSender(data.channel, data.user))) if (event.shouldSendTo(getSender(data.channel.getId(), data.user)))
action.accept(data.channel); action.accept(Mono.just(data.channel)); //TODO: Only store ID?
MCChatCustom.lastmsgCustom.stream().filter(clmd -> { MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
if (!clmd.brtoggles.contains(event.getTarget())) if (!clmd.brtoggles.contains(event.getTarget()))
return false; return false;
return event.shouldSendTo(clmd.dcp); return event.shouldSendTo(clmd.dcp);
}).map(clmd -> clmd.channel).forEach(action); }).map(clmd -> Mono.just(clmd.channel)).forEach(action);
} }
/** /**
* This method will find the best sender to use: if the player is online, use that, if not but connected then use that etc. * This method will find the best sender to use: if the player is online, use that, if not but connected then use that etc.
*/ */
static DiscordSenderBase getSender(IChannel channel, final IUser author) { static DiscordSenderBase getSender(Snowflake channel, final User author) {
//noinspection OptionalGetWithoutIsPresent //noinspection OptionalGetWithoutIsPresent
return Stream.<Supplier<Optional<DiscordSenderBase>>>of( // https://stackoverflow.com/a/28833677/2703239 return Stream.<Supplier<Optional<DiscordSenderBase>>>of( // https://stackoverflow.com/a/28833677/2703239
() -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null () -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null
() -> Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it () -> Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it
() -> Optional.ofNullable(getSender(UnconnectedSenders, channel, author)), // () -> Optional.ofNullable(getSender(UnconnectedSenders, channel, author)), //
() -> Optional.of(addSender(UnconnectedSenders, author, () -> Optional.of(addSender(UnconnectedSenders, author,
new DiscordSender(author, channel)))).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get(); new DiscordSender(author, (MessageChannel) DiscordPlugin.dc.getChannelById(channel).block())))).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get();
} }
/** /**
@ -205,15 +216,15 @@ public class MCChatUtils {
* *
* @param channel The channel to reset in - the process is slightly different for the public, private and custom chats * @param channel The channel to reset in - the process is slightly different for the public, private and custom chats
*/ */
public static void resetLastMessage(IChannel channel) { public static void resetLastMessage(Channel channel) {
if (notEnabled()) return; if (notEnabled()) return;
if (channel.getLongID() == module.chatChannel().get().getLongID()) { if (channel.getId().asLong() == module.chatChannel().get().asLong()) {
(lastmsgdata == null ? lastmsgdata = new LastMsgData(module.chatChannel().get(), null) (lastmsgdata == null ? lastmsgdata = new LastMsgData(module.chatChannelMono().block(), null)
: lastmsgdata).message = null; : lastmsgdata).message = null;
return; return;
} // Don't set the whole object to null, the player and channel information should be preserved } // Don't set the whole object to null, the player and channel information should be preserved
for (LastMsgData data : channel.isPrivate() ? MCChatPrivate.lastmsgPerUser : MCChatCustom.lastmsgCustom) { for (LastMsgData data : channel instanceof PrivateChannel ? MCChatPrivate.lastmsgPerUser : MCChatCustom.lastmsgCustom) {
if (data.channel.getLongID() == channel.getLongID()) { if (data.channel.getId().asLong() == channel.getId().asLong()) {
data.message = null; data.message = null;
return; return;
} }
@ -221,9 +232,23 @@ public class MCChatUtils {
//If it gets here, it's sending a message to a non-chat channel //If it gets here, it's sending a message to a non-chat channel
} }
public static void addStaticExcludedPlugin(Class<? extends Event> event, String plugin) {
staticExcludedPlugins.compute(event, (e, hs) -> hs == null
? Sets.newHashSet(plugin)
: (hs.add(plugin) ? hs : hs));
}
public static void callEventExcludingSome(Event event) { public static void callEventExcludingSome(Event event) {
if (notEnabled()) return; if (notEnabled()) return;
callEventExcluding(event, false, module.excludedPlugins().get()); val second = staticExcludedPlugins.get(event.getClass());
String[] first = module.excludedPlugins().get();
String[] both = second == null ? first
: Arrays.copyOf(first, first.length + second.size());
int i = first.length;
if (second != null)
for (String plugin : second)
both[i++] = plugin;
callEventExcluding(event, false, both);
} }
/** /**
@ -284,13 +309,59 @@ public class MCChatUtils {
} }
} }
/**
* Call it from an async thread.
*/
public static void callLoginEvents(DiscordConnectedPlayer dcp) {
Consumer<Supplier<String>> loginFail = kickMsg -> {
dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg.get());
MCChatPrivate.privateMCChat(dcp.getChannel(), false, dcp.getUser(), dcp.getChromaUser());
}; //Probably also happens if the user is banned or so
val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId());
callEventExcludingSome(event);
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
loginFail.accept(event::getKickMessage);
return;
}
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress());
callEventExcludingSome(ev);
if (ev.getResult() != PlayerLoginEvent.Result.ALLOWED) {
loginFail.accept(ev::getKickMessage);
return;
}
callEventExcludingSome(new PlayerJoinEvent(dcp, ""));
dcp.setLoggedIn(true);
DPUtils.getLogger().info(dcp.getName() + " (" + dcp.getUniqueId() + ") logged in from Discord");
});
}
/**
* Only calls the events if the player is actually logged in
*
* @param dcp The player
* @param needsSync Whether we're in an async thread
*/
public static void callLogoutEvent(DiscordConnectedPlayer dcp, boolean needsSync) {
if (!dcp.isLoggedIn()) return;
val event = new PlayerQuitEvent(dcp, "");
if (needsSync) callEventSync(event);
else callEventExcludingSome(event);
dcp.setLoggedIn(false);
DPUtils.getLogger().info(dcp.getName() + " (" + dcp.getUniqueId() + ") logged out from Discord");
}
static void callEventSync(Event event) {
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event));
}
@RequiredArgsConstructor @RequiredArgsConstructor
public static class LastMsgData { public static class LastMsgData {
public IMessage message; public Message message;
public long time; public long time;
public String content; public String content;
public final IChannel channel; public final MessageChannel channel;
public Channel mcchannel; public buttondevteam.core.component.channel.Channel mcchannel;
public final IUser user; public final User user;
} }
} }

View file

@ -1,11 +1,12 @@
package buttondevteam.discordplugin.mcchat; package buttondevteam.discordplugin.mcchat;
import buttondevteam.discordplugin.*; import buttondevteam.discordplugin.*;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.player.*; import buttondevteam.lib.player.*;
import com.earth2me.essentials.CommandSource; import com.earth2me.essentials.CommandSource;
import discord4j.core.object.entity.Role;
import discord4j.core.object.util.Snowflake;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.val; import lombok.val;
import net.ess3.api.events.AfkStatusChangeEvent; import net.ess3.api.events.AfkStatusChangeEvent;
@ -17,16 +18,14 @@ 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 org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result; import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.BroadcastMessageEvent; import org.bukkit.event.server.BroadcastMessageEvent;
import sx.blah.discord.handle.obj.IRole; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.obj.IUser;
import sx.blah.discord.util.DiscordException; import java.util.Objects;
import sx.blah.discord.util.MissingPermissionsException; import java.util.Optional;
@RequiredArgsConstructor @RequiredArgsConstructor
class MCListener implements Listener { class MCListener implements Listener {
@ -36,9 +35,11 @@ class MCListener implements Listener {
public void onPlayerLogin(PlayerLoginEvent e) { public void onPlayerLogin(PlayerLoginEvent e) {
if (e.getResult() != Result.ALLOWED) if (e.getResult() != Result.ALLOWED)
return; return;
if (e.getPlayer() instanceof DiscordConnectedPlayer)
return;
MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
.ifPresent(dcp -> MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(dcp, ""))); .ifPresent(dcp -> MCChatUtils.callLogoutEvent(dcp, false));
} }
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
@ -49,11 +50,11 @@ class MCListener implements Listener {
final Player p = e.getPlayer(); final Player p = e.getPlayer();
DiscordPlayer dp = e.GetPlayer().getAs(DiscordPlayer.class); DiscordPlayer dp = e.GetPlayer().getAs(DiscordPlayer.class);
if (dp != null) { if (dp != null) {
val user = DiscordPlugin.dc.getUserByID(Long.parseLong(dp.getDiscordID())); val user = DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID())).block();
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(), MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(),
new DiscordPlayerSender(user, user.getOrCreatePMChannel(), p)); new DiscordPlayerSender(user, Objects.requireNonNull(user).getPrivateChannel().block(), p)); //TODO: Don't block
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(), MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(),
new DiscordPlayerSender(user, module.chatChannel().get(), p)); //Stored per-channel new DiscordPlayerSender(user, module.chatChannelMono().block(), p)); //Stored per-channel
} }
final String message = e.GetPlayer().PlayerName().get() + " joined the game"; final String message = e.GetPlayer().PlayerName().get() + " joined the game";
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
@ -67,10 +68,10 @@ class MCListener implements Listener {
return; // Only care about real users return; // Only care about real users
MCChatUtils.OnlineSenders.entrySet() MCChatUtils.OnlineSenders.entrySet()
.removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId()))); .removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId())));
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
() -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) () -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
.ifPresent(dcp -> MCChatUtils.callEventExcludingSome(new PlayerJoinEvent(dcp, "")))); .ifPresent(MCChatUtils::callLoginEvents));
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
ChromaBot.getInstance()::updatePlayerList, 5); ChromaBot.getInstance()::updatePlayerList, 5);
final String message = e.GetPlayer().PlayerName().get() + " left the game"; final String message = e.GetPlayer().PlayerName().get() + " left the game";
@ -99,15 +100,13 @@ class MCListener implements Listener {
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false); MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false);
} }
private ConfigData<IRole> muteRole() { private ConfigData<Mono<Role>> muteRole() {
return DPUtils.roleData(module.getConfig(), "muteRole", "Muted"); return DPUtils.roleData(module.getConfig(), "muteRole", "Muted");
} }
@EventHandler @EventHandler
public void onPlayerMute(MuteStatusChangeEvent e) { public void onPlayerMute(MuteStatusChangeEvent e) {
try { final Mono<Role> role = muteRole().get();
DPUtils.performNoWait(() -> {
final IRole role = muteRole().get();
if (role == null) return; if (role == null) return;
final CommandSource source = e.getAffected().getSource(); final CommandSource source = e.getAffected().getSource();
if (!source.isPlayer()) if (!source.isPlayer())
@ -115,22 +114,20 @@ class MCListener implements Listener {
final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class) final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class)
.getAs(DiscordPlayer.class); .getAs(DiscordPlayer.class);
if (p == null) return; if (p == null) return;
final IUser user = DiscordPlugin.dc.getUserByID( DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID()))
Long.parseLong(p.getDiscordID())); .flatMap(user -> user.asMember(DiscordPlugin.mainServer.getId()))
.flatMap(user -> role.flatMap(r -> {
if (e.getValue()) if (e.getValue())
user.addRole(role); user.addRole(r.getId());
else else
user.removeRole(role); user.removeRole(r.getId());
val modlog = module.modlogChannel().get(); val modlog = module.modlogChannel().get();
String msg = (e.getValue() ? "M" : "Unm") + "uted user: " + user.getName(); String msg = (e.getValue() ? "M" : "Unm") + "uted user: " + user.getUsername() + "#" + user.getDiscriminator();
if (modlog != null)
DiscordPlugin.sendMessageToChannel(modlog, msg);
DPUtils.getLogger().info(msg); DPUtils.getLogger().info(msg);
}); if (modlog != null)
} catch (DiscordException | MissingPermissionsException ex) { return modlog.flatMap(ch -> ch.createMessage(msg));
TBMCCoreAPI.SendException("Failed to give/take Muted role to player " + e.getAffected().getName() + "!", return Mono.empty();
ex); })).subscribe();
}
} }
@EventHandler @EventHandler
@ -148,8 +145,9 @@ class MCListener implements Listener {
String name = event.getSender() instanceof Player ? ((Player) event.getSender()).getDisplayName() String name = event.getSender() instanceof Player ? ((Player) event.getSender()).getDisplayName()
: event.getSender().getName(); : event.getSender().getName();
//Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
val yeehaw = DiscordPlugin.mainServer.getEmojiByName("YEEHAW"); DiscordPlugin.mainServer.getEmojis().filter(e -> "YEEHAW".equals(e.getName()))
MCChatUtils.forAllMCChat(MCChatUtils.send(name + (yeehaw != null ? " <:YEEHAW:" + yeehaw.getStringID() + ">s" : " YEEHAWs"))); .take(1).singleOrEmpty().map(Optional::of).defaultIfEmpty(Optional.empty()).subscribe(yeehaw ->
MCChatUtils.forAllMCChat(MCChatUtils.send(name + (yeehaw.map(guildEmoji -> " <:YEEHAW:" + guildEmoji.getId().asString() + ">s").orElse(" YEEHAWs")))));
} }
@EventHandler @EventHandler

View file

@ -1,18 +1,23 @@
package buttondevteam.discordplugin.mcchat; package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel;
import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.ReadOnlyConfigData;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.util.Snowflake;
import lombok.Getter; import lombok.Getter;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import sx.blah.discord.handle.obj.IChannel; import reactor.core.publisher.Mono;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
@ -23,11 +28,12 @@ import java.util.stream.Collectors;
* Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM. * Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM.
*/ */
public class MinecraftChatModule extends Component<DiscordPlugin> { public class MinecraftChatModule extends Component<DiscordPlugin> {
private @Getter MCChatListener listener; private @Getter
MCChatListener listener;
public MCChatListener getListener() { //It doesn't want to generate /*public MCChatListener getListener() { //It doesn't want to generate
return listener; return listener; - And now ButtonProcessor didn't look beyond this - return instead of continue...
} }*/
/** /**
* A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command! * A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command!
@ -40,33 +46,46 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
/** /**
* The channel to use as the public Minecraft chat - everything public gets broadcasted here * The channel to use as the public Minecraft chat - everything public gets broadcasted here
*/ */
public ConfigData<IChannel> chatChannel() { public ConfigData<Snowflake> chatChannel() {
return DPUtils.channelData(getConfig(), "chatChannel", 239519012529111040L); return DPUtils.snowflakeData(getConfig(), "chatChannel", 239519012529111040L);
}
public Mono<MessageChannel> chatChannelMono() {
return DPUtils.getMessageChannel(chatChannel().getPath(), chatChannel().get());
} }
/** /**
* The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute * The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute
*/ */
public ConfigData<IChannel> modlogChannel() { public ReadOnlyConfigData<Mono<MessageChannel>> modlogChannel() {
return DPUtils.channelData(getConfig(), "modlogChannel", 283840717275791360L); return DPUtils.channelData(getConfig(), "modlogChannel", 283840717275791360L);
} }
/** /**
* 0 * The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here * The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here
*/ */
public ConfigData<String[]> excludedPlugins() { public ConfigData<String[]> excludedPlugins() {
return getConfig().getData("excludedPlugins", new String[]{"ProtocolLib", "LibsDisguises", "JourneyMapServer"}); return getConfig().getData("excludedPlugins", new String[]{"ProtocolLib", "LibsDisguises", "JourneyMapServer"});
} }
/**
* If this setting is on then players logged in through the 'mcchat' command will be able to teleport using plugin commands.
* They can then use commands like /tpahere to teleport others to that place.<br />
* If this is off, then teleporting will have no effect.
*/
public ConfigData<Boolean> allowFakePlayerTeleports() {
return getConfig().getData("allowFakePlayerTeleports", false);
}
@Override @Override
protected void enable() { protected void enable() {
if (DPUtils.disableIfConfigError(this, chatChannel())) return; if (DPUtils.disableIfConfigErrorRes(this, chatChannel(), chatChannelMono()))
return;
listener = new MCChatListener(this); listener = new MCChatListener(this);
DiscordPlugin.dc.getDispatcher().registerListener(listener);
TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin()); TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin());
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin());//These get undone if restarting/resetting - it will ignore events if disabled TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin());//These get undone if restarting/resetting - it will ignore events if disabled
getPlugin().getManager().registerCommand(new MCChatCommand()); getPlugin().getManager().registerCommand(new MCChatCommand());
getPlugin().getManager().registerCommand(new ChannelconCommand()); getPlugin().getManager().registerCommand(new ChannelconCommand(this));
val chcons = getConfig().getConfig().getConfigurationSection("chcons"); val chcons = getConfig().getConfig().getConfigurationSection("chcons");
if (chcons == null) //Fallback to old place if (chcons == null) //Fallback to old place
@ -76,20 +95,28 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
for (val chconkey : chconkeys) { for (val chconkey : chconkeys) {
val chcon = chcons.getConfigurationSection(chconkey); val chcon = chcons.getConfigurationSection(chconkey);
val mcch = Channel.getChannels().filter(ch -> ch.ID.equals(chcon.getString("mcchid"))).findAny(); val mcch = Channel.getChannels().filter(ch -> ch.ID.equals(chcon.getString("mcchid"))).findAny();
val ch = DiscordPlugin.dc.getChannelByID(chcon.getLong("chid")); val ch = DiscordPlugin.dc.getChannelById(Snowflake.of(chcon.getLong("chid"))).block();
val did = chcon.getLong("did"); val did = chcon.getLong("did");
val user = DiscordPlugin.dc.fetchUser(did); val user = DiscordPlugin.dc.getUserById(Snowflake.of(did)).block();
val groupid = chcon.getString("groupid"); val groupid = chcon.getString("groupid");
val toggles = chcon.getInt("toggles"); val toggles = chcon.getInt("toggles");
val brtoggles = chcon.getStringList("brtoggles"); val brtoggles = chcon.getStringList("brtoggles");
if (!mcch.isPresent() || ch == null || user == null || groupid == null) if (!mcch.isPresent() || ch == null || user == null || groupid == null)
continue; continue;
Bukkit.getScheduler().runTask(getPlugin(), () -> { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) Bukkit.getScheduler().runTask(getPlugin(), () -> { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase)
val dcp = new DiscordConnectedPlayer(user, ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname")); val dcp = new DiscordConnectedPlayer(user, (MessageChannel) ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this);
MCChatCustom.addCustomChat(ch, groupid, mcch.get(), user, dcp, toggles, brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::get).filter(Objects::nonNull).collect(Collectors.toSet())); MCChatCustom.addCustomChat((MessageChannel) ch, groupid, mcch.get(), user, dcp, toggles, brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::get).filter(Objects::nonNull).collect(Collectors.toSet()));
}); });
} }
} }
try {
new LPInjector(MainPlugin.Instance);
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e);
} catch (NoClassDefFoundError e) {
getPlugin().getLogger().info("No LuckPerms, not injecting");
}
} }
@Override @Override
@ -97,10 +124,10 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
val chcons = MCChatCustom.getCustomChats(); val chcons = MCChatCustom.getCustomChats();
val chconsc = getConfig().getConfig().createSection("chcons"); val chconsc = getConfig().getConfig().createSection("chcons");
for (val chcon : chcons) { for (val chcon : chcons) {
val chconc = chconsc.createSection(chcon.channel.getStringID()); val chconc = chconsc.createSection(chcon.channel.getId().asString());
chconc.set("mcchid", chcon.mcchannel.ID); chconc.set("mcchid", chcon.mcchannel.ID);
chconc.set("chid", chcon.channel.getLongID()); chconc.set("chid", chcon.channel.getId().asLong());
chconc.set("did", chcon.user.getLongID()); chconc.set("did", chcon.user.getId().asLong());
chconc.set("mcuid", chcon.dcp.getUniqueId().toString()); chconc.set("mcuid", chcon.dcp.getUniqueId().toString());
chconc.set("mcname", chcon.dcp.getName()); chconc.set("mcname", chcon.dcp.getName());
chconc.set("groupid", chcon.groupID); chconc.set("groupid", chcon.groupID);

View file

@ -1,44 +0,0 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayer;
import buttondevteam.lib.player.TBMCPlayerBase;
import org.bukkit.entity.Player;
@CommandClass(modOnly = false, path = "accept")
public class AcceptMCCommand extends DiscordMCCommandBase {
@Override
public String[] GetHelpText(String alias) {
return new String[] { //
"§6---- Accept Discord connection ----", //
"Accept a pending connection between your Discord and Minecraft account.", //
"To start the connection process, do §b/connect <MCname>§r in the " + DPUtils.botmention() + " channel on Discord", //
"Usage: /" + alias + " accept" //
};
}
@Override
public boolean OnCommand(Player player, String alias, String[] args) {
String did = ConnectCommand.WaitingToConnect.get(player.getName());
if (did == null) {
player.sendMessage("§cYou don't have a pending connection to Discord.");
return true;
}
DiscordPlayer dp = ChromaGamerBase.getUser(did, DiscordPlayer.class);
TBMCPlayer mcp = TBMCPlayerBase.getPlayer(player.getUniqueId(), TBMCPlayer.class);
dp.connectWith(mcp);
dp.save();
mcp.save();
ConnectCommand.WaitingToConnect.remove(player.getName());
MCChatUtils.UnconnectedSenders.remove(did); //Remove all unconnected, will be recreated where needed
player.sendMessage("§bAccounts connected.");
return true;
}
}

View file

@ -1,32 +0,0 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.lib.chat.CommandClass;
import org.bukkit.entity.Player;
@CommandClass(modOnly = false, path = "decline")
public class DeclineMCCommand extends DiscordMCCommandBase {
@Override
public String[] GetHelpText(String alias) {
return new String[] { //
"§6---- Decline Discord connection ----", //
"Decline a pending connection between your Discord and Minecraft account.", //
"To start the connection process, do §b/connect <MCname>§r in the " + DPUtils.botmention() + " channel on Discord", //
"Usage: /" + alias + " decline" //
};
}
@Override
public boolean OnCommand(Player player, String alias, String[] args) {
String did = ConnectCommand.WaitingToConnect.remove(player.getName());
if (did == null) {
player.sendMessage("§cYou don't have a pending connection to Discord.");
return true;
}
player.sendMessage("§bPending connection declined.");
return true;
}
}

View file

@ -0,0 +1,131 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.discordplugin.commands.VersionCommand;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayer;
import buttondevteam.lib.player.TBMCPlayerBase;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
@CommandClass(path = "discord", helpText = {
"Discord",
"This command allows performing Discord-related actions."
})
public class DiscordMCCommand extends ICommand2MC {
@Command2.Subcommand
public boolean accept(Player player) {
String did = ConnectCommand.WaitingToConnect.get(player.getName());
if (did == null) {
player.sendMessage("§cYou don't have a pending connection to Discord.");
return true;
}
DiscordPlayer dp = ChromaGamerBase.getUser(did, DiscordPlayer.class);
TBMCPlayer mcp = TBMCPlayerBase.getPlayer(player.getUniqueId(), TBMCPlayer.class);
dp.connectWith(mcp);
dp.save();
mcp.save();
ConnectCommand.WaitingToConnect.remove(player.getName());
MCChatUtils.UnconnectedSenders.remove(did); //Remove all unconnected, will be recreated where needed
player.sendMessage("§bAccounts connected.");
return true;
}
@Command2.Subcommand
public boolean decline(Player player) {
String did = ConnectCommand.WaitingToConnect.remove(player.getName());
if (did == null) {
player.sendMessage("§cYou don't have a pending connection to Discord.");
return true;
}
player.sendMessage("§bPending connection declined.");
return true;
}
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = {
"Reload Discord plugin",
"Reloads the config. To apply some changes, you may need to also run /discord reset."
})
public void reload(CommandSender sender) {
if (DiscordPlugin.plugin.tryReloadConfig())
sender.sendMessage("§bConfig reloaded.");
else
sender.sendMessage("§cFailed to reload config.");
}
public static boolean resetting = false;
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = {
"Reset ChromaBot", //
"This command disables and then enables the plugin." //
})
public void reset(CommandSender sender) {
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> {
resetting = true; //Turned off after sending enable message (ReadyEvent)
sender.sendMessage("§bDisabling DiscordPlugin...");
Bukkit.getPluginManager().disablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bEnabling DiscordPlugin...");
Bukkit.getPluginManager().enablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bReset finished!");
});
}
@Command2.Subcommand(helpText = {
"Version command",
"Prints the plugin version"
})
public void version(CommandSender sender) {
sender.sendMessage(VersionCommand.getVersion());
}
@Command2.Subcommand(helpText = {
"Invite",
"Shows an invite link to the server"
})
public void invite(CommandSender sender) {
String invi = DiscordPlugin.plugin.inviteLink().get();
if (invi.length() > 0) {
sender.sendMessage("§bInvite link: " + invi);
return;
}
DiscordPlugin.mainServer.getInvites().limitRequest(1)
.switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("§cNo invites found for the server.")))
.subscribe(inv -> {
sender.sendMessage("§bInvite link: https://discord.gg/" + inv.getCode());
}, e -> sender.sendMessage("§cThe invite link is not set and the bot has no permission to get it."));
}
@Override
public String[] getHelpText(Method method, Command2.Subcommand ann) {
switch (method.getName()) {
case "accept":
return new String[]{ //
"Accept Discord connection", //
"Accept a pending connection between your Discord and Minecraft account.", //
"To start the connection process, do §b/connect <MCname>§r in the " + DPUtils.botmention() + " channel on Discord", //
};
case "decline":
return new String[]{ //
"Decline Discord connection", //
"Decline a pending connection between your Discord and Minecraft account.", //
"To start the connection process, do §b/connect <MCname>§r in the " + DPUtils.botmention() + " channel on Discord", //
};
default:
return super.getHelpText(method, ann);
}
}
}

View file

@ -1,9 +0,0 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.PlayerCommandBase;
@CommandClass(modOnly = false, path = "discord")
public abstract class DiscordMCCommandBase extends PlayerCommandBase {
}

View file

@ -1,26 +0,0 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.TBMCCommandBase;
import org.bukkit.command.CommandSender;
@CommandClass(path = "discord reload")
public class ReloadMCCommand extends TBMCCommandBase {
@Override
public boolean OnCommand(CommandSender sender, String alias, String[] args) {
if (DiscordPlugin.plugin.tryReloadConfig())
sender.sendMessage("§bConfig reloaded."); //TODO: Convert to new command system
else
sender.sendMessage("§cFailed to reload config.");
return true;
}
@Override
public String[] GetHelpText(String alias) {
return new String[]{
"Reload",
"Reloads the config. To apply some changes, you may need to also run /discord reset."
};
}
}

View file

@ -1,35 +0,0 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.TBMCCommandBase;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@CommandClass(path = "discord reset", modOnly = true)
public class ResetMCCommand extends TBMCCommandBase { //Not player-only, so not using DiscordMCCommandBase
public static boolean resetting = false;
@Override
public boolean OnCommand(CommandSender sender, String s, String[] strings) {
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> {
resetting = true; //Turned off after sending enable message (ReadyEvent)
sender.sendMessage("§bDisabling DiscordPlugin...");
Bukkit.getPluginManager().disablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bEnabling DiscordPlugin...");
Bukkit.getPluginManager().enablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bReset finished!");
});
return true;
}
@Override
public String[] GetHelpText(String s) {
return new String[]{ //
"§6---- Reset ChromaBot ----", //
"This command disables and then enables the plugin." //
};
}
}

View file

@ -1,20 +0,0 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.commands.VersionCommand;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.TBMCCommandBase;
import org.bukkit.command.CommandSender;
@CommandClass(path = "discord version")
public class VersionMCCommand extends TBMCCommandBase {
@Override
public boolean OnCommand(CommandSender commandSender, String s, String[] strings) {
commandSender.sendMessage(VersionCommand.getVersion());
return true;
}
@Override
public String[] GetHelpText(String s) {
return VersionCommand.getVersion(); //Heh
}
}

View file

@ -1,6 +1,10 @@
package buttondevteam.discordplugin.playerfaker; package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.DiscordSenderBase; import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bukkit.*; import org.bukkit.*;
@ -11,8 +15,6 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.metadata.MetadataValue; import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
import java.util.*; import java.util.*;
@ -20,10 +22,11 @@ import java.util.*;
@Setter @Setter
@SuppressWarnings("deprecated") @SuppressWarnings("deprecated")
public abstract class DiscordEntity extends DiscordSenderBase implements Entity { public abstract class DiscordEntity extends DiscordSenderBase implements Entity {
protected DiscordEntity(IUser user, IChannel channel, int entityId, UUID uuid) { protected DiscordEntity(User user, MessageChannel channel, int entityId, UUID uuid, MinecraftChatModule module) {
super(user, channel); super(user, channel);
this.entityId = entityId; this.entityId = entityId;
uniqueId = uuid; uniqueId = uuid;
this.module = module;
} }
private HashMap<String, MetadataValue> metadata = new HashMap<String, MetadataValue>(); private HashMap<String, MetadataValue> metadata = new HashMap<String, MetadataValue>();
@ -34,6 +37,7 @@ public abstract class DiscordEntity extends DiscordSenderBase implements Entity
private EntityDamageEvent lastDamageCause; private EntityDamageEvent lastDamageCause;
private final Set<String> scoreboardTags = new HashSet<String>(); private final Set<String> scoreboardTags = new HashSet<String>();
private final UUID uniqueId; private final UUID uniqueId;
private final MinecraftChatModule module;
@Override @Override
public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
@ -42,7 +46,7 @@ public abstract class DiscordEntity extends DiscordSenderBase implements Entity
@Override @Override
public List<MetadataValue> getMetadata(String metadataKey) { public List<MetadataValue> getMetadata(String metadataKey) {
return Arrays.asList(metadata.get(metadataKey)); // Who needs multiple data anyways return Collections.singletonList(metadata.get(metadataKey)); // Who needs multiple data anyways
} }
@Override @Override
@ -91,31 +95,35 @@ public abstract class DiscordEntity extends DiscordSenderBase implements Entity
@Override @Override
public boolean teleport(Location location) { public boolean teleport(Location location) {
if (module.allowFakePlayerTeleports().get())
this.location = location; this.location = location;
return true; return true;
} }
@Override @Override
public boolean teleport(Location location, TeleportCause cause) { public boolean teleport(Location location, TeleportCause cause) {
if (module.allowFakePlayerTeleports().get())
this.location = location; this.location = location;
return true; return true;
} }
@Override @Override
public boolean teleport(Entity destination) { public boolean teleport(Entity destination) {
if (module.allowFakePlayerTeleports().get())
this.location = destination.getLocation(); this.location = destination.getLocation();
return true; return true;
} }
@Override @Override
public boolean teleport(Entity destination, TeleportCause cause) { public boolean teleport(Entity destination, TeleportCause cause) {
if (module.allowFakePlayerTeleports().get())
this.location = destination.getLocation(); this.location = destination.getLocation();
return true; return true;
} }
@Override @Override
public List<Entity> getNearbyEntities(double x, double y, double z) { public List<Entity> getNearbyEntities(double x, double y, double z) {
return Arrays.asList(); return Collections.emptyList();
} }
@Override @Override
@ -163,7 +171,7 @@ public abstract class DiscordEntity extends DiscordSenderBase implements Entity
@Override @Override
public List<Entity> getPassengers() { public List<Entity> getPassengers() {
return Arrays.asList(); return Collections.emptyList();
} }
@Override @Override

View file

@ -1,7 +1,11 @@
package buttondevteam.discordplugin.playerfaker; package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Delegate; import lombok.experimental.Delegate;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.advancement.Advancement; import org.bukkit.advancement.Advancement;
@ -14,27 +18,39 @@ import org.bukkit.entity.Player;
import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.map.MapView; import org.bukkit.map.MapView;
import org.bukkit.permissions.PermissibleBase; import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.ServerOperator;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Scoreboard;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.*; import java.util.*;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class DiscordFakePlayer extends DiscordHumanEntity implements Player { public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
protected DiscordFakePlayer(IUser user, IChannel channel, int entityId, UUID uuid, String mcname) { protected DiscordFakePlayer(User user, MessageChannel channel, int entityId, UUID uuid, String mcname, MinecraftChatModule module) {
super(user, channel, entityId, uuid); super(user, channel, entityId, uuid, module);
perm = new PermissibleBase(Bukkit.getOfflinePlayer(uuid)); origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid));
name = mcname; name = mcname;
} }
@Delegate @Delegate(excludes = ServerOperator.class)
private PermissibleBase perm; private PermissibleBase origPerm;
private @Getter String name; private @Getter String name;
private @Getter OfflinePlayer basePlayer;
@Getter
@Setter
private PermissibleBase perm;
public void setOp(boolean value) { //CraftPlayer-compatible implementation
this.origPerm.setOp(value);
this.perm.recalculatePermissions();
}
public boolean isOp() { return this.origPerm.isOp(); }
@Override @Override
public EntityType getType() { public EntityType getType() {
return EntityType.PLAYER; return EntityType.PLAYER;
@ -42,7 +58,7 @@ public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
@Override @Override
public String getCustomName() { public String getCustomName() {
return user.getName(); return user.getUsername();
} }
@Override @Override
@ -127,7 +143,7 @@ public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
@Override @Override
public String getDisplayName() { public String getDisplayName() {
return user.getDisplayName(DiscordPlugin.mainServer); return Objects.requireNonNull(user.asMember(DiscordPlugin.mainServer.getId()).block()).getDisplayName();
} }
@Override @Override

View file

@ -1,5 +1,8 @@
package buttondevteam.discordplugin.playerfaker; package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
@ -8,14 +11,12 @@ import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Villager; import org.bukkit.entity.Villager;
import org.bukkit.inventory.*; import org.bukkit.inventory.*;
import org.bukkit.inventory.InventoryView.Property; import org.bukkit.inventory.InventoryView.Property;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
import java.util.UUID; import java.util.UUID;
public abstract class DiscordHumanEntity extends DiscordLivingEntity implements HumanEntity { public abstract class DiscordHumanEntity extends DiscordLivingEntity implements HumanEntity {
protected DiscordHumanEntity(IUser user, IChannel channel, int entityId, UUID uuid) { protected DiscordHumanEntity(User user, MessageChannel channel, int entityId, UUID uuid, MinecraftChatModule module) {
super(user, channel, entityId, uuid); super(user, channel, entityId, uuid, module);
} }
private PlayerInventory inv = new DiscordPlayerInventory(this); private PlayerInventory inv = new DiscordPlayerInventory(this);

View file

@ -1,5 +1,8 @@
package buttondevteam.discordplugin.playerfaker; package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bukkit.Location; import org.bukkit.Location;
@ -16,15 +19,13 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IUser;
import java.util.*; import java.util.*;
public abstract class DiscordLivingEntity extends DiscordEntity implements LivingEntity { public abstract class DiscordLivingEntity extends DiscordEntity implements LivingEntity {
protected DiscordLivingEntity(IUser user, IChannel channel, int entityId, UUID uuid) { protected DiscordLivingEntity(User user, MessageChannel channel, int entityId, UUID uuid, MinecraftChatModule module) {
super(user, channel, entityId, uuid); super(user, channel, entityId, uuid, module);
} }
private @Getter EntityEquipment equipment = new DiscordEntityEquipment(this); private @Getter EntityEquipment equipment = new DiscordEntityEquipment(this);

View file

@ -0,0 +1,240 @@
package buttondevteam.discordplugin.playerfaker.perm;
import buttondevteam.core.MainPlugin;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer;
import buttondevteam.lib.TBMCCoreAPI;
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase;
import me.lucko.luckperms.bukkit.inject.permissible.LPPermissible;
import me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.User;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
public final class LPInjector implements Listener { //Disable login event for LuckPerms
private LPBukkitPlugin plugin;
private BukkitConnectionListener connectionListener;
private Set<UUID> deniedLogin;
private Field detectedCraftBukkitOfflineMode;
private Method printCraftBukkitOfflineModeError;
private Field PERMISSIBLE_BASE_ATTACHMENTS_FIELD;
private Method convertAndAddAttachments;
private Method getActive;
private Method setOldPermissible;
private Method getOldPermissible;
public LPInjector(MainPlugin mp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
LPBukkitBootstrap bs = (LPBukkitBootstrap) Bukkit.getPluginManager().getPlugin("LuckPerms");
Field field = LPBukkitBootstrap.class.getDeclaredField("plugin");
field.setAccessible(true);
plugin = (LPBukkitPlugin) field.get(bs);
MCChatUtils.addStaticExcludedPlugin(PlayerLoginEvent.class, "LuckPerms");
MCChatUtils.addStaticExcludedPlugin(PlayerQuitEvent.class, "LuckPerms");
field = LPBukkitPlugin.class.getDeclaredField("connectionListener");
field.setAccessible(true);
connectionListener = (BukkitConnectionListener) field.get(plugin);
field = connectionListener.getClass().getDeclaredField("deniedLogin");
field.setAccessible(true);
//noinspection unchecked
deniedLogin = (Set<UUID>) field.get(connectionListener);
field = connectionListener.getClass().getDeclaredField("detectedCraftBukkitOfflineMode");
field.setAccessible(true);
detectedCraftBukkitOfflineMode = field;
printCraftBukkitOfflineModeError = connectionListener.getClass().getDeclaredMethod("printCraftBukkitOfflineModeError");
printCraftBukkitOfflineModeError.setAccessible(true);
//PERMISSIBLE_FIELD = DiscordFakePlayer.class.getDeclaredField("perm");
//PERMISSIBLE_FIELD.setAccessible(true); //Hacking my own plugin, while we're at it
PERMISSIBLE_BASE_ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments");
PERMISSIBLE_BASE_ATTACHMENTS_FIELD.setAccessible(true);
convertAndAddAttachments = LPPermissible.class.getDeclaredMethod("convertAndAddAttachments", Collection.class);
convertAndAddAttachments.setAccessible(true);
getActive = LPPermissible.class.getDeclaredMethod("getActive");
getActive.setAccessible(true);
setOldPermissible = LPPermissible.class.getDeclaredMethod("setOldPermissible", PermissibleBase.class);
setOldPermissible.setAccessible(true);
getOldPermissible = LPPermissible.class.getDeclaredMethod("getOldPermissible");
getOldPermissible.setAccessible(true);
TBMCCoreAPI.RegisterEventsForExceptions(this, mp);
}
//Code copied from LuckPerms - me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent e) {
/* Called when the player starts logging into the server.
At this point, the users data should be present and loaded. */
if (!(e.getPlayer() instanceof DiscordFakePlayer))
return; //Normal players must be handled by the plugin
final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer();
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLogger().info("Processing login for " + player.getUniqueId() + " - " + player.getName());
}
final User user = plugin.getUserManager().getIfLoaded(player.getUniqueId());
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
if (user == null) {
deniedLogin.add(player.getUniqueId());
if (!connectionListener.getUniqueConnections().contains(player.getUniqueId())) {
plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() +
" doesn't have data pre-loaded, they have never been processed during pre-login in this session." +
" - denying login.");
try {
if ((Boolean) detectedCraftBukkitOfflineMode.get(connectionListener)) {
printCraftBukkitOfflineModeError.invoke(connectionListener);
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR_CB_OFFLINE_MODE.asString(plugin.getLocaleManager()));
return;
}
} catch (IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
}
} else {
plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() +
" doesn't currently have data pre-loaded, but they have been processed before in this session." +
" - denying login.");
}
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR.asString(plugin.getLocaleManager()));
return;
}
// User instance is there, now we can inject our custom Permissible into the player.
// Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online.
try {
// get the existing PermissibleBase held by the player
PermissibleBase oldPermissible = player.getPerm();
// Make a new permissible for the user
LPPermissible lpPermissible = new LPPermissible(player, user, plugin);
// Inject into the player
inject(player, lpPermissible, oldPermissible);
} catch (Throwable t) {
plugin.getLogger().warn("Exception thrown when setting up permissions for " +
player.getUniqueId() + " - " + player.getName() + " - denying login.");
t.printStackTrace();
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_SETUP_ERROR.asString(plugin.getLocaleManager()));
return;
}
plugin.refreshAutoOp(player, true);
}
// Wait until the last priority to unload, so plugins can still perform permission checks on this event
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent e) {
if (!(e.getPlayer() instanceof DiscordFakePlayer))
return;
final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer();
connectionListener.handleDisconnect(player.getUniqueId());
// perform unhooking from bukkit objects 1 tick later.
// this allows plugins listening after us on MONITOR to still have intact permissions data
this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> {
// Remove the custom permissible
try {
uninject(player, true);
} catch (Exception ex) {
ex.printStackTrace();
}
// Handle auto op
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1L);
}
//me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector
private void inject(DiscordFakePlayer player, LPPermissible newPermissible, PermissibleBase oldPermissible) throws IllegalAccessException, InvocationTargetException {
// seems we have already injected into this player.
if (oldPermissible instanceof LPPermissible) {
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
}
// Move attachments over from the old permissible
//noinspection unchecked
List<PermissionAttachment> attachments = (List<PermissionAttachment>) PERMISSIBLE_BASE_ATTACHMENTS_FIELD.get(oldPermissible);
convertAndAddAttachments.invoke(newPermissible, attachments);
attachments.clear();
oldPermissible.clearPermissions();
// Setup the new permissible
((AtomicBoolean) getActive.invoke(newPermissible)).set(true);
setOldPermissible.invoke(newPermissible, oldPermissible);
// inject the new instance
player.setPerm(newPermissible);
}
private void uninject(DiscordFakePlayer player, boolean dummy) throws Exception {
// gets the players current permissible.
PermissibleBase permissible = player.getPerm();
// only uninject if the permissible was a luckperms one.
if (permissible instanceof LPPermissible) {
LPPermissible lpPermissible = ((LPPermissible) permissible);
// clear all permissions
lpPermissible.clearPermissions();
// set to inactive
((AtomicBoolean) getActive.invoke(lpPermissible)).set(false);
// handle the replacement permissible.
if (dummy) {
// just inject a dummy class. this is used when we know the player is about to quit the server.
player.setPerm(DummyPermissibleBase.INSTANCE);
} else {
PermissibleBase newPb = (PermissibleBase) getOldPermissible.invoke(lpPermissible);
if (newPb == null) {
newPb = new PermissibleBase(player);
}
player.setPerm(newPb);
}
}
}
}

View file

@ -4,17 +4,19 @@ import buttondevteam.core.ComponentManager;
import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ReadOnlyConfigData;
import discord4j.core.event.domain.role.RoleCreateEvent;
import discord4j.core.event.domain.role.RoleDeleteEvent;
import discord4j.core.event.domain.role.RoleEvent;
import discord4j.core.event.domain.role.RoleUpdateEvent;
import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.Role;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import sx.blah.discord.handle.impl.events.guild.role.RoleCreateEvent; import reactor.core.publisher.Mono;
import sx.blah.discord.handle.impl.events.guild.role.RoleDeleteEvent;
import sx.blah.discord.handle.impl.events.guild.role.RoleEvent;
import sx.blah.discord.handle.impl.events.guild.role.RoleUpdateEvent;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IRole;
import java.awt.*; import java.awt.*;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -24,7 +26,7 @@ public class GameRoleModule extends Component<DiscordPlugin> {
@Override @Override
protected void enable() { protected void enable() {
getPlugin().getManager().registerCommand(new RoleCommand(this)); getPlugin().getManager().registerCommand(new RoleCommand(this));
GameRoles = DiscordPlugin.mainServer.getRoles().stream().filter(this::isGameRole).map(IRole::getName).collect(Collectors.toList()); GameRoles = DiscordPlugin.mainServer.getRoles().filterWhen(this::isGameRole).map(Role::getName).collect(Collectors.toList()).block();
} }
@Override @Override
@ -32,7 +34,7 @@ public class GameRoleModule extends Component<DiscordPlugin> {
} }
private ConfigData<IChannel> logChannel() { private ReadOnlyConfigData<Mono<MessageChannel>> logChannel() {
return DPUtils.channelData(getConfig(), "logChannel", 239519012529111040L); return DPUtils.channelData(getConfig(), "logChannel", 239519012529111040L);
} }
@ -43,41 +45,55 @@ public class GameRoleModule extends Component<DiscordPlugin> {
val logChannel = grm.logChannel().get(); val logChannel = grm.logChannel().get();
if (roleEvent instanceof RoleCreateEvent) { if (roleEvent instanceof RoleCreateEvent) {
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> { Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> {
if (roleEvent.getRole().isDeleted() || !grm.isGameRole(roleEvent.getRole())) Role role=((RoleCreateEvent) roleEvent).getRole();
return; //Deleted or not a game role grm.isGameRole(role).flatMap(b -> {
GameRoles.add(roleEvent.getRole().getName()); if (!b)
return Mono.empty(); //Deleted or not a game role
GameRoles.add(role.getName());
if (logChannel != null) if (logChannel != null)
DiscordPlugin.sendMessageToChannel(logChannel, "Added " + roleEvent.getRole().getName() + " as game role. If you don't want this, change the role's color from the default."); return logChannel.flatMap(ch -> ch.createMessage("Added " + role.getName() + " as game role. If you don't want this, change the role's color from the default."));
return Mono.empty();
}).subscribe();
}, 100); }, 100);
} else if (roleEvent instanceof RoleDeleteEvent) { } else if (roleEvent instanceof RoleDeleteEvent) {
if (GameRoles.remove(roleEvent.getRole().getName()) && logChannel != null) Role role=((RoleDeleteEvent) roleEvent).getRole().orElse(null);
DiscordPlugin.sendMessageToChannel(logChannel, "Removed " + roleEvent.getRole().getName() + " as a game role."); if(role==null) return;
if (GameRoles.remove(role.getName()) && logChannel != null)
logChannel.flatMap(ch -> ch.createMessage("Removed " + role.getName() + " as a game role.")).subscribe();
} else if (roleEvent instanceof RoleUpdateEvent) { } else if (roleEvent instanceof RoleUpdateEvent) {
val event = (RoleUpdateEvent) roleEvent; val event = (RoleUpdateEvent) roleEvent;
if (!grm.isGameRole(event.getNewRole())) { if(!event.getOld().isPresent()) {
if (GameRoles.remove(event.getOldRole().getName()) && logChannel != null) DPUtils.getLogger().warning("Old role not stored, cannot update game role!");
DiscordPlugin.sendMessageToChannel(logChannel, "Removed " + event.getOldRole().getName() + " as a game role because it's color changed.");
} else {
if (GameRoles.contains(event.getOldRole().getName()) && event.getOldRole().getName().equals(event.getNewRole().getName()))
return; return;
boolean removed = GameRoles.remove(event.getOldRole().getName()); //Regardless of whether it was a game role }
GameRoles.add(event.getNewRole().getName()); //Add it because it has no color Role or=event.getOld().get();
grm.isGameRole(event.getCurrent()).flatMap(b -> {
if (!b) {
if (GameRoles.remove(or.getName()) && logChannel != null)
return logChannel.flatMap(ch -> ch.createMessage("Removed " + or.getName() + " as a game role because it's color changed."));
} else {
if (GameRoles.contains(or.getName()) && or.getName().equals(event.getCurrent().getName()))
return Mono.empty();
boolean removed = GameRoles.remove(or.getName()); //Regardless of whether it was a game role
GameRoles.add(event.getCurrent().getName()); //Add it because it has no color
if (logChannel != null) { if (logChannel != null) {
if (removed) if (removed)
DiscordPlugin.sendMessageToChannel(logChannel, "Changed game role from " + event.getOldRole().getName() + " to " + event.getNewRole().getName() + "."); return logChannel.flatMap(ch -> ch.createMessage("Changed game role from " + or.getName() + " to " + event.getCurrent().getName() + "."));
else else
DiscordPlugin.sendMessageToChannel(logChannel, "Added " + event.getNewRole().getName() + " as game role because it has the default color."); return logChannel.flatMap(ch -> ch.createMessage("Added " + event.getCurrent().getName() + " as game role because it has the default color."));
} }
} }
return Mono.empty();
}).subscribe();
} }
} }
private boolean isGameRole(IRole r) { private Mono<Boolean> isGameRole(Role r) {
if (r.getGuild().getLongID() != DiscordPlugin.mainServer.getLongID()) if (r.getGuildId().asLong() != DiscordPlugin.mainServer.getId().asLong())
return false; //Only allow on the main server return Mono.just(false); //Only allow on the main server
val rc = new Color(149, 165, 166, 0); val rc = new Color(149, 165, 166, 0);
return r.getColor().equals(rc) return Mono.just(r.getColor().equals(rc)).filter(b -> b).flatMap(b ->
&& DiscordPlugin.dc.getOurUser().getRolesForGuild(DiscordPlugin.mainServer) DiscordPlugin.dc.getSelf().flatMap(u -> u.asMember(DiscordPlugin.mainServer.getId())).flatMap(m -> m.hasHigherRoles(Collections.singleton(r)))) //Below one of our roles
.stream().anyMatch(or -> r.getPosition() < or.getPosition()); //Below one of our roles .defaultIfEmpty(false);
} }
} }

View file

@ -1,14 +1,13 @@
package buttondevteam.discordplugin.role; package buttondevteam.discordplugin.role;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.commands.Command2DCSender; import buttondevteam.discordplugin.commands.Command2DCSender;
import buttondevteam.discordplugin.commands.ICommand2DC; import buttondevteam.discordplugin.commands.ICommand2DC;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import discord4j.core.object.entity.Role;
import lombok.val; import lombok.val;
import sx.blah.discord.handle.obj.IRole;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -27,12 +26,12 @@ public class RoleCommand extends ICommand2DC {
"This command adds a role to your account." "This command adds a role to your account."
}) })
public boolean add(Command2DCSender sender, @Command2.TextArg String rolename) { public boolean add(Command2DCSender sender, @Command2.TextArg String rolename) {
final IRole role = checkAndGetRole(sender, rolename); final Role role = checkAndGetRole(sender, rolename);
if (role == null) if (role == null)
return true; return true;
try { try {
DPUtils.perform(() -> sender.getMessage().getAuthor().addRole(role)); sender.getMessage().getAuthorAsMember()
sender.sendMessage("added role."); .subscribe(m -> m.addRole(role.getId()).subscribe(r -> sender.sendMessage("added role.")));
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Error while adding role!", e); TBMCCoreAPI.SendException("Error while adding role!", e);
sender.sendMessage("an error occured while adding the role."); sender.sendMessage("an error occured while adding the role.");
@ -45,12 +44,12 @@ public class RoleCommand extends ICommand2DC {
"This command removes a role from your account." "This command removes a role from your account."
}) })
public boolean remove(Command2DCSender sender, @Command2.TextArg String rolename) { public boolean remove(Command2DCSender sender, @Command2.TextArg String rolename) {
final IRole role = checkAndGetRole(sender, rolename); final Role role = checkAndGetRole(sender, rolename);
if (role == null) if (role == null)
return true; return true;
try { try {
DPUtils.perform(() -> sender.getMessage().getAuthor().removeRole(role)); sender.getMessage().getAuthorAsMember()
sender.sendMessage("removed role."); .subscribe(m -> m.removeRole(role.getId()).subscribe(r -> sender.sendMessage("removed role.")));
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Error while removing role!", e); TBMCCoreAPI.SendException("Error while removing role!", e);
sender.sendMessage("an error occured while removing the role."); sender.sendMessage("an error occured while removing the role.");
@ -63,7 +62,7 @@ public class RoleCommand extends ICommand2DC {
sender.sendMessage("list of roles:\n" + grm.GameRoles.stream().sorted().collect(Collectors.joining("\n"))); sender.sendMessage("list of roles:\n" + grm.GameRoles.stream().sorted().collect(Collectors.joining("\n")));
} }
private IRole checkAndGetRole(Command2DCSender sender, String rolename) { private Role checkAndGetRole(Command2DCSender sender, String rolename) {
String rname = rolename; String rname = rolename;
if (!grm.GameRoles.contains(rolename)) { //If not found as-is, correct case if (!grm.GameRoles.contains(rolename)) { //If not found as-is, correct case
val orn = grm.GameRoles.stream().filter(r -> r.equalsIgnoreCase(rolename)).findAny(); val orn = grm.GameRoles.stream().filter(r -> r.equalsIgnoreCase(rolename)).findAny();
@ -74,7 +73,12 @@ public class RoleCommand extends ICommand2DC {
} }
rname = orn.get(); rname = orn.get();
} }
final List<IRole> roles = DiscordPlugin.mainServer.getRolesByName(rname); val frname = rname;
final List<Role> roles = DiscordPlugin.mainServer.getRoles().filter(r -> r.getName().equals(frname)).collectList().block();
if (roles == null) {
sender.sendMessage("an error occured.");
return null;
}
if (roles.size() == 0) { if (roles.size() == 0) {
sender.sendMessage("the specified role cannot be found on Discord! Removing from the list."); sender.sendMessage("the specified role cannot be found on Discord! Removing from the list.");
grm.GameRoles.remove(rolename); grm.GameRoles.remove(rolename);

View file

@ -0,0 +1,16 @@
package buttondevteam.discordplugin.util;
import buttondevteam.discordplugin.listeners.CommonListeners;
public class Timings {
private long start;
public Timings() {
start = System.nanoTime();
}
public void printElapsed(String message) {
CommonListeners.debug(message + " (" + (System.nanoTime() - start) / 1000000L + ")");
start = System.nanoTime();
}
}