All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.twitch4j.chat.util.MessageParser Maven / Gradle / Ivy

The newest version!
package com.github.twitch4j.chat.util;

import com.github.twitch4j.chat.events.channel.IRCMessageEvent;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

import java.nio.CharBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

@UtilityClass
public class MessageParser {

    @Nullable
    @VisibleForTesting
    public IRCMessageEvent parse(@NotNull String rawMessage) {
        return parse(rawMessage, new ConcurrentHashMap<>(0), new ConcurrentHashMap<>(0), null);
    }

    @Nullable
    @ApiStatus.Internal
    public IRCMessageEvent parse(@NotNull String raw, @NotNull Map channelIdToChannelName, @NotNull Map channelNameToChannelId, @Nullable Collection botOwnerIds) {
        final int len = raw.length();
        if (len == 0) return null;
        final char[] chars = raw.toCharArray();
        int i = 0;

        // Tags
        final Map tags;
        if (chars[0] == '@') {
            tags = new HashMap<>(32);
            i = parseTags(chars, tags);
        } else {
            tags = Collections.emptyMap();
        }

        // Client
        if (chars[i] == ':') i++;
        int exclamation = -1;
        int space = -1;
        for (int j = i; j < len; j++) {
            final char c = chars[j];
            if (c == '!') {
                if (exclamation < 0)
                    exclamation = j;
            } else if (c == ' ') {
                space = j;
                break;
            }
        }
        if (space < 0 || space + 1 >= len) return null;
        final int clientNameEnd = exclamation > 0 ? exclamation : space;
        final CharSequence clientName = CharBuffer.wrap(chars, i, clientNameEnd - i);
        i = space + 1;

        // Command
        int commandEnd = ArrayUtils.indexOf(chars, ' ', i);
        if (commandEnd < 0) {
            commandEnd = len;
        }
        final String commandType = raw.substring(i, commandEnd);
        i = commandEnd + 1;

        if (i >= len) {
            return new IRCMessageEvent(raw, tags, clientName, commandType, null, null, null, channelIdToChannelName, channelNameToChannelId, botOwnerIds);
        }

        // Channel
        int messageStart = raw.indexOf(" :", i);
        if (messageStart < 0) {
            messageStart = len;
        }
        final CharSequence channel = CharBuffer.wrap(chars, i, messageStart - i);
        final int chanDelim = StringUtils.indexOf(channel, " = "); // handle 353 NAMES
        final CharSequence channelPart = chanDelim < 0 ? channel : channel.subSequence(chanDelim + " = ".length(), channel.length());
        final String channelName = (
            channelPart.length() > 0 && channelPart.charAt(0) == '#'
                ? channelPart.subSequence(1, channelPart.length())
                : channelPart
        ).toString();

        // Message
        messageStart++;
        if (messageStart >= len) {
            return new IRCMessageEvent(raw, tags, clientName, commandType, channelName, null, null, channelIdToChannelName, channelNameToChannelId, botOwnerIds);
        }
        final CharSequence payload = CharBuffer.wrap(chars, messageStart, len - messageStart);
        final String message = raw.substring(messageStart + 1);
        return new IRCMessageEvent(raw, tags, clientName, commandType, channelName, payload, message, channelIdToChannelName, channelNameToChannelId, botOwnerIds);
    }

    @VisibleForTesting
    public int parseTags(char[] inputChars, Map output) {
        final int len = inputChars.length;
        int i = 0;
        int delim = -1;
        for (int j = i + 1; j < len; j++) {
            final char c = inputChars[j];
            final boolean boundary = c == ';';
            if (boundary || c == ' ') {
                final int tagStart = i + 1;
                final CharSequence tag = CharBuffer.wrap(inputChars, tagStart, j - tagStart);
                parseTag(output, tag, delim - tagStart);
                i = j;
                delim = -1;
                if (!boundary) break;
            } else if (c == '=' && delim < 0) {
                delim = j;
            }
        }
        return ++i;
    }

    private void parseTag(Map tags, CharSequence tag, int delim) {
        final CharSequence key, value;
        if (delim < 0) {
            key = tag;
            value = null;
        } else {
            key = tag.subSequence(0, delim);
            value = tag.subSequence(delim + 1, tag.length());
        }
        tags.put(key.toString(), value);
    }

    @ApiStatus.Internal
    public void consumeLines(@NotNull String source, @NotNull Consumer consumer) {
        int len = source.length();
        int start = 0;
        int i;

        while ((i = source.indexOf('\n', start)) >= 0) {
            boolean carriage = i > 0 && source.charAt(i - 1) == '\r';
            consumer.accept(source.substring(start, carriage ? i - 1 : i));
            start = i + 1;
        }

        if (start < len) {
            consumer.accept(source.substring(start));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy