GAS(Google App Script)の備忘録(その1・サーバーとして利用)

GASは「Googleアカウント」と「インターネット接続環境」があれば利用できます
*「JavaScript」の基本的な知識が必要です

まずは、サーバーとして利用させていただきます😊

目次
  1. 基本的なこと
  2. UrlFetchAppクラスとHTTPResponseクラス
  3. Webアプリとして公開
    1. コンテンツサービス
    2. HTMLサービス
      1. HtmlOutputクラス
      2. HtmlTemplateクラス
      3. google.script.run
  4. トリガー

基本的なこと

  • GASは開発環境(サーバーやエディタ)の準備が不要です
  • GASから(Googleのサーバー側から)「Web上のリソース(データ)をURLフェッチ(取得)」したり、「JSONなどの形式でテキストを提供」したり、「HTMLファイルを返却」したりできます
  • 様々なサービス(Gmail・スプレッドシート・Googleマップ・Googleドライブ・Googleカレンダー・Googleドキュメント・Googleフォーム・Google翻訳など)と連携できます
  • 様々なトリガー(自動で実行するための機能)を設定できます
  • 「GASで参照・編集したいファイル」はGoogleドライブ上に保存する必要があります(直接ローカルドライブ上に置いたファイルを参照できません)
  • スクリプトの実行時間は1回当たり最大6分(カスタム関数の1処理あたり最大30秒)と上限があります
  • トリガーには「Simple Triggers」と「Installable Triggers」があります
    • 「Simple Triggers」はスクリプトエディタに設定(自分で実行内容を設定)
    • 「Installable Triggers」はGASのスクリプトの左上時計マークから設定(サービス毎に既にあるトリガーを選択)

一部のGASの機能には1日の割り当てと制限があります
制限を超えると、例外を投げて実行が停止します

GASの利用方法
スタンドアロン(単独のGAS)とコンテナバインド(サービスに紐付くGAS)

スタンドアロンは、GoogleドライブからGASを新規ファイルとして開きます
この場合、例えばスプレッドシートを開く際は、IDや名前を指定する必要がありスプレッドシート側からGASの実行はできません
*IDについて:Googleドライブ上のファイルにはユニークなIDが付与されます
URLのd/のあとの部分「https://…………../d/{ID}/」

GoogleドライブからGASを選択

コンテナバインドは、紐付けるサービスの「メニューから」GASのスクリプトエディタを開きます(主にスプレッドシートから開くことが多いです)
スタンドアロンで使用できないコマンドが使えます
*Googleドライブ上では紐ずくサービスが表示されます(GASファイルとして表示されません)

スプレッドシートからGASを選択

アクセス権限の承認

権限を確認→アカウントを選択して→詳細→プロジェクトに移動→許可をクリック

UrlFetchAppクラスとHTTPResponseクラス

リクエストを送り、レスポンスを受け取ります

備考:制限
URLフェッチの呼び出し 20,000 /1日
レスポンスサイズ 50MB /1回

大前提:リクエストとレスポンスは文字列です(JSONも文字列です)

  • JSON.stringify(Obj)
    「JavaScriptのオブジェクト」を「JSON文字列」に変換します
  • JSON.parse(ObjStr)
    「JSON文字列」を「JavaScriptオブジェクト」に変換します
UrlFetchApp.fetch(url, paramsObj)

URLを取得する
UrlFetchAppクラスの「fetchメソッド」でリクエストを送り
レスポンスに対してHTTPResponseクラスの「getContentText()」で文字列としてエンコードされたHTTPのコンテンツを取得します
*レスポンスからバイナリコンテンツ(画像など)を取得する時は「getContent()」を使用します

function myFunction() {
const url = "http://www.google.com/"
const res = UrlFetchApp.fetch(url);
//レスポンスの文字列
console.log(res.getContentText());
}

POSTリクエストを行います(第二引数にオプション(オブジェクト)を設定)
「Content-type」には通常「application/json(jsonフォーマット)」もしくは「application/x-www-form-urlencoded(HTMLのフォームの送信形式と同じ)」をHTTPヘッダーに指定します

function myFunction() {
//POSTリクエストの内容
const data = {
  'body': 'hoge',
};
const options = {
  'method' : 'post',
  'contentType': 'application/json',
  //POSTは JSON文字列に変換
  'payload' : JSON.stringify(data)
};
const res = UrlFetchApp.fetch('https://httpbin.org/post', options);
//ヘッダーの属性のマップを取得
console.log(res.getContentText());
}

