Help us understand the problem. What is going on with this article?

Server Side Swift(v4.0.3)でLINE BOTの作成 (とハマったところ) #love_swift

More than 1 year has passed since last update.

結構ハマったんですけど、エコーボット作れたので記録します。

Swift愛好会合宿 2018/1/20-1/21で記事書いてます。 #love_swift

やっぱり言語勉強にBOTはいい題材な気がします。

環境構築

こっちにまとめました。

バージョンなどは

  • Ubuntu 16.04
  • Swift 4.0.3
  • Vapor 2.4.0

となっています。

Package.swiftの準備

Package.swift
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "webapp",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        .package(url: "https://github.com/vapor/vapor.git", from: "2.4.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "webapp",
            dependencies: ["Vapor"]),
    ]
)

main.swiftの編集

main.swift
import Vapor

let drop = try Droplet()
let endpoint = "https://api.line.me/v2/bot/message/reply"
let accessToken = ""

drop.get("hello") { req in
    print(req)
    return "Hello Vapor!!!"
}

drop.post("callback"){ req in
    print(req);

    guard let object = req.data["events"]?.array?.first?.object else{
        return Response(status: .ok, body: "this message is not supported")
    }

    guard let message = object["message"]?.object?["text"]?.string, let replyToken = object["replyToken"]?.string else{
        return Response(status: .ok, body: "this message is not supported")
    }

    print("-----------------");
    print(message);

    var requestData: JSON = JSON()
    try requestData.set("replyToken", replyToken)
    try requestData.set("messages", [
        ["type": "text", "text": message]
    ])

    let response: Response = try drop.client.post(
        endpoint,
        query: ["name": "mybot"],
        [
            "Content-Type": "application/json",
            "Authorization": "Bearer \(accessToken)"
        ],
        requestData
    )

    print(response)
    return Response(status: .ok, body: "reply")
}

try drop.run()

実行

$ swift build
$ ./.build/x86_64-unknown-linux/debug/webapp

これでおうむ返しのLINE BOTができました。

この辺をみると良さそう

ハマったポイントはHTTP Request

基本的にはVapor + LINE Messaging API で Bot を開発するを参考にしながら書いたのですが、

あとはこのリクエストをURLSessionに投げれば・・・と思ったのですが、UbuntuでURLSessionを使うと何故か正しく機能しないので(corelib-foundationのバグですかね?)、実際にはcurlをTask(Process)を使って叩くようにしています。Dropletのclientを使ってPOSTしてもダメだったのでよく分からない。

と参考記事にも書いてあった通り、URLRequest(url: URL(string: endpoint)!)が動いてくれず、VaporのHTTP Clientのdrop.client.post()なども調べたのですが、ドキュメントに書いてある記述だとエラーになり、Header情報を付加しないでのリクエストだと問題ないけどLINE BOTの場合はAuthorizationヘッダーにシークレット情報を詰め込む必要があったりと、やり方を検討するところでかなり時間がかかりました汗

ドキュメントに書いてある

try drop.client.get("http://some-endpoint/json", headers: [
    "API-Key": "vapor123"
])

の記述はどうやらもう使えない(?)みたいで、 Server-Side-Swift VaporでAPIを作って学んだことまとめに書いてあるHTTPリクエストのやり方だとコンパイルエラーが出なかったです。

let response: Response = try drop.client.get("https://qiita.com/api/v2/items", query: [
    "page": 1,
    "per_page": 100,
    "query": "user:hirothings"
], [
    "Authorization": "Bearer hogehogehoge"
])

また、全くドキュメントに書いてなかったですが、Vaporでの実装例をGitHubで探して

https://github.com/FabrizioBrancati/SwiftyBot/blob/master/Sources/SwiftyBot/main.swift#L403

こちらのソースに行き着きました。どうやらPOSTリクエストのBODYは一番最後の引数になるようです。

ドキュメントではdrop.client.get(エンドポイント,ヘッダー)といった呼び出し方でしたが、
実際は drop.client.get(クエリ,ヘッダー,ボディ)という指定の仕方でした。

VaporのHTTP Clientの仕様が変わってるようでしたがドキュメントにも記載がないので大元のコードを探してたけどたどり着けず... 隣にいた@jollyjoesterさんに助けてもらい(ありがとうございました!)

https://github.com/vapor/engine/blob/master/Sources/HTTP/Request/Request.swift#L12-L17

にたどり着き確認できました。

Request.swift
    public init(
        method: Method,
        uri: URI,
        version: Version = Version(major: 1, minor: 1),
        headers: [HeaderKey: String] = [:],
        body: Body = .data([])
    )

ログを見てたら2.0.0-bata.3以降はこの仕様になっているようです。
ドキュメントが追いついてない可能性がたかそうですね..

ということでdrop.client.post()endpoint,query,header,bodyの順番で引数を与えることで利用できます。

try drop.client.post(
        endpoint,
        query: ["name": "mybot"],
        [
            "Content-Type": "application/json",
            "Authorization": "Bearer \(accessToken)"
        ],
        responseData
    )

何はともあれ、これで最新バージョンのServer Side SwiftでLINE BOTを作ることができました!

まだまだServer Side Swiftは調べてても日本語記事が全然出てこなく、発展途中感がハンパないですが興味のある人がいたら参考にしてみてもらえたら幸いです。

n0bisuke
プロトタイピング専門スクール「プロトアウトスタジオ」で教えたりしてます。 プロフ -> https://dotstud.io/members/n0bisuke
https://protoout.studio
dotstudio
全ての人がモノづくりを楽しむ世界を目指して活動しています。
https://dotstud.io
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