Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What are the problem?

SwiftPMとSlackKitを使ってSlackBotを作った話

この記事は Willgate Advent Calendar 2019 の15日目の記事です。

昨日は @ryota_hnk さんの OKE(Oracle Container Engine for Kubernetes) を Rancher で監視する でした。
本日は、ウィルゲートでPHPやJavaScript、CSSで開発している@N06ARです!
よろしくお願いします!

はじめに

私は普段の業務でSwiftを書くことがありませんが、最近で昔作ったiOSアプリをSwift1からSwift5にジャンプアップデートする機会がありました。
久しぶりのSwiftが楽しかったので、SlackKitを使いSlackBotを作ってみました。
そこでこの記事では、SlackKitを使ったSlackBotの導入をご紹介します!

ここで書かれていること

この記事では次の3点について書いています。

  • SlackKitの導入方法
  • SlackKitを使ったSlackでのメッセージの送受信
  • SlackKitで作ったSlackBotを使ってみての感想x

この3点で、私が失敗や上手く行かなかったところは(:bomb:)を書いています。
もし、この記事を読んで作ろう!と思った方は、参考にしてみてください!

開発環境

開発環境は、次のバージョンで開発しました。

バージョン
OS macOS Catalina
Swift 5.1
SlackKit 4.5.0
Xcode 11

0.事前準備

コードを書き始める前に、事前に必要なものを準備します。

0-1.SlackでAPI Tokenの発行

はじめにSlackからAPI Tokenを取得してきます。
このURLからAPI Tokenを発行します。

Botの作成

Botの名前を入力し、botを追加するボタンを押すと次の画像が一部として表示される画面が表示されます。
画像のところにAPI Tokenがあるのでメモしておいてください。
(画像のTokenはダミーです)

API Tokenの表示箇所

0-2.(オプション)LibreSSLのインストール

