0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GAS + Firestore + Discord WebhookでSNS運用を半自動化した話

0
Posted at

はじめに

「エンジニアなのにSNS運用は手動でやっている」──心当たりのある方は多いのではないでしょうか。

毎日Xを開いて、ネタを考えて、下書きして、投稿する。1回あたり20〜30分、週5回投稿するなら月に10時間近くの工数です。エンジニアの時間としては決して安くありません。

そこで、SNS運用の「半自動化」システムを作りました。完全自動化ではなく、あえて人間の承認ステップを残した設計にしています。この記事ではアーキテクチャと実装のポイントをまとめます。

環境

  • Google Apps Script(GAS)
  • Cloud Firestore(Firebase)
  • Discord Webhook / Bot API
  • Node.js 24.x(ローカルスクリプト用)
  • X API v2(Free プラン)

アーキテクチャ全体像

[Notion DB] → 同期スクリプト → [Firestore: raw]
                                     ↓
                              AI生成 → [Firestore: posts]
                                     ↓
                           Discord #approval に通知
                                     ↓
                           ユーザー承認(日時リプライ)
                                     ↓
                           GAS Cron(10分毎)→ X投稿

日常のメモや気づきをNotionに書き溜め、それを定期的にFirestoreに同期。AIで投稿案を生成し、Discordの承認フローを経て予約投稿する流れです。

ポイント1:なぜ「半自動化」なのか

最初は完全自動化を考えましたが、2つの理由でやめました。

1. 誤投稿リスク
AIが生成した文章をそのまま投稿すると、意図しない表現や事実誤認が混じるリスクがあります。個人ブランドに直結するSNSでこれは致命的です。

2. 「自分の言葉」の担保
AIのリライトがうまくいっても、自分の体験や意見が薄まると読者に響きません。最終チェックで「自分ならこう言い換える」を挟むことで、投稿の質を保てます。

承認フローにDiscordを採用した理由は、スマホからでも1タップで承認できるからです。Slackでも同じ構成は可能ですが、個人利用ならDiscordの方が無料枠が広いです。

ポイント2:GAS Cronのハマりどころ

GASのトリガーでCron的な定期実行を実現していますが、いくつか注意点があります。

// トリガー設定
function setupTriggers() {
  // 既存トリガーを削除(重複防止)
  ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));

  // 10分毎に投稿チェック
  ScriptApp.newTrigger('cronPostScheduledPosts')
    .timeBased()
    .everyMinutes(10)
    .create();

  // 毎日13:00にDiscord通知
  ScriptApp.newTrigger('sendPendingPostsToDiscord')
    .timeBased()
    .atHour(13)
    .everyDays(1)
    .create();
}

ハマりポイント:

  • GASの everyMinutes() は1, 5, 10, 15, 30のいずれかしか指定できない
  • タイムゾーンは appsscript.json で明示しないとUTCになる
  • 1日の実行時間上限(無料:90分)に注意。10分毎×144回=1440回/日だが、1回の実行が数秒なら問題なし

ポイント3:X API Freeプランの制約

// X API v2 での投稿(OAuth 1.0a)
async function postToX(text) {
  const oauth = buildOAuthHeader('POST', endpoint, params);
  const res = UrlFetchApp.fetch(endpoint, {
    method: 'post',
    headers: { Authorization: oauth },
    contentType: 'application/json',
    payload: JSON.stringify({ text }),
  });
  return JSON.parse(res.getContentText());
}

Freeプランの制約:

  • 月間500投稿まで(1日約16本が上限)
  • 予約投稿APIは使えない → 自前でCronを組む必要あり
  • Rate limit: 17リクエスト/15分

予約投稿APIが使えない点が最大の制約です。これを回避するために、Firestoreに scheduled_post_time を持たせ、GASのCronで「予定時刻を過ぎた投稿」を検出して投稿する方式にしました。

ポイント4:Firestoreのステータス管理

投稿のライフサイクルをFirestoreのフィールドで管理しています。

posts/{docId}
├── status: "サニタイズ済" → "投稿予約済" → "投稿済"
├── generated_post: "投稿テキスト..."
├── target: "[経営者]" | "[エンジニア]"
├── scheduled_post_time: Timestamp
├── posted_at: Timestamp
├── x_url: "https://x.com/..."
└── media_published: boolean

ステータス遷移がシンプルなので、Firestoreのクエリ1本でフィルタリングできます。

// 投稿予約済みかつ予定時刻を過ぎたものを取得
const now = new Date();
const snapshot = await firestore
  .collection('posts')
  .where('status', '==', '投稿予約済')
  .where('scheduled_post_time', '<=', now)
  .get();

運用コスト

項目 月額
GAS 無料
Firestore 無料枠内(月5万読取/2万書込)
Discord Bot 無料
X API Free 無料
合計 0円

サーバーレス構成に寄せることで、ランニングコストをゼロに抑えられています。

まとめ

  • SNS運用の半自動化は「完全自動」より「人間の承認を残す」方が品質を保てる
  • GAS + Firestore + Discord の組み合わせで運用コスト0円
  • X API Freeプランの制約(予約投稿API不可)はFirestore + Cronで回避可能
  • 月10時間の手動作業が月1.5時間に削減

エンジニアがSNS運用に時間を取られるのはもったいないです。「仕組みを作る」こと自体がエンジニアリングの練習にもなるので、一石二鳥でおすすめです。


この記事を書いた人

BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。

GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。

👉 BENTEN Web Works — お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?