BotkitでSlack Botを作りつつ自己アップデートできるようにした

Slack皆さん使ってますか?

Slackはただチャットが出来るだけではなく豊富なAPIによってもたらされる便利Botたちの存在が非常に大きいです.

Botを開発する際には,Githubが開発しているHubotが有名ですが,ここ最近ではSlack botに特化した(別に特化しているわけでもなさそう)BotkitというBotフレームワークが出ました.

github.com

Botの設置自体はリポジトリのREADME.mdを見ればわかるので,見てください. で,自己アップデートというのは何かって言うと,Botのコードを書き換えてpushした後に設置しているサーバにログインして,pullしてrestartするっていう作業がめっちゃだるかったので,自分で自分を更新して再起動させる仕組みを作りました.

基本的なコンセプトは HubotでHubotの更新をforeverを利用してHubotにさせてみる - MANA-DOT ここから得ました.ありがとうございます.

foreverでの起動

まず,参考ページの通りforeverでの起動をします. 元リポジトリの起動方法だと,tokenを環境変数で与えるようになっていますが,foreverでの環境変数の与え方がわからなかったので,Botのソース内に直書きするかファイルを読む仕組みにしましょう.

forever start bot.js

これで起動はできます.

参考ページにもある通り,foreverは死んでもうまいこと生き返らせてくれる機能を持っているので,アップデート処理が終わったら自殺するというコードをかけば良いでしょう.

アップデート処理

まずはコードを出します.

var child_process = require('child_process');

function updateSelf(bot, message){
  child_process.exec('git reset --hard origin/master', function(error, stdout, stderr){
    bot.reply(message, 'Botが更新されました!');
    bot.reply(message, 'Botを再起動します');
    setTimeout(function(){
      process.exit();
    }, 2000);
  });
}

controller.hears(['update'], 'direct_mention', function(bot, message) {
  bot.reply(message, 'Botのアップデートを開始します');

  child_process.exec('git fetch', function(error, stdout, stderr){
    child_process.exec('git log master..origin/master', function(error, stdout, stderr){
      if(stdout == ""){
        bot.reply(message, 'Botは最新です');
      }else{
        bot.startConversation(message, function(error, convo){
          bot.reply(message, '更新内容は以下のとおりです');
          bot.reply(message, '```\n' + stdout + '\n```');
          convo.ask('アップデートを行いますか?(y/n)', [
            {
              pattern: bot.utterances.yes,
              callback: function(response, convo){
                updateSelf(bot, message);
                convo.next();
              }
            },
            {
              pattern: bot.utterances.no,
              callback: function(response, convo){
                bot.reply(message, 'アップデートを中止します');
                convo.next();
              }
            },
            {
              pattern: 'じゃあそれで',
              callback: function(response, convo){
                updateSelf(bot, message);
                convo.next();
              }
            },
            {
              default: true,
                callback: function(response, convo){
                  convo.repeat();
                  convo.next();
                }
            }
          ]);
        });
      }
    });
  });
});

まずgitを操作したりするために,コマンド実行可能な child_process を利用します. update コマンドが発行された時にまず,git fetchを行い,アップデートの確認を行います. アップデートがあるのであれば,なんらかの標準出力がなされるので,それでアップデートがあるかどうかを判定します.

アップデートがある場合は,変更点を git log で出力するようにしました. また,BotkitのConversation機能を用いて,アップデートを行うかどうかの確認を行う仕組みを導入しました.

アップデートが許可された場合には, git reset --hard origin/master とすることで,origin/master への追従を行い,最新版へのコード更新が行われます. その後, process.exit() を発行し,自殺した後foreverによって再び生かされることになります.

process.exit() を発行するときの setTimeout が無いと,bot.reply() が間に合わず何も出力されないケースがあるので,適当に時間とって setTimeout した方がいいです.

実行時の様子

f:id:programmerMOT:20160226125551p:plain

終わり

これでBot開発が捗る.

記事を自分で読み返して, git fetch しておいて,アップデートを了承しなかった場合,もう一度updateした時には,fetchが働かず永久にupdateできないというバグを見つけてしまった. アップデートがあるかどうかの判定を git log master..origin/master でやれば良さそう.

NearbyでAndroid,iOS通信(解説は元気が出たら)

久々にこっちで書く.

