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

GAE/GoでAppStoreのレビューをSlackに通知してくれるやつ作った

More than 5 years have passed since last update.

smart newsのレビューをslackに通知したサンプル
Slack.png

まだ、実際のAppStoreのレビューと見比べるとちゃんと全部拾えてないとか変なところありますが、そこそこできたので晒します。

リポジトリ

使い方とかはこちらで解説。アプリ3つ分ぐらいの処理であれば、GAEの無料分で十分運用できます。
https://github.com/yosukesuzuki/goisky-tools

フレームワークの選定

go言語の強者達はフレームワークなんていりません、net/httpでいいんじゃんて方が多いようなのですが、そんなに強者じゃないので、フレームワークがほしいです。

これまで、MartiniGorilla/Muxを使ったりしてましたが、Martiniは作者があまりやる気なくしていたり、Gorilla/Muxはフレームワークではなくtoolkitだと言っている通りフレームワークとしてのタガがゆるいので、自分のようなgo弱な人が使うとけっこうコードがぐじゃぐじゃになってしまいました。

Beegaeを採用

今回使ったのは、Beegoという上海在住の中国人?エンジニアが作っているフレームワークのGAE版=Beegaeです。割とわかりやすいMVC構造を持っています。

main.go <- ルーティング
┗ models/ <- モデル
┗ views/ <- ビュー/テンプレート
┗ controllers/ <- コントローラー

日本語のドキュメントはないですが、英語のドキュメントは整備されています。ユーザーコミュニティとしては中国人が多いようですが、githubでの会話は英語でできました。

その他、機能はいろいろ整備されていて、サービスをサッと作るにはなかなかいいです。

今回使ってないですが、GAEに特化したセッション機能とかも使えるようです。

ただ、ちょこちょこ、この機能なんで用意されてないの?みたいのがない気がします。ドキュメント全部把握してないだけかもしれないですが、JSONでレスポンス返すときにステータス・コード指定できないとか(JSONじゃなくてHTMLでエラーを返すなら簡単に指定できますが)。

MVCの構造

main.go

ルーティングの部分です。

func init() {
    beegae.TemplateLeft = "{{{"
    beegae.TemplateRight = "}}}"
    beegae.Router("/admin/api/v1/blobstoreimage/handler", &controllers.BlobStoreImageController{}, "*:Handler")
    beegae.Router("/admin/api/v1/blobstoreimage/uploadurl", &controllers.BlobStoreImageController{}, "*:UploadURL")
    beegae.Router("/admin/api/v1/blobstoreimage/:key_name", &controllers.BlobStoreImageController{}, "get:GetEntity;patch:UpdateEntity;delete:DeleteEntity")
    beegae.Router("/admin/api/v1/blobstoreimage", &controllers.BlobStoreImageController{})
    beegae.Router("/admin/task/iosapp/getappreview/:key_name", &controllers.IOSAppController{}, "*:GetAppReview")
    beegae.Router("/admin/task/iosapp/getreviews", &controllers.IOSAppController{}, "*:GetReviews")
    beegae.Router("/admin/api/v1/iosapp/:key_name", &controllers.IOSAppController{}, "get:GetEntity;patch:UpdateEntity;delete:DeleteEntity")
    beegae.Router("/admin/api/v1/iosapp", &controllers.IOSAppController{})
    beegae.Router("/admin/form", &controllers.AdminFormController{})
    beegae.Router("/admin/", &controllers.AdminController{})
    beegae.Router("/", &controllers.MainController{})
    beegae.Run()
}

REST APIでcontrollerのほうにGet()やPost()を実装していれば、何も書く必要ないです。メソッドごとに受けるfunctionを決めたい場合は第3引数で、

"get:GetEntity;patch:UpdateEntity;delete:DeleteEntity"

のように指定できます。

models

modelsには対応するDatastoreのKindごとにファイルを作ってます。annotationでjsonとdatastore上での名前を付けます。この部分の記述が長くなるのがイマイチなので、なんか勝手に命名規則にしたがって変えてくれるやつがほしい。というか知っていたら教えて下さい。

