Twitterで、ツイートを外部に出す機能あるでしょ。例えばこういうやつ。↓
フルーツサンドとゼリー
— ゲーミングにがうり (@nigauri) April 9, 2023
良い pic.twitter.com/C1YiSv9oCG
これみたいに、Blueskyの記事を外部(例えばブログ等)に出力する方法についてのメモ。GAS(Google Apps Script)を使います。
一言で表すと「BlueskyのPostのURLを引数にすると『document.writeでPostの内容を書き出すJavaScript』を返却するWebサービス」をGAS上に作成する。
ただBlueskyは現在のところ招待制&ユーザ登録してないと記事が読めないというクローズドなSNSなので、記事を勝手に外部に出力する機能はどうかと思うんだけどあくまで勉強として。
以前書いた「Blueskyの自分の投稿をRSSに変換する」記事の応用なのでまずはそちらをどうぞ。

もくじ
PostのURLを解析する
まず、PostのURLは「アプリからShareで取得した場合」と「Webアプリから取得した場合」で少し違う。
- アプリから取得 … https://bsky.app/profile/nigauri.me/post/3jtw7hfft4c2c
- Webアプリから取得 … https://staging.bsky.app/profile/nigauri.me/post/3jtw7hfft4c2c
ただ違うと言ってもサブドメインのstagingがつくかどうかだけで他は一緒。
このうち実際に使うのは赤文字部分、ハンドル名の部分と末尾のIDっぽいやつだけ。なのでこんな感じの処理を作る。
const postUrlRegex = /^https:\/\/(staging\.)?bsky\.app\/profile\/([^/]+)\/post\/(.+)$/;
function getInfoFromURI(uri) {
if (postUrlRegex.test(uri)) {
let result = postUrlRegex.exec(uri);
if (result != null) {
return {
handle: result[2],
postid: result[3],
}
}
}
return null;
}ハンドル名からDIDを取得する
詳しくは後述するけど、投稿を取得するためのURIを作成するためにはハンドル名ではなく対象のハンドル名を持つユーザのDIDを取得する必要がある。
com.atproto.identity.resolveHandleにハンドル名をつけて投げたときの戻り値にDIDが格納されているのでそれを持っておく。ちなみにこの機能はログイン無しで使える。
let info = getInfoFromURI(paramUri);
// ここでnullチェックを行うこと。今回は省略
let url = https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${info.handle};
let response = UrlFetchApp.fetch(url);
let did = JSON.parse(response.getContentText()).did;ログインする
GASを使用してBlueskyの自分の投稿をRSSに変換する 参照
Postを取得する
投稿を取得するのに必要なPostのAT URIを作成する
投稿を取得するのに必要なuri情報はat-uri(at://did:plc:…)形式で記述する必要がある。HTTPのURI形式からの直接的な変換・解決方法がよくわからなかったので今回は自力で作成する…。
と言ってもこれを作るために必要な情報はもう得ているのであとは組み立てるだけだ。ずばりこう。
https://bsky.app/profile/nigauri.me/post/3jtw7hfft4c2c
↓
at://did:plc:wajg6p7j7nciarpetolo75t2/app.bsky.feed.post/3jtw7hfft4c2c
Postの内容を取得する
at-uriを作成してapp.bsky.feed.getPostThreadの引数として渡すと対象のPostの内容が返ってくる。
let aturi =at://${did}/app.bsky.feed.post/${info.postid}; let posturi =https://bsky.social/xrpc/app.bsky.feed.getPostThread?uri=${aturi}; const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions & { method: GoogleAppsScript.URL_Fetch.HttpMethod } = { "method": "get", "contentType": "application/json", "headers": { "Authorization":Bearer ${accessJwt}} }; let response = UrlFetchApp.fetch(posturi, options); let responseJSON = JSON.parse(response.getContentText());
あとはこの戻り値を使って好きにすればいい。
返却用のJavaScriptを作成する
今回は取得した内容をdocument.writeでその場にそのまま書き出しちゃうようなスクリプトとして作成する。
以下は投稿文、画像、ユーザアイコン、ユーザ名等だけを出力するような例。
let post = responseJSON.thread.post;
let record = post.record;
let author = post.author;
let imgTag = "";
let embed = post.embed;
if (embed != null && embed.images != null && 0 < embed.images.length) {
for (let image of embed.images) {
imgTag += <img src="${image.fullsize}" />;
}
}
if (imgTag != "") {
imgTag = "<br>" + imgTag;
}
let js = document.write(
<div class="blueskypost">
<p class="blueskytext">${record.text}${imgTag}</p>
<p class="blueskycreatedAt">${post.record.createdAt}</p>
<p class="blueskyauthor">
<img src="${author.avatar}">
${author.displayName} (${author.handle})
</p>
</div>
);;Webサービス化する
doGet() を作ってさっきのJavaScriptをレスポンスとして返す。
function doGet(e) {
let paramUri = e.parameter.uri;
let info = getInfoFromURI(paramUri);
let did = ハンドル名からDIDを取得する処理(info.handle);
let accessJwt = accessJwt取得処理();
let response = Post取得処理(accessJwt, did, info.postid);
let js = JavaScript作成処理(response);
let out = ContentService.createTextOutput();
out.setMimeType(ContentService.MimeType.JAVASCRIPT);
out.setContent(js);
return out;
}あとはWebアプリとしてデプロイしてURLをメモっておく。
使い方
例えば文中に
<script src="https://script.google.com/macros/s/xxx/exec?uri=https://bsky.app/profile/nigauri.me/post/3jtv53vqf522f"></script>
などと書くとその場に対象のPostの内容がJavaScriptで書き出される。見た目はCSSでなんとかする。
↓表示例