余談
「https://httpbin.org/」はHTTPリクエストしたヘッダー情報の確認が簡単にできるサービスです(自分が送ったパラメータやヘッダー情報を返却してくれます)

GASで設定した何らかのトリガーが実行された時に「LINE Notify」へリクエストを送り、LINEで通知を受け取ります
*「LINE Notify」はWebサービスからの通知をLINEで受信することが出来るサービスです

  1. 自分だけのLINEグループを作り
  2. LINE Notifyのサイトでトークンを取得(右上マイページから任意のトークン名を入力、一覧に表示されているトークルームから通知を送ってほしいグループを選択します)
  3. LINE NotifyをLINEグループに招待
  4. GAS側で下記の「sendLineMessage(メッセージ内容)」関数を実行(トリガーなどを設定)してLINE Notifyにリクエストを送ります
//送りたいメッセージを引数に渡す
function sendLineMessage(mag){
 const LINE_NOTIFY_TOKEN = "LINE Notifyのトークン "
 const LINE_NOTIFY_API = "https://notify-api.line.me/api/notify";
 const options = {
  'method' : 'post',
  'headers': { "Authorization": `Bearer ${LINE_NOTIFY_TOKEN}` },
  'payload' : {'message': mag }
  };
 UrlFetchApp.fetch(LINE_NOTIFY_API, options);
}

Webアプリとして公開

doGet(e)またはdoPost(e)」関数が「コンテンツサービスのTextOutputオブジェクト」または 「HTMLサービスのHtmlOutputオブジェクト」を返す場合はWebアプリとして公開できます

コンテンツサービス

「テキスト・XML・ JSON」 などのさまざまな形式でコンテンツを返します

function doGet() {
  return ContentService.createTextOutput('Hello, world!');
}

APIもどき(GETリクエストに対してJSONをレスポンスします)

//提供するオブジェクト
const data = {
  status : 'success',
};
function doGet(){ 
 // listデータをjsonに変換
  var output = JSON.stringify(data);
 //コンテンツのMIMEタイプ(JSON)も設定します
  return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON);
}

GETリクエストにパラメーターを含めます
「Webアプリ」としてデブロイして(アプリケーションにアクセス出来るユーザーは「全員」にします)
コピーしたURLに?param=hogeを追加してアクセスします
https://script.google.com/macros/s/{ID}/exec?param=hoge
この場合hogeと表示されます

function doGet(e) {
  var param = e.parameter.param;
  console.log(param);
  return ContentService.createTextOutput(param);
}

Google翻訳サービスを使って、英単語を日本語で返す例
https://script.google.com/macros/s/{ID}}/exec?text={英単語}&source=en&target=ja

function doGet(e) {
    // リクエストパラメータを取得する
    const p = e.parameter;
    // Google翻訳のパラメーターの部分
    //exec?text={}&source={}&target={}
    const translatedText = LanguageApp.translate(p.text, p.source, p.target);
    // レスポンスボディの作成
    const body = {}
    if (translatedText) {
        body.status = 200
        body.text = translatedText
    } else {
        body.status = 400
        body.text = "Bad Request"
    }
    var res = ContentService.createTextOutput();
    res.setMimeType(ContentService.MimeType.JSON);
    res.setContent(JSON.stringify(body));
    return res;
}
//Google翻訳
var text = LanguageApp.translate('This is a pen', 'en', 'ja');
console.log(text); //これはペンです

余談
CORS非対応のサードパーティAPI(ブラウザからのリクエストNG)に対して自分でサーバー環境を作ることなく利用する方法(ブラウザからはGASへリクエストを送ります)
GASからサードパーティAPIへ「UrlFetchAppクラス」を使ってリクエストを送り
GASで受け取った「サードパーティAPIからのレスポンス」を「コンテンツサービス」を使ってJSONをレスポンスします😅

*GASをサーバーサイドプロキシのように機能させていたアプリがエラーに😭

HTMLサービス

HtmlServiceクラス」は HTMLやテキストコンテンツを返すためのサービスです

GASの関数とやり取りできるWebページを作成できます(HTML5の一部の高度な機能は利用できません)
*コンテナバインドの場合はカスタムメニュー・ダイアログ・サイドバーなどのUI部品としても使用可能です(この場合はdoGet()特に関数は必要なく、デプロイしたりする必要もありません onOpen() 関数やopenDialog()を使用します)

HtmlOutputクラス

HtmlService.createHtmlOutput()の戻り値は「HtmlOutputクラス」です
HtmlOutputクラスから提供できるオブジェクトはサニタイズされたHTMLを返します
自動的に「IFRAMEサウンドボックスモード」で実行されます
IFRAMEサウンドボックスモードでの制限事項