SlackKitの依存パッケージの中にSwiftNIOがあり、LibreSSLが必要になります。
インストールしていなければ1-5.ビルドでビルドに失敗するのでインストールします。(:bomb:

$ brew install libressl

1.SwiftPMの設定

SwiftPMを使いSwiftパッケージディレクトリ(プロジェクトフォルダ)を作成し、SlackKitを使えるようにします。

1-1.SlackKitの実行パッケージディレクトリの作成

次のコマンドを実行して、SlackBotを作るディレクトリを作成し、実行パッケージディレクトリを作成します。
swift package init --type executableを実行した時に特に入力を求められませんが、問題ありません。

$ mkdir 【プロジェクト名】
$ cd 【プロジェクト名】
$ swift package init --type executable

1-2.(オプション)実行確認

次のコマンドを実行した後 Hello, world!と表示されたら準備完了です!

$ swift build
$ ./.build/debug/【プロジェクト名】

1-3.SwiftPMで使用するパッケージにSlackKitを追加

次にPackage.swiftを編集します。
Package.swiftをエディタで開きdependenciesの箇所に今回使用するパッケージのSlackKitを追加します。

Package.swift
import PackageDescription

let package = Package(
    name: "【プロジェクト名】",
    dependencies: [
        .package(url: "https://github.com/pvzig/SlackKit.git", .upToNextMinor(from: "4.5.0"))
    ]
)

1-4. (オプション)テストもしたい場合

テストもしたい場合は、targetsにもSlackKitのパッケージを追加する必要があります。
SlackKitの追加を忘れると1-5.ビルドでビルドに失敗します。(:bomb:

Package.swift
import PackageDescription

let package = Package(
    name: "【プロジェクト名】",
    dependencies: [
        .package(url: "https://github.com/pvzig/SlackKit.git", .upToNextMinor(from: "4.5.0"))
    ],
    targets: [
        .target(
            name: "【プロジェクト名】",
            dependencies: ["SlackKit"]),
        .testTarget(
            name: "【プロジェクト名】Tests",
            dependencies: ["【プロジェクト名】","SlackKit"]),
    ]
)

1-5.ビルド

Package.swiftを編集した後に、 swift buildをするとSlackKitを取得し使えるようになります。


$ swift build

1-6.(オプション)XcodeやAppCodeを使う場合

XcodeやAppCodeなどのエディタを使いたくなると思います。その時は次のコマンドを実行して.xcodeprojを生成してください。


$ swift package generate-xcodeproj

ただし、Package.swiftにパッケージを追加変更する度に、.xcodeprojを再生成する必要があります。(:bomb:
AppCodeで発生しましたが、プロジェクト内のファイル名を変更したときビルドエラーになりました。
その時も再生成してあげると良さそうです。(:bomb::bomb:

2.SlackBotのコードを書く

ここから実際にSlackKitを使ってSlackbotのコードを書いていきます。

2-1.main.swiftの編集

まずはじめに、今回SwiftPMで作成した のエントリーポイントのmain.swiftを編集します。
今回は、SlackBotの機能をmain.swiftに書かずSlackBotクラスを作成し分離します。
SlackBotのインスタンス生成時に、0-1.SlackでAPI Tokenの発行 で準備したAPI Tokenを引数に入れます。

Sources/【プロジェクト名】/main.swift
import Foundation
SlackBot(apiToken: "xoxb-XXXXXXXXXXXXXXXXXXXX")
RunLoop.main.run()

2-3.SlackBotのクラスを作成

次に、SlackBotクラス用のファイルを作成し、コードを作成していきます。
コンストラクタ(init)で、API Tokenを受け取れるようにして、
SlackKitでSlack Web APISlack Real Time Messaging API使えるようにするためにAPI Tokenを渡します。
API Tokenを渡すには、addWebAPIAccessWithTokenaddRTMBotWithAPITokenを使用します。

Sources/【プロジェクト名】/SlackBot.swift
import Foundation
import SlackKit

class SlackBot {
    /// SlackBotのKit
    let slackkit = SlackKit()

    init(apiToken: String) {
        slackkit.addWebAPIAccessWithToken(apiToken)
        slackkit.addRTMBotWithAPIToken(apiToken)
    }
}

2-4.メッセージを受け取る

次にSlackからメッセージを受け取ります。
メッセージは、SlackKitのnotificationForEventを使い受け取ります。
受け取ったメッセージの内容は、クロージャのevent.messageに入っており、
event.messageには発言したユーザIDやチャンネルID、スレッドID、メッセージの文章等が入っています。

Sources/【プロジェクト名】/SlackBot.swift
import Foundation
import SlackKit

class Slackbot {
    /// SlackBotのKit
    let slackkit = SlackKit()

    init(apiToken: String) {
        slackkit.addWebAPIAccessWithToken(apiToken)
        slackkit.addRTMBotWithAPIToken(apiToken)
        slackkit.notificationForEvent(.message) { [weak self] (event, client) in
            // ここに聞いたメッセージを受信したときのメッセージを書く!
        }
    }
}

【余談】SlackKitのサンプルコードのように書くときの注意

サンプルコードのこの箇所のように、実装した場合でAPI Tokenを渡すと
listenメソッド実行時に、clientクラスが生成されないためかnilのためguard構文でreturnされるので何もせずに終わります。(:bomb:

Sources/【プロジェクト名】/SlackBot.swift
import Foundation
import SlackKit

class SlackBot {
    /// SlackBotのKit
    let slackkit = SlackKit()

    init(apiToken: String) {
        slackkit.addWebAPIAccessWithToken(apiToken)
        slackkit.addRTMBotWithAPIToken(apiToken)
        slackkit.notificationForEvent(.message) { [weak self] (event, client) in
            self?.listen(client?.client, message: event.message)
        }
    }

    private func listen(_ client: Client?, message: Message?) {
        guard let client = client,
              let message = message,
              let text = message.text,
              let channel = message.channel
                else {
            return
        }
    } 
}

2-5.メッセージを送信する

メッセージを受け取れるようになったので、今度はメッセージを送れるようにします。
今回は、全てのメッセージに大してこんにちはと返事をするメッセージを送信する処理を書きます。
メッセージの送信には、SlackKitWebAPIクラスのsendMessageメソッドを使用します。
sendMessageメソッドの引数には、送信するチャンネル表示するユーザネームアイコン画像のURLメッセージを入れてください。

Sources/【プロジェクト名】/SlackBot.swift
import Foundation
import SlackKit

class SlackBot {
    /// SlackBotのKit
    let slackkit = SlackKit()

    init(apiToken: String) {
        slackkit.addWebAPIAccessWithToken(apiToken)
        slackkit.addRTMBotWithAPIToken(apiToken)
        slackkit.notificationForEvent(.message) { [weak self] (event, client) in
            // ここに聞いたメッセージを受信したときのメッセージを書く!
            slackkit.webAPI?.sendMessage(
                channel: message.channel,
                text: "こんにちは",
                username: "【botの名前】",
                iconURL:  "【アイコン画像のURL】",
                success: nil,
                failure: { (error) in
                    print("メッセージ送信失敗:\(error)")
                }
            )
        }
    }
}

これでbotが入ったチャンネルか、DMでメッセージを送った時にこんにちはと返事するようになりました!
メッセージ以外にもAttachmentクラスを使いAttachmentをつけることもできます。

【余談】アイコン画像とBotの名前がない場合

実のところ表示するユーザネームアイコン画像のURLは、必須ではないので引数に入れず送信することができます。
仮に引数なしで送信をした場合は、次の画像の結果になります。
表示するユーザネームとアイコン画像のURLがない場合の画像
アイコン画像がbotの画像になり、名前がbotになります。(:bomb:
そのため、名前と画像のURLを渡たしてあげる必要があります。

2-6.起動!!

最後ビルドして起動します。
起動には、デバックモードの起動とリリースモードの起動があります。

2-6-1.デバックモードで起動!

ビルドは今まで通り次のコマンドを入力してください。


$ swift build

最後に次のコマンドで起動します!
コマンド実行するとシェルの入力はできなくなるので、nohupを使いバックグラウンド実行すると良いです。


.build/debug/【プロジェクト名】

2-6-2.リリースモードで起動!

ビルドコマンドは今までとは少し異なります。


$ swift build -c release

最後に次のコマンドで起動します!


.build/release/【プロジェクト名】

3.macOS以外で起動したい!

SlackBotを動かすのにLinuxやサービスを使って動かしたいことがあると思います。
そこで、HerokuDockerで動作させる方法をご紹介します。

3-1.Herokuにデプロイしたい場合

Herokuにデプロイする場合は、HerokuCLIを使用します。
そのため、次のコマンドでHerokuCLIをインストールしてください。

brew tap heroku/brew && brew install heroku

3-1-1.Heroku用設定ファイルの追加

はじめに、Heroku用の設定ファイルを追加します。
次のコマンドを実行して、.swift-versionProcfileを作成してください。

$ echo 5.1 > .swift-version
$ echo slackbot: .swift-bin/【プロジェクト名】 > Procfile

Herokuでデプロイするために、heroku-buildpack-swiftを使用します。
heroku-buildpack-swiftでは、ビルドしたときのディレクトリ名は.buildではなく.swift-binなので気をつけてください。(:bomb:

3-1-2.Herokuの設定

次にHerokuCLIでアカウントにログインし、Herokuにアプリケーションを作成します。
アプリケーション作成時に、heroku-buildpack-swiftをbuildpackとして追加します。

$ heroku login
$ heroku create --buildpack kyle/swift 【プロジェクト名】

3-1-3.デプロイ

Herokuに、コードをpushすればheroku側でデプロイが開始されます。

$ git push heroku master

起動するには、Herokuの画面からslackbotDynosをオンにします。

3-2.Dockerのコンテナにデプロイしたい場合

次は、DockerでSlackBotを起動する方法をご紹介します。

3-2-1.Dockerfileを書く

まず、プロジェクトのディレクトリに次のDockerfileを追加してください。
(他のディレクトリにDockerfileを置く場合はCOPYの第1引数を修正してください。)
libssl-devをインストールしていますが、これはSwiftNIOで必要となるためです。
libssl-devをインストールしないとbuildできません。(:bomb::bomb::bomb:

FROM swift:5.1
RUN chmod -R o+r /usr/lib/swift/CoreFoundation && \
    apt-get update && \
    apt-get install -y libssl-dev
COPY . /【プロジェクト名】
WORKDIR /【プロジェクト名】
RUN swift build -c release
CMD [ "/【プロジェクト名】/.build/release/【プロジェクト名】"]

3-2-2.コンテナのビルド

次のコマンドを実行し、Dockerfileを元にビルドします。

docker build . -t slackbot

3-2-3.起動!

最後に、次のコマンドを実行してコンテナを立ち上げてください。

docker run --detach -t slackbot

これで、DockerでSlackBotの起動できます。

4.SwiftとSwiftPM、SlackKitのよかったところ、困ったところ

今回SwiftでSlackBot作りました、作っていて次のよかったところと困ったところがありました。

良かったところ

  • macOSなら開発環境がすぐにできる
  • sendMessegeメソッド

macOSならSwiftの開発環境が簡単にできる

さすが開発元がAppleなだけあり、さえXcodeをインストールすればSwiftの開発環境はおおよそ揃えられました。
SlackKitでは追加でライブラリを一つインストールする必要がありましたが
また、Xcode11からはSwiftPMのパッケージを管理できるようになりました。

sendMessegeメソッド

余談でも書きましたが、SlackKitのWebAPIを使うとSlackBotの名前とアイコン画像を変更することができます。
細かい仕様を調ベていないので詳細は分かりませんが、昔Node.jsBotKitでSlackBotを作った時はなかった仕様でした。
名前と画像が切り替えられるサンプル
これを逆手にとると、画像のようにメッセージごとに名前とアイコン画像を切り替えることができます。
これが不具合なのか、いつ修正されるか分かりませんが、アイコンを切り替えて感情豊かなBotを作れそうです。

困ったところ

  • Swiftのパッケージの大半がiOSのパッケージ
  • Herokuやbitbucket-pipelineなどがSwiftに公式で対応していない
  • 記事などの知見が少なく古い

Swiftのパッケージの大半がiOSのパッケージ

Googleカレンダーを使った機能を実装したいときに、使えそうなパッケージがないか調べるとiOSでしか使えないパッケージしかない等のことが多かったです。
使ったことがないので正確なことは言えませんが、Vaporのパッケージは色々揃っているので使うと楽になりそうです。

Herokuやbitbucket-pipelineなどがSwiftに公式で対応していない

AttrasianやHerokuなどのWebサイトを見てみるとわかりますが、自動デプロイに公式で対応してません。
そのため、Dockerで対処するか、すでにあるライブラリやパッケージ等を頼りに対応する必要がありました。

記事などの知見が少なく古い

良くありがちなことですが、今では使用できないものが書かれている記事が多かったです。
主にSlackKitを導入するにあたり、資料が古くSwiftやSwiftPMのバージョンアップにより上手く動作しないことがありました。
特にlibressl関連とHerokuDockerは、問題が解決するより、SlackBotの作成時間の方が短かったです。

今回作ったSlackBot

ななちゃんBot

今回作ったSlackBot私の家にいる なな(猫)を模したBotをつくりました。
ななちゃんBotは、私の分報チャンネルに生息しています。

Botができること

ななちゃんbotには、次の機能を実装しました。

  • メッセージにあるキーワード(ななちゃん)があるとリアクションする
  • 勤怠の打刻をリマインドとリンクの表示
  • GoogleMapでランチまたは、居酒屋を検索したURLの表示
  • ユーザを祝ったり応援する機能

残念ながら二つ目の勤怠の打刻の通知以外はあまり使われていませんが、要望や個人的に欲しかった機能を実装しました。

SlackBotを分報に入れて気がついたこと

よかったこと

Botにキャラクターを持たせること

今回たまたま思いつきでしたが、家のなな(猫)をBotにしたことは正解でした。
ななちゃんBotの最初期は、個人のSlackチームで作っていたことや、SlackKitが手探りだったこと、環境が出来上がっていなかったこともありバグが多かったです。
それに、確認できる環境がSlack上のため、本番で動作確認をしている状態でした。

ななちゃんの想定外の動き

この時はバグを発見したら極力すぐに直すようにしていました。
ですが、このバグに関してSlackでは次のような反応があり、バグがあっても良いのではという意見がありました

この自由沙がむしろ猫っぽい

この件について社内の同僚から、ご紹介いただいたこの記事に次のことが書かれていました。

バグは極力直さない
バカな子ほど可愛いという諺がある
怒られにくくするには愛着がわきやすい Bot を作るのが大切ということです

Botに何かしらのキャラクターを入れると愛着がわく

読んでみて確かにと思いました。
記事の通り愛着が沸きやすいマスコットとして作った方が周囲から可愛がられ、今後社内で広く使うには良い方法だと感じました。
また、Botで遊んでいるのを見ていると開発のモチベーションも上がるため、偶然でしたが猫のBotとして作ったのは正解だったと思いました。

失敗したこと

デプロイ方法を見つけるまでローカルで起動していた

今回HerokuとDockerでの起動方法をご紹介しましたが、先月ではまだできないでいて、
最初は私のローカル(MacBook Pro)で動作させていていました。
ただ、ローカルで動作していると、Sleepするとき会議の移動でネットワークが一時的に切断されたときに、オフラインになり使えなくなる状態ができ不便に思うことが増えました。
また、その度に再起動していたのですが、何度もやっていると流石に面倒でした。
そのためMac以外で常時起動できる方法を探しHerokuとDockerに行き着きました。

最後に

今回SlackBotをSwiftで作ることで、Swiftのverison1から久しぶりにがっつりコードをかきました。
Version1のころと比較して、結構文法が変わっていましたが変わらないところは変わっていなかったのですんなり使えるようになりました。
また今回初めて技術記事を書きましたが、こんなに文章を書くのが難しいとは思いませんでした。
普段から情報を発信している方々の凄さに驚きました。
最後になりますが、もしこの記事の内容で誤り等ありましたら、お教えいただけますと幸いです。

参考・参照文献

明日は@8845musignさんです!
よろしくお願いします!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What are the problem?