Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

GAS: Slackからフォーム入力でDocBaseのメモを生成したい

皆様こんにちは、BPSの協力会社として横浜を拠点に活動しております、株式会社ECNのFuseです。

突然ですが、みなさんはDocBase、使ってますか?
ECNではDocBaseを議事録の記録だけでなく、TechRachoの記事の下書きを保存するスペースとしても使っています。
ですが…

解決したい問題

現状、新しい記事を書こうと思った時には記事の執筆状況を管理するためのスプレッドシート手作業で行を追加し、手作業でテンプレートから新しいメモを生成し…と手作業だらけです。
現状に特に不満はなかったのですが事故が怖いのと新しを身につけたかったので
ワーローで便利にしていきましょう。

目次

  1. まえがき
  2. 解決したい問題
  3. 目次
  4. 使う技術やサービス
  5. Slackから表にデータを追加したい
    • 下準備
    • ワークフロー作成
  6. データの追加に反応してGASを走らせる
    • プロジェクト作成
    • GASが自動で走るようにする
  7. APIを叩いてメモを生成する
    • 下準備
    • OAuthスコープの編集
    • トークンの取得
    • 関数の制作
  8. まとめ
    • 最終的なコード

使う技術やサービス

Slackワークフロー
slackの便利機能。定例的な業務を自動化できるすごい機能です。
DocBase
メモやドキュメントをクラウドで共有できるサービス。記事の執筆に使ってます。
Googleスプレッドシート
クラウド上で利用できる表計算ソフト。Slackからデータを追加できる。
GAS
Googleのサービスと連携できるJSベースのFaaS。サーバを意識せずに使えるすごいサービスです。

Slackから表にデータを追加したい

下準備

ワークフローを作り始める前にまずは下準備、送られてきた加工前のデータを一時的に保存するシートと

▲見るからに一時作業用なシート。名前もそのまま「一時作業用シート」とします。

行の書式のコピー元になるシートを用意しておきます。

▲名前は「書式コピー元」とします

ワークフロー作成

下準備が終わったら、いよいよSlackのワークフロービルダーに移動します。

ワークフローに名前を付けて…

できました。
入力されたデータを一時作業用シートに追加するワークフローです。

データの追加に反応してGASを走らせる

プロジェクト作成

ワークフローが完成したので、続いて一時作業用シートの更新に反応し起動するGASのプロジェクトを作ります。
紐づけたいスプレッドシートを開き、
拡張機能>Apps Scriptでスプレッドシートに紐づいたGASプロジェクトを新規作成できます。

▲こうして

▲こう

その後はサービスからGoogle Sheets APIを追加し、スプレッドシート周りの機能を使えるようにしてやります。

GASが自動で走るようにする

その後は下のコードを書いたらコード部分は一区切り。