function doGet() {
  return HtmlService.createHtmlOutput('<b>Hello, world!</b>');
}

スクリプトを「デプロイをテスト(常に最後に保存されたコードを実行)」にするとリロードするだけで変更が反映されます( コードと同期されます)
新しいデプロイ」では、変更するたびにテプロイする必要があります(バージョン付きデプロイ)
開発中は最初に「新しいデプロイ」で公開し、「デプロイをテスト」で取得したURLで変更を確認します
*バージョン付きデプロイはアーカイブできますが、削除することはできません

index.htmlファイル」を返します
createHtmlOutputFromFile('index')
左側にある [ファイルを追加] をクリックし HTMLファイル(ファイル名index)を追加します

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

動的にHTMLを書き換える

  • テンプレート:テンプレート内のコードは、ページが読み込まれるときに1回実行され、処理が完了するまでコンテンツはクライアントに送信されません
  • google.script.run:非同期方式で「GAS関数」を呼び出します(ブラウザ側JavaScriptで書き換えるイメージ)

HtmlTemplateクラス

備考
動的にHTMLを書き換える場合:HtmlService.createTemplateFromFile('index').evaluate()
単純なHTMLの場合: HtmlService.createHtmlOutputFromFile('index')

HtmlTemplateクラス(HTMLを動的に構築するためのテンプレートオブジェクト)では「evaluate()メソッド」はテンプレートを評価して「HtmlOutputオブジェクト」を返します
*getCode()メソッドは評価可能なJavaScriptコードの文字列を生成します

function doGet() {
//index.htmlをテンプレートに使う
  t = HtmlService.createTemplateFromFile('index'); 
  t.title = 'タイトル';
//テンプレートの実際の出力コードを確認できます
  console.log( t.getCode() ); 
  return t.evaluate()
}

テンプレートを使用した変数の受け渡し
HtmlOutputオブジェクトを返します

function doGet() {
return HtmlService.createTemplateFromFile('index').evaluate()
}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <title><?= title ?></title>
  </head>
  <body>
  <h1><?= title ?></h1>
  </body>
</html>

CSSやJavaScriptを別ファイルにする場合

function doGet(request) {
  return HtmlService.createTemplateFromFile('index')
      .evaluate();
}
function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
}
<!DOCTYPE html>
<html>
  <head>
    <?!= include('index.css'); ?>
  </head>
  <body>
    <?!= include('index.js'); ?>
  </body>
</html>
<style>
</style>
<script>
</script>

google.script.run

google.script.run
「HTMLサービスページ」が「GAS関数」を呼び出せるようにする非同期のクライアント側 「JavaScript API 」です
withSuccessHandler(function)
クライアント側のコードは「GAS関数」の完了を待たずに次に進むため、引数に「GAS関数が応答したときに実行するクライアント側のコールバック関数」を指定できます
「GAS関数」が値を返す場合、その値を引数として新しい関数に渡します

<script>
//doSomething()はGASの関数
 google.script.run.withSuccessHandler(onSuccess).doSomething()
//onSuccessはGASの関数が成功した後に実行する関数
function onSuccess() {}
</script>

(ドキュメントの例)
クライアント側から、ファイルを Googleドライブにアップロードして、ファイルのURLを出力します
*「form」では「フィールド名をキー・フィールド値を値」とするオブジェクトになります
{myFile: ‘値’}

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}
function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
 // フォーム送信を止めます
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
//GAS側processForm関数にフォームオブジェクトを渡して実行
//完了後にprocessForm関数の戻り値をupdateUrl関数の引数に渡して実行
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>

トリガー

「Installable Triggers」

  • 左側「トリガー]」をクリックします
  • 右下 「トリガーを追加」をクリックします
  • 作成するトリガーのタイプを選択してトリガーを作成します
  • 「保存」をクリックします

「Simple Triggers」

  • onOpen(e) :「スプレッドシート・ドキュメント・プレゼンテーション・フォーム」を開くと実行
  • onEdit(e):スプレッドシートの値を変更すると実行
  • onSelectionChange(e) :スプレッドシートで選択内容を変更すると実行
  • doGet(e) :ウェブアプリに HTTP GET リクエストを送信すると実行
  • doPost(e) : ウェブアプリに HTTP POST リクエストを送信すると実行

トリガーが発生すると、GAS関数にイベントオブジェクト(e)を引数として渡します
イベントオブジェクトには、トリガーが発生する原因となったコンテキストに関する情報が含まれます