🚀

Blueskyで画像投稿するbotをサクッと作る

2024/02/12に公開

Blueskyに招待されなくてもアカウントが作れるようになったことを機にアカウントを作ったのですが、
今ひとつXとの棲み分けがわからず、何を書いて良いかわからなかったので、
好きなアイドルグループ(日向坂46)のメンバーの画像を定期的に投稿するbotを作って遊んでみました。

以下の2つさえあればできるので、ご興味があればやってみて下さい。
※自身の推しの画像を投稿するもよし、業務の中でbotを作って自動化したい業務(リマインダーなど)に応用するもよしといった感じかと思います。

  • Googleアカウント(Google Apps Scriptを用いました)
  • Blueskyのアカウント

処理の流れ

全体の処理の流れとしては、以下のようになります。

// botPostBlueSky関数を定期実行し、BlueSkyに定期的に日向坂46メンバーの画像をポストする
function botPostBlueSky() {
  try {
    // 投稿するメンバーを無作為に決定
    var targetMember = decideTargetMember();
    // 投稿するメンバーの画像を取得
    var imageUrl = getImageUrl(targetMember);
    // ポストに添える文を作成
    var text = targetMember + "さん(日向坂46)の可愛い画像です!";
    // ポストする
    postContentAndImage(imageUrl, text);
  } catch (error) {
    console.error("エラーが発生しました: " + error);
  }
}

以下の処理について、順番に解説します。

  • 投稿するメンバーを無作為に決定
  • 投稿するメンバーの画像を取得
  • ポストする

投稿するメンバーを無作為に決定

やっていることは至ってシンプルで、以下の2ステップのみです。

  1. プログラム作成当時の日向坂46のメンバー全員が格納された配列を作成
  2. 配列からランダムにメンバーを選んで返す

※randomIndex(無作為に決められた配列のインデックス番号を格納する変数)のロジックとしては、
以下のようになります。

  1. 乱数(0〜1未満の小数)をMath.random()で生成
  2. メンバー全員を格納した配列の要素数を上記に掛け算
  3. 上記でできた数からMath.floor()で小数点以下を切り捨て(正の数を扱うので切り捨てです。負の数を扱う場合は切り上げとなります。)
// 画像投稿対象のメンバーをランダムに決める関数
function decideTargetMember() {
  var hinatazakaMembers = 
  ["加藤史帆", "齊藤京子", "佐々木久美", "佐々木美玲", "高瀬愛奈", "高本彩花", "東村芽依", 
  "金村美玖", "河田陽菜", "小坂菜緒", "富田鈴花", "丹生明里", "濱岸ひより", "松田好花", 
  "上村ひなの", "髙橋未来虹", "森本茉莉", "山口陽世", 
  "石塚瑶季", "小西夏菜実", "清水理央", "正源寺陽子", "竹内希来里", "平尾帆夏", "平岡海月", "藤嶌果歩", "宮地すみれ", "山下葉留花", "渡辺莉奈"];
  var randomIndex = Math.floor(Math.random() * hinatazakaMembers.length);
  return hinatazakaMembers[randomIndex];
}

投稿するメンバーの画像を取得

やっていることとしては、以下のような流れになります。

  1. Googleの画像検索で「メンバー名 可愛い 日向坂46」と検索
  2. 画像検索した画面のhtmlを取得
  3. 上記から、正規表現で画像のURLを取得(上位表示された10件の中からランダムに取得。)

画像検索に関しては、以下のプログラムの変数searchUrlのURLに適当にメンバー名をあてはめて検索すると実行できるので、イメージが湧くかと思います。

// 投稿対象の画像のURLを取得する関数
function getImageUrl(memberName) {
  var searchUrl = "https://www.google.com/search?q=" + memberName + "+可愛い+日向坂46&tbm=isch";
  var response = UrlFetchApp.fetch(searchUrl);
  var content = response.getContentText();
  
  var imageUrlRegex = /<img[^>]*src="(https:\/\/[^">]+)"/g;
  var imageUrlMatches = content.match(imageUrlRegex);
  
  if (imageUrlMatches) {
    var randomIndex = Math.floor(Math.random() * Math.min(imageUrlMatches.length, 10)); // トップ10件の画像からランダムに選択
    var imageUrl = imageUrlMatches[randomIndex].match(/src="([^"]+)"/)[1];
    return imageUrl;
  } else {
    throw new Error("画像が見つかりませんでした。");
  }
}

ポストする

ポストする際には、以下のような段階を踏む必要があります。

  1. Blueskyにログイン
  2. 投稿したい画像のアップロード
  3. ポストする際にBlueskyに投げるリクエストに必要な情報の作成
  4. 実際にポストする

コードに起こすと、以下のようになります。

