boomboompower/ToggleChat

Separator toggle strips ChatComponents

Moire9 opened this issue · 8 comments

Describe the bug
While separators are disabled, party invite messages are broken. In previous versions, having separators disabled would not remove the separators around party invites (as they are one dash longer than normal separators). However, building from latest commit causes it to remove all but that one line, and additionally breaks the invite itself. Normally, you can click on the invite, to run /party accept <username> and it additionally has hover text telling you to click on it to do just that, however, with separators disabled, both of those are absent, forcing you to execute /p accept <username> yourself.

To Reproduce
Steps to reproduce the behavior:

  1. Disable separators from being shown.
  2. Have someone invite you to party.
  3. See bugged text.

Expected behavior
Expected behavior would be to either keep the entire line of separators (as it used to do), or remove the entire line. Either way, the party invite should still function and still have a command run on click.

Screenshots

You can see the party invite and lack of hover text
(In the image, my mouse was not captured. However, I am hovering over the text, and there is no hover-text).
What is should look like (with the mouse hovering too):
image

Desktop (please complete the following information):

  • OS: Windows
  • Mod Version: Compiled from c2c26f1
  • Minecraft Version: 1.8.9

Additional context
None is necessary (I think).

Hypixel changed separators a while back so they are integrated into the message. When the client receives a party or guild invite, the invite is formatted with the \n character (new line) which makes the message appear separate however it's actually 1 message.

ToggleChat (when toggling separators) tries to replace all separators characters with blanks since the message cannot be ignored. In doing so this removes all chat formatting, which is what is being seen here.


Reflection could be used to iterate through all ChatComponents in the message and remove separator chars from each component however this may impact performance.

I'll test this tomorrow and look for a workaround which doesn't involve reflection. I'll also post an example of the chat format which hypixel sends to the client


A reference to where separators are edited
A reference to where ToggleChat edits the message

