Open15

GASをgit管理してうまくやる

oratakeoratake

公式のreadmeを見ながら設定まわり

とりあえずnpmでグローバルにインストール

$ npm install -g @google/clasp

loginする

$ clasp login

提示されたURLからログインすると、ホームディレクトリに .clasprc.json が生成される

oratakeoratake

Claspをwrapしているasideというのがあるので、そちらに乗り換え
https://github.com/google/aside
デフォでTSになっていたり、Jestのテストが入っていたりとモリモリになっている。
これはGoogleAppsScript向けの型定義をちゃんと調べねばならなさそう。
doPostのeventですらこう

const doPost = (e: GoogleAppsScript.Events.DoPost) => { /** hogePiyo */ }
oratakeoratake

Slack App側で設定しておくところ
https://api.slack.com/apps/
上記の作成したアプリ内で設定。どちらもFeatures内。

  • Event Subscriptions
    Slackのイベント(投稿、ユーザ作成、チャンネル作成など)を送ってもらうURLの設定
  • Incoming Webhooks
    Add New Webhooks to WorkspaceってとこでSlackにつぶやくためのWebhook URLがある
oratakeoratake

doPostでSlackのeventを受けるとき

最初にSlackのEvent Subscriptionが本当に所有しているかの確認のためにchallengeリクエストを飛ばすので、それの反応をさせる

const doPost = (e: GoogleAppsScript.Events.DoPost) => {
  const params = e.parameters;

  // SlackのEvent SubscriptionsでURL確認のためのリクエスト用
  if('challenge' in params){
    return ContentService.createTextOutput(params.challenge);
  }
  // 省略...

とおもったらparamsの型で.challengeとかねぇぞって怒られたので、
slack apiとかの型定義っぽいものを入れてみる。本当にこれであってるかは不明

$ npm i -D @slack/web-api @slack/webhook

型についてはslackのsdkのドキュメントがあったのでこれで
https://slack.dev/node-slack-sdk/typescript

で、これに対して何を返せばいいかはこちら
https://api.slack.com/apis/connections/events-api#challenge
レスポンスでオウム返しすればよいっぽい

ContentService.createTextOutput(params.challenge);
これが何を返しているかだが、ContentServiceクラスはGASのクラス。似てるものにHtmlServiceもある。
https://iwb.jp/google-apps-script-contentservice-htmlservice/
ただ、今回だとJSONを返してもよいっぽいのでJSONを返す。
JSONで返す参考
https://qiita.com/tfuruya/items/3c306ee03d1ac290bcef

二転三転してplaneテキストを返すように。あとe.parametersの挙動があまりよくなさそう。どこかでlogを表示したい。Logger.logではGASの実行からは確認できなかった。スプシーに吐き出そうかな
https://zenn.dev/shuuuuuun/articles/9de830ec37de30

oratakeoratake

コンパイル時にdoPostとかは使ってないのに書くんじゃねぇとeslintに怒られるので回避
一旦はアンダーバーを不可した場合無視とした
https://madogiwa0124.hatenablog.com/entry/2022/05/07/154628

上記、GASでは_doPostではPOST時にフックしなかったので、doPostに戻す。
代わりにeslintの設定を一瞬相殺するコメントを使う

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const doPost = (e) => { /** 処理 */ }
oratakeoratake

Could not read API credentials. Are you logged in globally?

npm run deployで上記。多分プロジェクト直下にしか .clasprcが無いからと思われる
homeにも.clasprcを置く

oratakeoratake

doPostだけ置いておくと使われていないfunctionだと怒られるので、eslintの除外用にコメントを置く

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const doPost = (e: GoogleAppsScript.Events.DoPost) => { /** 処理 */ }
oratakeoratake

Github Actions でデプロイ にpushする参考
https://zenn.dev/yumechi/articles/5dd115924f81bc

デプロイについてはこちらが参考になりそう
GASはデプロイの度にIDが変わるので、IDを変えずにデプロイするためにオプション付きでdeployする必要がある
https://www.aruse.net/entry/2022/10/09/130019

$ clasp deploy --deploymentId <ここにデプロイID>

このデプロイIDについてはSecretsにいれておいてActionsでCDするようにできるかも。

oratakeoratake

ログを吐かせるときにスプシーに吐かせるのがよさそう。
以下、event_logsというシートにタイムスタンプとLogを吐く場合。
LoggerのgetLog()でそこまでの全ログを吐くっぽい

  // スプシーにログを排出
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('event_logs');
  const timestamp = new Date();
  Logger.log(e.postData.contents);
  sheet.appendRow([timestamp, Logger.getLog()]);

https://developers.google.com/apps-script/reference/base/logger?hl=ja

oratakeoratake

Geminiにコードを吐かせたらApps Scriptのライブラリ(UrlFetchApp)とかを使うときに一旦anyで定義して回避しておくハックを出してきた
追記: 自分で導入したライブラリも行けるらしい。スクレイピング用のCheerioとか。

// Apps Scriptライブラリから提供されるグローバル変数をTypeScript用に宣言しておくと、
// 型チェックや開発時のオートコンプリートの恩恵を受けやすくなります。
// @types/cheerio のような型定義ファイルが適切に設定されていれば不要な場合もありますが、
// Apps Scriptのグローバルライブラリに対しては 'any' 型で宣言するのが安全な場合があります。
declare const Cheerio: any; // Cheerioライブラリ用の型宣言
declare const UrlFetchApp: any; // UrlFetchAppはApps Scriptの組み込みサービス

追記: UrlFetchAppはaside導入時に入る? @types/google-apps-script google-apps-script.url-fetch.d.ts ですでに詳細の定義があるっぽい
今日時点でのpackage.jsonのdependenciesは "@types/google-apps-script": "^1.0.97",

oratakeoratake

ライブラリを導入した場合、aside(clasp)の自動デプロイで消滅することがあるので、appsscript.jsonで定義しておく。
以下、CheerioとParserの例
versionについてはわからなかったのでGASでライブラリを検索したあと最新のものを拾って入れた。

appsscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
    "libraries": [
      {
        "userSymbol": "Cheerio",
        "version": "16",
        "libraryId": "1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0",
        "developmentMode": true
      },
      {
        "userSymbol": "Parser",
        "version": "8",
        "libraryId": "1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw",
        "developmentMode": true
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}