Resource Converting Legacy and B+ formatted strings into MD's ChatComponentAPI

Discussion in 'Spigot Plugin Development' started by BananaPuncher714, Jul 6, 2018.

  1. [​IMG]
    What's this?
    MDChat is a 1 class utility method that allows you to convert legacy chat into md_5's ChatComponent API. Not only does this support legacy formatting, it also supports B+ formatting, a new and easy way to include hover/click events in simple strings!

    Find out more about how to use B+ formatting here.
    This utility class is ported over from here, another simpler and slightly more efficient NMS chat implementation. ;)

    Great, so how do I use this?
    Using MDChat is extremely simple.
    Code (Java):
    String legacy = ChatColor.translateAlternateColorCodes( '&', "&cHello, &a&lWorld!" );

    // Since this is only legacy chat, we don't want to parse B+ formatting
    TextComponent component = MDUtil.getMessageFromString( legacy, false);

    player.spigot().sendMessage( component );
    As you can see, it produces this:
    [​IMG]

    But that is not the main purpose of MDChat. Just like this plugin, this utility will allow you to easily craft custom messages, as well as letting configuration nerds make their own custom click and hover events.
    Code (Java):
    String b_plus = ChatColor.translateAlternateColorCodes( '&',
        "&fClick <&e&lhere{&bC&dl&bi&dc&bk &dm&be&d!&b!}(open_url:https://www.spigotmc.org/resources/authors/aeternumnetwork.558882/)> for more epicness!!" );

    // We set the second argument to "true", since we ARE converting B+ formatted strings
    TextComponent component = MDUtil.getMessageFromString( b_plus, true );

    player.spigot().sendMessage( component );
    This snippet makes this:
    [​IMG]
    And when clicked, it leads to
    here :p

    You can set the click action to be any of the ClickEvent.Action values, so you can make it run a command too.

    You can find the source online here, too
    Code (Java):
    [/SIZE]
    package your.package.here;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    import net.md_5.bungee.api.ChatColor;
    import net.md_5.bungee.api.chat.BaseComponent;
    import net.md_5.bungee.api.chat.ClickEvent;
    import net.md_5.bungee.api.chat.ComponentBuilder;
    import net.md_5.bungee.api.chat.HoverEvent;
    import net.md_5.bungee.api.chat.TextComponent;

    /**
     * A fast and easy way to convert legacy or B+ formatted chat into MD's ChatComponent API
     *
     * Ported over from https://github.com/BananaPuncher714/BrickBoard/tree/master/io/github/bananapuncher714/brickboard/api/chat
     *
     * Find out more about B+ formatting here: https://bananapuncher714.gitbook.io/brickboard/b+-style-chat-formatting
     *
     * @author BananaPuncher714
     */

    public class MDChat {
       private final static char PLACEHOLDER = '\u02A7';
       
       /**
        * Gets a TextComponent from a message
        *
        * @param message
        * Must be legacy formatted
        * @return
        * The newly created TextComponent
        */

       public static TextComponent getMessageFromString( String message ) {
           return getMessageFromString( message, false );
       }
       
       /**
        * Gets a TextComponent from a message
        *
        * @param message
        * A legacy or B+ formatted message
        * @param readExtra
        * Whether or not to parse B+ formatting, A.K.A. hover and click events
        * @return
        * Newly created TextComponent
        */

       public static TextComponent getMessageFromString( String message, boolean readExtra ) {
           TextComponent chatMessage = new TextComponent();

           if ( message == null ) {
               return chatMessage;
           }
           
           BaseComponent last = new TextComponent( "" );

           List< TextComponent > actions = new ArrayList< TextComponent >();
           // This is where we start looking for B+ formatting
           if ( readExtra ) {
               // Thanks to StarShadow#3546 for negative look behind
               // Will stop certain strings from getting converted
               List< String > caught = getMatches( message, "<(.+?)(?<!\\\\)>" );
               for ( String action : caught ) {
                   String hover = getMatch( action, "\\{(.+?)(?<!\\\\)\\}" );
                   String click = getMatch( action, "\\((.+?\\:.+?)(?<!\\\\)\\)" );
                   if ( hover == null && click == null ) {
                       // Requires a click or hover, or skip
                       continue;
                   }
                   String desc = action;
                   HoverEvent hAction = null;
                   ClickEvent cAction = null;
                   if ( click != null ) {
                       String[] clickSplit = click.split( "\\:", 2 );
                       ClickEvent.Action cActionAction;
                       try {
                           cActionAction = ClickEvent.Action.valueOf( clickSplit[ 0 ].toUpperCase() );
                       } catch ( IllegalArgumentException exception ) {
                           cActionAction = null;
                       }
                       if ( cActionAction == null ) {
                           if ( hover == null ) {
                               continue;
                           }
                       } else {
                           cAction = new ClickEvent( cActionAction, clickSplit[ 1 ] );
                           desc = desc.replace( "(" + click + ")", "" );
                       }
                   }
                   if ( hover != null ) {
                       hAction = new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( hover.replace( "\\n", "\n" ) ).create() );
                       desc = desc.replace( "{" + hover + "}", "" );
                   }
                   // Replace all the escaped tags
                   desc = desc.replace( "\\{", "{" ).replace( "\\}", "}" );
                   desc = desc.replace( "\\(", "(" ).replace( "\\)", ")" );
                   TextComponent description = getMessageFromString( desc, false );
                   for ( BaseComponent component : description.getExtra() ) {
                       component.setHoverEvent( hAction );
                       component.setClickEvent( cAction );
                   }
                   message = message.replace( "<" + action + ">", "" + ChatColor.COLOR_CHAR + PLACEHOLDER );
                   actions.add( description );
               }
               // Replace all the escaped tags
               message = message.replace( "\\<", "<" ).replace( "\\>", ">" );
           }
           
           // More efficient conversion using split, as opposed to iterating over every character
           String[] parts = ( " " + message ).split( "" + ChatColor.COLOR_CHAR );
           for ( String part : parts ) {
               if ( part.isEmpty() ) {
                   continue;
               }
               char colorCharacter = part.charAt( 0 );
               if ( colorCharacter == PLACEHOLDER ) {
                   chatMessage.addExtra( actions.remove( 0 ) );
               } else {
                   ChatColor color = ChatColor.getByChar( colorCharacter );

                   if ( color != null ) {
                       if ( isColor( color ) ) {
                           clearFormatting( last );
                           last.setColor( color );
                       } else {
                           if ( color == ChatColor.BOLD ) {
                               last.setBold( true );
                           } else if ( color == ChatColor.ITALIC ) {
                               last.setItalic( true );
                           } else if ( color == ChatColor.UNDERLINE ) {
                               last.setUnderlined( true );
                           } else if ( color == ChatColor.MAGIC ) {
                               last.setObfuscated( true );
                           } else if ( color == ChatColor.RESET ) {
                               clearFormatting( last );
                           } else if ( color == ChatColor.STRIKETHROUGH ) {
                               last.setStrikethrough( true );
                           }
                       }
                   }
               }
               if ( part.length() > 1 ) {
                   if ( last instanceof TextComponent ) {
                       ( ( TextComponent ) last ).setText( part.substring( 1 ) );
                   }
                   chatMessage.addExtra( last );
                   last = last.duplicate();
               }
           }

           return chatMessage;
       }
       
       // Simple method to clear all formats, something that's pretty useful but not implemented *cough*
       private static void clearFormatting( BaseComponent component ) {
           component.setBold( false );
           component.setItalic( false );
           component.setUnderlined( false );
           component.setObfuscated( false );
           component.setStrikethrough( false );
       }
       
       // Funnily enough, MD's ChatColor doesn't have this method
       private static boolean isColor( ChatColor color ) {
           return org.bukkit.ChatColor.valueOf( color.name() ).isColor();
       }
       
       private static List< String > getMatches( String string, String regex ) {
           Pattern pattern = Pattern.compile( regex );
           Matcher matcher = pattern.matcher( string );
           List< String > matches = new ArrayList< String >();
           while ( matcher.find() ) {
               matches.add( matcher.group( 1 ) );
           }
           return matches;
       }
       
       private static String getMatch( String string, String regex ) {
           Pattern pattern = Pattern.compile( regex );
           Matcher matcher = pattern.matcher( string );
           if ( matcher.find() ) {
               return matcher.group( 1 );
           } else {
               return null;
           }
       }
    }
    [SIZE=4]

    If you have any questions, suggestions, or find any bugs, feel free to tell me!



    And now, let me :):love::p:cool::unsure:;)(y):LOL::D;);):ROFLMAO::alien:(y):rolleyes::cool::devilish:
     
    #1 BananaPuncher714, Jul 6, 2018
    Last edited: Jul 24, 2019
    • Like Like x 4
    • Useful Useful x 3
    • Winner Winner x 1
  2. very interesting, thanks
     
    • Friendly Friendly x 1
  3. Looks interesting, might include this in future projects.
     
  4. Yeah that’s cool. I would improve this stuff by defining a better syntax for memorization.

    Code (Text):
    Welcome \url{\hover{here}{A very good hovermessage}}{http://www.some-url.com} you can find certain information important for you.
    I took LaTeX as an example how to define commands. Here you know the exact meaning of your command.
     
    • Like Like x 1
    • Agree Agree x 1
  5. Im aware that this is an old post and this is necroposting but, the util dont let you use multiple lines on hover when the string contain "\n"

    I got around this setting a token and replacing it on the ChatComponent#setHoverAction
     
    • Like Like x 1
  6. Thanks for the heads up, I'll add it to my gist and update it when I have the time!
     
  7. I did a PullRequest, check it out when you have time!
     
    • Useful Useful x 1
  8. Thanks, I looked it over. It's an improvement, but it would be better if it used \\n as a token when replacing newlines. I've updated the MDChat gist to replace \n in hover actions with a newline. You can find it here.
     
    • Agree Agree x 1