Node.js(TypeScript)でBlueSky APIを使って画像付きでツイートするメモ

## はじめに

Twitter APIをしらべてみたらBlueSkyも気になってみた。
結論から言えば、"@atproto/api"さえインストールすればもう大丈夫。よくできている。

文字列にリンク先がある場合はRichTextを使えば、自動判別してくれる。便利。っていうかRichTextを使わないとリンクしてくれない。いけず。

``` typescript:blueskyManager.ts
import {BskyAgent, RichText} from '@atproto/api'
import fs from "fs";
export class BlueskyManager {
    private readonly _agent: BskyAgent;
    constructor() {
        this._agent = new BskyAgent({
            service: "https://bsky.social",
        });
    }

    private login = async () => {
        await this._agent.login({
            identifier: process.env.BLUESKY_USER,
            password: process.env.BLUESKY_PASSWORD
        })
    }
    public send = async (message: string): Promise<void> => {
        await this.login();
        await this._agent.post({
            text: message,
            createdAt: new Date().toISOString()
        })
    }

    public sendRichText = async (message: string): Promise<void> => {
        await this.login();
        const rt: RichText = new RichText({
            text: message,
        })
        await rt.detectFacets(this._agent)
        const postRecord: any = {
            $type: 'app.bsky.feed.post',
            text: rt.text,
            facets: rt.facets,
            createdAt: new Date().toISOString(),
        }
        await this._agent.post(postRecord);
    }
    public sendEmbedImage = async (message: string, imagePath: string): Promise<void> => {
        await this.login();
        try {
            const buffer: Buffer = await fs.readFileSync(imagePath);
            const response: any = await this._agent.uploadBlob(new Uint8Array(buffer), {
                encoding: "image/jpeg",
            });
            await this._agent.post({
                text: message,
                createdAt: new Date().toISOString(),
                embed: {
                    "$type": "app.bsky.embed.images",
                    "images": [{
                        "alt": "image",
                        "image": response.data.blob,
                    }],
                },
            })
        } catch (err) {
            console.log("err " + err);
        }
    }

    public sendRichTextEmbedImage = async (message: string, imagePath: string): Promise<void> => {
        await this.login();
        const buffer: Buffer = await fs.readFileSync(imagePath);
        const response: any = await this._agent.uploadBlob(new Uint8Array(buffer), {
            encoding: "image/jpeg",
        });
        const rt: RichText = new RichText({
            text: message,
        })
        await rt.detectFacets(this._agent)
        const postRecord: any = {
            $type: 'app.bsky.feed.post',
            text: rt.text,
            facets: rt.facets,
            createdAt: new Date().toISOString(),
            embed: {
                "$type": "app.bsky.embed.images",
                "images": [{
                    "alt": "image",
                    "image": response.data.blob,
                }],
            },
        }
        await this._agent.post(postRecord);
    }
}
```


## 参考サイト
このサイトさえあれば、大丈夫。魚拓取っとておく級。
https://qiita.com/eXpresser/items/fce2066f442bd37c8a36