Attempts at mocking the server, fixes
This commit is contained in:
parent
d784d8b1e2
commit
666f05ff12
10 changed files with 111 additions and 98 deletions
40
pom.xml
40
pom.xml
|
@ -21,21 +21,8 @@
|
|||
<testSourceDirectory>target/generated-test-sources/delombok</testSourceDirectory> -->
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src</directory>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>*.properties</include>
|
||||
<include>*.yml</include>
|
||||
<include>*.csv</include>
|
||||
<include>*.txt</include>
|
||||
</includes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<finalName>Chroma-Discord</finalName>
|
||||
|
@ -68,28 +55,6 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>target</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- <plugin> <groupId>org.projectlombok</groupId> <artifactId>lombok-maven-plugin</artifactId>
|
||||
<version>1.16.16.0</version> <executions> <execution> <id>delombok</id> <phase>generate-sources</phase>
|
||||
<goals> <goal>delombok</goal> </goals> <configuration> <addOutputDirectory>false</addOutputDirectory>
|
||||
|
@ -241,6 +206,11 @@
|
|||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<version>3.5.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -7,7 +7,6 @@ import buttondevteam.discordplugin.exceptions.ExceptionListenerModule;
|
|||
import buttondevteam.discordplugin.fun.FunModule;
|
||||
import buttondevteam.discordplugin.listeners.CommonListeners;
|
||||
import buttondevteam.discordplugin.listeners.MCListener;
|
||||
import buttondevteam.discordplugin.mcchat.MCChatPrivate;
|
||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
|
||||
import buttondevteam.discordplugin.mccommands.DiscordMCCommand;
|
||||
|
@ -260,7 +259,6 @@ public class DiscordPlugin extends ButtonPlugin {
|
|||
public void pluginDisable() {
|
||||
Timings timings = new Timings();
|
||||
timings.printElapsed("Actual disable start (logout)");
|
||||
MCChatPrivate.logoutAll();
|
||||
timings.printElapsed("Config setup");
|
||||
getConfig().set("serverup", false);
|
||||
if (ChromaBot.getInstance() == null) return; //Failed to load
|
||||
|
|
|
@ -3,24 +3,17 @@ package buttondevteam.discordplugin.broadcaster;
|
|||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import lombok.val;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerListWatcher {
|
||||
|
@ -28,45 +21,6 @@ public class PlayerListWatcher {
|
|||
private static Object mock;
|
||||
private static MethodHandle fHandle; //Handle for PlayerList.f(EntityPlayer) - Only needed for 1.16
|
||||
|
||||
/*public PlayerListWatcher(DedicatedServer minecraftserver) {
|
||||
super(minecraftserver); // <-- Does some init stuff and calls Bukkit.setServer() so we have to use Objenesis
|
||||
}
|
||||
|
||||
public void sendAll(Packet<?> packet) {
|
||||
plist.sendAll(packet);
|
||||
try { // Some messages get sent by directly constructing a packet
|
||||
if (packet instanceof PacketPlayOutChat) {
|
||||
Field msgf = PacketPlayOutChat.class.getDeclaredField("a");
|
||||
msgf.setAccessible(true);
|
||||
MCChatUtils.forAllMCChat(MCChatUtils.send(((IChatBaseComponent) msgf.get(packet)).toPlainText()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("Failed to broadcast message sent to all players - hacking failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(IChatBaseComponent ichatbasecomponent, boolean flag) { // Needed so it calls the overridden method
|
||||
plist.getServer().sendMessage(ichatbasecomponent);
|
||||
ChatMessageType chatmessagetype = flag ? ChatMessageType.SYSTEM : ChatMessageType.CHAT;
|
||||
|
||||
// CraftBukkit start - we run this through our processor first so we can get web links etc
|
||||
this.sendAll(new PacketPlayOutChat(CraftChatMessage.fixComponent(ichatbasecomponent), chatmessagetype));
|
||||
// CraftBukkit end
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(IChatBaseComponent ichatbasecomponent) { // Needed so it calls the overriden method
|
||||
this.sendMessage(ichatbasecomponent, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(IChatBaseComponent[] iChatBaseComponents) { // Needed so it calls the overridden method
|
||||
for (IChatBaseComponent component : iChatBaseComponents) {
|
||||
sendMessage(component, true);
|
||||
}
|
||||
}*/
|
||||
|
||||
static boolean hookUpDown(boolean up) throws Exception {
|
||||
val csc = Bukkit.getServer().getClass();
|
||||
Field conf = csc.getDeclaredField("console");
|
||||
|
|
|
@ -186,6 +186,7 @@ public class MCChatListener implements Listener {
|
|||
* @param wait Wait 5 seconds for the threads to stop
|
||||
*/
|
||||
public static void stop(boolean wait) {
|
||||
MCChatPrivate.logoutAll();
|
||||
if (sendthread != null) sendthread.interrupt();
|
||||
if (recthread != null) recthread.interrupt();
|
||||
try {
|
||||
|
@ -203,7 +204,6 @@ public class MCChatListener implements Listener {
|
|||
MCChatPrivate.lastmsgPerUser.clear();
|
||||
MCChatCustom.lastmsgCustom.clear();
|
||||
MCChatUtils.lastmsgfromd.clear();
|
||||
MCChatUtils.ConnectedSenders.clear();
|
||||
MCChatUtils.UnconnectedSenders.clear();
|
||||
recthread = sendthread = null;
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
@ -28,11 +28,13 @@ public class MCChatPrivate {
|
|||
if (start) {
|
||||
val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID(), op.getName(), mcm);
|
||||
MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender);
|
||||
MCChatUtils.LoggedInPlayers.put(mcp.getUUID(), sender);
|
||||
if (p == null) // Player is offline - If the player is online, that takes precedence
|
||||
MCChatUtils.callLoginEvents(sender);
|
||||
} else {
|
||||
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
|
||||
assert sender != null;
|
||||
MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId());
|
||||
if (p == null // Player is offline - If the player is online, that takes precedence
|
||||
&& sender.isLoggedIn()) //Don't call the quit event if login failed
|
||||
MCChatUtils.callLogoutEvent(sender, true);
|
||||
|
|
|
@ -28,10 +28,7 @@ import reactor.core.publisher.Mono;
|
|||
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -49,8 +46,8 @@ public class MCChatUtils {
|
|||
* May contain P<DiscordID> as key for public chat
|
||||
*/
|
||||
public static final HashMap<String, HashMap<Snowflake, DiscordPlayerSender>> OnlineSenders = new HashMap<>();
|
||||
static @Nullable
|
||||
LastMsgData lastmsgdata;
|
||||
public static final HashMap<UUID, DiscordConnectedPlayer> LoggedInPlayers = new HashMap<>();
|
||||
static @Nullable LastMsgData lastmsgdata;
|
||||
static LongObjectHashMap<Message> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
|
||||
private static MinecraftChatModule module;
|
||||
private static final HashMap<Class<? extends Event>, HashSet<String>> staticExcludedPlugins = new HashMap<>();
|
||||
|
@ -283,7 +280,6 @@ public class MCChatUtils {
|
|||
* @param only Flips the operation and <b>includes</b> the listed plugins
|
||||
* @param plugins The plugins to exclude. Not case sensitive.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static void callEventExcluding(Event event, boolean only, String... plugins) { // Copied from Spigot-API and modified a bit
|
||||
if (event.isAsynchronous()) {
|
||||
if (Thread.holdsLock(Bukkit.getPluginManager())) {
|
||||
|
|
|
@ -40,9 +40,9 @@ class MCListener implements Listener {
|
|||
return;
|
||||
if (e.getPlayer() instanceof DiscordConnectedPlayer)
|
||||
return;
|
||||
MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders
|
||||
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
|
||||
.ifPresent(dcp -> MCChatUtils.callLogoutEvent(dcp, false));
|
||||
var dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId());
|
||||
if (dcp != null)
|
||||
MCChatUtils.callLogoutEvent(dcp, false);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
|
@ -75,9 +75,7 @@ class MCListener implements Listener {
|
|||
MCChatUtils.OnlineSenders.entrySet()
|
||||
.removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId())));
|
||||
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin,
|
||||
() -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
|
||||
.filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
|
||||
.ifPresent(MCChatUtils::callLoginEvents));
|
||||
() -> Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId())).ifPresent(MCChatUtils::callLoginEvents));
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
|
||||
ChromaBot.getInstance()::updatePlayerList, 5);
|
||||
final String message = e.getQuitMessage();
|
||||
|
|
|
@ -5,6 +5,7 @@ import buttondevteam.core.component.channel.Channel;
|
|||
import buttondevteam.discordplugin.DPUtils;
|
||||
import buttondevteam.discordplugin.DiscordConnectedPlayer;
|
||||
import buttondevteam.discordplugin.DiscordPlugin;
|
||||
import buttondevteam.discordplugin.playerfaker.ServerWatcher;
|
||||
import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
|
||||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||
|
@ -29,6 +30,7 @@ import java.util.stream.Collectors;
|
|||
*/
|
||||
public class MinecraftChatModule extends Component<DiscordPlugin> {
|
||||
private @Getter MCChatListener listener;
|
||||
private ServerWatcher serverWatcher;
|
||||
|
||||
/**
|
||||
* 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!
|
||||
|
@ -113,6 +115,11 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
|
|||
return getConfig().getData("enableVanillaCommands", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether players logged on from Discord should be recognised by other plugins. Some plugins might break if it's turned off.
|
||||
*/
|
||||
public final ConfigData<Boolean> addFakePlayersToBukkit = getConfig().getData("addFakePlayersToBukkit", true);
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
if (DPUtils.disableIfConfigErrorRes(this, chatChannel(), chatChannelMono()))
|
||||
|
@ -156,10 +163,22 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
|
|||
log("No LuckPerms, not injecting");
|
||||
//e.printStackTrace();
|
||||
}
|
||||
|
||||
try { //TODO: Config ^^
|
||||
serverWatcher = new ServerWatcher();
|
||||
serverWatcher.enableDisable(true);
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("Failed to hack the server (object)!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
try {
|
||||
serverWatcher.enableDisable(false);
|
||||
} catch (Exception e) {
|
||||
TBMCCoreAPI.SendException("Failed to restore the server object!", e);
|
||||
}
|
||||
val chcons = MCChatCustom.getCustomChats();
|
||||
val chconsc = getConfig().getConfig().createSection("chcons");
|
||||
for (val chcon : chcons) {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package buttondevteam.discordplugin.playerfaker;
|
||||
|
||||
import buttondevteam.discordplugin.mcchat.MCChatUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ServerWatcher {
|
||||
private List<Player> playerList;
|
||||
private final List<Player> fakePlayers = new ArrayList<>();
|
||||
private Server origServer;
|
||||
|
||||
public void enableDisable(boolean enable) throws Exception {
|
||||
var serverField = Bukkit.class.getDeclaredField("server");
|
||||
serverField.setAccessible(true);
|
||||
if (enable) {
|
||||
var serverClass = Bukkit.getServer().getClass();
|
||||
var mock = Mockito.mock(serverClass, Mockito.withSettings()
|
||||
.stubOnly().defaultAnswer(invocation -> {
|
||||
var method = invocation.getMethod();
|
||||
int pc = method.getParameterCount();
|
||||
Player player = null;
|
||||
switch (method.getName()) {
|
||||
case "getPlayer":
|
||||
if (pc == 1 && method.getParameterTypes()[0] == UUID.class)
|
||||
player = MCChatUtils.LoggedInPlayers.get(invocation.<UUID>getArgument(0));
|
||||
break;
|
||||
case "getPlayerExact":
|
||||
if (pc == 1) {
|
||||
final String argument = invocation.getArgument(0);
|
||||
player = MCChatUtils.LoggedInPlayers.values().stream()
|
||||
.filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null);
|
||||
}
|
||||
break;
|
||||
case "getOnlinePlayers":
|
||||
if (playerList == null) {
|
||||
@SuppressWarnings("unchecked") var list = (List<Player>) invocation.callRealMethod();
|
||||
playerList = new AppendListView<>(list, fakePlayers);
|
||||
}
|
||||
return playerList;
|
||||
}
|
||||
if (player != null)
|
||||
return player;
|
||||
return invocation.callRealMethod();
|
||||
}));
|
||||
var originalServer = serverField.get(null);
|
||||
for (var field : serverClass.getFields()) //Copy public fields, private fields aren't accessible directly anyways
|
||||
field.set(mock, field.get(originalServer));
|
||||
serverField.set(null, mock);
|
||||
origServer = (Server) originalServer;
|
||||
} else if (origServer != null)
|
||||
serverField.set(null, origServer);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class AppendListView<T> extends AbstractSequentialList<T> {
|
||||
private final List<T> originalList;
|
||||
private final List<T> additionalList;
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int i) {
|
||||
int os = originalList.size();
|
||||
return i < os ? originalList.listIterator(i) : additionalList.listIterator(i - os);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return originalList.size() + additionalList.size();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
mock-maker-inline
|
Loading…
Reference in a new issue