logo
コーディング

投稿日

Slackのつぶやきを自動でNotion日記にするツールを作った話

実は15年くらい日記を書いています。入院して意識がなかった2日を除いて毎日。手書きではなくデジタル日記なんですが、かつての神アプリが使えなくなってからいろいろ面倒になってたんですよね。そこで、その神アプリのようなものを自分で作ればいいじゃないか!と奮闘した記録を残しておきます。

昔は PostEver + Evernote を使っていた

PostEverは、かつて存在したiOSアプリ。iPhoneから書いた文章をEvernoteに投稿して、1日分の送信を1つのノートにまとめてくれます。その瞬間に感じたことを残すのに最適。そう、日記にピッタリのアプリなんです。ただ、PostEverの開発が終了してから、私もノートアプリをEvernoteからNotionに移行。それからは類似のアプリがないため、Notionに手動で毎日新規ノートを作成して、せっせと日記を書くようにしていました。

でもNotionだと目的の日記ページにいくまでに数タップ必要なので地味に面倒。それでもなんとか日記は続けていたのですが、寝る前に一日にあったことを思い出しながらまとめて書くのも面倒。PostEverお願いかむばっく!!!そう祈りながら日々過ごしていたのでした。

そんな悩みを解決するために、「ないなら作ればいいじゃない!」の精神で、Slackに投稿した内容を毎日決まった時間に自動でNotionのデータベースにまとめてくれる仕組みを作ってみました。アプリとして一般公開はしていないので、完全に自分用ですね。誰かのお役に立てるかもなので、具体的な手順を紹介します。

完成形イメージ

毎晩22時になると、その日のSlack投稿(前日22時〜当日21時59分まで)が、Notionのデータベースに1ページとして自動で作成されます。Slackに投稿した時間も都度テキストの最後に加えます。

とある日の日記の一部抜粋 ↓

朝ごはん食べて読書した。今日はいつもより早いペース。今日はプール行く!デザイン案確定する! (06:59)
セールで、Whittardの紅茶がかなり安くなってたから、他の文房具といっしょに買っちゃったー! (07:50)
Dir en grey のチケット分配してもらった!整番悪くない! (08:53)

仕組み

この自動化は以下の3つの要素で作っています。

  1. Slack: 日々のつぶやきを投稿する場所。
  2. Pythonスクリプト: Mac上で動作し、Slackからメッセージを取得してNotionに送信するプログラム。
  3. Notion: 日記が保存されるデータベース。

全体の流れはシンプルです。

  1. Macのlaunchdという機能が、毎日22時にPythonスクリプトを起動
  2. スクリプトがSlack APIを呼び出し、指定したチャンネルからメッセージを取得
  3. 取得したメッセージを整形し、Notion APIを呼び出して新しいページとしてデータベースに追加

必要なもの

  • エディター(VSCode)
  • Python 3
  • Slackアカウント
  • Notionアカウント

今どきはCursorでコーディングするのが主流かしら?と思いますが、Geminiに課金していて使い慣れているのもあって、VSCodeにGemini Code Assistをインストールして利用しました。Gemini Code Assistを使えばコードの補完や修正、解説、生成なんかもできちゃいます。

それではさっそく作ってみましょう!

Step 1: Notionの準備

まずは日記を保存するNotion側を設定します。

1-1. インテグレーションの作成

Notion APIを利用するための「インテグレーション」を作成します。

  1. Notionインテグレーション作成ページにアクセスします
  2. 「新しいインテグレーション」を作成し、名前(例: Slack Diary Integration)を付けます
  3. 作成後、「シークレット」タブに表示される内部インテグレーションシークレットをコピーして、安全な場所にメモしておきます。これがNotionのAPIキーになります。

1-2. データベースの作成

次に、日記を保存するためのデータベースを作成します。

  1. Notionで新しいページを作成し、「テーブル」を選択してデータベースを作成します。
  2. データベースには、最低でも以下の2つのプロパティを用意します。
    • 日付プロパティ: Date (プロパティの種類: 日付)
    • タイトルプロパティ: Title (プロパティの種類: タイトル)
  3. データベースを開き、右上の「・・・」メニューから「リンクをコピー」を選択します。コピーしたURLから、https://www.notion.so/ の後、?v= の前にある32文字の英数字がデータベースIDです。これもメモしておきます。

1-3. データベースとインテグレーションを連携

作成したデータベースで、先ほど作成したインテグレーションが使えるように招待します。

  1. データベース右上の「・・・」メニューから「コネクトの追加」を選択します。
  2. 検索窓にインテグレーションの名前(例: Slack Diary Integration)を入力し、表示されたものを選択して連携させます。

Step 2: Slackの準備

次に、メッセージを取得するSlack側を設定します。

2-1. Slackアプリの作成

  1. Slack API: Your Appsにアクセスし、「Create New App」をクリックします。
  2. 「From scratch」を選択し、アプリ名(例: Notion Diary Bot)と、利用するワークスペースを選びます。

2-2. 権限(Scope)の設定

