Slack に自分の
アカウント名義で定時投稿する
(postMessage APIとUser Token)

関連記事

1. SlackのBot Token と User Token

slackでAIやターミナルコマンドから操作して定時の定型文を送るようにしたいと考えました。
ただし、API連携botという別のアカウントでの投稿ではなく、自分のアカウントからの投稿を自動化したいです。
というのも、定時で日報のテンプレートを投稿して、あとから情報を付け足すような運用をしているからです。

なぜ Bot Token ではダメか Bot Token xoxb- で始まる 編集できない User Token xoxp- で始まる 自分名義で編集可 投稿後に手作業で追記したいなら User Token 一択

https://slack.com/api/chat.postMessage に認証情報や投稿内容を付けてアクセスすると、Slack内のチャンネルにメッセージに送信することができます。
これを、スクリプトから実行すればよいわけです。

Slack API を使って自動投稿する際、よく紹介されるのは Bot Tokenxoxb- から始まるトークン)。
これで chat.postMessage を呼ぶと、投稿者はボットアカウントになります1
ただし、投稿後に ボット名義の投稿は「自分のメッセージ」として扱われないため、通常のユーザー操作では後から編集できません。

自分のアカウント名義で代わりに投稿させたいなら、User Tokenxoxp-)を使います。
User Token で投稿すると、Slack 上の表示名・アイコンがトークン所有者のものになり、後から手作業で編集できます2

2. Slackでのセットアップ手順

セットアップ手順 STEP 1 Slack App を作成 api.slack.com/apps → Create New App → From scratch STEP 2 User Token Scope を追加 OAuth & Permissions → User Token Scopes → chat:write STEP 3 インストールしてトークン取得 Install to Workspace → xoxp-… をコピーして保管

2.1. Slack App を作成する

api.slack.com/apps にアクセスして「Create New App」を選びます。
「From scratch」を選び、App 名と対象ワークスペースを指定します。

APIキーを作成する
APIキーを作成する

2.2. User Token Scope を追加する

左メニューの「OAuth & Permissions」を開き、「User Token Scopes」欄に chat:write を追加します。
Bot Token Scopes ではなく User Token Scopes に追加するのがポイントです3

APIの権限を設定する
APIの権限を設定する

チャンネルへの参加が必要です。
User Token を使った投稿は、トークン所有者(あなた)が参加していないチャンネルには送れません。
投稿先がプライベートチャンネルの場合は特に確認してください。

2.3. ワークスペースに関連付ける

同じページ上部の「Install to Workspace」を実行します。
認証画面が出るので「許可する」を選ぶと、「User OAuth Token」(xoxp- から始まる文字列)が表示されます。
これをコピーして安全な場所に保管してください。

2.3. ワークスペースに関連付ける

メッセージの編集可否はワークスペース設定に依存します。
デフォルトでは任意のメンバーがいつでも自分のメッセージを編集できますが、管理者が「1分以内」「24時間以内」「禁止」といった制限に変更できます4。Slack の管理者設定でメッセージ編集の権限を確認してください。

3. 投稿スクリプトを作成する

たとえば、以下のシェルスクリプトを post_daily_report.sh として保存します。

投稿スクリプト post_daily_report.sh SLACK_USER_TOKEN= “xoxp-…” CHANNEL_ID= “C0123456789” curl -sS \ https://slack.com/api/ chat.postMessage -H “Authorization: Bearer \ $SLACK_USER_TOKEN” –data ‘{“channel”:…, “text”: TEMPLATE}’ # jqでJSONエスケープを安全に処理 $ chmod +x ./post_daily_report.sh User Token で 投稿者 = あなた名義 jq で JSONエスケープ を安全処理 chmod +x で 実行権限を 付与する
#!/bin/zsh

SLACK_USER_TOKEN="xoxp-xxxxxxxx-xxxxxxxx-xxxxxxxxxxxx"
CHANNEL_ID="C0123456789"

TEMPLATE="$(LC_TIME=ja_JP.UTF-8 date '+%Y-%m-%d(%a)'
・今日やったこと:
・明日やること:
・詰まっていること:
(後で追記)"



# Slack送信処理
curl -sS https://slack.com/api/chat.postMessage \
  -H "Authorization: Bearer $SLACK_USER_TOKEN" \
  -H "Content-Type: application/json; charset=utf-8" \
  --data "{
    \"channel\": \"$CHANNEL_ID\",
    \"text\": \"$TEMPLATE\"
  }"
