1
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?

SlackからワンコマンドでGoogleカレンダー招待を作成するBotを作った【AWS Lambda + Python】

1
Posted at

SlackからワンコマンドでGoogleカレンダー招待リンクを作成するBotを作った

はじめに

Slackでミーティングを設定する際、以下のような課題はありませんか?

  • メンバーのメールアドレスを一人ずつコピペするのが面倒
  • Googleカレンダーを開いてゲストを追加するのに時間がかかる
  • チャンネル全員を招待したいのに、一人ずつ追加するのが大変

そこで、Slackのコマンド一つでGoogleカレンダーの招待リンクを自動生成するBotを作成しました。

デモ

# 個別メンション
/calendar @田中 @佐藤 @鈴木

# チャンネル全員
/calendar @channel

実行すると、自分だけに以下のメッセージが表示されます:

✅ カレンダーリンクを作成しました

件名: ミーティング
日時: 2025年12月25日 21:00-22:00
ゲスト:
・田中太郎 <tanaka@example.com>
・佐藤花子 <sato@example.com>
・...

[Googleカレンダーを開く]

ボタンをクリックすると、ゲストが全員追加された状態でGoogleカレンダーが開きます。

作成したもの

主な機能

✅ Slash Commandで簡単実行

  • /calendar @user1 @user2 で個別メンション
  • /calendar @channel でチャンネル全員
    • /calendar @here でチャンネル全員

✅ 自動的にメールアドレスを取得

  • Slackのプロフィールから自動取得
  • 手動でのコピペ不要

✅ プライバシー保護

  • メッセージは実行者だけに表示(ephemeral)
  • 他のメンバーに通知が行かない

✅ スマートな時刻設定

  • 実行時刻の次の正時に自動設定
  • 例:14:34に実行 → 15:00-16:00

✅ 大人数対応

  • @channelで数十人のメンバーにも対応
  • タイムアウトエラーなし(非同期処理)

技術スタック

  • AWS Lambda - サーバーレス実行環境
  • Python 3.14 - 実装言語
  • Slack Bolt for Python - Slack Bot開発フレームワーク
  • Slack API - Slash Command、Users API
  • Google Calendar API - カレンダーリンク生成

アーキテクチャ

Slack → Lambda Function URL → Lambda (Python 3.14)
                                  ↓
                          Slack Bolt Framework
                                  ↓
                    ┌──────────────┴──────────────┐
                    ↓                             ↓
              Slack API                    Google Calendar
           (User情報取得)                    (リンク生成)

プロジェクト構成

ディレクトリ/ファイル構造

slack-calendar-bot/
├── app.py                    # メインのアプリケーションコード
├── requirements.txt          # Python依存パッケージ
├── README.md                 # プロジェクト説明(オプション)
├── .gitignore               # Git除外設定(オプション)
├── package/                  # デプロイ用パッケージディレクトリ
│   ├── app.py               # app.pyのコピー
│   ├── slack_bolt/          # Slack Boltライブラリ
│   ├── slack_sdk/           # Slack SDKライブラリ
│   ├── requests/            # Requestsライブラリ
│   └── ...                  # その他の依存ライブラリ
└── lambda_function.zip      # Lambda用デプロイパッケージ

各ファイルの役割

ファイル 役割
app.py メインコード(Slack Bolt、Lambda Handler)
requirements.txt 依存パッケージ一覧
package/ Lambdaにデプロイする全ファイル
lambda_function.zip Lambda用のZIPファイル

ローカル開発環境のセットアップ

前提条件

  • Python 3.12以上
  • pip(Pythonパッケージマネージャー)
  • AWS CLI(オプション)
  • Git(オプション)

1. プロジェクトディレクトリの作成

# プロジェクトディレクトリを作成
mkdir slack-calendar-bot
cd slack-calendar-bot

2. requirements.txtの作成

cat > requirements.txt << EOF
slack-bolt==1.18.0
requests
EOF

requirements.txtの内容:

slack-bolt==1.18.0
requests

3. app.pyの作成

app.pyを作成し、以下のコードを記述します:

