Blueskyの個別ポストURLから埋め込みコードを生成する

Blueskyのポスト埋め込みコード(<blockquote ~)を個別ポストのURLから生成するPHPコード。

事前準備

  • Blueskyのアプリパスワードを取得しておく
  • phpdotenvをインストールして.envファイルに以下を入力
ID='Blueskyのアカウント'
API='アプリパスワード'

コード

コード例の埋め込みポストはBluesky公式のポストから。

//phpdotenv
require __DIR__. '/vendor/autoload.php';
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
/**
* BlueskyアカウントのDIDを取得
*/
function BlueskyDid($bsky_handle) {
$query_url = "https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle={$bsky_handle}";
$ch = curl_init($query_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* アクセストークンの取得
*/
function getBlueskyAccessToken($identifier, $password) {
$login_url = 'https://bsky.social/xrpc/com.atproto.server.createSession';
$data = [
'identifier' => $identifier,
'password' => $password
];
$ch = curl_init($login_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
$response = curl_exec($ch);
curl_close($ch);
$session = json_decode($response, true);
return $session['accessJwt'] ?? null;
}
/**
* postを取得
*/
function getBlueskyPost($token, $post_url) {
$ch = curl_init($post_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer {$token}"]);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 埋め込み生成
*/
$url = 'https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l';
preg_match('/https:\/\/bsky\.app\/profile\/(.+)\/post\/(.+)/', $url, $match);
$account = BlueskyDid($match[1]);
$account_data = json_decode($account, true) ?: [];
$api_url = "https://bsky.social/xrpc/app.bsky.feed.getPosts?uris=at://{$account_data['did']}/app.bsky.feed.post/{$match[2]}";
$token = getBlueskyAccessToken($_ENV['ID'], $_ENV['API']);
$post = getBlueskyPost($token, $api_url);
$post_data = json_decode($post, true) ?: [];
//日付変換 UTC->JST
$post_date = new DateTime($post_data['posts'][0]['record']['createdAt']);
$post_date->setTimeZone(new DateTimeZone('Asia/Tokyo'));
$bluesky_embed = <<<EOT
<blockquote class="bluesky-embed" data-bluesky-uri="{$post_data['posts'][0]['uri']}" data-bluesky-cid="{$post_data['posts'][0]['cid']}" data-bluesky-embed-color-mode="system"><p lang="{$post_data['posts'][0]['record']['langs'][0]}">{$post_data['posts'][0]['record']['text']}<br><br><a href="https://bsky.app/profile/{$post_data['posts'][0]['author']['did']}/post/{$match[2]}?ref_src=embed">[image or embed]</a></p>&mdash; {$post_data['posts'][0]['author']['displayName']} (<a href="https://bsky.app/profile/{$post_data['posts'][0]['author']['did']}?ref_src=embed">@{$post_data['posts'][0]['author']['handle']}</a>) <a href="https://bsky.app/profile/{$post_data['posts'][0]['author']['did']}/post/{$match[2]}?ref_src=embed">{$post_date->format('Y年n月j日 G:i')}</a></blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>
EOT;
echo $bluesky_embed;

getBlueskyPost()で取得できるデータにポストのslug単体がないので、URLから切り出している($post_data['posts'][0]['uri']から切り出してもいいかもだけど)

テキストのみのポストもURLつきのポストも画像付きのポストも同じコードで埋め込める。

ただしログインしないと見れないポストは、埋め込みウィジェット内に「「The author of this post has requested their posts not be displayed on external sites.」という文言と該当ポストへのリンクが表示される。
現時点ではこれを識別する方法はなさそう?

参考

Get Started | Bluesky