
Blueskyへの投稿だけが可能なウェブページをGoogle Apps Scriptでつくった
BlueskyはSNSですから、「投稿を見る」と「投稿する」の2つのことを行いますが、ときには「見たくないけど投稿はしたい」という気分の日もありますよね。ミュートなどを駆使していても、そのときあまり読みたくないタイプの投稿がタイムラインに流れてくることはあります。反応なんて意識せずにとにかく連投したいときもあります。思いついたことを放流させる機会はいつでも確保しておきたい。
なので、つくりました。Blueskyに投稿するだけのページを。

これはGoogle Apps Scriptを使ってつくったウェブアプリです。
コードと使い方は後半に貼っておきます。
先に謝辞
わたしが「投稿しかできないBlueskyアプリみたいなの、自分でつくれるものかな」と書いたところへ、「Mastodonだとできますね(大意)」とリプライをくれた結城浩さん。ありがとうございます。おかげで、いっちょやってみるかと思えました。
Google Apps ScriptでBlueskyに投稿する記事をQiitaに書かれていた、Gatsuo De'modoriさん。ほぼすべて参考にしました。ありがとうございます。
素晴らしいBlueskyクライアント「有頂天(Ucho-ten)」。ここに「投稿するだけ」モードがあればいいのになと思ったのがきっかけです。
そして「こんなことやりたいんだけど」と言っていたら修正不要のコードを書いてくれたChatGPT 4o。すごいね。
コードとつくりかた
以下、Google Apps Scriptをほぼ触ったことのない人でもできるようにという気持ちで、スクショを多用しながら紹介します。
ステップ1: HTMLフォームの作成
Google Apps Scriptプロジェクト内に「index.html」というファイルを作成します。
最初の画面で、サイドバーの「ファイル」のところの十字マークから「ファイルを追加」を選び、HTMLファイルを追加します。



ファイル名は「無題」じゃなくて「index」にしてください。
そしたら、下記のコードを貼ります。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
.submit-button { /* 投稿ボタンをかわいくする部分 */
background-color: #00bfff;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 12px;
}
.icon {
width: 50px; /* アイコンの幅 */
height: 50px; /* アイコンの高さ */
border-radius: 50%; /* 丸くする */
}
</style>
</head>
<body>
<img src="https://hogehoge.jpg" alt="アイコン" class="icon"> <!-- 画像のURLを指定 -->
<form id="postForm">
<textarea id="message" placeholder="最近どう?" rows="4" cols="50" oninput="updateCharCount()"></textarea><br>
<span id="charCount">0</span> / 300 characters<br>
<button type="button" class="submit-button" onclick="postMessage()">投稿</button>
</form>
<script>
function postMessage() {
var message = document.getElementById('message').value.trim();
if (message === '') {
return; // テキストが空の場合、何もしない
}
google.script.run.postToBluesky(message);
document.getElementById('message').value = ''; // テキストエリアをクリア
updateCharCount(); // 文字数カウントをリセット
}
function updateCharCount() {
var message = document.getElementById('message').value;
document.getElementById('charCount').innerText = message.length;
}
document.addEventListener('keydown', function(event) {
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
postMessage(); // Control+Enterでも投稿できる
}
});
</script>
</body>
</html>
こんな感じになります。
真ん中あたりに<img src = 〜〜>ってところがあるのですが、そこは自分のアイコンの画像URLを貼っておいてください。気分が出ます。

ステップ2: コードの作成
「コード.gs」というファイルに移動します。最初に出てきてた画面ね。

そしたら、下記を貼ります。
// スクリプトプロパティからユーザーIDとアプリパスワードを取得します。
const USER_ID = PropertiesService.getScriptProperties().getProperty("USER_ID");
const APP_PASSWORD = PropertiesService.getScriptProperties().getProperty("APP_PASSWORD");
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function postToBluesky(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());
}
}
こんな感じになります。
「実行」の左にあるフロッピーディスクを模したボタンを押して保存しておいてください。

コードの2行目と3行目に「USER_ID」と「APP_PASSWORD」という項目があるんですが、これは別の場所で指定します。
ステップ3: スクリプトプロパティの設定
左端の歯車マークを押して、「プロジェクトの設定」を開きます。

下の方に「スクリプトプロパティ」があるので、そこから「スクリプトプロパティを追加」を選びます。

USER_IDとAPP_PASSWORDを入力してください。APP_PASSWORDはアプリパスワードのことです。

ステップ4: デプロイ
画面右上の「デプロイ」を押して、「新しいデプロイ」を押します。


そしたら左上の小さい歯車を押します(ここの画面なんかわかりにくいんだよな)。

説明は空欄でいいです。ウェブアプリのところは「自分」にしとけばいいです。アクセスできるユーザーも「自分のみ」にしましょう。

「デプロイ」を押すとなにやら出てきますが、承認していきます。

こんな感じになります。

左下のちいさな「詳細」をクリックします。

一番下の「〜(安全ではないページ)に移動」をクリックして、先に進めてください。
こんな感じになります(画面は一部分です)。

下のウェブアプリのURLを開くと、こんな感じのが出てくるはず。

これで完成です。URLやAPP_PASSWORDをほかの人に教えないように気をつけましょう。