(後述の「コード全体」セクション参照)

4. 依存パッケージのインストール

# packageディレクトリを作成
mkdir package

# 依存パッケージをpackageディレクトリにインストール
pip3 install -r requirements.txt -t ./package

# app.pyをpackageにコピー
cp app.py package/

実行結果の確認:

ls -la package/

# 以下のようなファイル/ディレクトリが表示されればOK
# app.py
# slack_bolt/
# slack_sdk/
# requests/
# certifi/
# urllib3/
# ...

5. デプロイパッケージの作成

# packageディレクトリ内でZIPを作成
cd package
zip -r ../lambda_function.zip .
cd ..

# ZIPファイルのサイズを確認
ls -lh lambda_function.zip
# 約2-3MBになっていればOK

6. ローカルでのテスト(オプション)

Lambda環境を再現してローカルテストする場合:

# AWS SAM CLIを使用
sam local start-api

# または、Docker + Lambda Runtime Emulatorを使用
docker run -p 9000:8080 \
  -v $(pwd)/package:/var/task \
  public.ecr.aws/lambda/python:3.14 \
  app.lambda_handler

開発→デプロイの流れ

開発フロー

1. ローカルでコード編集
   ↓
2. packageにコピー
   ↓
3. ZIPファイル作成
   ↓
4. Lambdaにアップロード
   ↓
5. Slackでテスト
   ↓
6. CloudWatch Logsで確認

コード変更時の手順

# 1. app.pyを編集
vim app.py

# 2. packageに反映
cp app.py package/

# 3. ZIPを再作成
rm lambda_function.zip
cd package
zip -r ../lambda_function.zip .
cd ..

# 4. Lambdaにアップロード
# AWS Lambda コンソール → アップロード元 → .zipファイル

.gitignoreの設定(オプション)

Gitで管理する場合、以下を.gitignoreに追加:

# Python
__pycache__/
*.py[cod]
*$py.class
*.so

# Virtual Environment
venv/
env/
ENV/

# Package
package/
lambda_function.zip

# Environment Variables
.env

# IDE
.vscode/
.idea/
*.swp
*.swo

実装のポイント

1. Slackの3秒ルール対策

Slackは、Slash Commandの応答を3秒以内に返さないとoperation_timeoutエラーになります。

解決策:Lazy Listener + 非同期処理

def calendar_ack(body, ack, logger):
    """即座に進捗メッセージを返す(3秒以内)"""
    ack({
        "response_type": "ephemeral",
        "text": "⏳ カレンダーリンクを作成中...",
    })

def handle_calendar_command(body, client, logger):
    """非同期で実際の処理を実行"""
    # 重い処理(ユーザー情報取得、メール取得など)
    # response_urlで結果を返す
    pass

# Lazy Listenerとして登録
app.command("/calendar")(ack=calendar_ack, lazy=[handle_calendar_command])

2. チャンネルメンバー全員の取得

@channelや@hereを検出し、チャンネルメンバー全員を取得します。

def get_channel_members(client, channel_id):
    """チャンネルメンバーを取得(Bot除外)"""
    # チャンネルメンバー取得
    members_result = client.conversations_members(channel=channel_id)
    member_ids = members_result.get("members", [])
    
    # 全ユーザーリストを一度に取得(効率化)
    users_result = client.users_list()
    
    # Botユーザーを除外
    bot_ids = {u["id"] for u in users_result["members"] if u.get("is_bot")}
    human_members = [uid for uid in member_ids if uid not in bot_ids]
    
    return human_members

重要:Botがチャンネルメンバーである必要がある

conversations.members APIを使うには、Botがそのチャンネルに追加されている必要があります。

# チャンネルにBotを追加
/invite @Calendar Link Bot

3. 実行時刻の次の正時に設定

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

# 日本時間で現在時刻を取得
jst = ZoneInfo('Asia/Tokyo')
now = datetime.now(jst)

# 次の正時(整時刻)に設定
if now.minute > 0 or now.second > 0:
    start_dt = now.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1)
