CoffeeScriptを使ったTips集 その2

Twitterでつぶやく

Twitterのアクセストークンを作成する

まずTwitterのサイトで下準備をします。

https://dev.twitter.com/apps からTwitterアカウントでサインインし、「Create a new application」ボタンをクリックして新しいアプリケーションを作成します。

Create a new applicationをクリック

アプリケーションを作成したらSettingsタブを開き、Application TypeのAccess項目で「Read and Write」を選択し、ページ下部の「Update this Twitter application's settings」ボタンをクリックします。

Read and Writeを選択

Detailsタブを開き、ページ下部の「Create my access token」ボタンをクリックするとAccess tokenが表示されます。

Create my access tokenをクリック

Access tokenが表示されない場合はページをリロードしてみてください。ページに表示されているConsumer keyConsumer secretAccess tokenAccess token secretの4つの情報を下のプログラムで使います。

プログラムの作成

ntwitterモジュールをインストールします。

$ npm install ntwitter

以下のプログラムでつぶやきを送信できます。CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRETはそれぞれ先ほど取得した値に置き換えてください。

twitter = require "ntwitter"

twit = new twitter
  consumer_key       : "CONSUMER_KEY"
  consumer_secret    : "CONSUMER_SECRET"
  access_token_key   : "ACCESS_TOKEN"
  access_token_secret: "ACCESS_TOKEN_SECRET"

# つぶやく
twit.updateStatus "CoffeeScriptからこんにちは!", (err, data) ->
  if err
    console.log "error: #{err.message}"
  else
    console.log "updated"

重複する内容のつぶやきを連続で送信しようとすると「Status is a duplicate」というエラーが返ってきます。その場合は、内容を変更してつぶやいてみてください。

簡単なTwitter botを作る

「今何時?」というつぶやきに対して時刻を返信するTwitter botを作ってみます。「今何時」以外のつぶやきが来た場合は返信しません。

twitter = require "ntwitter"

twit = new twitter
  consumer_key       : "CONSUMER_KEY"
  consumer_secret    : "CONSUMER_SECRET"
  access_token_key   : "ACCESS_TOKEN"
  access_token_secret: "ACCESS_TOKEN_SECRET"

# 自分のTwitter IDを取得する
twit.verifyCredentials (err, data) ->
  if err then throw err
  user_id_str = data.id_str

  # userストリームに接続する
  twit.stream "user", (stream) ->
    console.log "ready"
    stream.on "data", (data) ->  # 新しいデータが来た
      if data.in_reply_to_user_id_str is user_id_str  # @付きのつぶやきが来た
        console.log "new mention from @#{data.user.screen_name}: #{data.text}"

        # 本文から@usernameを取り除く
        text = data.text.replace /^@\w+\S+/, ""

        reply = null
        if /今何時/.test text
          d = new Date
          reply = "#{d.getHours()}時#{d.getMinutes()}分だよ"
        if reply?
          reply = "@#{data.user.screen_name} #{reply}"
          console.log "update: #{reply}"

          # つぶやく
          twit.updateStatus reply, {
            in_reply_to_status_id: data.id_str
          }, (err, data) ->
            if err
              console.log "update error: #{err}"
            else
              console.log "update success"

天気予報を取得する

まずrequestモジュールをインストールします。

$ npm install request

Livedoorお天気Webサービスを使って、簡単に天気予報を取得できます。

request = require "request"

# cityId一覧: http://weather.livedoor.com/forecast/rss/primary_area.xml
cityId = "130010"  # 東京

apiUrl = "http://weather.livedoor.com/forecast/webservice/json/v1?city=#{cityId}"

# URLにアクセスしてデータを取得する
request apiUrl, (err, response, body) ->
  if err  # プログラムエラー
    throw err

  if response.statusCode is 200  # 取得成功
    # JSONとして解釈する
    try
      json = JSON.parse body
    catch e
      console.log "JSON parse error: #{e}"

    # 直近の予報データを取得
    forecast = json.forecasts[json.forecasts.length-2]

    # 文を作る
    weather = "#{json.location.city}の#{forecast.dateLabel}の天気は#{forecast.telop}"
    if forecast.temperature.max?  # 気温情報がある場合
      weather += "、最高気温は#{forecast.temperature.max.celsius}度、" + \
        "最低気温は#{forecast.temperature.min.celsius}度"
    weather += "です。"
    console.log weather
  else  # APIレスポンスエラー
    console.log "Response error: #{response.statusCode}"

【実行結果】

東京の明日の天気は晴のち曇、最高気温は35度、最低気温は26度です。

各地の天気予報を返信するTwitter botを作る

上記の手法を組み合わせると、「東京の天気」や「札幌の天気」というつぶやきに反応して各地の直近の天気予報を返信してくれるTwitter botを作ることができます。

twitter = require "ntwitter"
request = require "request"

twit = new twitter
  consumer_key       : "CONSUMER_KEY"
  consumer_secret    : "CONSUMER_SECRET"
  access_token_key   : "ACCESS_TOKEN"
  access_token_secret: "ACCESS_TOKEN_SECRET"

