Search code examples
swifttwitch

How to parse twitch IRC emotes in Swift?


I'm trying to parse Twitch IRC emotes and extract it from a string. Twitch IRC message provides a range which is a location of an emote in a string. Example:

string="Kappa abcdef"; range start=0; range end=4

means emote Kappa is located from the index 0 to 4 in the string "Kappa abcdef".

Here's my function that will return an emote based on the range:

func extractEmoteName(from message: String, rangeStart: Int, rangeEnd: Int) -> String? {
    guard
        let indexStart = message.index(message.startIndex, offsetBy: rangeStart, limitedBy: message.endIndex),
        let limitIndex = message.index(message.endIndex, offsetBy: -1, limitedBy: message.startIndex),
        let indexEnd = message.index(message.startIndex, offsetBy: rangeEnd, limitedBy: limitIndex)
    else {
        print("out of bounds")
        return nil
    }
    
    return String(message[indexStart ... indexEnd])
}

Using this on the example string: extractEmoteName(from: "Kappa abcdef", rangeStart: 0, rangeEnd: 4) will return Kappa as a result.

I'm having a trouble with the strings that contain emojis with multiple unicode scalars:

string="⚠️ Kappa"; range start=3; range end=7

Result of the extraction function is nil:

extractEmoteName(from: "⚠️ Kappa", rangeStart: 3, rangeEnd: 7) // prints "out of bounds"

At the same time, messages with the single scalar emotes parses fine because the range provided by Twitch is different:

string="🙂 Kappa"; range start=2; range end=6

extractEmoteName(from: "🙂 Kappa", rangeStart: 2, rangeEnd: 6) // returns "Kappa"

What should be changed in extractEmoteName to make it work with all emoji characters?


Solution

  • You need to use unicodeScalars, it will represent the string in unicode scalars: ​

    func extractEmoteName(from input: String, rangeStart: Int, rangeEnd: Int) -> String? {
        let message = input.unicodeScalars
        
        guard
            let indexStart = message.index(message.startIndex, offsetBy: rangeStart, limitedBy: message.endIndex),
            let limitIndex = message.index(message.endIndex, offsetBy: -1, limitedBy: message.startIndex),
            let indexEnd = message.index(message.startIndex, offsetBy: rangeEnd, limitedBy: limitIndex)
        else {
            print("out of bounds")
            return nil
        }
        
        return String(message[indexStart ... indexEnd])