else:
    start_dt = now.replace(minute=0, second=0, microsecond=0)

end_dt = start_dt + timedelta(hours=1)

# Google Calendar用のフォーマット(UTC)
start_time = start_dt.astimezone(ZoneInfo('UTC')).strftime("%Y%m%dT%H%M%SZ")
end_time = end_dt.astimezone(ZoneInfo('UTC')).strftime("%Y%m%dT%H%M%SZ")

動作例:

  • 14:34に実行 → 15:00-16:00
  • 20:05に実行 → 21:00-22:00

4. プライバシー保護(ephemeral)

すべてのメッセージをephemeral(自分だけに表示)に設定:

# ack()の応答
ack({
    "response_type": "ephemeral",  # 自分だけに表示
    "text": "⏳ カレンダーリンクを作成中...",
})

# 最終結果も自分だけに表示
success_message = {
    "response_type": "ephemeral",
    "text": "✅ カレンダーリンクを作成しました",
}

プロジェクト構成

ディレクトリ/ファイル構造

slack-calendar-bot/
├── app.py                    # メインのアプリケーションコード
├── requirements.txt          # Python依存パッケージ
├── README.md                 # プロジェクト説明(オプション)
├── .gitignore               # Git除外設定(オプション)
├── package/                  # デプロイ用パッケージディレクトリ
│   ├── app.py               # app.pyのコピー
│   ├── slack_bolt/          # Slack Boltライブラリ
│   ├── slack_sdk/           # Slack SDKライブラリ
│   ├── requests/            # Requestsライブラリ
│   ├── urllib3/             # urllib3ライブラリ
│   ├── certifi/             # SSL証明書
│   └── ...                  # その他の依存ライブラリ
└── lambda_function.zip      # Lambda用デプロイパッケージ

各ファイルの役割

ファイル/フォルダ 役割 作成方法
app.py メインコード(Slack Bolt、Lambda Handler) 手動作成
requirements.txt 依存パッケージ一覧 手動作成
package/ Lambdaにデプロイする全ファイル pip installで自動生成
lambda_function.zip Lambda用のZIPファイル zipコマンドで作成

ローカル開発環境のセットアップ

前提条件

以下がインストールされていることを確認:

  • Python 3.12以上

    python3 --version
    # Python 3.12.0 以上であればOK
    
  • pip(Pythonパッケージマネージャー)

    pip3 --version
    
  • zip(ZIPコマンド)

    zip --version
    # Macには標準でインストール済み
    

ステップ1: プロジェクトディレクトリの作成

# プロジェクトディレクトリを作成
mkdir slack-calendar-bot
cd slack-calendar-bot

# 現在のディレクトリを確認
pwd
# /Users/your-name/slack-calendar-bot

ステップ2: requirements.txtの作成

依存パッケージを定義するファイルを作成:

cat > requirements.txt << 'EOF'
slack-bolt==1.18.0
requests
EOF

requirements.txtの内容:

slack-bolt==1.18.0
requests

各パッケージの役割:

  • slack-bolt - Slack Bot開発フレームワーク
  • requests - HTTP通信ライブラリ(response_url用)

ステップ3: app.pyの作成

メインのアプリケーションコードを作成:

# エディタで開く(VSCode, vim, nanoなど)
code app.py  # VSCodeの場合
# または
vim app.py   # vimの場合

app.pyの内容:

(後述の「コード全体」セクションの完全版を参照)

ステップ4: 依存パッケージのインストール

# packageディレクトリを作成
mkdir package

# 依存パッケージをpackageディレクトリにインストール
pip3 install -r requirements.txt -t ./package

# インストールされたパッケージを確認
ls -la package/

実行結果の例:

drwxr-xr-x  slack_bolt/
drwxr-xr-x  slack_sdk/
drwxr-xr-x  requests/
drwxr-xr-x  urllib3/
drwxr-xr-x  certifi/
...

サイズ確認:

du -sh package/
# 約10-15MBになる

ステップ5: app.pyをpackageにコピー

# app.pyをpackageディレクトリにコピー
cp app.py package/