# cityId一覧
cityIds =
  "稚内": "011000"
  "旭川": "012010"
  "留萌": "012020"
  "網走": "013010"
  "北見": "013020"
  "紋別": "013030"
  "根室": "014010"
  "釧路": "014020"
  "帯広": "014030"
  "室蘭": "015010"
  "浦河": "015020"
  "札幌": "016010"
  "岩見沢": "016020"
  "倶知安": "016030"
  "函館": "017010"
  "江差": "017020"
  "青森": "020010"
  "むつ": "020020"
  "八戸": "020030"
  "盛岡": "030010"
  "宮古": "030020"
  "大船渡": "030030"
  "仙台": "040010"
  "白石": "040020"
  "秋田": "050010"
  "横手": "050020"
  "山形": "060010"
  "米沢": "060020"
  "酒田": "060030"
  "新庄": "060040"
  "福島": "070010"
  "小名浜": "070020"
  "若松": "070030"
  "水戸": "080010"
  "土浦": "080020"
  "宇都宮": "090010"
  "大田原": "090020"
  "前橋": "100010"
  "みなかみ": "100020"
  "さいたま": "110010"
  "熊谷": "110020"
  "秩父": "110030"
  "千葉": "120010"
  "銚子": "120020"
  "館山": "120030"
  "東京": "130010"
  "大島": "130020"
  "八丈島": "130030"
  "父島": "130040"
  "横浜": "140010"
  "小田原": "140020"
  "新潟": "150010"
  "長岡": "150020"
  "高田": "150030"
  "相川": "150040"
  "富山": "160010"
  "伏木": "160020"
  "金沢": "170010"
  "輪島": "170020"
  "福井": "180010"
  "敦賀": "180020"
  "甲府": "190010"
  "河口湖": "190020"
  "長野": "200010"
  "松本": "200020"
  "飯田": "200030"
  "岐阜": "210010"
  "高山": "210020"
  "静岡": "220010"
  "網代": "220020"
  "三島": "220030"
  "浜松": "220040"
  "名古屋": "230010"
  "豊橋": "230020"
  "津": "240010"
  "尾鷲": "240020"
  "大津": "250010"
  "彦根": "250020"
  "京都": "260010"
  "舞鶴": "260020"
  "大阪": "270000"
  "神戸": "280010"
  "豊岡": "280020"
  "奈良": "290010"
  "風屋": "290020"
  "和歌山": "300010"
  "潮岬": "300020"
  "鳥取": "310010"
  "米子": "310020"
  "松江": "320010"
  "浜田": "320020"
  "西郷": "320030"
  "岡山": "330010"
  "津山": "330020"
  "広島": "340010"
  "庄原": "340020"
  "下関": "350010"
  "山口": "350020"
  "柳井": "350030"
  "萩": "350040"
  "徳島": "360010"
  "日和佐": "360020"
  "高松": "370000"
  "松山": "380010"
  "新居浜": "380020"
  "宇和島": "380030"
  "高知": "390010"
  "室戸岬": "390020"
  "清水": "390030"
  "福岡": "400010"
  "八幡": "400020"
  "飯塚": "400030"
  "久留米": "400040"
  "佐賀": "410010"
  "伊万里": "410020"
  "長崎": "420010"
  "佐世保": "420020"
  "厳原": "420030"
  "福江": "420040"
  "熊本": "430010"
  "阿蘇乙姫": "430020"
  "牛深": "430030"
  "人吉": "430040"
  "大分": "440010"
  "中津": "440020"
  "日田": "440030"
  "佐伯": "440040"
  "宮崎": "450010"
  "延岡": "450020"
  "都城": "450030"
  "高千穂": "450040"
  "鹿児島": "460010"
  "鹿屋": "460020"
  "種子島": "460030"
  "名瀬": "460040"
  "那覇": "471010"
  "名護": "471020"
  "久米島": "471030"
  "南大東": "472000"
  "宮古島": "473000"
  "石垣島": "474010"
  "与那国島": "474020"

# cityIdに対応する天気予報を取得する
getWeatherByCity = (cityId, callback) ->
  apiUrl = "http://weather.livedoor.com/forecast/webservice/json/v1?city=#{cityId}"
  request apiUrl, (err, response, body) ->
    if err
      callback err
      return

    if response.statusCode is 200
      try
        json = JSON.parse body
      catch e
        callback new Error "JSON parse error"
        return
      forecast = json.forecasts[json.forecasts.length-2]  # 直近の予報データ
      weather = "#{json.location.city}の#{forecast.dateLabel}の天気は#{forecast.telop}"
      if forecast.temperature.max?  # 気温情報がある場合
        weather += "、最高気温は#{forecast.temperature.max.celsius}度、" + \
          "最低気温は#{forecast.temperature.min.celsius}度"
      weather += "です。"
      callback null, weather
    else
      callback new Error "Response error: #{response.statusCode}"

# textに対する返信文を作成する
getReply = (text, callback) ->
  if (match = /(\S+)の天気/.exec text)?
    city = match[1]
    if cityIds[city]?
      getWeatherByCity cityIds[city], callback
    else
      callback null, "#{city}の天気はわかりません…。"
  else
    callback null, null

twit.verifyCredentials (err, data) ->
  if err then throw err
  user_id_str = data.id_str

  twit.stream "user", (stream) ->
    console.log "ready"
    stream.on "data", (data) ->
      if data.in_reply_to_user_id_str is user_id_str
        console.log "new mention from @#{data.user.screen_name}: #{data.text}"
        text = data.text.replace /^@\w+\S+/, ""

        # 返信文を作成
        getReply text, do (data) -> (err, reply) ->
          if err
            console.log "getReply error: #{err}"
            return
          if reply?
            reply = "@#{data.user.screen_name} #{reply}"
            console.log "update: #{reply}"
            # つぶやく
            twit.updateStatus reply, {
              in_reply_to_status_id: data.id_str
            }, (err, resp) ->
              if err
                console.log "update error: #{err}"
              else
                console.log "update success"