Skip to content

A Kotlin library, providing integration of Twitch IRC capabilities within JVM based applications (mainly intended for use with Spring Boot) by leveraging Spring’s WebSocket Support.

License

Notifications You must be signed in to change notification settings

sndpg/twitch-chat-client

Repository files navigation

Kotlin Twitch Chat Client for Spring Boot

Warning

Pre-release state. Certain parts of the API will probably change or may be removed going forward.

A Kotlin library, providing integration of Twitch IRC capabilities within JVM based applications (mainly intended for use with Spring Boot) by leveraging Spring’s WebSocket Support.

Artifacts

Repositories

The project artifacts are currently only available through GitHub Packages.

To use them within your own project, see the corresponding documentation Installing a Gradle package.

Core

To use this library add the following dependency to the gradle build file:

build.gradle
dependencies {
    implementation "io.github.sykq:twitch-chat-client-core:0.0.1"
}
build.gradle.kts
dependencies {
    implementation("io.github.sykq:twitch-chat-client-core:0.0.1")
}

When using maven, add the following to the dependencies sections of your pom.xml:

pom.xml
<dependency>
    <groupId>io.github.sykq</groupId>
    <artifactId>twitch-chat-client-core</artifactId>
    <version>0.0.1</version>
</dependency>

Spring Boot

To use this library in a Spring Boot application, add the following Spring Boot Starter to the dependency section of your gradle build file:

build.gradle
dependencies {
    implementation "io.github.sykq:twitch-chat-client-spring-boot-starter:0.0.1"
}
build.gradle.kts
dependencies {
    implementation("io.github.sykq:twitch-chat-client-spring-boot-starter:0.0.1")
}

When using maven, add the following to the dependencies sections of your pom.xml:

pom.xml
<dependency>
    <groupId>io.github.sykq</groupId>
    <artifactId>twitch-chat-client-spring-boot-starter</artifactId>
    <version>0.0.1</version>
</dependency>

Usage

Tip

This project’s API uses Kotlin’s function literals with receiver feature in several places.

Make sure you’re familiar with it to effectively use this library.

TmiClient

The io.github.sykq.tcc.TmiClient class is part of the core library and facilitates the connection with the Twitch Messaging Interface.

To create a new TmiClient instance use the tmiClient() factory method within the io.github.sykq.tcc package:

import io.github.sykq.tcc.tmiClient

fun main() {
    val tmiClient = tmiClient {
        // configure your instance
        username = "myTwitchChatBot"
        passwordProperty = "TMI_CLIENT_PASSWORD" (1)

        // define the channels to join
        channels += "myChannel"
        channels += "someOtherChannel"

        onConnect {
            join("yetAnotherChannel") (2)
            textMessage("connected") (3)
        }
    }

    tmiClient.block {
        // specify what should happen upon receiving a message
        if (it.text == "!hello") { (4)
            textMessage("Hi ${it.user}")
        }
    }
}
  1. the passwordProperty can be set to the name of an environment variable of system property whose value will be used as the password for connecting to the TMI

  2. optionally join another channel upon connecting

  3. set an optional onConnect action, which will send the text "connected" to all joined channels

  4. the incoming TmiMessage is the lambda parameter (it), the TmiSession is the function receiver (this)

To connect to the Twitch Messaging Interface (TMI) through the TmiClient you need to provide your Twitch username (login name) in lowercase as the username and an associated OAuth token as the password. Such a token can be generated with the help of the Twitch Chat Password Generator.

Tip

Instead of directly setting the password through the password property, the TmiClient supports reading an according value from an environment variable or system property with the name of the given by the passwordProperty.

By default, the environment variable/system property with key TMI_CLIENT_PASSWORD is used to retrieve the password.

The same functionality is present for the usernameProperty to read the username from an environment variable or system property. Here, the key TMI_CLIENT_USERNAME is used as the default key.

These properties are only read if no password or username are explicitly set within the TmiClient’s configurer during initialization.

See Connecting to Twitch IRC in the official docs for details on how to use your Twitch account to connect to the TMI.

With Spring Boot

Adding the spring-boot-starter listed in Spring Boot to your Spring Boot project will pull in a AutoConfiguration which adds a BotRegistry-Bean to the ApplicationContext and provide the additional BotBase, Bot and PublishingBot interfaces which serve as an additional layer above the TmiClient, allowing for implementations to hold bot-specific state.

