GASでよく使うけど忘れる系のチートシート〜外部API使う時の系〜

 

以前に、スプシ系のチートシートは作ったんですが、

GASでよく使うけど忘れる系のチートシート〜スプシ系〜

外部API使う時も毎回ググりまくるのでまとめておきます!

スクリプト プロパティ(環境変数)

// スクリプト プロパティの取得
const GITHUB_TOKEN = PropertiesService.getScriptProperties().getProperty('GITHUB_TOKEN');

ref: https://developers.google.com/apps-script/reference/properties?hl=en

API叩く時

const url = 'https://api.github.com/users/kin29/repos';
const option = {
  'method' : 'get',
  'headers': {
      'Accept': 'application/vnd.github+json',
      'Authorization': 'Bearer '+ GITHUB_TOKEN,
      'X-GitHub-Api-Version': '2022-11-28'
  }
};
const response = UrlFetchApp.fetch(url, option).getContentText();
// JSON.parse(response)するとobjectになって使いやすい

ref: https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app

 

Heroku + SendGrid → GAS + Gmail に移行したよ

 

2022/11/28にHerokuのフリープランがなくなるので、
Heroku + SendGridで作っていたものを、GAS + Gmailに移行しました。

実際に移行したのは、過去記事にもまとめたSlackコマンドです。

slackコマンドを作ってみた!

Heroku + SendGrid → GAS + Gmail対応のdiff:
https://github.com/kin29/slack2mail/compare/5ead7fc8f0bccd368c99dd5215e507bd78e38887…2e8bb4af713276237cb3c9a03c33d73be8bbc8bc

GASでGmailを送信する

const toAddress = 'to@example.com';
const title = 'メール件名';
const content = 'メール内容';
const options = {
  'from': 'from@example.com',
};
GmailApp.sendEmail(toAddress, title, content, options);

claspを使って、実装済みのGASをcloneする

$ clasp clone [scriptId]

さいごに

もう1つHerokuのフリープランを使って動かしているアプリがあるので、コレも早く対応したいところではあります…

GASのテスト(Jest)を書く!!!

 

GASを書いていて、テストを書きたくなる時って結構ありませんか?
ただ、なかなか手をつけることができずにテストを書かずにおいてしまい罪悪感が積もりに積もったので、ついにテストを書きました 🎉🍾✨
そのことについてまとめます❕
(TypeScriptはまだまだ勉強中でエセなのでご了承ください🙇)

もくじ

 

準備🎒

npm install -D clasp @types/google-apps-script

$ npm install -D clasp @types/google-apps-script

tsconfig.jsonを追加

$ tsc -init
//tsconfig.json
{
  "compilerOptions": {
    "lib": ["esnext"],
    "experimentalDecorators": true
  }
}

npx clasp create --type standalone --rootDir src

$ npx clasp create --type standalone --rootDir src

// src/appsscript.json と .clasp.json が生成される

