タイトル長い

くっそ長いタイトルで恐縮ですが、Google Apps Script(GAS)とAPI FLASHとSlackAPIをClaspとJestとGitHub Actionで調理して定期的にWebページのスクリーンショットを撮っていきたいと思います。

作成したコード GitHubはこちらとなります。

GASへの詳しい設定方法は拙作コードのReadmeをご参照いただければと思います。

Google Apps Script(GAS)とは?

Google Driveをご存じでしょうか?

スプレッドシートやらプレゼンテーションと呼ばれる某ExcelやPowerPointの基本機能がWeb画面で扱えるアレです。

Google Apps Scriptはそれのマクロだと思っていただけるとイメージがつきやすいかと思います。

ExcelやPowerPointだとVBAがマクロ言語ですが、Google Apps ScriptはJavaScriptが言語です。

さすがGoogle公式言語。

またGoogle Apps Scriptのことを省略してGASとか言ったりするそうです。

ガスガス

GAS専用のマクロ用関数がある程度用意されてるのでマクロを組むのも簡単ですし、時間で関数をキックするトリガー機能もあるので、簡単なFaaS(Function as a Service)として 利用することもできます。

今回は後者の使い方が中心となります。

JavaScriptはJavaScriptなんだけどさ

さきほど書いたとおり、ほとんどGASはJavaScriptなのですが、ES6な記法ではないので、JavaScriptとは恐れ多くても言えないというのが実状です。

なので上記と合わせ素のGASを使うと下記のような悲しいことがおきます。

  • 古くさいJavaScriptを書く
  • テストできない
  • CIに乗っけられない(手デプロイ)

これは悲しいですね。

悲しい世界には美しい花が咲く

と悲しい気持ちになってるところで見つけたのがclaspです。

claspはGoogle謹製のGASデプロイツールなのですが、勇者が Claspを使ったTypeSciptテンプレートを作ってました。天才かよ。

今回はこちらのテンプレートを借りて開発を進めたいとおもいます。

API FLASH のサービス層作成

今回のGASの目的は、WEBページのスクリーンショットを撮るということですので、スクリーンショットを簡単に取得する方法としてAPI FLASHを使います、

API FLASHとは

API FLASHとは、ChromeベースのWebscreenshotAPI提供サービスです。

API FLASH自体はAWS Lambdaを使ってるらしく、スケーラビリティが高いと主張してます。

おそらく、Serverless-chromeをLambdaで実行しているものと思われます。

Chromeベースのキャプチャリングなので、レンダリングも正確で非対応ページがすくないのもうれしいところです。

さらに、うれしい機能として遅延キャプチャリング機能があり、ページのレンダリングを待ってからキャプチャを撮ることも可能です。

https://api.apiflash.com/v1/urltoimage?access_key=hoge&url=hoge&delay=10

のようなURLでHttp Getをするだけでキャプチャが撮れるんです、すごいですね。

ちなみに、無料版には1ヶ月あたりの利用制限がありますのでご注意を。

実際できあがったコード

TypeSciptでこんな感じのサービス層を作りました。

export class ApiFlashService {
  static captureWebPage = (
    url: string,
    accessKey: string,
    width: string,
    height: string,
    delay: string
  ): GoogleAppsScript.Base.Blob => {
    const captureUrl = ApiFlashService.createChaptureUrl(url, accessKey, width, height, delay);
    const responseData = UrlFetchApp.fetch(captureUrl);
    return responseData.getBlob();
  };
  private static createChaptureUrl = (
    url: string,
    accessKey: string,
    width: string,
    height: string,
    delay: string
  ): string => {
    return `https://api.apiflash.com/v1/urltoimage?access_key=${accessKey}&url=${url}&width=${width}&height=${height}&delay=${delay}&fresh=true`;
  };
}

GASの場合APIのコールにはURLFetchAppが利用できます。

UrlFetchApp.fetch(url) とするだけでAPIがコールできます。

URLFetchAppはもちろんGAS専用のAPIですが、TypeSciptのLintがちゃんと利くのが何気にすごいと思いました。

img

Slackのサービス層

コードは省略しますが上と同じ要領でURLFetchを使って、Slackコール部も作ります。

テスト

テストはJestで作ります。

ポイントは先ほどから利用しているURLFetchをMock化する必要があることです。

Jestのグローバル変数定義であらかじめURLFetchを作り、JestのMock関数をテストケースごとにfetch関数と置き換えることで実現できます。

package.jsonに

  "jest": {
    "verbose": true,
    "globals": {
      "UrlFetchApp": {}
    },
  },

とすることでグローバルにUrlFetchAppができますので、テストコードで

const mockFetch = jest.fn();
UrlFetchApp.fetch = mockFetch;

describe('sendSlackServiceOK', () => {
  it('sendImage', () => {
    const actual = SendSlackService.sendImage('test-token', 'test-image', 'test-title', '#test');
    const expectedOption = {
      method: 'post',
      payload: { token: 'test-token', file: 'test-image', channels: '#test', title: 'test-title' }
    };
    expect(mockFetch.mock.calls[0][0]).toBe('https://slack.com/api/files.upload');
    expect(mockFetch.mock.calls[0][1]).toEqual(expectedOption);
    expect(actual).toBe(true);
  });
});

とすることでfetch関数がmockに置き換わり、テスト可能です。

mock関数をあらかじめ作成しておくと、コールのassertも可能です。

Buildとdeploy

StarterではWebpackを使ってTypeSciptのGAS化を実行しているようです。

npm run build

とすることで、dist配下にGASのコードが配置されました。

デプロイは、claspを利用します。

clasp login
clasp push

とするだけでデプロイできちゃいます。

GitHub Actionで自動デプロイさせる

ここまできたらあとはCIに乗っけるだけです。

clasp loginをローカル上で実施したときに取得できるトークンがclasprc.jsonに出力されているので、こちらをGitHub ActionのSecret機能で渡してあげます。

img

あとは、testが通ったらclasp loginして、build, deployする定義をかけばよいです。

GitHub ActionのSecretにはこのようにアクセスします。

echo "${{ secrets.CLASPRC_JSON }}" > ~/.clasprc.json

完成!

GASにデプロイできたらcronでトリガーさせてあげれば定期的にキャプチャをとります。

やったー!!

無事にSlackへキャプチャが送られてきました!

Img