/* * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod * Copyright (C) 2021 cyoung06 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package kr.syeyoung.dungeonsguide.chat; import kr.syeyoung.dungeonsguide.DungeonsGuide; import kr.syeyoung.dungeonsguide.events.HypixelJoinedEvent; import kr.syeyoung.dungeonsguide.events.StompConnectedEvent; import kr.syeyoung.dungeonsguide.stomp.*; import kr.syeyoung.dungeonsguide.utils.TextUtils; import lombok.Getter; import lombok.Setter; import net.minecraft.client.Minecraft; import net.minecraft.util.ChatComponentText; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.json.JSONArray; import org.json.JSONObject; import java.security.SecureRandom; import java.util.*; import java.util.function.Consumer; public class PartyManager implements StompMessageHandler { public static final PartyManager INSTANCE = new PartyManager(); @Getter private PartyContext partyContext; public PartyContext getPartyContext(boolean createIfNeeded) { PartyContext pc = partyContext == null && createIfNeeded ? partyContext = new PartyContext() : partyContext; if (createIfNeeded) pc.addRawMember(Minecraft.getMinecraft().getSession().getUsername()); return pc; } @Getter @Setter private int maxParty = 5; @Getter private String askToJoinSecret = null; private static final SecureRandom random = new SecureRandom(); private static final String validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; private Set> partyBuiltCallback = new HashSet<>(); public PartyManager() { ChatProcessor cp = ChatProcessor.INSTANCE; // Not in Party cp.subscribe((str, a) -> { if (str.equals("§cYou are not currently in a party.§r") || str.equals("§eYou left the party.§r") || str.equals("§cYou must be in a party to join the party channel!§r") || str.equals("§cThe party was disbanded because all invites expired and the party was empty§r") || str.equals("§cYou are not in a party and were moved to the ALL channel.§r") || str.startsWith("§cThe party was disbanded") || str.endsWith("§ehas disbanded the party!§r") || str.startsWith("§cYou are not in a party") || str.startsWith("§eYou have been kicked from the party by ")) { leaveParty(); for (Consumer partyContextConsumer : partyBuiltCallback) { try { partyContextConsumer.accept(null); } catch (Exception e) { e.printStackTrace(); } } partyBuiltCallback.clear(); a.put("type", "notinparty"); } return ChatProcessResult.NONE; }); // All invite cp.subscribe((str, a) -> { if (str.endsWith("§aenabled All Invite§r")) { getPartyContext(true).setAllInvite(true); a.put("type", "allinvite_on"); } else if (str.endsWith("§cdisabled All Invite§r") || str.equals("§cYou are not allowed to invite players.§r")) { getPartyContext(true).setAllInvite(false); a.put("type", "allinvite_off"); potentialInvitenessChange(); } return ChatProcessResult.NONE; }); // Member building cp.subscribe(new ChatSubscriber() { boolean memberExpected; PartyContext partyContext = new PartyContext(); @Override public ChatProcessResult process(String txt, Map context) { if (txt.startsWith("§6Party Members ")) { memberExpected = true; partyContext = new PartyContext(); partyContext.setPartyModerator(new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); partyContext.setPartyMember(new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); context.put("type", "member_start"); } else if (txt.startsWith("§eParty ") && txt.contains(":")){ String role = txt.split(":")[0]; String playerNames = TextUtils.stripColor(txt.split(":")[1]); for (String s : playerNames.split(" ")) { if (s.isEmpty()) continue; if (s.equals("●")) continue; if (s.startsWith("[")) continue; partyContext.addRawMember(s); if (role.contains("Moder")) partyContext.addPartyModerator(s); if (role.contains("Member")) partyContext.addPartyMember(s); if (role.contains("Leader")) partyContext.setPartyOwner(s); } if (role.contains("Moder")) { partyContext.setModeratorComplete(true); context.put("type", "member_moder"); } if (role.contains("Member")) { partyContext.setMemberComplete(true); context.put("type", "member_member"); } if (role.contains("Leader")) { context.put("type", "member_leader"); } } else if (txt.startsWith("§9§m---------------------------")) { if (memberExpected) { PartyContext old = getPartyContext(true); old.setPartyOwner(partyContext.getPartyOwner()); old.setPartyModerator(partyContext.getPartyModerator()); old.setPartyMember(partyContext.getPartyMember()); old.setPartyRawMembers(new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); old.getPartyRawMembers().addAll(old.getPartyMember()); old.getPartyRawMembers().addAll(old.getPartyModerator()); old.getPartyRawMembers().add(old.getPartyOwner()); old.setModeratorComplete(true); old.setMemberComplete(true); old.setRawMemberComplete(true); old.setPartyExistHypixel(true); memberExpected = false; context.put("type", "member_end"); for (Consumer partyContextConsumer : partyBuiltCallback) { try { partyContextConsumer.accept(partyContext); } catch (Exception e) { e.printStackTrace(); } } partyBuiltCallback.clear(); if (old.getPartyID() == null) { joinedParty(); } potentialInvitenessChange(); } } return ChatProcessResult.NONE; } }); // Player party join / leave cp.subscribe((str ,a) -> { if (str.endsWith("§ejoined the party.§r")) { String username = null; for (String s : TextUtils.stripColor(str).split(" ")) { if (s.startsWith("[")) continue; username = s; break; } if (username != null) { getPartyContext(true).addPartyMember(username); } a.put("type", "party_join"); } else if (str.endsWith("§ehas been removed from the party.§r") || str.endsWith("§ehas left the party.§r")) { String username = null; for (String s : TextUtils.stripColor(str).split(" ")) { if (s.startsWith("[")) continue; username = s; break; } if (username != null && partyContext != null) { getPartyContext().removeFromParty(username); } a.put("type", "party_leave"); } else if (str.endsWith(" They have §r§c60 §r§eseconds to accept.§r")) { String[] messageSplit = TextUtils.stripColor(str).split(" "); String inviter = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; inviter = s; break; } if (inviter != null && partyContext != null) { if (getPartyContext().hasMember(inviter)) { getPartyContext().setAllInvite(true); } } getPartyContext(true).setPartyExistHypixel(true); a.put("type", "party_invite_exist"); } else if (str.equals("§cCouldn't find a player with that name!§r") || str.equals("§cYou cannot invite that player since they're not online.")) { a.put("type", "party_invite_noexist"); String username = Minecraft.getMinecraft().getSession().getUsername(); if (partyContext != null && getPartyContext().hasMember(username)) { getPartyContext().setAllInvite(true); } } return ChatProcessResult.NONE; }); // Promotion cp.subscribe((str, a) -> { if (str.startsWith("§eThe party was transferred to ")) { // §eThe party was transferred to §r§b[MVP§r§f+§r§b] apotato321 §r§eby §r§a[VIP§r§6+§r§a] syeyoung§r String[] messageSplit = TextUtils.stripColor(str.substring(31)).split(" "); String newLeader = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; newLeader = s; break; } String oldLeader; boolean left= false; if (str.endsWith("§r§eleft§r")) { oldLeader = messageSplit[messageSplit.length-2]; left = true; } else { oldLeader = messageSplit[messageSplit.length-1]; } if (oldLeader != null && newLeader != null ) { getPartyContext(true).setPartyOwner(newLeader); if (left) getPartyContext(true).removeFromParty(oldLeader); else getPartyContext(true).addPartyModerator(oldLeader); } a.put("type", "party_transfer"); potentialInvitenessChange(); } else if (str.endsWith("§eto Party Leader§r")) { // §a[VIP§r§6+§r§a] syeyoung§r§e has promoted §r§b[MVP§r§f+§r§b] apotato321 §r§eto Party Leader§r String[] messageSplit = TextUtils.stripColor(str).split(" "); String oldLeader = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; oldLeader = s; break; } messageSplit = TextUtils.stripColor(str.substring(str.indexOf("has promoted") + 13)).split(" "); String newLeader = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; newLeader = s; break; } if (oldLeader != null && newLeader != null) { getPartyContext(true).setPartyOwner(newLeader); getPartyContext(true).addPartyModerator(oldLeader); } a.put("type", "party_transfer"); potentialInvitenessChange(); } else if (str.endsWith("§r§eto Party Moderator§r")) { // §b[MVP§r§f+§r§b] apotato321§r§e has promoted §r§a[VIP§r§6+§r§a] syeyoung §r§eto Party Moderator§r String[] messageSplit = TextUtils.stripColor(str).split(" "); String oldLeader = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; oldLeader = s; break; } messageSplit = TextUtils.stripColor(str.substring(str.indexOf("has promoted") + 13)).split(" "); String newModerator = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; newModerator = s; break; } if (oldLeader != null && newModerator != null) { getPartyContext(true).setPartyOwner(oldLeader); getPartyContext(true).addPartyModerator(newModerator); } a.put("type", "party_promotion"); potentialInvitenessChange(); } else if (str.endsWith("§r§eto Party Member§r")) { String[] messageSplit = TextUtils.stripColor(str).split(" "); String oldLeader = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; oldLeader = s; break; } messageSplit = TextUtils.stripColor(str.substring(str.indexOf("has demoted") + 12)).split(" "); String newMember = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; newMember = s; break; } if (oldLeader != null && newMember != null) { getPartyContext(true).setPartyOwner(oldLeader); getPartyContext(true).addPartyMember(newMember); } a.put("type", "party_demotion"); potentialInvitenessChange(); } return ChatProcessResult.NONE; }); // Player Join cp.subscribe(new ChatSubscriber() { boolean joined; @Override public ChatProcessResult process(String str, Map context) { if (str.startsWith("§eYou have joined ")) { String[] messageSplit = TextUtils.stripColor(str.substring(18)).split(" "); String leader = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; leader = s; break; } leader = leader.substring(0, leader.length()-2); // remove 's partyContext = new PartyContext(); getPartyContext().setPartyOwner(leader); getPartyContext().addPartyMember(Minecraft.getMinecraft().getSession().getUsername()); context.put("type", "party_selfjoin_leader"); joined= true; } else if (str.startsWith("§eYou'll be partying with: ")) { String[] players = TextUtils.stripColor(str.substring(27)).split(" "); for (String player : players) { if (player.startsWith("[")) continue; getPartyContext().addRawMember(player); } context.put("type", "party_selfjoin_players"); } else if (str.startsWith("§9§m---------------------------") && joined) { joined = false; getPartyContext().setRawMemberComplete(true); joinedParty(); potentialInvitenessChange(); } return ChatProcessResult.NONE; }}); // Player Join Dungon cp.subscribe((str, a)-> { if (str.contains("§r§ejoined the dungeon group! (§r§b")) { String username = TextUtils.stripColor(str).split(" ")[3]; if (username.equalsIgnoreCase(Minecraft.getMinecraft().getSession().getUsername())) { partyContext = new PartyContext(); requestPartyList((str2) -> { potentialInvitenessChange(); }); } else { getPartyContext(true).setMemberComplete(false); requestPartyList((str2) -> {}); } } return ChatProcessResult.NONE; }); } public void toggleAllowAskToJoin() { if (canInvite()) { if (askToJoinSecret != null) askToJoinSecret = null; else { updateAskToJoin(); } } } public void updateAskToJoin() { StringBuilder secretBuilder = new StringBuilder(); for (int i = 0; i < 20; i++) { secretBuilder.append(validChars.charAt(random.nextInt(validChars.length()))); } askToJoinSecret = secretBuilder.toString(); StompInterface stompInterface = DungeonsGuide.getDungeonsGuide().getStompConnection(); stompInterface.send(new StompPayload().payload(new JSONObject().put("secret", askToJoinSecret).toString()).header("destination", "/app/party.setjoinsecret")); } public static ChatSubscriber dashShredder() { return (str, a) -> (int)a.get("removed") == 0 && str.startsWith("§9§m---------------------------") ? ChatProcessResult.REMOVE_LISTENER_AND_CHAT : ChatProcessResult.NONE; } public static ChatSubscriber typeShredder(boolean end, String... types) { return (str, a) -> (int)a.get("removed") == 0 &&Arrays.stream(types).anyMatch(s -> s.equals(a.getOrDefault("type", null))) ? (end ? ChatProcessResult.REMOVE_LISTENER_AND_CHAT : ChatProcessResult.REMOVE_CHAT) : ChatProcessResult.NONE; } public static ChatSubscriber combinedShredder(ChatSubscriber... chatSubscribers) { return (str, a) -> { boolean removeChat = false; boolean removeListener = false; for (ChatSubscriber chatSubscriber : chatSubscribers) { ChatProcessResult chatProcessResult = chatSubscriber.process(str, a); if (chatProcessResult.isRemoveChat()) removeChat = true; if (chatProcessResult.isRemoveListener()) removeListener = true; } return (removeChat && removeListener) ? ChatProcessResult.REMOVE_LISTENER_AND_CHAT : (removeChat ? ChatProcessResult.REMOVE_CHAT : (removeListener ? ChatProcessResult.REMOVE_LISTENER : ChatProcessResult.NONE)); }; } @SubscribeEvent public void onHypixelJoin(HypixelJoinedEvent skyblockJoinedEvent) { partyContext = null; requestPartyList((a) -> { if (a == null) return; if (isLeader() || isModerator()) return; if (a.getAllInvite() != null) return; requestAllInvite(); }); } private void leaveParty() { if (partyContext != null) { getPartyContext().setPartyExistHypixel(false); if (getPartyContext().isSelfSolo()) return; if (getPartyContext().getPartyID() != null) { JSONObject object = new JSONObject(); object.put("partyid", getPartyContext().getPartyID()); StompInterface stompInterface = DungeonsGuide.getDungeonsGuide().getStompConnection(); stompInterface.send(new StompPayload().payload(object.toString()).header("destination", "/app/party.leave")); } } partyContext = new PartyContext(); playerInvAntiSpam.clear(); getPartyContext().setPartyExistHypixel(false); getPartyContext().setPartyOwner(Minecraft.getMinecraft().getSession().getUsername()); getPartyContext().setPartyModerator(new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); getPartyContext().setMemberComplete(true); getPartyContext().setPartyMember(new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); getPartyContext().setModeratorComplete(true); getPartyContext().setAllInvite(false); joinedParty(); } private void joinedParty() { JSONArray jsonArray = new JSONArray(); for (String member : getPartyContext().getPartyRawMembers()) { jsonArray.put(member); } JSONObject object = new JSONObject(); object.put("members", jsonArray); StompInterface stompInterface = DungeonsGuide.getDungeonsGuide().getStompConnection(); stompInterface.send(new StompPayload().payload(object.toString()).header("destination", "/app/party.join")); getPartyContext().setPartyID("!@#!@#!@#..........FETCHING..........$!@$!@$!@$"+UUID.randomUUID().toString()); } public boolean isLeader() { return partyContext != null && getPartyContext().hasLeader(Minecraft.getMinecraft().getSession().getUsername()); // "getUsername" } public boolean isModerator() { return partyContext != null && getPartyContext().hasModerator(Minecraft.getMinecraft().getSession().getUsername()); } public boolean canInvite() { return isLeader() || isModerator() || (partyContext != null && getPartyContext().getAllInvite() != null && getPartyContext().getAllInvite()); } private boolean requested = false; public void requestPartyList(Consumer onPartyCallback) { if (requested) { partyBuiltCallback.add(onPartyCallback); return; } requested = true; ChatProcessor.INSTANCE.addToChatQueue("/pl", () -> { ChatProcessor.INSTANCE.subscribe(dashShredder()); ChatProcessor.INSTANCE.subscribe(combinedShredder(typeShredder(true, "member_end"), dashShredder(), typeShredder(false,"notinparty", "member_start", "member_moder", "member_leader", "member_member"))); }, true); partyBuiltCallback.add(onPartyCallback); partyBuiltCallback.add(pc -> requested=false); } public void requestAllInvite() { if (isLeader() || isModerator()) return; if (partyContext != null && getPartyContext().getAllInvite() != null) return; ChatProcessor.INSTANCE.addToChatQueue("/p invite -", () -> { ChatProcessor.INSTANCE.subscribe(dashShredder()); ChatProcessor.INSTANCE.subscribe(typeShredder(true, "notinparty", "allinvite_off", "party_invite_noexist")); ChatProcessor.INSTANCE.subscribe(dashShredder()); }, true); } private Map playerInvAntiSpam = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @Override public void handle(StompInterface stompInterface, StompPayload stompPayload) { JSONObject object = new JSONObject(stompPayload.payload()); if ("/user/queue/party.check".equals(stompPayload.headers().get("destination"))) { String playerName = object.getString("player"); String token = object.getString("token"); if (partyContext == null) { requestPartyList((pc) -> { boolean contains = pc.getPartyRawMembers().contains(playerName); if (!contains) { DungeonsGuide.getDungeonsGuide().getStompConnection().send(new StompPayload().payload(new JSONObject().put("status", "failure").put("token", token).toString()).header("destination", "/app/party.check.resp")); } else { DungeonsGuide.getDungeonsGuide().getStompConnection().send(new StompPayload().payload(new JSONObject().put("status", "success").put("token", token).toString()).header("destination", "/app/party.check.resp")); } }); } else { if (getPartyContext().getPartyRawMembers().contains(playerName)) { DungeonsGuide.getDungeonsGuide().getStompConnection().send(new StompPayload().payload(new JSONObject().put("status", "success").put("token", token).toString()).header("destination", "/app/party.check.resp")); } else if (getPartyContext().isMemberComplete() && getPartyContext().isModeratorComplete() && getPartyContext().getPartyOwner() != null) { DungeonsGuide.getDungeonsGuide().getStompConnection().send(new StompPayload().payload(new JSONObject().put("status", "failure").put("token", token).toString()).header("destination", "/app/party.check.resp")); } else { requestPartyList((pc) -> { boolean contains = pc.getPartyRawMembers().contains(playerName); if (!contains) { DungeonsGuide.getDungeonsGuide().getStompConnection().send(new StompPayload().payload(new JSONObject().put("status", "failure").put("token", token).toString()).header("destination", "/app/party.check.resp")); } else { DungeonsGuide.getDungeonsGuide().getStompConnection().send(new StompPayload().payload(new JSONObject().put("status", "success").put("token", token).toString()).header("destination", "/app/party.check.resp")); } }); } } } else if ("/user/queue/party.broadcast".equals(stompPayload.headers().get("destination"))) { try { Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("§eDungeons Guide §7:: Message Broadcasted from player:: \n" + new JSONObject(stompPayload.payload()).getString("payload"))); } catch (Exception e) { e.printStackTrace(); } } else if ("/user/queue/party.join".equals(stompPayload.headers().get("destination"))) { String playerName = object.getString("player"); String secret = object.getString("secret"); if (secret.equals(askToJoinSecret) && partyContext != null && getPartyContext().getPartyRawMembers().size() < maxParty && playerInvAntiSpam.getOrDefault(playerName, 0L) < System.currentTimeMillis() - 5000) { playerInvAntiSpam.put(playerName, System.currentTimeMillis()); ChatProcessor.INSTANCE.addToChatQueue("/p invite "+playerName,() -> {}, true); } } else if ("/user/queue/party.askedtojoin.resp".equals(stompPayload.headers().get("destination"))) { String invFrom = object.getString("username"); String token2 = object.getString("token"); if (!token2.equals(lastToken)) return; lastToken = null; ChatProcessor.INSTANCE.addToChatQueue("/p accept "+invFrom, () -> {}, true); long end = System.currentTimeMillis() + 3000; ChatProcessor.INSTANCE.subscribe((str, a) -> { if (!str.contains("§r§ehas invited you to join their party!")) return System.currentTimeMillis() > end ? ChatProcessResult.REMOVE_LISTENER : ChatProcessResult.NONE; String[] messageSplit = TextUtils.stripColor(str).split(" "); String inviter = null; for (String s : messageSplit) { if (s.startsWith("[")) continue; if (s.startsWith("-")) continue;; inviter = s; break; } if (invFrom.equalsIgnoreCase(inviter)) { ChatProcessor.INSTANCE.addToChatQueue("/p accept "+invFrom, () -> {}, true); } return ChatProcessResult.NONE; }); } else { String str = object.getString("status"); if ("success".equals(str) && partyContext != null) { getPartyContext().setPartyID(object.getString("partyId")); if (askToJoinSecret != null) { updateAskToJoin(); } } else if (partyContext != null){ getPartyContext().setPartyID(null); } } } @SubscribeEvent public void stompConnect(StompConnectedEvent stompConnectedEvent) { stompConnectedEvent.getStompInterface().subscribe(StompSubscription.builder() .stompMessageHandler(this).ackMode(StompSubscription.AckMode.AUTO).destination("/user/queue/party.resp").build()); stompConnectedEvent.getStompInterface().subscribe(StompSubscription.builder() .stompMessageHandler(this).ackMode(StompSubscription.AckMode.AUTO).destination("/user/queue/party.check").build()); stompConnectedEvent.getStompInterface().subscribe(StompSubscription.builder() .stompMessageHandler(this).ackMode(StompSubscription.AckMode.AUTO).destination("/user/queue/party.broadcast").build()); stompConnectedEvent.getStompInterface().subscribe(StompSubscription.builder() .stompMessageHandler(this).ackMode(StompSubscription.AckMode.AUTO).destination("/user/queue/party.join").build()); stompConnectedEvent.getStompInterface().subscribe(StompSubscription.builder() .stompMessageHandler(this).ackMode(StompSubscription.AckMode.AUTO).destination("/user/queue/party.askedtojoin.resp").build()); } private String lastToken; public void joinWithToken(String secret) { lastToken = secret; if (partyContext != null && getPartyContext().isPartyExistHypixel()) ChatProcessor.INSTANCE.addToChatQueue("/p leave", () -> {}, true); DungeonsGuide.getDungeonsGuide().getStompConnection().send(new StompPayload().method(StompHeader.SEND) .header("destination", "/app/party.askedtojoin") .payload(new JSONObject().put("token", secret).toString())); } private void potentialInvitenessChange() { if (askToJoinSecret != null && !canInvite()) askToJoinSecret = null; } }