Mastodon のストリーミング API を使いたいが、Qiitadon で動かない。
TL; DR
ホストを
streaming.qiitadon.com
、ポートを4000
に変更しましょう。
Qiita の マストドン・インスタンス「Qiitadon」は、Pawoo などと Mastodon Streaming API のエンドポイントが以下のように異なっています。
項目 | 値 | 備考 |
---|---|---|
ホスト名 |
streaming.qiitadon.com |
qiitadon.com は CDN を通すので、パススルー用にストリーミング専用のサブ・ドメインが用意されているようです。 |
ポート |
4000 |
一般的には 443
|
PHP のパッケージを作った
他のインスタンス(Mastodon サーバ)でも、もっと簡単に Mastodon Streaming API が使えるようにクラスにしてみたのでよろしかったら使ってみて下さい。Packagist にも公開しているので、composer
でインストールできます。
- Javascript + Websocket ならこちら
- Qiitadon 新着 通知 | gist | @woxtu @ GitHub
動作の仕組み、詳細や Pure PHP で自分で実装したい場合は TS; DR 参照。
composer require keinos/mastodon-streaming-api-listener
<?php
require_once __DIR__ . '/../vendor/autoload.php';
// サーバーの URL を指定するだけでポートなども自動で検知します。
$conf = [
'url_host' => 'https://qiitadon.com/',
'type_stream' => 'local', // LTL を指定。指定しない場合は 'public'(デフォルト)に設定されます。
];
$listener = new \KEINOS\MSTDN_TOOLS\Listener\Listener($conf);
foreach($listener as $event_name => $data_payload) {
echo 'Event name: ' . $event_name . PHP_EOL;
echo 'Data: '. PHP_EOL;
print_r(json_decode($data_payload));
}
TS; DR
この Qiitadon の Streaming API のアドレス streaming.qiitadon.com
とポート 4000
は、ソースを覗いて見つけました。view-source:https://qiitadon.com/web/timelines/public
の 26 行目です。
しかし、Mastodon API の仕様をよくよく読むと instance
というエンドポイント(API のメソッドを実行する URL)がありました。その API を叩くとサラリと情報が JSON 形式で返ってきました。なんということでしょう。
インスタンス(Mastodon サーバー)情報の API エンドポイント
https://qiitadon.com/api/v1/instance
instance
Informational endpoint to discover information about a Mastodon website.
( instance | API Methods @ docs.joinmastodon.org より)(筆者訳)
instance
Mastodon の Web サイトに関する情報を得るためのエンドポイント。
以下のリクエストすると、Qiitadon のインスタンス(サーバー)情報を取得できます。API を叩いて JSON から urls
要素を抜き出すと、ちゃんとポートが 4000
であることがわかります。プロトコルも wss
であることがわかります。つまり SSL/TLS 通信の WebSocket プロトコルということです。
$ curl -s https://qiitadon.com/api/v1/instance | jq .urls
{
"streaming_api": "wss://streaming.qiitadon.com:4000"
}
🐒
WebSocket
ライブラリ/パッケージを使ってHTTP/1.1 401 Unauthorized
のレスポンスが返ってくる場合は、アクセス・トークンを Qiitadon で発行して、リクエストのヘッダーに以下を加えてみてください。これはプロトコルを HTTP から WebSocket にアップグレードする際に、Mastodon はアクセス・トークンを必要とするからです。Authorization: Bearer [アクセストークン]
- 合わせて読みたい: WebSocketについて調べてみた。 @ Qiita
- 参考文献: http://localhost:4000/ {"error":"Error: Missing access token"} | Issue #1016 | Mastodon @ GitHub
PHP でストリーミングを受信するサンプル
Pure PHP
WebSocket のパッケージを使ってもいいのですが、ここでは Pure PHP + fsockopen()
でソケットから生の情報を取得してみたいと思います。WebSocket
を使う場合はアクセス・トークンを必要とするからです。
以下は、簡単な動作を確認する PHP スクリプトです。実行後、数秒待つとストリームが始まります。
- Mastodon は SSL 接続が必須なので PHP の OpenSSL が有効である必要があります)
- macOS の場合は何もインストールしなくても(デフォルトで)動きます。
- もし Mac のファイアーウォールで跳ねられる場合は、ターミナルに「フルディスクアクセス」の権限を与えてみてください。
<?php
const CRLF="\r\n";
// ソケットの準備
$hostname = 'ssl://streaming.qiitadon.com';
$port = 4000;
$timeout = 5; //sec
$errno = '';
$errstr = '';
// ソケットオープン
$fp = fsockopen($hostname, $port, $errno, $errstr, $timeout);
if (! $fp) {
die("Error: {$errstr} ({$errno})");
}
// リクエストヘッダの準備
$method = 'GET';
$endpoint = '/api/v1/streaming/public';
$host = 'qiitadon.com'; //streaming.qiitadon.com でない
$user_agent = 'qithub-bot';
$req = [
"{$method} {$endpoint} HTTP/1.1",
"Host: {$host}",
"User-Agent: {$user_agent}",
];
$glue = CRLF;
$req = implode($glue, $req) . CRLF . CRLF;
// GETリクエストを送信
fwrite($fp, $req);
// ストリーミングの読み込み
while (! feof($fp)) {
$read = fgets($fp);
print_r($read);
// バッファを出力(画面更新)
@ob_flush();
@flush();
}
fclose($fp);
上記スクリプトの簡易説明ですが、基本的に以下を行っています。
- Qiitadon と通信できるように
fsockopen
ソケットを作り、ファイルとして読み書きできるようにファイル・ポインタを取得する。 - その際の接続先アドレスを
streaming.qiitadon.com
、ポートを 4000 番、通信プロトコルをssl
に設定。(ssl://streaming.qiitadon.com
) - ソケットに GET メソッドでデータを書き込む。(
fwrite(<ソケットのファイル・ポインタ>,<データ>)
) -
EOF
(データの終端)が送られて来るまでソケットを読みこむ。(fgets(<ソケットのファイル・ポインタ>)
)
TIPS
LTL を受信する
FTL(連合タイムライン)でなく、LTL(ローカル・タイムライン)のストリーミングが欲しい場合はエンドポイントは以下になります。public:local
つまり /public
が /public/local
になります。
-
/api/v1/streaming/public/local
- streaming | timelines | methods @ Mastodon documentation
流れてくるデータ
WebSocket を使わない場合は、Server-Sent Events として送信されてきます。
SSL ポートにソケットを接続・オープンして、エンドポイントに GET
リクエストすると HTTP ヘッダー情報(ハンドシェイク後のレスポンス・ヘッダー)が流れた後、「データサイズ」(chunk
) → 「情報」(event
or data
)→ 空行 の順で流れてきます。
chunk size
は「情報」の長さで、情報 + "\n
" のバイト長になります。なお、「情報」のタイプが data:
の場合、本体データを payload
と呼びます。
[chunk size]
event: [name]
e ← 下記 "event: update\n" のバイト数
event: update
[chunk size]
data: [payload]
588 ← 下記 "data: {"id":...}\n" のバイト数
data: {"id":...}
「情報」が長い場合は、分割されて送信されます。その場合も「データサイズ」→「情報」で流れてきます。受け手は分割されたデータを結合して使う必要があります。
5a3 ← 下記行のバイト数
data: {"id":"10 ... ssing.png
9a ← 下記行のバイト数
","followe ... "tags":[],"emojis":[]}
注意すべきは改行コードです。基本的に CRLF
(\r\n
)ですが、「情報」がチャンクでない(分割されていない)場合は LF
(\n
)になります。また、その時「情報」のタイプが data
であった場合は、さらに \n
(LF, x0a
)が追加されます。
そのため、条件による改行の組み合わせが複雑なので、受け取ったデータは CRLF
を LF
に統一すると楽です。
このようにソケットの生情報をゴリゴリ解析して実装するのも楽しいのですが、やはり WebSocket の PHP クライアントのライブラリを使う方が楽です。しかし、WebSocket の場合は、アクセストークンが必須なので、ライブラリを作ってみました。(注意:サーバが "whitelist-mode" になっている場合は、
- Simple Mastodon Streaming API Listener @ GitHub
- Composer install:
$ composer require keinos/mastodon-streaming-api-listener
動作確認済み環境
- macOS HighSierra(OSX 10.13.6)
-
php --version
: PHP 7.2.6 (cli)
-
- macOS Mojave(OSX 10.14.6) + ターミナルのフルディスクアクセス付き
-
php --version
: PHP 7.1.33 (cli)
-
参考文献
- Streamingサーバーに接続できない Issue #54 @ increments @ GitHub
- PHP で Mastodon の Streaming API を受信する。 @ Qiita
- Qiitadon酒場 | PHP チャンネル @ Discord
-
view-source:https://qiitadon.com/web/timelines/public
のソースの26行目