Code language: PHP (php)

SLACK_USER_TOKENCHANNEL_ID を自分の値に書き換えてください。
チャンネル ID は Slack でチャンネルを右クリックし「チャンネル詳細を表示」から確認できます5

トークンは環境変数で管理してください。
ただし、スクリプト内にトークンを直書きするのはリスクがあるため、.env ファイルや OS のキーチェーン、あるいは GitHub Actions Secret などに分離する運用をおすすめします6

chat.postMessage には1チャンネルあたり毎秒1件というレートリミットがあります7。日報の定時投稿1件であれば問題ありません。

.local/bin/にスクリプトに保存し、実行権限を付けました。

chmod +x ~/.local/bin/post-slack-daily-report
Code language: JavaScript (javascript)

3. 投稿スクリプトを作成する

3.1. LaunchAgentsに登録しておく

毎朝、自動実行するために、「ログイン項目」に登録しておくことにしました。

たとえば、「com.chiilabo.post-daily-report.plist」というファイル名で、~/Library/LauchAgentsに追加します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.chiilabo.post-slack-daily-report</string>

  <key>ProgramArguments</key>
  <array>
    <string>/bin/zsh</string>
    <string>/Users/username/.local/bin/post-slack-daily-report</string>
  </array>

  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key><integer>9</integer>
    <key>Minute</key><integer>0</integer>
  </dict>

  <key>RunAtLoad</key>
  <true/>

  <key>StandardOutPath</key>
  <string>/tmp/post-slack-daily-report.out</string>
  <key>StandardErrorPath</key>
  <string>/tmp/post-slack-daily-report.err</string>
</dict>
</plist>Code language: HTML, XML (xml)

macOS 標準のジョブスケジューラで、StartCalendarInterval で時刻指定しつつ RunAtLoad と組み合わせると、9時のタイミングと起動後のタイミングにスクリプトを実行するようにしました。

launchd は .plist ファイルでそのスクリプトを呼びます。
再起動すると、ログイン時に実行されます。

すぐに、ログイン項目を追加するには、launchctl bootstrapコマンドもあります。

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.chiilabo.post-slack-daily-report.plistCode language: JavaScript (javascript)
ログイン項目に追加された通知
ログイン項目に追加された通知

ただし、このままでは一日にパソコンを何度も起動後するとそのたびに実行されてしまいます。
そこで、スクリプトの方には、以下のように実行制限を付けました。

# 土日は送らない
weekday=$(date +%u)  # 1=月 ... 7=日
if (( weekday > 5 )); then
  exit 0
fi

