【JavaScript】Mastodonで情報収集(コピペでお試し可)

おはようございます！大先生です。
近頃TwitterがAPIの制限を増やしたり、サービス名をXにしたりとやりたい放題で
そろそろ潮時かと思い、Mastodonで情報収集する方向性で持っていこうというお話。

## そもそもMastodonって何？
Mastodonとは、Twitterアンチの方が作成した非中央集権型SNSを提供するためのソフトウェアです。
同じ仕組みで動作しているソフトウェアにMisskeyというものがあり、
こちらは日本人のsyuiloさんが作成した非中央集権型SNSです。
~~(実はMisskeyのほうが見やすくてサブカルに強いイメージがあるので個人的にはこっちのほうが好き)~~

#### Tips: 非中央集権型SNSとは
サーバーに情報を蓄積していくSNSサービス(TwitterやFacebookなど)とは違い、
ブロックチェーン技術を用いてデータの分散を行ったSNSのこと。
上記の特性上、
- 運営のヘイト買って利用規約違反していないにも関わらずBANされる
- 金曜ロードショーで天空の城ラピュタが放映したときにサーバーがバルスされる
なんてことにならないので結構住みやすい。

## Mastodonインスタンス情報の取得
なにはともあれ動かさないと、ですね。
普段はC#とかJavaとかでWebAPI叩いてたのしー！しているので今回は勉強も兼ねてJavaScriptを使ってみようかと思います。(なので、変な実装でも許してヒヤシンス)
Mastodonのサーバーはmstdn.jpを使用します。

```HTML:MastodonAPI_Practice.html
<!DOCTYPE html>
<html>
    <head>
        <title>MastodonAPI Practice</title>
    </head>
    <body>
        <textarea id="responseResultArea" placeholder="結果" 
            style="width:500px; height:500px; font-size:24px"></textarea>
    </body>
</html>
<script>
    var httpRequest = new XMLHttpRequest();
    
    httpRequest.onload = (event) => {
        if(httpRequest.status == "200") {
            alert(httpRequest.status);
            document.getElementById("responseResultArea").value = JSON.stringify(httpRequest.response, null, "\t");
        }
    };

    httpRequest.responseType = "json";
    httpRequest.open("GET","https://mstdn.jp/api/v2/instance");
    httpRequest.send();
</script>
```


```json:結果
// responseResultArea.value
// 注釈は出力後挿入
{
    "domain": "mstdn.jp",
    "title": "mstdn.jp",
    "version": "4.1.3",
    "source_url": "https://github.com/tootsuite/mastodon",
    "description": "Mastodon日本鯖です．\r\nよろしくお願いいたします。\r\n\r\n(Maintained by Sujitech, LLC)",
    "usage": {
        "users": {
            "active_month": 61470
        }
    },
    // ===省略===
    "configuration": {
        "urls": {
            "streaming": "wss://mstdn.jp",
            "status": ""
        },
    // ===省略===
}
```


...めっちゃ簡単やんけ。(関西人アピール)

これだけで実装できるのほんとやっぱりJavaScriptの強みですね～。
なにより、JavaScript全然触ったことない私でもこれくらいなら小一時間でできるくらいですしね
この中でconfiguration.urls.streamingの値がWebSocketのURLですね。
WebSocketを使えば通知を意のままに操れるといっても過言ではないんではないでしょうか？

#### Tips: WebSocketとは
通信プロトコルの一つで相互接続状態にしておくことで、リアルタイムで情報の共有が行える。
Webhookと比較するのは間違えてるのかもしれないですが、DiscordでWebhookを使っていた身からすると
とても安定した通信と、Slackを使用したり、サーバーを立てる必要がないのはとても大きいですね。
(もしかしたら使い方間違えてたかもしれないケド…)

## WebSocketでタイムラインの受信
次はWebSocketを使用してタイムラインの受信を行います。

