LPInjector and mcchat fixes

Fixed LPInjector registering to the Core
Stop MCChatListener from having multiple active instances
MinecraftChatModule.state instead of all the flags
Showing MinecraftChatModule enable/disable on Discord
/discord reset --> restart
Wait for each shutdown message to send on shutdown (although it hasn't really been an issue so far)
This means using Mono<?> in a lot of places
Also added a contract (IntelliJ) to warn if not subscribed
Faking getOnlinePlayers() is unnecessary and causes too much trouble
Today's work
This commit is contained in:
Norbi Peti 2020-10-10 00:29:21 +02:00
parent ccc15aa048
commit a27a262858
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
17 changed files with 311 additions and 175 deletions

View file

@ -4,7 +4,7 @@ A plugin that provides Minecraft chat functionality and other features.
## Setup ## Setup
This plugin needs Chroma-Core to work. If you have that and this plugin, start the server, and follow the instructions. This plugin needs Chroma-Core to work. If you have that and this plugin, start the server, and follow the instructions.
You'll need a Discord application made, and a bot account created for it. You'll need a Discord application made, and a bot account created for it.
You can restart the plugin using /discord reset without having to restart the whole server. You can restart the plugin using /discord restart without having to restart the whole server.
## Building ## Building
Maven is used to build this project with all of its dependencies. You will need Spigot 1.12.2 and 1.14.4 built using BuildTools. Maven is used to build this project with all of its dependencies. You will need Spigot 1.12.2 and 1.14.4 built using BuildTools.

View file

@ -15,16 +15,12 @@ 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)}) * 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; private static @Getter ChromaBot instance;
private DiscordPlugin dp;
/** /**
* This will set the instance field. * This will set the instance field.
*
* @param dp The Discord plugin
*/ */
ChromaBot(DiscordPlugin dp) { ChromaBot() {
instance = this; instance = this;
this.dp = dp;
} }
static void delete() { static void delete() {
@ -37,7 +33,7 @@ public class ChromaBot {
* @param message The message to send, duh (use {@link MessageChannel#createMessage(String)}) * @param message The message to send, duh (use {@link MessageChannel#createMessage(String)})
*/ */
public void sendMessage(Function<Mono<MessageChannel>, Mono<Message>> message) { public void sendMessage(Function<Mono<MessageChannel>, Mono<Message>> message) {
MCChatUtils.forAllMCChat(ch -> message.apply(ch).subscribe()); MCChatUtils.forPublicPrivateChat(message::apply).subscribe();
} }
/** /**
@ -47,7 +43,7 @@ public class ChromaBot {
* @param toggle The toggle type for channelcon * @param toggle The toggle type for channelcon
*/ */
public void sendMessageCustomAsWell(Function<Mono<MessageChannel>, Mono<Message>> message, @Nullable ChannelconBroadcast toggle) { public void sendMessageCustomAsWell(Function<Mono<MessageChannel>, Mono<Message>> message, @Nullable ChannelconBroadcast toggle) {
MCChatUtils.forCustomAndAllMCChat(ch -> message.apply(ch).subscribe(), toggle, false); MCChatUtils.forCustomAndAllMCChat(message::apply, toggle, false).subscribe();
} }
public void updatePlayerList() { public void updatePlayerList() {

View file

@ -8,6 +8,8 @@ import discord4j.core.object.entity.channel.MessageChannel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.Delegate; 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.*;
import org.bukkit.attribute.Attribute; import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance; import org.bukkit.attribute.AttributeInstance;
@ -24,10 +26,8 @@ import org.mockito.MockSettings;
import org.mockito.Mockito; import org.mockito.Mockito;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Collection; import java.net.InetSocketAddress;
import java.util.Collections; import java.util.*;
import java.util.HashSet;
import java.util.UUID;
import static org.mockito.Answers.RETURNS_DEFAULTS; import static org.mockito.Answers.RETURNS_DEFAULTS;
@ -160,6 +160,11 @@ public abstract class DiscordConnectedPlayer extends DiscordSenderBase implement
location.getYaw(), location.getPitch()); location.getYaw(), location.getPitch());
} }
@Override
public Location getEyeLocation() {
return getLocation();
}
@Override @Override
@Deprecated @Deprecated
public double getMaxHealth() { public double getMaxHealth() {
@ -222,6 +227,71 @@ public abstract class DiscordConnectedPlayer extends DiscordSenderBase implement
return GameMode.SPECTATOR; return GameMode.SPECTATOR;
} }
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, public static DiscordConnectedPlayer create(User user, MessageChannel channel, UUID uuid, String mcname,
MinecraftChatModule module) { MinecraftChatModule module) {
return Mockito.mock(DiscordConnectedPlayer.class, return Mockito.mock(DiscordConnectedPlayer.class,

View file

@ -3,6 +3,8 @@ package buttondevteam.discordplugin;
import buttondevteam.discordplugin.mcchat.MCChatPrivate; import buttondevteam.discordplugin.mcchat.MCChatPrivate;
import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.UserClass; import buttondevteam.lib.player.UserClass;
import discord4j.core.object.entity.User;
import discord4j.core.object.entity.channel.MessageChannel;
@UserClass(foldername = "discord") @UserClass(foldername = "discord")
public class DiscordPlayer extends ChromaGamerBase { public class DiscordPlayer extends ChromaGamerBase {
@ -20,7 +22,7 @@ public class DiscordPlayer extends ChromaGamerBase {
/** /**
* Returns true if player has the private Minecraft chat enabled. For setting the value, see * Returns true if player has the private Minecraft chat enabled. For setting the value, see
* {@link MCChatPrivate#privateMCChat(sx.blah.discord.handle.obj.MessageChannel, boolean, sx.blah.discord.handle.obj.User, DiscordPlayer)} * {@link MCChatPrivate#privateMCChat(MessageChannel, boolean, User, DiscordPlayer)}
*/ */
public boolean isMinecraftChatEnabled() { public boolean isMinecraftChatEnabled() {
return MCChatPrivate.isMinecraftChatEnabled(this); return MCChatPrivate.isMinecraftChatEnabled(this);

View file

@ -7,10 +7,10 @@ import buttondevteam.discordplugin.exceptions.ExceptionListenerModule;
import buttondevteam.discordplugin.fun.FunModule; import buttondevteam.discordplugin.fun.FunModule;
import buttondevteam.discordplugin.listeners.CommonListeners; import buttondevteam.discordplugin.listeners.CommonListeners;
import buttondevteam.discordplugin.listeners.MCListener; import buttondevteam.discordplugin.listeners.MCListener;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.mccommands.DiscordMCCommand; import buttondevteam.discordplugin.mccommands.DiscordMCCommand;
import buttondevteam.discordplugin.role.GameRoleModule; import buttondevteam.discordplugin.role.GameRoleModule;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.discordplugin.util.Timings; import buttondevteam.discordplugin.util.Timings;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.ButtonPlugin;
@ -29,13 +29,10 @@ import discord4j.core.object.entity.Role;
import discord4j.core.object.presence.Activity; import discord4j.core.object.presence.Activity;
import discord4j.core.object.presence.Presence; import discord4j.core.object.presence.Presence;
import discord4j.core.object.reaction.ReactionEmoji; import discord4j.core.object.reaction.ReactionEmoji;
import discord4j.rest.util.Color;
import discord4j.store.jdk.JdkStoreService; import discord4j.store.jdk.JdkStoreService;
import lombok.Getter; import lombok.Getter;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.mockito.internal.util.MockUtil; import org.mockito.internal.util.MockUtil;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -43,7 +40,6 @@ import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
@ButtonPlugin.ConfigOpts(disableConfigGen = true) @ButtonPlugin.ConfigOpts(disableConfigGen = true)
public class DiscordPlugin extends ButtonPlugin { public class DiscordPlugin extends ButtonPlugin {
@ -118,7 +114,7 @@ public class DiscordPlugin extends ButtonPlugin {
getLogger().info("Initializing..."); getLogger().info("Initializing...");
plugin = this; plugin = this;
manager = new Command2DC(); manager = new Command2DC();
registerCommand(new DiscordMCCommand()); //Register so that the reset command works registerCommand(new DiscordMCCommand()); //Register so that the restart command works
String token; String token;
File tokenFile = new File("TBMC", "Token.txt"); File tokenFile = new File("TBMC", "Token.txt");
if (tokenFile.exists()) //Legacy support if (tokenFile.exists()) //Legacy support
@ -132,7 +128,7 @@ public class DiscordPlugin extends ButtonPlugin {
conf.set("token", "Token goes here"); conf.set("token", "Token goes here");
conf.save(privateFile); conf.save(privateFile);
getLogger().severe("Token not found! Please set it in private.yml then do /discord reset"); 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("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."); 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; return;
@ -152,7 +148,7 @@ public class DiscordPlugin extends ButtonPlugin {
}); /* All guilds have been received, client is fully connected */ }); /* All guilds have been received, client is fully connected */
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e, this); TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e, this);
getLogger().severe("You may be able to reset the plugin using /discord reset"); getLogger().severe("You may be able to restart the plugin using /discord restart");
} }
} }
@ -168,7 +164,7 @@ public class DiscordPlugin extends ButtonPlugin {
mainServer = mainServer().get().orElse(null); //Shouldn't change afterwards mainServer = mainServer().get().orElse(null); //Shouldn't change afterwards
if (mainServer == null) { if (mainServer == null) {
if (event.size() == 0) { if (event.size() == 0) {
getLogger().severe("Main server not found! Invite the bot and do /discord reset"); getLogger().severe("Main server not found! Invite the bot and do /discord restart");
dc.getApplicationInfo().subscribe(info -> dc.getApplicationInfo().subscribe(info ->
getLogger().severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId().asString() + "&scope=bot&permissions=268509264")); getLogger().severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId().asString() + "&scope=bot&permissions=268509264"));
saveConfig(); //Put default there saveConfig(); //Put default there
@ -182,40 +178,6 @@ public class DiscordPlugin extends ButtonPlugin {
DPUtils.disableIfConfigErrorRes(null, commandChannel(), DPUtils.getMessageChannel(commandChannel())); DPUtils.disableIfConfigErrorRes(null, commandChannel(), DPUtils.getMessageChannel(commandChannel()));
//Won't disable, just prints the warning here //Won't disable, just prints the warning here
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());
new ChromaBot(this).updatePlayerList(); //Initialize ChromaBot - 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());
if (DiscordMCCommand.resetting) //These will only execute if the chat is enabled
ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.CYAN)
.setTitle("Discord plugin restarted - chat connected."))), ChannelconBroadcast.RESTART); //Really important to note the chat, hmm
else if (getConfig().getBoolean("serverup", false)) {
ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.YELLOW)
.setTitle("Server recovered from a crash - chat connected."))), ChannelconBroadcast.RESTART);
val thr = new Throwable(
"The server shut down unexpectedly. See the log of the previous run for more details.");
thr.setStackTrace(new StackTraceElement[0]);
TBMCCoreAPI.SendException("The server crashed!", thr, this);
} else
ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.GREEN)
.setTitle("Server started - chat connected."))), ChannelconBroadcast.RESTART);
DiscordMCCommand.resetting = false; //This is the last event handling this flag
getConfig().set("serverup", true);
saveConfig();
TBMCCoreAPI.SendUnsentExceptions();
TBMCCoreAPI.SendUnsentDebugMessages();
CommonListeners.register(dc.getEventDispatcher()); CommonListeners.register(dc.getEventDispatcher());
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this); TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this);
TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class); TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class);
@ -223,6 +185,25 @@ public class DiscordPlugin extends ButtonPlugin {
? ((DiscordSenderBase) sender).getChromaUser() : null)); ? ((DiscordSenderBase) sender).getChromaUser() : null));
IHaveConfig.pregenConfig(this, 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();
if (!TBMCCoreAPI.IsTestServer()) { if (!TBMCCoreAPI.IsTestServer()) {
dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe(); dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe();
} else { } else {
@ -234,46 +215,24 @@ public class DiscordPlugin extends ButtonPlugin {
} }
} }
/**
* Always true, except when running "stop" from console
*/
public static boolean Restart;
@Override @Override
public void pluginPreDisable() { public void pluginPreDisable() {
if (ChromaBot.getInstance() == null) return; //Failed to load if (ChromaBot.getInstance() == null) return; //Failed to load
Timings timings = new Timings(); Timings timings = new Timings();
timings.printElapsed("Disable start"); timings.printElapsed("Disable start");
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> {
timings.printElapsed("Sending message to " + ch.getMention());
if (DiscordMCCommand.resetting)
ecs.setColor(Color.ORANGE).setTitle("Discord plugin restarting");
else
ecs.setColor(Restart ? Color.ORANGE : Color.RED)
.setTitle(Restart ? "Server restarting" : "Server stopping")
.setDescription(
Bukkit.getOnlinePlayers().size() > 0
? (DPUtils
.sanitizeString(Bukkit.getOnlinePlayers().stream()
.map(Player::getDisplayName).collect(Collectors.joining(", ")))
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
+ "thrown out") //TODO: Make configurable
: ""); //If 'restart' is disabled then this isn't shown even if joinleave is enabled
})).subscribe(), ChannelconBroadcast.RESTART, false);
timings.printElapsed("Updating player list"); timings.printElapsed("Updating player list");
ChromaBot.getInstance().updatePlayerList(); ChromaBot.getInstance().updatePlayerList();
timings.printElapsed("Done"); timings.printElapsed("Done");
if (MinecraftChatModule.state == DPState.RUNNING)
MinecraftChatModule.state = DPState.STOPPING_SERVER;
} }
@Override @Override
public void pluginDisable() { public void pluginDisable() {
Timings timings = new Timings(); Timings timings = new Timings();
timings.printElapsed("Actual disable start (logout)"); timings.printElapsed("Actual disable start (logout)");
timings.printElapsed("Config setup");
getConfig().set("serverup", false);
if (ChromaBot.getInstance() == null) return; //Failed to load if (ChromaBot.getInstance() == null) return; //Failed to load
saveConfig();
try { try {
SafeMode = true; // Stop interacting with Discord SafeMode = true; // Stop interacting with Discord
ChromaBot.delete(); ChromaBot.delete();

View file

@ -82,6 +82,7 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
while (!stop) { while (!stop) {
try { try {
if (!isEnabled()) { if (!isEnabled()) {
//noinspection BusyWait
Thread.sleep(10000); Thread.sleep(10000);
continue; continue;
} }
@ -135,6 +136,7 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
e.printStackTrace(); e.printStackTrace();
} }
try { try {
//noinspection BusyWait
Thread.sleep(10000); Thread.sleep(10000);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();

View file

@ -144,7 +144,7 @@ public class PlayerListWatcher {
if (packet.getClass() == ppoc) { if (packet.getClass() == ppoc) {
Field msgf = ppoc.getDeclaredField("a"); Field msgf = ppoc.getDeclaredField("a");
msgf.setAccessible(true); msgf.setAccessible(true);
MCChatUtils.forAllMCChat(MCChatUtils.send((String) toPlainText.invoke(msgf.get(packet)))); MCChatUtils.forPublicPrivateChat(MCChatUtils.send((String) toPlainText.invoke(msgf.get(packet)))).subscribe();
} }
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Failed to broadcast message sent to all players - hacking failed.", e, module); TBMCCoreAPI.SendException("Failed to broadcast message sent to all players - hacking failed.", e, module);

View file

@ -3,6 +3,8 @@ package buttondevteam.discordplugin.listeners;
import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.commands.ConnectCommand; import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.lib.TBMCCommandPreprocessEvent; import buttondevteam.lib.TBMCCommandPreprocessEvent;
import buttondevteam.lib.player.TBMCPlayerGetInfoEvent; import buttondevteam.lib.player.TBMCPlayerGetInfoEvent;
import buttondevteam.lib.player.TBMCPlayerJoinEvent; import buttondevteam.lib.player.TBMCPlayerJoinEvent;
@ -53,6 +55,9 @@ public class MCListener implements Listener {
@EventHandler @EventHandler
public void onCommandPreprocess(TBMCCommandPreprocessEvent e) { public void onCommandPreprocess(TBMCCommandPreprocessEvent e) {
DiscordPlugin.Restart = !e.getMessage().equalsIgnoreCase("/stop"); // The variable is always true except if stopped if (e.getMessage().equalsIgnoreCase("/stop"))
MinecraftChatModule.state = DPState.STOPPING_SERVER;
else
MinecraftChatModule.state = DPState.RESTARTING_SERVER;
} }
} }

View file

@ -45,8 +45,9 @@ public class MCChatListener implements Listener {
private BukkitTask sendtask; private BukkitTask sendtask;
private final LinkedBlockingQueue<AbstractMap.SimpleEntry<TBMCChatEvent, Instant>> sendevents = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<AbstractMap.SimpleEntry<TBMCChatEvent, Instant>> sendevents = new LinkedBlockingQueue<>();
private Runnable sendrunnable; private Runnable sendrunnable;
private static Thread sendthread; private Thread sendthread;
private final MinecraftChatModule module; private final MinecraftChatModule module;
private boolean stop = false; //A new instance will be created on enable
public MCChatListener(MinecraftChatModule minecraftChatModule) { public MCChatListener(MinecraftChatModule minecraftChatModule) {
module = minecraftChatModule; module = minecraftChatModule;
@ -62,7 +63,7 @@ public class MCChatListener implements Listener {
sendrunnable = () -> { sendrunnable = () -> {
sendthread = Thread.currentThread(); sendthread = Thread.currentThread();
processMCToDiscord(); processMCToDiscord();
if (DiscordPlugin.plugin.isEnabled()) //Don't run again if shutting down if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down
sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable); sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable);
}; };
sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable); sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable);
@ -188,12 +189,14 @@ public class MCChatListener implements Listener {
// The maps may not contain the senders for UnconnectedSenders // The maps may not contain the senders for UnconnectedSenders
/** /**
* Stop the listener. Any calls to onMCChat will restart it as long as we're not in safe mode. * Stop the listener permanently. Enabling the module will create a new instance.
* *
* @param wait Wait 5 seconds for the threads to stop * @param wait Wait 5 seconds for the threads to stop
*/ */
public static void stop(boolean wait) { public void stop(boolean wait) {
stop = true;
MCChatPrivate.logoutAll(); MCChatPrivate.logoutAll();
MCChatUtils.LoggedInPlayers.clear();
if (sendthread != null) sendthread.interrupt(); if (sendthread != null) sendthread.interrupt();
if (recthread != null) recthread.interrupt(); if (recthread != null) recthread.interrupt();
try { try {
@ -221,7 +224,7 @@ public class MCChatListener implements Listener {
private BukkitTask rectask; private BukkitTask rectask;
private final LinkedBlockingQueue<MessageCreateEvent> recevents = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<MessageCreateEvent> recevents = new LinkedBlockingQueue<>();
private Runnable recrun; private Runnable recrun;
private static Thread recthread; private Thread recthread;
// Discord // Discord
public Mono<Boolean> handleDiscord(MessageCreateEvent ev) { public Mono<Boolean> handleDiscord(MessageCreateEvent ev) {
@ -257,7 +260,7 @@ public class MCChatListener implements Listener {
recrun = () -> { //Don't return in a while loop next time recrun = () -> { //Don't return in a while loop next time
recthread = Thread.currentThread(); recthread = Thread.currentThread();
processDiscordToMC(); processDiscordToMC();
if (DiscordPlugin.plugin.isEnabled()) //Don't run again if shutting down if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Continue message processing rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Continue message processing
}; };
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing

View file

@ -3,6 +3,8 @@ package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.ComponentManager; import buttondevteam.core.ComponentManager;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer;
import discord4j.core.object.entity.User; import discord4j.core.object.entity.User;
import discord4j.core.object.entity.channel.MessageChannel; import discord4j.core.object.entity.channel.MessageChannel;
@ -35,11 +37,13 @@ public class MCChatPrivate {
} else { } else {
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user); val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
assert sender != null; assert sender != null;
MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId()); Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
if (p == null // Player is offline - If the player is online, that takes precedence if ((p == null || p instanceof DiscordSenderBase) // Player is offline - If the player is online, that takes precedence
&& sender.isLoggedIn()) //Don't call the quit event if login failed && sender.isLoggedIn()) //Don't call the quit event if login failed
MCChatUtils.callLogoutEvent(sender, true); MCChatUtils.callLogoutEvent(sender, false); //The next line has to run *after* this one, so can't use the needsSync parameter
MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId());
sender.setLoggedIn(false); sender.setLoggedIn(false);
});
} }
} // ---- PermissionsEx warning is normal on logout ---- } // ---- PermissionsEx warning is normal on logout ----
if (!start) if (!start)
@ -65,8 +69,7 @@ public class MCChatPrivate {
for (val entry : MCChatUtils.ConnectedSenders.entrySet()) for (val entry : MCChatUtils.ConnectedSenders.entrySet())
for (val valueEntry : entry.getValue().entrySet()) for (val valueEntry : entry.getValue().entrySet())
if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out
MCChatUtils.callLogoutEvent(valueEntry.getValue(), false); //This is sync MCChatUtils.callLogoutEvent(valueEntry.getValue(), !Bukkit.isPrimaryThread());
MCChatUtils.ConnectedSenders.clear();
} }
} }

View file

@ -29,6 +29,7 @@ import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.AuthorNagException; import org.bukkit.plugin.AuthorNagException;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.RegisteredListener;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -37,6 +38,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -67,7 +69,7 @@ public class MCChatUtils {
} }
private static boolean notEnabled() { private static boolean notEnabled() {
return getModule() == null; return (module == null) || (!module.disabling && (getModule() == null)); //Allow using things while disabling the module
} }
private static MinecraftChatModule getModule() { private static MinecraftChatModule getModule() {
@ -142,30 +144,34 @@ public class MCChatUtils {
return null; return null;
} }
public static void forAllMCChat(Consumer<Mono<MessageChannel>> action) { public static Mono<?> forPublicPrivateChat(Function<Mono<MessageChannel>, Mono<?>> action) {
if (notEnabled()) return; if (notEnabled()) return Mono.empty();
action.accept(module.chatChannelMono()); var list = new ArrayList<Mono<?>>();
list.add(action.apply(module.chatChannelMono()));
for (LastMsgData data : MCChatPrivate.lastmsgPerUser) for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
action.accept(Mono.just(data.channel)); list.add(action.apply(Mono.just(data.channel)));
// lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat
return Mono.whenDelayError(list);
} }
/** /**
* For custom and all MC chat * For custom and all MC chat
* *
* @param action The action to act * @param action The action to act (cannot complete empty)
* @param toggle The toggle to check * @param toggle The toggle to check
* @param hookmsg Whether the message is also sent from the hook * @param hookmsg Whether the message is also sent from the hook
*/ */
public static void forCustomAndAllMCChat(Consumer<Mono<MessageChannel>> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) { public static Mono<?> forCustomAndAllMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return; if (notEnabled()) return Mono.empty();
var list = new ArrayList<Publisher<?>>();
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg) if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
forAllMCChat(action); list.add(forPublicPrivateChat(action));
final Consumer<MCChatCustom.CustomLMD> customLMDConsumer = cc -> action.accept(Mono.just(cc.channel)); final Function<MCChatCustom.CustomLMD, Publisher<?>> customLMDFunction = cc -> action.apply(Mono.just(cc.channel));
if (toggle == null) if (toggle == null)
MCChatCustom.lastmsgCustom.forEach(customLMDConsumer); MCChatCustom.lastmsgCustom.stream().map(customLMDFunction).forEach(list::add);
else else
MCChatCustom.lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).forEach(customLMDConsumer); MCChatCustom.lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).map(customLMDFunction).forEach(list::add);
return Mono.whenDelayError(list);
} }
/** /**
@ -175,16 +181,17 @@ public class MCChatUtils {
* @param sender The sender to check perms of or null to send to all that has it toggled * @param sender The sender to check perms of or null to send to all that has it toggled
* @param toggle The toggle to check or null to send to all allowed * @param toggle The toggle to check or null to send to all allowed
*/ */
public static void forAllowedCustomMCChat(Consumer<Mono<MessageChannel>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) { public static Mono<?> forAllowedCustomMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) {
if (notEnabled()) return; if (notEnabled()) return Mono.empty();
MCChatCustom.lastmsgCustom.stream().filter(clmd -> { Stream<Publisher<?>> st = MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
//new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple //new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
if (toggle != null && (clmd.toggles & toggle.flag) == 0) if (toggle != null && (clmd.toggles & toggle.flag) == 0)
return false; //If null then allow return false; //If null then allow
if (sender == null) if (sender == null)
return true; return true;
return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)); return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender));
}).forEach(cc -> action.accept(Mono.just(cc.channel))); //TODO: Send error messages on channel connect }).map(cc -> action.apply(Mono.just(cc.channel))); //TODO: Send error messages on channel connect
return Mono.whenDelayError(st::iterator); //Can't convert as an iterator or inside the stream, but I can convert it as a stream
} }
/** /**
@ -195,32 +202,35 @@ public class MCChatUtils {
* @param toggle The toggle to check or null to send to all allowed * @param toggle The toggle to check or null to send to all allowed
* @param hookmsg Whether the message is also sent from the hook * @param hookmsg Whether the message is also sent from the hook
*/ */
public static void forAllowedCustomAndAllMCChat(Consumer<Mono<MessageChannel>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) { public static Mono<?> forAllowedCustomAndAllMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return; if (notEnabled()) return Mono.empty();
var cc = forAllowedCustomMCChat(action, sender, toggle);
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg) if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
forAllMCChat(action); return Mono.whenDelayError(forPublicPrivateChat(action), cc);
forAllowedCustomMCChat(action, sender, toggle); return Mono.whenDelayError(cc);
} }
public static Consumer<Mono<MessageChannel>> send(String message) { public static Function<Mono<MessageChannel>, Mono<?>> send(String message) {
return ch -> ch.flatMap(mc -> { return ch -> ch.flatMap(mc -> {
resetLastMessage(mc); resetLastMessage(mc);
return mc.createMessage(DPUtils.sanitizeString(message)); return mc.createMessage(DPUtils.sanitizeString(message));
}).subscribe(); });
} }
public static void forAllowedMCChat(Consumer<Mono<MessageChannel>> action, TBMCSystemChatEvent event) { public static Mono<?> forAllowedMCChat(Function<Mono<MessageChannel>, Mono<?>> action, TBMCSystemChatEvent event) {
if (notEnabled()) return; if (notEnabled()) return Mono.empty();
var list = new ArrayList<Mono<?>>();
if (event.getChannel().isGlobal()) if (event.getChannel().isGlobal())
action.accept(module.chatChannelMono()); list.add(action.apply(module.chatChannelMono()));
for (LastMsgData data : MCChatPrivate.lastmsgPerUser) for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
if (event.shouldSendTo(getSender(data.channel.getId(), data.user))) if (event.shouldSendTo(getSender(data.channel.getId(), data.user)))
action.accept(Mono.just(data.channel)); //TODO: Only store ID? list.add(action.apply(Mono.just(data.channel))); //TODO: Only store ID?
MCChatCustom.lastmsgCustom.stream().filter(clmd -> { MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
if (!clmd.brtoggles.contains(event.getTarget())) if (!clmd.brtoggles.contains(event.getTarget()))
return false; return false;
return event.shouldSendTo(clmd.dcp); return event.shouldSendTo(clmd.dcp);
}).map(clmd -> Mono.just(clmd.channel)).forEach(action); }).map(clmd -> action.apply(Mono.just(clmd.channel))).forEach(list::add);
return Mono.whenDelayError(list);
} }
/** /**
@ -357,8 +367,11 @@ public class MCChatUtils {
} }
callEventExcludingSome(new PlayerJoinEvent(dcp, "")); callEventExcludingSome(new PlayerJoinEvent(dcp, ""));
dcp.setLoggedIn(true); dcp.setLoggedIn(true);
if (module != null) if (module != null) {
if (module.serverWatcher != null)
module.serverWatcher.fakePlayers.add(dcp);
module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged in from Discord"); module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged in from Discord");
}
}); });
} }
@ -374,8 +387,11 @@ public class MCChatUtils {
if (needsSync) callEventSync(event); if (needsSync) callEventSync(event);
else callEventExcludingSome(event); else callEventExcludingSome(event);
dcp.setLoggedIn(false); dcp.setLoggedIn(false);
if (module != null) if (module != null) {
module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged out from Discord"); module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged out from Discord");
if (module.serverWatcher != null)
module.serverWatcher.fakePlayers.remove(dcp);
}
} }
static void callEventSync(Event event) { static void callEventSync(Event event) {

View file

@ -63,7 +63,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); MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
ChromaBot.getInstance().updatePlayerList(); ChromaBot.getInstance().updatePlayerList();
}); });
} }
@ -80,7 +80,7 @@ class MCListener implements Listener {
ChromaBot.getInstance()::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); MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
} }
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
@ -92,7 +92,7 @@ class MCListener implements Listener {
@EventHandler(priority = EventPriority.LOW) @EventHandler(priority = EventPriority.LOW)
public void onPlayerDeath(PlayerDeathEvent e) { public void onPlayerDeath(PlayerDeathEvent e) {
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage()), e.getEntity(), ChannelconBroadcast.DEATH, true); MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage()), e.getEntity(), ChannelconBroadcast.DEATH, true).subscribe();
} }
@EventHandler @EventHandler
@ -102,7 +102,7 @@ class MCListener implements Listener {
return; return;
final String msg = base.getDisplayName() final String msg = base.getDisplayName()
+ " is " + (e.getValue() ? "now" : "no longer") + " AFK."; + " is " + (e.getValue() ? "now" : "no longer") + " AFK.";
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false); MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false).subscribe();
} }
private ConfigData<Mono<Role>> muteRole() { private ConfigData<Mono<Role>> muteRole() {
@ -137,12 +137,12 @@ class MCListener implements Listener {
@EventHandler @EventHandler
public void onChatSystemMessage(TBMCSystemChatEvent event) { public void onChatSystemMessage(TBMCSystemChatEvent event) {
MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage()), event); MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage()), event).subscribe();
} }
@EventHandler @EventHandler
public void onBroadcastMessage(BroadcastMessageEvent event) { public void onBroadcastMessage(BroadcastMessageEvent event) {
MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false); MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false).subscribe();
} }
@EventHandler @EventHandler
@ -151,8 +151,8 @@ class MCListener implements Listener {
: event.getSender().getName(); : event.getSender().getName();
//Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
DiscordPlugin.mainServer.getEmojis().filter(e -> "YEEHAW".equals(e.getName())) DiscordPlugin.mainServer.getEmojis().filter(e -> "YEEHAW".equals(e.getName()))
.take(1).singleOrEmpty().map(Optional::of).defaultIfEmpty(Optional.empty()).subscribe(yeehaw -> .take(1).singleOrEmpty().map(Optional::of).defaultIfEmpty(Optional.empty()).flatMap(yeehaw ->
MCChatUtils.forAllMCChat(MCChatUtils.send(name + (yeehaw.map(guildEmoji -> " <:YEEHAW:" + guildEmoji.getId().asString() + ">s").orElse(" YEEHAWs"))))); MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + (yeehaw.map(guildEmoji -> " <:YEEHAW:" + guildEmoji.getId().asString() + ">s").orElse(" YEEHAWs"))))).subscribe();
} }
@EventHandler @EventHandler

View file

@ -1,12 +1,13 @@
package buttondevteam.discordplugin.mcchat; package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel;
import buttondevteam.discordplugin.ChannelconBroadcast;
import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.playerfaker.ServerWatcher; import buttondevteam.discordplugin.playerfaker.ServerWatcher;
import buttondevteam.discordplugin.playerfaker.perm.LPInjector; import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
@ -15,10 +16,11 @@ import buttondevteam.lib.architecture.ReadOnlyConfigData;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import discord4j.common.util.Snowflake; import discord4j.common.util.Snowflake;
import discord4j.core.object.entity.channel.MessageChannel; import discord4j.core.object.entity.channel.MessageChannel;
import discord4j.rest.util.Color;
import lombok.Getter; import lombok.Getter;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.entity.Player;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.ArrayList; import java.util.ArrayList;
@ -30,9 +32,11 @@ import java.util.stream.Collectors;
* Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM. * Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM.
*/ */
public class MinecraftChatModule extends Component<DiscordPlugin> { public class MinecraftChatModule extends Component<DiscordPlugin> {
public static DPState state = DPState.RUNNING;
private @Getter MCChatListener listener; private @Getter MCChatListener listener;
private ServerWatcher serverWatcher; ServerWatcher serverWatcher;
private LPInjector lpInjector; private LPInjector lpInjector;
boolean disabling = false;
/** /**
* A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command! * A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command!
@ -123,6 +127,11 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
*/ */
private final ConfigData<Boolean> addFakePlayersToBukkit = getConfig().getData("addFakePlayersToBukkit", true); private final ConfigData<Boolean> addFakePlayersToBukkit = getConfig().getData("addFakePlayersToBukkit", true);
/**
* Set by the component to report crashes.
*/
private final ConfigData<Boolean> serverUp = getConfig().getData("serverUp", false);
@Override @Override
protected void enable() { protected void enable() {
if (DPUtils.disableIfConfigErrorRes(this, chatChannel(), chatChannelMono())) if (DPUtils.disableIfConfigErrorRes(this, chatChannel(), chatChannelMono()))
@ -158,7 +167,7 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
try { try {
if (lpInjector == null) if (lpInjector == null)
lpInjector = new LPInjector(MainPlugin.Instance); lpInjector = new LPInjector(DiscordPlugin.plugin);
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e, this); TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e, this);
} catch (NoClassDefFoundError e) { } catch (NoClassDefFoundError e) {
@ -170,23 +179,68 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
try { try {
serverWatcher = new ServerWatcher(); serverWatcher = new ServerWatcher();
serverWatcher.enableDisable(true); serverWatcher.enableDisable(true);
log("Finished hooking into the server");
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Failed to hack the server (object)!", e, this); TBMCCoreAPI.SendException("Failed to hack the server (object)!", e, this);
} }
} }
if (state == DPState.RESTARTING_PLUGIN) { //These will only execute if the chat is enabled
sendStateMessage(Color.CYAN, "Discord plugin restarted - chat connected."); //Really important to note the chat, hmm
state = DPState.RUNNING;
} else if (state == DPState.DISABLED_MCCHAT) {
sendStateMessage(Color.CYAN, "Minecraft chat enabled - chat connected.");
state = DPState.RUNNING;
} else if (serverUp.get()) {
sendStateMessage(Color.YELLOW, "Server started after a crash - chat connected.");
val thr = new Throwable("The server shut down unexpectedly. See the log of the previous run for more details.");
thr.setStackTrace(new StackTraceElement[0]);
TBMCCoreAPI.SendException("The server crashed!", thr, this);
} else
sendStateMessage(Color.GREEN, "Server started - chat connected.");
serverUp.set(true);
} }
@Override @Override
protected void disable() { protected void disable() {
disabling = true;
if (state == DPState.RESTARTING_PLUGIN) //These will only execute if the chat is enabled
sendStateMessage(Color.ORANGE, "Discord plugin restarting");
else if (state == DPState.RUNNING) {
sendStateMessage(Color.ORANGE, "Minecraft chat disabled");
state = DPState.DISABLED_MCCHAT;
} else {
String kickmsg = Bukkit.getOnlinePlayers().size() > 0
? (DPUtils
.sanitizeString(Bukkit.getOnlinePlayers().stream()
.map(Player::getDisplayName).collect(Collectors.joining(", ")))
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
+ "thrown out") //TODO: Make configurable
: "";
if (state == DPState.RESTARTING_SERVER)
sendStateMessage(Color.ORANGE, "Server restarting", kickmsg);
else if (state == DPState.STOPPING_SERVER)
sendStateMessage(Color.RED, "Server stopping", kickmsg);
else
sendStateMessage(Color.GRAY, "Unknown state, please report.");
} //If 'restart' is disabled then this isn't shown even if joinleave is enabled
serverUp.set(false); //Disable even if just the component is disabled because that way it won't falsely report crashes
try { //If it's not enabled it won't do anything try { //If it's not enabled it won't do anything
if (serverWatcher != null) if (serverWatcher != null) {
serverWatcher.enableDisable(false); serverWatcher.enableDisable(false);
} catch (Exception e) { log("Finished unhooking the server");
}
} catch (
Exception e) {
TBMCCoreAPI.SendException("Failed to restore the server object!", e, this); TBMCCoreAPI.SendException("Failed to restore the server object!", e, this);
} }
val chcons = MCChatCustom.getCustomChats(); val chcons = MCChatCustom.getCustomChats();
val chconsc = getConfig().getConfig().createSection("chcons"); val chconsc = getConfig().getConfig().createSection("chcons");
for (val chcon : chcons) { for (
val chcon : chcons) {
val chconc = chconsc.createSection(chcon.channel.getId().asString()); val chconc = chconsc.createSection(chcon.channel.getId().asString());
chconc.set("mcchid", chcon.mcchannel.ID); chconc.set("mcchid", chcon.mcchannel.ID);
chconc.set("chid", chcon.channel.getId().asLong()); chconc.set("chid", chcon.channel.getId().asLong());
@ -197,11 +251,23 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
chconc.set("toggles", chcon.toggles); chconc.set("toggles", chcon.toggles);
chconc.set("brtoggles", chcon.brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::getName).collect(Collectors.toList())); chconc.set("brtoggles", chcon.brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::getName).collect(Collectors.toList()));
} }
MCChatListener.stop(true); listener.stop(true);
disabling = false;
} }
@Override /**
protected void unregister(JavaPlugin plugin) { * It will block to make sure all messages are sent
lpInjector = null; //Plugin restart, events need to be registered again */
private void sendStateMessage(Color color, String message) {
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color)
.setTitle(message))), ChannelconBroadcast.RESTART, false).block();
}
/**
* It will block to make sure all messages are sent
*/
private void sendStateMessage(Color color, String message, String extra) {
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color)
.setTitle(message).setDescription(extra))), ChannelconBroadcast.RESTART, false).block();
} }
} }