Yes. Exactly, I noticed that it was one-line, and it is this that makes my watchdog feature impossible to fully implement due to the current state of ToggleChat. You see, after my code didn't work numerous times, I decided to look at Hypixel's strings on Crowdin (I'm a translator), and realized that the Watchdog message, unlike every other message, is not one message with newlines, but rather 6 seperate messages, sent at once. Which means TC would have to be able to provide the surrounding messages to a class, for "context." I think I have an idea of how to do this, though.

Deep searching could be used (this is reflection-free).

    private void modifySeparators(TypeMessageSeparator toggle, IChatComponent componentIn, String formattedText) {
        if (!formattedText.contains("▬▬") && !formattedText.contains("---")) {
            return;
        }
        
        if (componentIn.getSiblings().size() > 0) {
            for (int i = 0; i < componentIn.getSiblings().size(); i++) {
                IChatComponent child = componentIn.getSiblings().get(i);
                
                if (!(child instanceof ChatComponentText)) {
                    continue;
                }

                String fixed = toggle.editMessage(child.getFormattedText());

                componentIn.getSiblings().set(i, new ChatComponentText(fixed));
            }
        }
    }

However if more TextComponent are hidden in the children of the children, it won't work with this example. More loops could be used, however for a long message this could hang the client/reduce the framerate.

Hmm. I don't think that Hypixel uses nested ChatComponents, but then again, my knowlege of such things is fairly limited. I do know, that they use both a hoverText, and a clickEvent, and not anything further, however I don't know if they would be in the children of the children.

New lines are just \n characters

Message logged in the client logs (automatically stripped by the client)

-----------------------------------------------------
[MVP++] SirNapkin1334 has invited you to join their party!
Click here to join! You have 60 seconds to accept.
-----------------------------------------------------

Formatted message

§6-----------------------------------------------------
§r§6[MVP§r§9++§r§6] SirNapkin1334 §r§ehas invited you to join their party!
§r§6Click here §r§eto join! You have 60 seconds to accept.§r§6
§r§6-----------------------------------------------------§r

Heres what IntelliJ debug sees

TextComponent{text='-----------------------------------------------------
', siblings=[TextComponent{text='[JRY', siblings=[], style=Style{hasParent=true, color=§6, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=null, hoverEvent=null, insertion=null}}, TextComponent{text='++', siblings=[], style=Style{hasParent=true, color=§9, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=null, hoverEvent=null, insertion=null}}, TextComponent{text='] SirNapkin1334 ', siblings=[], style=Style{hasParent=true, color=§6, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=null, hoverEvent=null, insertion=null}}, TextComponent{text='has invited you to join their party!
', siblings=[], style=Style{hasParent=true, color=§e, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=null, hoverEvent=null, insertion=null}}, TextComponent{text='Click here ', siblings=[], style=Style{hasParent=true, color=§6, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=ClickEvent{action=RUN_COMMAND, value='/party accept SirNapkin1334'}, hoverEvent=HoverEvent{action=SHOW_TEXT, value='TextComponent{text='Click to run
/party accept SirNapkin1334', siblings=[], style=Style{hasParent=false, color=null, bold=null, italic=null, underlined=null, obfuscated=null, clickEvent=null, hoverEvent=null, insertion=null}}'}, insertion=null}}, TextComponent{text='to join! You have 60 seconds to accept.', siblings=[], style=Style{hasParent=true, color=§e, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=ClickEvent{action=RUN_COMMAND, value='/party accept SirNapkin1334'}, hoverEvent=HoverEvent{action=SHOW_TEXT, value='TextComponent{text='Click to run
/party accept SirNapkin1334', siblings=[], style=Style{hasParent=false, color=null, bold=null, italic=null, underlined=null, obfuscated=null, clickEvent=null, hoverEvent=null, insertion=null}}'}, insertion=null}}, TextComponent{text='
', siblings=[], style=Style{hasParent=true, color=null, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=null, hoverEvent=null, insertion=null}}, TextComponent{text='-----------------------------------------------------', siblings=[], style=Style{hasParent=true, color=§6, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=null, hoverEvent=null, insertion=null}}], style=Style{hasParent=false, color=§6, bold=false, italic=false, underlined=false, obfuscated=false, clickEvent=null, hoverEvent=null, insertion=null}}

Interestingly, party expiration messages are separate, so are friend expirations and failed messages

[13:42:07] [Client thread/INFO]: [CHAT] -----------------------------------------------------
[13:42:07] [Client thread/INFO]: [CHAT] The party invite from [JRY+] Brano has expired.
[13:42:07] [Client thread/INFO]: [CHAT] -----------------------------------------------------

and with the formatting

[13:42:07] [Client thread/INFO]: [CHAT] §6-----------------------------------------------------§r
[13:42:07] [Client thread/INFO]: [CHAT] §eThe party invite from §r§6[JRY§r§9++§r§6] SirNapkin1334 §r§ehas expired.§r
[13:42:07] [Client thread/INFO]: [CHAT] §6-----------------------------------------------------§r

Another instance of a newline-based message


➔ Welcome to the Prototype Lobby
All games in this lobby are currently in development.
Click here to leave feedback! ➤ https://hypixel.net/PTL

Heres the code I'm testing as of now

Does not work

    /**
     * A hack to modify the separators in a {@link IChatComponent} by modifying sibling classes if possible
     *
     * @param toggle the TypeMessageSeparator toggle
     * @param componentIn the component to modify
     * @param formattedText the formattedText (just for an existance check)
     */
    private void modifySeparators(TypeMessageSeparator toggle, IChatComponent componentIn, String formattedText) {
        if (!formattedText.contains("▬▬") && !formattedText.contains("---")) {
            return;
        }

        // The first line of a ChatComponent is completely separate to the actual component
        if (componentIn instanceof ChatComponentText) {
            ChatComponentText original = (ChatComponentText) componentIn;

            String firstLineText = original.getUnformattedTextForChat();

            if (firstLineText.contains("---") || firstLineText.contains("▬")) {
                // Create a modified component with styling stripped.
                ChatComponentText newText = new ChatComponentText(toggle.editMessage(firstLineText));

                // Apply style
                newText.setChatStyle(original.getChatStyle());

                // Keep any styling on this first piece of text
                newText.getSiblings().addAll(componentIn.getSiblings());

                // Set the component to our new component
                componentIn = newText;
            }
        }

        if (componentIn.getSiblings().size() > 0) {
            // Loop through each sibling
            for (int i = 0; i < componentIn.getSiblings().size(); i++) {
                IChatComponent child = componentIn.getSiblings().get(i);

                if (!(child instanceof ChatComponentText)) {
                    continue;
                }

                String formattedChildText = child.getFormattedText();

                // If this does not have anything worth replacing we'll ignore it.
                if (!formattedChildText.contains("---") && !formattedChildText.contains("▬")) {
                    continue;
                }

                // Replace the contents of the message if required.
                String fixed = toggle.editMessage(formattedChildText);

                // If the text has not been modified there is no point replacing the message
                if (fixed.equals(formattedChildText)) {
                    continue;
                }

                // Create a new message from the replaced content
                ChatComponentText newComponent = new ChatComponentText(fixed);

                // Add the old style of the child onto this new component (keeps Chat events working)
                newComponent.setChatStyle(child.getChatStyle());

                // Replace the sibling in the component with our new message.
                componentIn.getSiblings().set(i, newComponent);
            }
        }
    }

This has been resolved in following commit a16b7d1

See the following file for the fix

public IChatComponent removeSeparators(IChatComponent component) {
// In the vanilla game everything is part of the style.
if (!(component instanceof ChatComponentStyle)) return component;
// We only really want to modify text components.
if (component instanceof ChatComponentText) {
// Check it for a pattern match
if (doesPatternMatch(component)) {
// Construct a new TextComponent to replace the other one
ChatComponentText newText = new ChatComponentText("");
// Set the chat style and the siblings to the former component
// so it follows the same format and has the same chat colors.
newText.setChatStyle(component.getChatStyle());
newText.getSiblings().addAll(component.getSiblings());
// Discard the old component.
component = newText;
}
}
// Remove all siblings if their pattern matches.
component.getSiblings().removeIf(this::doesPatternMatch);
return component;
}