NearbyというのはGoogleが提供しているpub-sub型のメッセージ通信の仕組みで,BluetoothとかWi-Fiとか非可聴音をうまく使って近辺にある端末を探しだしいい感じに通信させてくれる仕組み. AndroidiOSで動くようになっていて,相互のプラットフォーム間での通信も簡単にできる.前にAndroid間での通信はしたんだけど,そういえばお布施を払わなくてもiOS実機試せると思い出し,Android <-> iOS通信してみることとした. 本当は気合をいれて解説するとばかうけなんだろうけど,眠いのでとりあえず後日.リポジトリと参考サイトの列挙でとどめておこうと思う.

リポジトリ

github.com

github.com

iOS版は気合入れてSwiftで書いてみた.ただゴミだと思うので,詳しい人教えてほしい. APIKeyは,Androidはプロジェクトルートにgoogle_api_key.txtという名前でファイルを置いておもむろにAPIkeyだけかけばいい. iOSソースコード中に書いてあるんのでいい感じに書き換えてくれ.

参考文献

Googleの公式ドキュメントとりあえず読むと良い. Overview  |  Nearby Messages API  |  Google Developers

AndroidでのNearby解説.記事内はKotlinで書かれているが自分はJavaで書いた. Android - Nearby Messages APIでチャットみたいなのを作ってみる - Qiita

Android ListViewに関してコードパクった頂きました.ありがとうございます. 灯火: Android : ListView が一番上 / 一番下にスクロールしたかを調べる

AndroidGoogle API Keyを外部ファイルとしていい感じに管理する方法.gradle便利だなぁ. Manage Google Maps API Key with Gradle in Android Studio - Stack Overflow

iOSでのNearby解説.本当にお世話になりました. Nearby Messages by Swift - ゆずとみかんといちご

battery-historian使ってみた

BatteryHistorianとは2014年のGoogleI/Oで発表されたAndroidアプリにおける電力使用量を詳しく調査するための開発者ツールの一種です.

前置き等はこちらの記事が参考になるかとおもいます.

techlife.cookpad.com

この記事ではpythonスクリプトという説明がなされていますが,あるとき見るとやり方が変わっていたので,それについてだらだらっと書きます.


battery-historianの公式リポジトリはこちら github.com

基本的にはこのリポジトリのREADMEを参照すれば良さそう.

またBatteryHistorian2.0からはpythonではなく,Golang製になっていました. Golangそのものの設定はググるといいと思う.GOPATHだけちゃんと設定していればいい.

ちなみに自分はGOPATH=~/localにしてる.そうするとそれ以外のソースコードとかも統一して管理できる.

Protocol BufferのGoライブラリを入れる

$ go get -u github.com/golang/protobuf/proto
$ go get -u github.com/golang/protobuf/protoc-gen-go

The compiler plugin, protoc-gen-go, will be installed in $GOBIN, which must be in your $PATH for the protocol compiler, protoc, to find it.

protoc-gen-goというバイナリを使用するらしいので,PATH通しておけとのこと.$GOBIN設定していればその場所に,してなければデフォルトで$GOPATH/binに入るらしい.

改めてREADME読んで$GOBINなるものを初めて知った.デフォルトは$GOPATH/binらしいから普通の人は気にしなくていいと思う.

Protocol BufferはGoogleが作ってるシリアライズフォーマット.研究室のコードで使われてた形跡とかあったけどあんまり知らない.

Battery-Historianダウンロード

# battery-historianダウンロード
$ go get -u github.com/google/battery-historian
# battery-historianのディレクトリに移動
$ cd $GOPATH/src/github.com/google/battery-historian

# セットアップスクリプトを起動
$ bash setup.sh
# Battery-historianサーバ起動
$ go run cmd/battery-historian/battery-historian.go [--port <default:9999>]

あとはgitから落としてきた上で,セットアップスクリプト動かすだけ.

見て分かる通り今回のバージョンからGolangによるサーバが立つかんじらしい.

ここまでできたらlocalhost:9999にアクセス. f:id:programmerMOT:20150711123805p:plain

統計データ抽出

あとは開発者モードをオンにしたAndroid端末を用意する.これはググって.

その後adbを利用して

$ adb bugreport > bugreport.txt

として出てくるbugreport.txtをbattery-historianにアップロードすると解析してもらえる.

bugreport出力は時間がそこそこかかるので注意.