type IOSApp struct {
    KeyName    string    `json:"key_name" datastore:"KeyName"`
    AppID      string    `json:"app_id" datastore:"AppID"`
    Title      string    `json:"title" datastore:"Tite"`
    WebhookURL string    `json:"webhook_url" datastore:"WebhookURL"`
    IconURL    string    `json:"icon_url" datastore:"IconURL"`
    Content    string    `json:"content" datastore:"Content,noindex"`
    Region     string    `json:"region" datastore:"Region"`
    UpdatedAt  time.Time `json:"updated_at" datastore:"UpdatedAt"`
    CreatedAt  time.Time `json:"created_at" datastore:"CreatedAt"`
}

controllers

こちらも上記のmodelsに対応する形でファイルを作り、ルーティングされて来た処理を裁きます。

まず、下のように処理を書いて、this.Data["json"]のところにデータを渡すと最終的に、RenderのところでJSONにして返してくれます。このときにステータスコード渡したいのですが、そういう機能がないような。

func (this *IOSAppController) Get() {
    iosapps := []models.IOSApp{}
    _, err := datastore.NewQuery("IOSApp").Order("-UpdatedAt").GetAll(this.AppEngineCtx, &iosapps)
    if err != nil {
        this.Data["json"] = err
        return
    }
    listDataSet := map[string]interface{}{"items": iosapps}
    this.Data["json"] = listDataSet
}

func (this *IOSAppController) Render() error {
    if _, ok := this.Data["json"].(error); ok {
        this.AppEngineCtx.Errorf("iosapp error: %v", this.Data["json"])
    }
    this.ServeJson()
    return nil
}

また、JSONのREST APIで受ける処理が多いですが、一部HTMLを返すページ処理もあるので、こちらはその分ファイルが増えています。

ただ単に静的なHTMLを返す場合は以下のようにするだけです。

func (this *MainController) Get() {
    this.Layout = "layout.html"
    this.TplNames = "index.html"
}

appreviewについては、直接的にやり取りする必要がないので作ってません。

views

HTMLテンプレートが入っています。layout.htmlが外側の共通の枠になっていて、その中に各テンプレートが埋め込まれます。controllerで上のように指定します。

テンプレートの中で変数を使うにはgoでは通常 {{ variable }}のように記述しますが、今回使ったvuejsの標準のテンプレート表記とバッティングします。そこでgo側は{{{ variable }}}のように3つ括弧を続ける方式にしました。main.goのところで指定してます。

    beegae.TemplateLeft = "{{{"
    beegae.TemplateRight = "}}}"

レビューの取得処理

さて、レビューの取得処理をどうしているかというと、https://github.com/grych/AppStoreReviews というpython製のプログラムを発見してこちらの処理を参考にgoに移しました。

itunesアプリが使っていると思われるxmlを取得して、それをgoqueryというライブラリで処理しました。スクレイピングよりはだいぶいいですが、すごい処理しにくいXML構造で、ここの処理のコードはぶっちゃけだいぶ汚いです。標準のxml処理ライブラリとか使いたかったのですが、うまくいかなかったです。ベターな方法あれば書き直したいです。

GAEの特殊対応

travis ciとの連携

細かいことは前にGAE/GoのWebアプリをTravisCIで自動テスト&自動デプロイするで書きましたの参照してください。

今回新たにやったのは2つです。

最新のSDKを取得

GAEのSDKはどんどんバージョンアップしつつ、最新2つ分ぐらいしかダウンロードできなくなります。古いSDKは使わせたくないみたいですね。CIの環境づくりの時に1.9.17とかベタッとバージョン書いていると、SDKの取得処理で失敗してCIの実行が止まったりしてだるいです。これだけ直してコミットするのもだるいです。

いろいろ調べていたら、以下のURLから最新のSDKファイルのありかが、わかることがわかったのでそれを取得するスクリプト(ここはpython)を書きました。
https://www.googleapis.com/storage/v1/b/appengine-sdks/o?prefix=featured

go getを別スクリプトに