// src/appsscript.json
{
  "timeZone": "America/New_York",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

// .clasp.json
{"scriptId":"xxx","rootDir":"src"}

テストコードはclasp pushする必要がないためsrc/以下にappsscript.jsonを生成しました。

//構成
.
├── README.md
├── node_modules/
│
├── src    ←clasp pushの対象ルートディレクトリ 
│   ├── _utils.ts
│   ├── appsscript.json
│   └── code.ts
│
├── tests ←テストコードのディレクトリ(clasp pushしない)
│   ├── _utils.test.ts
│   ├── date-mock.ts
│   └── gmail-message-mock.ts
│
├── jest.config.js
├── package.json
├── tsconfig.json
└── .clasp.json

npm install -D jest "@types/jest" ts-jest typescript

$ npm install -D jest "@types/jest" ts-jest typescript

jest.config.jsを追加

$ jest --init

参考: https://typescript-jp.gitbook.io/deep-dive/intro-1/jest#suteppu2jestwosuru

//jest.config.js
module.exports = {
  "roots": [
    "<rootDir>"
  ],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
}

tsconfig.jsonにesModuleInterop=trueを追加

以下のwarningがでたため、言われるがままに”esModuleInterop”: trueを追加しました。

ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`)
//tsconfig.json
{
  "compilerOptions": {
    "lib": ["esnext"],
    "experimentalDecorators": true,
+   "esModuleInterop": true,  //追加
  }
}

src/appsscript.jsonのtimeZoneを”Asia/Tokyo”に修正

//src/appsscript.json
{
- "timeZone": "America/New_York",
+ "timeZone": "Asia/Tokyo",
  ...
}

npm testでテスト実行できるようにする

//package.json
{
  ....
+ "scripts": {
+   "test": "jest"
+ }
}

テストを書く📊

GmailMessageのモックを作る

テストできるように、インターフェースGmailMessageを実装してモック(GmailMessageMock)を作成しました。

tests/gmail-message-mock.ts

import GmailMessage = GoogleAppsScript.Gmail.GmailMessage;

export class GmailMessageMock implements GmailMessage {
    constructor(
        private body: string,
    ) {
    }

    getBody(): string {
        return this.body;
    }
    ...
}

tests/_utils.test.ts

import { Utils } from "../src/_utils";
import { GmailMessageMock } from "./gmail-message-mock";

describe('Utils.createBodyMessage', () => {
    test('[body]xxxを生成できること', () => {
        const inputGmailMessage = new GmailMessageMock('テストメッセージです');

        const util = new Utils();
        const actual = util.createBodyMessage(inputGmailMessage);
        expect(actual).toBe('[body]テストメッセージです');
    })
})

src/_utils.ts

export class Utils {
    public createBodyMessage(gmailMessage: GoogleAppsScript.Gmail.GmailMessage): string {
        return "[body]" + gmailMessage.getBody();
    }
}

ついに、テストできるようになりましたー🎉🎉🎉

$ npm test

> test
> jest

 PASS  tests/_utils.test.ts
  Utils.createBodyMessage
    ✓ [body]xxxxを生成できること (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.657 s
Ran all test suites.

メモ📝

– GASではimportができないので、読み込み順序をいい感じにするために先に読み込まれる必要があるファイルについては_hoge.tsのようにアンダーバーから始まるファイル名にしました🚨
– clasp pushされるとcode.ts→code.gsに変換されます。

参考にした記事🙇

https://developers.google.com/apps-script/guides/clasp
– https://kotamat.com/post/gas-testing/

最後に

詳細が気になる方は、https://github.com/kin29/line-notify-from-gmailにコミット履歴があるので参考にしてみてください。

(最初yarn使ってたのですが、npmだけでいけそうなので最後にyarn.lockを消したりしてます)

 

GASでよく使うけど忘れる系のチートシート〜スプシ系〜

GASでよく使うけど忘れるコードを備忘録として残しておきます📝

スプシ取得系

  • 指定セルの中身を取得
  • 指定列(縦)のすべての中身を取得
  • 最後の行を取得
  • 最後の列を取得
//指定セル(シート1のA1)の中身を取得
let value = SpreadSheetObj.getSheetByName('シート1').getRange('A1').getValue();

//指定列(シート1のB列)のすべての中身を取得
let values = SpreadSheetObj.getSheetByName('シート1').getRange("B:B").getValues();

//(シート1の)最後の行を取得
let lastRow = SpreadSheetObj.getSheetByName('シート1').getLastRow();

//(シート1の)最後の列を取得
let lastCol = SpreadSheetObj.getSheetByName('シート1').getLastColumn();

スプシ書き込み系

const OUTPUT_SHEET_ID = 'スプシID'
const datas = ['A列に入れたい文字', 'B列に入れたい文字', 'C列に入れたい文字'];

var SpreadSheetObj = SpreadsheetApp.openById(OUTPUT_SHEET_ID); //アウトプットするシート

//シート 1の最終行の挿入
SpreadSheetObj.getSheetByName('シート1').appendRow([datas[0], datas[1], datas[2]]);

//更新
SpreadsheetApp.flush();

スプシクリア

const targetSheet = SpreadsheetApp.openById(OUTPUT_SHEET_ID).getSheetByName(sheetName);
var deleteRowCount = targetSheet.getLastRow() - 1;
if (deleteRowCount > 0) {
  targetSheet
  .getRange(startRow, startColumn, deleteRowCount, deleteColumnCount)
  .clear();
}

公式リファレンス:
https://developers.google.com/apps-script/reference/spreadsheet/

GASのconsole.logとLogger.logの違いって何だ?

 

GoogleAppScript(GAS)のconsole.logとLogger.logの違いを検証します!

エディターから実行した時

→ 特に違いは分かりませんでした 🤔

 

トリガーで実行した時

→ Cloud のログを見るとログレベルに違いがありました

console.log→デバック(DEBUG)、Logger.log→情報(INFO)

 

リファレンスで比較

▷Class Logger

https://developers.google.com/apps-script/reference/base/logger#log(Object)

Logger.getLog()で今までのログを取得できる!!!(clearもできる)

(ログレベルはINFO)

 

▷Class console

https://developers.google.com/apps-script/reference/base/console

ログレベルを使い分けれる!!!(日本語なのチョットキモイ)
– 🔴ERROR(エラー)     : console.error()
– 🟠WARNING(警告)  : console.warn()
– INFO(情報)                : console.info()
– DEBUG(情報)            : console.log()

console.info()とconsole.log()は違いがわからない🤔

 

 

GASでGET,POSTを受け取る方法

久々のGASについてです!

個人的に作りたいアプリが浮かんだので、それを作るためにGASを使うことがあったのでおさらいがてら書きます✏️

参照:https://developers.google.com/apps-script/guides/web

やることはたった3つです!!!

  1. GET,POSTを受け取る関数を書く
  2. 公開する
  3. 動作確認してみる

1. GET,POSTを受け取る関数を書く

引数eでクエリストリングなどの詳細も取得できますー🌱
GETメソッドを受け取る場合

function doGet(e) {
  console.log('getされました');
}

POSTメソッドを受け取る場合

function doPost(e) {
  console.log('postされました');
}

2. 公開する

これをしないと、実際にこのGASにアクセスするURLが発行されません💦

  • ファイル > 版を管理…より説明文を適当に書いてバージョンをSave new version→OKします。
    (最初はバージョン1で保存されると思います。)
  • 公開 > ウェブアプリケーションとして導入 より、project versionを↑で保存したバージョンに、
    Execute the app as(アプリの実行方法)、Who has access to the app(アプリにアクセスできるユーザー)を選択し、「更新」を押下します。
  • Current web app URLが発行されます🚀
    このURLにリクエストすることで、1で作った関数が実行されるようになります。

3. 動作確認してみる

Talend API Testerを使って、リクエストしてみます。
「METHOD」を選んで、2で発行されたURLを貼り付けて、「Send」するだけです。
200が返ってきたので、いい感じそうです🍻

GASのログも確認してみましょう👀(表示 > ログ > App Scriptダッシュボード)

まとめ

個人的に、
Execute the app as(アプリの実行方法)、Who has access to the app(アプリにアクセスできるユーザー)の設定どうするかが気になりました。
個人で使うものであれば、「自分だけ(only myself)」がアクセスできて、「自分(me)」が実行でいいかなと思います。

GASのユニットテストを書いてみる

お久しぶりです。
ネタが尽きてきたのと、サボってました…

名古屋にきて、名古屋飛ばしはあるものの
宮崎にいた時よりも行きたいアーティストのライブがありふれていて金欠の日々です。
(適度な副業あれば、ください^^)

 

今回の本題に入ります!

7月にテストをきちんと書いているとてもいい会社に転職しました。
フロントエンドでもきちんとテストに向き合っていて刺激的な毎日です。

そこで、わたしの大好きなGASでもテストかけないかなーってふいに思い、
ググってみると、、、、ある!!!

 

その名は、

\  QUnit for Google Apps Script  /

QUnit for Google Apps Script is an experimental fork of QUnit that enables developers to test their Google Apps Script code.

QUnitの実験的なGAS用ユニットテストのようです。
そもそも、QUnitを知らない不束者です…

 

使い方

参考:https://qiita.com/yooo_gooo/items/07c9513a87f6633e40c1

一. QUnitライブラリを追加

qunit/gas/README.md を参考にしてみます。

  1. Select “Resources” > “Manage Libraries…” in the Google Apps Script editor.
  2. Enter the project key (MxL38OxqIK-B73jyDTvCe-OBao7QLBR4j) in the “Find a Library” field, and choose “Select”.
  3. Select the highest version number, and choose QUnit as the identifier. (Do not turn on Development Mode unless you know what you are doing. The development version may not work.)
  4. Press Save. You can now use the QUnit library when writing your tests. You can see a list of available functions by typing QUnit followed by a dot. Alternatively, read the API docs.
  1. スクリプトエディタを開いて、リソース>ライブラリを選択する。
  2. ライブラリを追加の欄に、「MxL38OxqIK-B73jyDTvCe-OBao7QLBR4j」を入れて追加ボタンを押す
  3. QUnitの一番大きいバージョンを選択する。(詳しくない限り、デベロッッパーモードは有効にしない) ※今回はバージョン4を選びました。
  4. 保存ボタンを押下する。これでQunitライブラリを使えるようになりました。使用可能なQunit関数は API docs.を読んで。

QUniyライブラリの追加

 

 

二. テストを書く

README通り、 API docs.>QUnit (version 4) を参考にしました。

function doGet( e ) {
  QUnit.urlParams( e.parameter );
  QUnit.config({ title: "Unit tests for my project" });
  QUnit.load( isPublicHolidayTest );
  
  return QUnit.getHtml();
};

QUnit.helpers(this);

 

/**
 * テストコード
 */
function isPublicHolidayTest() {
  test('isPublicHoliday Test', function(assert) {
    var date = '2019-08-11';
    assert.ok(isPublicHoliday(new Date(date)), date +' は休日');

    var date = '2019-08-20';
    assert.ok(!isPublicHoliday(new Date(date)), date +' は休日ではない');

    var date = '2019-01-01';
    assert.ok(isPublicHoliday(new Date(date)), date +' は休日');
  });
}

 

/**
 * テスト対象の関数
 * (祝日であればtrue、そうでなければfalseを返す)
 */
function isPublicHoliday(data){
  //日本の祝日のカレンダーID
  var holidayCalId = "ja.japanese#holiday@group.v.calendar.google.com";
  var holidayCal = CalendarApp.getCalendarById(holidayCalId);

  return holidayCal.getEventsForDay(data).length > 0
}

 

▼assert.ok(value[, message])

を使ったことがなかったんですが、valueがtrueであれば「okay(message)」をechoするっぽいです。

assert.ok(true);                            //okay
assert.ok(true, 'okokok!!!');      //okokok!!!

参考:https://nodejs.org/api/assert.html#assert_assert_ok_value_message

 

 

三. テストを実行

スクリプトエディタの
公開 > ウェブアプリケーションとして導入 > 「最新のコードでテスト」のリンク
へ飛ぶとテストが実行され、結果がみれます。
※アプリケーションにアクセスできるユーザー:「自分だけ」にできます。

QUnitのテスト結果

 

 

感想

うーん、clasp使って、CIでテストさせたいですよねー
毎回、スクリプトエディタでテスト実行するのは面倒 😄

フロントのテストって書いたことがないのですが、
GASだったらセル依存となると思うので、テストを書くの難しそうだなあと感じました。。。

GASの「起動時間の最大値を超えました」の壁を超えてみる!!!

 

新玉ねぎっておいしいですよね。甘くって。
ただ、生で食べる時は、まあまあ薄くきらないと苦くって悲しくなりました。

今回は、みんな大好きGASの
「最大起動時間の壁を超える!」
の巻です。

 

GASの起動時間の最大値は?

リファレンスより、

「Consumer(多分無課金の人)のScript runtime」は 6minのようですね。

 

つまり、

一回の処理が6分を超えると、
「起動時間の最大値を超えました」
と表示され処理が途中で終わってしまいます。
(そもそも、6分以上の処理させんなよって感じなんですが、、、)

ググったら、回避方法を書いてくれてる方がおり、とても助かりました。
おかげで、わたしのバカ長い処理も何回かに分けて、
最後まで実行完了することができるようになりました。

参考:http://direction-note.com/gas-beyond-execution-time/

やり方

処理が5分を超える場合は、
トリガーを設定して、1分後に処理を続きからするようにする。

 

①for文のカウントなどを保持し、正しく続きから処理を再開するために、
GASのスクリプトプロパティでデータを保持させます。

スクリプトのプロパティとは?

ファイル > プロジェクトのプロパティ > スクリプトのプロパティ
にあり、キーバリューでデータを保持できます。

スクリプトのプロパティ

↑ キー:nowRow、バリュー:10のデータを保持させている。
この画面の「行を追加」ボタンよりデータを追加/削除することが可能。

 

GASにて追加することも可能

let properties = PropertiesService.getScriptProperties();

//スクリプトプロパティの登録
properties.setProperty(key, value);

//スクリプトプロパティのバリューを取得
properties.getProperty(key); //return value

 

 

②最大処理時間を超える前の5分で処理を中断し、
1分後にトリガーを設定する。

let properties = PropertiesService.getScriptProperties();
let targetArray = ['foo', 'bar', 'baz'....];  //この配列が膨大量の時に有効

/* メイン */
function mainSort() {  
  ...
  setOutputSheet();
}

/* 5分以上かかる可能性のある処理 */
function setOutputSheet(){
  let startTime = new Date();
  //スクリプトプロパティにトリガーIDを保存するときに使用するkey名
  let triggerKey = "trigger";  
  ...
  for(let i = 0; i < targetArray.length; i++){
    //開始時刻(startTime)と現時点の処理時点の時間を比較する 
    let diff = parseInt((new Date() - startTime) / (1000 * 60));
    if(diff >= 5){
    ...
      //トリガー(1分後)を登録する
      setTrigger(triggerKey, "mainSort");
      return;
    }

    //todo 5分以上かかる可能性のある処理
  }   
  
  //全て実行終えたらトリガー不要なためを削除する
  deleteTrigger(triggerKey);
}


/**
 *指定したkeyに保存されているトリガーIDを使って、トリガーを削除する
 *
 * @param string triggerKey
 * @return void
 */
function deleteTrigger(triggerKey) {
  let triggerId = properties.getProperty(triggerKey);
  if(!triggerId) return;
  
  ScriptApp.getProjectTriggers().filter(function(trigger){
    return trigger.getUniqueId() == triggerId;
  })
  .forEach(function(trigger) {
      ScriptApp.deleteTrigger(trigger);
  });
  properties.deleteProperty(triggerKey);
}

/**
 * トリガーを発行する
 *
 * @param string triggerKey
 * @param string funcName
 * @return void
 */
function setTrigger(triggerKey, funcName){
  //既に同名で保存しているトリガーがあったら削除
  deleteTrigger(triggerKey);
  
  //1分後にトリガーを登録する
  let date = new Date();
  date.setMinutes(date.getMinutes() + 1);
  let triggerId = ScriptApp.newTrigger(funcName).timeBased().at(date).create().getUniqueId();
  Logger.log('setTrigger function_name "%s".', funcName);
  
  //あとでトリガーを削除するために「スクリプトのプロパティ」にトリガーIDを保存しておく
  properties.setProperty(triggerKey, triggerId);
}

 

作ってみたやつ

①②を組み合わせると、最大起動時間の壁を超えれるということです!
作ってみた全体はこんな感じです。
※このコード自体の処理は5分超えなかったのでsleep()とかして試してましたっw

 

こんな感じの横並びを、、、

縦にソートします。

 

プレーヤーが日々追加されることを想定して、
一年の日数 × 最終的なプレーヤー人数分で縦に並ぶように作ってます。

 

コード全体(テキトーなので参考程度で! )

let properties = PropertiesService.getScriptProperties();

//★メイン
function mainSort() {
  
  //プレーヤーマスター配列を作成
  let playerMaster = getPlayerMaster();
  let playerCount = Object.keys(playerMaster).length
  
  //日付の配列を取得
  let dataMaster = getDataMaster();
  let dataCount = Object.keys(dataMaster).length
  
  //日別結果データの取得
  let DayDate = getDayDate(playerCount, dataCount);
  
  //出力用シート(outputSheet)に日別結果の縦版の値を出力する。
  setOutputSheet(playerMaster, dataMaster, DayDate);
}

/**
 * 「No」「プレーヤー」を配列で取得する
 * 
 * @return arrShopMaster = {
 *   1: 'リュウ',
 *   2: 'ケン'
 * };
 */
function getPlayerMaster() {
  let ABcolumnAll = inputSheet.getRange('A2:B101').getValues();
  let LastRow = ABcolumnAll.filter(String).length;
  let arrPlayerMaster = [];
  
  //「-」を除く
  for(let i = 0; i < LastRow; i++){
    if(!ABcolumnAll[i][1].match(/\-/)){
      arrPlayerMaster[ABcolumnAll[i][0]] = ABcolumnAll[i][1]; 
    }  
  }

  return arrPlayerMaster;
}

/**
 * 日付を配列で取得する
 * 
 * @return array arrDataMaster
 */
function getDataMaster() {
  let rowAll = inputSheet.getRange('C1:ND1').getValues();
  let arrDataMaster = [];
  
  for(let i = 0; i < yearSum; i++){
    arrDataMaster[i] = rowAll[0][i];  
  }

  return arrDataMaster;
}

/**
 * 日付をキーにその日の結果を格納する。
 *
 * @param intger playerCount
 * @param intger dataCount
 * @return array arrRet
 */
function getDayDate(playerCount, dataCount)
{
  let col = inputSheet.getRange('C2:NQ'+(playerCount+1)).getValues();

  let arrRet = [];
  for(let i = 0; i < playerCount; i++){
    arrRet[i] = [];
    for(let j = 0; j < dataCount; j++){
      arrRet[i][j] = col[i][j];
    }
  }
  return arrRet;
}

/**
 * 5分以上かかる可能性のある処理
 *
 * @return void
 */
function setOutputSheet(playerMaster, dataMaster, DayDate){
  //処理の開始時刻を取得
  let startTime = new Date();
  //sleep(300000); //意図的に(300秒/60秒=)5分スリープさせる
  
  //途中経過保存用の変数
  let rowKey = "nowRow";   //何行目まで処理したかを保存するときに使用するkey
  let dateKey = "nowDate";  //yearSumが何行目まで処理したかを保存するkey
  let playerCountKey = "nowPlayerCount"; //lastRowが何行目まで処理したかを保存するkey
  let triggerKey = "trigger";  //トリガーIDを保存するときに使用するkey
  
  let nowRow = parseInt(properties.getProperty(rowKey));
  if(!nowRow) nowRow = 1;  //初回処理の時
  
  let lastRow = Object.keys(DayDate).length;

  let now_i = 0;
  let nowDate = parseInt(properties.getProperty(dateKey));
  if(nowDate){ 
    let now_i = nowDate;
    properties.deleteProperty(dateKey);
  }
  for(let i = now_i; i < yearSum; i++){
    let date = dataMaster[i];
    
    let now_j = 0;
    let nowPlayerCount = parseInt(properties.getProperty(playerCountKey));
    if(nowPlayerCount){ 
      let now_j = nowPlayerCount;
      properties.deleteProperty(playerCountKey);
    }
    for(let j = now_j; j < lastRow; j++) { 
        //開始時刻(startTime)と現時点の処理時点の時間を比較する
        let diff = parseInt((new Date() - startTime) / (1000 * 60));     
        if(diff >= 5){
        //何行まで処理したかなどを「スクリプトのプリロパティ」に保存する
        properties.setProperty(rowKey, nowRow);
        properties.setProperty(dateKey, i);
        properties.setProperty(playerCountKey, j);
        //トリガー(1分後)を登録する
        setTrigger(triggerKey, "mainSort");
        return;
      }

      outputSheet.getRange(nowRow, 1).setValue(date);
      outputSheet.getRange(nowRow, 2).setValue(playerMaster[j+1]);
      outputSheet.getRange(nowRow, 3).setValue(DayDate[j][i]);
      nowRow++;
    }
  }
  
  //全て実行終えたらトリガーや何行目まで実行したかなどのデータは
  //不要なためを削除する
  deleteTrigger(triggerKey);
  properties.deleteProperty(rowKey);
  properties.deleteProperty(dateKey);
  properties.deleteProperty(playerCountKey);
}


/**
 * 指定したkeyに保存されているトリガーIDを使って、トリガーを削除する
 *
 * @param string triggerKey
 * @return void
 */
function deleteTrigger(triggerKey) {
  let triggerId = properties.getProperty(triggerKey);
  if(!triggerId) return;
  
  ScriptApp.getProjectTriggers().filter(function(trigger){
    return trigger.getUniqueId() == triggerId;
  })
  .forEach(function(trigger) {
      ScriptApp.deleteTrigger(trigger);
  });
  properties.deleteProperty(triggerKey);
}

/**
 * トリガーを発行。トリガーを発行した箇所から
 *
 * @param string triggerKey
 * @param string funcName
 * @return void
 */
function setTrigger(triggerKey, funcName){
  //既に同じ名前で保存しているトリガーがあったら削除
  deleteTrigger(triggerKey);
  
  let date = new Date();
  date.setMinutes(date.getMinutes() + 1); //1分後に再実行
  
  let triggerId = ScriptApp.newTrigger(funcName).timeBased().at(date).create().getUniqueId();
  Logger.log('setTrigger function_name "%s".', funcName);
  
  //あとでトリガーを削除するために「スクリプトのプロパティ」にトリガーIDを保存しておくproperties.setProperty(triggerKey, triggerId);
}

claspでGAS(GoogleAppsScript)ファイルをGit管理する。

突然ですが、
私、ガチャガチャが好きなんです。
25才の今でも、ガンガンやっちゃってます。
最近の一押しはワニワニパニックとこれです。

 

はい、本題に入ります。

最近、社内ツールをGASで作ることが多かったり、
私自身slackのbotはGASで作ることが多いため、
Git管理をしたかったので方法を探しておりました。
そこで、この記事を参考にGit管理ができたので、書きます!
https://qiita.com/rf_p/items/7492375ddd684ba734f8

 
下記リポジトリはgasで作ったLINEbotです。
kin29/linebot_calendar
このソースも GAS + clasp + Gitをつかって管理しています。

 

使うもの

– GASファイル(プロジェクト)
– clasp
– Gitリポジトリ(GithubでもBitbucketでもなんでもok!)

 

claspとは?

Googleドライブ上にあるGoogleAppScriptなどのファイルを
コマンド操作でファイルの変更や保存などがブラウザでなく、
ローカル側で行うことができるものです。
GASって普通はGoogleドライブからブラウザ上でしかソースを書けないと思っていました。
claspを使うとエディターを使って、
コード整形とかも簡単にできるので超便利です。
参考:https://qiita.com/HeRo/items/4e65dcc82783b2766c03

 

0.claspコマンドの導入

npm i @google/clasp -g

 

1.clasp login

https://script.google.com/home/usersettings
にアクセスし、Google Apps Script APIをオンに。
これで該当アカウントのGASプロジェクトをclaspから操作が可能になります。
そして、ターミナルでclasp loginと打ちます。
すると、ブラウザが開いて許可しますかてきな質問が出てくるのでokすると成功したよメッセージがでてくればログイン完了です

$ clasp login
No credentials given, continuing with default...
🔑  Authorize clasp by visiting this url:
https://accounts.google.com/o/oauth2/v2/auth?access_type=XXXXXXXXXXXX...

Authorization successful.
Default credentials saved to: ~/.clasprc.json

 

2.既にあるGASファイルをローカルでpullする

$ mkdir [ファルダ名]  //このフォルダ以下をGit管理します。
$ cd [フォルダ名]
$ clasp clone [スクリプトID]
Cloned 2 files.
└─ コード.js
└─ appsscript.json

スクリプトIDは、 Git管理したいGASファイルを開いて、
ファイル>プロジェクトのプロパティ>情報の「スクリプト ID」に書いてます。

$ vi .clasp.json
{"scriptId":"[スクリプトID]"}

↑をしないと、複数GASプロジェクトが存在していると、
clasp pullした時にどのプロジェクトをpullしてくるかわからないので、
予期しないプロジェクトをpullしてきたりするのでした方がいいです!

 

3.Gitにファーストコミットする

リモートリポジトリは、GithubでもBitbucketでもなんでもokです。

$ git init
$ vi .gitignore //.clasp.jsonはGit管理外にする。
$ git status
$ git add -A
$ git commit -m 'first commit'
$ git remote add origin [リポジトリURL]
$ git push -u origin master

 

4-1.GoogleドライブよりGASファイルを更新後、ローカルにpullする

Googleドライブでコード変更した時は、ローカルにpullして同期します。

$ clasp pull

.clasp.jsonのスクリプトIDに基づくGASプロジェクトをpullしてきます。

 

4-2.ローカルよりGASファイルを更新後、ブラウザ側にpushする

ローカルでコード変更した時は、Googleドライブにpushして同期します。

$ clasp push

.clasp.jsonのスクリプトIDに基づくGASプロジェクトにpushします。

 

Gitで変更分をコミットする。

$ git add -A
$ git commit -m 'バグ修正'
$ git push origin [ブランチ名]

こんな感じです。
ちょっと面倒ですが、Git管理できるのは良きです。