// コード.gs
function onEditCell(e) { // セルが編集されたら実行
  if(e.source.getSheetName()!="一時作業用シート") return; // 編集されたシートが一時作業用シートでなければキャンセル
  const ActiveSheet=e.source.getSheetByName("一時作業用シート"); // 一時作業用シートをオブジェクトとして取得
  if(ActiveSheet.getLastRow()<=1) return; // 行が1行もなければキャンセル
  const sheet_name = 'シート1' // 行の追加先のシート名
  const sheet = e.source.getSheetByName(sheet_name); // メインのシートを取得
  const range = ActiveSheet.getRange(ActiveSheet.getLastRow(),1,1,5); // 一時作業用シートに追加された行を取得
  const source = e.source.getSheetByName("書式コピー元").getRange("A1:I1"); // 行に使う書式を引っ張ってくる
  var lastRow = range.getValues() // 入力された値をパース
  const addArray = [['=ROW()-2', lastRow[0][1],  // *TODO:メモを生成しURLを返す関数MakeNewMemo(lastRow[0][1],lastRow[0][3])*/,'','',lastRow[0][2]+`(`+lastRow[0][3]+')',lastRow[0][4],'','']] // メインのシートに追加するデータ
  const dist=sheet.getRange(sheet.getLastRow()+1,1,1,9) // 完成したデータの追加先
  source.copyTo(dist, SpreadsheetApp.CopyPasteType.PASTE_FORMAT); // 書式をペースト
  source.copyTo(dist, SpreadsheetApp.CopyPasteType.PASTE_DATA_VALIDATION); // データ規則をペースト
  source.copyTo(dist, SpreadsheetApp.CopyPasteType.PASTE_CONDITIONAL_FORMATTING); // 条件付き書式をペースト
  dist.setValues(addArray); // 新しい行を追加する
  const lastRowNo = ActiveSheet.getLastRow();
  ActiveSheet.deleteRow(lastRowNo); // 使用したデータを一時作業用シートから削除
} 

その後は新しいトリガーをトリガータブから生成し、下の画像のように設定してあげればひとまずはワークフローの起動と同時に(エラー吐くけど)GASも走るようになります。

APIを叩いてメモを生成する

このままでは当然動かないのでToDo部分にある「メモを生成しURLを返す関数」を作っていきます。
まずはAPIを呼び出す下準備としてやらなければいけないことが2つ。
「OAuthスコープの編集」「トークンの取得」です。

下準備

OAuthスコープの編集

OAuthスコープを編集するためにはまずはappsscript.jsonを編集できる状態にする必要があります。

その後は、出現したファイルをこのように編集し、外部にアプリがリクエストを送れるようにします。

  • appsscript.json:
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Sheets",
        "version": "v4",
        "serviceId": "sheets"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "oauthScopes": [
   "https://www.googleapis.com/auth/script.external_request",
   "https://www.googleapis.com/auth/spreadsheets"
  ],
  "runtimeVersion": "V8"
}

トークンの取得

APIを通じて外部サービスにアクセスするときは大体の場合トークンが必要です。
DocBaseのAPI用のトークンを取得しましょう。

DocBaseの設定画面からアクセストークン画面へ移動し、「トークンを作成」ボタンを押すと、

こんな画面に移動します。スコープの読み取りと書き込みの両方にチェックが入っていることを確認したら、
トークンを作成しましょう。

取得したトークンは後で使うのでDocBaseのチーム名と一緒にスクリプトプロパティに格納しておきます。
べた書きしたくない情報はスクリプトプロパティに入れてしまいましょう。

関数の作成

準備が整ったのでいよいよコードを書いていきます。今回は

  • メモを生成しURLを返す「function MakeNewMemo(title,pn)
  • テンプレートを取得する「function getTemplate(token,domain)

の二つの関数を書いていきましょう。
このようなコードを記述していきます。

// コード.gs(一部抜粋)
function MakeNewMemo(title,pn){
  const token = PropertiesService.getScriptProperties().getProperty('TOKEN') // トークンを取得
  const domain = PropertiesService.getScriptProperties().getProperty('DOMAIN') // チーム名(ドメインの一部)を取得
  var temp=getTemplate(token,domain); // 記事のテンプレートをとってくる
  var url = "https://api.docbase.io/teams/"+domain+"/posts"; // メモ生成APIのエンドポイント
  var headers = { // HTTPリクエストのヘッダー
    "Accept": "application/json",
    "X-DocBaseToken":token, // トークン
    "Content-type": "application/json"
  }

  var data = { // データ部
    "title": "[WIP]"+title, // メモのタイトル
    "body": temp.replace("◯◯",pn), // ○○をペンネームに置き換える
    "draft": false,
    "tags": [],
    "scope": "group",
    "groups":[30755],
    "notice": false
  }

  var options = { // リクエストのオプション(メソッド・データ・ヘッダー)
    "method": "post",
    "payload": JSON.stringify(data),
    "headers": headers
  };
  var resp=UrlFetchApp.fetch(url, options);//リクエストを送信
  var json=resp.getContentText(); // レスポンスを引っ張り出す
  var jsonData = JSON.parse(json); // 連想配列にする
  return jsonData["url"]; // 出来上がったメモのURLを返す
}

function getTemplate(token,domain){ // メモのテンプレートを取得する
  var url = "https://api.docbase.io/teams/"+domain+"/posts/**取得したい記事のID**"; // エンドポイント
  var headers = { // ヘッダ
    "X-DocBaseToken":token
  }
  var options = {//GETメソッドでヘッダのみデータなし
    "method": "get",
    "headers" : headers
  };
  var resp=UrlFetchApp.fetch(url, options);
  var json=resp.getContentText();
  var jsonData = JSON.parse(json);
  return jsonData["body"]//テンプレート記事の中身を返す
}

GASでのHTTPリクエストの流れは以下のようになります。

  1. ヘッダー、ペイロードのオブジェクトを必要に応じて作成
  2. メソッド、ヘッダー、ペイロードを1まとめにしたリクエストのオブジェクトを作成
  3. リクエストオブジェクトとURLを指定して送信
  4. レスポンスの中身を今回はテキストとして取得
  5. レスポンスの中身を連想配列にパース
  6. 必要なデータを取り出す

この流れを使って必要なデータを取り出して、呼び出し元に返しながらメモを生成していきます。
この2つの関数を完成させたら、少し駆け足気味ですが、記事作成システムは完成です!

まとめ

  • ワークフローはスプレッドシートと連携できて便利
  • GASもスプレッドシートや外部のAPIと連携できて便利

最終的なコード

function onEditCell(e) { // セルが編集されたら実行
  if(e.source.getSheetName()!="一時作業用シート") return; // 編集されたシートが一時作業用シートでなければキャンセル
  const ActiveSheet=e.source.getSheetByName("一時作業用シート"); // 一時作業用シートをオブジェクトとして取得
  if(ActiveSheet.getLastRow()<=1) return; // 行が1行もなければキャンセル
  const sheet_name = 'シート1'//行の追加先のシート名
  const sheet = e.source.getSheetByName(sheet_name); // メインのシートを取得
  const range = ActiveSheet.getRange(ActiveSheet.getLastRow(),1,1,5); // 一時作業用シートに追加された行を取得
  const source = e.source.getSheetByName("書式コピー元").getRange("A1:I1"); // 行に使う書式を引っ張ってくる
  var lastRow = range.getValues() // 入力された値をパース
  const addArray = [['=ROW()-2', lastRow[0][1], MakeNewMemo(lastRow[0][1],lastRow[0][3]),'','',lastRow[0][2]+`(`+lastRow[0][3]+')',lastRow[0][4],'','']] // メインのシートに追加するデータ
  const dist=sheet.getRange(sheet.getLastRow()+1,1,1,9) // 完成したデータの追加先
  source.copyTo(dist, SpreadsheetApp.CopyPasteType.PASTE_FORMAT); // 書式をペースト
  source.copyTo(dist, SpreadsheetApp.CopyPasteType.PASTE_DATA_VALIDATION); // データ規則をペースト
  source.copyTo(dist, SpreadsheetApp.CopyPasteType.PASTE_CONDITIONAL_FORMATTING); // 条件付き書式をペースト
  dist.setValues(addArray); // 新しい行を追加する
  const lastRowNo = ActiveSheet.getLastRow();
  ActiveSheet.deleteRow(lastRowNo); // 使用したデータを一時作業用シートから削除
} 
function MakeNewMemo(title,pn){
  const token = PropertiesService.getScriptProperties().getProperty('TOKEN') // トークンを取得
  const domain = PropertiesService.getScriptProperties().getProperty('DOMAIN') // チーム名(ドメインの一部)を取得
  var temp=getTemplate(token,domain); // 記事のテンプレートをとってくる
  var url = "https://api.docbase.io/teams/"+domain+"/posts"; // メモ生成APIのエンドポイント
  var headers = { // HTTPリクエストのヘッダー
    "Accept": "application/json",
    "X-DocBaseToken":token, // トークン
    "Content-type": "application/json"
  }

  var data = { // データ部
    "title": "[WIP]"+title, // メモのタイトル
    "body": temp.replace("◯◯",pn), // ○○をペンネームに置き換える
    "draft": false,
    "tags": [],
    "scope": "group",
    "groups":[30755],
    "notice": false
  }

  var options = { // リクエストのオプション(メソッド・データ・ヘッダー)
    "method": "post",
    "payload": JSON.stringify(data),
    "headers": headers
  };
  var resp=UrlFetchApp.fetch(url, options); // リクエストを送信
  var json=resp.getContentText(); // レスポンスを引っ張り出す
  var jsonData = JSON.parse(json); // 連想配列にする
  return jsonData["url"]; // 出来上がったメモのURLを返す
}

function getTemplate(token,domain){ // メモのテンプレートを取得する
  var url = "https://api.docbase.io/teams/"+domain+"/posts/**取得したい記事のID**"; // エンドポイント
  var headers = { // ヘッダ
    "X-DocBaseToken":token
  }
  var options = { // GETメソッドでヘッダのみデータなし
    "method": "get",
    "headers" : headers
  };
  var resp=UrlFetchApp.fetch(url, options);
  var json=resp.getContentText();
  var jsonData = JSON.parse(json);
  return jsonData["body"] // テンプレート記事の中身を返す
}

株式会社ECNはPHP、JavaScriptを中心にお客様のご要望に合わせたwebサービス、システム開発を承っております。
ビジネスの最初から最後までサポートを行い
お客様のイメージに合わせたWebサービス、システム開発、デザインを行います。


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。