Posted at

ドシロウトがHTMLファイルひとつで複数のマストドンからストリーミングしてみた


0.初めに

私はエンジニアではないただのドシロウトです。

自分が登録していないマストドンのインスタンスの直近のローカルタイムライン(LTL)を眺めようとして以下の記事を見つけました。


タイムラインを“のぞき見” アカウント作成不要のマストドン用Webサービス登場 「Mastodon Timeline Peeping Tool」

http://www.itmedia.co.jp/news/articles/1704/20/news037.html


Mastodon Timeline Peeping Toolを利用すれば、確かにアカウントなしで他のインスタンスのローカル、連合タイムライン(FTL)をのぞき見できます。

私は複数インスタンスの直近の情報が見たかったのでこのツールの公開されているソースを調べました。


yukimochi/Mastodon-Timeline-Peeping-Tool

https://github.com/yukimochi/Mastodon-Timeline-Peeping-Tool


多分マストドン基礎知識なんでしょうがストリーミングAPIを利用して直近の情報を得ているとはじめて知りました。


tootsuite/documentation

https://github.com/tootsuite/documentation/blob/master/Using-the-API/Streaming-API.md


そこでMastodon Timeline Peeping Toolを参考にしてHTMLファイル一つで複数インスタンスのLTL、FTLをまとめて取得、簡易表示するツールを作ってみました。


1.作ったもの

以下の画像の通りです。

mastqiita.jpg

ボタンを押すと事前に指定済みの複数インスタンスのLTLまたはFTLに新着投稿があるとリアルタイムに表示します。

HTML表示は可能な限り簡素化しました。


2.コード

短いですが以下です。HTMLファイル一つでストリーミングできてます。


test.html

<!DOCTYPE html>

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>マストドンストリーミングAPI</h1>
<button class="start-ws-ltl">ローカルタイムライン</button>
<button class="start-ws-ftl">連合タイムライン</button>
<br><br>
<div class="activity-stream"></div>
<script>
var ws_connection = null; /* webソケット接続用 */
// インスタンスをカンマ区切りで指定
var instance = '(インスタンスを指定)';
var instance2 = instance.split(',');
var federate = null; /* ローカル、連合タイムライン判定用 */

window.onload = function () {
event.preventDefault();
var ws_ltl = document.getElementsByClassName("start-ws-ltl")[0];
var ws_ftl = document.getElementsByClassName("start-ws-ftl")[0];
// ローカルタイムラインボタン押下時
ws_ltl.addEventListener('click', function (event) {
event.preventDefault();
federate = false;
// インスタンス分だけWebSocket接続を開く
for (var i=0;i < instance2.length;i++) {
open_ws(instance2[i], federate);
// メッセージが受信時のddEventListener
ws_connection.addEventListener('message', function (event) {
insert_toot(JSON.parse(event.data).payload);
});
};
});

// 連合タイムラインボタン押下時
ws_ftl.addEventListener('click', function (event) {
event.preventDefault();
federate = true;

for (var i=0;i < instance2.length;i++) {
open_ws(instance2[i], federate);
ws_connection.addEventListener('message', function (event) {
insert_toot(JSON.parse(event.data).payload);
});
};
});
}

// WebSocket接続関数
function open_ws(instance2, federate) {
url = 'wss://' + instance2 + '/api/v1/streaming';
if (federate) {
url += '?stream=public';
} else {
url += '?stream=public:local';
}
ws_connection = new WebSocket(url);
}

// トゥート→HTML書出し関数
function insert_toot(text) {
entrys = processing_entrys(text); /* トゥート取得 */
if (entrys !== null) {
entrys.forEach(entry => {
parent = document.getElementsByClassName("activity-stream")[0];
parent.innerHTML = entry+'<hr>'+ parent.innerHTML;
});
}
}

// トゥート編集関数
function processing_entrys(data) {
statuses = [JSON.parse(data)];
if (statuses.length > 0) {
entrys = [];
for (let idx = 0; idx < statuses.length; idx++) {
const status = statuses[idx];
try {
account = status['account'];
media_attachments = status['media_attachments'];
status__header = html_status__header(status['url'], status['created_at'], account['url'], account['avatar'], account['display_name'], account['acct']);
status__content = html_status__content(status['content']);

md_att = [];
for (let idx_md_att = 0; idx_md_att < media_attachments.length; idx_md_att++) {
const media_attachment = media_attachments[idx_md_att];
md_att.push(media_attachment['url']);
}
MediaGallery = null;
if (media_attachments.length > 0) {
MediaGallery = html_MediaGallery(md_att);
}
entrys.unshift(html_entry(status__header, status__content, MediaGallery));
}
catch (e) {

}
};
return entrys;
} else {
return null;
}
}

// トゥート結合関数
function html_entry(status__header, status__content, MediaGallery) {
var entry;
entry = status__header + '<BR>';
entry += status__content + '<BR>';
if (MediaGallery !== null) {
entry += MediaGallery + '<BR>';
}
return entry;
}

// トゥートヘッダー編集関数
function html_status__header(status_url, status_time, author_url, author_avatar_url, author_name, author_id) {
var status__header;
status__header = author_name + ' ' + new Date(status_time).toLocaleString();

var wkaa = author_url.replace('https://','');
wkaa = wkaa.replace(('/@'+author_id),'');
status__header += ' @' + author_id +'(' + wkaa +')';

return status__header;
}

// トゥートテキスト部編集関数…特に何もしていない
function html_status__content(text) {
var status__content;
status__content = text;
return status__content;
}

// トゥート画像部編集関数
function html_MediaGallery(img_urls) {
var media_gallery;
img_urls.forEach(img_url => {
media_gallery = '<img width=\"100\" src=\"';
media_gallery += img_url;
media_gallery += '\"><BR>';
});
return media_gallery;
}

</script>
</body>
</html>



3.気づいた事

試していて気づいた事は以下です。


  • 流量の少ないインスタンスをまとめて眺めるのに便利

  • 多分、ずっと動かすと重くなる(HTMLに書き出しつづけるので)

  • インスタンスによってはストリーミングに対応していない(Qiita丼など)

  • 複数タブでこのHTMLを実行するとエラーになる

  • ローカルでも動く


4.まとめ

元のツールが素晴らしいのでたったこれだけでストリーミングが取得できました。

なかなか便利かなと思います。

以 上