parent
261725dc0f
commit
3f6135f427
9 changed files with 446 additions and 459 deletions
53
pom.xml
53
pom.xml
|
@ -78,40 +78,6 @@
|
||||||
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
|
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>pre-scala</id>
|
|
||||||
<configuration>
|
|
||||||
<compilerArgs>-proc:only</compilerArgs>
|
|
||||||
</configuration>
|
|
||||||
<phase>generate-sources</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>compile</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>net.alchim31.maven</groupId>
|
|
||||||
<artifactId>scala-maven-plugin</artifactId>
|
|
||||||
<version>4.4.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>generate-sources</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>compile</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<configuration>
|
|
||||||
<!-- <sendJavaToScalac>false</sendJavaToScalac> -->
|
|
||||||
<addToClasspath>target/generated-sourcess</addToClasspath>
|
|
||||||
<removeFromClasspath>src/main/java</removeFromClasspath>
|
|
||||||
<sourceDir>src/main/scala</sourceDir>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
@ -157,15 +123,6 @@
|
||||||
<id>papermc</id>
|
<id>papermc</id>
|
||||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||||
</repository>
|
</repository>
|
||||||
<repository>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>true</enabled>
|
|
||||||
</snapshots>
|
|
||||||
<id>ossSonatypeSnapshot</id>
|
|
||||||
<name>OSS Sonatype Snapshots</name>
|
|
||||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
|
||||||
<layout>default</layout>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -265,15 +222,5 @@
|
||||||
<artifactId>mockito-core</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<version>3.5.13</version>
|
<version>3.5.13</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.scala-lang</groupId>
|
|
||||||
<artifactId>scala-library</artifactId>
|
|
||||||
<version>2.13.1</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.projectreactor</groupId>
|
|
||||||
<artifactId>reactor-scala-extensions_2.13</artifactId>
|
|
||||||
<version>0.7.0</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
52
src/main/java/buttondevteam/discordplugin/ChromaBot.java
Executable file
52
src/main/java/buttondevteam/discordplugin/ChromaBot.java
Executable file
|
@ -0,0 +1,52 @@
|
||||||
|
package buttondevteam.discordplugin;
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||||
|
import discord4j.core.object.entity.Message;
|
||||||
|
import discord4j.core.object.entity.channel.MessageChannel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ChromaBot {
|
||||||
|
/**
|
||||||
|
* May be null if it's not initialized. Initialization happens after the server is done loading (using {@link BukkitScheduler#runTaskAsynchronously(org.bukkit.plugin.Plugin, Runnable)})
|
||||||
|
*/
|
||||||
|
private static @Getter ChromaBot instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will set the instance field.
|
||||||
|
*/
|
||||||
|
ChromaBot() {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void delete() {
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to the chat channels and private chats.
|
||||||
|
*
|
||||||
|
* @param message The message to send, duh (use {@link MessageChannel#createMessage(String)})
|
||||||
|
*/
|
||||||
|
public void sendMessage(Function<Mono<MessageChannel>, Mono<Message>> message) {
|
||||||
|
MCChatUtils.forPublicPrivateChat(message::apply).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to the chat channels, private chats and custom chats.
|
||||||
|
*
|
||||||
|
* @param message The message to send, duh
|
||||||
|
* @param toggle The toggle type for channelcon
|
||||||
|
*/
|
||||||
|
public void sendMessageCustomAsWell(Function<Mono<MessageChannel>, Mono<Message>> message, @Nullable ChannelconBroadcast toggle) {
|
||||||
|
MCChatUtils.forCustomAndAllMCChat(message::apply, toggle, false).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePlayerList() {
|
||||||
|
MCChatUtils.updatePlayerList();
|
||||||
|
}
|
||||||
|
}
|
292
src/main/java/buttondevteam/discordplugin/DiscordPlugin.java
Executable file
292
src/main/java/buttondevteam/discordplugin/DiscordPlugin.java
Executable file
|
@ -0,0 +1,292 @@
|
||||||
|
package buttondevteam.discordplugin;
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.announcer.AnnouncerModule;
|
||||||
|
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule;
|
||||||
|
import buttondevteam.discordplugin.commands.*;
|
||||||
|
import buttondevteam.discordplugin.exceptions.ExceptionListenerModule;
|
||||||
|
import buttondevteam.discordplugin.fun.FunModule;
|
||||||
|
import buttondevteam.discordplugin.listeners.CommonListeners;
|
||||||
|
import buttondevteam.discordplugin.listeners.MCListener;
|
||||||
|
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||||
|
import buttondevteam.discordplugin.mccommands.DiscordMCCommand;
|
||||||
|
import buttondevteam.discordplugin.role.GameRoleModule;
|
||||||
|
import buttondevteam.discordplugin.util.DPState;
|
||||||
|
import buttondevteam.discordplugin.util.Timings;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import buttondevteam.lib.architecture.ConfigData;
|
||||||
|
import buttondevteam.lib.architecture.IHaveConfig;
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import discord4j.common.util.Snowflake;
|
||||||
|
import discord4j.core.DiscordClientBuilder;
|
||||||
|
import discord4j.core.GatewayDiscordClient;
|
||||||
|
import discord4j.core.event.domain.guild.GuildCreateEvent;
|
||||||
|
import discord4j.core.event.domain.lifecycle.ReadyEvent;
|
||||||
|
import discord4j.core.object.entity.Guild;
|
||||||
|
import discord4j.core.object.entity.Role;
|
||||||
|
import discord4j.core.object.presence.Activity;
|
||||||
|
import discord4j.core.object.presence.Presence;
|
||||||
|
import discord4j.core.object.reaction.ReactionEmoji;
|
||||||
|
import discord4j.store.jdk.JdkStoreService;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.val;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.core.Logger;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.mockito.internal.util.MockUtil;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ButtonPlugin.ConfigOpts(disableConfigGen = true)
|
||||||
|
public class DiscordPlugin extends ButtonPlugin {
|
||||||
|
public static GatewayDiscordClient dc;
|
||||||
|
public static DiscordPlugin plugin;
|
||||||
|
public static boolean SafeMode = true;
|
||||||
|
@Getter
|
||||||
|
private Command2DC manager;
|
||||||
|
private boolean starting;
|
||||||
|
private BukkitLogWatcher logWatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix to use with Discord commands like /role. It only works in the bot channel.
|
||||||
|
*/
|
||||||
|
private final ConfigData<Character> prefix = getIConfig().getData("prefix", '/', str -> ((String) str).charAt(0), Object::toString);
|
||||||
|
|
||||||
|
public static char getPrefix() {
|
||||||
|
if (plugin == null) return '/';
|
||||||
|
return plugin.prefix.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to.
|
||||||
|
*/
|
||||||
|
private ConfigData<Optional<Guild>> mainServer() {
|
||||||
|
return getIConfig().getDataPrimDef("mainServer", 0L,
|
||||||
|
id -> {
|
||||||
|
//It attempts to get the default as well
|
||||||
|
if ((long) id == 0L)
|
||||||
|
return Optional.empty(); //Hack?
|
||||||
|
return dc.getGuildById(Snowflake.of((long) id))
|
||||||
|
.onErrorResume(t -> Mono.fromRunnable(() -> getLogger().warning("Failed to get guild: " + t.getMessage()))).blockOptional();
|
||||||
|
},
|
||||||
|
g -> g.map(gg -> gg.getId().asLong()).orElse(0L));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The (bot) channel to use for Discord commands like /role.
|
||||||
|
*/
|
||||||
|
public ConfigData<Snowflake> commandChannel = DPUtils.snowflakeData(getIConfig(), "commandChannel", 0L);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The role that allows using mod-only Discord commands.
|
||||||
|
* If empty (''), then it will only allow for the owner.
|
||||||
|
*/
|
||||||
|
public ConfigData<Mono<Role>> modRole;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access.
|
||||||
|
*/
|
||||||
|
public ConfigData<String> inviteLink = getIConfig().getData("inviteLink", "");
|
||||||
|
|
||||||
|
private void setupConfig() {
|
||||||
|
modRole = DPUtils.roleData(getIConfig(), "modRole", "Moderator");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad() { //Needed by ServerWatcher
|
||||||
|
var thread = Thread.currentThread();
|
||||||
|
var cl = thread.getContextClassLoader();
|
||||||
|
thread.setContextClassLoader(getClassLoader());
|
||||||
|
MockUtil.isMock(null); //Load MockUtil to load Mockito plugins
|
||||||
|
thread.setContextClassLoader(cl);
|
||||||
|
getLogger().info("Load complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pluginEnable() {
|
||||||
|
try {
|
||||||
|
getLogger().info("Initializing...");
|
||||||
|
plugin = this;
|
||||||
|
manager = new Command2DC();
|
||||||
|
registerCommand(new DiscordMCCommand()); //Register so that the restart command works
|
||||||
|
String token;
|
||||||
|
File tokenFile = new File("TBMC", "Token.txt");
|
||||||
|
if (tokenFile.exists()) //Legacy support
|
||||||
|
//noinspection UnstableApiUsage
|
||||||
|
token = Files.readFirstLine(tokenFile, StandardCharsets.UTF_8);
|
||||||
|
else {
|
||||||
|
File privateFile = new File(getDataFolder(), "private.yml");
|
||||||
|
val conf = YamlConfiguration.loadConfiguration(privateFile);
|
||||||
|
token = conf.getString("token");
|
||||||
|
if (token == null || token.equalsIgnoreCase("Token goes here")) {
|
||||||
|
conf.set("token", "Token goes here");
|
||||||
|
conf.save(privateFile);
|
||||||
|
|
||||||
|
getLogger().severe("Token not found! Please set it in private.yml then do /discord restart");
|
||||||
|
getLogger().severe("You need to have a bot account to use with your server.");
|
||||||
|
getLogger().severe("If you don't have one, go to https://discordapp.com/developers/applications/ and create an application, then create a bot for it and copy the bot token.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
starting = true;
|
||||||
|
//System.out.println("This line should show up for sure");
|
||||||
|
val cb = DiscordClientBuilder.create(token).build().gateway();
|
||||||
|
//System.out.println("Got gateway bootstrap");
|
||||||
|
cb.setInitialStatus(si -> Presence.doNotDisturb(Activity.playing("booting")));
|
||||||
|
cb.setStoreService(new JdkStoreService()); //The default doesn't work for some reason - it's waaay faster now
|
||||||
|
//System.out.println("Initial status and store service set");
|
||||||
|
cb.login().doOnError(t -> {
|
||||||
|
stopStarting();
|
||||||
|
//System.out.println("Got this error: " + t); t.printStackTrace();
|
||||||
|
}).subscribe(dc -> {
|
||||||
|
//System.out.println("Login successful, got dc: " + dc);
|
||||||
|
DiscordPlugin.dc = dc; //Set to gateway client
|
||||||
|
dc.on(ReadyEvent.class) // Listen for ReadyEvent(s)
|
||||||
|
.map(event -> event.getGuilds().size()) // Get how many guilds the bot is in
|
||||||
|
.flatMap(size -> dc
|
||||||
|
.on(GuildCreateEvent.class) // Listen for GuildCreateEvent(s)
|
||||||
|
.take(size) // Take only the first `size` GuildCreateEvent(s) to be received
|
||||||
|
.doOnError(t -> {
|
||||||
|
stopStarting();
|
||||||
|
//System.out.println("Got error: " + t);
|
||||||
|
t.printStackTrace();
|
||||||
|
})
|
||||||
|
.collectList()).doOnError(t -> stopStarting()).subscribe(this::handleReady); // Take all received GuildCreateEvents and make it a List
|
||||||
|
}); /* All guilds have been received, client is fully connected */
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e, this);
|
||||||
|
getLogger().severe("You may be able to restart the plugin using /discord restart");
|
||||||
|
stopStarting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopStarting() {
|
||||||
|
synchronized (this) {
|
||||||
|
starting = false;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Guild mainServer;
|
||||||
|
|
||||||
|
private void handleReady(List<GuildCreateEvent> event) {
|
||||||
|
//System.out.println("Got ready event");
|
||||||
|
try {
|
||||||
|
if (mainServer != null) { //This is not the first ready event
|
||||||
|
getLogger().info("Ready event already handled"); //TODO: It should probably handle disconnections
|
||||||
|
dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe(); //Update from the initial presence
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mainServer = mainServer().get().orElse(null); //Shouldn't change afterwards
|
||||||
|
if (mainServer == null) {
|
||||||
|
if (event.size() == 0) {
|
||||||
|
getLogger().severe("Main server not found! Invite the bot and do /discord restart");
|
||||||
|
dc.getApplicationInfo().subscribe(info ->
|
||||||
|
getLogger().severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId().asString() + "&scope=bot&permissions=268509264"));
|
||||||
|
saveConfig(); //Put default there
|
||||||
|
return; //We should have all guilds by now, no need to retry
|
||||||
|
}
|
||||||
|
mainServer = event.get(0).getGuild();
|
||||||
|
getLogger().warning("Main server set to first one: " + mainServer.getName());
|
||||||
|
mainServer().set(Optional.of(mainServer)); //Save in config
|
||||||
|
}
|
||||||
|
SafeMode = false;
|
||||||
|
setupConfig();
|
||||||
|
DPUtils.disableIfConfigErrorRes(null, commandChannel, DPUtils.getMessageChannel(commandChannel));
|
||||||
|
//Won't disable, just prints the warning here
|
||||||
|
|
||||||
|
if (MinecraftChatModule.state == DPState.STOPPING_SERVER) {
|
||||||
|
stopStarting();
|
||||||
|
return; //Reusing that field to check if stopping while still initializing
|
||||||
|
}
|
||||||
|
CommonListeners.register(dc.getEventDispatcher());
|
||||||
|
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this);
|
||||||
|
TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class, DiscordPlayer::new);
|
||||||
|
ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof DiscordSenderBase
|
||||||
|
? ((DiscordSenderBase) sender).getChromaUser() : null));
|
||||||
|
|
||||||
|
IHaveConfig.pregenConfig(this, null);
|
||||||
|
|
||||||
|
var cb = new ChromaBot(); //Initialize ChromaBot
|
||||||
|
Component.registerComponent(this, new GeneralEventBroadcasterModule());
|
||||||
|
Component.registerComponent(this, new MinecraftChatModule());
|
||||||
|
Component.registerComponent(this, new ExceptionListenerModule());
|
||||||
|
Component.registerComponent(this, new GameRoleModule()); //Needs the mainServer to be set
|
||||||
|
Component.registerComponent(this, new AnnouncerModule());
|
||||||
|
Component.registerComponent(this, new FunModule());
|
||||||
|
cb.updatePlayerList(); //The MCChatModule is tested to be enabled
|
||||||
|
|
||||||
|
getManager().registerCommand(new VersionCommand());
|
||||||
|
getManager().registerCommand(new UserinfoCommand());
|
||||||
|
getManager().registerCommand(new HelpCommand());
|
||||||
|
getManager().registerCommand(new DebugCommand());
|
||||||
|
getManager().registerCommand(new ConnectCommand());
|
||||||
|
|
||||||
|
TBMCCoreAPI.SendUnsentExceptions();
|
||||||
|
TBMCCoreAPI.SendUnsentDebugMessages();
|
||||||
|
|
||||||
|
var blw = new BukkitLogWatcher();
|
||||||
|
blw.start();
|
||||||
|
((Logger) LogManager.getRootLogger()).addAppender(blw);
|
||||||
|
logWatcher = blw;
|
||||||
|
|
||||||
|
if (!TBMCCoreAPI.IsTestServer()) {
|
||||||
|
dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe();
|
||||||
|
} else {
|
||||||
|
dc.updatePresence(Presence.online(Activity.playing("testing"))).subscribe();
|
||||||
|
}
|
||||||
|
getLogger().info("Loaded!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("An error occurred while enabling DiscordPlugin!", e, this);
|
||||||
|
}
|
||||||
|
stopStarting();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pluginPreDisable() {
|
||||||
|
if (MinecraftChatModule.state == DPState.RUNNING)
|
||||||
|
MinecraftChatModule.state = DPState.STOPPING_SERVER;
|
||||||
|
synchronized (this) {
|
||||||
|
if (starting) {
|
||||||
|
try {
|
||||||
|
wait(10000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ChromaBot.getInstance() == null) return; //Failed to load
|
||||||
|
Timings timings = new Timings();
|
||||||
|
timings.printElapsed("Disable start");
|
||||||
|
timings.printElapsed("Updating player list");
|
||||||
|
ChromaBot.getInstance().updatePlayerList();
|
||||||
|
timings.printElapsed("Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pluginDisable() {
|
||||||
|
Timings timings = new Timings();
|
||||||
|
timings.printElapsed("Actual disable start (logout)");
|
||||||
|
if (ChromaBot.getInstance() == null) return; //Failed to load
|
||||||
|
|
||||||
|
try {
|
||||||
|
SafeMode = true; // Stop interacting with Discord
|
||||||
|
ChromaBot.delete();
|
||||||
|
((Logger) LogManager.getRootLogger()).removeAppender(logWatcher);
|
||||||
|
timings.printElapsed("Logging out...");
|
||||||
|
dc.logout().block();
|
||||||
|
mainServer = null; //Allow ReadyEvent again
|
||||||
|
//Configs are emptied so channels and servers are fetched again
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final ReactionEmoji DELIVERED_REACTION = ReactionEmoji.unicode("✅");
|
||||||
|
}
|
|
@ -1,257 +0,0 @@
|
||||||
package buttondevteam.discordplugin
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.announcer.AnnouncerModule
|
|
||||||
import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule
|
|
||||||
import buttondevteam.discordplugin.commands._
|
|
||||||
import buttondevteam.discordplugin.exceptions.ExceptionListenerModule
|
|
||||||
import buttondevteam.discordplugin.fun.FunModule
|
|
||||||
import buttondevteam.discordplugin.listeners.{CommonListeners, MCListener}
|
|
||||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule
|
|
||||||
import buttondevteam.discordplugin.mccommands.DiscordMCCommand
|
|
||||||
import buttondevteam.discordplugin.role.GameRoleModule
|
|
||||||
import buttondevteam.discordplugin.util.{DPState, Timings}
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI
|
|
||||||
import buttondevteam.lib.architecture._
|
|
||||||
import buttondevteam.lib.player.ChromaGamerBase
|
|
||||||
import com.google.common.io.Files
|
|
||||||
import discord4j.common.util.Snowflake
|
|
||||||
import discord4j.core.{DiscordClientBuilder, GatewayDiscordClient}
|
|
||||||
import discord4j.core.`object`.entity.{ApplicationInfo, Guild, Role}
|
|
||||||
import discord4j.core.`object`.presence.{Activity, Presence}
|
|
||||||
import discord4j.core.`object`.reaction.ReactionEmoji
|
|
||||||
import discord4j.core.event.domain.guild.GuildCreateEvent
|
|
||||||
import discord4j.core.event.domain.lifecycle.ReadyEvent
|
|
||||||
import discord4j.gateway.ShardInfo
|
|
||||||
import discord4j.store.jdk.JdkStoreService
|
|
||||||
import org.apache.logging.log4j.LogManager
|
|
||||||
import org.apache.logging.log4j.core.Logger
|
|
||||||
import org.bukkit.command.CommandSender
|
|
||||||
import org.bukkit.configuration.file.YamlConfiguration
|
|
||||||
import org.mockito.internal.util.MockUtil
|
|
||||||
import reactor.core.Disposable
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.util.Optional
|
|
||||||
|
|
||||||
@ButtonPlugin.ConfigOpts(disableConfigGen = true) object DiscordPlugin {
|
|
||||||
private[discordplugin] var dc: GatewayDiscordClient = null
|
|
||||||
private[discordplugin] var plugin: DiscordPlugin = null
|
|
||||||
private[discordplugin] var SafeMode = true
|
|
||||||
|
|
||||||
def getPrefix: Char = {
|
|
||||||
if (plugin == null) return '/'
|
|
||||||
plugin.prefix.get
|
|
||||||
}
|
|
||||||
|
|
||||||
private[discordplugin] var mainServer: Guild = null
|
|
||||||
private[discordplugin] val DELIVERED_REACTION = ReactionEmoji.unicode("✅")
|
|
||||||
}
|
|
||||||
|
|
||||||
@ButtonPlugin.ConfigOpts(disableConfigGen = true) class DiscordPlugin extends ButtonPlugin {
|
|
||||||
private var _manager: Command2DC = null
|
|
||||||
|
|
||||||
def manager: Command2DC = _manager
|
|
||||||
|
|
||||||
private var starting = false
|
|
||||||
private var logWatcher: BukkitLogWatcher = null
|
|
||||||
/**
|
|
||||||
* The prefix to use with Discord commands like /role. It only works in the bot channel.
|
|
||||||
*/
|
|
||||||
final private val prefix = getIConfig.getData("prefix", '/', (str: Any) => str.asInstanceOf[String].charAt(0), (_: Char).toString)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to.
|
|
||||||
*/
|
|
||||||
private def mainServer = getIConfig.getDataPrimDef("mainServer", 0L, (id: Any) => {
|
|
||||||
def foo(id: Any) = { //It attempts to get the default as well
|
|
||||||
if (id.asInstanceOf[Long] == 0L) Optional.empty //Hack?
|
|
||||||
else DiscordPlugin.dc.getGuildById(Snowflake.of(id.asInstanceOf[Long])).onErrorResume((t: Throwable) => Mono.fromRunnable(() => getLogger.warning("Failed to get guild: " + t.getMessage))).blockOptional
|
|
||||||
}
|
|
||||||
|
|
||||||
foo(id)
|
|
||||||
}, (g: Optional[Guild]) => (g.map((gg: Guild) => gg.getId.asLong): Optional[Long]).orElse(0L))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The (bot) channel to use for Discord commands like /role.
|
|
||||||
*/
|
|
||||||
var commandChannel: ReadOnlyConfigData[Snowflake] = DPUtils.snowflakeData(getIConfig, "commandChannel", 0L)
|
|
||||||
/**
|
|
||||||
* The role that allows using mod-only Discord commands.
|
|
||||||
* If empty (''), then it will only allow for the owner.
|
|
||||||
*/
|
|
||||||
var modRole: ReadOnlyConfigData[Mono[Role]] = null
|
|
||||||
/**
|
|
||||||
* The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access.
|
|
||||||
*/
|
|
||||||
var inviteLink: ConfigData[String] = getIConfig.getData("inviteLink", "")
|
|
||||||
|
|
||||||
private def setupConfig(): Unit = modRole = DPUtils.roleData(getIConfig, "modRole", "Moderator")
|
|
||||||
|
|
||||||
override def onLoad(): Unit = { //Needed by ServerWatcher
|
|
||||||
val thread = Thread.currentThread
|
|
||||||
val cl = thread.getContextClassLoader
|
|
||||||
thread.setContextClassLoader(getClassLoader)
|
|
||||||
MockUtil.isMock(null) //Load MockUtil to load Mockito plugins
|
|
||||||
thread.setContextClassLoader(cl)
|
|
||||||
getLogger.info("Load complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
override def pluginEnable(): Unit = try {
|
|
||||||
getLogger.info("Initializing...")
|
|
||||||
DiscordPlugin.plugin = this
|
|
||||||
_manager = new Command2DC
|
|
||||||
registerCommand(new DiscordMCCommand) //Register so that the restart command works
|
|
||||||
var token: String = null
|
|
||||||
val tokenFile = new File("TBMC", "Token.txt")
|
|
||||||
if (tokenFile.exists) { //Legacy support
|
|
||||||
//noinspection UnstableApiUsage
|
|
||||||
token = Files.readFirstLine(tokenFile, StandardCharsets.UTF_8)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val privateFile = new File(getDataFolder, "private.yml")
|
|
||||||
val conf = YamlConfiguration.loadConfiguration(privateFile)
|
|
||||||
token = conf.getString("token")
|
|
||||||
if (token == null || token.equalsIgnoreCase("Token goes here")) {
|
|
||||||
conf.set("token", "Token goes here")
|
|
||||||
conf.save(privateFile)
|
|
||||||
getLogger.severe("Token not found! Please set it in private.yml then do /discord restart")
|
|
||||||
getLogger.severe("You need to have a bot account to use with your server.")
|
|
||||||
getLogger.severe("If you don't have one, go to https://discordapp.com/developers/applications/ and create an application, then create a bot for it and copy the bot token.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
starting = true
|
|
||||||
//System.out.println("This line should show up for sure");
|
|
||||||
val cb = DiscordClientBuilder.create(token).build.gateway
|
|
||||||
//System.out.println("Got gateway bootstrap");
|
|
||||||
cb.setInitialStatus((si: ShardInfo) => Presence.doNotDisturb(Activity.playing("booting")))
|
|
||||||
cb.setStoreService(new JdkStoreService) //The default doesn't work for some reason - it's waaay faster now
|
|
||||||
//System.out.println("Initial status and store service set");
|
|
||||||
cb.login.doOnError((t: Throwable) => {
|
|
||||||
def foo(t: Throwable): Unit = {
|
|
||||||
stopStarting()
|
|
||||||
//System.out.println("Got this error: " + t); t.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
foo(t)
|
|
||||||
}).subscribe((dc: GatewayDiscordClient) => {
|
|
||||||
def foo(dc: GatewayDiscordClient): Disposable = { //System.out.println("Login successful, got dc: " + dc);
|
|
||||||
DiscordPlugin.dc = dc //Set to gateway client
|
|
||||||
dc.on(classOf[ReadyEvent]).map(_.getGuilds.size).flatMap(dc.on(classOf[GuildCreateEvent]).take(_).collectList)
|
|
||||||
.doOnError(_ => stopStarting()).subscribe(this.handleReady _) // Take all received GuildCreateEvents and make it a List
|
|
||||||
}
|
|
||||||
|
|
||||||
foo(dc)
|
|
||||||
}) /* All guilds have been received, client is fully connected */
|
|
||||||
} catch {
|
|
||||||
case e: Exception =>
|
|
||||||
TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e, this)
|
|
||||||
getLogger.severe("You may be able to restart the plugin using /discord restart")
|
|
||||||
stopStarting()
|
|
||||||
}
|
|
||||||
|
|
||||||
private def stopStarting(): Unit = {
|
|
||||||
this synchronized (starting = false)
|
|
||||||
notifyAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
private def handleReady(event: java.util.List[GuildCreateEvent]): Unit = { //System.out.println("Got ready event");
|
|
||||||
try {
|
|
||||||
if (DiscordPlugin.mainServer != null) { //This is not the first ready event
|
|
||||||
getLogger.info("Ready event already handled") //TODO: It should probably handle disconnections
|
|
||||||
DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe //Update from the initial presence
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DiscordPlugin.mainServer = mainServer.get.orElse(null) //Shouldn't change afterwards
|
|
||||||
if (DiscordPlugin.mainServer == null) {
|
|
||||||
if (event.size == 0) {
|
|
||||||
getLogger.severe("Main server not found! Invite the bot and do /discord restart")
|
|
||||||
DiscordPlugin.dc.getApplicationInfo.subscribe((info: ApplicationInfo) => getLogger.severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId.asString + "&scope=bot&permissions=268509264"))
|
|
||||||
saveConfig() //Put default there
|
|
||||||
return //We should have all guilds by now, no need to retry
|
|
||||||
}
|
|
||||||
DiscordPlugin.mainServer = event.get(0).getGuild
|
|
||||||
getLogger.warning("Main server set to first one: " + DiscordPlugin.mainServer.getName)
|
|
||||||
mainServer.set(Optional.of(DiscordPlugin.mainServer)) //Save in config
|
|
||||||
}
|
|
||||||
DiscordPlugin.SafeMode = false
|
|
||||||
setupConfig()
|
|
||||||
DPUtils.disableIfConfigErrorRes(null, commandChannel, DPUtils.getMessageChannel(commandChannel))
|
|
||||||
//Won't disable, just prints the warning here
|
|
||||||
if (MinecraftChatModule.state eq DPState.STOPPING_SERVER) {
|
|
||||||
stopStarting()
|
|
||||||
return //Reusing that field to check if stopping while still initializing
|
|
||||||
}
|
|
||||||
CommonListeners.register(DiscordPlugin.dc.getEventDispatcher)
|
|
||||||
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener, this)
|
|
||||||
TBMCCoreAPI.RegisterUserClass(classOf[DiscordPlayer], () => new DiscordPlayer)
|
|
||||||
ChromaGamerBase.addConverter((sender: CommandSender) => Optional.ofNullable(sender match {
|
|
||||||
case dsender: DiscordSenderBase => dsender.getChromaUser
|
|
||||||
case _ => null
|
|
||||||
}))
|
|
||||||
IHaveConfig.pregenConfig(this, null)
|
|
||||||
ChromaBot.enabled = true //Initialize ChromaBot
|
|
||||||
Component.registerComponent(this, new GeneralEventBroadcasterModule)
|
|
||||||
Component.registerComponent(this, new MinecraftChatModule)
|
|
||||||
Component.registerComponent(this, new ExceptionListenerModule)
|
|
||||||
Component.registerComponent(this, new GameRoleModule) //Needs the mainServer to be set
|
|
||||||
Component.registerComponent(this, new AnnouncerModule)
|
|
||||||
Component.registerComponent(this, new FunModule)
|
|
||||||
ChromaBot.updatePlayerList() //The MCChatModule is tested to be enabled
|
|
||||||
manager.registerCommand(new VersionCommand)
|
|
||||||
manager.registerCommand(new UserinfoCommand)
|
|
||||||
manager.registerCommand(new HelpCommand)
|
|
||||||
manager.registerCommand(new DebugCommand)
|
|
||||||
manager.registerCommand(new ConnectCommand)
|
|
||||||
TBMCCoreAPI.SendUnsentExceptions()
|
|
||||||
TBMCCoreAPI.SendUnsentDebugMessages()
|
|
||||||
val blw = new BukkitLogWatcher
|
|
||||||
blw.start()
|
|
||||||
LogManager.getRootLogger.asInstanceOf[Logger].addAppender(blw)
|
|
||||||
logWatcher = blw
|
|
||||||
if (!TBMCCoreAPI.IsTestServer) DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe
|
|
||||||
else DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("testing"))).subscribe
|
|
||||||
getLogger.info("Loaded!")
|
|
||||||
} catch {
|
|
||||||
case e: Exception =>
|
|
||||||
TBMCCoreAPI.SendException("An error occurred while enabling DiscordPlugin!", e, this)
|
|
||||||
}
|
|
||||||
stopStarting()
|
|
||||||
}
|
|
||||||
|
|
||||||
override def pluginPreDisable(): Unit = {
|
|
||||||
if (MinecraftChatModule.state eq DPState.RUNNING) MinecraftChatModule.state = DPState.STOPPING_SERVER
|
|
||||||
this synchronized {
|
|
||||||
if (starting) try wait(10000)
|
|
||||||
catch {
|
|
||||||
case e: InterruptedException =>
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!ChromaBot.enabled) return //Failed to load
|
|
||||||
val timings = new Timings
|
|
||||||
timings.printElapsed("Disable start")
|
|
||||||
timings.printElapsed("Updating player list")
|
|
||||||
ChromaBot.updatePlayerList()
|
|
||||||
timings.printElapsed("Done")
|
|
||||||
}
|
|
||||||
|
|
||||||
override def pluginDisable(): Unit = {
|
|
||||||
val timings = new Timings
|
|
||||||
timings.printElapsed("Actual disable start (logout)")
|
|
||||||
if (!ChromaBot.enabled) return
|
|
||||||
try {
|
|
||||||
DiscordPlugin.SafeMode = true // Stop interacting with Discord
|
|
||||||
ChromaBot.enabled = false
|
|
||||||
LogManager.getRootLogger.asInstanceOf[Logger].removeAppender(logWatcher)
|
|
||||||
timings.printElapsed("Logging out...")
|
|
||||||
DiscordPlugin.dc.logout.block
|
|
||||||
DiscordPlugin.mainServer = null //Allow ReadyEvent again
|
|
||||||
} catch {
|
|
||||||
case e: Exception =>
|
|
||||||
TBMCCoreAPI.SendException("An error occured while disabling DiscordPlugin!", e, this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
package buttondevteam.discordplugin.listeners;
|
||||||
|
|
||||||
|
import buttondevteam.discordplugin.DPUtils;
|
||||||
|
import buttondevteam.discordplugin.DiscordPlugin;
|
||||||
|
import buttondevteam.discordplugin.commands.Command2DCSender;
|
||||||
|
import buttondevteam.discordplugin.util.Timings;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import discord4j.common.util.Snowflake;
|
||||||
|
import discord4j.core.object.entity.Message;
|
||||||
|
import discord4j.core.object.entity.Role;
|
||||||
|
import discord4j.core.object.entity.channel.PrivateChannel;
|
||||||
|
import lombok.val;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class CommandListener {
|
||||||
|
/**
|
||||||
|
* Runs a ChromaBot command. If mentionedonly is false, it will only execute the command if it was in #bot with the correct prefix or in private.
|
||||||
|
*
|
||||||
|
* @param message The Discord message
|
||||||
|
* @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message
|
||||||
|
* @return Whether it <b>did not run</b> the command
|
||||||
|
*/
|
||||||
|
public static Mono<Boolean> runCommand(Message message, Snowflake commandChannelID, boolean mentionedonly) {
|
||||||
|
Timings timings = CommonListeners.timings;
|
||||||
|
Mono<Boolean> ret = Mono.just(true);
|
||||||
|
if (message.getContent().length() == 0)
|
||||||
|
return ret; //Pin messages and such, let the mcchat listener deal with it
|
||||||
|
val content = message.getContent();
|
||||||
|
timings.printElapsed("A");
|
||||||
|
return message.getChannel().flatMap(channel -> {
|
||||||
|
Mono<?> tmp = ret;
|
||||||
|
if (!mentionedonly) { //mentionedonly conditions are in CommonListeners
|
||||||
|
timings.printElapsed("B");
|
||||||
|
if (!(channel instanceof PrivateChannel)
|
||||||
|
&& !(content.charAt(0) == DiscordPlugin.getPrefix()
|
||||||
|
&& channel.getId().asLong() == commandChannelID.asLong())) //
|
||||||
|
return ret;
|
||||||
|
timings.printElapsed("C");
|
||||||
|
tmp = ret.then(channel.type()).thenReturn(true); // Fun (this true is ignored - x)
|
||||||
|
}
|
||||||
|
final StringBuilder cmdwithargs = new StringBuilder(content);
|
||||||
|
val gotmention = new AtomicBoolean();
|
||||||
|
timings.printElapsed("Before self");
|
||||||
|
return tmp.flatMapMany(x ->
|
||||||
|
DiscordPlugin.dc.getSelf().flatMap(self -> self.asMember(DiscordPlugin.mainServer.getId()))
|
||||||
|
.flatMapMany(self -> {
|
||||||
|
timings.printElapsed("D");
|
||||||
|
gotmention.set(checkanddeletemention(cmdwithargs, self.getMention(), message));
|
||||||
|
gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention(), message) || gotmention.get());
|
||||||
|
val mentions = message.getRoleMentions();
|
||||||
|
return self.getRoles().filterWhen(r -> mentions.any(rr -> rr.getName().equals(r.getName())))
|
||||||
|
.map(Role::getMention);
|
||||||
|
}).map(mentionRole -> {
|
||||||
|
timings.printElapsed("E");
|
||||||
|
gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get()); // Delete all mentions
|
||||||
|
return !mentionedonly || gotmention.get(); //Stops here if false
|
||||||
|
}).switchIfEmpty(Mono.fromSupplier(() -> !mentionedonly || gotmention.get())))
|
||||||
|
.filter(b -> b).last(false).filter(b -> b).doOnNext(b -> channel.type().subscribe()).flatMap(b -> {
|
||||||
|
String cmdwithargsString = cmdwithargs.toString();
|
||||||
|
try {
|
||||||
|
timings.printElapsed("F");
|
||||||
|
if (!DiscordPlugin.plugin.getManager().handleCommand(new Command2DCSender(message), cmdwithargsString))
|
||||||
|
return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix() + "help for help.")
|
||||||
|
.map(m -> false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin);
|
||||||
|
}
|
||||||
|
return Mono.just(false); //If the command succeeded or there was an error, return false
|
||||||
|
}).defaultIfEmpty(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkanddeletemention(StringBuilder cmdwithargs, String mention, Message message) {
|
||||||
|
final char prefix = DiscordPlugin.getPrefix();
|
||||||
|
if (message.getContent().startsWith(mention)) // TODO: Resolve mentions: Compound arguments, either a mention or text
|
||||||
|
if (cmdwithargs.length() > mention.length() + 1) {
|
||||||
|
int i = cmdwithargs.indexOf(" ", mention.length());
|
||||||
|
if (i == -1)
|
||||||
|
i = mention.length();
|
||||||
|
else
|
||||||
|
//noinspection StatementWithEmptyBody
|
||||||
|
for (; i < cmdwithargs.length() && cmdwithargs.charAt(i) == ' '; i++)
|
||||||
|
; //Removes any space before the command
|
||||||
|
cmdwithargs.delete(0, i);
|
||||||
|
cmdwithargs.insert(0, prefix); //Always use the prefix for processing
|
||||||
|
} else
|
||||||
|
cmdwithargs.replace(0, cmdwithargs.length(), prefix + "help");
|
||||||
|
else {
|
||||||
|
if (cmdwithargs.length() == 0)
|
||||||
|
cmdwithargs.replace(0, 0, prefix + "help");
|
||||||
|
else if (cmdwithargs.charAt(0) != prefix)
|
||||||
|
cmdwithargs.insert(0, prefix);
|
||||||
|
return false; //Don't treat / as mention, mentions can be used in public mcchat
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,105 +0,0 @@
|
||||||
package buttondevteam.discordplugin.listeners
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.{DPUtils, DiscordPlugin}
|
|
||||||
import buttondevteam.discordplugin.commands.Command2DCSender
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI
|
|
||||||
import discord4j.common.util.Snowflake
|
|
||||||
import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel}
|
|
||||||
import discord4j.core.`object`.entity.{Member, Message, Role, User}
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
object CommandListener {
|
|
||||||
/**
|
|
||||||
* Runs a ChromaBot command. If mentionedonly is false, it will only execute the command if it was in #bot with the correct prefix or in private.
|
|
||||||
*
|
|
||||||
* @param message The Discord message
|
|
||||||
* @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message
|
|
||||||
* @return Whether it <b>did not run</b> the command
|
|
||||||
*/
|
|
||||||
def runCommand(message: Message, commandChannelID: Snowflake, mentionedonly: Boolean): Mono[Boolean] = {
|
|
||||||
val timings = CommonListeners.timings
|
|
||||||
val ret = Mono.just(true)
|
|
||||||
if (message.getContent.isEmpty) return ret //Pin messages and such, let the mcchat listener deal with it
|
|
||||||
val content = message.getContent
|
|
||||||
timings.printElapsed("A")
|
|
||||||
message.getChannel.flatMap((channel: MessageChannel) => {
|
|
||||||
def foo(channel: MessageChannel): Mono[Boolean] = {
|
|
||||||
var tmp = ret
|
|
||||||
if (!mentionedonly) { //mentionedonly conditions are in CommonListeners
|
|
||||||
timings.printElapsed("B")
|
|
||||||
if (!channel.isInstanceOf[PrivateChannel] && !(content.charAt(0) == DiscordPlugin.getPrefix && channel.getId.asLong == commandChannelID.asLong)) { //
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
timings.printElapsed("C")
|
|
||||||
tmp = ret.`then`(channel.`type`).thenReturn(true) // Fun (this true is ignored - x)
|
|
||||||
}
|
|
||||||
val cmdwithargs = new StringBuilder(content)
|
|
||||||
val gotmention = new AtomicBoolean
|
|
||||||
timings.printElapsed("Before self")
|
|
||||||
tmp.flatMapMany((x: Boolean) => DiscordPlugin.dc.getSelf.flatMap((self: User) => self.asMember(DiscordPlugin.mainServer.getId)).flatMapMany((self: Member) => {
|
|
||||||
def foo(self: Member) = {
|
|
||||||
timings.printElapsed("D")
|
|
||||||
gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message))
|
|
||||||
gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get)
|
|
||||||
val mentions = message.getRoleMentions
|
|
||||||
self.getRoles.filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention)
|
|
||||||
}
|
|
||||||
|
|
||||||
foo(self)
|
|
||||||
}).map((mentionRole: String) => {
|
|
||||||
def foo(mentionRole: String) = {
|
|
||||||
timings.printElapsed("E")
|
|
||||||
gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get) // Delete all mentions
|
|
||||||
!mentionedonly || gotmention.get //Stops here if false
|
|
||||||
}
|
|
||||||
|
|
||||||
foo(mentionRole)
|
|
||||||
}).switchIfEmpty(Mono.fromSupplier(() => !mentionedonly || gotmention.get))).filter((b: Boolean) => b).last(false).filter((b: Boolean) => b).doOnNext((b: Boolean) => channel.`type`.subscribe).flatMap((b: Boolean) => {
|
|
||||||
def foo(): Mono[Boolean] = {
|
|
||||||
val cmdwithargsString = cmdwithargs.toString
|
|
||||||
try {
|
|
||||||
timings.printElapsed("F")
|
|
||||||
if (!DiscordPlugin.plugin.manager.handleCommand(new Command2DCSender(message), cmdwithargsString)) return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix + "help for help.").map((_: Message) => false)
|
|
||||||
} catch {
|
|
||||||
case e: Exception =>
|
|
||||||
TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin)
|
|
||||||
}
|
|
||||||
Mono.just(false) //If the command succeeded or there was an error, return false
|
|
||||||
}
|
|
||||||
|
|
||||||
foo()
|
|
||||||
}).defaultIfEmpty(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
foo(channel)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private def checkanddeletemention(cmdwithargs: StringBuilder, mention: String, message: Message): Boolean = {
|
|
||||||
val prefix = DiscordPlugin.getPrefix
|
|
||||||
if (message.getContent.startsWith(mention)) { // TODO: Resolve mentions: Compound arguments, either a mention or text
|
|
||||||
if (cmdwithargs.length > mention.length + 1) {
|
|
||||||
var i = cmdwithargs.indexOf(" ", mention.length)
|
|
||||||
if (i == -1) i = mention.length
|
|
||||||
else { //noinspection StatementWithEmptyBody
|
|
||||||
while ( {
|
|
||||||
i < cmdwithargs.length && cmdwithargs.charAt(i) == ' '
|
|
||||||
}) { //Removes any space before the command
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmdwithargs.delete(0, i)
|
|
||||||
cmdwithargs.insert(0, prefix) //Always use the prefix for processing
|
|
||||||
}
|
|
||||||
else cmdwithargs.replace(0, cmdwithargs.length, prefix + "help")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (cmdwithargs.isEmpty) cmdwithargs.replace(0, 0, prefix + "help")
|
|
||||||
else if (cmdwithargs.charAt(0) != prefix) cmdwithargs.insert(0, prefix)
|
|
||||||
return false //Don't treat / as mention, mentions can be used in public mcchat
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -68,7 +68,7 @@ class MCListener implements Listener {
|
||||||
final String message = e.getJoinMessage();
|
final String message = e.getJoinMessage();
|
||||||
if (message != null && message.trim().length() > 0)
|
if (message != null && message.trim().length() > 0)
|
||||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
|
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
|
||||||
ChromaBot.updatePlayerList();
|
ChromaBot.getInstance().updatePlayerList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class MCListener implements Listener {
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
|
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
|
||||||
() -> Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId())).ifPresent(MCChatUtils::callLoginEvents));
|
() -> Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId())).ifPresent(MCChatUtils::callLoginEvents));
|
||||||
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
|
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
|
||||||
ChromaBot::updatePlayerList, 5);
|
ChromaBot.getInstance()::updatePlayerList, 5);
|
||||||
final String message = e.getQuitMessage();
|
final String message = e.getQuitMessage();
|
||||||
if (message != null && message.trim().length() > 0)
|
if (message != null && message.trim().length() > 0)
|
||||||
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
|
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
|
||||||
|
@ -119,7 +119,7 @@ class MCListener implements Listener {
|
||||||
final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class)
|
final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class)
|
||||||
.getAs(DiscordPlayer.class);
|
.getAs(DiscordPlayer.class);
|
||||||
if (p == null) return;
|
if (p == null) return;
|
||||||
DPUtils.ignoreError(DiscordPlugin.dc().getUserById(Snowflake.of(p.getDiscordID()))
|
DPUtils.ignoreError(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID()))
|
||||||
.flatMap(user -> user.asMember(DiscordPlugin.mainServer.getId()))
|
.flatMap(user -> user.asMember(DiscordPlugin.mainServer.getId()))
|
||||||
.flatMap(user -> role.flatMap(r -> {
|
.flatMap(user -> role.flatMap(r -> {
|
||||||
if (e.getValue())
|
if (e.getValue())
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import buttondevteam.discordplugin.DiscordPlugin
|
|
||||||
|
|
||||||
object Test extends App {
|
|
||||||
println(DiscordPlugin.plugin)
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package buttondevteam.discordplugin
|
|
||||||
|
|
||||||
import buttondevteam.discordplugin.mcchat.MCChatUtils
|
|
||||||
import discord4j.core.`object`.entity.Message
|
|
||||||
import discord4j.core.`object`.entity.channel.MessageChannel
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
|
|
||||||
import javax.annotation.Nullable
|
|
||||||
|
|
||||||
object ChromaBot {
|
|
||||||
private var _enabled = false
|
|
||||||
|
|
||||||
def enabled = _enabled
|
|
||||||
|
|
||||||
private[discordplugin] def enabled_=(en: Boolean): Unit = _enabled = en
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to the chat channels and private chats.
|
|
||||||
*
|
|
||||||
* @param message The message to send, duh (use {@link MessageChannel# createMessage ( String )})
|
|
||||||
*/
|
|
||||||
def sendMessage(message: java.util.function.Function[Mono[MessageChannel], Mono[Message]]): Unit =
|
|
||||||
MCChatUtils.forPublicPrivateChat(message.apply(_)).subscribe
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to the chat channels, private chats and custom chats.
|
|
||||||
*
|
|
||||||
* @param message The message to send, duh
|
|
||||||
* @param toggle The toggle type for channelcon
|
|
||||||
*/
|
|
||||||
def sendMessageCustomAsWell(message: Function[Mono[MessageChannel], Mono[Message]], @Nullable toggle: ChannelconBroadcast): Unit =
|
|
||||||
MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, false).subscribe
|
|
||||||
|
|
||||||
def updatePlayerList(): Unit =
|
|
||||||
MCChatUtils.updatePlayerList()
|
|
||||||
}
|
|
Loading…
Reference in a new issue