# 9時前なら送らない
hour=$(date +%H)        # "00"〜"23"
if (( 10#$hour < 9 )); then
  exit 0
fi

# 当日送信済みなら送らない
today=$(date +%Y-%m-%d)
flag="$HOME/Library/Caches/slack-daily-report-$today.sent"
if [[ -f "$flag" ]]; then
  exit 0
fi

# ここに Slack 送信処理(curl など)
# 成功したらフラグ作成
touch "$flag"Code language: PHP (php)

4. 【補足】macOSではない場合(cron)

cronを利用する場合は、crontab -e を開いて以下を追記します。

cron で平日のみ定時実行 0 9 * * 1-5 /path/to/post_daily_report.sh 分:0 時:9 日・月:毎日 曜日:月〜金 UTC環境の場合 0 0 * * 1-5 … JST 9:00 = UTC 0:00 TZ確認コマンド timedatectl Linux / date(macOS) 0 と 7 はどちらも日曜 ── 1-5 が月〜金の正しい書き方 構文確認は crontab.guru が便利

この例では平日(月〜金)の 9:00 に実行します8

0 9 * * 1-5 /path/to/post_daily_report.sh

cron はシステムのタイムゾーンで動くため、実行マシンが UTC に設定されている場合は JST(UTC+9)にオフセットして時刻を指定します。JST の 9:00 は UTC の 0:00 なので、その場合は以下になります。

0 0 * * 1-5 /path/to/post_daily_report.sh

タイムゾーンの確認は timedatectl(Linux)または date コマンドで行えます。

ただし、MacBookのように常時稼働していないノートPCの場合には、注意が必要です。

cron はスリープ中や電源オフ中のジョブをスキップします。
つまり、たとえ9:00 に設定していてもその時間にMacBookが閉じていれば、その日のジョブは単純に失われます。
起動後に自動で実行されるわけではないです。

起動後に「スキップ分を遅れて実行する」仕組みは、anacron というツールが担います。
ただし macOS の標準 cron には anacron は付属していません。

4.1. 【補足】Slack の chat.scheduleMessage を使う

あるいは、Slack 側にあらかじめ予約投稿を積んでおく方法もあります。
一度 API を叩ければあとは Slack サーバー側で処理されるため、MacBook がオフでも投稿されます。
post_at に UNIX タイムスタンプを渡すので、投稿予定日の日付を計算して埋め込みます。

# 翌平日9:00のUNIXタイムスタンプを計算して予約
TARGET_DATE=$(date -v+1d +%Y/%m/%d)   # macOS
# TARGET_DATE=$(date -d tomorrow +%Y/%m/%d)  # Linux

POST_AT=$(date -v+1d -v9H -v0M -v0S +%s)  # macOS
# POST_AT=$(date -d "tomorrow 09:00" +%s)   # Linux

TEXT="【日報】${TARGET_DATE}\n・今日やったこと:\n・明日やること:"

curl -sS https://slack.com/api/chat.scheduleMessage \
  -H "Authorization: Bearer $SLACK_USER_TOKEN" \
  -H "Content-Type: application/json" \
  --data "$(jq -n \
    --arg c "C0123456789" \
    --arg t "$TEXT" \
    --argjson p "$POST_AT" \
    '{channel:$c, text:$t, post_at:$p}')"Code language: PHP (php)

ただし毎日繰り返すには定期的に予約を追加し続ける仕組みは、必要です。

4.2. クラウドで動かす (GitHub Actions)

GitHub Actions の schedule トリガーや、常時稼働のVPS・Raspberry Pi に cron を置く方法です。
MacBook の状態に依存しないため確実です。

日報の定時投稿が目的なら、GitHub Actions の schedule が設定も簡単で一番手軽だと思います。

  1. Bot Token はユーザーの identity には紐付かず、アプリそのものに紐付きます。インストールしたユーザーが無効化されてもアプリは動き続けるため、長期的な自動化向けの設計ですが、「自分の名義で投稿したい」用途には向きません。 – Tokens | Slack Developer Docs
  2. User Token はトークン所有者がワークスペースで持つのと同等のアクセス権を持ちます。書き込み操作はユーザー本人が操作したものとして扱われます。 – Tokens | Slack Developer Docs
  3. 現行の Slack Apps では chat:write が旧来の chat:write:user(ユーザー名義投稿)と chat:write:bot(ボット名義投稿)を統合した後継スコープです。以前のスコープ名で検索すると古い情報がヒットすることがあるので注意してください。 – chat:write permission scope | Slack
  4. 編集権限の設定はワークスペースの「設定と管理」→「ワークスペースの設定」→「権限」タブの「メッセージの編集と削除」から変更できます。Org レベルでポリシーが設定されている場合はそちらが優先されます。 – Manage permissions for message editing and deletion | Slack
  5. チャンネル ID の取得方法はもう一つあります。ブラウザで Slack を開いたときの URL(https://app.slack.com/client/TXXXXXXX/CXXXXXXX)の末尾部分が C から始まるチャンネル ID です。また、デスクトップアプリでチャンネルを右クリック →「リンクをコピー」で取得できるリンク(https://WORKSPACE.slack.com/archives/CXXXXXXX)の archives/ 以降もチャンネル ID になっています。 – How to find Slack member, channel, and user group IDs – Zapier
  6. User Token はデフォルトでは有効期限がなく、漏えいすると恒久的に悪用されるリスクがあります。Slack のアプリ設定でトークンローテーションを有効にすると、アクセストークンが12時間ごとに失効・更新される運用にできます。 – Security best practices | Slack Developer Docs
  7. chat.postMessage のレートリミットは1チャンネルあたりおよそ毎秒1件で、ワークスペース全体では1分あたり数百件が上限とされています。日報の定時投稿1件であれば問題はありません。 – chat.postMessage method | Slack Developer Docs
  8. cron の曜日フィールドで 1-5 は月曜(1)から金曜(5)を表します。07 はどちらも日曜を示すため、0-6 と書くと月曜〜土曜ではなく全曜日になる点に注意してください。cron の構文確認には crontab.guru が便利です。 – Cron job format and time zone | Cloud Scheduler | Google Cloud Documentation