249 Views
July 26, 25
スライド概要
https://fortee.jp/techramen-25-conf/proposal/cb93dba0-d7fe-46db-8b30-5fbd6af31056
「箱庭」をハックするGoogle Apps Script: Slack Bot開発で得 た知見(可能性と挑戦) TechRAMEN 2025 Conference Jul 26, 2025. v0.0.1 @katzumi( ) かつみ Press Space for next page
自己紹介 (かつみ)と申します。 katzumi 「障害のない社会をつくる」をビジョンとする「LITALICO(りたりこ)」に所属しています 社内では SBO(Slack Bot Officer)を自称 以下のアカウントで活動しています。 katzchum k2tzumi katzumi
お願い 🙏 写真撮影、 での実況について SNS 登壇者の励みになるので是非ともご意見やご感想など、フィードバック頂けると助かります mm このスライドには複数のリンクがあります スライドの内容は、すでに以下の場所で公開されていますので、ぜひお手元でご覧ください forteeのプロポーザルページ または の投稿 🙆♀📷 🙅♂📹💸 🙅📸👨👦👦 #techramen25conf #shoyu
今日のお題
の Google Apps Script(GAS) 知見 立ち食いしてってね。ロットを乱したらすみません🛎️
GAS 本日のスコープ の開発手法には2つのパターンがある コンテナバインド型 スタンドアロン型
GAS 本日のスコープ の開発手法には2つのパターンがある コンテナバインド型 ファイルに紐づく Sheets, Docs, Forms 等に埋め込み シンプルトリガー(onOpen 等)でスクリプト実行 WebApp 化は技術的に可能だが非推奨 ファイル自動化・マクロ用途 スタンドアロン型
GAS 本日のスコープ の開発手法には2つのパターンがある コンテナバインド型 スタンドアロン型 ファイルに紐づく 独立したスクリプトプロジェクト Sheets, Docs, Forms 等に埋め込み WebApp として公開可能 シンプルトリガー(onOpen 等)でスクリプト実行 時間駆動トリガー(定期実行)のみ WebApp 化は技術的に可能だが非推奨 外部からの HTTP リクエスト対応 ファイル自動化・マクロ用途 色々ゴニョゴニョできる
GAS 本日のスコープ の開発手法には2つのパターンがある コンテナバインド型 スタンドアロン型 ファイルに紐づく 独立したスクリプトプロジェクト Sheets, Docs, Forms 等に埋め込み WebApp として公開可能 シンプルトリガー(onOpen 等)でスクリプト実行 時間駆動トリガー(定期実行)のみ WebApp 化は技術的に可能だが非推奨 外部からの HTTP リクエスト対応 ファイル自動化・マクロ用途 色々ゴニョゴニョできる ⚠️ 今回はスタンドアロン型webappをベ ースとします!
作ったSlack Bot(アプリ)
個のアプリ 12
作ったSlack Botたち 実務系 Mob Timer Bot 対話的なモブプロタイマー 勤怠Bot 勤怠システムと連動した打刻(開発停止中) LGTM画像作成コマンド LGTM 画像を作成する 雑談系 OpenAI Bot と Slack を繋ぐ Bot echo bot invite されたチャネルの内容を別チャネル に echo する。times の集約に利用 ChatGPT 便利系 選んでコマンド 通称綾鷹コマンド Pic Search 画像検索コマンド。ksk コマンド対応 お天気アプリ 気象庁の API から天気等を取得する 通知系 新しい絵文字を通知 チャネル作成Webhook 新チャネルを通知 Emoji Webhook 展開 URL esa esa の URL を展開 Strava Strava のアクティビティをドヤるやつ
Why GAS?
趣味プログラミングの環境としてアリなのでは? 環境構築の面倒だったり、プログラミング以外のコストもかけたくない … クレカ登録不要 Google アカウントがあれば 今すぐ無料で開始 環境構築の手数が少ない 設定作業が最小限 実行環境の準備が不要 バージョン管理が容易 デプロイがシンプル 数ステップでデプロイ完了 フルマネージド サーバー管理・スケーリング・監視 Google任せで安心 TypeScript 開発 clasp でローカル開発 型安全でモダンな開発
導入によるモダン開発環境の構築
clasp
🚀 clasp で TypeScript 開発環境を手に入れよう
clasp導入のメリット
普段遣いのローカルのエディタで開発できる
Web ベースのスクリプトエディタからの卒業
コードを git 管理できる
Typescript で開発できる
デプロイバージョンの管理ができる
の導入方法
classp インストール
classp
$ npm install -g @google/clasp
Typescript
で開発
環境をセットアップ
# TypeScript
npm init -y
npm install -D typescript @types/google-apps-script
tsconfig.json
作成
{
"compilerOptions": {
"target": "ES2019",
"lib": ["ES2019"],
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
導入によるモダン開発環境の構築
clasp
🚀 clasp で TypeScript 開発環境を手に入れよう
clasp導入のメリット
普段遣いのローカルのエディタで開発できる
Web ベースのスクリプトエディタからの卒業
コードを git 管理できる
Typescript で開発できる
デプロイバージョンの管理ができる
MEMO
は
があります
Typescript supportがDropされます
Rollupを使ってのトランスパイルが必要になりま
す
clasp v3 Breaking Changes
の導入方法
classp インストール
classp
$ npm install -g @google/clasp
Typescript
で開発
環境をセットアップ
# TypeScript
npm init -y
npm install -D typescript @types/google-apps-script
tsconfig.json
作成
{
"compilerOptions": {
"target": "ES2019",
"lib": ["ES2019"],
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
つらい課題 GAS こんな壁があったりします npm ライブラリが使えない 画像加工が鬼門 普通の JavaScript が動かない… Canvas もなければライブラリもない Web アプリの実行モデルが同期 データ永続化が特殊 レスポンスを返すと、この時点で実行終了。非同期 workspace のファイルをストレージ代わりにできる 処理ができない けれど ログが見えない WebApp のログの確認方法が確立されていない デバッグが地獄… HTTP リクエストが特殊 ホワイトリストで登録必須 UserAgent を変更できない
ライブラリ使えない問題 npm 独自ライブラリがあるけれどね GAS は独自環境のため、Node.js やブラウザ向けの npm ライブラリは基本的に使用できません。 モジュールシステム非対応です😅 Slack Bolt 使えません😭 GAS 向けの野良ライブラリもあるけれど ここらへんの API 使えない (使用不可) // Node.js API const fs = require('fs'); const path = require('path'); const http = require('http'); ブラウザ (使用不可) ファイルシステム ❌ パス操作 ❌ // HTTP サーバー ❌ // // // API document.getElementById('id'); // DOM window.location.href; // 専用API(使用可能) // GAS SpreadsheetApp.getActiveSheet(); UrlFetchApp.fetch(url); 操作 ❌ ブラウザ情報 ❌ スプレッドシート ✅ 通信 ✅ // // HTTP
Slack
自前で
Slack APIのクライアントを実装
の
は 公開されている ので生暖かく
Web API
では
UrlFetchApp.fetch
以外に HTTP 通信を行う手段はありません。
以外の外部サービスとの連携や Web スクレイピングに欠かせません。
GAS
UrlFetchApp
Google Workspace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private invokeAPI(endPoint: string, payload: Record<never, never>): Response {
let response: HTTPResponse;
try {
switch (this.preferredHttpMethod(endPoint)) {
case "post":
response = UrlFetchApp.fetch(
endPoint,
this.postRequestOptions(payload)
);
break;
case "get":
response = UrlFetchApp.fetch(
this.formUrlEncoded(endPoint, payload),
this.getRequestOptions()
);
break;
default:
throw new Error("Unknown method.");
の3秒ルール問題 Slack Bot 3 秒間レスポンスを返さないと “ Acknowledgment response All apps must, as a minimum, acknowledge the receipt of a valid interaction payload. To do that, your app must reply to the HTTP POST request with an HTTP 200 OK response. This must be sent within 3 seconds of receiving the payload. If your app doesn't do that, the Slack user who interacted with the app will see an error message, so ensure your app responds quickly. Otherwise, the user won't see anything when your app only sends an acknowledgment response. If you want to do more, keep reading. 参考: "Respond immediately to the initial request") slash コマンドの実行時など問題になりがち (
の3秒ルール問題 Slack Bot 3 秒を超えたら即エラー = Bot 死亡 💀 1 2 3 4 5 6 7 8 9 10 11 function doPost(e) { // SlackBot 3 は 秒以内にレスポンス必須! // 外部API呼び出し(5秒かかる) const result = callExternalAPI(); // ここまで到達する前に がエラー扱い ⏰ 3秒超過 // Slack return ContentService.createTextOutput( JSON.stringify({text: result}) ); }
の3秒ルール問題 Slack Bot 3 秒を超えたら即エラー = Bot 死亡 💀 1 2 3 4 5 6 7 8 9 10 11 GAS function doPost(e) { // SlackBot 3 は 秒以内にレスポンス必須! // 外部API呼び出し(5秒かかる) const result = callExternalAPI(); // ここまで到達する前に がエラー扱い ⏰ 3秒超過 // Slack return ContentService.createTextOutput( JSON.stringify({text: result}) ); } 1 2 3 4 5 6 7 8 9 10 11 は同期的実行モデル アプリのエンドポイント レスポンスをすぐに返す // Web function doGet() { // const output = ContentService.createTextOutput( " ); リクエストを受け付けました。処理はバックグラウンドで開始されます..." ここで非同期処理をキックしようとする // asyncFunctionThatWillBeTerminated(); return output; // この時点でスクリプトの実行は終了する 12 13 } 14 15 16 async function asyncFunctionThatWillBeTerminated() { Logger.log(" ..."); 17 18 19 非同期処理を開始 秒待機 非同期処理が完了しました!"); // **このログは出力されない** Utilities.sleep(10000); // 10 Logger.log(" }
3 簡易 Jobキューシステムを作った 秒ルール回避 疑似非同期 生存 🎉 + = の を使って簡単に非同期を行えるようにライブラリ化しました。 が JobBroker に実行を通知し、JobBroker がキャッシュされたパラメータを用いてアプリケ ーション本体の関数を非同期で実行します。 GAS Trigger TimeBased Trigger 向け簡易job-queueを作った Google Apps Script Zenn アプリケーションの globalThis をライブラリに渡すことで、ライブラリがアプリケーション本体のグローバル 関数を動的に呼び出せます アプリ内のfunctionをGASライブラリからコールバックさせる方法 Zenn
思いがけない副次効果
アプリのログ問題が解消!! Web デバッグが凄くやりやすくなりました トリガー実行時にログ出力( console.info() )すると、ログ表示できます 種類が webapp の場合は、GAS エディタ上でログ表示できません。 本当は GCP を作って、StackDriver Logging(現 Cloud Logging)に出力させる必要があったみたい
リクエストが特殊
HTTP
スクレイピングに制約
UserAgent を更新できない
bot として割と致命的
で設定できないヘッダーの例
// GAS
const options = {
'method': 'GET',
'headers': {
'User-Agent': 'Custom Agent', //
'Host': 'example.com',
//
'Content-Length': '100',
//
'Accept': 'text/html',
//
'Accept-Language': 'ja-JP',
'Cookie': 'session=abc123',
'X-Custom-Header': 'value'
}
};
❌ 無効
❌ 無効
❌ 無効
✅ 有効
// ✅ 有効
// ✅ 有効
// ✅ 有効
リクエストが特殊 HTTP スクレイピングに制約 リクエスト時の IP を固定できない GAS では、どのサーバーがスクリプトを実行するか、どの IP アドレスからアクセスするかを予測できません GAS からのリクエストは、Google の特定の IP アドレス範囲 から送信されます。 [1] で公開されています ↩︎ 1. https://www.gstatic.com/ipranges/goog.json
リクエストが特殊
HTTP
スクレイピングに制約
ホワイトリストに登録していない URL にアクセスできない
マニフェストファイル( appsscript.json )に urlFetchWhitelist フィールドを含めます
[1]
1
2
3
4
5
6
7
{
"timeZone": "Asia/Tokyo",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"urlFetchWhitelist": [
"https://slack.com/",
8
9
10
11
12
1.
"https://api.slack.com/",
"https://hooks.slack.com/",
"https://www.googleapis.com/"
]
}
参考: https://developers.google.com/apps-script/manifest/allowlist-url ↩︎
諦めて他の FaaSにリクエスト処理を移譲 どう頑張っても は変えられない UserAgent 勤怠 Bot では UserAgent で弾かれたので、仕方無しに Netlify 経由でアクセスするようにしました GitHub - k2tzumi/works-kintai-slask-command-netlify-functions Contribute to k2tzumi/works-kintai-slask-command-netlify-functions development by creating an account on GitHub. GitHub 使えていたのですが、認証時のセキュリティが強化された 為、開発停滞中 ヘッドレスブラウザ利用も GAS では難しい [1] でのFingerPrintを取得 ↩︎ 1. Canvas
LGTM 画像加工も苦労した 画像を作りたかった インチキしています。Cloudinary が使いやすかったです 😅 画像を作成するSlack Appを作ったお話 LGTM Zenn Pure JS の画像編集ライブラリを WebPack でバンドル。トランスパイルして GAS で動かそうと試みた… GitHub - k2tzumi/lgtm-slash-command-npm-library-based: It's not working It's not working. Contribute to k2tzumi/lgtm-slash-command-npm-library-based development by creating an account on GitHub. GitHub
画像レスポンスも工夫が必要 できるけれど、 のみ GAS では画像を直接レスポンス出来ないので、Google Drive にアップロードして共有 URL を作成して配信させ る ContentService.setMimeType CSV, iCal, JavaScript, JSON, Text, vCard GitHub - k2tzumi/slack-strava-unfurling Contribute to k2tzumi/slack-strava-unfurling development by creating an account on GitHub. GitHub
セッション管理も時前で行う必要がある データの永続化が特殊 PropertiesService ( )として利用 スクリプト単位で永続化 JSON 文字列で複雑なデータも保存 容量制限:500KB Hidden パラメータ持ち回り Slack の Interactive Components に hidden データを埋め込んで状態管理 Slack ならユーザーには見えづらいので、やりやすい KVS Key-Value Store
まだまだ壁があります リダイレクトができない HTML をレスポンスさせて強制的にリフレッシュさせることはできますが、使い所が限定的です addOns.common.openLinkUrlPrefixes の指定も必要
気になっていること ライブラリ問題に一石を投じるか!? で始めるWebAssembly - Qiita GAS 本記事は 🔗 はじめに て 以下 WebAssembly( 日目の記事になります ここでは Google Apps Script(以下GAS) を用い に入門するための紹介を行います GAS や wasm,... QualiArts Advent Calendar 2022 16 wasm) qiita.com Go を Wasm にビルドして Google Apps Script で動かす Zenn
ご清聴ありがとうございました 良い ライフを GAS