Google Apps Scriptで議事録テンプレ作成を楽にした

【移行のお知らせ】[プライバシー上の懸念](https://blog.qiita.com/about-user-page-renewal-20200326/)により、Qiitaアカウントの削除を検討しています。
各投稿は個人blog( https://gateau.hatenablog.com/archive/category/Qiita )に移行しました。
今後も参照される方は、ブックマークなど移行をお願い致します。

---

DroidKaigiでは月に1回、スタッフが集まってMTGをしています。
Google Documentに各々事前に議題を書き、当日議論した結果を書き残して議事録とする運用です。
そのテンプレを挿入するGoogle Apps Script（以下、GAS）を作りました。

ヘルプの右にあるボタンを押すと…
<img width="1040" alt="gas_template_1_public.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/aa4be242-3ba7-4569-965e-90ca72e6efc4.png">

別ファイルから取得したテンプレートが一番上に挿入されました！

<img width="1036" alt="gas_template_2.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/452ad8a4-eccc-aa46-55d7-4675e581dbe4.png">

### この記事の目的

* スタッフ向け（自分含む）
  * スクリプトの内容を理解し、メンテナンス出来るようになる
* それ以外の方向け
  * DroidKaigiやそのスタッフに興味を持ってもらう
  * GASに興味を持ってもらう（そしてノウハウをアウトプットしてほしい！情報が少ない！）

最終的なコードはこちらなので、これが書ける方はこの記事を読む必要はありません。（むしろレビュー歓迎です）

```javascript
function onOpen() {
  var ui = DocumentApp.getUi();
  ui.createMenu('★次回議事録テンプレ挿入')
      .addItem('最上部に挿入', 'insertTemplate')
      .addToUi();
}

function insertTemplate() {
  
  var TEMPLATE_DOC_ID = "XXXXXXXXXXXXXXXXXXXXXX"
  // URLの一部がID https://docs.google.com/document/d/XXXXXXXXXXXXXXXXXXXXXX/edit
  
  var template_doc = DocumentApp.openById(TEMPLATE_DOC_ID);
  var this_doc = DocumentApp.getActiveDocument();
  
  this_doc.getBody().insertHorizontalRule(0);
  
  template_doc.getBody().getParagraphs().forEach(function(value, i) {
    if (value.getType() == DocumentApp.ElementType.LIST_ITEM) {  // 箇条書きの場合
      this_doc.getBody().insertListItem(i, value.getText()).setGlyphType(DocumentApp.GlyphType.BULLET);
    } else {
      this_doc.getBody().insertParagraph(i, value.getText());
      this_doc.getBody().getParagraphs()[i].setAttributes(value.getAttributes()); // 見出しなどの属性を設定
    }
  })

}
```

## GASを動かしてみる

[公式ドキュメント](https://developers.google.com/apps-script/reference/document/) と [初心者のためのGoogle Apps Scriptプログラミング入門](https://www.tuyano.com/index2?id=638001) を参考に読みつつ、まずはまっさらな状態からスタートしてみましょう。  

1. テスト用のdocumentを作成して保存する
2. ツール->スクリプトエディタ でGAS用エディタを開く 
3. 左上で適当なプロジェクト名を入力し、保存すると実行できるようになる
4. 再生ボタン▶のマークをクリック、もしくは「実行」から関数を選択し実行する
<img width="650" alt="GAS_test1.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/abb3f4e8-f948-a06e-9dff-46f35b4f4831.png">

「関数myFunctionを実行中」と表示され、まだ関数の中身は何も無いので何も起きずに終了します。


### 補足：スクリプトエディタを開けない場合

Googleアカウントを複数ログインしている状態だと、エラーで開けないことがあります。
その場合は、ブラウザを変えてアカウント1つだけログインした状態にすると開けました。  

![gas_error_1.png](https://qiita-image-store.s3.amazonaws.com/0/36199/a208398c-2fc4-77ef-34ed-8c7b42150bf0.png)

## ログを出力する

デバッグしやすいようにログ出力の方法を学びましょう。
`Logger.log();` でログ出力が可能です。  

```javascript
function myFunction() {
  Logger.log("Google Apps Scriptに挑戦しています");
}
```

実行し、「表示」->「ログ」 で内容を確認できます。
<img width="870" alt="GAS_test2.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/726b50bf-b4e9-f188-820c-b3d6e14c38d9.png">

## Documentの情報を取得する

`DocumentApp.getActiveDocument();` で現在のDocumentを取得します。
this_docは[Document型](https://developers.google.com/apps-script/reference/document/document)で、 `getName()` をするとドキュメント名を取得できます。
任意のDocument情報を取得するには `DocumentApp.openById();` でID指定します。IDはURLの一部です。

```javascript
function myFunction() {

  var this_doc = DocumentApp.getActiveDocument();
  Logger.log(this_doc);
  Logger.log(this_doc.getName());
  
  var template_doc = DocumentApp.openById("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
  // https://docs.google.com/document/d/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/edit
  //                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ この部分がID
  Logger.log(template_doc);
  Logger.log(template_doc.getName());

}
```

実行結果
<img width="708" alt="スクリーンショット 2018-06-10 15.31.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/a3834192-a210-f820-ccb8-2588f18da3b2.png">

ただし、こちらのコードは初回実行時にデータへのアクセス権限を承認する必要があります。

## アクセス権限を承認する
GASはデータアクセスが発生する際、このような承認ダイアログを表示します。
<img width="470" alt="スクリーンショット 2018-06-10 15.37.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/c34e054a-7b0e-3ec4-d1f6-9d64eefff8c2.png">
許可を確認し、アカウントを選択
![スクリーンショット_2018-06-10_15_17_58.png](https://qiita-image-store.s3.amazonaws.com/0/36199/1981abaf-d116-0c32-9849-9a62b6262270.png)
左下の「詳細を表示」で下に増える部分から、「<GAS名>（安全ではないページ）に移動」をクリック
![スクリーンショット_2018-06-10_15_18_09.png](https://qiita-image-store.s3.amazonaws.com/0/36199/89b969d8-9f10-0287-426c-9f6a2402fd2c.png)
「許可」でGASを実行可能になります。
![スクリーンショット_2018-06-10_15_18_15.png](https://qiita-image-store.s3.amazonaws.com/0/36199/28cc082d-fb10-f046-34c5-3d1b220c0a3f.png)

## 文字を挿入する

```javascript
function myFunction() {
  var this_doc = DocumentApp.getActiveDocument();
  this_doc.getBody().insertParagraph(0, "一番上に文字を挿入したい！") // 0は挿入する位置
}
```

`getBody()`でDocumentから本文（[Body](https://developers.google.com/apps-script/reference/document/body)）を取得できます。
Bodyに `appendXXX`, `insertXXXX` をすると任意の要素を追加できます。

<img width="594" alt="スクリーンショット 2018-06-10 20.20.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/0d35ec00-d2da-6f6a-5d69-a94e800f7c96.png">

実行して追加された文字は通常の操作と同じく「編集」->「元に戻す」で戻せます。

## Documentに実行メニューを追加する

実行がスクリプトエディタからだけでは不便なので、Document側に実行メニューを追加してみましょう。

```javascript
function onOpen() {
  var ui = DocumentApp.getUi();
  ui.createMenu('メニューに表示される名前')
      .addItem('myFunctionを実行する', 'myFunction')
      .addToUi();
}

function myFunction() {
  var this_doc = DocumentApp.getActiveDocument();
  this_doc.getBody().insertParagraph(0, "一番上に文字を挿入したい！")
}
```
Documentを開いてすぐ実行する処理は `onOpen()` 内に記述します。
`onOpen()` 内で 'メニューに表示される名前' というメニューを作成し、そこに表示名と実行する関数を定義します。

Documentで「ヘルプ」の横に追加したメニューが表示されるので実行します。
<img width="814" alt="スクリーンショット 2018-06-10 18.49.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/8679cd45-67bd-5a66-8f1e-d1ecd127605b.png">

完了すると「スクリプトが終了しました」という表示され、先ほどと同じように一番上に挿入されました。

<img width="709" alt="スクリーンショット 2018-06-10 18.50.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/581a6eb2-9b94-c8e5-59d3-4ea85b99f976.png">


## 箇条書きを挿入する

箇条書きの挿入は`insertListItem()`や`appendListItem()`を使います。
デフォルトでは番号付きリストになり、 `setGlyphType()` で丸や矢印などタイプの変更が可能です。
定数は[こちら](https://developers.google.com/apps-script/reference/document/glyph-type)に定義されており、見た目を試した結果が下記の通りです。

```javascript
function myFunction() {
  var body = DocumentApp.getActiveDocument().getBody();

  body.appendListItem("default");
  body.appendListItem("BULLET").setGlyphType(DocumentApp.GlyphType.BULLET);
  body.appendListItem("HOLLOW_BULLET").setGlyphType(DocumentApp.GlyphType.HOLLOW_BULLET);
  body.appendListItem("SQUARE_BULLET").setGlyphType(DocumentApp.GlyphType.SQUARE_BULLET);
  body.appendListItem("NUMBER").setGlyphType(DocumentApp.GlyphType.NUMBER);
  body.appendListItem("LATIN_UPPER").setGlyphType(DocumentApp.GlyphType.LATIN_UPPER);
  body.appendListItem("LATIN_LOWER").setGlyphType(DocumentApp.GlyphType.LATIN_LOWER);
  body.appendListItem("ROMAN_UPPER").setGlyphType(DocumentApp.GlyphType.ROMAN_UPPER);
  body.appendListItem("ROMAN_LOWER").setGlyphType(DocumentApp.GlyphType.ROMAN_LOWER);

  // 実際には 1つずつ `body.appendHorizontalRule();` を入れてましたが省略
}
```

<img width="244" alt="スクリーンショット 2018-06-10 20.32.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/96c690ba-c6f5-faff-5150-ab73caed2fff.png">

## 繰り返し処理をする

for文やforEach文の利用が可能です。
forEachを使って、予め作成しておいたテンプレートドキュメントの内容をログに出力してみましょう。

```javascript
function myFunction() {
  var template_doc = DocumentApp.openById("XXXXXXXXXXXXXXXXXXXXXX");
  
  template_doc.getBody().getParagraphs().forEach(function(value, i) {
    Logger.log(value.getText());
  })
}
```
<img width="699" alt="スクリーンショット 2018-06-10 20.14.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/36199/6a0a7c8d-131c-1dcc-c735-286463fd0179.png">

それっぽくなりました。あとはここまでの内容を駆使すれば、最初のスクリプトの内容が理解出来るはずです。

## まとめ

やっていることは単なるコピペですが、元のテンプレがどこにあるかなどを知らないと出来ないですし、スタッフの人数が増えると全員に周知するのは困難です。
そこで、目につくところにメニューがあれば新しく入った人でも実行出来ると思い作りました。
calendarやslack連携といった拡張なんかも考えられるので、みんなで推進していきたいと思います！