View file

@ -7,6 +7,8 @@ import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.commands.ConnectCommand; import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.discordplugin.commands.VersionCommand; import buttondevteam.discordplugin.commands.VersionCommand;
import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC; import buttondevteam.lib.chat.ICommand2MC;
@ -58,7 +60,7 @@ public class DiscordMCCommand extends ICommand2MC {
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = { @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = {
"Reload Discord plugin", "Reload Discord plugin",
"Reloads the config. To apply some changes, you may need to also run /discord reset." "Reloads the config. To apply some changes, you may need to also run /discord restart."
}) })
public void reload(CommandSender sender) { public void reload(CommandSender sender) {
if (DiscordPlugin.plugin.tryReloadConfig()) if (DiscordPlugin.plugin.tryReloadConfig())
@ -67,26 +69,24 @@ public class DiscordMCCommand extends ICommand2MC {
sender.sendMessage("§cFailed to reload config."); sender.sendMessage("§cFailed to reload config.");
} }
public static boolean resetting = false;
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = { @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = {
"Reset ChromaBot", // "Restart the plugin", //
"This command disables and then enables the plugin." // "This command disables and then enables the plugin." //
}) })
public void reset(CommandSender sender) { public void restart(CommandSender sender) {
Runnable task = () -> { Runnable task = () -> {
if (!DiscordPlugin.plugin.tryReloadConfig()) { if (!DiscordPlugin.plugin.tryReloadConfig()) {
sender.sendMessage("§cFailed to reload config so not resetting. Check the console."); sender.sendMessage("§cFailed to reload config so not restarting. Check the console.");
return; return;
} }
resetting = true; //Turned off after sending enable message (ReadyEvent) MinecraftChatModule.state = DPState.RESTARTING_PLUGIN; //Reset in MinecraftChatModule
sender.sendMessage("§bDisabling DiscordPlugin..."); sender.sendMessage("§bDisabling DiscordPlugin...");
Bukkit.getPluginManager().disablePlugin(DiscordPlugin.plugin); Bukkit.getPluginManager().disablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bEnabling DiscordPlugin..."); sender.sendMessage("§bEnabling DiscordPlugin...");
Bukkit.getPluginManager().enablePlugin(DiscordPlugin.plugin); Bukkit.getPluginManager().enablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bReset finished!"); sender.sendMessage("§bRestart finished!");
}; };
if (!Bukkit.getName().equals("Paper")) { if (!Bukkit.getName().equals("Paper")) {
getPlugin().getLogger().warning("Async plugin events are not supported by the server, running on main thread"); getPlugin().getLogger().warning("Async plugin events are not supported by the server, running on main thread");
@ -116,9 +116,8 @@ public class DiscordMCCommand extends ICommand2MC {
} }
DiscordPlugin.mainServer.getInvites().limitRequest(1) DiscordPlugin.mainServer.getInvites().limitRequest(1)
.switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("§cNo invites found for the server."))) .switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("§cNo invites found for the server.")))
.subscribe(inv -> { .subscribe(inv -> sender.sendMessage("§bInvite link: https://discord.gg/" + inv.getCode()),
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."));
}, e -> sender.sendMessage("§cThe invite link is not set and the bot has no permission to get it."));
} }
@Override @Override

