ruby で Mastodon の Streaming API を受信する

Mastodon の bot 等を作るために Streaming API を取得したかったのですが mastodon-api gem には REST API しかなかったのと、具体的なコード例がなかったのとでいくらか調査が必要でした。
その調べた結果を残しておきます。

以下の記事も参考にしました
「PHPでMastodonのStreaming APIを受信する」
http://qiita.com/yyano/items/841f79266faf2dc8b6dc

# Streaming API 概要
タイムラインや通知を Websocket で受け取ることができます。
URL は `https://(インスタンスのURL)/api/v1/streaming`。
アプリケーションの API アクセストークンとタイムライン種類をクエリで渡すことができます `https://(インスタンスのURL)/api/v1/streaming?access_token=(アクセストークン)&stream=[user|public|hashtag]`

アクセストークンの取得については他の Mastodon API 解説記事を参照してください。

# faye-websocket gem を使う例
以下の gem を使うのでインストールする。

- eventmachine
- faye-websocket

``` ruby:
require "eventmachine"
require "faye/websocket"

INSTANCE="(インスタンスのURL)"
TOKEN='(アクセストークン)'
TL='user'

EM.run do
  conn = Faye::WebSocket::Client.new(
    "wss://#{INSTANCE}/api/v1/streaming?access_token=#{TOKEN}&stream=#{TL}",
  )
 
    conn.on :open do |e|
        puts "connection success."
    end
 
    conn.on :error do |e|
        puts "error occured."
    end
 
    conn.on :close do |e|
        puts "connection close."
    end
 
    conn.on :message do |msg|
        puts "message receive."
        puts msg.data
    end
end
```

# websocket-client-simple gem を使う例
必要なのは client だけだとか、eventmachine でネイティブエクステンションをインストールしたくないとかいった場合 websocket-client-simple を使うのが良いです。
 
以下の gem を使うのでインストールする。

- websocket-client-simple

``` ruby:
require 'websocket-client-simple'

INSTANCE="(インスタンスのURL)"
TOKEN='(アクセストークン)'
TL='user'

url = "https://#{INSTANCE}/api/v1/streaming?access_token=#{TOKEN}&stream=#{TL}"

# --- streaming receiver
begin
  ws = WebSocket::Client::Simple.connect(url)
rescue => e
  puts "error: #{e}"
else
  ws.on :message do |msg|
    puts "!message"
    p msg
  end
  
  ws.on :open do
    puts "streaming open"
  end
  
  ws.on :close do |e|
    puts "close"
    p e
    exit 1
  end
  
  ws.on :error do |e|
    p e
  end
end

loop do
  sleep 1
end
```


# 簡単な bot のサンプル
メンションで「にゃーん」と言われたら、相手に「にゃーん」と返す簡単な応答 bot です。
Streaming API で通知を監視し、該当のメンションを見つけたら REST API で応答しています。

以下の gem を使用しています

- mastodon-api
- websocket-client-simple
- nokogiri

```ruby:
# coding: utf-8
require 'mastodon'
require 'websocket-client-simple'
require 'nokogiri'
require 'json'

# --- debug switch
VERB = true

# --- config
INSTANCE="(インスタンスのURL)"
TOKEN='(アクセストークン)'
TL='user'

BASEURL = "https://#{INSTANCE}" 
STREAMURL = "#{BASEURL}/api/v1/streaming?access_token=#{TOKEN}&stream=#{TL}"


queue = Queue.new
mastodon_client = Mastodon::REST::Client.new(base_url: BASEURL, bearer_token: TOKEN)

# --- action
def answer(request)
  result = {}
  p request if VERB
  contents = Nokogiri::HTML.parse(request["status"]["content"])
  text = ''
  contents.search('p').children.each do |item|
    text += item.text.strip if item.text?
  end
  #
  if text == "にゃーん"
    result[:username] = request["account"]["username"]
    result[:answer] = "にゃーん"
  end
  #
  p result
  return result
end

# --- streaming receiver
begin
  ws = WebSocket::Client::Simple.connect(STREAMURL)
rescue => e
  puts "error: #{e}"
else
  ws.on :message do |msg|
    if msg.data.size > 0
      begin
        toot = JSON.parse(msg.data)
        if toot["event"] == "notification"
          body = JSON.parse(toot["payload"])
          if body["type"] == "mention"
            puts "!message" if VERB
            res = answer(body)
            if res.size > 0
              queue.push(res)
            end
          end
        end
      rescue => e
        puts "content parse error."
        puts e
      end
    end
  end
  
  ws.on :open do
    puts "streaming open"
  end
  
  ws.on :close do |e|
    puts "close"
    p e
    exit 1
  end
  
  ws.on :error do |e|
    p e
  end
end


# --- wait loop and response sender
loop do
  action = queue.pop
  if action.key?(:username) && action.key?(:answer)
    toot = "@#{action[:username]} #{action[:answer]}"
    begin
      result = mastodon_client.create_status(toot)
      p result if VERB
    rescue => e
      p e
      exit 1
    end
  end
  sleep 0.2
end
```