Playframework + Scalaを使ってSlackにメッセージを送って遊ぼう

皆さんこんにちは。

世の中には「あんまん派」もいるということを知って
驚きを隠せない「肉まん派」のムネリンです。

皆さん、こんなこと経験ありませんか?

仕事が終えオフィスを出ると・・・
「うわ、雨降ってた〜!傘会社においてきちゃったよ!」

帰るときの天気なんて晩御飯何にするかの方が大事なので全然気にも留めないですよね。

とはいえ困りますよね。どうすれば傘を忘れるの防げますかね?

帰る間際に強制的に天気予報見る仕組みがあればいいんじゃないか!?

ということでSlackに現在の天気を通知する仕組みをHeroku上のPlayframework/Scalaで作ってみたので
紹介します!

Scala-Heroku-Slack

仕組みはこんな感じです。
仕様:
- 定時10分前にSlack上に現在の天気情報を通知する。
- 通知方法はcronからWebAPIを実行するようにする。
- 天気情報は下記のAPIからJSON形式で取得する。
http://weather.livedoor.com/weather_hacks/webservice

取得する情報は
・現在の天気
・現在地
・天気マーク(画像)

Heroku上にScala(Playframework)を使って簡単WebAPIを作成してみます。
もちろんソースコードはIntelliJ IDEAのScala Worksheet使ってガリガリ書きました!
(参考:IntelliJ IDEAのScala Worksheet使ってScalaを効率的に学ぼう

HerokuでのPlayframework環境構築は
公式のとても分かり易いマニュアルを見ながら構築していきましょう!
Getting Started with Scala and Play on Heroku

開発中によく使うコマンドは下記にまとめておきます。

## Herokuにログインする
$ heroku login

## localでコンパイル
sbt compile stage

## Heroku アプリをlocalで起動
$ heroku local web

## Herokuにアプリをdeploy
$ git add .
$ git commit
$ git push heroku master

## アプリをブラウザで起動
$ heroku open

## ログの確認
$ heroku logs

では、まずはお天気APIを叩くところから作成していきます!

APIを叩くところはPlayframeworkのWSライブラリを使っています。
これを使うとHTTP通信を非同期に行うことができます!
リクエストの結果がFuture型で返ってきます。今回は非同期特に意味なしです笑
返ってきた結果をjoson4sライブラリを使ってパースして返します。

/**
   * Livedoorお天気APIからオフィル付近の情報を
   * JSON形式で取得する
   * @param cityCode オフィス付近のCityCode
   * @return JSON形式で天気情報を返す
   */
  def getWeatherJsonFromLivedoorApi(cityCode:String) = {
    implicit val context = play.api.libs.concurrent.Execution.Implicits.defaultContext
    val weatherApiUrl: String = s"http://weather.livedoor.com/forecast/webservice/json/v1?city=$cityCode"
    val futureResponse = WS.url(weatherApiUrl).get().map {
      response => response.json.toString()
    }

    val response: String = Await.result(futureResponse, Duration.Inf)

    JsonMethods.parse(response)
  }
 
 
次に取得したJSON形式のお天気情報から必要な
情報だけ取り出します。

/**
   * オフィス付近のお天気情報を
   * JSON形式で取得し、
   * 必要な情報だけ取り出す。
   * @return 現在の天気&お天気マーク
   */
def getWeatherInOffice:String = {
    
    val cityCode:String = "130010"
    val json = getWeatherJsonFromLivedoorApi(cityCode)

    val title: List[String] = for {
      JObject(child) <- json
      JField("title", JString(title)) <- child
    } yield title

    val weather: List[String] = for {
      JObject(child) <- json
      JField("date", JString(date)) <- child
      JField("telop", JString(telop)) <- child
    } yield date + " " + telop

    val imageUrl:List[String] = for {
      JObject(child) <- json \\ "image"
      JField("url", JString(url)) <- child
    } yield url

    imageUrl.headOption.getOrElse("画像なし") + " " + title.headOption.getOrElse("場所:取得できへん") + " --> " + weather.headOption.getOrElse("天気:取得できへん")
  }

今回の肝、Slack上の特定のチャンネルにメッセージを投げるメソッドです。
このAPIを使うには事前にTokenを作成しておく必要があるので
下記画面から作成しておきます。

Slack Web API

Slack APIを叩くときは同じくWSライブラリを使います。
/**
   * Slack上の特定のチャンネルにSlackAPIを経由してメッセージを投げる
   * @param message Slack上に投げるメッセージ
   */
  def sendMessageToSlack(message:String):Unit = {
    val slackApiToken:String = "作成したTOKENを書く"
    val channelId:String = "Cで始まるチャンネルID"
    val slackApiUrl = "https://slack.com/api/"
    val method  = "chat.postMessage"
    val sendParamMap:Map[String,String] = Map(
      "token" ->  slackApiToken,
      "channel"  -> channelId,
      "text"   -> message
    )
    val sendParams:String = sendParamMap.map(param => encode(param._1,"utf-8") +"="+ encode(param._2,"utf-8")).mkString("&")
    println(sendParams)

    val slackRequestUrl:String = slackApiUrl + method + "?" + sendParams
    WS.url(slackRequestUrl).get()
  }
  /**
    * オフィス付近の現在の天気をSlackにぶん投げる
    */
   def weather = Action { implicit request =>
     val secureKey:Option[String] = request.getQueryString("secureKey")

     secureKey match {
       case Some("hogehoge") => {
         val weatherMessage:String = getWeatherInOffice

         sendMessageToSlack(weatherMessage)

         Ok("ok")
       }
       case _ => {
         Ok("ng")
       }
     }
   }
   
これで完成です!
Heroku上にデプロイして実行してみましょう!

HerokuのURLにアクセスすると・・・

17

おー、やったぜ!

最後におまけです。

せっかく簡単なAPIを作成したのでSlackに「天気」というメッセージを投げたら
作成した天気取得APIを叩いて天気情報を通知してもらうようにしてみましょう。

それにはSlackのWebhookの仕組みを使います。

Webhookは、Slackの下記画面から登録できます。

01


こんな感じで登録します。楽勝ですね!
Triger Wordは安直に「天気」にしました。

15


では、試してみましょう。

Slack上で「天気」と入力します。

49

これでWebhookの仕組みでHeroku上のAPIをぶっ叩いてくれます。
下記のように天気情報をHerokuに通知してくれます。

00

やったぜ。

今回紹介したのは基本的な使い方ですが、
この仕組みを応用してSlack上で「にゃ〜ん」と呟いたら
Twitter上の「にゃんこ」の画像をランダムにSlack上に流すなんて
業務妨害テロ行為
癒されAPIも簡単に作れちゃいます。

何かおもしろAPIを作ったら周りのメンバーと共有し合えると
Slackのチャンネルが大荒れ メンバー間コミュニケーションが活発になって仕事が捗りますね!