From bf538d9bd0f689ada75b053a78c07480af109867 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 25 May 2019 01:48:06 +0200 Subject: [PATCH] LP injecting, fake player fixes #96 LuckPerms Added support for excluding plugins from certain events from code Also might have fixed some message timeouts by relocating netty --- pom.xml | 422 ++++--- .../discordplugin/DiscordConnectedPlayer.java | 6 +- .../discordplugin/mcchat/MCChatPrivate.java | 17 +- .../discordplugin/mcchat/MCChatUtils.java | 92 +- .../discordplugin/mcchat/MCListener.java | 25 +- .../mcchat/MinecraftChatModule.java | 8 + .../playerfaker/DiscordFakePlayer.java | 1091 +++++++++-------- .../playerfaker/perm/LPInjector.java | 240 ++++ 8 files changed, 1114 insertions(+), 787 deletions(-) create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java diff --git a/pom.xml b/pom.xml index fdc1d53..9fed5ae 100755 --- a/pom.xml +++ b/pom.xml @@ -1,45 +1,45 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - com.github.TBMCPlugins - DiscordPlugin - master-SNAPSHOT - jar + com.github.TBMCPlugins + DiscordPlugin + master-SNAPSHOT + jar - DiscordPlugin - http://maven.apache.org + DiscordPlugin + http://maven.apache.org - - - src/main/java - - - src - - **/*.java - - - - src/main/resources - - *.properties - *.yml - *.csv - *.txt - - true - - - DiscordPlugin - - - maven-compiler-plugin - 3.6.2 - - 1.8 - 1.8 + + + src/main/java + + + src + + **/*.java + + + + src/main/resources + + *.properties + *.yml + *.csv + *.txt + + true + + + DiscordPlugin + + + maven-compiler-plugin + 3.6.2 + + 1.8 + 1.8 - - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.2 - - - package - - shade - - - - - org.spigotmc:spigot-api - com.github.TBMCPlugins.ButtonCore:ButtonCore - net.ess3:Essentials - - - true - - - - - - org.apache.maven.plugins - maven-resources-plugin - 3.0.1 - - - copy - compile - - copy-resources - - - target - - - resources - - - - - - - - - maven-surefire-plugin + + + + org.apache.maven.plugins + maven-shade-plugin 2.4.2 - - false - - - - - + + + package + + shade + + + + + org.spigotmc:spigot-api + com.github.TBMCPlugins.ButtonCore:ButtonCore + net.ess3:Essentials + + + true + + + io.netty + buttondevteam.discordplugin.io.netty + + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.1 + + + copy + compile + + copy-resources + + + target + + + resources + + + + + + + + + maven-surefire-plugin + 2.4.2 + + false + + + + + - - UTF-8 + + UTF-8 master - + - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - jcenter - http://jcenter.bintray.com - - - jitpack.io - https://jitpack.io - + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + jcenter + http://jcenter.bintray.com + + + jitpack.io + https://jitpack.io + - - Essentials - http://repo.ess3.net/content/repositories/essrel/ - - - projectlombok.org - http://projectlombok.org/mavenrepo - + + Essentials + http://repo.ess3.net/content/repositories/essrel/ + + + projectlombok.org + http://projectlombok.org/mavenrepo + - + - - - junit - junit - 3.8.1 - test - - - org.spigotmc - spigot-api - 1.12-R0.1-SNAPSHOT - provided - - - org.spigotmc - spigot - 1.12.2-R0.1-SNAPSHOT - provided - - + + + junit + junit + 3.8.1 + test + + + org.spigotmc + spigot-api + 1.12-R0.1-SNAPSHOT + provided + + + org.spigotmc + spigot + 1.12.2-R0.1-SNAPSHOT + provided + + com.discord4j discord4j-core 3.0.5 - - - org.slf4j - slf4j-jdk14 - 1.7.21 - - - com.github.TBMCPlugins.ButtonCore - ButtonCore - ${branch}-SNAPSHOT - provided - - - com.github.milkbowl - VaultAPI - master-SNAPSHOT - provided - - - net.ess3 - Essentials - 2.13.1 + + + org.slf4j + slf4j-jdk14 + 1.7.21 + + + com.github.TBMCPlugins.ButtonCore + ButtonCore + ${branch}-SNAPSHOT provided - - - - org.projectlombok - lombok - 1.16.16 - provided - + + + com.github.milkbowl + VaultAPI + master-SNAPSHOT + provided + + + net.ess3 + Essentials + 2.13.1 + provided + + + + org.projectlombok + lombok + 1.16.16 + provided + - - - org.objenesis - objenesis - 2.6 - test - - - com.vdurmont - emoji-java - 4.0.0 - + + + org.objenesis + objenesis + 2.6 + test + + + com.vdurmont + emoji-java + 4.0.0 + - + + com.github.lucko + LuckPerms + v4.4 + provided + + - - - ci - - - env.TRAVIS_BRANCH - - - - - ${env.TRAVIS_BRANCH} - - - + + + ci + + + env.TRAVIS_BRANCH + + + + + ${env.TRAVIS_BRANCH} + + + diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java index de96b18..6e30a53 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java @@ -6,15 +6,19 @@ import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.User; import lombok.Getter; +import lombok.Setter; import java.util.UUID; public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer { private static int nextEntityId = 10000; private @Getter VanillaCommandListener vanillaCmdListener; + @Getter + @Setter + private boolean loggedIn = false; public DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, MinecraftChatModule module) { - super(user, channel, nextEntityId++, uuid, mcname ,module); + super(user, channel, nextEntityId++, uuid, mcname, module); vanillaCmdListener = new VanillaCommandListener<>(this); } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java index e43bf74..344ecf5 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java @@ -3,15 +3,12 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.core.ComponentManager; import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.lib.player.TBMCPlayer; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.PrivateChannel; import discord4j.core.object.entity.User; import lombok.val; import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.bukkit.event.player.PlayerQuitEvent; import java.util.ArrayList; @@ -32,11 +29,14 @@ public class MCChatPrivate { val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName(), mcm); MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender); if (p == null)// Player is offline - If the player is online, that takes precedence - MCListener.callLoginEvents(sender); + MCChatUtils.callLoginEvents(sender); } else { val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user); - if (p == null)// Player is offline - If the player is online, that takes precedence - callEventSync(new PlayerQuitEvent(sender, "")); + assert sender != null; + if (p == null // Player is offline - If the player is online, that takes precedence + && sender.isLoggedIn()) //Don't call the quit event if login failed + MCChatUtils.callLogoutEvent(sender, true); + sender.setLoggedIn(false); } } // ---- PermissionsEx warning is normal on logout ---- if (!start) @@ -60,11 +60,8 @@ public class MCChatPrivate { for (val entry : MCChatUtils.ConnectedSenders.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 - MCChatUtils.callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync + MCChatUtils.callLogoutEvent(valueEntry.getValue(), false); //This is sync MCChatUtils.ConnectedSenders.clear(); } - private static void callEventSync(Event event) { - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> MCChatUtils.callEventExcludingSome(event)); - } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java index bd516c2..cbccc0d 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java @@ -5,6 +5,7 @@ import buttondevteam.discordplugin.*; import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; +import com.google.common.collect.Sets; import discord4j.core.object.entity.*; import discord4j.core.object.util.Snowflake; import io.netty.util.collection.LongObjectHashMap; @@ -15,14 +16,20 @@ import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.AuthorNagException; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; 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.function.Consumer; import java.util.function.Supplier; @@ -43,6 +50,7 @@ public class MCChatUtils { static @Nullable LastMsgData lastmsgdata; static LongObjectHashMap lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks private static MinecraftChatModule module; + private static HashMap, HashSet> staticExcludedPlugins = new HashMap<>(); public static void updatePlayerList() { if (notEnabled()) return; @@ -74,19 +82,19 @@ public class MCChatUtils { if (s.length < 3) return; s[0] = Bukkit.getOnlinePlayers().size() + " player" + (Bukkit.getOnlinePlayers().size() != 1 ? "s" : "") - + " online"; + + " online"; s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream() - .map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", ")); + .map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", ")); ((TextChannel) lmd.channel).edit(tce -> tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe(); //Don't wait } public static T addSender(HashMap> senders, - User user, T sender) { + User user, T sender) { return addSender(senders, user.getId().asString(), sender); } public static T addSender(HashMap> senders, - String did, T sender) { + String did, T sender) { var map = senders.get(did); if (map == null) map = new HashMap<>(); @@ -96,7 +104,7 @@ public class MCChatUtils { } public static T getSender(HashMap> senders, - Snowflake channel, User user) { + Snowflake channel, User user) { var map = senders.get(user.getId().asString()); if (map != null) return map.get(channel); @@ -104,7 +112,7 @@ public class MCChatUtils { } public static T removeSender(HashMap> senders, - Snowflake channel, User user) { + Snowflake channel, User user) { var map = senders.get(user.getId().asString()); if (map != null) return map.remove(channel); @@ -195,11 +203,11 @@ public class MCChatUtils { static DiscordSenderBase getSender(Snowflake channel, final User author) { //noinspection OptionalGetWithoutIsPresent return Stream.>>of( // https://stackoverflow.com/a/28833677/2703239 - () -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null - () -> Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it - () -> Optional.ofNullable(getSender(UnconnectedSenders, channel, author)), // - () -> Optional.of(addSender(UnconnectedSenders, author, - new DiscordSender(author, (MessageChannel) DiscordPlugin.dc.getChannelById(channel).block())))).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get(); + () -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null + () -> Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it + () -> Optional.ofNullable(getSender(UnconnectedSenders, channel, author)), // + () -> Optional.of(addSender(UnconnectedSenders, author, + new DiscordSender(author, (MessageChannel) DiscordPlugin.dc.getChannelById(channel).block())))).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get(); } /** @@ -213,7 +221,7 @@ public class MCChatUtils { System.out.println("Reset last message"); if (channel.getId().asLong() == module.chatChannel().get().asLong()) { (lastmsgdata == null ? lastmsgdata = new LastMsgData(module.chatChannelMono().block(), null) - : lastmsgdata).message = null; + : lastmsgdata).message = null; System.out.println("Reset done: public chat"); return; } // Don't set the whole object to null, the player and channel information should be preserved @@ -227,9 +235,23 @@ public class MCChatUtils { //If it gets here, it's sending a message to a non-chat channel } + public static void addStaticExcludedPlugin(Class event, String plugin) { + staticExcludedPlugins.compute(event, (e, hs) -> hs == null + ? Sets.newHashSet(plugin) + : (hs.add(plugin) ? hs : hs)); + } + public static void callEventExcludingSome(Event event) { if (notEnabled()) return; - callEventExcluding(event, false, module.excludedPlugins().get()); + val second = staticExcludedPlugins.get(event.getClass()); + String[] first = module.excludedPlugins().get(); + String[] both = second == null ? first + : Arrays.copyOf(first, first.length + second.size()); + int i = first.length; + if (second != null) + for (String plugin : second) + both[i++] = plugin; + callEventExcluding(event, false, both); } /** @@ -290,6 +312,50 @@ public class MCChatUtils { } } + /** + * Call it from an async thread. + */ + public static void callLoginEvents(DiscordConnectedPlayer dcp) { + Consumer> loginFail = kickMsg -> { + dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg.get()); + MCChatPrivate.privateMCChat(dcp.getChannel(), false, dcp.getUser(), dcp.getChromaUser()); + }; //Probably also happens if the user is banned or so + val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId()); + callEventExcludingSome(event); + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + loginFail.accept(event::getKickMessage); + return; + } + Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> { + val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress()); + callEventExcludingSome(ev); + if (ev.getResult() != PlayerLoginEvent.Result.ALLOWED) { + loginFail.accept(ev::getKickMessage); + return; + } + callEventExcludingSome(new PlayerJoinEvent(dcp, "")); + dcp.setLoggedIn(true); + }); + } + + /** + * Only calls the events if the player is actually logged in + * + * @param dcp The player + * @param needsSync Whether we're in an async thread + */ + public static void callLogoutEvent(DiscordConnectedPlayer dcp, boolean needsSync) { + if (!dcp.isLoggedIn()) return; + val event = new PlayerQuitEvent(dcp, ""); + if (needsSync) callEventSync(event); + else callEventExcludingSome(event); + dcp.setLoggedIn(false); + } + + static void callEventSync(Event event) { + Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event)); + } + @RequiredArgsConstructor public static class LastMsgData { public Message message; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java index 279cc04..fe68342 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java @@ -18,12 +18,12 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.*; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerLoginEvent.Result; import org.bukkit.event.server.BroadcastMessageEvent; import reactor.core.publisher.Mono; -import java.net.InetAddress; import java.util.Objects; @RequiredArgsConstructor @@ -38,7 +38,7 @@ class MCListener implements Listener { 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.callEventExcludingSome(new PlayerQuitEvent(dcp, ""))); + .ifPresent(dcp -> MCChatUtils.callLogoutEvent(dcp, false)); } @EventHandler(priority = EventPriority.LOWEST) @@ -70,30 +70,13 @@ class MCListener implements Listener { Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny() - .ifPresent(MCListener::callLoginEvents)); + .ifPresent(MCChatUtils::callLoginEvents)); Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, ChromaBot.getInstance()::updatePlayerList, 5); final String message = e.GetPlayer().PlayerName().get() + " left the game"; MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true); } - /** - * Call it from an async thread. - */ - public static void callLoginEvents(DiscordConnectedPlayer dcp) { - val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId()); - MCChatUtils.callEventExcludingSome(event); - if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) - return; - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> { - val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress()); - MCChatUtils.callEventExcludingSome(ev); - if (ev.getResult() != Result.ALLOWED) - return; - MCChatUtils.callEventExcludingSome(new PlayerJoinEvent(dcp, "")); - }); - } - @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerKick(PlayerKickEvent e) { /*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting") diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java index b4f8ec6..9e9bfd3 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java @@ -1,9 +1,11 @@ package buttondevteam.discordplugin.mcchat; +import buttondevteam.core.MainPlugin; import buttondevteam.core.component.channel.Channel; import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.discordplugin.playerfaker.perm.LPInjector; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.architecture.Component; @@ -106,6 +108,12 @@ public class MinecraftChatModule extends Component { }); } } + + try { + new LPInjector(MainPlugin.Instance); + } catch (Exception e) { + TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e); + } } @Override diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java index f197c84..6879155 100755 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java @@ -5,6 +5,7 @@ import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.User; import lombok.Getter; +import lombok.Setter; import lombok.experimental.Delegate; import org.bukkit.*; import org.bukkit.advancement.Advancement; @@ -17,6 +18,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.map.MapView; import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.ServerOperator; import org.bukkit.plugin.Plugin; import org.bukkit.scoreboard.Scoreboard; @@ -26,693 +28,706 @@ import java.util.*; @SuppressWarnings("deprecation") public class DiscordFakePlayer extends DiscordHumanEntity implements Player { protected DiscordFakePlayer(User user, MessageChannel channel, int entityId, UUID uuid, String mcname, MinecraftChatModule module) { - super(user, channel, entityId, uuid, module); - perm = new PermissibleBase(Bukkit.getOfflinePlayer(uuid)); - name = mcname; - } - - @Delegate - private PermissibleBase perm; - - private @Getter String name; - - @Override - public EntityType getType() { - return EntityType.PLAYER; - } - - @Override - public String getCustomName() { - return user.getUsername(); - } - - @Override - public void setCustomName(String name) { - } - - @Override - public boolean isConversing() { - - return false; - } - - @Override - public void acceptConversationInput(String input) { - } - - @Override - public boolean beginConversation(Conversation conversation) { - return false; - } - - @Override - public void abandonConversation(Conversation conversation) { - } - - @Override - public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { - } - - @Override - public boolean isOnline() { - return true;// Let's pretend - } - - @Override - public boolean isBanned() { - return false; - } - - @Override - public boolean isWhitelisted() { - return true; - } - - @Override - public void setWhitelisted(boolean value) { - } - - @Override - public Player getPlayer() { - return this; - } - - @Override - public long getFirstPlayed() { - return 0; - } - - @Override - public long getLastPlayed() { - return 0; - } - - @Override - public boolean hasPlayedBefore() { - return false; - } - - @Override - public Map serialize() { - return new HashMap<>(); - } - - @Override - public void sendPluginMessage(Plugin source, String channel, byte[] message) { - } - - @Override - public Set getListeningPluginChannels() { - return Collections.emptySet(); - } - - @Override - public String getDisplayName() { - return Objects.requireNonNull(user.asMember(DiscordPlugin.mainServer.getId()).block()).getDisplayName(); - } - - @Override - public void setDisplayName(String name) { - } - - @Override - public String getPlayerListName() { - return getName(); - } - - @Override - public void setPlayerListName(String name) { - } - - @Override - public void setCompassTarget(Location loc) { - } - - @Override - public Location getCompassTarget() { - return new Location(Bukkit.getWorlds().get(0), 0, 0, 0); - } - - @Override - public InetSocketAddress getAddress() { - return null; - } - - @Override - public void sendRawMessage(String message) { - sendMessage(message); - } - - @Override - public void kickPlayer(String message) { - } - - @Override - public void chat(String msg) { - Bukkit.getPluginManager() - .callEvent(new AsyncPlayerChatEvent(true, this, msg, new HashSet<>(Bukkit.getOnlinePlayers()))); - } - - @Override - public boolean performCommand(String command) { - return Bukkit.getServer().dispatchCommand(this, command); - } - - @Override - public boolean isSneaking() { - return false; - } - - @Override - public void setSneaking(boolean sneak) { - } - - @Override - public boolean isSprinting() { - return false; - } - - @Override - public void setSprinting(boolean sprinting) { - } - - @Override - public void saveData() { - } - - @Override - public void loadData() { - } - - @Override - public void setSleepingIgnored(boolean isSleeping) { - } - - @Override - public boolean isSleepingIgnored() { - return false; - } - - @Override - public void playNote(Location loc, byte instrument, byte note) { - } - - @Override - public void playNote(Location loc, Instrument instrument, Note note) { - } - - @Override - public void playSound(Location location, Sound sound, float volume, float pitch) { - } + super(user, channel, entityId, uuid, module); + origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid)); + name = mcname; + } + + @Delegate(excludes = ServerOperator.class) + private PermissibleBase origPerm; + + private @Getter String name; + + private @Getter OfflinePlayer basePlayer; + + @Getter + @Setter + private PermissibleBase perm; + + public void setOp(boolean value) { //CraftPlayer-compatible implementation + this.origPerm.setOp(value); + this.perm.recalculatePermissions(); + } + + public boolean isOp() { return this.origPerm.isOp(); } + + @Override + public EntityType getType() { + return EntityType.PLAYER; + } + + @Override + public String getCustomName() { + return user.getUsername(); + } + + @Override + public void setCustomName(String name) { + } + + @Override + public boolean isConversing() { + + return false; + } + + @Override + public void acceptConversationInput(String input) { + } + + @Override + public boolean beginConversation(Conversation conversation) { + return false; + } + + @Override + public void abandonConversation(Conversation conversation) { + } + + @Override + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + } + + @Override + public boolean isOnline() { + return true;// Let's pretend + } + + @Override + public boolean isBanned() { + return false; + } + + @Override + public boolean isWhitelisted() { + return true; + } + + @Override + public void setWhitelisted(boolean value) { + } + + @Override + public Player getPlayer() { + return this; + } + + @Override + public long getFirstPlayed() { + return 0; + } + + @Override + public long getLastPlayed() { + return 0; + } + + @Override + public boolean hasPlayedBefore() { + return false; + } + + @Override + public Map serialize() { + return new HashMap<>(); + } + + @Override + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + } + + @Override + public Set getListeningPluginChannels() { + return Collections.emptySet(); + } + + @Override + public String getDisplayName() { + return Objects.requireNonNull(user.asMember(DiscordPlugin.mainServer.getId()).block()).getDisplayName(); + } + + @Override + public void setDisplayName(String name) { + } + + @Override + public String getPlayerListName() { + return getName(); + } + + @Override + public void setPlayerListName(String name) { + } + + @Override + public void setCompassTarget(Location loc) { + } + + @Override + public Location getCompassTarget() { + return new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + } + + @Override + public InetSocketAddress getAddress() { + return null; + } + + @Override + public void sendRawMessage(String message) { + sendMessage(message); + } + + @Override + public void kickPlayer(String message) { + } + + @Override + public void chat(String msg) { + Bukkit.getPluginManager() + .callEvent(new AsyncPlayerChatEvent(true, this, msg, new HashSet<>(Bukkit.getOnlinePlayers()))); + } + + @Override + public boolean performCommand(String command) { + return Bukkit.getServer().dispatchCommand(this, command); + } + + @Override + public boolean isSneaking() { + return false; + } + + @Override + public void setSneaking(boolean sneak) { + } + + @Override + public boolean isSprinting() { + return false; + } + + @Override + public void setSprinting(boolean sprinting) { + } + + @Override + public void saveData() { + } + + @Override + public void loadData() { + } + + @Override + public void setSleepingIgnored(boolean isSleeping) { + } + + @Override + public boolean isSleepingIgnored() { + return false; + } + + @Override + public void playNote(Location loc, byte instrument, byte note) { + } + + @Override + public void playNote(Location loc, Instrument instrument, Note note) { + } + + @Override + public void playSound(Location location, Sound sound, float volume, float pitch) { + } - @Override - public void playSound(Location location, String sound, float volume, float pitch) { - } + @Override + public void playSound(Location location, String sound, float volume, float pitch) { + } - @Override - public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { - } + @Override + public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { + } - @Override - public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) { - } + @Override + public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) { + } - @Override - public void stopSound(Sound sound) { - } + @Override + public void stopSound(Sound sound) { + } - @Override - public void stopSound(String sound) { - } + @Override + public void stopSound(String sound) { + } - @Override - public void stopSound(Sound sound, SoundCategory category) { - } + @Override + public void stopSound(Sound sound, SoundCategory category) { + } - @Override - public void stopSound(String sound, SoundCategory category) { - } + @Override + public void stopSound(String sound, SoundCategory category) { + } - @Override - public void playEffect(Location loc, Effect effect, int data) { - } + @Override + public void playEffect(Location loc, Effect effect, int data) { + } - @Override - public void playEffect(Location loc, Effect effect, T data) { - } + @Override + public void playEffect(Location loc, Effect effect, T data) { + } - @Override - public void sendBlockChange(Location loc, Material material, byte data) { - } + @Override + public void sendBlockChange(Location loc, Material material, byte data) { + } - @Override - public boolean sendChunkChange(Location loc, int sx, int sy, int sz, byte[] data) { - return false; - } + @Override + public boolean sendChunkChange(Location loc, int sx, int sy, int sz, byte[] data) { + return false; + } - @Override - public void sendBlockChange(Location loc, int material, byte data) { - } + @Override + public void sendBlockChange(Location loc, int material, byte data) { + } - @Override - public void sendSignChange(Location loc, String[] lines) throws IllegalArgumentException { - } + @Override + public void sendSignChange(Location loc, String[] lines) throws IllegalArgumentException { + } - @Override - public void sendMap(MapView map) { - } + @Override + public void sendMap(MapView map) { + } - @Override - public void updateInventory() { - } + @Override + public void updateInventory() { + } - @Override - public void awardAchievement(@SuppressWarnings("deprecation") Achievement achievement) { - } + @Override + public void awardAchievement(@SuppressWarnings("deprecation") Achievement achievement) { + } - @Override - public void removeAchievement(@SuppressWarnings("deprecation") Achievement achievement) { - } + @Override + public void removeAchievement(@SuppressWarnings("deprecation") Achievement achievement) { + } - @Override - public boolean hasAchievement(@SuppressWarnings("deprecation") Achievement achievement) { - return false; - } + @Override + public boolean hasAchievement(@SuppressWarnings("deprecation") Achievement achievement) { + return false; + } - @Override - public void incrementStatistic(Statistic statistic) throws IllegalArgumentException { - } + @Override + public void incrementStatistic(Statistic statistic) throws IllegalArgumentException { + } - @Override - public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { - } + @Override + public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { + } - @Override - public void incrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException { + @Override + public void incrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException { - } + } - @Override - public void decrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException { + @Override + public void decrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException { - } + } - @Override - public void setStatistic(Statistic statistic, int newValue) throws IllegalArgumentException { + @Override + public void setStatistic(Statistic statistic, int newValue) throws IllegalArgumentException { - } + } - @Override - public int getStatistic(Statistic statistic) throws IllegalArgumentException { + @Override + public int getStatistic(Statistic statistic) throws IllegalArgumentException { - return 0; - } + return 0; + } - @Override - public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { + @Override + public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { - } + } - @Override - public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { + @Override + public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { - } + } - @Override - public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException { + @Override + public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException { - return 0; - } + return 0; + } - @Override - public void incrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException { + @Override + public void incrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException { - } + } - @Override - public void decrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException { + @Override + public void decrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException { - } + } - @Override - public void setStatistic(Statistic statistic, Material material, int newValue) throws IllegalArgumentException { + @Override + public void setStatistic(Statistic statistic, Material material, int newValue) throws IllegalArgumentException { - } + } - @Override - public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { - } + } - @Override - public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { - } + } - @Override - public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { + @Override + public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { - return 0; - } + return 0; + } - @Override - public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) - throws IllegalArgumentException { + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) + throws IllegalArgumentException { - } + } - @Override - public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) { + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) { - } + } - @Override - public void setStatistic(Statistic statistic, EntityType entityType, int newValue) { + @Override + public void setStatistic(Statistic statistic, EntityType entityType, int newValue) { - } + } - @Override - public void setPlayerTime(long time, boolean relative) { + @Override + public void setPlayerTime(long time, boolean relative) { - } + } - @Override - public long getPlayerTime() { + @Override + public long getPlayerTime() { - return 0; - } + return 0; + } - @Override - public long getPlayerTimeOffset() { + @Override + public long getPlayerTimeOffset() { - return 0; - } + return 0; + } - @Override - public boolean isPlayerTimeRelative() { + @Override + public boolean isPlayerTimeRelative() { - return false; - } + return false; + } - @Override - public void resetPlayerTime() { + @Override + public void resetPlayerTime() { - } + } - @Override - public void setPlayerWeather(WeatherType type) { + @Override + public void setPlayerWeather(WeatherType type) { - } + } - @Override - public WeatherType getPlayerWeather() { + @Override + public WeatherType getPlayerWeather() { - return null; - } + return null; + } - @Override - public void resetPlayerWeather() { + @Override + public void resetPlayerWeather() { - } + } - @Override - public void giveExp(int amount) { + @Override + public void giveExp(int amount) { - } + } - @Override - public void giveExpLevels(int amount) { + @Override + public void giveExpLevels(int amount) { - } + } - @Override - public float getExp() { + @Override + public float getExp() { - return 0; - } + return 0; + } - @Override - public void setExp(float exp) { + @Override + public void setExp(float exp) { - } + } - @Override - public int getLevel() { + @Override + public int getLevel() { - return 0; - } + return 0; + } - @Override - public void setLevel(int level) { + @Override + public void setLevel(int level) { - } + } - @Override - public int getTotalExperience() { + @Override + public int getTotalExperience() { - return 0; - } + return 0; + } - @Override - public void setTotalExperience(int exp) { + @Override + public void setTotalExperience(int exp) { - } + } - @Override - public float getExhaustion() { + @Override + public float getExhaustion() { - return 0; - } + return 0; + } - @Override - public void setExhaustion(float value) { + @Override + public void setExhaustion(float value) { - } + } - @Override - public float getSaturation() { + @Override + public float getSaturation() { - return 0; - } + return 0; + } - @Override - public void setSaturation(float value) { + @Override + public void setSaturation(float value) { - } + } - @Override - public int getFoodLevel() { + @Override + public int getFoodLevel() { - return 0; - } + return 0; + } - @Override - public void setFoodLevel(int value) { + @Override + public void setFoodLevel(int value) { - } + } - @Override - public Location getBedSpawnLocation() { - return null; - } + @Override + public Location getBedSpawnLocation() { + return null; + } - @Override - public void setBedSpawnLocation(Location location) { - } + @Override + public void setBedSpawnLocation(Location location) { + } - @Override - public void setBedSpawnLocation(Location location, boolean force) { - } + @Override + public void setBedSpawnLocation(Location location, boolean force) { + } - @Override - public boolean getAllowFlight() { - return false; - } + @Override + public boolean getAllowFlight() { + return false; + } - @Override - public void setAllowFlight(boolean flight) { - } + @Override + public void setAllowFlight(boolean flight) { + } - @Override - public void hidePlayer(Player player) { - } + @Override + public void hidePlayer(Player player) { + } - @Override - public void showPlayer(Player player) { - } + @Override + public void showPlayer(Player player) { + } - @Override - public boolean canSee(Player player) { // Nobody can see them - return false; - } + @Override + public boolean canSee(Player player) { // Nobody can see them + return false; + } - @Override - public boolean isFlying() { - return false; - } + @Override + public boolean isFlying() { + return false; + } - @Override - public void setFlying(boolean value) { - } + @Override + public void setFlying(boolean value) { + } - @Override - public void setFlySpeed(float value) throws IllegalArgumentException { - } + @Override + public void setFlySpeed(float value) throws IllegalArgumentException { + } - @Override - public void setWalkSpeed(float value) throws IllegalArgumentException { - } + @Override + public void setWalkSpeed(float value) throws IllegalArgumentException { + } - @Override - public float getFlySpeed() { - return 0; - } + @Override + public float getFlySpeed() { + return 0; + } - @Override - public float getWalkSpeed() { - return 0; - } + @Override + public float getWalkSpeed() { + return 0; + } - @Override - public void setTexturePack(String url) { - } + @Override + public void setTexturePack(String url) { + } - @Override - public void setResourcePack(String url) { - } + @Override + public void setResourcePack(String url) { + } - @Override - public void setResourcePack(String url, byte[] hash) { - } + @Override + public void setResourcePack(String url, byte[] hash) { + } - @Override - public Scoreboard getScoreboard() { - return null; - } + @Override + public Scoreboard getScoreboard() { + return null; + } - @Override - public void setScoreboard(Scoreboard scoreboard) throws IllegalArgumentException, IllegalStateException { - } + @Override + public void setScoreboard(Scoreboard scoreboard) throws IllegalArgumentException, IllegalStateException { + } - @Override - public boolean isHealthScaled() { - return false; - } + @Override + public boolean isHealthScaled() { + return false; + } - @Override - public void setHealthScaled(boolean scale) { - } + @Override + public void setHealthScaled(boolean scale) { + } - @Override - public void setHealthScale(double scale) throws IllegalArgumentException { - } + @Override + public void setHealthScale(double scale) throws IllegalArgumentException { + } - @Override - public double getHealthScale() { - return 1; - } + @Override + public double getHealthScale() { + return 1; + } - @Override - public Entity getSpectatorTarget() { - return null; - } + @Override + public Entity getSpectatorTarget() { + return null; + } - @Override - public void setSpectatorTarget(Entity entity) { - } + @Override + public void setSpectatorTarget(Entity entity) { + } - @Override - public void sendTitle(String title, String subtitle) { - } + @Override + public void sendTitle(String title, String subtitle) { + } - @Override - public void sendTitle(String title, String subtitle, int fadeIn, int stay, int fadeOut) { - } + @Override + public void sendTitle(String title, String subtitle, int fadeIn, int stay, int fadeOut) { + } - @Override - public void resetTitle() { - } + @Override + public void resetTitle() { + } - @Override - public void spawnParticle(Particle particle, Location location, int count) { - } + @Override + public void spawnParticle(Particle particle, Location location, int count) { + } - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count) { + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count) { - } + } - @Override - public void spawnParticle(Particle particle, Location location, int count, T data) { + @Override + public void spawnParticle(Particle particle, Location location, int count, T data) { - } + } - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, T data) { + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, T data) { - } + } - @Override - public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, - double offsetZ) { + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ) { - } + } - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, - double offsetY, double offsetZ) { + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ) { - } + } - @Override - public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, - double offsetZ, T data) { + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ, T data) { - } + } - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, - double offsetY, double offsetZ, T data) { + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ, T data) { - } + } - @Override - public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, - double offsetZ, double extra) { + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ, double extra) { - } + } - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, - double offsetY, double offsetZ, double extra) { + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ, double extra) { - } + } - @Override - public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, - double offsetZ, double extra, T data) { + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ, double extra, T data) { - } + } - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, - double offsetY, double offsetZ, double extra, T data) { + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ, double extra, T data) { - } + } - @Override - public AdvancementProgress getAdvancementProgress(Advancement advancement) { // TODO: Test - return null; - } + @Override + public AdvancementProgress getAdvancementProgress(Advancement advancement) { // TODO: Test + return null; + } - @Override - public String getLocale() { + @Override + public String getLocale() { - return null; - } + return null; + } - @Override - public Player.Spigot spigot() { - return new Player.Spigot(); - } + @Override + public Player.Spigot spigot() { + return new Player.Spigot(); + } } diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java b/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java new file mode 100644 index 0000000..de5a84b --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java @@ -0,0 +1,240 @@ +package buttondevteam.discordplugin.playerfaker.perm; + +import buttondevteam.core.MainPlugin; +import buttondevteam.discordplugin.mcchat.MCChatUtils; +import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer; +import buttondevteam.lib.TBMCCoreAPI; +import me.lucko.luckperms.bukkit.LPBukkitBootstrap; +import me.lucko.luckperms.bukkit.LPBukkitPlugin; +import me.lucko.luckperms.bukkit.inject.dummy.DummyPermissibleBase; +import me.lucko.luckperms.bukkit.inject.permissible.LPPermissible; +import me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.locale.message.Message; +import me.lucko.luckperms.common.model.User; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.PermissionAttachment; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class LPInjector implements Listener { //Disable login event for LuckPerms + private LPBukkitPlugin plugin; + private BukkitConnectionListener connectionListener; + private Set deniedLogin; + private Field detectedCraftBukkitOfflineMode; + private Method printCraftBukkitOfflineModeError; + private Field PERMISSIBLE_BASE_ATTACHMENTS_FIELD; + private Method convertAndAddAttachments; + private Method getActive; + private Method setOldPermissible; + private Method getOldPermissible; + + public LPInjector(MainPlugin mp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException { + LPBukkitBootstrap bs = (LPBukkitBootstrap) Bukkit.getPluginManager().getPlugin("LuckPerms"); + Field field = LPBukkitBootstrap.class.getDeclaredField("plugin"); + field.setAccessible(true); + plugin = (LPBukkitPlugin) field.get(bs); + MCChatUtils.addStaticExcludedPlugin(PlayerLoginEvent.class, "LuckPerms"); + MCChatUtils.addStaticExcludedPlugin(PlayerQuitEvent.class, "LuckPerms"); + + field = LPBukkitPlugin.class.getDeclaredField("connectionListener"); + field.setAccessible(true); + connectionListener = (BukkitConnectionListener) field.get(plugin); + field = connectionListener.getClass().getDeclaredField("deniedLogin"); + field.setAccessible(true); + //noinspection unchecked + deniedLogin = (Set) field.get(connectionListener); + field = connectionListener.getClass().getDeclaredField("detectedCraftBukkitOfflineMode"); + field.setAccessible(true); + detectedCraftBukkitOfflineMode = field; + printCraftBukkitOfflineModeError = connectionListener.getClass().getDeclaredMethod("printCraftBukkitOfflineModeError"); + printCraftBukkitOfflineModeError.setAccessible(true); + + //PERMISSIBLE_FIELD = DiscordFakePlayer.class.getDeclaredField("perm"); + //PERMISSIBLE_FIELD.setAccessible(true); //Hacking my own plugin, while we're at it + PERMISSIBLE_BASE_ATTACHMENTS_FIELD = PermissibleBase.class.getDeclaredField("attachments"); + PERMISSIBLE_BASE_ATTACHMENTS_FIELD.setAccessible(true); + + convertAndAddAttachments = LPPermissible.class.getDeclaredMethod("convertAndAddAttachments", Collection.class); + convertAndAddAttachments.setAccessible(true); + getActive = LPPermissible.class.getDeclaredMethod("getActive"); + getActive.setAccessible(true); + setOldPermissible = LPPermissible.class.getDeclaredMethod("setOldPermissible", PermissibleBase.class); + setOldPermissible.setAccessible(true); + getOldPermissible = LPPermissible.class.getDeclaredMethod("getOldPermissible"); + getOldPermissible.setAccessible(true); + + TBMCCoreAPI.RegisterEventsForExceptions(this, mp); + } + + + //Code copied from LuckPerms - me.lucko.luckperms.bukkit.listeners.BukkitConnectionListener + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerLogin(PlayerLoginEvent e) { + /* Called when the player starts logging into the server. + At this point, the users data should be present and loaded. */ + + if (!(e.getPlayer() instanceof DiscordFakePlayer)) + return; //Normal players must be handled by the plugin + + final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer(); + + if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) { + plugin.getLogger().info("Processing login for " + player.getUniqueId() + " - " + player.getName()); + } + + final User user = plugin.getUserManager().getIfLoaded(player.getUniqueId()); + + /* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */ + if (user == null) { + deniedLogin.add(player.getUniqueId()); + + if (!connectionListener.getUniqueConnections().contains(player.getUniqueId())) { + + plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() + + " doesn't have data pre-loaded, they have never been processed during pre-login in this session." + + " - denying login."); + + try { + if ((Boolean) detectedCraftBukkitOfflineMode.get(connectionListener)) { + printCraftBukkitOfflineModeError.invoke(connectionListener); + e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR_CB_OFFLINE_MODE.asString(plugin.getLocaleManager())); + return; + } + } catch (IllegalAccessException | InvocationTargetException ex) { + ex.printStackTrace(); + } + + } else { + plugin.getLogger().warn("User " + player.getUniqueId() + " - " + player.getName() + + " doesn't currently have data pre-loaded, but they have been processed before in this session." + + " - denying login."); + } + + e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_STATE_ERROR.asString(plugin.getLocaleManager())); + return; + } + + // User instance is there, now we can inject our custom Permissible into the player. + // Care should be taken at this stage to ensure that async tasks which manipulate bukkit data check that the player is still online. + try { + // get the existing PermissibleBase held by the player + PermissibleBase oldPermissible = player.getPerm(); + + // Make a new permissible for the user + LPPermissible lpPermissible = new LPPermissible(player, user, plugin); + + // Inject into the player + inject(player, lpPermissible, oldPermissible); + + } catch (Throwable t) { + plugin.getLogger().warn("Exception thrown when setting up permissions for " + + player.getUniqueId() + " - " + player.getName() + " - denying login."); + t.printStackTrace(); + + e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_SETUP_ERROR.asString(plugin.getLocaleManager())); + return; + } + + plugin.refreshAutoOp(player, true); + } + + // Wait until the last priority to unload, so plugins can still perform permission checks on this event + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent e) { + if (!(e.getPlayer() instanceof DiscordFakePlayer)) + return; + + final DiscordFakePlayer player = (DiscordFakePlayer) e.getPlayer(); + + connectionListener.handleDisconnect(player.getUniqueId()); + + // perform unhooking from bukkit objects 1 tick later. + // this allows plugins listening after us on MONITOR to still have intact permissions data + this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> { + // Remove the custom permissible + try { + uninject(player, true); + } catch (Exception ex) { + ex.printStackTrace(); + } + + // Handle auto op + if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) { + player.setOp(false); + } + + // remove their contexts cache + this.plugin.getContextManager().onPlayerQuit(player); + }, 1L); + } + + //me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector + private void inject(DiscordFakePlayer player, LPPermissible newPermissible, PermissibleBase oldPermissible) throws IllegalAccessException, InvocationTargetException { + + // seems we have already injected into this player. + if (oldPermissible instanceof LPPermissible) { + throw new IllegalStateException("LPPermissible already injected into player " + player.toString()); + } + + // Move attachments over from the old permissible + + //noinspection unchecked + List attachments = (List) PERMISSIBLE_BASE_ATTACHMENTS_FIELD.get(oldPermissible); + + convertAndAddAttachments.invoke(newPermissible, attachments); + attachments.clear(); + oldPermissible.clearPermissions(); + + // Setup the new permissible + ((AtomicBoolean) getActive.invoke(newPermissible)).set(true); + setOldPermissible.invoke(newPermissible, oldPermissible); + + // inject the new instance + player.setPerm(newPermissible); + } + + private void uninject(DiscordFakePlayer player, boolean dummy) throws Exception { + + // gets the players current permissible. + PermissibleBase permissible = player.getPerm(); + + // only uninject if the permissible was a luckperms one. + if (permissible instanceof LPPermissible) { + LPPermissible lpPermissible = ((LPPermissible) permissible); + + // clear all permissions + lpPermissible.clearPermissions(); + + // set to inactive + ((AtomicBoolean) getActive.invoke(lpPermissible)).set(false); + + // handle the replacement permissible. + if (dummy) { + // just inject a dummy class. this is used when we know the player is about to quit the server. + player.setPerm(DummyPermissibleBase.INSTANCE); + + } else { + PermissibleBase newPb = (PermissibleBase) getOldPermissible.invoke(lpPermissible); + if (newPb == null) { + newPb = new PermissibleBase(player); + } + + player.setPerm(newPb); + } + } + } +}