コードレビューをチームにリクエストするSlack BotをSwift実装したお話🚢

# GithubでコードレビューをチームにリクエストするSlack BotをSwift実装したお話🚢

サーバーサイドSwiftで代表的なフレームワークVaporを使って、チーム開発をしているレポジトリーでPull Requestを出した時に同じチーム内の誰かにランダムでレビューをアサインするSlack Botを実装しました！

職人のように洗練されて行って欲しいのでGithubShokuninと名付けました👍

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">GithubShokunin 1.0.0-beta.1完成した🎉<a href="https://t.co/TSrent6A3u">https://t.co/TSrent6A3u</a></p>&mdash; yuzushioh (@yuzushioh) <a href="https://twitter.com/yuzushioh/status/812547425609007104">December 24, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

### 現状のGithubShokuninができること

現状のGithubShokuninができることは以下の一個だけです😇

- Slackで以下のようにメンションすることでGithub上でメンバーにランダムでReviewをリクエストできる。

```
@Bot review #PR Number
```

こんな感じです！

<img width="770" alt="Screen Shot 2016-12-24 at 15.25.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/80383/a7d4c760-1f1a-8231-d0d1-b88b94074f8c.png">

### GithubShokuninのセットアップ

#### Vaporのインストール方法は[ReadMe](https://github.com/yuzushioh/GithubShokunin#githubshokunin)にまとめてあります！

- Slack BotのTokenと名前を`bot-config.json`に書き込む

```json
{
    "token": "xoxo-2h4j43n5g3i2mn24g",
    "bot_name": "GithubShokunin"
}
```

- GithubのTokenやTeamIdをセットする

※レポジトリー上でReviewを行うチームを作成する必要があります！

```json
{
    "owner_name": "yuzushioh",
    "repo_name": "GithubShokunin",
    "review_team_id": "12345",
    "github_token": "biub3h24vb53vjjv2v42v24g(sample)"
}
```


## Vaporについて

#### [Vapor](https://github.com/vapor/vapor)
#### インストール [ReadMe](https://github.com/yuzushioh/GithubShokunin#-vapor)

以下のコードで、Hello World!を実装できます！

```swift
let drop = Droplet()

drop.get("/") { request in
    return "Hello World!"
}

drop.run()
```


## GithubShokuninの実装

### Hubotが使えないのでSlackの[Real Time Messaging API](https://api.slack.com/rtm)を使ってChatを監視しています。

#### Vaporの[WebSocket](https://github.com/vapor/engine)

以下のようにWebSocket Clientを作成できます。

```swift
import Engine
import WebSockets

try WebSocket.connect(to: url) { ws in
    print("Connected to \(url)")

    ws.onText = { ws, text in
        print("[event] - \(text)")
    }

    ws.onClose = { ws, _, _, _ in
        print("\n[CLOSED]\n")
    }
}
```

メンションされたユーザーが`bot-config.json`で指定したBotならば、そのチャットの内容に応じて処理を行うようになっています。

#### チャットの内容を元に`Content`を作成する

##### 現状はチャットの内容をもとに以下の3つにわけている。

```swift
enum MessageContent {
    case requestReview(prNumber: String)
    case commandLine
    case unknown
}
```

### Contentを元に必要なAPIを叩く

Vaporの[Droplet](https://vapor.readme.io/v0.14/docs/application)を使ってGithubのAPIを叩く

```swift
let response = try drop.client.get("http://github.com", query: ["q": "test"])
```

ResponseのJSONをModelにマッピングする。

```swift
guard let json = response.json else {
    throw BotError.invalidResponse
}
        
let pullRequest = try PullRequest(node: json.node)

```

### 作成したModelを元にBotのレスポンスを作成する

- SlackMessageというModelを用意している

```swift
struct SlackMessage {
    let id: UInt32
    let channel: String
    let text: String
    let type: String
    
    init(to channel: String, text: String) {
        self.id = UInt32.random()
        self.channel = channel
        self.text = text
        self.type = "message"
    }
}
```

このモデルはVaporの`NodeConvertible`に準拠していて、モデルからNodeを作り出すことができます！

```swift
extension SlackMessage: NodeConvertible {
    func makeNode(context: Context) throws -> Node {
        return try Node(node: [
            "id": id,
            "channel": channel,
            "type": "message",
            "text": text]
        )
    }
    
    init(node: Node, in context: Context)  throws {
        id = UInt32.random()
        channel = try node.extract("channel")
        text = try node.extract("text")
        type = try node.extract("type")
    }
}
```

作成いたSlackMessageをメンションがあったSlack Channelに投げ返す！！

```swift
try WebSocket.connect(to: url) { ws in
    print("Connected to \(url)")

    ws.onText = { ws, text in
        ......
        let response = SlackMessage(to: message.channel, text: responseText)
        try socket.send(response)
    }

    ws.onClose = { ws, _, _, _ in
        print("\n[CLOSED]\n")
    }
}
```

##　思ったこと

- Hubotが使えないのでCoffee Scriptでかいた時より格段にコード量が増えてしまった。
- Vaporは初めて2週間くらいだがなかなか使いやすいし書きやすい！

Vaporの参考にしたり、是非チーム開発で使ってみてください！