Google Apps Script のアドオンの作成手順

Googleスプレッドシートのアドオンを公開した。日本語の情報があまりなく、ググった感じでは「スクリプトエディタからデプロイして完了！」と簡単そうに書いてあるが、実際は非常に苦労したので手順と注意点をまとめておく。

ちなみに作成したアドオンはこちら。
-　[Gantt Chart Generator](https://chrome.google.com/webstore/detail/gantt-chart-generator/bnaicalmdphddkedcgchnfbjohmhdgni?utm_source=permalink)
-　[Precedent Diagram Maker](https://chrome.google.com/webstore/detail/precedence-diagram-maker/mapjdikjkobbpmhaeeahjeobhllacjkb?utm_source=permalink)


Google Apps Scriptには、Standalone ScriptとContainer Bound Scriptの２つのタイプがあるが、私が作ったのは後者のタイプ。それぞれの違いはここでは割愛するが、スプレッドシートのアドオンは後者で作った方が良いというのが私の結論。とりあえず公式ドキュメントを貼っておく。
- [Standalone Scripts](https://developers.google.com/apps-script/guides/standalone)
- [Container-bound Scripts](https://developers.google.com/apps-script/guides/bound)

## 大枠の申請手順

1. スクリプトを書く
2. OAuth clientのVerificationを申請
  - Google Search Consoleで所有権が確認されたドメインを用意
  - 英文でプライバシーポリシーを書く
3. テスト
4. スクリプトエディタからデプロイ
4. Add-on AdvisorのPublication Reviewを受ける
5. （必要であれば）修正対応をする
6. 承認されて完了

ざっとこんな感じ。英文でプライバシーポリシー!?と色々引っかかるところがあると思うが、実際に私はこの手順を踏んだ。想像以上に大変だった。もしもっと簡単にアドオンが公開できるのであればやり方を教えて欲しいぐらいだ。一つ一つ解説していく。

## スクリプトを書く
ここは申請手順とは少し主旨が異なるが、スクリプトを自身のGoogleドキュメント内で動かすのと、アドオンとして公開するのとでは、いくつか挙動を変える必要があるので触れておく。

### Authorization
アドオンの利用シーンとしては、例えば次のようなものが考えられる。

1. インストールし、その直後に使用している
2. 以前にインストールしており、現在のスプレッドシートでは使用していない

この1と2のケースでは、AuthModeが異なる。例えば、2ではAuthModeがNONEになっている。この状態だと使用できる機能に制約がかかり、不用意にグローバルスコープで何かを呼び出すとエラーになるので注意しよう。次のように、AuthModeがNONEのときは、アドオンを有効にする機能しか使えないようにするのが良いと思う。

```javascript
function onOpen(e) {
  Logger.log('AuthMode: ' + e.authMode);
  var menu = SpreadsheetApp.getUi().createAddonMenu();
  if(e && e.authMode == 'NONE'){
    menu.addItem('Getting Started', 'askEnabled');
  } else {
    var lang = Session.getActiveUserLocale();
    var sidebar_text = lang === 'ja' ? 'サイドバーの表示' : 'Show Sidebar';
    menu.addItem(sidebar_text, 'showSidebar');
  };
  menu.addToUi();
};


function askEnabled(){
  var lang = Session.getActiveUserLocale();
  var title = 'Your Script\'s Title';
  var msg = lang === 'ja' ? '有効になりました的なメッセージ' : 'Something like Your script has been enabled.';
  var ui = SpreadsheetApp.getUi();
  ui.alert(title, msg, ui.ButtonSet.OK);
};

```

### Time-driven triggers
Time-driven triggersはアドオンで使用されることが多いと思うが、使い方に注意が必要だ。自分のスクリプトだけで完結する場合は、"スクリプトエディタ > 編集 > 現在のプロジェクトのトリガー > 新しいトリガーを追加"とクリックして、関数を選ぶだけで良かったが、アドオンの場合はコードに組み込む必要がある。

```javascript
function setDailyTiggers(){
  var ss = SpreadsheetApp.getActive();
  //GAS-based function
  function createTimeDrivenTriggers() {
    ScriptApp.newTrigger('my_function')
    .timeBased()
    .atHour(0)
    .everyDays(1)
    .create();
  };

  //delete exiting triggers
  var triggers = ScriptApp.getUserTriggers(ss);
  for (var i = 0, len = triggers.length; i < len; i++){
    var tmp = triggers[i].getHandlerFunction();
    if(tmp === 'my_function'){
      ScriptApp.deleteTrigger(triggers[i]);
    };
  };
  createTimeDrivenTriggers();
};

//first function
function init(){
  setDailyTiggers()
};
```
簡単に解説すると、まずcreateTimeDrivenTriggersという毎日深夜1時起動のトリガーを設定する関数を用意する。そして、それを実行する前に、もし既存のトリガーがあれば削除をするようにしている。これはinitが実行される度にトリガーが蓄積されていくのを防ぐためだ。
また、アドオンではトリガーの種類も**一つだけ**にしなければならない。これをしないと審査に引っかかるので注意が必要だ。公式ドキュメントは下記。

- [Installable Triggers](https://developers.google.com/apps-script/guides/triggers/installable)

### 処理速度を早く
これはアドオンに限ったことではないが、処理速度を最大限に速くしておいた方がよい。その為に念頭に置くべきは、Google Apps ScriptのAPIをなるべく呼び出さないということだ。いくつかテクニックを紹介する。

#### シートの取得
これは[GASでSpreadsheetを操作する自分的ベストプラクティス](https://qiita.com/ryan5500/items/e72eb205fbe006c2eb6f)で紹介されていたものだが、とても重要だ。下記はscheduleというシートを取得しようとしているが、初めの一回だけAPIを呼べばよい。

``` Javascript
function getSpreadSheet(){
  if(getSpreadSheet.ss){return getSpreadSheet.ss; };
  getSpreadSheet.ss = SpreadsheetApp.getActive();
  return getSpreadSheet.ss;
};


function getScheduleSheet(){
  var ss = getSpreadSheet();
  if(getScheduleSheet.s_sheet){return getScheduleSheet.s_sheet; };
  getScheduleSheet.s_sheet = ss.getSheetByName('schedule');
  return getScheduleSheet.s_sheet;
};
```


#### 列の幅を変更する
例えば、列1から列100までの幅を10に変更するときに、APIのsetColumnWidthを素直に使えばこのようになる。

```javascript
function my_function(){
  var schedule = getScheduleSheet();
  for(var i = 1; i <= 100; i++){
    schedule.setColumnWidth(i, 10); //(columnPosition, width)
  };
};
```

ただ、これだとsetColumnWidthが100回呼び出されることになり、速度が落ちる。そこで使えるのがinsertColumnsAfterだ。次のようにすると、挿入するcolumnは1列目の幅を引き継ぐので、APIの呼び出しが3回で完結する。

```javascript
function my_function(){
  var schedule = getScheduleSheet();
  schedule.setColumnWidth(1, 10); //1列目の幅を10にする
  schedule.deleteColumns(2, 99); //2列目から99列を削除する
  schedule.insertColumnsAfter(1, 99); //1列目以降に99列挿入する

```

他にも色々と工夫したところがあるが、説明するのが大変なので割愛する。興味がある人はコードを公開しているので、そちらを参照してもらいたい。

- [Github - Gantt Chart Generator](https://github.com/takuya0206/ganttchart_generator)
- [Github - Precedence Diagram Maker](https://github.com/takuya0206/precedence_diagram_maker)



## OAuth clientのVerificationを申請
Verificationがなければ、アドオンの審査に入ってもらえない為、まずこれを終わらせなければならない。詳しい手順はやはり公式ドキュメントを参照するのが良いと思う。英語だが事細かに説明してくれている。
- [Requesting verification] (https://developers.google.com/apps-script/guides/client-verification#requesting_verification)


### Google Search Consoleで所有権が確認されたドメイン
注意点としては、Requirementsに挙げられている”Google Search Consoleで所有権が確認されたドメイン”だ。私は当初はてなブログにアドオンを説明する記事を書いて、それをVerificationの申請に使おうとしたが、なんとはてなのログイン画面の「Google+でログイン」の部分がGoogleのデザイン規定に引っかかりNGになってしまった。こればかりは自分で修正するわけにもいかず、結局[Google Sites](https://sites.google.com/)でアドオン用に簡易のホームページを作ることにした。

### 英文のPrivacy Policy
もう一つのRequirementであるPrivacy Policyだが、これは本格的なものを用意しなくても良さそうだ。[このページの一番下](https://sites.google.com/site/ganttchartgenerator/)にあるのが実際に申請に使ったもの。本当に一般的なことしか書いていないが、これで承認が出た。適当に改変して使ってもらって構わない。

### Authorizationのスコープ
Verification申請で一番厄介だったのがこれだ。申請のなかで求めているAuthorizationのスコープに対しての使用目的を明示しなければいけない。ただ、はっきり言って、どのAuthorizationが何を許してくれるかが明確ではないので、何を書いたらよいのか非常にわかりずらい。

>my app will use https://www.googleapis.com/auth/calendar.readonly to show a user's calendar data on the scheduling screen of my app to help users manage their schedule directly through my app.

これが公式ドキュメントの例だ。ここに関しては、スコープと申請内容が合っていなければ、Googleが指摘をしてくれるので、申請に通るまで何度かチャレンジするのが現実的だと思う。僕も一度目は弾かれてしまったが、指摘内容に合わせて書き直して、二回目で承認が出た。

## テスト
Verificationの申請には数日を要するので、その間にリリース前のテストをしてしまおう。やり方は下記の2つがある。

1. スクリプトエディアから"アドオンとしてテスト"を実行
2. アドオンを非公開でデプロイし、テスターユーザーアカウントでテスト

個人的には2のやり方がお薦めだ。なぜならば、1は機能が限定的になってしまい、実のところあまり参考にならない。気軽に出来るのが1の唯一のメリットなので、1で表面的なバグを直し、2でじっくりテストするのが良いと思う。2は実質的にデプロイの過程を踏まなければいけないので、少し面倒なのだ。
尚、テストは実際のユースケース沿って行うのがベターであるが、簡単にそれが再現できないものがある。実際に僕がハマったものは、**タイムゾーン**である。GoogleのAdd-on Advisorがどのタイムゾーンからアクセスするか不明なので、PCのシステム時刻、スプレッドシートのタイムゾーン設定などを考慮して実装しておかないとバグになりやすい。また、私の場合は、Googleカレンダーから日本の祝日を自動取得していたのだが、これはGoogleアカウントが日本で設定されていないと、日本の祝日が参照できずにエラーになってしまう。自身で行ったテストではエラーが出ないのだが、GoogleのAdd-on Advisorが実行するとエラーになっていた。

## スクリプトエディタからデプロイ
ここは表示される手順に沿って情報を入力していくだけなのだが、いくつか注意点がある。
### インストール後のヒント
後の審査で「Post-install tip」が適切でないと複数回の指摘を受けた。これが「インストール後のヒント」のことを指しているのだが、どう直して良いのかさっぱり分からず非常に困った。

これが審査に落ちたもの。個人的にはシンプルだが十分にインストール後の動作を指示していると思う。

>1. Open the add-on menu. 2. Click "Create Gantt Chart."


次が審査に通ったもの。

>Create your first gantt chart by choosing “Gantt Chart Generator > Create Gantt Chart.” Then input your WBS with schedule.

どうやらポイントは、「>」を使って、クリックすべきメニューの場所を示すことにあるようだ。


### ロゴ等の画像
アドオンの公開には下記の画像を（3つも！）用意する必要がある。サイズが異なるとNGなので注意が必要だ。

- アイコン 128×128 ピクセル
- スクリーンショット 1280x800 または 640x400 ピクセル
- プロモーション タイル画像 440x280 ピクセル

デザインにこだわりがある人は時間をかけるポイントだと思うが、僕はフリーツールを使って、簡単に済ましてすまった。[ロゴ作成が無料のサイト24選!おしゃれなロゴジェネレーター](https://univ.peraichi.com/1) ←こういうまとめから自分の用途に合うサービスを使って、簡易に行うのは個人的には有りだと思う。

### 非公開でデプロイ
上記でも触れたが、初めは一般公開をせず、非公開にしてテストをするようにしよう。デベロッパーアカウントから”テスターアカウントを編集”が実行できるので、自分のサブアカウントなどを登録するのが良いだろう。そうすると、非公開でもテストアカウントであればインストールが可能になる。

## Add-on AdvisorのPublication Reviewを受ける
審査に一発で通れば、リリースが完了なのだが、落ちてしまった場合、Add-on Advisorから改善依頼の連絡が来る。ここはさすがGoogle、Googleドキュメントを使って、改善のコミュニケーションをするようになっている。共同編集ができるので質問もしやすい（答えてくれるかは別だが）。指摘に合わせて鋭意改善するしかないのだが、僕は前述のPost-install tipとタイムゾーンで盛大にハマってしまった。皆さんのご健闘をお祈りする。

![Publication Review.png](https://qiita-image-store.s3.amazonaws.com/0/147086/2bda7d6b-df34-b1fe-72ee-81635ff68b4c.png)

## 承認されて完了
Add-on Advisorの承認が降りれば、晴れて完了だ。ああ、疲れた...！


## 終わりに
というわけで、これがアドオンのリリース手順だ。はっきり言って面倒である。にも関わらず、課金システムがデフォルトでついていないため、有料アドオンにしようと思うと、さらに面倒なことをしなければいけない。一方で、サーバーまわりのメンテナンスが一切不要なのは魅力的だと思う。どういう目的でアドオンを開発するかにもよるが、Googleのプラットフォームに自分の開発したコードが載るのは嬉しいものだ。私的にGoogle Apps Scriptを書いている人は、よければ一般公開してみてはどうだろうか。