gmoehra

(rmcStaff) - Bungeecord / Channel

Jan 14th, 2022
584
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.96 KB | None | 0 0
  1. /**
  2. * https://github.com/leonardosnt/bungeechannelapi
  3. * <p>
  4. * Copyright (C) 2017 leonardosnt <[email protected]>
  5. * Licensed under the MIT License. See LICENSE file in the project root for full license information.
  6. */
  7.  
  8. package redeminecraft.staff.proxy.api;
  9.  
  10. import com.google.common.collect.Iterables;
  11. import com.google.common.io.ByteArrayDataInput;
  12. import com.google.common.io.ByteArrayDataOutput;
  13. import com.google.common.io.ByteStreams;
  14. import org.bukkit.Bukkit;
  15. import org.bukkit.entity.Player;
  16. import org.bukkit.plugin.Plugin;
  17. import org.bukkit.plugin.messaging.Messenger;
  18. import org.bukkit.plugin.messaging.PluginMessageListener;
  19.  
  20. import java.net.InetSocketAddress;
  21. import java.util.*;
  22. import java.util.concurrent.CompletableFuture;
  23. import java.util.function.BiFunction;
  24.  
  25. /**
  26. * Documentation copied from https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/
  27. *
  28. * @author leonardosnt ([email protected])
  29. * @see <a href="https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/">https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/</a>
  30. */
  31. public class Channel {
  32.  
  33. private static final WeakHashMap<Plugin, Channel> registeredInstances = new WeakHashMap<>();
  34.  
  35. private final PluginMessageListener messageListener;
  36. private final Plugin plugin;
  37. private final Map<String, Queue<CompletableFuture<?>>> callbackMap;
  38.  
  39. private Map<String, ForwardConsumer> forwardListeners;
  40. private ForwardConsumer globalForwardListener;
  41.  
  42. /**
  43. * Get or create new Channel instance
  44. *
  45. * @param plugin the plugin instance.
  46. * @return the Channel instance for the {@code plugin}
  47. * @throws NullPointerException if the {@code plugin} is {@code null}
  48. */
  49. public synchronized static Channel of(Plugin plugin) {
  50. return registeredInstances.compute(plugin, (k, v) -> {
  51. if (v == null) v = new Channel(plugin);
  52. return v;
  53. });
  54. }
  55.  
  56. public Channel(Plugin plugin) {
  57. this.plugin = Objects.requireNonNull(plugin, "plugin cannot be null");
  58. this.callbackMap = new HashMap<>();
  59.  
  60. // Prevent dev's from registering multiple channel listeners,
  61. // by unregistering the old instance.
  62. synchronized (registeredInstances) {
  63. registeredInstances.compute(plugin, (k, oldInstance) -> {
  64. if (oldInstance != null) oldInstance.unregister();
  65. return this;
  66. });
  67. }
  68.  
  69. this.messageListener = this::onPluginMessageReceived;
  70.  
  71. Messenger messenger = Bukkit.getServer().getMessenger();
  72. messenger.registerOutgoingPluginChannel(plugin, "BungeeCord");
  73. messenger.registerIncomingPluginChannel(plugin, "BungeeCord", messageListener);
  74. }
  75.  
  76. /**
  77. * Set a global listener for all 'forwarded' messages.
  78. *
  79. * @param globalListener the listener
  80. * @see <a href="https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/#forward">https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/#forward</a>
  81. */
  82. public void registerForwardListener(ForwardConsumer globalListener) {
  83. this.globalForwardListener = globalListener;
  84. }
  85.  
  86. /**
  87. * Set a listener for all 'forwarded' messages in a specific subchannel.
  88. *
  89. * @param channelName the subchannel name
  90. * @param listener the listener
  91. * @see <a href="https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/#forward">https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/#forward</a>
  92. */
  93. public void registerForwardListener(String channelName, ForwardConsumer listener) {
  94. if (forwardListeners == null) {
  95. forwardListeners = new HashMap<>();
  96. }
  97. synchronized (forwardListeners) {
  98. forwardListeners.put(channelName, listener);
  99. }
  100. }
  101.  
  102. /**
  103. * Get the amount of players on a certain server, or on ALL the servers.
  104. *
  105. * @param serverName the server name of the server to get the player count of, or ALL to get the global player count
  106. * @return A {@link CompletableFuture} that, when completed, will return
  107. * the amount of players on a certain server, or on ALL the servers.
  108. * @throws IllegalArgumentException if there is no players online.
  109. */
  110. public CompletableFuture<Integer> getPlayerCount(String serverName) {
  111. Player player = getFirstPlayer();
  112. CompletableFuture<Integer> future = new CompletableFuture<>();
  113.  
  114. synchronized (callbackMap) {
  115. callbackMap.compute("PlayerCount-" + serverName, this.computeQueueValue(future));
  116. }
  117.  
  118. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  119. output.writeUTF("PlayerCount");
  120. output.writeUTF(serverName);
  121. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  122. return future;
  123. }
  124.  
  125. /**
  126. * Get a list of players connected on a certain server, or on ALL the servers.
  127. *
  128. * @param serverName the name of the server to get the list of connected players, or ALL for global online player list
  129. * @return A {@link CompletableFuture} that, when completed, will return a
  130. * list of players connected on a certain server, or on ALL the servers.
  131. * @throws IllegalArgumentException if there is no players online.
  132. */
  133. public CompletableFuture<List<String>> getPlayerList(String serverName) {
  134. Player player = getFirstPlayer();
  135. CompletableFuture<List<String>> future = new CompletableFuture<>();
  136.  
  137. synchronized (callbackMap) {
  138. callbackMap.compute("PlayerList-" + serverName, this.computeQueueValue(future));
  139. }
  140.  
  141. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  142. output.writeUTF("PlayerList");
  143. output.writeUTF(serverName);
  144. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  145. return future;
  146. }
  147.  
  148. /**
  149. * Get a list of server name strings, as defined in BungeeCord's config.yml.
  150. *
  151. * @return A {@link CompletableFuture} that, when completed, will return a
  152. * list of server name strings, as defined in BungeeCord's config.yml.
  153. * @throws IllegalArgumentException if there is no players online.
  154. */
  155. public CompletableFuture<List<String>> getServers() {
  156. Player player = getFirstPlayer();
  157. CompletableFuture<List<String>> future = new CompletableFuture<>();
  158.  
  159. synchronized (callbackMap) {
  160. callbackMap.compute("GetServers", this.computeQueueValue(future));
  161. }
  162.  
  163. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  164. output.writeUTF("GetServers");
  165. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  166. return future;
  167. }
  168.  
  169. /**
  170. * Connects a player to said subserver.
  171. *
  172. * @param player the player you want to teleport.
  173. * @param serverName the name of server to connect to, as defined in BungeeCord config.yml.
  174. */
  175. public void connect(Player player, String serverName) {
  176. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  177. output.writeUTF("Connect");
  178. output.writeUTF(serverName);
  179. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  180. }
  181.  
  182. /**
  183. * Connect a named player to said subserver.
  184. *
  185. * @param playerName name of the player to teleport.
  186. * @param server name of server to connect to, as defined in BungeeCord config.yml.
  187. * @throws IllegalArgumentException if there is no players online.
  188. */
  189. public void connectOther(String playerName, String server) {
  190. Player player = getFirstPlayer();
  191. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  192.  
  193. output.writeUTF("ConnectOther");
  194. output.writeUTF(playerName);
  195. output.writeUTF(server);
  196.  
  197. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  198. }
  199.  
  200. /**
  201. * Get the (real) IP of a player.
  202. *
  203. * @param player The player you wish to get the IP of.
  204. * @return A {@link CompletableFuture} that, when completed, will return the (real) IP of {@code player}.
  205. */
  206. public CompletableFuture<InetSocketAddress> getIp(Player player) {
  207. CompletableFuture<InetSocketAddress> future = new CompletableFuture<>();
  208.  
  209. synchronized (callbackMap) {
  210. callbackMap.compute("IP", this.computeQueueValue(future));
  211. }
  212.  
  213. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  214. output.writeUTF("IP");
  215. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  216. return future;
  217. }
  218.  
  219. /**
  220. * Send a message (as in, a chat message) to the specified player.
  221. *
  222. * @param playerName the name of the player to send the chat message.
  223. * @param message the message to send to the player.
  224. * @throws IllegalArgumentException if there is no players online.
  225. */
  226. public void sendMessage(String playerName, String message) {
  227. Player player = getFirstPlayer();
  228. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  229.  
  230. output.writeUTF("Message");
  231. output.writeUTF(playerName);
  232. output.writeUTF(message);
  233. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  234. }
  235.  
  236. /**
  237. * Get this server's name, as defined in BungeeCord's config.yml
  238. *
  239. * @return A {@link CompletableFuture} that, when completed, will return
  240. * the {@code server's} name, as defined in BungeeCord's config.yml.
  241. * @throws IllegalArgumentException if there is no players online.
  242. */
  243. public CompletableFuture<String> getServer() {
  244. Player player = getFirstPlayer();
  245. CompletableFuture<String> future = new CompletableFuture<>();
  246.  
  247. synchronized (callbackMap) {
  248. callbackMap.compute("GetServer", this.computeQueueValue(future));
  249. }
  250.  
  251. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  252. output.writeUTF("GetServer");
  253. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  254. return future;
  255. }
  256.  
  257. /**
  258. * Request the UUID of this player.
  259. *
  260. * @param player The player whose UUID you requested.
  261. * @return A {@link CompletableFuture} that, when completed, will return the UUID of {@code player}.
  262. */
  263. public CompletableFuture<String> getUUID(Player player) {
  264. CompletableFuture<String> future = new CompletableFuture<>();
  265.  
  266. synchronized (callbackMap) {
  267. callbackMap.compute("UUID", this.computeQueueValue(future));
  268. }
  269.  
  270. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  271. output.writeUTF("UUID");
  272. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  273. return future;
  274. }
  275.  
  276. /**
  277. * Request the UUID of any player connected to the BungeeCord proxy.
  278. *
  279. * @param playerName the name of the player whose UUID you would like.
  280. * @return A {@link CompletableFuture} that, when completed, will return the UUID of {@code playerName}.
  281. * @throws IllegalArgumentException if there is no players online.
  282. */
  283. public CompletableFuture<String> getUUID(String playerName) {
  284. Player player = getFirstPlayer();
  285. CompletableFuture<String> future = new CompletableFuture<>();
  286.  
  287. synchronized (callbackMap) {
  288. callbackMap.compute("UUIDOther-" + playerName, this.computeQueueValue(future));
  289. }
  290.  
  291. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  292. output.writeUTF("UUIDOther");
  293. output.writeUTF(playerName);
  294. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  295. return future;
  296. }
  297.  
  298. /**
  299. * Request the IP of any server on this proxy.
  300. *
  301. * @param serverName the name of the server.
  302. * @return A {@link CompletableFuture} that, when completed, will return the requested ip.
  303. * @throws IllegalArgumentException if there is no players online.
  304. */
  305. public CompletableFuture<InetSocketAddress> getServerIp(String serverName) {
  306. Player player = getFirstPlayer();
  307. CompletableFuture<InetSocketAddress> future = new CompletableFuture<>();
  308.  
  309. synchronized (callbackMap) {
  310. callbackMap.compute("ServerIP-" + serverName, this.computeQueueValue(future));
  311. }
  312.  
  313. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  314. output.writeUTF("ServerIP");
  315. output.writeUTF(serverName);
  316. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  317. return future;
  318. }
  319.  
  320. /**
  321. * Kick any player on this proxy.
  322. *
  323. * @param playerName the name of the player.
  324. * @param kickMessage the reason the player is kicked with.
  325. * @throws IllegalArgumentException if there is no players online.
  326. */
  327. public void kickPlayer(String playerName, String kickMessage) {
  328. Player player = getFirstPlayer();
  329. CompletableFuture<InetSocketAddress> future = new CompletableFuture<>();
  330.  
  331. synchronized (callbackMap) {
  332. callbackMap.compute("KickPlayer", this.computeQueueValue(future));
  333. }
  334.  
  335. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  336. output.writeUTF("KickPlayer");
  337. output.writeUTF(playerName);
  338. output.writeUTF(kickMessage);
  339. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  340. }
  341.  
  342. /**
  343. * Send a custom plugin message to said server. This is one of the most useful channels ever.
  344. * <b>Remember, the sending and receiving server(s) need to have a player online.</b>
  345. *
  346. * @param server the name of the server to send to,
  347. * ALL to send to every server (except the one sending the plugin message),
  348. * or ONLINE to send to every server that's online (except the one sending the plugin message).
  349. *
  350. * @param channelName Subchannel for plugin usage.
  351. * @param data data to send.
  352. * @throws IllegalArgumentException if there is no players online.
  353. */
  354. public void forward(String server, String channelName, byte[] data) {
  355. Player player = getFirstPlayer();
  356.  
  357. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  358. output.writeUTF("Forward");
  359. output.writeUTF(server);
  360. output.writeUTF(channelName);
  361. output.writeShort(data.length);
  362. output.write(data);
  363. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  364. }
  365.  
  366. /**
  367. * Send a custom plugin message to specific player.
  368. *
  369. * @param playerName the name of the player to send to.
  370. * @param channelName Subchannel for plugin usage.
  371. * @param data data to send.
  372. * @throws IllegalArgumentException if there is no players online.
  373. */
  374. public void forwardToPlayer(String playerName, String channelName, byte[] data) {
  375. Player player = getFirstPlayer();
  376.  
  377. ByteArrayDataOutput output = ByteStreams.newDataOutput();
  378. output.writeUTF("ForwardToPlayer");
  379. output.writeUTF(playerName);
  380. output.writeUTF(channelName);
  381. output.writeShort(data.length);
  382. output.write(data);
  383. player.sendPluginMessage(this.plugin, "BungeeCord", output.toByteArray());
  384. }
  385.  
  386. @SuppressWarnings("unchecked")
  387. private void onPluginMessageReceived(String channel, Player player, byte[] message) {
  388. if (!channel.equalsIgnoreCase("BungeeCord")) return;
  389.  
  390. ByteArrayDataInput input = ByteStreams.newDataInput(message);
  391. String subchannel = input.readUTF();
  392.  
  393. synchronized (callbackMap) {
  394. Queue<CompletableFuture<?>> callbacks;
  395.  
  396. if (subchannel.equals("PlayerCount") || subchannel.equals("PlayerList") ||
  397. subchannel.equals("UUIDOther") || subchannel.equals("ServerIP")) {
  398. String identifier = input.readUTF(); // Server/player name
  399. callbacks = callbackMap.get(subchannel + "-" + identifier);
  400.  
  401. if (callbacks == null || callbacks.isEmpty()) {
  402. return;
  403. }
  404.  
  405. CompletableFuture<?> callback = callbacks.poll();
  406.  
  407. try {
  408. switch (subchannel) {
  409. case "PlayerCount":
  410. ((CompletableFuture<Integer>) callback).complete(Integer.valueOf(input.readInt()));
  411. break;
  412.  
  413. case "PlayerList":
  414. ((CompletableFuture<List<String>>) callback).complete(Arrays.asList(input.readUTF().split(", ")));
  415. break;
  416.  
  417. case "UUIDOther":
  418. ((CompletableFuture<String>) callback).complete(input.readUTF());
  419. break;
  420.  
  421. case "ServerIP": {
  422. String ip = input.readUTF();
  423. int port = input.readUnsignedShort();
  424. ((CompletableFuture<InetSocketAddress>) callback).complete(new InetSocketAddress(ip, port));
  425. break;
  426. }
  427. }
  428. } catch (Exception ex) {
  429. callback.completeExceptionally(ex);
  430. }
  431.  
  432. return;
  433. }
  434.  
  435. callbacks = callbackMap.get(subchannel);
  436.  
  437. if (callbacks == null) {
  438. short dataLength = input.readShort();
  439. byte[] data = new byte[dataLength];
  440. input.readFully(data);
  441.  
  442. if (globalForwardListener != null) {
  443. globalForwardListener.accept(subchannel, player, data);
  444. }
  445.  
  446. if (forwardListeners != null) {
  447. synchronized (forwardListeners) {
  448. ForwardConsumer listener = forwardListeners.get(subchannel);
  449. if (listener != null) {
  450. listener.accept(subchannel, player, data);
  451. }
  452. }
  453. }
  454.  
  455. return;
  456. }
  457.  
  458. if (callbacks.isEmpty()) {
  459. return;
  460. }
  461.  
  462. final CompletableFuture<?> callback = callbacks.poll();
  463.  
  464. try {
  465. switch (subchannel) {
  466. case "GetServers":
  467. ((CompletableFuture<List<String>>) callback).complete(Arrays.asList(input.readUTF().split(", ")));
  468. break;
  469.  
  470. case "GetServer":
  471. case "UUID":
  472. ((CompletableFuture<String>) callback).complete(input.readUTF());
  473. break;
  474.  
  475. case "IP": {
  476. String ip = input.readUTF();
  477. int port = input.readInt();
  478. ((CompletableFuture<InetSocketAddress>) callback).complete(new InetSocketAddress(ip, port));
  479. break;
  480. }
  481.  
  482. default:
  483. break;
  484. }
  485. } catch (Exception ex) {
  486. callback.completeExceptionally(ex);
  487. }
  488. }
  489. }
  490.  
  491. /**
  492. * Unregister message channels.
  493. */
  494. public void unregister() {
  495. Messenger messenger = Bukkit.getServer().getMessenger();
  496. messenger.unregisterIncomingPluginChannel(plugin, "BungeeCord", messageListener);
  497. messenger.unregisterOutgoingPluginChannel(plugin);
  498. callbackMap.clear();
  499. }
  500.  
  501. private BiFunction<String, Queue<CompletableFuture<?>>, Queue<CompletableFuture<?>>> computeQueueValue(CompletableFuture<?> queueValue) {
  502. return (key, value) -> {
  503. if (value == null) value = new ArrayDeque<CompletableFuture<?>>();
  504. value.add(queueValue);
  505. return value;
  506. };
  507. }
  508.  
  509. private Player getFirstPlayer() {
  510. /**
  511. * if Bukkit Version < 1.7.10 then Bukkit.getOnlinePlayers() will return
  512. * a Player[] otherwise it'll return a Collection<? extends Player>.
  513. */
  514. Player firstPlayer = getFirstPlayer0(Bukkit.getOnlinePlayers());
  515.  
  516. if (firstPlayer == null) {
  517. throw new IllegalArgumentException("Bungee Messaging Api requires at least one player to be online.");
  518. }
  519.  
  520. return firstPlayer;
  521. }
  522.  
  523. @SuppressWarnings("unused")
  524. private Player getFirstPlayer0(Player[] playerArray) {
  525. return playerArray.length > 0 ? playerArray[0] : null;
  526. }
  527.  
  528. private Player getFirstPlayer0(Collection<? extends Player> playerCollection) {
  529. return Iterables.getFirst(playerCollection, null);
  530. }
  531.  
  532. @FunctionalInterface
  533. public interface ForwardConsumer {
  534. void accept(String channel, Player player, byte[] data);
  535. }
  536. }
Advertisement
Add Comment
Please, Sign In to add comment