概要
本記事では、子供のポケモンカード対戦記録をSlackのスラッシュコマンドからGoogle Spread Sheetsへ自動入力するシステムを構築した過程をまとめます。
単純なAPI連携の過程を記載するだけでなく、「サーバーレス特有の制約(Slack 3秒ルール)」や「セキュリティと環境変数の設計」、「暗号鍵フォーマットのトラブルシューティング」なども盛り込んでいます。
動機
日常の何かにITを使いたいと思ったのと、親子でポケカをしており、対戦記録を家族で共有、記録出来ればと思ったため。
構成
Slack → API Gateway → Lambda → Google Spread Sheets
スラックコマンドを入力すると、slackに通知が飛び、内容がスプレッドシートに記録されます
1. 受付レイヤー (Slack & API Gateway)
ユーザー: /pokeca [名前] [結果] [デッキ] と打つだけ。
インフラ: Slackからの application/x-www-form-urlencoded 形式のPOSTリクエストを、API Gatewayが受け取り、Lambdaへ橋渡し(プロキシ)します。
2. ロジックレイヤー (AWS Lambda)
使用コード
GOOGLE_SETTINGS、SPREADSHEET_ID部分はlambdaの変数設定を利用しています。
import json
import base64
import gspread
import os
from urllib.parse import parse_qs
from datetime import datetime
def lambda_handler(event, context):
try:
# --- 1. 環境変数から設定を読み込む ---
# Google CloudのサービスアカウントJSON(まるごと)
key_json_str = os.environ.get('GOOGLE_SETTINGS')
# 対象のスプレッドシートID
spreadsheet_id = os.environ.get('SPREADSHEET_ID')
if not key_json_str or not spreadsheet_id:
return response_to_slack("エラー:環境変数が正しく設定されていません。")
service_account_info = json.loads(key_json_str)
# 秘密鍵の改行コードを修正(環境変数を経由する際の必須処理)
service_account_info['private_key'] = service_account_info['private_key'].replace('\\n', '\n')
# --- 2. Slackからのデータ受け取り ---
body = event.get('body', '')
if event.get('isBase64Encoded'):
body = base64.b64decode(body).decode('utf-8')
params = parse_qs(body)
user_text = params.get('text', [''])[0]
words = user_text.split()
if len(words) < 3:
return response_to_slack("形式エラー!「名前 勝敗 デッキ」の順で入力してね。", ephemeral=True)
name, result, deck = words[0], words[1], words[2]
date_str = datetime.now().strftime('%Y/%m/%d %H:%M')
# --- 3. Google Sheetsへの接続と書き込み ---
gc = gspread.service_account_from_dict(service_account_info)
sh = gc.open_by_key(spreadsheet_id)
worksheet = sh.get_worksheet(0) # 1枚目のシートを指定
# データの追記(日付, 対戦相手, 勝敗, デッキ)
worksheet.append_row([date_str, name, result, deck])
return response_to_slack(f"✅ 記録完了!\n日付: {date_str}\n対戦相手: {name}\n結果: {result}\nデッキ: {deck}")
except Exception as e:
# ログ出力(CloudWatch用)
print(f"Error: {str(e)}")
return response_to_slack(f"エラーが発生しました: {str(e)}", ephemeral=True)
def response_to_slack(message, ephemeral=False):
"""Slackへのレスポンスフォーマットを作成"""
response_body = {
'response_type': 'ephemeral' if ephemeral else 'in_channel',
'text': message
}
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(response_body, ensure_ascii=False)
}
Runtime: Python 3.12を使用。
スプレッドシート連携するため、レイヤーにKlayers(gspread が含まれている公開レイヤー)を追加しました。
タイムアウト対応
構成を一通り作成して検証した際、Slackの3秒タイムアウト制約により operation_timeout が発生しましたが、メモリをデフォルトの128MBから512MBに変更したところ処理時間が劇的に短縮。結果として、無料枠内で安定したレスポンスを実現することが出来ました。
2025/02/04 追記
これでもたまにタイムアウトするので1024MBにしました
時刻表機ずれ対応
当初はスプレッドシートに記録される時刻がUTCでしたが、lambdaの関数設定に「TZ: Asia/Tokyo」を追加することで日本時刻で記録されるようにしました。
セキュリティへの配慮
Google Cloudの秘密鍵をコードにベタ書きせず、Lambdaの環境変数にJSONをまるごと格納して読み込む設計にしました。これにより、ソースコードの漏洩による不正アクセスのリスクを排除し、設定とロジックを分離しています。
3. データレイヤー (Google Cloud & Sheets)
1. プロジェクトの作成と API の有効化
プロジェクト作成
Google Cloud Console で新しいプロジェクトを作成。
APIの有効化
「APIとサービス」>「ライブラリ」から以下を検索し、有効化を実施しています。
- Google Sheets API
- Google Drive API(スプレッドシートの検索や権限確認に必要)
2. サービスアカウントの作成
プログラム専用の「身分証」を発行しています。
ロールは今回特定のシートを直接共有するため、ロール(権限)の割り当ては「なし(空)」のままで作成。
3. 秘密鍵(JSON)の発行
Lambda の環境変数GOOGLE_SETTINGSに設定した値。
作成したサービスアカウントから「新しい鍵を作成」で JSON を選択してダウンロードします。
4. スプレッドシート側の設定
書き込み先のスプレッドシートの「共有」ボタンから、サービスアカウントのメールアドレスを「編集者」として招待します。
今後の展望
この記録機能だけだいぶ味気ないので、月末などの決まったタイミングでslackに現在の勝率が通知される、溜まったデータをLambdaで集計し、勝率グラフをSlackに画像で返却する機能の追加なども出来ればと考えています。出来たらまた記事にしようと思います。


