trifle

技術メモ

Twitter APIに全く頼らずにslack botだけでタイムライン管理をしてみる

※ この記事はISer Advent Calendar 2018の3日目の記事としても書かれています.



最近はTwitterを開くのが面倒でやっていないのですが, 好きな声優 or アーティスト or アニメの最新の情報が手に入らないことに以前より悩まされていました. 昨日デレマスのライブに参加した際, 特にそれを痛感したので, これは何とかせねばならないということで, 打開策を講じてみることにしました.
ただし, 周知の通り, Twitter APIの新規OAuth認証は今年の夏に厳格化されましたし, この先もどうなるか分かりません. そこで今回はAPIに頼らずに泥臭くクロールしてみます. こんな感じのものができました. まだ動かし始めてから2時間程度なのでちゃんと継続して動いてくれるのか怪しいのですが, 安定して稼働してほしいですね.

f:id:HelloRusk:20181202221934p:plain



f:id:HelloRusk:20181202225615p:plain



手順

その0 slackの公式Twitterアプリケーションをインストールする

これを入れると, twitterのリンクを貼るだけで中身のツイートや写真まで展開されるので, 必須です.

その1 botの準備

slack botを作るためのフレームワークとしては,

など色々あるようです. hubot が一応GitHub謹製なので良いのかもしれません. 自分は hubot にしました. ドキュメンテーションhttps://hubot.github.com/docs/ を見るといいと思います. --adapter=slack を忘れないようにしましょう.
あとはpackage.json見たらNodeのengineのバージョンがアホみたいに小さかったので, 8.11.xに上げました. 本当になんであんなに低かったんだろう....

その2 herokuの設定

slack botトークンを環境変数として保存します.

heroku config:set HUBOT_SLACK_TOKEN:XXXXXXXXXXXXXXXXXXXXXXXXXXXX

slack botにフォロワーを記憶させたいので, ブラウザの方でherokuを開いてRedisのアドオンを入れておきます. また, heroku plugins:install heroku-redis しておくと, コマンドラインの方でもどうなっているのか確認できるので良いでしょう. また, 定番ですが, herokuのfree dynoは30分で寝るのでHeroku Scheduler(要クレカ番号登録)は必要です. heroku psで後どれくらい使ったらヤバイのかが見れるのを知っておくと便利でしょう.

その3 botの中身を書く

f:id:HelloRusk:20181202225938p:plain

hubot は中身のスクリプトが元々の例がcoffeescriptとかいうもので書かれているので, それに従ったのですが(実は他の言語でも書けた??調べていません), 結構つらかったです. do記法のようなシンタックスシュガーは確かに学問的には面白いのかもしれませんが...
幸いなことに, 普通のNodeとcoffeescriptを変換してくれるサイト http://js2.coffee/ があり, 結構助かりました. ともかくも, 中身はこんな感じで書いてます.

cronJob = require('cron').CronJob
request = require('request')

module.exports = (robot) ->

  robot.hear /^follow (\S*)/i, (res) ->
    newfollowing = res.match[1]
    following = robot.brain.get('following') ? []
    following.push newfollowing
    robot.brain.set('following', following)
    robot.brain.set(newfollowing, '0')

    res.send "#{newfollowing} をフォローしました"
  
  robot.hear /^remove (\S*)/i, (res) ->
    removed = res.match[1]
    following = robot.brain.get('following') ? []
    following = following.filter((n) -> n != removed)
    robot.brain.set('following', following)

    res.send "#{removed} のフォローを解除しました"

  robot.hear /^following$/i, (res) ->
    following = robot.brain.get('following') ? []
    textdata = following.join("\n")

    res.send textdata


  new cronJob('0 * * * * *', () ->
    following = robot.brain.get('following') ? []
    if following.length == 0
      return
    
    following.forEach (id) ->
      num = robot.brain.get(id)

      request 'https://twitter.com/' + id, (e, r, b) ->
        status_and_id_array = b.match(/data-tweet-id="\S*"/gi)
        if !status_and_id_array
          return

        tmp = '0'

        status_and_id_array.forEach (el) ->
          new_el = el.replace(/data-tweet-id="(\S*)"/gi, '$1')
          if Number(new_el) > Number(tmp)
            tmp = new_el
        
        if tmp > num
          robot.send {room: '#general'}, id + " の新しいツイート:"
          robot.send {room: '#general'}, "https://twitter.com/twitter/status/" + tmp
          robot.brain.set(id, tmp)

  ).start()

follow Xでフォロイーを追加, remove Xでフォロイーを削除, followingでフォロイー一覧...というのは自然なのですが, じゃあツイート取得はどうしているかというと, 愚直にhttps://twitter.com/[id]をGETし, data-tweet-id=の後の数値の文字列が最もデカいものを抜き取り, それを更新していくcron(1分に1回)を作っているわけです. 単純にdata-tweet-id=の後の数値の文字列の位置が一番上のものにしてしまうと, 固定ツイートが含まれてしまうこともあるので, ツイートと一対一対応し、かつ単調増加する数値を取得するのが良いと思いました. ただしNumber()で文字列を数値にするところでMaxIntを超えているので下2桁くらい抜け落ちたものを比較しているという雑さがあります. まあ100ツイートなんて世界中で瞬間的に生成されるのでいいかな〜と思うのですが, 桁が2つくらい増えたらちゃんとゼロ埋めして文字列で比較した方が良い感じがしました.
当然ですが, 非公開アカウントや, 1分間に何回もツイートするアカウントには対応できません(後者はcron間隔を狭めることで何とかなる部分もあるかもしれないが, sleepから覚めてビルドする際の時間もあるので, やっぱり無理です. そもそも自分はこれを公式アカウントのツイートの収集の目的で始めたので...).
(12/3 0:37 追記: よく考えると上のツイート2つのidを比較するだけで良い気がしました. でも突然固定ツイートの個数が増えたりしたらどうしよう.)

なお, hubotのbrainメソッドを使ったRedisとの連携は, 以下の記事が大変参考になりました.



というわけで, 雑にslack botを作ってみたという話でした.