# コピーされたか確認
ls package/app.py
# package/app.py

ステップ6: デプロイパッケージ(ZIP)の作成

# packageディレクトリに移動
cd package

# ZIPファイルを作成(カレントディレクトリの全ファイルを含める)
zip -r ../lambda_function.zip .

# 親ディレクトリに戻る
cd ..

# ZIPファイルのサイズを確認
ls -lh lambda_function.zip
# 約2-3MBになっていればOK

ZIPの内容を確認:

# ZIPの中身を確認
unzip -l lambda_function.zip | head -20

# app.pyが含まれているか確認
unzip -l lambda_function.zip | grep "app.py"
# 14591  12-25-2025 20:30   app.py

ステップ7: .gitignoreの設定(Git使用時)

Gitでバージョン管理する場合、以下のファイルを.gitignoreに追加:

cat > .gitignore << 'EOF'
# Python
__pycache__/
*.py[cod]
*$py.class
*.so

# Virtual Environment
venv/
env/
ENV/

# Package(生成されるファイル)
package/
lambda_function.zip

# Environment Variables(機密情報)
.env
.env.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# macOS
.DS_Store
EOF

最終的なディレクトリ構成

slack-calendar-bot/
├── app.py                    ✅ メインコード
├── requirements.txt          ✅ 依存パッケージ
├── .gitignore               ✅ Git除外設定
├── package/                  ✅ デプロイ用(約10-15MB)
│   ├── app.py
│   ├── slack_bolt/
│   ├── slack_sdk/
│   ├── requests/
│   └── ...
└── lambda_function.zip      ✅ デプロイパッケージ(約2-3MB)

開発→デプロイの流れ

初回セットアップ

1. プロジェクトディレクトリ作成
   ↓
2. requirements.txt作成
   ↓
3. app.py作成
   ↓
4. pip install(packageディレクトリに)
   ↓
5. app.pyをpackageにコピー
   ↓
6. ZIPファイル作成
   ↓
7. Lambdaにアップロード

コード変更時の手順

app.pyを修正した場合:

# 1. app.pyを編集
vim app.py

# 2. packageに反映
cp app.py package/

# 3. 古いZIPを削除
rm lambda_function.zip

# 4. 新しいZIPを作成
cd package
zip -r ../lambda_function.zip .
cd ..

# 5. サイズ確認
ls -lh lambda_function.zip

# 6. AWS Lambda コンソールでアップロード
# (ブラウザで手動アップロード)

もっと簡単に:ワンライナー

# すべてを一気に実行
cp app.py package/ && \
rm -f lambda_function.zip && \
cd package && zip -r ../lambda_function.zip . && cd .. && \
ls -lh lambda_function.zip

依存パッケージを追加した場合

requirements.txtに新しいパッケージを追加した場合:

# 1. packageディレクトリを削除
rm -rf package/

# 2. 再作成
mkdir package

# 3. 依存パッケージを再インストール
pip3 install -r requirements.txt -t ./package

# 4. app.pyをコピー
cp app.py package/

# 5. ZIP作成
cd package && zip -r ../lambda_function.zip . && cd ..

デバッグの流れ

1. Slackでコマンド実行
   ↓
2. エラー発生
   ↓
3. CloudWatch Logsで確認
   ↓
4. ローカルでapp.pyを修正
   ↓
5. packageにコピー
   ↓
6. ZIP作成
   ↓
7. Lambdaに再アップロード
   ↓
8. 再テスト

セットアップ手順

1. Slack Appの作成

  1. Slack APIにアクセス
  2. 「Create New App」 → 「From scratch」
  3. App Nameに「Calendar Link Bot」を入力
  4. ワークスペースを選択

2. 権限の設定

OAuth & Permissions → Bot Token Scopesに以下を追加:

  • commands - Slash Commandの実行
  • users:read - ユーザー情報の取得
  • users:read.email - メールアドレスの取得
  • chat:write - メッセージの送信
  • channels:read - パブリックチャンネル情報の取得
  • groups:read - プライベートチャンネル情報の取得

3. Slash Commandの作成

