Gaelykでemail Twitter連携アプリ作ってみた(解説編)


ざっくり解説。プロジェクトレイアウトはGaelyk本家にあるTemplate projectを拝借。IntelliJであっさりマウントできたので,あとは全部IntelliJ上で完結。GAEへのデプロイも楽ちん。


twitter連携にはもちろんTwitter4Jを利用させていただきました。なお,twitterとの連携にはOAuth認証を利用(プロジェクトレイアウトでモザイクかかってるファイルがそれ)。やり方わかんなくてOAuth認証でちょっとハマったけど,アクセストークン取れたらあとはすんなり。


GAE/Gaelykによくわかってなかったので,ちょとハマり「Javaにしとけばよかったかな?」思ったけど,慣れちゃうとホント簡単。むしろJavaでなんて組んでられっかって気分。
マジメなアプリ作ろう思ったらEC2のほうが自由が利いていいけど,この手のトイプログラムだとGAE/Gaelykのほうが簡単でいいね。:-)

メール連携について

本家のチュートリアルだとこの辺参照。
* Email support - Incoming email messages


もともとGAEがサポートしているメール受信をGaelykがさらに簡易化してくれている。事前準備として,appengine-web.xmlinbound-servicesmailを追加。でもって,web.xmlsecurity-constraintチュートリアル通りに設定。


メール受信用のサーブレットGaelykの場合,Groovlet)は,routes.groovyにこんな感じでしているだけ。

email to: "/receiveEmail.groovy"


このGroovlet名がそのままメールアドレスになる。例えば上記の例の場合だと,

receiveEmail@アプリケーション名.appspotmail.com


となる。receiveEmail.groovyの内容はこんなの。メールの解析はテキトー(添付ファイルとか気にしてない。

import twitter4j.*
import twitter4j.conf.ConfigurationBuilder

log.info "start!!!"
// メール受信
def msg = mail.parseMessage(request)
def postMessage = "@masanobuimai ${msg.content} / ${msg.from[0].address.split(/@/)[0]}"
log.info "postMessage:${postMessage}"

def cb = new ConfigurationBuilder()
cb.with {
  debugEnabled = true
  OAuthConsumerKey = "xxxxxxxxxxxxxxxxxxxxxx"
  OAuthConsumerSecret = "xxxxxxxxxxxxxxxxxxxxxx"
  OAuthAccessToken = "xxxxxxxxxxxxxxxxxxxxxx"
  OAuthAccessTokenSecret = "xxxxxxxxxxxxxxxxxxxxxx"
}

// twitterにポスト
// -> メール受信処理がコケるとメールが滞留するみたいなんで,コカさないようにする。
try {
  new TwitterFactory(cb.build()).instance.updateStatus(postMessage).with { status ->
    log.info "Successfully updated the status to [${status.text}]."
  }
} catch (e) {
  log.info "twitterにポスト失敗: ${e}"
}
// ついでにgmailにもポストしとく。
mail.send from: "receiveEmail@xxxx.appspotmail.com",
          to: "example@gmail.com",
          subject: "つまからの伝言",
          textBody: "${msg.content}"

log.info "end!!!"


ホント,簡単。:-)

Twitter連携について

定期的にmentionsの新着をチェックするんでGAEのcron使って監視させといた。新着チェック済みのmentionsは読み飛ばす必要があるんで,最後のチェックしたmentionsのIDを永続化しとかないといけない。あとメールの送信も。
本家のチュートリアルだとこの辺参照。
* Improvements to the low-level datastore API
* Email support - New send() method for the mail service


cronの設定はcron.xmlにこんな感じに記述するだけ。最小単位が1分だけど,まあいいか。

<cronentries>
  <cron>
    <url>/checkMentions.groovy</url>
    <description>checkMentions the run every 1 minutes</description>
    <schedule>every 1 minutes</schedule>
    <timezone>Asia/Tokyo</timezone>
  </cron>
</cronentries>


mentionsのチェックするcheckMentions.groovyの中身はこんなの。

import twitter4j.conf.ConfigurationBuilder
import twitter4j.TwitterFactory
import com.google.appengine.api.datastore.Entity
import com.google.appengine.api.datastore.Query
import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit

log.info "start!!!"

// 最後に取得したmentionsのIDを永続化しとかないと...。
def query = new Query("Settings")
query.addFilter("name", Query.FilterOperator.EQUAL, "one")
def result = datastore.prepare(query).asList(withLimit(10))
// SettingsっていうEntityがあればそれを使う,無ければ新しいEntityを作る。
def entity = !result.isEmpty() ? result[0] : new Entity("Settings")
// name="one"に強い意味は無いス。単にユニークにしたいだけ。
entity.name = "one"
def lastMentionId = entity.lastMentionId ?: 0

log.info "entity: ${entity}"

def cb = new ConfigurationBuilder()
cb.with {
  debugEnabled = true
  OAuthConsumerKey = "xxxxxxxxxxxxxxxxxxxxxx"
  OAuthConsumerSecret = "xxxxxxxxxxxxxxxxxxxxxx"
  OAuthAccessToken = "xxxxxxxxxxxxxxxxxxxxxx"
  OAuthAccessTokenSecret = "xxxxxxxxxxxxxxxxxxxxxx"
}

// mentionsのチェック。getMentions() を mentions と書けないもどかしさよ...。
new TwitterFactory(cb.build()).instance.getMentions().reverseEach { status ->
  if (status.id <= lastMentionId) {
    log.info "読み込み済みなので無視:id=${status.id}"
  } else {
    // 新着のmentionsをメールする
    mail.send from: "receiveEmail@xxxxx.appspotmail.com",
              to: "<ヨメのメールアドレス>",
              subject: "お父ちゃんからの連絡",
              textBody: status.text.substring(14)
    entity.lastMentionId = status.id
    log.info "mention: ${status.text}"
  }
}
// -> こっちのエラー処理はいいや。

// 最後に取得したmentionsのIDを永続化しとく。
entity.save()
log.info "end!!!"


コテコテのSIer勤めだけど、こんくらいのアプリをペロっと作れる程度の能力が残っていてよかった。あとは、こんなもん使うような事態にならなければもっと良い。:-)