✌️
【見落とさないSlack運用】Obsidianと連携して会話ログを毎日保存
Slackの会話ログを毎日自動でObsidianに保存する
Python + GitHub Actions + Private Repoで安全運用
0. TL;DR(要約)
- PythonでSlackログをMarkdown化(スレッド対応・JST表示)。
- GitHub Actionsが JST 00:30 に 前日分(JST 0:00–24:00) を自動収集。
- 収集結果は Privateリポジトリ(Obsidian Vault) に push。
- Obsidianはその Vault を pull して常に最新化。
- 機密情報は GitHub Secrets で安全管理(Slackトークン/チャンネルID/PAT)。
1. 背景とゴール
- 目的:Slackの会話を日次でアーカイブし、Obsidianで検索・参照できるようにする。
- 要件:無料プラン想定、Publicに会話を晒さない(Private保存)、メンテ容易。
-
設計:実行コード用 Public リポ(
slack-to-obsidian)と、保存用 Private リポ(xxx-obsidian-vault)を分離。
2. 全体アーキテクチャ
Slack → (Python) → logs/YYYY-MM-DD.md
│
└─ GitHub Actions(毎日 JST 00:30 実行)
│
└─ Private Repo(Obsidian Vault)
│
└─ Obsidian Git / cron で pull
- GitHub Actions の
cronは UTC指定 → JST 00:30 = UTC 15:30。 - 取得対象は 前日(JST 0:00–24:00) に固定(0:30時点で前日を確定集計)。
3. 前提・準備
3.1 Slack 側
-
Slack Bot Token(Botユーザー+権限付与)
- 例:
channels:history,groups:history,im:history,mpim:history,users:read(運用に合わせ最小権限)。
- 例:
3.2 GitHub リポ構成
-
Public:
slack-to-obsidian(PythonスクリプトとActionsを置く) -
Private:
xxx-obsidian-vault(Obsidian Vaultとして運用。logs/にMD保存)
3.3 GitHub Secrets(Public側=slack-to-obsidian に登録)
| Secret名 | 内容 |
|---|---|
SLACK_BOT_TOKEN |
Base64化した Slack Bot Token |
SLACK_CHANNEL_IDS |
取得対象CHIDのカンマ区切り(例:C01...,C02...) |
ENCODED_PAT_FOR_PUSH |
Base64化した Personal Access Token(classic / repo スコープ) |
PRIVATE_REPO_SLUG |
YourName/xxx-obsidian-vault |
4. ローカル実装(Python)
4.1 依存関係と .env
requirements.txt
slack_sdk==3.27.1
python-dotenv==1.0.1
.env(ローカル検証用の例)
SLACK_BOT_TOKEN=(Base64のSlack Bot Token)
SLACK_CHANNEL_IDS=C01234567,C07654321
4.2 収集スクリプト(前日/JST・スレッド対応・ユーザー名解決)
→ slack-to-obsidian.py は長いため別ファイルで管理(詳細は省略)
5. Slackから会話を取得してくるPythonコード
import os
import base64
from datetime import datetime, timedelta, timezone
from pathlib import Path
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from dotenv import load_dotenv
# タイムゾーン定義
JST = timezone(timedelta(hours=9))
UTC = timezone.utc
load_dotenv()
# SlackチャンネルID一覧
channel_ids_str = os.getenv("SLACK_CHANNEL_IDS")
if not channel_ids_str:
raise ValueError("SLACK_CHANNEL_IDS not found in environment variables")
CHANNEL_IDS = [cid.strip() for cid in channel_ids_str.split(",")]
# 保存ディレクトリ
OUTPUT_DIR = Path("logs")
OUTPUT_DIR.mkdir(exist_ok=True)
# Botトークン読み込み
token_encoded = os.getenv("SLACK_BOT_TOKEN")
if not token_encoded:
raise ValueError("SLACK_BOT_TOKEN not found")
SLACK_TOKEN = base64.b64decode(token_encoded).decode("utf-8")
client = WebClient(token=SLACK_TOKEN)
# 実行日(JST)の前日を対象にする
yesterday_jst_date = datetime.now(JST).date() - timedelta(days=1)
today_jst = datetime(
year=yesterday_jst_date.year,
month=yesterday_jst_date.month,
day=yesterday_jst_date.day,
tzinfo=JST,
)
# today_jst = datetime(2025, 7, 31, tzinfo=JST) # ← 任意に変更
log_date_str = today_jst.strftime("%Y-%m-%d")
# JSTの0:00〜24:00 → UTCタイムスタンプに変換
start_jst = datetime(today_jst.year, today_jst.month, today_jst.day, 0, 0, 0, tzinfo=JST)
end_jst = start_jst + timedelta(days=1)
start_ts = start_jst.astimezone(UTC).timestamp()
end_ts = end_jst.astimezone(UTC).timestamp()
# メッセージをMarkdown形式に変換
def format_message(ts, user, text, indent_level=0):
time_str = datetime.fromtimestamp(float(ts), JST).strftime("%H:%M")
indent = " " * indent_level
return f"{indent}- **{time_str}** [@{user}]: {text.strip()}"
# ユーザーIDからユーザー名を取得
user_cache = {}
def get_user_name(user_id):
if user_id in user_cache:
return user_cache[user_id]
try:
user_info = client.users_info(user=user_id)
name = user_info["user"]["real_name"] or user_info["user"]["name"]
user_cache[user_id] = name
return name
except:
return user_id
# スレッドの返信を取得
def fetch_thread_replies(channel_id, thread_ts):
replies = []
try:
thread_response = client.conversations_replies(
channel=channel_id,
ts=thread_ts,
limit=50
)
for reply in thread_response["messages"][1:]: # 最初は親なので除外
if "subtype" in reply:
continue
user = get_user_name(reply.get("user", "unknown"))
text = reply.get("text", "").replace("\n", " ")
replies.append(format_message(reply["ts"], user, text, indent_level=1))
except:
pass
return replies
# チャンネルごとに取得
md_lines = [f"# Slackログ({log_date_str})\n"]
for channel_id in CHANNEL_IDS:
try:
channel_info = client.conversations_info(channel=channel_id)
channel_name = channel_info["channel"]["name"]
md_lines.append(f"\n## #{channel_name}\n")
result = client.conversations_history(
channel=channel_id,
oldest=start_ts,
latest=end_ts,
inclusive=True,
limit=1000
)
for message in reversed(result["messages"]):
if "subtype" in message:
continue
user = get_user_name(message.get("user", "unknown"))
text = message.get("text", "").replace("\n", " ")
formatted = format_message(message["ts"], user, text)
md_lines.append(formatted)
# スレッドがある場合、返信を取得
if "thread_ts" in message and message["thread_ts"] == message["ts"]:
replies = fetch_thread_replies(channel_id, message["ts"])
md_lines.extend(replies)
except SlackApiError as e:
md_lines.append(f"\n> Error fetching messages for channel {channel_id}: {e}\n")
# 保存
output_file = OUTPUT_DIR / f"{log_date_str}.md"
with open(output_file, "w", encoding="utf-8") as f:
f.write("\n".join(md_lines))
print(f"Saved: {output_file}")
6. GitHub Actions(収集・Private Repoへpush)
.github/workflows/daily-log.yml
name: Daily Slack Log to Obsidian
on:
schedule:
- cron: '30 15 * * *' # JST 00:30(=UTC 15:30)
workflow_dispatch: # 手動実行も可
jobs:
run-script:
runs-on: ubuntu-latest
steps:
- name: Checkout source repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Slack log script
run: python slack-to-obsidian.py
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL_IDS: ${{ secrets.SLACK_CHANNEL_IDS }}
- name: Clone private Obsidian repo (no file writes; PAT kept in shell)
run: |
PAT=$(echo "${{ secrets.ENCODED_PAT_FOR_PUSH }}" | base64 -d)
git clone "https://${PAT}@github.com/${{ secrets.PRIVATE_REPO_SLUG }}.git" private-vault
- name: Copy logs to private repo
run: |
mkdir -p private-vault/logs
cp -r logs/*.md private-vault/logs/ || true
- name: Commit and push logs
run: |
cd private-vault
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add logs/
git commit -m "Add Slack logs for $(date '+%Y-%m-%d')" || echo "No changes to commit"
git push
7. Obsidian設定(同期)
7.1 Vault と Private Repo の紐付け
- Private Repo(例:
xxx-obsidian-vault)をローカルにクローン(~/Documents/obsidian-vaultなど)。 - Obsidian → Open folder as vault でそのフォルダを開く。
7.2 Obsidian Git プラグイン
- Community Plugins → Obsidian Git をインストール・有効化。
- 設定例
- Auto pull on startup:ON(起動時にpull)
- Auto pull interval:任意(例:10〜30分)
- さらに確実にするなら、Macの
cron/launchdでローカルのgit pullを 1:00 などに自動化(任意)。
8. まとめ
- 時刻設計(JST/UTC) と Secrets管理 が肝。
- 実行環境(Public)と保存環境(Private)を分離して漏洩リスクを最小化。
- これで Slack → Markdown → Private Vault → Obsidian が毎日自動で回る。
- 拡張(ページネーション・添付保存・整形)も容易に追加可能。
9. 最後に
私の作業ログをAIにインプットしてもらい書いた記事になります。
もし、分からないところ、躓いたポイントがあったら教えていただけるとありがたいです!
Discussion