go get を.travis.ymlに書いて、依存ライブラリの取得が可能ですが、beegaeが依存するappengineライブラリはgo getではとれないのでエラーが出て、実行が止まってしまいます。それはappengineまわりは別途取得するのでエラー出ても無視したい。そこでgo getするシェルスクリプトを作って、実行しています。

#!/bin/sh
echo "running go get to fetch dependencies..."
go get github.com/PuerkitoBio/goquery
go get github.com/yosukesuzuki/beegae
echo "dependencies fetched."
exit 0

blobstore 対応

slack通知とは直接的に関係しないですが、blobstoreに画像をアップできる機能をつけています。blobstoreに画像アップしたうえで、picasaインフラで配信するURLを取得すると使い勝手がいいです。

Slack通知の際のアイコンをカスタマイズできる機能を用意してますが、その際に使いたい画像をアップロードして、picasaインフラで配信するURLの後ろに「=s57-c」とつけると57x57ピクセルの正方形画像に成形できます。(sのあとの数字は任意に変更できる)

例えば以下のとおりです。
https://lh4.ggpht.com/hwT-2OO2D1481EgmkATIR5WzfGlmh2KUkEs9ph6hBA1I9maQ7cDo7tVo9TeTppQMsBfD8e_Uezt_W0ExGaNE70CBTbTm=s57-c

元画像
元画像

=s57-cパラメータ付き
57x57

で、この機能が便利なのでblobstoreに画像をアップロードしたあとのhandler処理で、Datastoreへの登録とかしようと思っていたのですが、標準のbeegaeでは動きません。router.goのところで、ちょこちょこrequestのデータをいじくっているらしくて、それが悪さしているようでした。

他のユーザーも困っていて、pull-requestもあがってますが、取り込まれていません。
https://github.com/astaxie/beegae/pull/16

しかたないのでforkして、このpull-requestを取り込んだバージョンを自分で用意しました。

https://github.com/yosukesuzuki/beegae

ちなみにgofmtを使うとimportのところを一気に書きなおすことができます。

$ gofmt -r '"github.com/astaxie/beegae" -> "github.com/yosukesuzuki/beegae"' -w ./

ただし、go界の巨人、mattnさんによるとgofmtとよりgorenameがいいようです。
http://mattn.kaoriya.net/software/lang/go/20150113141338.htm

local unit testがうまくいかない

この問題は、クリティカルなのですが、いったん放置しています。

$ goapp test
main.go:4:2: cannot find package "controllers" in any of:

goapp serveを実行した時はちゃんとbuildできているのに、goapp testでできないってどういうことなの?MLとかで情報探していますが、解決方法がまだわかりません。

かわりにcasperjsでのe2eテストを実行しています。

フロントエンド

GAE/Goとは関係ないですが、フロントエンドについても書いておきます。

Vuejs

angularも、jqueryも選択肢から外しつつ、reactjsは学習コスト的にきつそうだったのでvuejsで記述。goでAPIだけ用意してCRUDの部分はフロントでやっています。

Coffee Script

サービス用のコードをcoffeeで書くのは初めてでしたが、mizchiさんの解説読んだらうまくコンパイル環境ができました。chromeのdeveloper toolでもブレークポイントをcoffeeのまま付けられるのでデバッグも問題無いです。

http://qiita.com/mizchi/items/10a8e2b3e6c2c3235e61

Goisky_Tools_on_GAE_Go.png

Todo

  • unit testできるようにする
  • golintとするといろいろ怒られるので修正する

おまけというか経緯

自分個人で何か作ろうと思ったら基本的に、Google App Engineです。今回は下の記事を見て、この機能欲しいと思い作ってみました。

http://qiita.com/mach/items/a1477ff0c66aa65c6ac4

デフォルトの連携機能は用意されていませんが、App Store / Google Play でのレビューも Slack に流して皆で日々チェックしています。ちょっと前まで自前でストアのレビューをスクレイピングして流していましたが、メンテが大変なので、今は AppFigures と Zapier を組み合わせて実現しています。

AppFigures見て登録しようと思ったのですが、自分のアカウントをAppFiguresにあげるような動きが必要になるとか、有料だとかで断念しました。

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