分割送信サイズを環境変数から設定できるようにし、スクリプト全体をまとめます。
以下に、環境変数 DISCORD_CHUNK_SIZE を使用して分割サイズを設定できるようにした最終的なスクリプト例を示します。
#!/bin/bash
# --- 設定 ---
SESSION_NAME="codex_remote_notify_per_pane"
LOG_DIR="./pane_logs" # ログファイルを保存するディレクトリ
# --- Discord Webhook URL ---
# 環境変数 DISCORD_WEBHOOK_URL から取得。設定されていない場合はデフォルト値を使用。
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-YOUR_DISCORD_WEBHOOK_URL}" # <-- ここを実際のURLに置き換えるか、環境変数で設定してください!
# --- Discord の最大文字数制限 (Embed フィールド値) ---
# 環境変数 DISCORD_CHUNK_SIZE から取得。設定されていない場合はデフォルト値を使用。
DISCORD_MAX_CHARS="${DISCORD_CHUNK_SIZE:-3900}" # 4000文字制限より少し余裕を持たせる
# --- 処理対象の Pane リスト ---
# 各要素: "[WindowIndex, PaneIndex, \"Task Description\", \"Notification Identifier\"]"
# Notification Identifier は、通知のタイトルやフッターに使用します。
TARGET_PANE_CONFIG=(
"[0,0,\"Project A - Task 1\",\"Project A / Task 1\"]"
"[0,1,\"Project A - Task 2\",\"Project A / Task 2\"]"
"[1,0,\"Project B - Task 1\",\"Project B / Task 1\"]"
)
# --- tmux セッションの準備 ---
tmux has-session -t "$SESSION_NAME" 2>/dev/null || tmux new-session -d -s "$SESSION_NAME"
# --- ログディレクトリの作成 ---
mkdir -p "$LOG_DIR"
# --- JSON 文字列エスケープ関数 ---
# jq が利用可能な場合は jq を優先し、それ以外の場合は sed による簡易エスケープを行う
escape_json_string() {
local input="$1"
if command -v jq &> /dev/null; then
# jq を使用して安全にエスケープ
echo "$input" | jq -Rs '.'
else
# jq がない場合、sed による簡易エスケープ
input=$(echo "$input" | sed ':a;N;$!ba;s/
/\
/g') # 改行を
に
input=$(echo "$input" | sed 's/"/\\"/g') # ダブルクォートを \" に
input=$(echo "$input" | sed 's/\\/\\\\/g') # バックスラッシュを \\ に
echo "$input"
fi
}
# --- チャンクに分割して Discord に送信する関数 ---
send_to_discord_chunked() {
local title="$1"
local description="$2"
local output_content="$3"
local pane_identifier="$4" # 例: "Pane 0.0"
# 出力内容をチャンクに分割
local chunks=()
local current_chunk=""
local lines=()
# Bash 4+ で改行で分割
readarray -t lines <<< "$output_content"
for line in "${lines[@]}"; do
# チャンクに追加
if [ -z "$current_chunk" ]; then
current_chunk="$line"
else
# チャンクに追加した結果、長すぎる場合は、現在のチャンクを確定し、新しいチャンクを開始
if [ $(( ${#current_chunk} + ${#line} + 1 )) -gt $DISCORD_MAX_CHARS ]; then
chunks+=("$current_chunk")
current_chunk="$line"
else
current_chunk="$current_chunk"$'
'"$line"
fi
fi
done
# 最後のチャンクを追加
if [ -n "$current_chunk" ]; then
chunks+=("$current_chunk")
fi
# チャンクごとに Discord に送信
local embed_fields_json=""
local field_count=0
for i in "${!chunks[@]}"; do
local escaped_chunk=$(escape_json_string "${chunks[$i]}")
local field_name="Output Chunk $((i+1))/${#chunks[@]}"
local field_value="\`\`\`${escaped_chunk}\`\`\`" # コードブロックで囲む
# JSON の field オブジェクトを作成
local field_json="{\"name\": \"${field_name}\", \"value\": \"${field_value}\", \"inline\": false}"
if [ $field_count -gt 0 ]; then
embed_fields_json+=","
fi
embed_fields_json+="$field_json"
field_count=$((field_count + 1))
done
embed_fields_json="[${embed_fields_json}]" # 配列形式にする
# JSON ペイロードを組み立てる
local escaped_description=$(escape_json_string "$description")
local escaped_title=$(escape_json_string "$title")
local footer_text="Task: ${escaped_title} | ${pane_identifier} | Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
JSON_PAYLOAD=$(cat <<EOF
{
"content": "Remote task completed!",
"embeds": [
{
"title": "${escaped_title}",
"description": "${escaped_description}",
"color": 3447003,
"fields": ${embed_fields_json},
"footer": {
"text": "${footer_text}"
}
}
]
}
EOF
)
# Webhook URL が設定されているか確認
if [ "$DISCORD_WEBHOOK_URL" != "YOUR_DISCORD_WEBHOOK_URL" ] && [ -n "$DISCORD_WEBHOOK_URL" ]; then
echo "Discord に通知を送信します (${pane_identifier}, ${#chunks[@]} chunks)..."
curl -s -H "Content-Type: application/json" -X POST -d "$JSON_PAYLOAD" "$DISCORD_WEBHOOK_URL"
echo "Discord への通知が完了しました。"
else
echo "Discord Webhook URL が設定されていません。通知をスキップします。"
fi
}
# --- 各 Pane の処理 ---
for config_str in "${TARGET_PANE_CONFIG[@]}"; do
eval "pane_info=(${config_str})"
WINDOW_INDEX=${pane_info[0]}
PANE_INDEX=${pane_info[1]}
TASK_DESCRIPTION=${pane_info[2]} # 通知のタイトルや内容に使用
PANE_IDENTIFIER="${pane_info[3]}" # 通知フッターや識別子に使用
TIMESTAMP=$(date +%s%N)
LOG_FILE="${LOG_DIR}/log_${SESSION_NAME}_W${WINDOW_INDEX}_P${PANE_INDEX}_${TIMESTAMP}.txt"
MARKER_START="---[CMD_START_${SESSION_NAME}_W${WINDOW_INDEX}_P${PANE_INDEX}_${TIMESTAMP}]---"
MARKER_END="---[CMD_END_${SESSION_NAME}_W${WINDOW_INDEX}_P${PANE_INDEX}_${TIMESTAMP}]---"
echo "Pane ${WINDOW_INDEX}.${PANE_INDEX} (Task: ${TASK_DESCRIPTION}) の処理を開始します。"
echo "ログファイル: ${LOG_FILE}"
# 対象ペインに移動
tmux select-window -t "$SESSION_NAME:$WINDOW_INDEX"
tmux select-pane -t "$SESSION_NAME:$WINDOW_INDEX.$PANE_INDEX"
# pipe-pane の設定と codex コマンド送信
# codex コマンド実行前に pipe-pane を設定し、その出力をログファイルに追記
tmux pipe-pane -o "cat >> $LOG_FILE" \; \
send-keys -t "$SESSION_NAME:$WINDOW_INDEX.$PANE_INDEX" "codex" C-m
sleep 1 # codex 起動待機
# codex への入力を送信
tmux send-keys -t "$SESSION_NAME:$WINDOW_INDEX.$PANE_INDEX" "$TASK_DESCRIPTION" C-m
sleep 10 # codex の実行完了を待つ (実行時間に合わせて調整)
# コマンド実行後に終了マーカーを挿入
tmux send-keys -t "$SESSION_NAME:$WINDOW_INDEX.$PANE_INDEX" "$MARKER_END" C-m
echo "Pane ${WINDOW_INDEX}.${PANE_INDEX} のコマンド実行完了。"
# --- 直前の出力ログの抽出 ---
OUTPUT_TO_NOTIFY=$(sed -n "/${MARKER_START}/,/${MARKER_END}/p" "$LOG_FILE" | sed "1d;\$d")
if [ -n "$OUTPUT_TO_NOTIFY" ]; then
echo "--- 通知対象の出力 (Pane ${WINDOW_INDEX}.${PANE_INDEX}) ---"
# echo "$OUTPUT_TO_NOTIFY" # デバッグ用
echo "--------------------------------------------------"
# --- Discord への通知処理 (チャンク分割) ---
send_to_discord_chunked "${TASK_DESCRIPTION}" "Output from ${PANE_IDENTIFIER}" "$OUTPUT_TO_NOTIFY" "${PANE_IDENTIFIER}"
else
echo "Pane ${WINDOW_INDEX}.${PANE_INDEX} のマーカー間の出力が見つかりませんでした。"
fi
echo "" # Pane 間の区切り
done # for loop ends
# --- セッションのデタッチ (必要なら) ---
# tmux detach -t "$SESSION_NAME"
echo "すべての Pane の処理が完了しました。ログは ${LOG_DIR} に保存されています。"
exit 0
主な変更点とまとめ:
- 環境変数による分割サイズ設定:
DISCORD_MAX_CHARS="${DISCORD_CHUNK_SIZE:-3900}"- スクリプト実行前に
export DISCORD_CHUNK_SIZE=3000のように設定することで、分割サイズを変更できます。デフォルトは 3900 文字です。
- JSON エスケープの強化 (jq 推奨):
escape_json_string()関数内でjqコマンドが利用可能であれば、そちらを優先するようにしました。jqがなければsedによる簡易エスケープになります。jqを使用するには、sudo apt-get install jq(Debian/Ubuntu) またはbrew install jq(macOS) などでインストールが必要です。
send_to_discord_chunked()関数の改善:
lines=()とreadarray -t lines <<< "$output_content"を使用して、Bash 4+ で複数行の文字列を正しく配列に分割できるようにしました。- JSON Payload の組み立て部分で、
embed_fields_jsonをループ内で動的に構築し、最後に[と]で囲んで JSON 配列形式にしています。これにより、jqを使わない場合でも、より安全に JSON のfields部分を生成できます。
TARGET_PANE_CONFIGの変更:
- 4番目の要素を
Notification Identifierとし、通知のフッターや識別子として使用するようにしました。これにより、どのタスク/Pane からの通知か分かりやすくなります。
実行方法:
- スクリプトを
run_codex_discord.shのような名前で保存します。 chmod +x run_codex_discord.shで実行権限を付与します。- Discord Webhook URL を設定します:
- スクリプト内の
DISCORD_WEBHOOK_URL="YOUR_DISCORD_WEBHOOK_URL"を、あなたの Discord Webhook URL に置き換える。 - または、スクリプト実行前に環境変数で設定する:
export DISCORD_WEBHOOK_URL="あなたのWebhookURL"
- 分割サイズを設定します (任意):
- デフォルトで 3900 文字になります。変更したい場合は、環境変数で設定します:
export DISCORD_CHUNK_SIZE=2000
- スクリプトを実行します:
./run_codex_discord.sh
これで、codexの実行結果が tmux ペインから抽出され、4000 文字制限を超えても分割されて Discord の Embed フィールドにコードブロック形式で送信されるはずです。