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

※本記事はQiitaに投稿していた記事のexportです(元投稿日: 2018/6/11)

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

ヘルプの右にあるボタンを押すと… f:id:Gateau:20200328103652p:plain

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

f:id:Gateau:20200328103726p:plain

この記事の目的

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

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

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を動かしてみる

公式ドキュメント初心者のためのGoogle Apps Scriptプログラミング入門 を参考に読みつつ、まずはまっさらな状態からスタートしてみましょう。

  1. テスト用のdocumentを作成して保存する
  2. ツール->スクリプトエディタ でGAS用エディタを開く
  3. 左上で適当なプロジェクト名を入力し、保存すると実行できるようになる
  4. 再生ボタン▶のマークをクリック、もしくは「実行」から関数を選択し実行する

f:id:Gateau:20200328103821p:plain

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

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

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

f:id:Gateau:20200328103850p:plain

ログを出力する

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

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

実行し、「表示」->「ログ」 で内容を確認できます。 f:id:Gateau:20200328103931p:plain

Documentの情報を取得する

DocumentApp.getActiveDocument(); で現在のDocumentを取得します。 this_docはDocument型で、 getName() をするとドキュメント名を取得できます。 任意のDocument情報を取得するには DocumentApp.openById(); でID指定します。IDはURLの一部です。

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());

}

実行結果 f:id:Gateau:20200328103957p:plain

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

アクセス権限を承認する

GASはデータアクセスが発生する際、このような承認ダイアログを表示します。 f:id:Gateau:20200328104035p:plain
許可を確認し、アカウントを選択 f:id:Gateau:20200328104046p:plain
左下の「詳細を表示」で下に増える部分から、「<GAS名>(安全ではないページ)に移動」をクリック f:id:Gateau:20200328104122p:plain
「許可」でGASを実行可能になります。 f:id:Gateau:20200328104137p:plain

文字を挿入する

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

getBody()でDocumentから本文(Body)を取得できます。 Bodyに appendXXX, insertXXXX をすると任意の要素を追加できます。

f:id:Gateau:20200328104210p:plain

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

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

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

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で「ヘルプ」の横に追加したメニューが表示されるので実行します。 f:id:Gateau:20200328104257p:plain

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

箇条書きを挿入する

箇条書きの挿入はinsertListItem()appendListItem()を使います。 デフォルトでは番号付きリストになり、 setGlyphType() で丸や矢印などタイプの変更が可能です。 定数はこちらに定義されており、見た目を試した結果が下記の通りです。

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();` を入れてましたが省略
}

f:id:Gateau:20200328104417p:plain

繰り返し処理をする

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

function myFunction() {
  var template_doc = DocumentApp.openById("XXXXXXXXXXXXXXXXXXXXXX");

  template_doc.getBody().getParagraphs().forEach(function(value, i) {
    Logger.log(value.getText());
  })
}

f:id:Gateau:20200328105046p:plain

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

まとめ

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