やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

BlueSky API で投稿してみる JavaScript ブラウザ編

 GitHub Pagesにアップロードされたページから投稿できる。

成果物

eye-catch

手順

事前準備

  1. BlueSkyアカウントを作る
  2. アプリパスワードを作る

手順

  1. DEMOにアクセスする
  2. 自分のハンドルを入力する(ytyaru.bsky.social等)
  3. アプリパスワードを入力する(1234-abcd-5678-efgh等)
  4. 投稿内容を入力する(300字以内)
  5. 投稿ボタンを押す
  6. 自分のハンドルのリンクをクリックする
  7. 自分のBlueSkyプロフィールサイトに行くので投稿されたか確認する

技術情報

 実装するにあたり次の情報が必要だった。

  • ローカルサーバ
  • OGP
  • BlueSky API

ローカルサーバ

OGP (OpenGraph Protocol)

 BlueSkyでリンクカードを表示する時のメタデータをHTMLに設定する。その書式はOGPで定義されている。

<head prefix="og: https://ogp.me/ns#">
  <meta property="og:type" content="article">
  <meta property="og:title" content="BlueSky APIで投稿する">
  <meta property="og:description" content="JavaScriptでAPIを叩くページを作り、GitHub Pagesにアップロードした。">
  <meta property="og:url" content="https://ytyaru.github.io/JS.BlueSky.API.post.20250805153030/2/index.html">
  <meta property="og:site_name" content="GitHub Pages">
  <meta property="og:image" content="https://github.com/ytyaru/JS.BlueSky.API.post.20250805153030/blob/master/docs/asset/image/bsky-post-page.png?raw=true">
  ...
<head>
  1. 実行画面をスクショする
  2. 1をGitHubリポジトリにプッシュする
  3. 2の直リンクURLを取得する
  4. 3をOGPog:imageにセットする

BlueSky API

 以下を参考にした。

 日本語の情報を探していたが、なぜかGAS(Google Apps Script)上の情報ばかりだった。

 このGASは専用APIを使っているので、これをJavaScript APIに置換する作業が面倒だった。

bluesky.js

 BlueSky API部分はbluesky.jsにまとめた。以下抜粋。

class BlueSky {
    constructor(){this._={}}
    set handle(v) {if (Type.isStr(v) && 0 < v.length){this._.handle=v}}
    set appPw(v) {if (Type.isStr(v) && 0 < v.length){this._.appPw=v}}
    async #createSession() {
        const res = await fetch('https://bsky.social/xrpc/com.atproto.server.createSession', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                identifier: this._.handle,
                password: this._.appPw
            })
        });
        if (200===res.status) {
            const json = await res.json();
            const sessionData = json;
            console.log('✅ Blueskyログイン成功!');
            return sessionData.accessJwt;
        } else {throw new Error(`ログイン失敗`, res);}
    }
    async post(message) {
        try {
            const accessJwt = await this.#createSession();
            const res = await fetch('https://bsky.social/xrpc/com.atproto.repo.createRecord', {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${accessJwt}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    repo: this._.handle,
                    collection: 'app.bsky.feed.post',
                    record: {
                        text: message,
                        createdAt: new Date().toISOString()
                    }
                })
            });
            return 200 === res.status;
        } catch (error) {
            console.error('❌ 投稿エラー:', error);
            return false;
        }
    }
}

 ようするに以下2つのAPIを叩いている。

概要 API
アクセストークン取得する https://bsky.social/xrpc/com.atproto.server.createSessionDoc createSession
投稿する https://bsky.social/xrpc/com.atproto.repo.createRecordDoc createRecord

 ただこれ、公式サイトのログイン専用ページでないから行儀が悪い。

OAuth2認証

 サードパーティ製サイトで安全にログインするためには公式サイトが提供する認証ページとやり取りする方法がある。

 これは前にMastodon APIでやった記憶がある。

  1. 自作サイトで認証用ページURLをリクエストする
  2. ユーザは認証用ページでパスワードを入力するから自作サイトで無断収集されず安全
  3. 自作サイト側では2の応答結果としてアクセストークンをもらう
  4. アクセストークンをHTTPヘッダに付与して投稿APIを叩く

 ログイン認証ページでユーザ名とパスワードを入力するのだが、それは公式サイトのページだから安全が保証されている。自作サイトでそのページをリクエストすれば安全に認証できる。

 認証用ページからレスポンスが帰ってくるとURLパラメータとしてアクセストークンが付いてくるから、それをHTTPヘッダに付与してPOSTして投稿する。

デモ

https://ytyaru.github.io/Html.Mastodon.Account.Info.20220611094830/

リポジトリ

https://github.com/ytyaru/Html.Mastodon.Account.Info.20220611094830

認証用ページにリクエストするコード部分

https://github.com/ytyaru/Html.Mastodon.Account.Info.20220611094830/blob/master/docs/js/mastodon-authorizer.js#L39

BlueSkyではどうOAuth認証するか不明

 ググったけど分からなかった。いつかまた調査したい。