GAE -> Pub/Sub -> CloudFunctionsで無職カウントを自動化する

こんにちは、無職です。
無職になった日から、Twitter上で流行ってる無職日数カウントをしていたんですが、流石に手動更新はめんどくさいなと思ったので自動化しました。
でも「今時ローカルマシンからcronで...」みたいなのは面白くないので、せっかくなので最近βになったCloud FunctionsのGoを使います。

無職カウント

上記で設定されている「無職n日目」という名前(screen_name)のことです。 最近流行ってます。

構成

  1. GAEのcronジョブで、毎日一定時刻に無職日数を計算し、Cloud Pub/Subの特定topicへPublish
  2. Cloud Functionsで、Cloud Pub/Subの特定topicをsubscribeし、publishされたタイミングでTwitterAPIを叩き、screen_nameを更新する

といった構成です。

GAE

github.com

やってることは単純で、特定のURLにリクエストが来たら日数計算してCloud Pub/SubにPublishするのみです。 GAE初めて使ったのですが、割と手動デプロイしている人/組織が多い気がしていて、自動デプロイできるならすべきなのでは...?と思わなくもないです。
(Travis CIのデプロイ設定初めてでめっちゃコミットログ汚れてしまった...。)

注意点として、Travis CIでGAEへデプロイしてるとcron.yamlをデプロイしてくれないので、別個cron.yaml用の設定をするように注意しましょう。この辺。

stackoverflow.com

Cloud Functions

こんな感じ。
OAuth1の突破にはgomodule/oauth1を使用します。 Cloud Functionsでも普通に外部ライブラリimportできるの便利だ...Go modulesに感謝👏👏👏
(まだGo modules周り追ってないので追わなきゃ...)
Twitter API叩くの5年ぶりくらいだったので、普通にTwitter Developerの登録がめんどくさかったです。

github.com

// Package p contains a Pub/Sub Cloud Function.
package p

import (
    "context"
    "log"
    "net/url"
    "os"

    "github.com/gomodule/oauth1/oauth"
    "github.com/pkg/errors"
)

var (
    key      string
    seckey   string
    token    string
    tokensec string
)

// PubSubMessage is the payload of a Pub/Sub event. Please refer to the docs for
// additional information regarding Pub/Sub events.
type PubSubMessage struct {
    Data []byte `json:"data"`
}

func UpdateName(ctx context.Context, m PubSubMessage) error {
    InitOAuthConfig()
    if key == "" || seckey == "" || token == "" || tokensec == "" {
        return errors.New("bad oauth config")
    }

    u, _ := url.Parse("https://api.twitter.com/1.1/account/update_profile.json")
    q := u.Query()
    q.Set("name", string(m.Data))

    c := NewTwClient()
    at := oauth.Credentials{
        Token:  token,
        Secret: tokensec,
    }
    resp, err := c.Post(nil, &at, u.String(), q)
    if err != nil {
        return errors.Wrap(err, "Failed to send twitter request.")
    }
    defer resp.Body.Close()

    if resp.StatusCode >= 500 {
        return errors.New("Twitter is unavailable")
    }

    if resp.StatusCode >= 400 {
        return errors.New("Twitter request is invalid")
    }

    return nil
}

func InitOAuthConfig() {
    key = os.Getenv("API_KEY")
    seckey = os.Getenv("API_SECRET_KEY")
    token = os.Getenv("ACCESS_TOKEN")
    tokensec = os.Getenv("ACCESS_TOKEN_SECRET")
}

func NewTwClient() oauth.Client {
    return oauth.Client{
        Credentials: oauth.Credentials{
            Token:  key,
            Secret: seckey,
        },
    }
}

終わりに

最近コード書いてなくてリハビリ目的に雑に書きましたが、コード書きたくなって来たのでまた何か書きたいですね。皆さんも無職カウントの自動化をするときは参考になさってください。