// 画像と文章をポストする関数
function postContentAndImage(imageUrl, text) {
  try {
    // ログイン並びにアップロード
    var image = createImageForPost(imageUrl);
    // ポストする際にBlueskyに投げるリクエストに必要な情報の作成
    var record = createRecord(text, image.cid, image.mimeType);
    // 実際にポスト
    var response = post(record);
    console.log("ポストに成功しました。");
    console.log(response);
  } catch (error) {
    console.error("ポスト中にエラーが発生しました: " + error);
  }
}

順番に説明します。

Blueskyにログイン

やることはシンプルで、認証を行うためのリクエストを行い、
レスポンスを取得するのみです。

// BlueSkyにログインするための関数
function logInBlueSky() {
  var url = 'https://bsky.social/xrpc/com.atproto.server.createSession';
  var data = {
    'identifier': 'あなたのメールアドレス',
    'password': 'あなたのパスワード'
  };
  var options = {
    'method': 'post',
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8'
    },
    'payload': JSON.stringify(data)
  };
  var response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

投稿したい画像のアップロード

画像をポストに添付するにあたり、画像を事前にアップロードする必要があります。
なぜなら、実際にポストする段階で、画像は文字列情報として渡さなくてはならず、
その文字列情報は事前にアップロードしないと得ることはできないためです。
※以下のcreateImageForPost()が返している、cidとmimeTypeを得る必要があります。

// BlueSkyに画像をアップロードするための関数
function uploadImage(imageUrl) {
  var logInData = logInBlueSky();
  var accessJwt = logInData.accessJwt;
  var blob = UrlFetchApp.fetch(imageUrl).getBlob();
  var options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + accessJwt
    },
    'payload': blob
  };
  var url = 'https://bsky.social/xrpc/com.atproto.repo.uploadBlob';
  var response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

// 画像をアップロードし、ポストするのに必要な形に加工するための関数
function createImageForPost(imageUrl) {
  var imageData = uploadImage(imageUrl);
  var cid = imageData.blob.ref.$link;
  var mimeType = imageData.blob.mimeType;
  return {
    'cid': cid,
    'mimeType': mimeType
  };
}

ポストする際にBlueskyに投げるリクエストに必要な情報の作成

最終的に投稿する際に投稿したい情報を集約したjsonをPOSTリクエストとしてBluesky側に投げる必要が生じるので、
それを整形するための処理です。
※4枚までであれば、画像を複数枚添付することも可能です。
複数枚添付したい場合は、配列imagesに複数の画像の情報を渡す必要があります。

// ポストに必要なレコードの作成
function createRecord(text, cid, mimeType) {
  return {
    'text': text,
    'createdAt': (new Date()).toISOString(),
    'embed': {
      '$type': 'app.bsky.embed.images',
      'images': [
        {
          'image': {
            'cid': cid,
            'mimeType': mimeType
          },
          'alt': ''
        }
      ]
    }
  };
}

実際にポストする

これまで作成してきた、投稿のための情報を用いて投稿を行います。

// BlueSkyにポストする。
function post(record) {
  var logInData = logInBlueSky();
  var accessJwt = logInData.accessJwt;
  var did = logInData.did;
  var url = 'https://bsky.social/xrpc/com.atproto.repo.createRecord';
  var data = {
    'repo': did,
    'collection': 'app.bsky.feed.post',
    'record': record
  };
  var options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + accessJwt,
      'Content-Type': 'application/json; charset=UTF-8'
    },
    'payload': JSON.stringify(data)
  };
  var response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

実際に行われた投稿の例

以下のように、メンバー名を説明した文章と、そのメンバーの画像が1枚添えられた投稿が作成されます。
https://bsky.app/profile/ohimusdev46301.bsky.social/post/3kl7j2272l22u

定期実行するには

Google Apps Scriptでトリガーを設定することで可能です。
「gas トリガー」で検索すると、以下のような記事が出てきます。
参考にやってみて下さい。
https://ex-ture.com/blog/2022/12/23/google-app-scriptを特定のタイミングで自動で動かしたい/

また、初回実行時には、以下の記事に説明されているように、
承認作業を行わないと処理が実行されませんのでご注意ください。
https://qiita.com/STSHISHO/items/f2ce468ab78682c26699

他の参考文献

私が調べた中では、この2つの記事が特にわかりやすかったです。
リンクを共有する方法などについても書かれているので、一読されることをお勧めします。
(参考にさせていただき、ありがとうございました。)
https://nigauri.me/tech/bluesky/post-to-bluesky-using-gas/
https://note.com/keiga/n/n527865bcf0d5

やってみた感想

Google Apps Scriptは初めて用いましたが、
使用感についてはjavascriptに近い感じがしました。

サクッと簡単な自動化を行うのには役立ちそうなので、
業務の中で使うのに適した機会があれば使おうと思います。

Discussion