Features

TmiClient

Automatic PONG

The TMI-Server will send a PING message once about every five minutes. To ensure that the connection is not terminated, the TmiClient will automatically reply to all such messages with a PONG.

Sink for messages from sources independent of incoming messages

The TmiClient’s messageSink allows for writing of text messages to joined channels, which aren’t created as a response to an incoming message, but rather come from an independent source (e.g. some user interaction).

Therefore, it should be possible to implement an interactive chat client on top of a TmiClient.

The following example sends two messages to a channel through the sink and then simply prints these messages out to the console (they will be consumed as messages coming in from the channel which has been used by the sink to send the messages to):

Example for a TmiClient with a message sink
import reactor.core.publisher.Sinks

fun main() {
    val sink = Sinks.many().unicast().onBackpressureBuffer<String>() (1)
    sink.tryEmitNext("hello")
    sink.tryEmitNext("hello again")

    val tmiClient = tmiClient {
        // configure your instance
        username = "myUsername"
        passwordProperty = TmiClient.TMI_CLIENT_PASSWORD_KEY

        // define the channels to join
        channels += "myChannel"

        messageSink = sink
    }

    tmiClient.block { message ->
        println(message.text)
    }

}
  1. onBackpressureBuffer() replays all emissions pushed to this sink while no subscriber is registered, to the first (and only, since unicast() is used) subscriber. This allows us in this demo to push to the sink before the TmiClient establishes a connection to the TMI.

TmiSession

The io.github.sykq.tcc.TmiSession class is a wrapper over Spring’s WebSocketSession and provides methods specifically tailored for interacting with the TMI as well as the list of currently joined channels as the member variable joinedChannels.

This class (and its subclass ConfigurableTmiSession) is part of the signature of most of TmiClient's onConnect() and onMessage() variants.

Tip

An instance of ConfigurableTmiSession is provided as parameter of the onConnect() functions used by a TmiClient.

It offers methods to activate membership state event data, tags and commands capabilities.

E.g. to enrich incoming messages with tags, use the following instruction in an onConnect() function:

import io.github.sykq.tcc.tmiClient

    fun main() {
        val tmiClient = tmiClient {
            // configure your instance
            // ... (omitted for brevity)
            onConnect {
                tagCapabilities() (1)
            }
        }
        // ...
    }
  1. activate tag capabilities

Instances are implicitly provided by a TmiClient when using one of the according methods to establish a connection, e.g. connect(), connectAndTransform() or block().

