けんごのお屋敷

2013-10-23

FacebookChatAPIを使ってFacebookの友逹にメッセージを送信する

Facebook にはチャット機能があって、送ったチャットはここで見ることが出来ます。

もし相手がオンラインなら、画面右下にこんな感じのチャットダイアログが表示されます。

このチャット、使っている人も多いかと思いますが Facebook は Chat API というチャットメッセージ送信のための API を提供していて、この API を通して Facebook の友逹にチャットを送信できます。この Chat API は Graph API とはまったく別の物で、使い方も違います。Graph API を ruby から扱うための gem は他にもたくさんあるのですが、それらから Chat API を扱うことはできません。

今回、自分で作ってる Web サービスに通知の仕組みを組み込みたくて Chat API を ruby から利用するための gem を作って公開したので、それを紹介したいと思います。

Chat API の仕様については Facebook Developers のページで確認できます。

https://developers.facebook.com/docs/chat/

FacebookChat

facebook_chat というそのまんまの名前の gem を作りました。ソースは github で公開しています。

https://github.com/tkengo/facebook_chat

使い方はリポジトリの README にだいたい書いているのですが、ここでも紹介します。

インストール

bundler を使っている場合は Gemfile に以下の行を追加して

gem 'facebook_chat'

そして

$ bundle install

してください。

事前準備

Facebook Chat API を利用するためには事前に Facebook のアプリを作っておく必要があります。Facebook Developers のサイトから新しく Facebook アプリを作っておきます。その際、アプリの権限に xmpp_login という権限を追加しておきます。

チャットを送信するためにはアクセストークンが必要なので、チャットを送信する側のユーザーで OAuth 認証を済ませておき、アクセストークンを取得しておきます。ruby でやるとしたら omniauth-facebook 辺りが有名ではないでしょうか。その際に xmpp_login に許可を求めるのを忘れずに。

設定

アプリの準備が出来たらコーディングしていきます。まずは Chat API クライアントの設定を行います。以下のようなコードを書きます。Rails で開発をしている場合は、以下のコードを config/initializer/facebook_chat.rb に保存しても良いかと思います。

require 'facebook_chat'

FacebookChat::Client.configure do |config|
  config.api_key = '****' # 先に作っておいた Facebook アプリの API Key を指定します。
end

メッセージ送信

あとは、メッセージを送りたい箇所で以下のようなコードを書きます。

# メッセージを送る人のアクセストークンを渡してクライアントを作ります。
# アクセストークンは事前に別途OAuth認証をして取得したものを使います。
# OAuth認証の際にはxmpp_loginの許可を求めるのを忘れずに。
access_token = '****'
cilent = FacebookChat::Client.new(access_token)

# チャットの送り先ユーザーのIDを指定してsendメソッドを呼び出します。
to = '1000000000'
client.send(to, "こんにちは")

これで ID が 1000000000 の人に対して こんにちは というチャットメッセージが送信されます。

ちなみに ID は Graph API で取得できます。Graph API を扱う gem はいろいろありますので、別途探してみて下さい。※以下は Graph API Explorer を使って Facebook CEO の マークの ID を取得してみたところ。この ID を指定します。(*それにしても ID 4 って。さすが(笑)。ID 1 の人が誰なのか気になる。*)

実装の裏側

Chat API は メッセージの送受信に XMPP と呼ばれるプロトコルを利用しています(実際は XMPP に互換性のある独自のプロトコルを利用しているようですが。そのため従来の XMPP サーバーと仕様が異なる部分もあるそうです)。XMPP は XML をベースとしたプロトコルで、メッセージは XML 形式のテキストでやり取りします。

ruby から XMPP を用いて通信を行うための xmpp4r という gem があるのですが、僕が作った facebook_chat の gem もこれを利用しています。

認証

さて Chat API は XMPP を使って通信を行いますが、チャットを送信する前に認証をする必要があります。チャットを送るユーザーを Facebook が識別するためですね。認証には SASL という機構を使います。SASL は認証用のフレームワークで SASL を使った認証についてはいくつかの方式があります。

  • PLAIN : 単純にIDとパスワードを平文で送信して認証する
  • CRAM-MD5 : IDとパスワードを送信して認証するが、チャレンジ&レスポンス方式で、パスワードについては暗号化して送信する
  • DIGEST-MD5 : CRAM-MD5と同くIDとパスワードで認証。チャレンジ&レスポンス方式で、CRAM-MD5よりもう少し強固な認証方法

