はじめて、の次のGo

はじめてのGo - すぎゃーんメモ の続き?的な。
入門で終わらずにもうちょっとマトモに何か作れるようになりたい、ということで、以前にRubyで書いた「げんきだしてbot」をGoで書いてみた。

成果物のリポジトリはこちら。

「げんきだしてbot」とは

まぁUser streamからネガティヴな発言を拾ってきて「げんきだして!」とリプライを送るだけのTwitter botです。

ファイル分割

すべて同一のmainパッケージ内で書くけど、機能ごとにファイルを分けてみた。

  • twitter_client.go: Twitter APIを叩いて認証したりStreamに接続したりなど
  • config.go: 認証して得たaccess tokenを保存したり読み込んだり
  • genki-bot.go: 実際にStreamからTweetを読み取り返すべき発言を判定したりなどのロジック
  • main.go: main関数。コマンド引数を読んでgenki-botを呼ぶだけ

typeやfuncの定義が色んなところに分散していても、どうせgodef使って定義元にジャンプできるので問題ない。

設定値の保存/読取

前回は go-pit を使って設定値を読み書きしていたけど、簡単に自分で書いてみよう、と自前で用意してみた。
~/.config/以下に適当にディレクトリ掘ってini形式ファイルでaccess tokenを保存。JSONYAMLなどの構造化データもいいけど、Goで扱う場合 それほど複雑なデータ構造を必要としないならiniファイルくらいがお手軽で良いなーという印象を持った。

HOMEディレクトリの取得の際に最初はos/userパッケージを使っていたけど、これがどうもクロスコンパイルするときの障壁になるようなので避けることにした。

Stream API

普通にOAuth認証でのリクエストまでは問題ないとして、Stream APIに接続したときのレスポンスを継続して受け続けるために。

などを参考に。
bufio.NewScanner(resp.Body)で作った*bufio.Scannerに対してScanをかけることで 新たなデータが流れてきたときにbyte列を取得できるようだ。
ただこのときclient側から接続を切ろうとすると接続時のConnectionを維持している必要がありそうで、そのへんは面倒そうなのでここでは省略した。

正規表現

Ruby版では「疲れた」に対しては「疲れてるの?げんきだして!」と返信するが「お疲れさま」などには反応しないように/疲(?!れ(?:様|さ(?:ま|ん)))/のような正規表現を使って判定している。
が、これをこのままGoで使おうとするとinvalid or unsupported Perl syntax: `(?!`とエラーを吐かれてしまう。(?!pattern)、"negative look-ahead"否定先読みというのかな、これはGoではサポートされていないらしい。注意。

テスト

いちおうどんな発言に対してどんな返信をすべきか、または返信すべきでないか、を確かめるためにテストも書いておいた。

あまり書きやすいかんじはしないけど、とりあえず書いておけばgo testコマンドで簡単にテストを実行できるので安心できる。

Goroutineで簡単非同期

リプライを送る際にちょっとsleepで間を空けたり、またそのPOSTの送信で処理がブロックされる。Streamの受け取りは止めずにリプライ処理をするために、ここで初めてGoroutineを使用。

		if mention != nil {
			go func() {
				time.Sleep(time.Second * time.Duration(rand.Int31n(5)+5))
				tweet, err := bot.client.Mention(mention)
				if err != nil {
					log.Println(err)
				} else {
					log.Printf("tweeted: %s", tweet.Text)
				}
			}()
		}

Mentionメソッド自体は普通に返り値、エラーを戻す関数として定義しておいて、それを呼ぶ側をfunc(){...}()で囲んでGoroutineとして呼んでやればいいかな、と。
ここでは並行処理同士での協調や調整は必要ないはずなのでChannelとか難しいことは考えない。

ロスコンパイル

Goは簡単に他の環境用のバイナリを生成できる(前述の通りos/userのようなcgoを使うパッケージを含んでいるとハマったりするけれど)、ということで試してみた。

Mac OS上で作ったバイナリがさくらVPSで動かしているCentOS上で動くことが確認できた。感動。
drone.iowerckerを使ってバイナリ配布とかも後でチャレンジしてみよう。

Windowsについては試せる環境もっていないので無視で…。

まとめ

まだまだ慣れないけれど 少しずつGoを書く楽しさは感じはじめてる。
やっぱりツールが充実していてすぐに文法エラーを検出できたり定義を調べたり出来るのは強いなーと思う。
もうちょい色んなパッケージやライブラリのソース読んだりして良い書き方とか身に付けたい。


次は何を作ってみようか