ボットに必要な権限を与えます。

  1. 左側メニューの「OAuth & Permissions」に移動します。
  2. 「Scopes」セクションの「Bot Token Scopes」で、「Add an OAuth Scope」をクリックします。
  3. groups:history(プライベートチャンネル用)または channels:history(パブリックチャンネル用)を追加します。
  4. ページ上部の「Install to Workspace」をクリックして、アプリをワークスペースにインストールします。
  5. インストール後、同じページに表示されるBot User OAuth Tokenxoxb-で始まる文字列)をコピーしてメモしておきます。これがSlackのAPIキーです。

2-3. チャンネルIDの取得とボットの招待

  1. 日記を投稿するSlackチャンネルを開き、チャンネル名を右クリックして「リンクをコピー」します。URLの末尾にあるCから始まる文字列がチャンネルIDです。
  2. そのチャンネルで、/invite @<ボット名> と入力し、作成したボットをチャンネルに招待します。

Step 3: Pythonスクリプトの作成

Mac上で動かすスクリプトを作成します。

3-1. プロジェクトのセットアップ

ターミナルを開き、以下のコマンドを順に実行します。プロジェクトフォルダーの名前は「slack-notion-diary」としています。

# プロジェクト用のフォルダーを作成して移動
mkdir ~/Projects/slack-notion-diary
cd ~/Projects/slack-notion-diary

# Pythonの仮想環境を作成して有効化
python3 -m venv venv source venv/bin/activate

# 必要なライブラリをインストール
pip3 install python-dotenv slack_sdk notion-client

3-2. 秘密情報の設定

APIキーなどの秘密情報を安全に管理するため、.envファイルを作成します。

touch .env

作成した.envファイルをエディタで開き、先ほどメモした各種IDとトークンを以下のように記述します。

NOTION_API_TOKEN=内部インテグレーションシークレット
NOTION_DATABASE_ID=32文字のデータベースID
SLACK_API_TOKEN=xoxb-...
SLACK_CHANNEL_ID=Cから始まるチャンネルID
NOTION_DATE_PROPERTY_NAME=Date
NOTION_TITLE_PROPERTY_NAME=Title 

3-3. .gitignoreの作成

.envファイルが誤ってGitHubなどに公開されないように、Gitの無視リストを作成します。

touch .gitignore 

.gitignoreファイルに以下を記述します。

.env
venv/
*.log 

3-4. メインスクリプトの作成

main.pyというファイルを作成し、以下のコードを貼り付けます。タイトルは空にして、22時以降の寝る前に見返して、自分で今日のタイトルをつける感じにしています。

import os
import logging
import sys
from datetime import datetime, timedelta, timezone
from dotenv import load_dotenv
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from notion_client import Client, APIResponseError

# --- 初期設定 ---
# .envファイルから環境変数を読み込む
load_dotenv()

# ログ設定
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 環境変数から設定を読み込む
NOTION_API_TOKEN = os.getenv("NOTION_API_TOKEN")
NOTION_DATABASE_ID = os.getenv("NOTION_DATABASE_ID")
SLACK_API_TOKEN = os.getenv("SLACK_API_TOKEN")
SLACK_CHANNEL_ID = os.getenv("SLACK_CHANNEL_ID")
NOTION_DATE_PROP_NAME = os.getenv("NOTION_DATE_PROPERTY_NAME", "Date")
NOTION_TITLE_PROP_NAME = os.getenv("NOTION_TITLE_PROPERTY_NAME", "Title")

# タイムゾーン
JST = timezone(timedelta(hours=+9), 'JST')


def get_slack_messages(target_date):
    """Slackから指定期間のメッセージを取得する"""
    client = WebClient(token=SLACK_API_TOKEN, timeout=30) # タイムアウトを30秒に設定
    messages = []

    try:
        # 取得期間を計算 (指定日の前日22:00:00 JST から 指定日の21:59:59 JST)
        oldest_dt = (target_date - timedelta(days=1)).replace(hour=22, minute=0, second=0, microsecond=0)
        latest_dt = target_date.replace(hour=21, minute=59, second=59, microsecond=0)

        # Unixタイムスタンプに変換
        oldest_ts = oldest_dt.timestamp()
        latest_ts = latest_dt.timestamp()

        logging.info(f"Slackメッセージを取得します。期間: {oldest_dt} ~ {latest_dt}")

        # APIを呼び出してメッセージ履歴を取得
        result = client.conversations_history(
            channel=SLACK_CHANNEL_ID,
            latest=str(latest_ts),
            oldest=str(oldest_ts),
            inclusive=True
        )

        # ユーザーが投稿した通常のメッセージのみをフィルタリング(システム通知などを除外)
        user_messages = [msg for msg in result.get("messages", []) if "subtype" not in msg]

        # 投稿が古い順に並び替え
        messages = sorted(user_messages, key=lambda x: x["ts"])
        logging.info(f"{len(messages)}件のメッセージが見つかりました。")

    except SlackApiError as e:
        logging.error(f"Slack APIエラー: {e.response['error']}")
    except Exception as e:
        logging.error(f"予期せぬエラーが発生しました: {e}")

    return messages