View file

@ -15,7 +15,7 @@ import java.util.*;
public class ServerWatcher { public class ServerWatcher {
private List<Player> playerList; private List<Player> playerList;
private final List<Player> fakePlayers = new ArrayList<>(); public final List<Player> fakePlayers = new ArrayList<>();
private Server origServer; private Server origServer;
@IgnoreForBinding @IgnoreForBinding
@ -43,12 +43,12 @@ public class ServerWatcher {
.filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null); .filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null);
} }
break; break;
case "getOnlinePlayers": /*case "getOnlinePlayers":
if (playerList == null) { if (playerList == null) {
@SuppressWarnings("unchecked") var list = (List<Player>) method.invoke(origServer, invocation.getArguments()); @SuppressWarnings("unchecked") var list = (List<Player>) method.invoke(origServer, invocation.getArguments());
playerList = new AppendListView<>(list, fakePlayers); playerList = new AppendListView<>(list, fakePlayers);
} } - Your scientists were so preoccupied with whether or not they could, they didnt stop to think if they should.
return playerList; return playerList;*/
case "createProfile": //Paper's method, casts the player to a CraftPlayer case "createProfile": //Paper's method, casts the player to a CraftPlayer
if (pc == 2) { if (pc == 2) {
UUID uuid = invocation.getArgument(0); UUID uuid = invocation.getArgument(0);

View file

@ -1,7 +1,7 @@
package buttondevteam.discordplugin.playerfaker.perm; package buttondevteam.discordplugin.playerfaker.perm;
import buttondevteam.core.MainPlugin;
import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.mcchat.MCChatUtils; 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;
@ -42,7 +42,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
private final Method setOldPermissible; private final Method setOldPermissible;
private final Method getOldPermissible; private final Method getOldPermissible;
public LPInjector(MainPlugin mp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException { public LPInjector(DiscordPlugin dp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
LPBukkitBootstrap bs = (LPBukkitBootstrap) Bukkit.getPluginManager().getPlugin("LuckPerms"); LPBukkitBootstrap bs = (LPBukkitBootstrap) Bukkit.getPluginManager().getPlugin("LuckPerms");
Field field = LPBukkitBootstrap.class.getDeclaredField("plugin"); Field field = LPBukkitBootstrap.class.getDeclaredField("plugin");
field.setAccessible(true); field.setAccessible(true);
@ -77,7 +77,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
getOldPermissible = LuckPermsPermissible.class.getDeclaredMethod("getOldPermissible"); getOldPermissible = LuckPermsPermissible.class.getDeclaredMethod("getOldPermissible");
getOldPermissible.setAccessible(true); getOldPermissible.setAccessible(true);
TBMCCoreAPI.RegisterEventsForExceptions(this, mp); TBMCCoreAPI.RegisterEventsForExceptions(this, dp);
} }
@ -146,7 +146,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
t.printStackTrace(); t.printStackTrace();
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_SETUP_ERROR.asString(plugin.getLocaleManager())); e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_SETUP_ERROR.asString(plugin.getLocaleManager()));
return; //return;
} }
//this.plugin.getContextManager().signalContextUpdate(player); //this.plugin.getContextManager().signalContextUpdate(player);
@ -167,7 +167,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> { this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> {
// Remove the custom permissible // Remove the custom permissible
try { try {
uninject(player, true); uninject(player);
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
@ -207,7 +207,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
player.setPerm(newPermissible); player.setPerm(newPermissible);
} }
private void uninject(DiscordConnectedPlayer player, boolean dummy) throws Exception { private void uninject(DiscordConnectedPlayer player) throws Exception {
// gets the players current permissible. // gets the players current permissible.
PermissibleBase permissible = player.getPerm(); PermissibleBase permissible = player.getPerm();
@ -223,18 +223,9 @@ public final class LPInjector implements Listener { //Disable login event for Lu
((AtomicBoolean) getActive.invoke(lpPermissible)).set(false); ((AtomicBoolean) getActive.invoke(lpPermissible)).set(false);
// handle the replacement permissible. // handle the replacement permissible.
if (dummy) {
// just inject a dummy class. this is used when we know the player is about to quit the server. // just inject a dummy class. this is used when we know the player is about to quit the server.
player.setPerm(DummyPermissibleBase.INSTANCE); player.setPerm(DummyPermissibleBase.INSTANCE);
} else {
PermissibleBase newPb = (PermissibleBase) getOldPermissible.invoke(lpPermissible);
if (newPb == null) {
newPb = new PermissibleBase(player);
}
player.setPerm(newPb);
}
} }
} }
} }

View file

@ -0,0 +1,24 @@
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
}