Google Apps ScriptでBluesky Developer APIsで投稿してみる

# はじめに
**Microsoft MVP**で著名な[Hiroさん](https://mofumofupower.hatenablog.com/about)が
[Bluesky API](https://www.docs.bsky.app/)で[Power Automateから投稿をする記事](https://mofumofupower.hatenablog.com/entry/bluesky_automate_getstarted)を公開されていらっしゃいました。

https://mofumofupower.hatenablog.com/entry/bluesky_automate_getstarted

上記を参考に、**Google Apps Script**でBlueskyに投稿する方法を書いていこうかと思います。

# Bluesky Developer APIs
簡単にフィードの投稿ができます。
今回はまず、投稿することを実現します。

https://www.docs.bsky.app/

Blueskyへの投稿は
1. Bluesky APIを呼び出してセッションを作成、アクセストークンを取得する
1. (1)で取得したアクセストークンを使って、Blueskyに投稿する

という二段構えの方法です。

`Python`の場合は、専用のライブラリもあるようですので、
さらに投稿は簡単です。

https://www.docs.bsky.app/docs/get-started

# 1. 事前準備
APIを利用するためには、
- Blueskyのアカウント
- **APIを使うためのパスワード**

上記の二点が必要です。

前述の、[Hiroさんの記事](https://mofumofupower.hatenablog.com/entry/bluesky_automate_getstarted)に、明確に解説が書かれているため、この記事では割愛します。

- (再掲)

https://mofumofupower.hatenablog.com/entry/bluesky_automate_getstarted

# 2. Google Apps Script

まずはコード

```javascript:blueskyにシンプルに文字を投稿する
// スクリプトプロパティからユーザーIDとアプリパスワードを取得します。
const USER_ID = PropertiesService.getScriptProperties().getProperty("USER_ID");
const APP_PASSWORD = PropertiesService.getScriptProperties().getProperty("APP_PASSWORD");

/**
 * APIを呼び出してセッションを作成し、フィードに投稿します。
 */
function bluesky_Api() {
  const post_text = "投稿するテキスト";
  const sessionToken = createSession();
  if (sessionToken) {
    postToFeed(sessionToken, post_text);
  }
}

/**
 * セッションを作成するためのAPIを呼び出します。
 * @return {string} アクセスJWTトークン
 */
function createSession() {
  const url = 'https://bsky.social/xrpc/com.atproto.server.createSession';
  const payload = {
    "identifier": USER_ID,
    "password": APP_PASSWORD
  };
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const content = response.getContentText();
    const json = JSON.parse(content);
    Logger.log(json.accessJwt);
    return json.accessJwt;
  } catch (error) {
    Logger.log(error.toString());
    return null;
  }
}

/**
 * フィードに投稿するためのAPIを呼び出します。
 * @param {string} accessJwt アクセスJWTトークン
 * @param {string} post_text 投稿する文字列
 */
function postToFeed(accessJwt, post_text) {
  const url = 'https://bsky.social/xrpc/com.atproto.repo.createRecord';
  const payload = {
    "repo": USER_ID,
    "collection": "app.bsky.feed.post",
    "record": {
      "text": post_text,
      "createdAt": new Date().toISOString()
    }
  };
  const options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + accessJwt
    },
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const content = response.getContentText();
    const json = JSON.parse(content);
    Logger.log(json);
  } catch (error) {
    Logger.log(error.toString());
  }
}
```

## 1. スクリプト プロパティに秘密情報を格納
まずこちらに必要な秘密情報を格納します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/d86d90ef-b267-965c-10e3-f5f012c979dd.png)

取得は下記のコードで担います。
- `USER ID`と`Blueskyのアプリパスワード`

```javascript
const USER_ID = PropertiesService.getScriptProperties().getProperty("USER_ID");
const APP_PASSWORD = PropertiesService.getScriptProperties().getProperty("APP_PASSWORD");
```

## 2. セッションを作成して、アクセストークンを発行する

```javascript
function createSession() {
  const url = 'https://bsky.social/xrpc/com.atproto.server.createSession';
  const payload = {
    "identifier": USER_ID,
    "password": APP_PASSWORD
  };
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const content = response.getContentText();
    const json = JSON.parse(content);
    Logger.log(json.accessJwt);
    return json.accessJwt;
  } catch (error) {
    Logger.log(error.toString());
    return null;
  }
}
```

`https://bsky.social/xrpc/com.atproto.server.createSession`に、HTTP要求を送ることで、
投稿をするための`アクセス トークンが取得できます。

HTTP要求の戻り値の中にある`accessJwt`が、そのトークンに該当します。

## 3. BlueSkyに投稿する

```javascript
function postToFeed(accessJwt, post_text) {
  const url = 'https://bsky.social/xrpc/com.atproto.repo.createRecord';
  const payload = {
    "repo": USER_ID,
    "collection": "app.bsky.feed.post",
    "record": {
      "text": post_text,
      "createdAt": new Date().toISOString()
    }
  };
  const options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + accessJwt
    },
    'contentType': 'application/json',
    'payload': JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const content = response.getContentText();
    const json = JSON.parse(content);
    Logger.log(json);
  } catch (error) {
    Logger.log(error.toString());
  }
}
```

`accessJwt`は、`#2`で取得した値です。
`post_text`は、`Blueskyに投稿したい文字列`を設定します。

投稿に必要なボディは下記の部分です。

```javascript
const payload = {
    "repo": USER_ID, // 文字通りユーザーID
    "collection": "app.bsky.feed.post",  // フィードへの投稿はこちらで固定
    "record": {
      "text": post_text, // 投稿する文字列
      "createdAt": new Date().toISOString() // 投稿日付!?
    }
};
```

`createdAt`というプロパティで、投稿日付をコントロールします。
そもそもできるの？と思っていますが、タイムスタンプで設定の必要があるようです。

幸い現在時刻であれば`new Date().toISOString()`で簡単に取得できるので、
こちらで時間を設定することにしました。

# いざ投稿！
下記の設定で、Google Apps Scriptを起動すると・・・
```javascript
const post_text = "Google Apps ScriptでBluesky API!";
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/8c2da25a-0abe-3d37-89d4-4ef3e20f8c7f.png)

https://staging.bsky.app/profile/demodorigatsuo.bsky.social/post/3kl5dod5seo2i

非常に気軽です。
Google スプレッドシートと合わせれば、すぐにBotも作れてしまいますね。

今回は投稿の方法のみに焦点を当てて、書いてみました。
APIの種類がとんでもなく多いので、**BlueskyのAPIを試すだけで無限に時間が溶けそうです**

https://www.docs.bsky.app/docs/category/http-reference