def create_notion_page(messages, target_date):
    """取得したメッセージをNotionに投稿する"""
    if not messages:
        logging.info("投稿するメッセージがないため、処理を終了します。")
        return

    notion = Client(auth=NOTION_API_TOKEN, timeout_ms=30000) # タイムアウトを30秒に設定

    # Notionページのコンテンツ(ブロック)を作成
    children_blocks = []
    for msg in messages:
        post_time = datetime.fromtimestamp(float(msg['ts']), JST).strftime('%H:%M')
        content = f"{msg['text']} ({post_time})"
        children_blocks.append({
            "object": "block",
            "type": "paragraph",
            "paragraph": {
                "rich_text": [{"type": "text", "text": {"content": content}}]
            }
        })

    # 日記の日付を決定
    page_date = target_date.strftime('%Y-%m-%d')

    try:
        logging.info(f"Notionに {page_date} のページを作成します。")
        notion.pages.create(
            parent={"database_id": NOTION_DATABASE_ID},
            properties={
                NOTION_DATE_PROP_NAME: {
                    "date": {"start": page_date}
                },
                NOTION_TITLE_PROP_NAME: {
                    "title": [{"text": {"content": ""}}] # タイトルは空にする
                }
            },
            children=children_blocks
        )
        logging.info("Notionページの作成に成功しました。")

    except APIResponseError as e:
        logging.error(f"Notion APIエラー: {e}")
    except Exception as e:
        logging.error(f"予期せぬエラーが発生しました: {e}")


def get_target_date():
    """実行対象の日付を取得する。引数があればその日付を、なければ現在日時を返す"""
    # コマンドライン引数から日付を取得
    if len(sys.argv) > 1:
        try:
            logging.info(f"指定された日付で処理を実行します: {sys.argv[1]}")
            return datetime.strptime(sys.argv[1], '%Y-%m-%d').replace(tzinfo=JST)
        except ValueError:
            logging.error("日付の形式が正しくありません。YYYY-MM-DD形式で指定してください。")
            sys.exit(1) # エラーで終了
    # 引数がない場合は通常通り、実行時の日付を基準にする
    return datetime.now(JST)

def main():
    """メイン処理"""
    target_date = get_target_date()

    # 1. Slackからメッセージを取得
    slack_messages = get_slack_messages(target_date)

    # 2. Notionにページを作成
    create_notion_page(slack_messages, target_date)


if __name__ == "__main__":
    main()

Step 4: 自動実行の設定 (launchd)

最後に、このスクリプトを毎日自動で実行するように設定します。Macではcronよりも信頼性の高いlaunchdを使うのがおすすめとのこと。

4-1. 設定ファイルの作成

launchdのための.plistファイルを作成します。プロジェクトフォルダ内でcom.Macのユーザー名.slack-notion-diary.plistというファイルを作成し、以下の内容を貼り付けます。

<?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.Macのユーザー名.slack-notion-diary</string>

    <key>ProgramArguments</key>
    <array>
        <string>/Users/Macのユーザー名/Projects/slack-notion-diary/venv/bin/python3</string>
        <string>main.py</string>
    </array>

    <key>WorkingDirectory</key>
    <string>/Users/Macのユーザー名/Projects/slack-notion-diary</string>

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

    <key>StandardOutPath</key>
    <string>/Users/Macのユーザー名/Projects/slack-notion-diary/cron.log</string>

    <key>StandardErrorPath</key>
    <string>/Users/Macのユーザー名/Projects/slack-notion-diary/cron.log</string>
</dict>
</plist>

4-2. タスクの登録

作成した設定ファイルをlaunchdに登録します。

# launchd用のフォルダがなければ作成
mkdir -p ~/Library/LaunchAgents

# 設定ファイルを移動
mv com.Macのユーザー名.slack-notion-diary.plist ~/Library/LaunchAgents/

# タスクを登録・有効化
launchctl load ~/Library/LaunchAgents/com.Macのユーザー名.slack-notion-diary.plist

これで全てのセットアップは完了です!Slackのつぶやきを自動でNotionにまとめる仕組みが完成!

iPhoneではショートカット?というのかしら?背面2回タップでSlackを立ち上がるように設定したので、思ったことを記録したいと思った瞬間に残せるようになりました!やったね!

作るの楽しい!

実はこれを作ってみようと思ったきっかけは、初めて参加したPyCon JP 2025です。元々非エンジニアだったという方も多く登壇されており、AI頼みきりだったり、ご自身がク◯アプリを量産したと言う人もいましたが、みんな揃って楽しそうにお話されていたんですよね。それで、エンジニアではない私でもなんかできそう!と思えたんです。

1日1アプリ作成はさすがに時間の都合がつかないので無理そうですが、1週間に1つくらいを目指して手を動かしていきたいなぁ。いや、1ヶ月に1つくらい…が現実的……?

今回はテキストのみでしたが、もう少し工夫すれば画像や動画の投稿に対応させることもできそうです。ぜひ上記をベースにお好みにカスタマイズしてみてください!