All classes converted that I wanted
This commit is contained in:
parent
9f47509dcb
commit
c57ac26b2d
37 changed files with 1191 additions and 1396 deletions
|
@ -1,23 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.util.DPState;
|
|
||||||
import org.apache.logging.log4j.Level;
|
|
||||||
import org.apache.logging.log4j.core.Filter;
|
|
||||||
import org.apache.logging.log4j.core.LogEvent;
|
|
||||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
|
||||||
import org.apache.logging.log4j.core.filter.LevelRangeFilter;
|
|
||||||
import org.apache.logging.log4j.core.layout.PatternLayout;
|
|
||||||
|
|
||||||
public class BukkitLogWatcher extends AbstractAppender {
|
|
||||||
protected BukkitLogWatcher() {
|
|
||||||
super("ChromaDiscord",
|
|
||||||
LevelRangeFilter.createFilter(Level.INFO, Level.INFO, Filter.Result.ACCEPT, Filter.Result.DENY),
|
|
||||||
PatternLayout.createDefaultLayout());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(LogEvent logEvent) {
|
|
||||||
if (logEvent.getMessage().getFormattedMessage().contains("Attempting to restart with "))
|
|
||||||
MinecraftChatModule.state = DPState.RESTARTING_SERVER;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.mcchat.MinecraftChatModule
|
||||||
|
import buttondevteam.discordplugin.util.DPState
|
||||||
|
import org.apache.logging.log4j.Level
|
||||||
|
import org.apache.logging.log4j.core.{Filter, LogEvent}
|
||||||
|
import org.apache.logging.log4j.core.appender.AbstractAppender
|
||||||
|
import org.apache.logging.log4j.core.filter.LevelRangeFilter
|
||||||
|
import org.apache.logging.log4j.core.layout.PatternLayout
|
||||||
|
|
||||||
|
class BukkitLogWatcher private[discordplugin]() extends AbstractAppender("ChromaDiscord",
|
||||||
|
LevelRangeFilter.createFilter(Level.INFO, Level.INFO, Filter.Result.ACCEPT, Filter.Result.DENY),
|
||||||
|
PatternLayout.createDefaultLayout) {
|
||||||
|
override def append(logEvent: LogEvent): Unit =
|
||||||
|
if (logEvent.getMessage.getFormattedMessage.contains("Attempting to restart with "))
|
||||||
|
MinecraftChatModule.state = DPState.RESTARTING_SERVER
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
public enum ChannelconBroadcast {
|
|
||||||
JOINLEAVE,
|
|
||||||
AFK,
|
|
||||||
RESTART,
|
|
||||||
DEATH,
|
|
||||||
BROADCAST;
|
|
||||||
|
|
||||||
public final int flag;
|
|
||||||
|
|
||||||
ChannelconBroadcast() {
|
|
||||||
this.flag = 1 << this.ordinal();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
object ChannelconBroadcast extends Enumeration {
|
||||||
|
type ChannelconBroadcast = Value
|
||||||
|
val JOINLEAVE, AFK, RESTART, DEATH, BROADCAST = Value
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package buttondevteam.discordplugin
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast
|
||||||
import buttondevteam.discordplugin.mcchat.MCChatUtils
|
import buttondevteam.discordplugin.mcchat.MCChatUtils
|
||||||
import discord4j.core.`object`.entity.Message
|
import discord4j.core.`object`.entity.Message
|
||||||
import discord4j.core.`object`.entity.channel.MessageChannel
|
import discord4j.core.`object`.entity.channel.MessageChannel
|
|
@ -1,222 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
|
||||||
import buttondevteam.lib.architecture.Component;
|
|
||||||
import buttondevteam.lib.architecture.ConfigData;
|
|
||||||
import buttondevteam.lib.architecture.IHaveConfig;
|
|
||||||
import buttondevteam.lib.architecture.ReadOnlyConfigData;
|
|
||||||
import discord4j.common.util.Snowflake;
|
|
||||||
import discord4j.core.object.entity.Guild;
|
|
||||||
import discord4j.core.object.entity.Message;
|
|
||||||
import discord4j.core.object.entity.Role;
|
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
|
||||||
import discord4j.core.spec.EmbedCreateSpec;
|
|
||||||
import lombok.val;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public final class DPUtils {
|
|
||||||
|
|
||||||
private static final Pattern URL_PATTERN = Pattern.compile("https?://\\S*");
|
|
||||||
private static final Pattern FORMAT_PATTERN = Pattern.compile("[*_~]");
|
|
||||||
|
|
||||||
public static EmbedCreateSpec embedWithHead(EmbedCreateSpec ecs, String displayname, String playername, String profileUrl) {
|
|
||||||
return ecs.setAuthor(displayname, profileUrl, "https://minotar.net/avatar/" + playername + "/32.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes §[char] colour codes from strings & escapes them for Discord <br>
|
|
||||||
* Ensure that this method only gets called once (escaping)
|
|
||||||
*/
|
|
||||||
public static String sanitizeString(String string) {
|
|
||||||
return escape(sanitizeStringNoEscape(string));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes §[char] colour codes from strings
|
|
||||||
*/
|
|
||||||
public static String sanitizeStringNoEscape(String string) {
|
|
||||||
StringBuilder sanitizedString = new StringBuilder();
|
|
||||||
boolean random = false;
|
|
||||||
for (int i = 0; i < string.length(); i++) {
|
|
||||||
if (string.charAt(i) == '§') {
|
|
||||||
i++;// Skips the data value, the 4 in "§4Alisolarflare"
|
|
||||||
random = string.charAt(i) == 'k';
|
|
||||||
} else {
|
|
||||||
if (!random) // Skip random/obfuscated characters
|
|
||||||
sanitizedString.append(string.charAt(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sanitizedString.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String escape(String message) {
|
|
||||||
//var ts = new TreeSet<>();
|
|
||||||
var ts = new TreeSet<int[]>(Comparator.comparingInt(a -> a[0])); //Compare the start, then check the end
|
|
||||||
var matcher = URL_PATTERN.matcher(message);
|
|
||||||
while (matcher.find())
|
|
||||||
ts.add(new int[]{matcher.start(), matcher.end()});
|
|
||||||
matcher = FORMAT_PATTERN.matcher(message);
|
|
||||||
/*Function<MatchResult, String> aFunctionalInterface = result ->
|
|
||||||
Optional.ofNullable(ts.floor(new int[]{result.start(), 0})).map(a -> a[1]).orElse(0) < result.start()
|
|
||||||
? "\\\\" + result.group() : result.group();
|
|
||||||
return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
while (matcher.find()) {
|
|
||||||
matcher.appendReplacement(sb, Optional.ofNullable(ts.floor(new int[]{matcher.start(), 0})) //Find a URL start <= our start
|
|
||||||
.map(a -> a[1]).orElse(-1) < matcher.start() //Check if URL end < our start
|
|
||||||
? "\\\\" + matcher.group() : matcher.group());
|
|
||||||
}
|
|
||||||
matcher.appendTail(sb);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Logger getLogger() {
|
|
||||||
if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger() == null)
|
|
||||||
return Logger.getLogger("DiscordPlugin");
|
|
||||||
return DiscordPlugin.plugin.getLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlyConfigData<Mono<MessageChannel>> channelData(IHaveConfig config, String key) {
|
|
||||||
return config.getReadOnlyDataPrimDef(key, 0L, id -> getMessageChannel(key, Snowflake.of((Long) id)), ch -> 0L); //We can afford to search for the channel in the cache once (instead of using mainServer)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlyConfigData<Mono<Role>> roleData(IHaveConfig config, String key, String defName) {
|
|
||||||
return roleData(config, key, defName, Mono.just(DiscordPlugin.mainServer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Needs to be a {@link ConfigData} for checking if it's set
|
|
||||||
*/
|
|
||||||
public static ReadOnlyConfigData<Mono<Role>> roleData(IHaveConfig config, String key, String defName, Mono<Guild> guild) {
|
|
||||||
return config.getReadOnlyDataPrimDef(key, defName, name -> {
|
|
||||||
if (!(name instanceof String) || ((String) name).length() == 0) return Mono.empty();
|
|
||||||
return guild.flatMapMany(Guild::getRoles).filter(r -> r.getName().equals(name)).onErrorResume(e -> {
|
|
||||||
getLogger().warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage());
|
|
||||||
return Mono.empty();
|
|
||||||
}).next();
|
|
||||||
}, r -> defName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlyConfigData<Snowflake> snowflakeData(IHaveConfig config, String key, long defID) {
|
|
||||||
return config.getReadOnlyDataPrimDef(key, defID, id -> Snowflake.of((long) id), Snowflake::asLong);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mentions the <b>bot channel</b>. Useful for help texts.
|
|
||||||
*
|
|
||||||
* @return The string for mentioning the channel
|
|
||||||
*/
|
|
||||||
public static String botmention() {
|
|
||||||
if (DiscordPlugin.plugin == null) return "#bot";
|
|
||||||
return channelMention(DiscordPlugin.plugin.commandChannel.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 configs The configs to check for null
|
|
||||||
* @return Whether the component got disabled and a warning logged
|
|
||||||
*/
|
|
||||||
public static boolean disableIfConfigError(@Nullable Component<DiscordPlugin> component, ConfigData<?>... configs) {
|
|
||||||
for (val config : configs) {
|
|
||||||
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;
|
|
||||||
try {
|
|
||||||
if (component != null)
|
|
||||||
Component.setComponentEnabled(component, false);
|
|
||||||
path = config.getPath();
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (component != null)
|
|
||||||
TBMCCoreAPI.SendException("Failed to disable component after config error!", e, component);
|
|
||||||
else
|
|
||||||
TBMCCoreAPI.SendException("Failed to disable component after config error!", e, DiscordPlugin.plugin);
|
|
||||||
}
|
|
||||||
getLogger().warning("The config value " + path + " isn't set correctly " + (component == null ? "in global settings!" : "for component " + component.getClass().getSimpleName() + "!"));
|
|
||||||
getLogger().warning("Set the correct ID in the config" + (component == null ? "" : " or disable this component") + " to remove this message.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a response in the form of "@User, message". Use Mono.empty() if you don't have a channel object.
|
|
||||||
*
|
|
||||||
* @param original The original message to reply to
|
|
||||||
* @param channel The channel to send the message in, defaults to the original
|
|
||||||
* @param message The message to send
|
|
||||||
* @return A mono to send the message
|
|
||||||
*/
|
|
||||||
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 reply(original, ch, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #reply(Message, MessageChannel, String)
|
|
||||||
*/
|
|
||||||
public static Mono<Message> reply(Message original, Mono<MessageChannel> ch, String message) {
|
|
||||||
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() + ">";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a message channel for a config. Returns empty for ID 0.
|
|
||||||
*
|
|
||||||
* @param key The config key
|
|
||||||
* @param id The channel ID
|
|
||||||
* @return A message channel
|
|
||||||
*/
|
|
||||||
public static Mono<MessageChannel> getMessageChannel(String key, Snowflake id) {
|
|
||||||
if (id.asLong() == 0L) return Mono.empty();
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Mono<T> ignoreError(Mono<T> mono) {
|
|
||||||
return mono.onErrorResume(t -> Mono.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
209
src/main/java/buttondevteam/discordplugin/DPUtils.scala
Normal file
209
src/main/java/buttondevteam/discordplugin/DPUtils.scala
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI
|
||||||
|
import buttondevteam.lib.architecture.{Component, ConfigData, IHaveConfig, ReadOnlyConfigData}
|
||||||
|
import discord4j.common.util.Snowflake
|
||||||
|
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||||
|
import discord4j.core.`object`.entity.{Guild, Message, Role}
|
||||||
|
import discord4j.core.spec.EmbedCreateSpec
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
import java.util
|
||||||
|
import java.util.{Comparator, Optional}
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import javax.annotation.Nullable
|
||||||
|
|
||||||
|
object DPUtils {
|
||||||
|
private val URL_PATTERN = Pattern.compile("https?://\\S*")
|
||||||
|
private val FORMAT_PATTERN = Pattern.compile("[*_~]")
|
||||||
|
|
||||||
|
def embedWithHead(ecs: EmbedCreateSpec, displayname: String, playername: String, profileUrl: String): EmbedCreateSpec =
|
||||||
|
ecs.setAuthor(displayname, profileUrl, "https://minotar.net/avatar/" + playername + "/32.png")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes §[char] colour codes from strings & escapes them for Discord <br>
|
||||||
|
* Ensure that this method only gets called once (escaping)
|
||||||
|
*/
|
||||||
|
def sanitizeString(string: String): String = escape(sanitizeStringNoEscape(string))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes §[char] colour codes from strings
|
||||||
|
*/
|
||||||
|
def sanitizeStringNoEscape(string: String): String = {
|
||||||
|
val sanitizedString = new StringBuilder
|
||||||
|
var random = false
|
||||||
|
var i = 0
|
||||||
|
while ( {
|
||||||
|
i < string.length
|
||||||
|
}) {
|
||||||
|
if (string.charAt(i) == '§') {
|
||||||
|
i += 1 // Skips the data value, the 4 in "§4Alisolarflare"
|
||||||
|
random = string.charAt(i) == 'k'
|
||||||
|
}
|
||||||
|
else if (!random) { // Skip random/obfuscated characters
|
||||||
|
sanitizedString.append(string.charAt(i))
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
sanitizedString.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
private def escape(message: String) = { //var ts = new TreeSet<>();
|
||||||
|
val ts = new util.TreeSet[Array[Int]](Comparator.comparingInt((a: Array[Int]) => a(0)): Comparator[Array[Int]]) //Compare the start, then check the end
|
||||||
|
var matcher = URL_PATTERN.matcher(message)
|
||||||
|
while ( {
|
||||||
|
matcher.find
|
||||||
|
}) ts.add(Array[Int](matcher.start, matcher.end))
|
||||||
|
matcher = FORMAT_PATTERN.matcher(message)
|
||||||
|
/*Function<MatchResult, String> aFunctionalInterface = result ->
|
||||||
|
Optional.ofNullable(ts.floor(new int[]{result.start(), 0})).map(a -> a[1]).orElse(0) < result.start()
|
||||||
|
? "\\\\" + result.group() : result.group();
|
||||||
|
return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ val sb = new StringBuffer
|
||||||
|
while ( {
|
||||||
|
matcher.find
|
||||||
|
}) matcher.appendReplacement(sb, if (Optional.ofNullable(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start
|
||||||
|
(a: Array[Int]) => a(1)).orElse(-1) < matcher.start //Check if URL end < our start
|
||||||
|
) "\\\\" + matcher.group else matcher.group)
|
||||||
|
matcher.appendTail(sb)
|
||||||
|
sb.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
def getLogger: Logger = {
|
||||||
|
if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger == null) return Logger.getLogger("DiscordPlugin")
|
||||||
|
DiscordPlugin.plugin.getLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
def channelData(config: IHaveConfig, key: String): ReadOnlyConfigData[Mono[MessageChannel]] =
|
||||||
|
config.getReadOnlyDataPrimDef(key, 0L, (id: Any) =>
|
||||||
|
getMessageChannel(key, Snowflake.of(id.asInstanceOf[Long])), (_: Mono[MessageChannel]) => 0L) //We can afford to search for the channel in the cache once (instead of using mainServer)
|
||||||
|
def roleData(config: IHaveConfig, key: String, defName: String): ReadOnlyConfigData[Mono[Role]] =
|
||||||
|
roleData(config, key, defName, Mono.just(DiscordPlugin.mainServer))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be a {@link ConfigData} for checking if it's set
|
||||||
|
*/
|
||||||
|
def roleData(config: IHaveConfig, key: String, defName: String, guild: Mono[Guild]): ReadOnlyConfigData[Mono[Role]] = config.getReadOnlyDataPrimDef(key, defName, (name: Any) => {
|
||||||
|
def foo(name: Any): Mono[Role] = {
|
||||||
|
if (!name.isInstanceOf[String] || name.asInstanceOf[String].isEmpty) return Mono.empty[Role]
|
||||||
|
guild.flatMapMany(_.getRoles).filter((r: Role) => r.getName == name).onErrorResume((e: Throwable) => {
|
||||||
|
def foo(e: Throwable): Mono[Role] = {
|
||||||
|
getLogger.warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage)
|
||||||
|
Mono.empty[Role]
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(e)
|
||||||
|
}).next
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(name)
|
||||||
|
}, (_: Mono[Role]) => defName)
|
||||||
|
|
||||||
|
def snowflakeData(config: IHaveConfig, key: String, defID: Long): ReadOnlyConfigData[Snowflake] =
|
||||||
|
config.getReadOnlyDataPrimDef(key, defID, (id: Any) => Snowflake.of(id.asInstanceOf[Long]), _.asLong)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mentions the <b>bot channel</b>. Useful for help texts.
|
||||||
|
*
|
||||||
|
* @return The string for mentioning the channel
|
||||||
|
*/
|
||||||
|
def botmention: String = {
|
||||||
|
if (DiscordPlugin.plugin == null) return "#bot"
|
||||||
|
channelMention(DiscordPlugin.plugin.commandChannel.get)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 configs The configs to check for null
|
||||||
|
* @return Whether the component got disabled and a warning logged
|
||||||
|
*/
|
||||||
|
def disableIfConfigError(@Nullable component: Component[DiscordPlugin], configs: ConfigData[_]*): Boolean = {
|
||||||
|
for (config <- configs) {
|
||||||
|
val v = config.get
|
||||||
|
if (disableIfConfigErrorRes(component, config, v)) return true
|
||||||
|
}
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
def disableIfConfigErrorRes(@Nullable component: Component[DiscordPlugin], config: ConfigData[_], result: Any): Boolean = {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
if (result == null || (result.isInstanceOf[Mono[_]] && !result.asInstanceOf[Mono[_]].hasElement.block)) {
|
||||||
|
var path: String = null
|
||||||
|
try {
|
||||||
|
if (component != null) Component.setComponentEnabled(component, false)
|
||||||
|
path = config.getPath
|
||||||
|
} catch {
|
||||||
|
case e: Exception =>
|
||||||
|
if (component != null) TBMCCoreAPI.SendException("Failed to disable component after config error!", e, component)
|
||||||
|
else TBMCCoreAPI.SendException("Failed to disable component after config error!", e, DiscordPlugin.plugin)
|
||||||
|
}
|
||||||
|
getLogger.warning("The config value " + path + " isn't set correctly " + (if (component == null) "in global settings!"
|
||||||
|
else "for component " + component.getClass.getSimpleName + "!"))
|
||||||
|
getLogger.warning("Set the correct ID in the config" + (if (component == null) ""
|
||||||
|
else " or disable this component") + " to remove this message.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a response in the form of "@User, message". Use Mono.empty() if you don't have a channel object.
|
||||||
|
*
|
||||||
|
* @param original The original message to reply to
|
||||||
|
* @param channel The channel to send the message in, defaults to the original
|
||||||
|
* @param message The message to send
|
||||||
|
* @return A mono to send the message
|
||||||
|
*/
|
||||||
|
def reply(original: Message, @Nullable channel: MessageChannel, message: String): Mono[Message] = {
|
||||||
|
val ch = if (channel == null) original.getChannel
|
||||||
|
else Mono.just(channel)
|
||||||
|
reply(original, ch, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #reply(Message, MessageChannel, String)
|
||||||
|
*/
|
||||||
|
def reply(original: Message, ch: Mono[MessageChannel], message: String): Mono[Message] =
|
||||||
|
ch.flatMap(_.createMessage((if (original.getAuthor.isPresent)
|
||||||
|
original.getAuthor.get.getMention + ", "
|
||||||
|
else "") + message))
|
||||||
|
|
||||||
|
def nickMention(userId: Snowflake): String = "<@!" + userId.asString + ">"
|
||||||
|
|
||||||
|
def channelMention(channelId: Snowflake): String = "<#" + channelId.asString + ">"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a message channel for a config. Returns empty for ID 0.
|
||||||
|
*
|
||||||
|
* @param key The config key
|
||||||
|
* @param id The channel ID
|
||||||
|
* @return A message channel
|
||||||
|
*/
|
||||||
|
def getMessageChannel(key: String, id: Snowflake): Mono[MessageChannel] = {
|
||||||
|
if (id.asLong == 0L) return Mono.empty[MessageChannel]
|
||||||
|
|
||||||
|
DiscordPlugin.dc.getChannelById(id).onErrorResume(e => {
|
||||||
|
def foo(e: Throwable) = {
|
||||||
|
getLogger.warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage)
|
||||||
|
Mono.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(e)
|
||||||
|
}).filter(ch => ch.isInstanceOf[MessageChannel]).cast(classOf[MessageChannel])
|
||||||
|
}
|
||||||
|
|
||||||
|
def getMessageChannel(config: ConfigData[Snowflake]): Mono[MessageChannel] =
|
||||||
|
getMessageChannel(config.getPath, config.get)
|
||||||
|
|
||||||
|
def ignoreError[T](mono: Mono[T]): Mono[T] = mono.onErrorResume((_: Throwable) => Mono.empty)
|
||||||
|
}
|
|
@ -1,324 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.playerfaker.DiscordInventory;
|
|
||||||
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
|
||||||
import discord4j.core.object.entity.User;
|
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.experimental.Delegate;
|
|
||||||
import net.md_5.bungee.api.ChatMessageType;
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
import org.bukkit.*;
|
|
||||||
import org.bukkit.attribute.Attribute;
|
|
||||||
import org.bukkit.attribute.AttributeInstance;
|
|
||||||
import org.bukkit.attribute.AttributeModifier;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
|
||||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
|
||||||
import org.bukkit.inventory.Inventory;
|
|
||||||
import org.bukkit.inventory.PlayerInventory;
|
|
||||||
import org.bukkit.permissions.PermissibleBase;
|
|
||||||
import org.bukkit.permissions.ServerOperator;
|
|
||||||
import org.mockito.MockSettings;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.mockito.Answers.RETURNS_DEFAULTS;
|
|
||||||
|
|
||||||
public abstract class DiscordConnectedPlayer extends DiscordSenderBase implements IMCPlayer<DiscordConnectedPlayer> {
|
|
||||||
private @Getter VCMDWrapper vanillaCmdListener;
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean loggedIn = false;
|
|
||||||
|
|
||||||
@Delegate(excludes = ServerOperator.class)
|
|
||||||
private PermissibleBase origPerm;
|
|
||||||
|
|
||||||
private @Getter String name;
|
|
||||||
|
|
||||||
private @Getter OfflinePlayer basePlayer;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private PermissibleBase perm;
|
|
||||||
|
|
||||||
private Location location;
|
|
||||||
|
|
||||||
private final MinecraftChatModule module;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final UUID uniqueId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parameters must match with {@link #create(User, MessageChannel, UUID, String, MinecraftChatModule)}
|
|
||||||
*/
|
|
||||||
protected DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname,
|
|
||||||
MinecraftChatModule module) {
|
|
||||||
super(user, channel);
|
|
||||||
location = Bukkit.getWorlds().get(0).getSpawnLocation();
|
|
||||||
origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid));
|
|
||||||
name = mcname;
|
|
||||||
this.module = module;
|
|
||||||
uniqueId = uuid;
|
|
||||||
displayName = mcname;
|
|
||||||
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, module));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For testing
|
|
||||||
*/
|
|
||||||
protected DiscordConnectedPlayer(User user, MessageChannel channel) {
|
|
||||||
super(user, channel);
|
|
||||||
module = null;
|
|
||||||
uniqueId = UUID.randomUUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOp(boolean value) { //CraftPlayer-compatible implementation
|
|
||||||
this.origPerm.setOp(value);
|
|
||||||
this.perm.recalculatePermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOp() { return this.origPerm.isOp(); }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean teleport(Location location) {
|
|
||||||
if (module.allowFakePlayerTeleports.get())
|
|
||||||
this.location = location;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) {
|
|
||||||
if (module.allowFakePlayerTeleports.get())
|
|
||||||
this.location = location;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean teleport(Entity destination) {
|
|
||||||
if (module.allowFakePlayerTeleports.get())
|
|
||||||
this.location = destination.getLocation();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean teleport(Entity destination, PlayerTeleportEvent.TeleportCause cause) {
|
|
||||||
if (module.allowFakePlayerTeleports.get())
|
|
||||||
this.location = destination.getLocation();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getLocation(Location loc) {
|
|
||||||
if (loc != null) {
|
|
||||||
loc.setWorld(getWorld());
|
|
||||||
loc.setX(location.getX());
|
|
||||||
loc.setY(location.getY());
|
|
||||||
loc.setZ(location.getZ());
|
|
||||||
loc.setYaw(location.getYaw());
|
|
||||||
loc.setPitch(location.getPitch());
|
|
||||||
}
|
|
||||||
|
|
||||||
return loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Server getServer() {
|
|
||||||
return Bukkit.getServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendRawMessage(String message) {
|
|
||||||
sendMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void chat(String msg) {
|
|
||||||
Bukkit.getPluginManager()
|
|
||||||
.callEvent(new AsyncPlayerChatEvent(true, this, msg, new HashSet<>(Bukkit.getOnlinePlayers())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public World getWorld() {
|
|
||||||
return Bukkit.getWorlds().get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnline() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getLocation() {
|
|
||||||
return new Location(getWorld(), location.getX(), location.getY(), location.getZ(),
|
|
||||||
location.getYaw(), location.getPitch());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Location getEyeLocation() {
|
|
||||||
return getLocation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public double getMaxHealth() {
|
|
||||||
return 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Player getPlayer() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String displayName;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AttributeInstance getAttribute(Attribute attribute) {
|
|
||||||
return new AttributeInstance() {
|
|
||||||
@Override
|
|
||||||
public Attribute getAttribute() {
|
|
||||||
return attribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getBaseValue() {
|
|
||||||
return getDefaultValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBaseValue(double value) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<AttributeModifier> getModifiers() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addModifier(AttributeModifier modifier) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeModifier(AttributeModifier modifier) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getValue() {
|
|
||||||
return getDefaultValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getDefaultValue() {
|
|
||||||
return 20; //Works for max health, should be okay for the rest
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GameMode getGameMode() {
|
|
||||||
return GameMode.SPECTATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private final Player.Spigot spigot = new Player.Spigot() {
|
|
||||||
@Override
|
|
||||||
public InetSocketAddress getRawAddress() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playEffect(Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getCollidesWithEntities() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCollidesWithEntities(boolean collides) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void respawn() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getLocale() {
|
|
||||||
return "en_us";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Player> getHiddenPlayers() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(BaseComponent component) {
|
|
||||||
DiscordConnectedPlayer.super.sendMessage(component.toPlainText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(BaseComponent... components) {
|
|
||||||
for (var component : components)
|
|
||||||
sendMessage(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(ChatMessageType position, BaseComponent component) {
|
|
||||||
sendMessage(component); //Ignore position
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(ChatMessageType position, BaseComponent... components) {
|
|
||||||
sendMessage(components); //Ignore position
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isInvulnerable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Player.Spigot spigot() {
|
|
||||||
return spigot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DiscordConnectedPlayer create(User user, MessageChannel channel, UUID uuid, String mcname,
|
|
||||||
MinecraftChatModule module) {
|
|
||||||
return Mockito.mock(DiscordConnectedPlayer.class,
|
|
||||||
getSettings().useConstructor(user, channel, uuid, mcname, module));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DiscordConnectedPlayer createTest() {
|
|
||||||
return Mockito.mock(DiscordConnectedPlayer.class, getSettings().useConstructor(null, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MockSettings getSettings() {
|
|
||||||
return Mockito.withSettings()
|
|
||||||
.defaultAnswer(invocation -> {
|
|
||||||
try {
|
|
||||||
if (!Modifier.isAbstract(invocation.getMethod().getModifiers()))
|
|
||||||
return invocation.callRealMethod();
|
|
||||||
if (PlayerInventory.class.isAssignableFrom(invocation.getMethod().getReturnType()))
|
|
||||||
return Mockito.mock(DiscordInventory.class, Mockito.withSettings().extraInterfaces(PlayerInventory.class));
|
|
||||||
if (Inventory.class.isAssignableFrom(invocation.getMethod().getReturnType()))
|
|
||||||
return new DiscordInventory();
|
|
||||||
return RETURNS_DEFAULTS.answer(invocation);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Error in mocked player!");
|
|
||||||
e.printStackTrace();
|
|
||||||
return RETURNS_DEFAULTS.answer(invocation);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.stubOnly();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,251 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.mcchat.MinecraftChatModule
|
||||||
|
import buttondevteam.discordplugin.playerfaker.{DiscordInventory, VCMDWrapper}
|
||||||
|
import discord4j.core.`object`.entity.User
|
||||||
|
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||||
|
import net.md_5.bungee.api.ChatMessageType
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent
|
||||||
|
import org.bukkit._
|
||||||
|
import org.bukkit.attribute.{Attribute, AttributeInstance, AttributeModifier}
|
||||||
|
import org.bukkit.entity.Player.Spigot
|
||||||
|
import org.bukkit.entity.{Entity, Player}
|
||||||
|
import org.bukkit.event.player.{AsyncPlayerChatEvent, PlayerTeleportEvent}
|
||||||
|
import org.bukkit.inventory.{Inventory, PlayerInventory}
|
||||||
|
import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo}
|
||||||
|
import org.bukkit.plugin.Plugin
|
||||||
|
import org.mockito.Answers.RETURNS_DEFAULTS
|
||||||
|
import org.mockito.{MockSettings, Mockito}
|
||||||
|
import org.mockito.invocation.InvocationOnMock
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.util
|
||||||
|
import java.util._
|
||||||
|
|
||||||
|
object DiscordConnectedPlayer {
|
||||||
|
def create(user: User, channel: MessageChannel, uuid: UUID, mcname: String, module: MinecraftChatModule): DiscordConnectedPlayer =
|
||||||
|
Mockito.mock(classOf[DiscordConnectedPlayer], getSettings.useConstructor(user, channel, uuid, mcname, module))
|
||||||
|
|
||||||
|
def createTest: DiscordConnectedPlayer =
|
||||||
|
Mockito.mock(classOf[DiscordConnectedPlayer], getSettings.useConstructor(null, null))
|
||||||
|
|
||||||
|
private def getSettings: MockSettings = Mockito.withSettings.defaultAnswer((invocation: InvocationOnMock) => {
|
||||||
|
def foo(invocation: InvocationOnMock): AnyRef =
|
||||||
|
try {
|
||||||
|
if (!Modifier.isAbstract(invocation.getMethod.getModifiers))
|
||||||
|
invocation.callRealMethod
|
||||||
|
else if (classOf[PlayerInventory].isAssignableFrom(invocation.getMethod.getReturnType))
|
||||||
|
Mockito.mock(classOf[DiscordInventory], Mockito.withSettings.extraInterfaces(classOf[PlayerInventory]))
|
||||||
|
else if (classOf[Inventory].isAssignableFrom(invocation.getMethod.getReturnType))
|
||||||
|
new DiscordInventory
|
||||||
|
else
|
||||||
|
RETURNS_DEFAULTS.answer(invocation)
|
||||||
|
} catch {
|
||||||
|
case e: Exception =>
|
||||||
|
System.err.println("Error in mocked player!")
|
||||||
|
e.printStackTrace()
|
||||||
|
RETURNS_DEFAULTS.answer(invocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(invocation)
|
||||||
|
}).stubOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordConnectedPlayer] {
|
||||||
|
override def isPermissionSet(name: String): Boolean = this.origPerm.isPermissionSet(name)
|
||||||
|
|
||||||
|
override def isPermissionSet(perm: Permission): Boolean = this.origPerm.isPermissionSet(perm)
|
||||||
|
|
||||||
|
override def hasPermission(inName: String): Boolean = this.origPerm.hasPermission(inName)
|
||||||
|
|
||||||
|
override def hasPermission(perm: Permission): Boolean = this.origPerm.hasPermission(perm)
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin, name: String, value: Boolean): PermissionAttachment = this.origPerm.addAttachment(plugin, name, value)
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin): PermissionAttachment = this.origPerm.addAttachment(plugin)
|
||||||
|
|
||||||
|
override def removeAttachment(attachment: PermissionAttachment): Unit = this.origPerm.removeAttachment(attachment)
|
||||||
|
|
||||||
|
override def recalculatePermissions(): Unit = this.origPerm.recalculatePermissions()
|
||||||
|
|
||||||
|
def clearPermissions(): Unit = this.origPerm.clearPermissions()
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin, name: String, value: Boolean, ticks: Int): PermissionAttachment =
|
||||||
|
this.origPerm.addAttachment(plugin, name, value, ticks)
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin, ticks: Int): PermissionAttachment = this.origPerm.addAttachment(plugin, ticks)
|
||||||
|
|
||||||
|
override def getEffectivePermissions: util.Set[PermissionAttachmentInfo] = this.origPerm.getEffectivePermissions
|
||||||
|
|
||||||
|
def setLoggedIn(loggedIn: Boolean): Unit = this.loggedIn = loggedIn
|
||||||
|
|
||||||
|
def setPerm(perm: PermissibleBase): Unit = this.perm = perm
|
||||||
|
|
||||||
|
override def setDisplayName(displayName: String): Unit = this.displayName = displayName
|
||||||
|
|
||||||
|
override def getVanillaCmdListener: VCMDWrapper = this.vanillaCmdListener
|
||||||
|
|
||||||
|
def isLoggedIn: Boolean = this.loggedIn
|
||||||
|
|
||||||
|
override def getName: String = this.name
|
||||||
|
|
||||||
|
def getBasePlayer: OfflinePlayer = this.basePlayer
|
||||||
|
|
||||||
|
def getPerm: PermissibleBase = this.perm
|
||||||
|
|
||||||
|
override def getUniqueId: UUID = this.uniqueId
|
||||||
|
|
||||||
|
override def getDisplayName: String = this.displayName
|
||||||
|
|
||||||
|
private var vanillaCmdListener: VCMDWrapper = null
|
||||||
|
private var loggedIn = false
|
||||||
|
private var origPerm: PermissibleBase = null
|
||||||
|
private var name: String = null
|
||||||
|
private var basePlayer: OfflinePlayer = null
|
||||||
|
private var perm: PermissibleBase = null
|
||||||
|
private var location: Location = null
|
||||||
|
final private var module: MinecraftChatModule = null
|
||||||
|
final private var uniqueId: UUID = null
|
||||||
|
final private var displayName: String = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parameters must match with {@link #create ( User, MessageChannel, UUID, String, MinecraftChatModule)}
|
||||||
|
*/
|
||||||
|
def this(user: User, channel: MessageChannel, uuid: UUID, mcname: String, module: MinecraftChatModule) {
|
||||||
|
this(user, channel)
|
||||||
|
location = Bukkit.getWorlds.get(0).getSpawnLocation
|
||||||
|
perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid))
|
||||||
|
origPerm = perm
|
||||||
|
name = mcname
|
||||||
|
this.module = module
|
||||||
|
uniqueId = uuid
|
||||||
|
displayName = mcname
|
||||||
|
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, module))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For testing
|
||||||
|
*/
|
||||||
|
def this(user: User, channel: MessageChannel) {
|
||||||
|
this(user, channel)
|
||||||
|
module = null
|
||||||
|
uniqueId = UUID.randomUUID
|
||||||
|
}
|
||||||
|
|
||||||
|
override def setOp(value: Boolean): Unit = { //CraftPlayer-compatible implementation
|
||||||
|
this.origPerm.setOp(value)
|
||||||
|
this.perm.recalculatePermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isOp: Boolean = this.origPerm.isOp
|
||||||
|
|
||||||
|
override def teleport(location: Location): Boolean = {
|
||||||
|
if (module.allowFakePlayerTeleports.get) this.location = location
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
def teleport(location: Location, cause: PlayerTeleportEvent.TeleportCause): Boolean = {
|
||||||
|
if (module.allowFakePlayerTeleports.get) this.location = location
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
override def teleport(destination: Entity): Boolean = {
|
||||||
|
if (module.allowFakePlayerTeleports.get) this.location = destination.getLocation
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
def teleport(destination: Entity, cause: PlayerTeleportEvent.TeleportCause): Boolean = {
|
||||||
|
if (module.allowFakePlayerTeleports.get) this.location = destination.getLocation
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getLocation(loc: Location): Location = {
|
||||||
|
if (loc != null) {
|
||||||
|
loc.setWorld(getWorld)
|
||||||
|
loc.setX(location.getX)
|
||||||
|
loc.setY(location.getY)
|
||||||
|
loc.setZ(location.getZ)
|
||||||
|
loc.setYaw(location.getYaw)
|
||||||
|
loc.setPitch(location.getPitch)
|
||||||
|
}
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getServer: Server = Bukkit.getServer
|
||||||
|
|
||||||
|
override def sendRawMessage(message: String): Unit = sendMessage(message)
|
||||||
|
|
||||||
|
override def chat(msg: String): Unit = Bukkit.getPluginManager.callEvent(new AsyncPlayerChatEvent(true, this, msg, new util.HashSet[Player](Bukkit.getOnlinePlayers)))
|
||||||
|
|
||||||
|
override def getWorld: World = Bukkit.getWorlds.get(0)
|
||||||
|
|
||||||
|
override def isOnline = true
|
||||||
|
|
||||||
|
override def getLocation = new Location(getWorld, location.getX, location.getY, location.getZ, location.getYaw, location.getPitch)
|
||||||
|
|
||||||
|
override def getEyeLocation: Location = getLocation
|
||||||
|
|
||||||
|
@deprecated override def getMaxHealth = 20
|
||||||
|
|
||||||
|
override def getPlayer: DiscordConnectedPlayer = this
|
||||||
|
|
||||||
|
override def getAttribute(attribute: Attribute): AttributeInstance = new AttributeInstance() {
|
||||||
|
override def getAttribute: Attribute = attribute
|
||||||
|
|
||||||
|
override def getBaseValue: Double = getDefaultValue
|
||||||
|
|
||||||
|
override def setBaseValue(value: Double): Unit = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getModifiers: util.Collection[AttributeModifier] = Collections.emptyList
|
||||||
|
|
||||||
|
override def addModifier(modifier: AttributeModifier): Unit = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override def removeModifier(modifier: AttributeModifier): Unit = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getValue: Double = getDefaultValue
|
||||||
|
|
||||||
|
override def getDefaultValue: Double = 20 //Works for max health, should be okay for the rest
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getGameMode = GameMode.SPECTATOR
|
||||||
|
|
||||||
|
//noinspection ScalaDeprecation
|
||||||
|
@SuppressWarnings(Array("deprecation")) final private val spigot: Spigot = new Spigot() {
|
||||||
|
override def getRawAddress: InetSocketAddress = null
|
||||||
|
|
||||||
|
override def playEffect(location: Location, effect: Effect, id: Int, data: Int, offsetX: Float, offsetY: Float, offsetZ: Float, speed: Float, particleCount: Int, radius: Int): Unit = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getCollidesWithEntities = false
|
||||||
|
|
||||||
|
override def setCollidesWithEntities(collides: Boolean): Unit = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override def respawn(): Unit = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getLocale = "en_us"
|
||||||
|
|
||||||
|
override def getHiddenPlayers: util.Set[Player] = Collections.emptySet
|
||||||
|
|
||||||
|
override def sendMessage(component: BaseComponent): Unit =
|
||||||
|
DiscordConnectedPlayer.super.sendMessage(component.toPlainText)
|
||||||
|
|
||||||
|
override def sendMessage(components: BaseComponent*): Unit =
|
||||||
|
for (component <- components)
|
||||||
|
sendMessage(component)
|
||||||
|
|
||||||
|
override def sendMessage(position: ChatMessageType, component: BaseComponent): Unit =
|
||||||
|
sendMessage(component) //Ignore position
|
||||||
|
override def sendMessage(position: ChatMessageType, components: BaseComponent*) =
|
||||||
|
sendMessage(components)
|
||||||
|
|
||||||
|
override def isInvulnerable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override def spigot: Spigot = spigot
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
|
||||||
import buttondevteam.lib.player.UserClass;
|
|
||||||
import discord4j.core.object.entity.User;
|
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
|
||||||
|
|
||||||
@UserClass(foldername = "discord")
|
|
||||||
public class DiscordPlayer extends ChromaGamerBase {
|
|
||||||
private String did;
|
|
||||||
// private @Getter @Setter boolean minecraftChatEnabled;
|
|
||||||
|
|
||||||
public DiscordPlayer() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDiscordID() {
|
|
||||||
if (did == null)
|
|
||||||
did = getFileName();
|
|
||||||
return did;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if player has the private Minecraft chat enabled. For setting the value, see
|
|
||||||
* {@link MCChatPrivate#privateMCChat(MessageChannel, boolean, User, DiscordPlayer)}
|
|
||||||
*/
|
|
||||||
public boolean isMinecraftChatEnabled() {
|
|
||||||
return MCChatPrivate.isMinecraftChatEnabled(this);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.mcchat.MCChatPrivate
|
||||||
|
import buttondevteam.lib.player.{ChromaGamerBase, UserClass}
|
||||||
|
|
||||||
|
@UserClass(foldername = "discord") class DiscordPlayer() extends ChromaGamerBase {
|
||||||
|
private var did: String = null
|
||||||
|
|
||||||
|
// private @Getter @Setter boolean minecraftChatEnabled;
|
||||||
|
def getDiscordID: String = {
|
||||||
|
if (did == null) did = getFileName
|
||||||
|
did
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if player has the private Minecraft chat enabled. For setting the value, see
|
||||||
|
* {@link MCChatPrivate# privateMCChat ( MessageChannel, boolean, User, DiscordPlayer)}
|
||||||
|
*/
|
||||||
|
def isMinecraftChatEnabled: Boolean = MCChatPrivate.isMinecraftChatEnabled(this)
|
||||||
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
|
||||||
import discord4j.core.object.entity.User;
|
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
|
|
||||||
public abstract class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer<DiscordPlayerSender> {
|
|
||||||
|
|
||||||
protected Player player;
|
|
||||||
private @Getter final VCMDWrapper vanillaCmdListener;
|
|
||||||
|
|
||||||
public DiscordPlayerSender(User user, MessageChannel channel, Player player, MinecraftChatModule module) {
|
|
||||||
super(user, channel);
|
|
||||||
this.player = player;
|
|
||||||
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player, module));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(String message) {
|
|
||||||
player.sendMessage(message);
|
|
||||||
super.sendMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(String[] messages) {
|
|
||||||
player.sendMessage(messages);
|
|
||||||
super.sendMessage(messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DiscordPlayerSender create(User user, MessageChannel channel, Player player, MinecraftChatModule module) {
|
|
||||||
return Mockito.mock(DiscordPlayerSender.class, Mockito.withSettings().stubOnly().defaultAnswer(invocation -> {
|
|
||||||
if (!Modifier.isAbstract(invocation.getMethod().getModifiers()))
|
|
||||||
return invocation.callRealMethod();
|
|
||||||
return invocation.getMethod().invoke(((DiscordPlayerSender) invocation.getMock()).player, invocation.getArguments());
|
|
||||||
}).useConstructor(user, channel, player, module));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.mcchat.MinecraftChatModule
|
||||||
|
import buttondevteam.discordplugin.playerfaker.VCMDWrapper
|
||||||
|
import discord4j.core.`object`.entity.User
|
||||||
|
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.invocation.InvocationOnMock
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
|
||||||
|
object DiscordPlayerSender {
|
||||||
|
def create(user: User, channel: MessageChannel, player: Player, module: MinecraftChatModule): DiscordPlayerSender =
|
||||||
|
Mockito.mock(classOf[DiscordPlayerSender], Mockito.withSettings.stubOnly.defaultAnswer((invocation: InvocationOnMock) => {
|
||||||
|
def foo(invocation: InvocationOnMock): AnyRef = {
|
||||||
|
if (!Modifier.isAbstract(invocation.getMethod.getModifiers))
|
||||||
|
invocation.callRealMethod
|
||||||
|
else
|
||||||
|
invocation.getMethod.invoke(invocation.getMock.asInstanceOf[DiscordPlayerSender].player, invocation.getArguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(invocation)
|
||||||
|
}).useConstructor(user, channel, player, module))
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DiscordPlayerSender(val user: User, val channel: MessageChannel, var player: Player, val module: Nothing) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordPlayerSender] {
|
||||||
|
val vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player, module))
|
||||||
|
|
||||||
|
override def getVanillaCmdListener: VCMDWrapper = this.vanillaCmdListener
|
||||||
|
|
||||||
|
override def sendMessage(message: String): Unit = {
|
||||||
|
player.sendMessage(message)
|
||||||
|
super.sendMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def sendMessage(messages: Array[String]): Unit = {
|
||||||
|
player.sendMessage(messages)
|
||||||
|
super.sendMessage(messages)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,117 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import discord4j.core.object.entity.Member;
|
|
||||||
import discord4j.core.object.entity.User;
|
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
|
||||||
import lombok.val;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Server;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.bukkit.permissions.PermissibleBase;
|
|
||||||
import org.bukkit.permissions.Permission;
|
|
||||||
import org.bukkit.permissions.PermissionAttachment;
|
|
||||||
import org.bukkit.permissions.PermissionAttachmentInfo;
|
|
||||||
import org.bukkit.plugin.Plugin;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class DiscordSender extends DiscordSenderBase implements CommandSender {
|
|
||||||
private PermissibleBase perm = new PermissibleBase(this);
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public DiscordSender(User user, MessageChannel channel) {
|
|
||||||
super(user, channel);
|
|
||||||
val def = "Discord user";
|
|
||||||
name = user == null ? def : user.asMember(DiscordPlugin.mainServer.getId())
|
|
||||||
.onErrorResume(t -> Mono.empty()).blockOptional().map(Member::getDisplayName).orElse(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiscordSender(User user, MessageChannel channel, String name) {
|
|
||||||
super(user, channel);
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPermissionSet(String name) {
|
|
||||||
return perm.isPermissionSet(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPermissionSet(Permission perm) {
|
|
||||||
return this.perm.isPermissionSet(perm);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasPermission(String name) {
|
|
||||||
if (name.contains("essentials") && !name.equals("essentials.list"))
|
|
||||||
return false;
|
|
||||||
return perm.hasPermission(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasPermission(Permission perm) {
|
|
||||||
return this.perm.hasPermission(perm);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
|
|
||||||
return perm.addAttachment(plugin, name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin) {
|
|
||||||
return perm.addAttachment(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) {
|
|
||||||
return perm.addAttachment(plugin, name, value, ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
|
|
||||||
return perm.addAttachment(plugin, ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAttachment(PermissionAttachment attachment) {
|
|
||||||
perm.removeAttachment(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recalculatePermissions() {
|
|
||||||
perm.recalculatePermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
|
||||||
return perm.getEffectivePermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOp() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOp(boolean value) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Server getServer() {
|
|
||||||
return Bukkit.getServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Spigot spigot() {
|
|
||||||
return new CommandSender.Spigot();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import discord4j.core.`object`.entity.User
|
||||||
|
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||||
|
import org.bukkit.{Bukkit, Server}
|
||||||
|
import org.bukkit.command.CommandSender
|
||||||
|
import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo}
|
||||||
|
import org.bukkit.plugin.Plugin
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
import java.util
|
||||||
|
|
||||||
|
class DiscordSender(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with CommandSender {
|
||||||
|
private val perm = new PermissibleBase(this)
|
||||||
|
private var name: String = null
|
||||||
|
|
||||||
|
def this(user: User, channel: MessageChannel) {
|
||||||
|
this(user, channel)
|
||||||
|
val `def` = "Discord user"
|
||||||
|
name = if (user == null) `def`
|
||||||
|
else user.asMember(DiscordPlugin.mainServer.getId).onErrorResume((_: Throwable) => Mono.empty).blockOptional.map(_.getDisplayName).orElse(`def`)
|
||||||
|
}
|
||||||
|
|
||||||
|
def this(user: User, channel: MessageChannel, name: String) {
|
||||||
|
this(user, channel)
|
||||||
|
this.name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isPermissionSet(name: String): Boolean = perm.isPermissionSet(name)
|
||||||
|
|
||||||
|
override def isPermissionSet(perm: Permission): Boolean = this.perm.isPermissionSet(perm)
|
||||||
|
|
||||||
|
override def hasPermission(name: String): Boolean = {
|
||||||
|
if (name.contains("essentials") && !(name == "essentials.list")) return false
|
||||||
|
perm.hasPermission(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def hasPermission(perm: Permission): Boolean = this.perm.hasPermission(perm)
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin, name: String, value: Boolean): PermissionAttachment = perm.addAttachment(plugin, name, value)
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin): PermissionAttachment = perm.addAttachment(plugin)
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin, name: String, value: Boolean, ticks: Int): PermissionAttachment = perm.addAttachment(plugin, name, value, ticks)
|
||||||
|
|
||||||
|
override def addAttachment(plugin: Plugin, ticks: Int): PermissionAttachment = perm.addAttachment(plugin, ticks)
|
||||||
|
|
||||||
|
override def removeAttachment(attachment: PermissionAttachment): Unit = perm.removeAttachment(attachment)
|
||||||
|
|
||||||
|
override def recalculatePermissions(): Unit = perm.recalculatePermissions()
|
||||||
|
|
||||||
|
override def getEffectivePermissions: util.Set[PermissionAttachmentInfo] = perm.getEffectivePermissions
|
||||||
|
|
||||||
|
override def isOp = false
|
||||||
|
|
||||||
|
override def setOp(value: Boolean): Unit = {
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getServer: Server = Bukkit.getServer
|
||||||
|
|
||||||
|
override def getName: String = name
|
||||||
|
|
||||||
|
override def spigot = new CommandSender.Spigot
|
||||||
|
}
|
|
@ -1,75 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
|
||||||
import discord4j.core.object.entity.User;
|
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.bukkit.scheduler.BukkitTask;
|
|
||||||
|
|
||||||
public abstract class DiscordSenderBase implements CommandSender {
|
|
||||||
/**
|
|
||||||
* May be null.
|
|
||||||
*/
|
|
||||||
protected User user;
|
|
||||||
protected MessageChannel channel;
|
|
||||||
|
|
||||||
protected DiscordSenderBase(User user, MessageChannel channel) {
|
|
||||||
this.user = user;
|
|
||||||
this.channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private volatile String msgtosend = "";
|
|
||||||
private volatile BukkitTask sendtask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the user. May be null.
|
|
||||||
*
|
|
||||||
* @return The user or null.
|
|
||||||
*/
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageChannel getChannel() {
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DiscordPlayer chromaUser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the user data on first query.
|
|
||||||
*
|
|
||||||
* @return A Chroma user of Discord or a Discord user of Chroma
|
|
||||||
*/
|
|
||||||
public DiscordPlayer getChromaUser() {
|
|
||||||
if (chromaUser == null) chromaUser = DiscordPlayer.getUser(user.getId().asString(), DiscordPlayer.class);
|
|
||||||
return chromaUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(String message) {
|
|
||||||
try {
|
|
||||||
final boolean broadcast = new Exception().getStackTrace()[2].getMethodName().contains("broadcast");
|
|
||||||
if (broadcast) //We're catching broadcasts using the Bukkit event
|
|
||||||
return;
|
|
||||||
final String sendmsg = DPUtils.sanitizeString(message);
|
|
||||||
synchronized (this) {
|
|
||||||
msgtosend += "\n" + sendmsg;
|
|
||||||
if (sendtask == null)
|
|
||||||
sendtask = Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> {
|
|
||||||
channel.createMessage((user != null ? user.getMention() + "\n" : "") + msgtosend.trim()).subscribe();
|
|
||||||
sendtask = null;
|
|
||||||
msgtosend = "";
|
|
||||||
}, 4); // Waits a 0.2 second to gather all/most of the different messages
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
TBMCCoreAPI.SendException("An error occured while sending message to DiscordSender", e, DiscordPlugin.plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(String[] messages) {
|
|
||||||
sendMessage(String.join("\n", messages));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase
|
||||||
|
import discord4j.core.`object`.entity.User
|
||||||
|
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.command.CommandSender
|
||||||
|
import org.bukkit.scheduler.BukkitTask
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param user May be null.
|
||||||
|
* @param channel May not be null.
|
||||||
|
*/
|
||||||
|
abstract class DiscordSenderBase protected(var user: User, var channel: MessageChannel) extends CommandSender {
|
||||||
|
private var msgtosend = ""
|
||||||
|
private var sendtask: BukkitTask = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user. May be null.
|
||||||
|
*
|
||||||
|
* @return The user or null.
|
||||||
|
*/
|
||||||
|
def getUser: User = user
|
||||||
|
|
||||||
|
def getChannel: MessageChannel = channel
|
||||||
|
|
||||||
|
private var chromaUser: DiscordPlayer = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the user data on first query.
|
||||||
|
*
|
||||||
|
* @return A Chroma user of Discord or a Discord user of Chroma
|
||||||
|
*/
|
||||||
|
def getChromaUser: DiscordPlayer = {
|
||||||
|
if (chromaUser == null) chromaUser = ChromaGamerBase.getUser(user.getId.asString, classOf[DiscordPlayer])
|
||||||
|
chromaUser
|
||||||
|
}
|
||||||
|
|
||||||
|
override def sendMessage(message: String): Unit = try {
|
||||||
|
val broadcast = new Exception().getStackTrace()(2).getMethodName.contains("broadcast")
|
||||||
|
if (broadcast) { //We're catching broadcasts using the Bukkit event
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val sendmsg = DPUtils.sanitizeString(message)
|
||||||
|
this synchronized msgtosend += "\n" + sendmsg
|
||||||
|
if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => {
|
||||||
|
def foo(): Unit = {
|
||||||
|
channel.createMessage((if (user != null) user.getMention + "\n"
|
||||||
|
else "") + msgtosend.trim).subscribe
|
||||||
|
sendtask = null
|
||||||
|
msgtosend = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
foo()
|
||||||
|
}, 4) // Waits a 0.2 second to gather all/most of the different messages
|
||||||
|
} catch {
|
||||||
|
case e: Exception =>
|
||||||
|
TBMCCoreAPI.SendException("An error occured while sending message to DiscordSender", e, DiscordPlugin.plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def sendMessage(messages: Array[String]): Unit = sendMessage(String.join("\n", messages))
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
package buttondevteam.discordplugin;
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
public interface IMCPlayer<T> extends Player {
|
|
||||||
VCMDWrapper getVanillaCmdListener();
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package buttondevteam.discordplugin
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.playerfaker.VCMDWrapper
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
|
||||||
|
trait IMCPlayer[T] extends Player {
|
||||||
|
def getVanillaCmdListener: VCMDWrapper
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package buttondevteam.discordplugin.mcchat
|
package buttondevteam.discordplugin.mcchat
|
||||||
|
|
||||||
import buttondevteam.core.{ComponentManager, MainPlugin, component}
|
import buttondevteam.core.{ComponentManager, MainPlugin, component}
|
||||||
|
import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast
|
||||||
import buttondevteam.discordplugin._
|
import buttondevteam.discordplugin._
|
||||||
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule
|
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule
|
||||||
import buttondevteam.discordplugin.mcchat.MCChatCustom.CustomLMD
|
import buttondevteam.discordplugin.mcchat.MCChatCustom.CustomLMD
|
||||||
|
@ -105,14 +106,14 @@ object MCChatUtils {
|
||||||
|
|
||||||
def getSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
|
def getSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
|
||||||
val map = senders.get(user.getId.asString)
|
val map = senders.get(user.getId.asString)
|
||||||
if (map != null) return map.get(channel)
|
if (map != null) map.get(channel)
|
||||||
null
|
else null.asInstanceOf
|
||||||
}
|
}
|
||||||
|
|
||||||
def removeSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
|
def removeSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
|
||||||
val map = senders.get(user.getId.asString)
|
val map = senders.get(user.getId.asString)
|
||||||
if (map != null) return map.remove(channel)
|
if (map != null) map.remove(channel)
|
||||||
null
|
else null.asInstanceOf
|
||||||
}
|
}
|
||||||
|
|
||||||
def forPublicPrivateChat(action: Mono[MessageChannel] => Mono[_]): Mono[_] = {
|
def forPublicPrivateChat(action: Mono[MessageChannel] => Mono[_]): Mono[_] = {
|
||||||
|
@ -154,7 +155,7 @@ object MCChatUtils {
|
||||||
if (notEnabled) return Mono.empty
|
if (notEnabled) return Mono.empty
|
||||||
val st = MCChatCustom.lastmsgCustom.stream.filter((clmd) => {
|
val st = MCChatCustom.lastmsgCustom.stream.filter((clmd) => {
|
||||||
def foo(clmd: CustomLMD): Boolean = { //new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
|
def foo(clmd: CustomLMD): Boolean = { //new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
|
||||||
if (toggle != null && ((clmd.toggles & toggle.flag) eq 0)) return false //If null then allow
|
if (toggle != null && ((clmd.toggles & (1 << toggle.id)) eq 0)) return false //If null then allow
|
||||||
if (sender == null) return true
|
if (sender == null) return true
|
||||||
clmd.groupID.equals(clmd.mcchannel.getGroupID(sender))
|
clmd.groupID.equals(clmd.mcchannel.getGroupID(sender))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
package buttondevteam.discordplugin.mccommands;
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.DPUtils;
|
|
||||||
import buttondevteam.discordplugin.DiscordPlayer;
|
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
|
||||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
|
||||||
import buttondevteam.discordplugin.util.DPState;
|
|
||||||
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) {
|
|
||||||
if (checkSafeMode(player)) return true;
|
|
||||||
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);
|
|
||||||
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) {
|
|
||||||
if (checkSafeMode(player)) return true;
|
|
||||||
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 restart."
|
|
||||||
})
|
|
||||||
public void reload(CommandSender sender) {
|
|
||||||
if (DiscordPlugin.plugin.tryReloadConfig())
|
|
||||||
sender.sendMessage("§bConfig reloaded.");
|
|
||||||
else
|
|
||||||
sender.sendMessage("§cFailed to reload config.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = {
|
|
||||||
"Restart the plugin", //
|
|
||||||
"This command disables and then enables the plugin." //
|
|
||||||
})
|
|
||||||
public void restart(CommandSender sender) {
|
|
||||||
Runnable task = () -> {
|
|
||||||
if (!DiscordPlugin.plugin.tryReloadConfig()) {
|
|
||||||
sender.sendMessage("§cFailed to reload config so not restarting. Check the console.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MinecraftChatModule.state = DPState.RESTARTING_PLUGIN; //Reset in MinecraftChatModule
|
|
||||||
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("§bRestart finished!");
|
|
||||||
};
|
|
||||||
if (!Bukkit.getName().equals("Paper")) {
|
|
||||||
getPlugin().getLogger().warning("Async plugin events are not supported by the server, running on main thread");
|
|
||||||
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, task);
|
|
||||||
} else
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, task);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
if (checkSafeMode(sender)) return;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkSafeMode(CommandSender sender) {
|
|
||||||
if (DiscordPlugin.SafeMode) {
|
|
||||||
sender.sendMessage("§cThe plugin isn't initialized. Check console for details.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
package buttondevteam.discordplugin.mccommands
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin, DiscordSenderBase}
|
||||||
|
import buttondevteam.discordplugin.commands.{ConnectCommand, VersionCommand}
|
||||||
|
import buttondevteam.discordplugin.mcchat.{MCChatUtils, MinecraftChatModule}
|
||||||
|
import buttondevteam.discordplugin.util.DPState
|
||||||
|
import buttondevteam.lib.chat.{Command2, CommandClass, ICommand2MC}
|
||||||
|
import buttondevteam.lib.player.{ChromaGamerBase, TBMCPlayer, TBMCPlayerBase}
|
||||||
|
import discord4j.core.`object`.ExtendedInvite
|
||||||
|
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 = Array(Array(
|
||||||
|
"Discord",
|
||||||
|
"This command allows performing Discord-related actions."
|
||||||
|
))) class DiscordMCCommand extends ICommand2MC {
|
||||||
|
@Command2.Subcommand def accept(player: Player): Boolean = {
|
||||||
|
if (checkSafeMode(player)) return true
|
||||||
|
val did = ConnectCommand.WaitingToConnect.get(player.getName)
|
||||||
|
if (did == null) {
|
||||||
|
player.sendMessage("§cYou don't have a pending connection to Discord.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val dp = ChromaGamerBase.getUser(did, classOf[DiscordPlayer])
|
||||||
|
val mcp = TBMCPlayerBase.getPlayer(player.getUniqueId, classOf[TBMCPlayer])
|
||||||
|
dp.connectWith(mcp)
|
||||||
|
ConnectCommand.WaitingToConnect.remove(player.getName)
|
||||||
|
MCChatUtils.UnconnectedSenders.remove(did) //Remove all unconnected, will be recreated where needed
|
||||||
|
player.sendMessage("§bAccounts connected.")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand def decline(player: Player): Boolean = {
|
||||||
|
if (checkSafeMode(player)) return true
|
||||||
|
val 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.")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(Array(
|
||||||
|
"Reload Discord plugin",
|
||||||
|
"Reloads the config. To apply some changes, you may need to also run /discord restart."
|
||||||
|
))) def reload(sender: CommandSender): Unit =
|
||||||
|
if (DiscordPlugin.plugin.tryReloadConfig) sender.sendMessage("§bConfig reloaded.")
|
||||||
|
else sender.sendMessage("§cFailed to reload config.")
|
||||||
|
|
||||||
|
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(Array(
|
||||||
|
"Restart the plugin", //
|
||||||
|
"This command disables and then enables the plugin." //
|
||||||
|
))) def restart(sender: CommandSender): Unit = {
|
||||||
|
val task: Runnable = () => {
|
||||||
|
def foo(): Unit = {
|
||||||
|
if (!DiscordPlugin.plugin.tryReloadConfig) {
|
||||||
|
sender.sendMessage("§cFailed to reload config so not restarting. Check the console.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
MinecraftChatModule.state = DPState.RESTARTING_PLUGIN //Reset in MinecraftChatModule
|
||||||
|
sender.sendMessage("§bDisabling DiscordPlugin...")
|
||||||
|
Bukkit.getPluginManager.disablePlugin(DiscordPlugin.plugin)
|
||||||
|
if (!sender.isInstanceOf[DiscordSenderBase]) { //Sending to Discord errors
|
||||||
|
sender.sendMessage("§bEnabling DiscordPlugin...")
|
||||||
|
}
|
||||||
|
Bukkit.getPluginManager.enablePlugin(DiscordPlugin.plugin)
|
||||||
|
if (!sender.isInstanceOf[DiscordSenderBase]) sender.sendMessage("§bRestart finished!")
|
||||||
|
}
|
||||||
|
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Bukkit.getName == "Paper")) {
|
||||||
|
getPlugin.getLogger.warning("Async plugin events are not supported by the server, running on main thread")
|
||||||
|
Bukkit.getScheduler.runTask(DiscordPlugin.plugin, task)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand(helpText = Array(Array(
|
||||||
|
"Version command",
|
||||||
|
"Prints the plugin version"))) def version(sender: CommandSender): Unit = {
|
||||||
|
sender.sendMessage(VersionCommand.getVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand(helpText = Array(Array(
|
||||||
|
"Invite",
|
||||||
|
"Shows an invite link to the server"
|
||||||
|
))) def invite(sender: CommandSender): Unit = {
|
||||||
|
if (checkSafeMode(sender)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val invi: String = DiscordPlugin.plugin.inviteLink.get
|
||||||
|
if (invi.nonEmpty) {
|
||||||
|
sender.sendMessage("§bInvite link: " + invi)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DiscordPlugin.mainServer.getInvites.limitRequest(1)
|
||||||
|
.switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("§cNo invites found for the server.")))
|
||||||
|
.subscribe((inv: ExtendedInvite) => sender.sendMessage("§bInvite link: https://discord.gg/" + inv.getCode), _ => sender.sendMessage("§cThe invite link is not set and the bot has no permission to get it."))
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] = {
|
||||||
|
method.getName match {
|
||||||
|
case "accept" =>
|
||||||
|
Array[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" =>
|
||||||
|
Array[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")
|
||||||
|
case _ =>
|
||||||
|
super.getHelpText(method, ann)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def checkSafeMode(sender: CommandSender): Boolean = {
|
||||||
|
if (DiscordPlugin.SafeMode) {
|
||||||
|
sender.sendMessage("§cThe plugin isn't initialized. Check console for details.")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
package buttondevteam.discordplugin.playerfaker;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.experimental.Delegate;
|
|
||||||
import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker;
|
|
||||||
import org.mockito.plugins.MockMaker;
|
|
||||||
|
|
||||||
public class DelegatingMockMaker implements MockMaker {
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Delegate
|
|
||||||
private MockMaker mockMaker = new SubclassByteBuddyMockMaker();
|
|
||||||
@Getter
|
|
||||||
private static DelegatingMockMaker instance;
|
|
||||||
|
|
||||||
public DelegatingMockMaker() {
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package buttondevteam.discordplugin.playerfaker
|
||||||
|
|
||||||
|
import org.mockito.MockedConstruction
|
||||||
|
import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker
|
||||||
|
import org.mockito.invocation.MockHandler
|
||||||
|
import org.mockito.mock.MockCreationSettings
|
||||||
|
import org.mockito.plugins.MockMaker
|
||||||
|
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
object DelegatingMockMaker {
|
||||||
|
def getInstance: DelegatingMockMaker = DelegatingMockMaker.instance
|
||||||
|
|
||||||
|
private var instance: DelegatingMockMaker = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class DelegatingMockMaker() extends MockMaker {
|
||||||
|
DelegatingMockMaker.instance = this
|
||||||
|
|
||||||
|
override def createMock[T](settings: MockCreationSettings[T], handler: MockHandler[_]): T =
|
||||||
|
this.mockMaker.createMock(settings, handler)
|
||||||
|
|
||||||
|
override def createSpy[T](settings: MockCreationSettings[T], handler: MockHandler[_], instance: T): Optional[T] =
|
||||||
|
this.mockMaker.createSpy(settings, handler, instance)
|
||||||
|
|
||||||
|
override def getHandler(mock: Any): MockHandler[_] =
|
||||||
|
this.mockMaker.getHandler(mock)
|
||||||
|
|
||||||
|
override def resetMock(mock: Any, newHandler: MockHandler[_], settings: MockCreationSettings[_]): Unit = {
|
||||||
|
this.mockMaker.resetMock(mock, newHandler, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isTypeMockable(`type`: Class[_]): MockMaker.TypeMockability =
|
||||||
|
this.mockMaker.isTypeMockable(`type`)
|
||||||
|
|
||||||
|
override def createStaticMock[T](`type`: Class[T], settings: MockCreationSettings[T], handler: MockHandler[_]): MockMaker.StaticMockControl[T] =
|
||||||
|
this.mockMaker.createStaticMock(`type`, settings, handler)
|
||||||
|
|
||||||
|
override def createConstructionMock[T](`type`: Class[T], settingsFactory: Function[MockedConstruction.Context, MockCreationSettings[T]], handlerFactory: Function[MockedConstruction.Context, MockHandler[T]], mockInitializer: MockedConstruction.MockInitializer[T]): MockMaker.ConstructionMockControl[T] =
|
||||||
|
this.mockMaker.createConstructionMock[T](`type`, settingsFactory: Function[MockedConstruction.Context, MockCreationSettings[T]], handlerFactory, mockInitializer)
|
||||||
|
|
||||||
|
def setMockMaker(mockMaker: MockMaker): Unit = {
|
||||||
|
this.mockMaker = mockMaker
|
||||||
|
}
|
||||||
|
|
||||||
|
def getMockMaker: MockMaker = this.mockMaker
|
||||||
|
|
||||||
|
private var mockMaker: MockMaker = new SubclassByteBuddyMockMaker
|
||||||
|
}
|
|
@ -1,96 +0,0 @@
|
||||||
package buttondevteam.discordplugin.playerfaker;
|
|
||||||
|
|
||||||
import com.destroystokyo.paper.profile.CraftPlayerProfile;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Server;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker;
|
|
||||||
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class ServerWatcher {
|
|
||||||
private List<Player> playerList;
|
|
||||||
public final List<Player> fakePlayers = new ArrayList<>();
|
|
||||||
private Server origServer;
|
|
||||||
|
|
||||||
@IgnoreForBinding
|
|
||||||
public void enableDisable(boolean enable) throws Exception {
|
|
||||||
var serverField = Bukkit.class.getDeclaredField("server");
|
|
||||||
serverField.setAccessible(true);
|
|
||||||
if (enable) {
|
|
||||||
var serverClass = Bukkit.getServer().getClass();
|
|
||||||
var originalServer = serverField.get(null);
|
|
||||||
DelegatingMockMaker.getInstance().setMockMaker(new InlineByteBuddyMockMaker());
|
|
||||||
var settings = Mockito.withSettings().stubOnly()
|
|
||||||
.defaultAnswer(invocation -> {
|
|
||||||
var method = invocation.getMethod();
|
|
||||||
int pc = method.getParameterCount();
|
|
||||||
Player player = null;
|
|
||||||
switch (method.getName()) {
|
|
||||||
case "getPlayer":
|
|
||||||
if (pc == 1 && method.getParameterTypes()[0] == UUID.class)
|
|
||||||
player = MCChatUtils.LoggedInPlayers.get(invocation.<UUID>getArgument(0));
|
|
||||||
break;
|
|
||||||
case "getPlayerExact":
|
|
||||||
if (pc == 1) {
|
|
||||||
final String argument = invocation.getArgument(0);
|
|
||||||
player = MCChatUtils.LoggedInPlayers.values().stream()
|
|
||||||
.filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
/*case "getOnlinePlayers":
|
|
||||||
if (playerList == null) {
|
|
||||||
@SuppressWarnings("unchecked") var list = (List<Player>) method.invoke(origServer, invocation.getArguments());
|
|
||||||
playerList = new AppendListView<>(list, fakePlayers);
|
|
||||||
} - Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
|
|
||||||
return playerList;*/
|
|
||||||
case "createProfile": //Paper's method, casts the player to a CraftPlayer
|
|
||||||
if (pc == 2) {
|
|
||||||
UUID uuid = invocation.getArgument(0);
|
|
||||||
String name = invocation.getArgument(1);
|
|
||||||
player = uuid != null ? MCChatUtils.LoggedInPlayers.get(uuid) : null;
|
|
||||||
if (player == null && name != null)
|
|
||||||
player = MCChatUtils.LoggedInPlayers.values().stream()
|
|
||||||
.filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null);
|
|
||||||
if (player != null)
|
|
||||||
return new CraftPlayerProfile(player.getUniqueId(), player.getName());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (player != null)
|
|
||||||
return player;
|
|
||||||
return method.invoke(origServer, invocation.getArguments());
|
|
||||||
});
|
|
||||||
//var mock = mockMaker.createMock(settings, MockHandlerFactory.createMockHandler(settings));
|
|
||||||
//thread.setContextClassLoader(cl);
|
|
||||||
var mock = Mockito.mock(serverClass, settings);
|
|
||||||
for (var field : serverClass.getFields()) //Copy public fields, private fields aren't accessible directly anyways
|
|
||||||
if (!Modifier.isFinal(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()))
|
|
||||||
field.set(mock, field.get(originalServer));
|
|
||||||
serverField.set(null, mock);
|
|
||||||
origServer = (Server) originalServer;
|
|
||||||
} else if (origServer != null)
|
|
||||||
serverField.set(null, origServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public static class AppendListView<T> extends AbstractSequentialList<T> {
|
|
||||||
private final List<T> originalList;
|
|
||||||
private final List<T> additionalList;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ListIterator<T> listIterator(int i) {
|
|
||||||
int os = originalList.size();
|
|
||||||
return i < os ? originalList.listIterator(i) : additionalList.listIterator(i - os);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return originalList.size() + additionalList.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package buttondevteam.discordplugin.playerfaker
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.DiscordConnectedPlayer
|
||||||
|
import buttondevteam.discordplugin.mcchat.MCChatUtils
|
||||||
|
import com.destroystokyo.paper.profile.CraftPlayerProfile
|
||||||
|
import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding
|
||||||
|
import org.bukkit.{Bukkit, Server}
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker
|
||||||
|
import org.mockito.invocation.InvocationOnMock
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
import java.util
|
||||||
|
import java.util._
|
||||||
|
|
||||||
|
object ServerWatcher {
|
||||||
|
|
||||||
|
class AppendListView[T](private val originalList: java.util.List[T], private val additionalList: java.util.List[T]) extends java.util.AbstractSequentialList[T] {
|
||||||
|
|
||||||
|
override def listIterator(i: Int): util.ListIterator[T] = {
|
||||||
|
val os = originalList.size
|
||||||
|
if (i < os) originalList.listIterator(i)
|
||||||
|
else additionalList.listIterator(i - os)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def size: Int = originalList.size + additionalList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerWatcher {
|
||||||
|
final val fakePlayers = new util.ArrayList[Player]
|
||||||
|
private var origServer: Server = null
|
||||||
|
|
||||||
|
@IgnoreForBinding
|
||||||
|
@throws[Exception]
|
||||||
|
def enableDisable(enable: Boolean): Unit = {
|
||||||
|
val serverField = classOf[Bukkit].getDeclaredField("server")
|
||||||
|
serverField.setAccessible(true)
|
||||||
|
if (enable) {
|
||||||
|
val serverClass = Bukkit.getServer.getClass
|
||||||
|
val originalServer = serverField.get(null)
|
||||||
|
DelegatingMockMaker.getInstance.setMockMaker(new InlineByteBuddyMockMaker)
|
||||||
|
val settings = Mockito.withSettings.stubOnly.defaultAnswer((invocation: InvocationOnMock) => {
|
||||||
|
def foo(invocation: InvocationOnMock): AnyRef = {
|
||||||
|
val method = invocation.getMethod
|
||||||
|
val pc = method.getParameterCount
|
||||||
|
var player: DiscordConnectedPlayer = null
|
||||||
|
method.getName match {
|
||||||
|
case "getPlayer" =>
|
||||||
|
if (pc == 1 && (method.getParameterTypes()(0) eq classOf[UUID])) player = MCChatUtils.LoggedInPlayers.get(invocation.getArgument[UUID](0))
|
||||||
|
case "getPlayerExact" =>
|
||||||
|
if (pc == 1) {
|
||||||
|
val argument = invocation.getArgument(0)
|
||||||
|
player = MCChatUtils.LoggedInPlayers.values.stream.filter((dcp) => dcp.getName.equalsIgnoreCase(argument)).findAny.orElse(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*case "getOnlinePlayers":
|
||||||
|
if (playerList == null) {
|
||||||
|
@SuppressWarnings("unchecked") var list = (List<Player>) method.invoke(origServer, invocation.getArguments());
|
||||||
|
playerList = new AppendListView<>(list, fakePlayers);
|
||||||
|
} - Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
|
||||||
|
return playerList;*/ case "createProfile" => //Paper's method, casts the player to a CraftPlayer
|
||||||
|
if (pc == 2) {
|
||||||
|
val uuid = invocation.getArgument(0)
|
||||||
|
val name = invocation.getArgument(1)
|
||||||
|
player = if (uuid != null) MCChatUtils.LoggedInPlayers.get(uuid)
|
||||||
|
else null
|
||||||
|
if (player == null && name != null) player = MCChatUtils.LoggedInPlayers.values.stream.filter((dcp) => dcp.getName.equalsIgnoreCase(name)).findAny.orElse(null)
|
||||||
|
if (player != null) return new CraftPlayerProfile(player.getUniqueId, player.getName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (player != null) return player
|
||||||
|
method.invoke(origServer, invocation.getArguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(invocation)
|
||||||
|
})
|
||||||
|
//var mock = mockMaker.createMock(settings, MockHandlerFactory.createMockHandler(settings));
|
||||||
|
//thread.setContextClassLoader(cl);
|
||||||
|
val mock = Mockito.mock(serverClass, settings)
|
||||||
|
for (field <- serverClass.getFields) { //Copy public fields, private fields aren't accessible directly anyways
|
||||||
|
if (!Modifier.isFinal(field.getModifiers) && !Modifier.isStatic(field.getModifiers)) field.set(mock, field.get(originalServer))
|
||||||
|
}
|
||||||
|
serverField.set(null, mock)
|
||||||
|
origServer = originalServer.asInstanceOf[Server]
|
||||||
|
}
|
||||||
|
else if (origServer != null) serverField.set(null, origServer)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package buttondevteam.discordplugin.playerfaker;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.DiscordSenderBase;
|
import buttondevteam.discordplugin.DiscordSenderBase;
|
||||||
import buttondevteam.discordplugin.IMCPlayer;
|
import buttondevteam.discordplugin.IMCPlayer;
|
||||||
|
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
|
@ -2,6 +2,7 @@ package buttondevteam.discordplugin.playerfaker.perm;
|
||||||
|
|
||||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
import buttondevteam.discordplugin.DiscordPlugin;
|
||||||
|
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
|
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
|
||||||
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
|
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
package buttondevteam.discordplugin.role;
|
|
||||||
|
|
||||||
import buttondevteam.core.ComponentManager;
|
|
||||||
import buttondevteam.discordplugin.DPUtils;
|
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
|
||||||
import buttondevteam.lib.architecture.Component;
|
|
||||||
import buttondevteam.lib.architecture.ComponentMetadata;
|
|
||||||
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.Role;
|
|
||||||
import discord4j.core.object.entity.channel.MessageChannel;
|
|
||||||
import discord4j.rest.util.Color;
|
|
||||||
import lombok.val;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically collects roles with a certain color.
|
|
||||||
* Users can add these roles to themselves using the /role Discord command.
|
|
||||||
*/
|
|
||||||
@ComponentMetadata(enabledByDefault = false)
|
|
||||||
public class GameRoleModule extends Component<DiscordPlugin> {
|
|
||||||
public List<String> GameRoles;
|
|
||||||
|
|
||||||
private final RoleCommand command = new RoleCommand(this);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void enable() {
|
|
||||||
getPlugin().getManager().registerCommand(command);
|
|
||||||
GameRoles = DiscordPlugin.mainServer.getRoles().filterWhen(this::isGameRole).map(Role::getName).collect(Collectors.toList()).block();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void disable() {
|
|
||||||
getPlugin().getManager().unregisterCommand(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The channel where the bot logs when it detects a role change that results in a new game role or one being removed.
|
|
||||||
*/
|
|
||||||
private final ReadOnlyConfigData<Mono<MessageChannel>> logChannel = DPUtils.channelData(getConfig(), "logChannel");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The role color that is used by game roles.
|
|
||||||
* Defaults to the second to last in the upper row - #95a5a6.
|
|
||||||
*/
|
|
||||||
private final ReadOnlyConfigData<Color> roleColor = getConfig().<Color>getConfig("roleColor")
|
|
||||||
.def(Color.of(149, 165, 166))
|
|
||||||
.getter(rgb -> Color.of(Integer.parseInt(((String) rgb).substring(1), 16)))
|
|
||||||
.setter(color -> String.format("#%08x", color.getRGB())).buildReadOnly();
|
|
||||||
|
|
||||||
public static void handleRoleEvent(RoleEvent roleEvent) {
|
|
||||||
val grm = ComponentManager.getIfEnabled(GameRoleModule.class);
|
|
||||||
if (grm == null) return;
|
|
||||||
val GameRoles = grm.GameRoles;
|
|
||||||
val logChannel = grm.logChannel.get();
|
|
||||||
Predicate<Role> notMainServer = r -> r.getGuildId().asLong() != DiscordPlugin.mainServer.getId().asLong();
|
|
||||||
if (roleEvent instanceof RoleCreateEvent) {
|
|
||||||
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> {
|
|
||||||
Role role = ((RoleCreateEvent) roleEvent).getRole();
|
|
||||||
if (notMainServer.test(role))
|
|
||||||
return;
|
|
||||||
grm.isGameRole(role).flatMap(b -> {
|
|
||||||
if (!b)
|
|
||||||
return Mono.empty(); //Deleted or not a game role
|
|
||||||
GameRoles.add(role.getName());
|
|
||||||
if (logChannel != null)
|
|
||||||
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 game role color."));
|
|
||||||
return Mono.empty();
|
|
||||||
}).subscribe();
|
|
||||||
}, 100);
|
|
||||||
} else if (roleEvent instanceof RoleDeleteEvent) {
|
|
||||||
Role role = ((RoleDeleteEvent) roleEvent).getRole().orElse(null);
|
|
||||||
if (role == null) return;
|
|
||||||
if (notMainServer.test(role))
|
|
||||||
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) {
|
|
||||||
val event = (RoleUpdateEvent) roleEvent;
|
|
||||||
if (!event.getOld().isPresent()) {
|
|
||||||
grm.logWarn("Old role not stored, cannot update game role!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Role or = event.getOld().get();
|
|
||||||
if (notMainServer.test(or))
|
|
||||||
return;
|
|
||||||
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 its 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 (removed)
|
|
||||||
return logChannel.flatMap(ch -> ch.createMessage("Changed game role from " + or.getName() + " to " + event.getCurrent().getName() + "."));
|
|
||||||
else
|
|
||||||
return logChannel.flatMap(ch -> ch.createMessage("Added " + event.getCurrent().getName() + " as game role because it has the color of one."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Mono.empty();
|
|
||||||
}).subscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mono<Boolean> isGameRole(Role r) {
|
|
||||||
if (r.getGuildId().asLong() != DiscordPlugin.mainServer.getId().asLong())
|
|
||||||
return Mono.just(false); //Only allow on the main server
|
|
||||||
val rc = roleColor.get();
|
|
||||||
return Mono.just(r.getColor().equals(rc)).filter(b -> b).flatMap(b ->
|
|
||||||
DiscordPlugin.dc.getSelf().flatMap(u -> u.asMember(DiscordPlugin.mainServer.getId()))
|
|
||||||
.flatMap(m -> m.hasHigherRoles(Collections.singleton(r.getId())))) //Below one of our roles
|
|
||||||
.defaultIfEmpty(false);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package buttondevteam.discordplugin.role
|
||||||
|
|
||||||
|
import buttondevteam.core.ComponentManager
|
||||||
|
import buttondevteam.discordplugin.{DPUtils, DiscordPlugin}
|
||||||
|
import buttondevteam.lib.architecture.{Component, ComponentMetadata}
|
||||||
|
import discord4j.core.`object`.entity.Role
|
||||||
|
import discord4j.core.`object`.entity.channel.MessageChannel
|
||||||
|
import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleEvent, RoleUpdateEvent}
|
||||||
|
import discord4j.rest.util.Color
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically collects roles with a certain color.
|
||||||
|
* Users can add these roles to themselves using the /role Discord command.
|
||||||
|
*/
|
||||||
|
@ComponentMetadata(enabledByDefault = false) object GameRoleModule {
|
||||||
|
def handleRoleEvent(roleEvent: RoleEvent): Unit = {
|
||||||
|
val grm = ComponentManager.getIfEnabled(classOf[GameRoleModule])
|
||||||
|
if (grm == null) return
|
||||||
|
val GameRoles = grm.GameRoles
|
||||||
|
val logChannel = grm.logChannel.get
|
||||||
|
val notMainServer = (r: Role) => r.getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong
|
||||||
|
roleEvent match {
|
||||||
|
case roleCreateEvent: RoleCreateEvent => Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => {
|
||||||
|
def foo(): Unit = {
|
||||||
|
val role = roleCreateEvent.getRole
|
||||||
|
if (notMainServer(role)) return
|
||||||
|
grm.isGameRole(role).flatMap((b: Boolean) => {
|
||||||
|
def foo(b: Boolean): Mono[_] = {
|
||||||
|
if (!b) return Mono.empty //Deleted or not a game role
|
||||||
|
GameRoles.add(role.getName)
|
||||||
|
if (logChannel != null) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Added " + role.getName + " as game role. If you don't want this, change the role's color from the game role color."))
|
||||||
|
Mono.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(b)
|
||||||
|
}).subscribe
|
||||||
|
}
|
||||||
|
|
||||||
|
foo()
|
||||||
|
}, 100)
|
||||||
|
case roleDeleteEvent: RoleDeleteEvent =>
|
||||||
|
val role = roleDeleteEvent.getRole.orElse(null)
|
||||||
|
if (role == null) return
|
||||||
|
if (notMainServer(role)) return
|
||||||
|
if (GameRoles.remove(role.getName) && logChannel != null) logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Removed " + role.getName + " as a game role.")).subscribe
|
||||||
|
case roleUpdateEvent: RoleUpdateEvent =>
|
||||||
|
if (!roleUpdateEvent.getOld.isPresent) {
|
||||||
|
grm.logWarn("Old role not stored, cannot update game role!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val or = roleUpdateEvent.getOld.get
|
||||||
|
if (notMainServer(or)) return
|
||||||
|
val cr = roleUpdateEvent.getCurrent
|
||||||
|
grm.isGameRole(cr).flatMap((b: Boolean) => {
|
||||||
|
def foo(b: Boolean): Mono[_] = {
|
||||||
|
if (!b) if (GameRoles.remove(or.getName) && logChannel != null) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Removed " + or.getName + " as a game role because its color changed."))
|
||||||
|
else {
|
||||||
|
if (GameRoles.contains(or.getName) && or.getName == cr.getName) return Mono.empty
|
||||||
|
val removed = GameRoles.remove(or.getName) //Regardless of whether it was a game role
|
||||||
|
GameRoles.add(cr.getName) //Add it because it has no color
|
||||||
|
if (logChannel != null) if (removed) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Changed game role from " + or.getName + " to " + cr.getName + "."))
|
||||||
|
else return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Added " + cr.getName + " as game role because it has the color of one."))
|
||||||
|
}
|
||||||
|
Mono.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(b)
|
||||||
|
}).subscribe
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ComponentMetadata(enabledByDefault = false) class GameRoleModule extends Component[DiscordPlugin] {
|
||||||
|
var GameRoles: java.util.List[String] = null
|
||||||
|
final private val command = new RoleCommand(this)
|
||||||
|
|
||||||
|
override protected def enable(): Unit = {
|
||||||
|
getPlugin.manager.registerCommand(command)
|
||||||
|
GameRoles = DiscordPlugin.mainServer.getRoles.filterWhen(this.isGameRole _).map(_.getName).collect(Collectors.toList).block
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def disable(): Unit = getPlugin.manager.unregisterCommand(command)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The channel where the bot logs when it detects a role change that results in a new game role or one being removed.
|
||||||
|
*/
|
||||||
|
final private val logChannel = DPUtils.channelData(getConfig, "logChannel")
|
||||||
|
/**
|
||||||
|
* The role color that is used by game roles.
|
||||||
|
* Defaults to the second to last in the upper row - #95a5a6.
|
||||||
|
*/
|
||||||
|
final private val roleColor = getConfig.getConfig[Color]("roleColor").`def`(Color.of(149, 165, 166)).getter((rgb: Any) => Color.of(Integer.parseInt(rgb.asInstanceOf[String].substring(1), 16))).setter((color: Color) => String.format("#%08x", color.getRGB)).buildReadOnly
|
||||||
|
|
||||||
|
private def isGameRole(r: Role): Mono[Boolean] = {
|
||||||
|
if (r.getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong) return Mono.just(false) //Only allow on the main server
|
||||||
|
val rc = roleColor.get
|
||||||
|
if (r.getColor equals rc)
|
||||||
|
DiscordPlugin.dc.getSelf.flatMap((u) => u.asMember(DiscordPlugin.mainServer.getId)).flatMap((m) => m.hasHigherRoles(Collections.singleton(r.getId))).defaultIfEmpty(false) //Below one of our roles
|
||||||
|
else Mono.just(false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
package buttondevteam.discordplugin.role;
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.DiscordPlugin;
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
|
||||||
import buttondevteam.lib.chat.Command2;
|
|
||||||
import buttondevteam.lib.chat.CommandClass;
|
|
||||||
import discord4j.core.object.entity.Role;
|
|
||||||
import lombok.val;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@CommandClass
|
|
||||||
public class RoleCommand extends ICommand2DC {
|
|
||||||
|
|
||||||
private GameRoleModule grm;
|
|
||||||
|
|
||||||
RoleCommand(GameRoleModule grm) {
|
|
||||||
this.grm = grm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command2.Subcommand(helpText = {
|
|
||||||
"Add role",
|
|
||||||
"This command adds a role to your account."
|
|
||||||
})
|
|
||||||
public boolean add(Command2DCSender sender, @Command2.TextArg String rolename) {
|
|
||||||
final Role role = checkAndGetRole(sender, rolename);
|
|
||||||
if (role == null)
|
|
||||||
return true;
|
|
||||||
try {
|
|
||||||
sender.getMessage().getAuthorAsMember()
|
|
||||||
.flatMap(m -> m.addRole(role.getId()).switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("added role."))))
|
|
||||||
.subscribe();
|
|
||||||
} catch (Exception e) {
|
|
||||||
TBMCCoreAPI.SendException("Error while adding role!", e, grm);
|
|
||||||
sender.sendMessage("an error occured while adding the role.");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command2.Subcommand(helpText = {
|
|
||||||
"Remove role",
|
|
||||||
"This command removes a role from your account."
|
|
||||||
})
|
|
||||||
public boolean remove(Command2DCSender sender, @Command2.TextArg String rolename) {
|
|
||||||
final Role role = checkAndGetRole(sender, rolename);
|
|
||||||
if (role == null)
|
|
||||||
return true;
|
|
||||||
try {
|
|
||||||
sender.getMessage().getAuthorAsMember()
|
|
||||||
.flatMap(m -> m.removeRole(role.getId()).switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("removed role."))))
|
|
||||||
.subscribe();
|
|
||||||
} catch (Exception e) {
|
|
||||||
TBMCCoreAPI.SendException("Error while removing role!", e, grm);
|
|
||||||
sender.sendMessage("an error occured while removing the role.");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Command2.Subcommand
|
|
||||||
public void list(Command2DCSender sender) {
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
boolean b = false;
|
|
||||||
for (String role : (Iterable<String>) grm.GameRoles.stream().sorted()::iterator) {
|
|
||||||
sb.append(role);
|
|
||||||
if (!b)
|
|
||||||
for (int j = 0; j < Math.max(1, 20 - role.length()); j++)
|
|
||||||
sb.append(" ");
|
|
||||||
else
|
|
||||||
sb.append("\n");
|
|
||||||
b = !b;
|
|
||||||
}
|
|
||||||
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '\n')
|
|
||||||
sb.append('\n');
|
|
||||||
sender.sendMessage("list of roles:\n```\n" + sb + "```");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Role checkAndGetRole(Command2DCSender sender, String rolename) {
|
|
||||||
String rname = rolename;
|
|
||||||
if (!grm.GameRoles.contains(rolename)) { //If not found as-is, correct case
|
|
||||||
val orn = grm.GameRoles.stream().filter(r -> r.equalsIgnoreCase(rolename)).findAny();
|
|
||||||
if (!orn.isPresent()) {
|
|
||||||
sender.sendMessage("that role cannot be found.");
|
|
||||||
list(sender);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
rname = orn.get();
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
sender.sendMessage("the specified role cannot be found on Discord! Removing from the list.");
|
|
||||||
grm.GameRoles.remove(rolename);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (roles.size() > 1) {
|
|
||||||
sender.sendMessage("there are multiple roles with this name. Why are there multiple roles with this name?");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return roles.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
package buttondevteam.discordplugin.role
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.DiscordPlugin
|
||||||
|
import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC}
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI
|
||||||
|
import buttondevteam.lib.chat.{Command2, CommandClass}
|
||||||
|
import discord4j.core.`object`.entity.Role
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
@CommandClass class RoleCommand private[role](var grm: GameRoleModule) extends ICommand2DC {
|
||||||
|
@Command2.Subcommand(helpText = Array(Array(
|
||||||
|
"Add role",
|
||||||
|
"This command adds a role to your account."
|
||||||
|
))) def add(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = {
|
||||||
|
val role = checkAndGetRole(sender, rolename)
|
||||||
|
if (role == null) return true
|
||||||
|
try sender.getMessage.getAuthorAsMember.flatMap(m => m.addRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("added role.")))).subscribe
|
||||||
|
catch {
|
||||||
|
case e: Exception =>
|
||||||
|
TBMCCoreAPI.SendException("Error while adding role!", e, grm)
|
||||||
|
sender.sendMessage("an error occured while adding the role.")
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand(helpText = Array(Array(
|
||||||
|
"Remove role",
|
||||||
|
"This command removes a role from your account."
|
||||||
|
))) def remove(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = {
|
||||||
|
val role = checkAndGetRole(sender, rolename)
|
||||||
|
if (role == null) return true
|
||||||
|
try sender.getMessage.getAuthorAsMember.flatMap(m => m.removeRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("removed role.")))).subscribe
|
||||||
|
catch {
|
||||||
|
case e: Exception =>
|
||||||
|
TBMCCoreAPI.SendException("Error while removing role!", e, grm)
|
||||||
|
sender.sendMessage("an error occured while removing the role.")
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand def list(sender: Command2DCSender): Unit = {
|
||||||
|
val sb = new StringBuilder
|
||||||
|
var b = false
|
||||||
|
for (role <- grm.GameRoles.stream.sorted.iterator.asInstanceOf[Iterable[String]]) {
|
||||||
|
sb.append(role)
|
||||||
|
if (!b) for (_ <- 0 until Math.max(1, 20 - role.length)) {
|
||||||
|
sb.append(" ")
|
||||||
|
}
|
||||||
|
else sb.append("\n")
|
||||||
|
b = !b
|
||||||
|
}
|
||||||
|
if (sb.nonEmpty && sb.charAt(sb.length - 1) != '\n') sb.append('\n')
|
||||||
|
sender.sendMessage("list of roles:\n```\n" + sb + "```")
|
||||||
|
}
|
||||||
|
|
||||||
|
private def checkAndGetRole(sender: Command2DCSender, rolename: String): Role = {
|
||||||
|
var rname = rolename
|
||||||
|
if (!grm.GameRoles.contains(rolename)) { //If not found as-is, correct case
|
||||||
|
val orn = grm.GameRoles.stream.filter(r => r.equalsIgnoreCase(rolename)).findAny
|
||||||
|
if (!orn.isPresent) {
|
||||||
|
sender.sendMessage("that role cannot be found.")
|
||||||
|
list(sender)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
rname = orn.get
|
||||||
|
}
|
||||||
|
val frname = rname
|
||||||
|
val 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) {
|
||||||
|
sender.sendMessage("the specified role cannot be found on Discord! Removing from the list.")
|
||||||
|
grm.GameRoles.remove(rolename)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (roles.size > 1) {
|
||||||
|
sender.sendMessage("there are multiple roles with this name. Why are there multiple roles with this name?")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
roles.get(0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
package buttondevteam.discordplugin.util;
|
|
||||||
|
|
||||||
public enum DPState {
|
|
||||||
/**
|
|
||||||
* Used from server start until anything else happens
|
|
||||||
*/
|
|
||||||
RUNNING,
|
|
||||||
/**
|
|
||||||
* Used when /restart is detected
|
|
||||||
*/
|
|
||||||
RESTARTING_SERVER,
|
|
||||||
/**
|
|
||||||
* Used when the plugin is disabled by outside forces
|
|
||||||
*/
|
|
||||||
STOPPING_SERVER,
|
|
||||||
/**
|
|
||||||
* Used when /discord restart is run
|
|
||||||
*/
|
|
||||||
RESTARTING_PLUGIN,
|
|
||||||
/**
|
|
||||||
* Used when the plugin is in the RUNNING state when the chat is disabled
|
|
||||||
*/
|
|
||||||
DISABLED_MCCHAT
|
|
||||||
}
|
|
31
src/main/java/buttondevteam/discordplugin/util/DPState.scala
Normal file
31
src/main/java/buttondevteam/discordplugin/util/DPState.scala
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package buttondevteam.discordplugin.util
|
||||||
|
|
||||||
|
object DPState extends Enumeration {
|
||||||
|
type DPState = Value
|
||||||
|
val
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used from server start until anything else happens
|
||||||
|
*/
|
||||||
|
RUNNING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when /restart is detected
|
||||||
|
*/
|
||||||
|
RESTARTING_SERVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when the plugin is disabled by outside forces
|
||||||
|
*/
|
||||||
|
STOPPING_SERVER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when /discord restart is run
|
||||||
|
*/
|
||||||
|
RESTARTING_PLUGIN,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when the plugin is in the RUNNING state when the chat is disabled
|
||||||
|
*/
|
||||||
|
DISABLED_MCCHAT = Value
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
package buttondevteam.discordplugin.util;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
12
src/main/java/buttondevteam/discordplugin/util/Timings.scala
Normal file
12
src/main/java/buttondevteam/discordplugin/util/Timings.scala
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package buttondevteam.discordplugin.util
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.listeners.CommonListeners
|
||||||
|
|
||||||
|
class Timings() {
|
||||||
|
private var start = System.nanoTime
|
||||||
|
|
||||||
|
def printElapsed(message: String): Unit = {
|
||||||
|
CommonListeners.debug(message + " (" + (System.nanoTime - start) / 1000000L + ")")
|
||||||
|
start = System.nanoTime
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
import buttondevteam.discordplugin.DiscordPlugin
|
|
||||||
|
|
||||||
object Test extends App {
|
|
||||||
println(DiscordPlugin.plugin)
|
|
||||||
}
|
|
Loading…
Reference in a new issue