あとデフォルトで取れるデータで記録されないものとかあるらしいので,イカのコマンドで有効にする*1

$ adb shell dumpsys batterystats --enable full-wake-history

bugreportのリセットはイカのコマンドで.

$ adb shell dumpsys batterystats --reset

データの可視化

無事ファイルをセットできると,こんな画面が見える.

それぞれのパラメータの意味はぶっちゃけ全部分かってない...

f:id:programmerMOT:20150711135951p:plain


参考文献

5分でわかるBatteryHistorianによるAndroidアプリの解析方法 - クックパッド開発者ブログ

google/battery-historian · GitHub

Battery Historian (unofficial) - Google Docs

*1:ぶっちゃけこの操作の意味分かってない.また調べないと...

zshを江添さんっぽくする方法

mattn.kaoriya.net

要するにパクリリスペクトを受けた

bashではコマンドが無いときはcommand_not_found_handleでフックできる.

zshではちょっと違ってcommand_not_found_handlerでフックできる.rを忘れずに

command_not_found_handler(){
  echo "$1: コマンドではない。"
}

f:id:programmerMOT:20150601124925p:plain

mattnさんのように,最後にreturn 127;とつけたらzsh標準のnot foundも出てきて最初かっこわるかった.時間を見つけてソース読んでおきたい.

curlするruboty plugin書いた

書いた

github: ruboty-http_curl

gem: ruboty-http_curl | RubyGems.org | your community gem host

jenkinsのビルド開始とかをrubotyからしたくて,curlコマンド打てるようになりたかったから作った. ついでに,gemsへのアップロードもしてみたかったので勉強がてらやった.

ぶっちゃけ自分でも使いにくい.アップデートはやるつもり.

ruboty-gen

Ruboty の Plugin のつくりかた #ruboty - Qiita

ここを参考にpluginを書きました.ありがとうございます.

rubygems

なんかruboty-genに全部用意されてて,rake releaseってしたらアップロードされた便利.

rubygemsのアカウントつくるのも簡単で,rubygemsのページでアカウント作った後,おもむろにこのコマンドを打てみたいなこと言われるから打ったら認証情報も落ちてきて便利だった.

おわりに

長文書く力なくなってきた.

2015/04/06に解決した問題

今日解決した問題雑に書く.

とあるマシンが再起動時に上がってこないうえsshdも立ち上がってない

マシンのリブート時にNASへのマウントでパスワード求められて止まってた.sshd起動前に止まってたからsshは入れないのにpingは通るみたいな辛い感じだった.apachessl鍵とかパスワード付きだと起動時にパスワード求められて止まるのと同じ症状かな.

NASにはcifsでマウントするんだけど,guestでアクセスできればよかったので/etc/fstabにguestでつなげるよう設定を書いた.

/etc/fstabに

//xxx.xxx.xxx.xxx/nas_dir /mnt/mnt_dir     cifs    defaults,guest 0 0

ってした.

slack移行の準備

slack移行のために通知をslackでも有効にした.物によって通知先変えたいからかなり頑張った.時間があったらジョブを複数指定して指定したジョブの通知をあそこに向けるみたいなプラグイン書きたい.ジョブを分けているとたくさんあって大変*1.今回は手でjenkinsおじさんの通知を全部書き換えた.死ぬかと思った.

jenkinsおじさんのジョブ階層化

jenkinsのジョブが死ぬほどあってスクロールとかもろもろ大変だったので,階層化するプラグイン入れた.

参考文献: blog.fenrir-inc.com

これの

Categorized Jobs View - Jenkins - Jenkins Wiki

Categorized Jobs Viewというのを入れた.正規表現でジョブを区分けもできるので,プロジェクトごとに共通した単語が入っていればプロジェクトごとに分けるのも簡単.

以上

*1:ユニットテストとかアクセプタンステストごとに分けてる

mavenをgradleにするの簡単になってた

pom.xmlが鎮座するプロジェクトで,とりあえずgradle化しようとしておもむろにgradle initしたらgradleの関連ファイルが自動生成された上いい感じにpom.xmlを読んでいい感じのbuild.gradleが生成されてた.便利.

dependenciesとかrootProject.nameとかいい感じにセットアップされてて本当に便利.

シンプルなpom.xmlだったというのもあるだろうから,めっちゃ複雑なやつのときはどうなるか今度試したい*1

*1:複雑なpom.xmlとは