IRCクライアントJavaライブラリKittehを使ってみる

JavaのIRCクライアントライブラリの[Kitteh](https://kitteh.org/irc-client-library/)を使ってみます。

Kitteh IRC Client Libraryを略してKICLとも呼ばれるようです。

# 環境

OS：Windows10
Java：openjdk 17.0.6 2023-01-17 LTS
Maven：3.8.7

# 設定

## Mavenのプロジェクトにインポート

```xml:pom.xml
    <dependency>
      <groupId>org.kitteh.irc</groupId>
      <artifactId>client-lib</artifactId>
      <version>8.0.0</version>
    </dependency>
```

## IRCサーバのSSL証明書をjavaキーストアに登録する[^1]（SSL通信をする場合）

[^1]:https://kitteh-irc-client-library.readthedocs.io/en/latest/advanced/tls_import/

今回接続するIRCサーバはSSL通信に自己証明書を使っているので、KICLからSSL通信をするためにIRCサーバのSSL証明書をjavaキーストアに追加します。

以下のコマンドを適宜修正して実行します。

```powershell
keytool -trustcacerts -keystore ./cacerts -noprompt -importcert -alias irchost -file "C:\Program Files\UnrealIRCd 6\conf\tls\server.cert.pem"
```

`-keystore`オプションに指定する`cacerts`ファイルはJAVA_HOME/lib下にあるはずなので、環境に合わせて適切にパスを指定します。

`-file`オプションには登録したいSSL証明書のパスを指定します。今回はUnrealIRCdでIRCサーバ立ててたのでそのサーバ証明書を指定しています。

実行すると、パスワードを聞かれるので`changeit`を入力します。

`証明書がキーストアに追加されました`というメッセージが出力されれば完了です。

# サンプルコード

[ドキュメント](https://kitteh-irc-client-library.readthedocs.io/en/latest/)のサンプルコードを参考に次のようなコードを書いてみました。
実行することでローカルに立てていたIRCサーバの6697ポートにSSL通信でメッセージ送受信ができました。
確認方法は、別にGUIのIRCクライアントを開いてチャネルやトークに届いたメッセージを確認しました。
コード中のコメントにも書きましたが、非暗号化通信にもすぐに切り替えられました。

処理内容には特に深い意味はありません。

```java:App.java
public class App {
    public static class Listener {
        @Handler
        public void onUserJoinChannel(ChannelJoinEvent event) { // チャンネルに入ったときの処理
            if (event.getClient().isUser(event.getUser())) { // 自分が入室したとき
                event.getChannel().sendMessage("こんにちは Kitteh's here for cuddles.");
                return;
            }
            event.getChannel().sendMessage("Welcome, " + event.getUser().getNick() + "! :3");
        }

        @Handler
        public void onMessageSent(ChannelMessageEvent event) { // メッセージを受信したときの処理
            System.out.println(event.getMessage());
            event.getChannel().sendMessage(event.getMessage() + " from " + event.getActor().getNick());
        }
    }

    public static void main(String[] args) {
        Client client = Client.builder().nick("miuKor").server().host("127.0.0.1").password("password")
            // .port(6667, SecurityType.INSECURE) // ここをアンコメントすると6667ポートに暗号化なしで通信する
            .then().buildAndConnect();
        client.getEventManager().registerEventListener(new Listener());
        client.addChannel("#kicl"); // #kiclチャンネルに入る
        // 1秒に一回メッセージを送る
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            client.sendMessage("#kicl", "こんにちは from miuKor"); // 第１引数はニックネームを入力すれば個人トークに送れる
        }
    }
}
```

# ユーザがサーバーに接続しているか確認する

ユーザがIRCサーバに接続しているか確認するにはISONコマンドが使えます。[^2]
[^2]:https://www.rfc-editor.org/rfc/rfc1459.html#section-5.8

Kittehにはこのコマンド用に個別に用意されたクラスが存在しないので、コマンドを生で実行することを考えます。

一緒にコマンド結果を処理するためのイベントリスナーも実装します。
ISONコマンドのレスポンスには`303`というコードが含まれていますのでこのコードを含むレスポンスを処理するリスナーを実装します。[^3]

[^3]:https://www.rfc-editor.org/rfc/rfc1459.html#section-6.2

以下のようにして実行できます。

```java
public class App {
    public static class Listener {
        @NumericFilter(303)
        @Handler
        public void onIson(ClientReceiveNumericEvent event) {
            System.out.println(event.getParameters().get(1)); // オンラインであれば user1 と出力される
        }
    }

    public static void main(String[] args) {
        var client = Client.builder()
                .nick("kitteh")
                .server()
                .host("127.0.0.1")
                .password("mzbot123")
                .port(6667, SecurityType.INSECURE)
                .then()
                .buildAndConnect();

        var listener = new Listener();
        client.getEventManager().registerEventListener(listener);

        client.sendRawLine("ISON user1"); // user1 がオンラインかどうか確認する
    }
}
```

## 連続送信の間隔時間を設定する

多数のメッセージを連続で送信する場合、その送信の間隔時間を設定できます。

`Client#setMessageSendingQueueSupplier`を使用します。

```java
var client = Client.builder().nick("kitteh")...省略....buildAndConnect();

// 複数行テキストは改行で分割して1つずつ送信するように設定
client.setMessageCutter((message, size) -> List.of(message.split("\r?\n")));
// 連続送信の時間間隔を100ミリ秒に設定
client.setMessageSendingQueueSupplier(SingleDelaySender.getSupplier(100));

以降で client.sendMultiLineMessage などで複数行あるテキストを一行ごと100ミリ秒の間隔で連続で送られる。
```

ちなみにIRCサーバ側にanti-flood機能などがあって、短い間隔で多くメッセージを送っても一定以上より送信間隔時間が短くならない場合があります。

# 最後に

使ってみた感触としてはコード量も少なく手軽にIRC通信できる印象です。

[ドキュメント](https://kitteh-irc-client-library.readthedocs.io/en/latest/)のサンプルコードが豊富でうれしいです。

SSL通信のための設定が単純に私の知識がなくて一番手こずりました（笑）