などなど。この辺の認証方式についてはググればいっぱい情報が出てくると思うのでここでは触れません。

Chat API もこの SASL で認証が出来るので、これを使って認証していきます。ただし Chat API が対応している認証方式は以下の2つです。

  • X-FACEBOOK-PLATFORM
  • DIGEST-MD5

ここで X-FACEBOOK-PLATFORM という認証方式が出てきましたが、これは Facebook のアクセストークンを使って認証する方法です。先に紹介した、IDとパスワードで認証する方式とは違う Facebook 独自のものになります。

Facebook によると可能な限り X-FACEBOOK-PLATFORM の方の認証方式を使うようにと推奨されているので facebook_chat もこちらを使っています。

X-FACEBOOK-PLATFORM

X-FACEBOOK-PLATFORM の認証の流れとしては以下の通りです。

  1. Chat API に対して X-FACEBOOK-PLATFORM で認証開始します、という要求を投げる。
  2. Chat API からの戻り値を Base64 でデコードすると & で区切られた key/value 形式のテキストが返ってきます。
  3. methodnonce というキーが含まれているのでその値を保存しておきます。
  4. 3 で保存しておいた methodnonce と、さらにアクセストークンなど、以下のような認証用のパラメータを構築します。
    • method : 3 で保存しておいた method の値
    • nonce : 3 で保存しておいた nonce の値
    • access_token : 認証したいユーザーのアクセストークン
    • api_key : Facebook アプリの API Key
    • call_id : 連番
    • v1.0 固定
  5. 4 で作ったパラメータを Base64 エンコードして Chat API に送り返します。
  6. パラメータが正しければそのアクセストークンで認証完了しています。その認証済アクセストークンを使ってチャットの送信などが出来ます。

実際のコードは この辺り です。

※1〜3まで

stream.send(generate_auth('X-FACEBOOK-PLATFORM')) do |reply|
  if reply.name == 'challenge' and reply.namespace == Jabber::SASL::NS_SASL
    challenge = CGI.parse(Base64::decode64(reply.text))
  else
    error = reply.first_element(nil).name
  end
  true
end

@method = challenge['method'][0]
@nonce = challenge['nonce'][0]

※4〜5まで

response = {
  :method => @method,
  :nonce => @nonce,
  :access_token => access_token,
  :api_key => FacebookChat::Client.configuration.api_key,
  :call_id => Time.now.to_i,
  :v => '1.0'
}
response_text = response.collect {|k, v| "#{k}=#{v}" }.join('&')

response = REXML::Element.new('response')
response.add_namespace Jabber::SASL::NS_SASL
response.text = Base64::encode64(response_text)

error = nil
@stream.send(response) do |reply|
  if reply.name != 'success'
    error = reply.first_element(nil).name
  end
  true
end

メッセージの送信

こうして認証が完了したアクセストークンを使ってメッセージを送信します。送信というからには送信する先の宛先が必要なのですが Facebook Chat では各ユーザーに JID という、見た目はメールアドレスのような ID が割り当てられているので、それを利用します。

この JID は、以下のような形式になっています。

ユーザーID@chat.facebook.com

先の例でいくと、マークの JID は 4@chat.facebook.com。ID が 1000000000 の人の JID は 1000000000@chat.facebook.com です。SASL で認証を済ませた XMPP 通信の中で、このような JID に対してメッセージを送るように指示を出せば実際に Facebook 上でチャットメッセージが送信されます。

実際のコードは この辺り です。

def send(id, message)
  jid = "#{id}@#{FacebookChat::Client.configuration.host}"
  message = Jabber::Message.new(jid, message)
  @client.send(message)
end

まとめ

実装の話は XMPP やら SASL やらよくわからん単語が出てきましたが、クライアントはそれを意識させないで簡単に使えるように作ったつもりなので良ければ使ってみてバグなどあれば教えて頂ければ嬉しいです。

  • このエントリーをはてなブックマークに追加