```HTML:WebSocket_Test.html
<!DOCTYPE html>
<html>
    <head>
        <title>WebSocket Test</title>
        <style>*{font-size: 24px;}</style>
    </head>
    <body>
        <input id="connectToggleSocketButton" type="button" value="通信開始"
            onclick="connectToggleSocketButton_Click();">
        <span id="connectStatusIcon">■</span> <label id="connectStatusLabel">通信停止中</label>
        <textarea id="responseResultArea" placeholder="結果" 
            style="width:1000px; height:800px;"></textarea>
    </body>
</html>
<script>
    var isStarted = false;
    var socket;

    function connectToggleSocketButton_Click() {
        if(isStarted) { // 通信停止時
            socket.close();
        } else { // 通信開始時
            socket = new WebSocket("wss://mstdn.jp/api/v1/streaming/?stream=public:local");

            socket.onopen = event => {
                    var connectStatusIcon = document.getElementById("connectStatusIcon");
                    connectStatusIcon.innerText = "●";
                    connectStatusIcon.style.color = "#F00";
                    document.getElementById("connectStatusLabel").innerText = "通信中";
                    document.getElementById("connectToggleSocketButton").value = "通信停止";
                    isStarted = true;
            };
            socket.onmessage = event => {
                var resData = JSON.parse(event.data);
                document.getElementById("responseResultArea").value = JSON.stringify(JSON.parse(resData.payload), null, "\t")
                    + document.getElementById("responseResultArea").value;
            };
            socket.onerror = event => {
                alert("OnError" + event.data);
            };

            socket.onclose = () => {
                var connectStatusIcon = document.getElementById("connectStatusIcon");
                connectStatusIcon.innerText = "■";
                connectStatusIcon.style.color = "#000";
                document.getElementById("connectStatusLabel").innerText = "通信停止中";
                document.getElementById("connectToggleSocketButton").value = "通信開始";
                isStarted = false;
                socket = null;
            };
        }
    }
</script>
```

```json:結果
// responseResultArea.value
// 注釈は出力後挿入
// 人様の実際のトゥートのため一部情報を伏せてます
{
    "id": "[***]",
    "created_at": "2023-08-09T15:46:11.156Z",
    "in_reply_to_id": null,
    "in_reply_to_account_id": null,
    "sensitive": false,
    "spoiler_text": "",
    "visibility": "public",
    "language": "ja",
    "uri": "https://mstdn.jp/users/[***]",
    "url": "https://mstdn.jp/[***]",
    "replies_count": 0,
    "reblogs_count": 0,
    "favourites_count": 0,
    "edited_at": null,
    "content": "[HTML形式の投稿内容]",
    "reblog": null,
    "application": {
        "name": "Tootle for Mastodon",
        "website": null
    },
    "account": {
        "id": "[***]",
        "username": "[***]",
        "acct": "[***]",
        "display_name": "[***]",
        "locked": false,
        "bot": false,
        "discoverable": true,
        "group": false,
        "created_at": "2017-04-22T00:00:00.000Z",
    // ===省略===
}
```
これを使えば好きなタイミングで投稿の取得が可能になり、好きなタイミングで投稿の取得を停止することができます。
結果の中のcontentの部分が投稿された内容になりますね。

今回はmstdn.jpのローカルタイムライン(同一サーバーのユーザーの投稿)を使用しましたが、
実際は連合タイムライン(同一サーバーのユーザーがフォローしている他ユーザー等の投稿)も確認をすることができます。
ただ、連合タイムラインは他サーバーの投稿まで取得するのでとてつもないレベルで受信するのであまりお勧めしません。

## 最後に
今回はJavaScriptで行いましたが、C#やJava、Pythonなどの言語を使用するとMastodonのネイティブアプリの開発にも使えますね。
ここではやっていませんが、タグやユーザーで絞り込んで受信することも可能なようなので推しの監視も可能なので
ストーカーにならない程度に活用できたらなと思います。
以上、長くなってしまいましたが、最後までお付き合いいただきありがとうございました。
ご指摘やご質問等ございましたらお気軽にコメントをば。

#### 参考資料
- [Mastodon公式ドキュメント](https://docs.joinmastodon.org/)
- [Mastodon APIを使ってみる](https://www.antun.net/tips/api/mastodon.html)