Slash Commands → Create New Command

  • Command: /calendar
  • Request URL: (後でLambda Function URLを設定)
  • Short Description: Googleカレンダーリンクを作成

4. Lambda関数の作成

4.1 Lambda関数を作成

AWS Lambda コンソールで新しい関数を作成:

関数名: slack-calendar-bot
ランタイム: Python 3.14
アーキテクチャ: x86_64

4.2 環境変数を設定

設定 → 環境変数 → 編集 で以下を追加:

  • SLACK_BOT_TOKEN: xoxb-... (Bot User OAuth Token)
  • SLACK_SIGNING_SECRET: Slack AppのSigning Secret

4.3 Function URLを作成

設定 → Function URL → Create function URL

  • 認証タイプ: NONE
  • 追加のCORS設定: 無効

生成されたURLをコピー → Slash CommandのRequest URLに設定

4.4 デプロイパッケージのアップロード

前述の「ローカル開発環境のセットアップ」で作成したlambda_function.zipをアップロード:

AWS Lambda コンソール → コード → アップロード元 → .zip ファイル → lambda_function.zipを選択

※ ローカルでのZIP作成手順は「ローカル開発環境のセットアップ」セクションを参照

5. Slack Appをインストール

Install App → Install to Workspace → 許可する

6. テスト

使いたいチャンネルで:

# Botを追加(@channel使用時に必要)
/invite @Calendar Link Bot

# テスト実行
/calendar @channel

コード全体

app.py(抜粋)

クリックして展開
import os
import re
import json
import requests
from urllib.parse import urlencode
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from slack_bolt import App
from slack_bolt.adapter.aws_lambda import SlackRequestHandler

# Slack App初期化
app = App(
    token=os.environ.get("SLACK_BOT_TOKEN"),
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
    process_before_response=True
)

def has_channel_mention(text):
    """@channelまたは@hereが含まれているか判定"""
    return "@channel" in text or "@here" in text

def get_channel_members(client, channel_id):
    """チャンネルメンバーを取得(Bot除外)"""
    members_result = client.conversations_members(channel=channel_id)
    member_ids = members_result.get("members", [])
    
    users_result = client.users_list()
    bot_ids = {u["id"] for u in users_result["members"] if u.get("is_bot")}
    
    return [uid for uid in member_ids if uid not in bot_ids]

def calendar_ack(body, ack, logger):
    """即座に応答(3秒ルール対策)"""
    ack({
        "response_type": "ephemeral",
        "text": "⏳ カレンダーリンクを作成中...",
    })

def handle_calendar_command(body, client, logger):
    """カレンダーリンク作成処理"""
    command = body
    text = command.get("text", "").strip()
    channel_id = command.get("channel_id")
    response_url = command.get("response_url")
    
    # メンション処理
    if has_channel_mention(text):
        mentioned_user_ids = get_channel_members(client, channel_id)
    else:
        mentioned_user_ids = extract_user_mentions(text)
    
    # メールアドレス取得
    users = []
    for uid in mentioned_user_ids:
        email, real_name = fetch_user_email(client, uid)
        if email:
            users.append({"email": email, "real_name": real_name})
    
    # 日時設定(次の正時)
    jst = ZoneInfo('Asia/Tokyo')
    now = datetime.now(jst)
    
    if now.minute > 0 or now.second > 0:
        start_dt = now.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1)
    else:
        start_dt = now.replace(minute=0, second=0, microsecond=0)
    
    end_dt = start_dt + timedelta(hours=1)
    
    # カレンダーリンク生成
    link = build_google_calendar_link("ミーティング", start_dt, end_dt, users)
    
    # response_urlで結果を返す
    send_response(response_url, {
        "response_type": "ephemeral",
        "text": "✅ カレンダーリンクを作成しました",
        # ... blocks
    })

# Lazy Listenerとして登録
app.command("/calendar")(ack=calendar_ack, lazy=[handle_calendar_command])

# Lambda用ハンドラー
def lambda_handler(event, context):
    slack_handler = SlackRequestHandler(app=app)
    return slack_handler.handle(event, context)