Example methods

  • join() can be used to join one or more additional channels (as in additional to the channels specified within a TmiClient’s Configurer when creating a new TmiClient.

  • leave() can be used to leave one or more channels.

  • textMessage() can be used to send a text message to one or more channels. The following invocation will send Hello to all joined channels:

    tmiSession.textMessage("Hello")

    The following will send a textMessage to all joined channels whose name starts with the letter a (this is just an artificial example which should demonstrate the possibility to reference the joinedChannels list):

    textMessage("Hello", *joinedChannels.filter { it.startsWith("a") }.toTypedArray())
  • clearChat() sends the command /clear to a given channel. Such command sending methods are available for several other command. See the TmiSession class and its KDoc comments for the full list.

TmiMessage

The io.github.sykq.tcc.TmiMessage class represents an incoming message originating from one of the channels within the TMI.

It consists of:

  • timestamp = the timestamp of arrival at the client.

  • channel = the name of the originating channel of a message.

  • user = the authoring user of a message.

  • text = the text of a message.

  • type = one of the supported TmiMessageTypes

  • tags = the list of tags associated with a message. NOTE: Will only be supplied if tag capabilities are activated.

Instances are implicitly provided by a TmiClient each time onMessage() is invoked and one of the according methods to establish a connection, e.g. connect(), connectAndTransform() or block() is used.

OnCommandAction

The io.github.sykq.tcc.action.OnCommandAction represents an action in response to an incoming message, which will only be executed if the message is equal to a specified command.

The following examples will send the message Hello, user! in response to an incoming message with text !greet (where user is the author of the message):

Send a message in response to an incoming !greet command
val onGreetCommand = OnCommandAction("!greet") { (tmiMessage, command) -> (1)
    textMessage("Hello, ${tmiMessage.user}!")
}

tmiClient.block {
    onGreetCommand(this, it) (2)
    // potentially other actions
    // ...
}
  1. the callback is of type TmiSession.(CommandMessageContext) → Unit. The example shows a destructured CommandMessageContext.

  2. invoke the onGreetCommand as part of a tmiClient’s onMessage actions. this corresponds to the function receiver TmiSession, and it is the incoming TmiMessage (lambda parameter).

Tip

The OnCommandAction type is a subtype of (TmiSession, TmiMessage) → Unit and therefore has to be invoked with according objects as parameters within an onMessage block.

OnCheerAction

The io.github.sykq.tcc.action.OnCheerAction represents an action in response to an incoming message, which will only be executed if the message consists of a cheer (Bits donation) which fulfills a given condition linked to the amount of cheered Bits.

The following examples will send the message Thank you user for n bits! in response to an incoming cheer, if the Bit amount was greater than 100 Bits (where user is the author of the message and n is the amount of Bits donated):

Send a message in response to an incoming cheer
val onBigCheer = OnCheerAction(CheerAmountCondition.greaterThan(100)) { tmiMessage, cheerAmount -> (1)
    textMessage("Thank you ${tmiMessage.user} for $cheerAmount bits!")
}

tmiClient.block {
    onBigCheer(this, it) (2)
    // potentially other actions
    // ...
}
  1. the callback is of type TmiSession.(TmiMessage, Int) → Unit, where the Int parameter is the amount cheered.

  2. invoke onBigCheer as part of a tmiClient’s onMessage actions. this corresponds to the function receiver TmiSession, and it is the incoming TmiMessage (lambda parameter).

Tip

The OnCheerAction type is a subtype of (TmiSession, TmiMessage) → Unit and therefore has to be invoked with according objects as parameters within an onMessage block.

CheerAmountCondition

The singleton io.github.sykq.tcc.action.CheerAmountCondition contains several predefined conditions which can be used to test incoming cheer amounts, e.g.:

exactly(cheerAmount)

for checking that a cheer equals as a specific amount.

OnUserNoticeAction

The io.github.sykq.tcc.action.OnUserNoticeAction represents an action response to an incoming message of type USERNOTICE, which will only be executed if the message is a USERNOTICE.

Change internal state in response to a sub or resub USERNOTICE in a joined channel
val onSubOrResub = OnUserNoticeAction(UserNoticeType.SUB, UserNoticeType.RESUB) { (1)
    changeSomeInternalState(it) (2)
}

tmiClient.block {
    onSubOrResubAction(this, it) (3)
    // potentially other actions
    // ...
}
  1. perform the specified action if the USERNOTICE is of sub-type sub or resub. The sub-type is extracted from the tag with key msg-id.

  2. it is the incoming message, this the active TmiSession

  3. invoke onSubOrResub as part of a tmiClient’s onMessage actions. this corresponds to the function receiver TmiSession, and it is the incoming TmiMessage (lambda parameter).

Tip

The OnUserNoticeAction type is a subtype of (TmiSession, TmiMessage) → Unit and therefore has to be invoked with according objects as parameters within an onMessage block.

Connecting with multiple accounts (Spring Boot)

TODO, but see TmiProperties for now.

BotRegistry (Spring Boot)

TODO

BotBase/Bot/PublishingBot (Spring Boot)

TODO

Spring Boot service sample project

A sample project consisting of a Spring Boot web service application which uses the features of this library can be found at https://github.com/sykq/twitch-chat-bot-service

Building

Important

Builds with JDK 16+ won’t succeed since kapt uses some internal API which is no longer accessible in those builds.
There is a solution by setting several according compiler args (--add-opens …​, see https://youtrack.jetbrains.com/issue/KT-45545#focus=Comments-27-4862682.0-0), but for now I’ll stay on JDK 11 to build.

About

A Kotlin library, providing integration of Twitch IRC capabilities within JVM based applications (mainly intended for use with Spring Boot) by leveraging Spring’s WebSocket Support.

Topics

Resources

License

Stars

Watchers

Forks

Packages 3

 
 
 

Languages