トラブルシューティング

operation_timeout エラー

原因: 3秒以内に応答できていない

解決策:

  • Lazy Listener + 非同期処理を使用
  • ack()で即座に進捗メッセージを返す
  • 重い処理はresponse_urlで返す

channel_not_found エラー

原因: Botがチャンネルメンバーでない

解決策:

/invite @Calendar Link Bot

メールアドレスが取得できない

原因: Slackプロフィールにメールアドレスが設定されていない

解決策:

  • Slackプロフィールにメールアドレスを追加
  • または、users:read.email権限を確認

運用コスト

AWS Lambda

無料枠(永続):

  • リクエスト数: 月100万リクエストまで無料
  • 実行時間: 月40万GB-秒まで無料

このBotの実質コスト(月3,000回実行):

  • Lambda実行料金: $0(無料枠内)
  • CloudWatch Logs: 約$0.001(約0.1円/月)

ほぼ無料で運用可能!

使用例

定例ミーティング

/calendar @channel
→ チーム全員のカレンダーリンクを作成
→ 件名を「週次ミーティング」に変更
→ 日時を毎週月曜10:00に設定

急な打ち合わせ

/calendar @田中 @佐藤
→ 次の正時のミーティングリンクを作成
→ すぐにGoogle Meetを追加

プロジェクトキックオフ

/calendar @channel
→ 件名を「プロジェクトキックオフ」に変更
→ アジェンダを説明欄に追加

メリット

時間短縮

従来の方法:

  1. Googleカレンダーを開く
  2. 新規イベントを作成
  3. メンバーのメールアドレスをコピー
  4. ゲストを一人ずつ追加
  5. 日時を設定

Calendar Link Bot:

  1. /calendar @channel を実行
  2. ボタンをクリック
  3. 日時を調整(必要に応じて)

プライバシー保護

  • メッセージは実行者だけに表示
  • チャンネルが汚れない
  • 他のメンバーに通知が行かない

柔軟性

  • 個別メンションも@channelにも対応
  • 日時は後から自由に調整可能
  • Googleカレンダーのすべての機能が使える

今後の展望

現在、以下の機能追加を検討しています:

1. 参加メンバーの空いている時間をレコメンド

構想:

  • Google Calendar APIを使って参加者のカレンダーを取得
  • 全員が空いている時間帯を自動検出
  • 最適な時間をレコメンド

例:

/calendar @田中 @佐藤 @鈴木

↓

✅ 以下の時間帯が全員空いています:
• 明日 14:00-15:00
• 明後日 10:00-11:00
• 金曜日 15:00-16:00

[明日 14:00-15:00で作成]

技術的な課題:

  • Google Calendar APIの認証(OAuth 2.0)
  • 複数人のカレンダーを並行取得
  • プライバシー考慮(カレンダー情報へのアクセス許可)

2. AIの導入

構想1: 自然言語での時刻指定

/calendar @田中 @佐藤 明日の午後に1時間
→ 明日の14:00-15:00でリンク作成

/calendar @channel 来週の月曜日の朝
→ 来週月曜日の10:00-11:00でリンク作成

構想2: ミーティング内容の自動提案

Slackの過去のメッセージを分析して:

  • 件名を自動提案
  • アジェンダを生成
  • 必要な参加者を推薦

構想3: スマートなリマインダー

AIが以下を判断:

  • ミーティング前の最適なリマインダー時刻
  • 準備が必要な場合は早めに通知
  • 場所の移動時間を考慮

技術スタック候補:

  • OpenAI API(自然言語処理)
  • Claude API(文脈理解)
  • LangChain(LLMアプリケーション開発)

まとめ

Slack Calendar Link Botにより、カレンダー招待の作成時間を60-70%削減できました。

主な特徴:

  • ✅ Slash Commandで簡単実行
  • ✅ @channel , @here 対応
  • ✅ プライバシー保護
  • ✅ ほぼ無料で運用可能

今後はAIを活用して、さらに使いやすく進化させていく予定です。

参考資料

1
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
1
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?