【GAS】GASでスプレッドシート、Googleフォーム、Slackと連携する方法

# この記事をおすすめする人

- 仕事で業務効率化を頼まれてどうしていいかわからない人
- GASの存在は知っているけど、使い方がわからない人
- GASでできる幅を増やしてみたい人

# Google Apps Script（GAS）とは
Google Apps Script（GAS）は、**Googleが作ったJavaScriptをベースとしたスクリプトサービス**で、Googleのサービス群（スプレッドシート、Googleフォーム、Slackなど）との連携を簡単に実現することができるサービスである。

### GASでできること
- [Google SpreadSheetのデータの読み書き](https://qiita.com/Kooo-X/items/49531c3cd409d1777e11#google-spreadsheet%E3%81%AE%E6%93%8D%E4%BD%9C%E6%96%B9%E6%B3%95)
- [Google Formの回答共有](https://qiita.com/Kooo-X/items/49531c3cd409d1777e11#google-form%E3%81%AE%E6%93%8D%E4%BD%9C%E6%96%B9%E6%B3%95)
- [Gmailの操作（送信、受信メールの内容共有など）](https://qiita.com/Kooo-X/items/49531c3cd409d1777e11#gmail%E3%82%92%E6%93%8D%E4%BD%9C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)
- [Slackへのメッセージ投稿](https://qiita.com/Kooo-X/items/49531c3cd409d1777e11#slack%E3%81%A8%E9%80%A3%E6%90%BA%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)
などなど
（リンクから該当部分に飛べます👍）

# Google SpreadSheetの操作方法
### GASの開き方
1. GASで操作したいGoogle SpreadSheetを開く
2. 以下の画像のように、「拡張機能」→「Apps Script」を選択
![Screenshot 2023-12-23 at 22.04.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/aad90954-ccd6-cc0f-4054-34256ea39a51.png)
3. 以上！！（こんな感じの画面が開くはず！）
![Screenshot 2023-12-23 at 22.07.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/22d62a56-c359-acc6-33f8-128349a0b546.png)


### Google SpreadSheetを操作するためのコード
（前提として、全てのコードが上から順に繋がっていると思ってください！！）
〜〜〜下準備〜〜〜
```javascript:GASとGoogle SpreadSheetを接続するコード
const ss = SpreadsheetApp.getActiveSpreadsheet()
```

```javascript:シートを読み込むコード
const sheet = ss.getSheetByName('シート名（下のタブのところの名前）')
```

〜〜〜各種コード〜〜〜
```javascript:シートの内容を取得するコード（全情報を出力）
var values = sheet.getDataRange().getValues() 
// 出力：[["A1", "B1"], ["A2", "B2"]]

// シートの指定はsheetで指定したシートが参照される
```

```javascript:シートに内容を書き込むコード（単一セル）
sheet.getRange(1, 1).setValue("書き込みたい内容")

// 数字は順に行、列の番号（ex. A1→1,1 / B1→1,2）
// 「=」から始めれば関数も入れられる
```

```javascript:シートに内容を書き込むコード（複数セル）
sheet.getRange(1, 1, 2, 2).setValues(2次元配列)

// 数字は順に変更対象の開始行、列、終了の行、列の番号
// （ex. A1:B2→1,1,1,2 / C3:D5→3,3,5,4）
```

:::note alert
【注意事項】
setValuesの引数には、セルをいくつ選択して更新するにしても必ず2次元配列である必要がある
:::

〜〜〜番外編〜〜〜
```javascript:GASとGASを開いたGoogle SpreadSheet以外のものと接続するコード
// 通常は以下のコードでGASを開いたGoogle SpreadSheetと繋がる
const ss = SpreadsheetApp.getActiveSpreadsheet()

// 以下のコードで他のGoogle SpreadSheetと繋げられる
const ss = SpreadsheetApp.openById("Google SpreadSheetのID")
```
:::note
【IDについて】
Google SpreadSheetのIDは、Google SpreadSheetのURLに含まれている

https://docs.google.com/spreadsheets/d/< ここの部分がIDになっている >/edit#gid=0
:::

```javascript:まとめ
// 下準備
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet = ss.getSheetByName('シート名（下のタブのところの名前）')


// 各種コード
var values = sheet.getDataRange().getValues()
sheet.getRange(1, 1).setValue("書き込みたい内容")


// 番外編
const ss = SpreadsheetApp.getActiveSpreadsheet()
const ss = SpreadsheetApp.openById("Google SpreadSheetのID")
```


# Google Formの操作方法
### GASの開き方
1. GASで操作したいGoogle Formの編集画面を開く
2. 以下の画像のように、「右上の3点」→「Apps Script」を選択
![Screenshot 2023-12-23 at 22.45.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/f313fa24-ad4b-8bf8-6e3d-a8d7ce5c9602.png)
3. 以上！！


## Google Formを操作するための方法・コード
### 初期設定（絶対必要）

①関数の作成（フォームが回答された時に実行したいやつです！）
```javascript:
function onFormSubmit(e){
  console.log(e)
}

// 引数（括弧の中）に回答内容などの回答に関する情報が格納される
```

:::note
引数（括弧の中）には「e」などの変数を必ず入れること
:::

②画面の保存ボタンでの保存（フロッピーみたいなやつ）

③トリガーを開く
- 左側のタブから時計マークを選択する
![Screenshot 2023-12-25 at 5.12.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/3d85251c-aed4-2894-143e-73d97b5a3da1.png)

④右下の「トリガーを追加」からトリガーの設定画面を開く
![Screenshot 2023-12-25 at 5.13.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/fa3014b9-7b9b-3c5e-fa2a-0db6626a5fc2.png)

⑤トリガーの設定をし、保存する
| 項目名 | 選択肢 |
| --- | --- |
| 実行する関数を選択 | 実行させたい関数名 |
| 実行するデプロイを選択 | Head |
| イベントのソースを選択 | フォームから |
| イベントの種類を選択 | フォーム送信時 |

（もし画面が切り替わった場合）
⑥「Choose an account」でGoogleアカウントを選択
⑦「Allow」を選択

完了！！
![Screenshot 2023-12-25 at 5.21.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/0be6926a-95a5-ba7b-e7a9-60c2e3bba1aa.png)


〜〜〜各種コード〜〜〜
```javascript:回答情報について
function onFormSubmit(e){
  console.log(e)
}

// 変数eの内容
{ toString: [Function],
  authMode: 
   { toString: [Function: toString],
     name: [Function: toString],
     toJSON: [Function: toString],
     ordinal: [Function: ordinal],
     compareTo: [Function: compareTo],
     NONE: 
      { toString: [Function: toString],
        name: [Function: toString],
        toJSON: [Function: toString],
        ordinal: [Function: ordinal],
        compareTo: [Function: compareTo],
        NONE: [Circular],
        CUSTOM_FUNCTION: [Object],
        LIMITED: [Object],
        FULL: [Circular] },
     CUSTOM_FUNCTION: 
      { toString: [Function: toString],
        name: [Function: toString],
        toJSON: [Function: toString],
        ordinal: [Function: ordinal],
        compareTo: [Function: compareTo],
        NONE: [Object],
        CUSTOM_FUNCTION: [Circular],
        LIMITED: [Object],
        FULL: [Circular] },
     LIMITED: 
      { toString: [Function: toString],
        name: [Function: toString],
        toJSON: [Function: toString],
        ordinal: [Function: ordinal],
        compareTo: [Function: compareTo],
        NONE: [Object],
        CUSTOM_FUNCTION: [Object],
        LIMITED: [Circular],
        FULL: [Circular] },
     FULL: [Circular] },
  response: 
   { toString: [Function],
     submit: [Function],
     getTimestamp: [Function],
     getId: [Function],
     getRespondentEmail: [Function],
     getItemResponses: [Function],
     getGradableItemResponses: [Function],
     getResponseForItem: [Function],
     getGradableResponseForItem: [Function],
     withItemResponse: [Function],
     withItemGrade: [Function],
     toPrefilledUrl: [Function],
     getEditResponseUrl: [Function] },
  source: 
   { toString: [Function],
     getId: [Function],
     removeViewer: [Function],
     addViewers: [Function],
     addViewer: [Function],
     getViewers: [Function],
     submitGrades: [Function],
     setAcceptingResponses: [Function],
     createResponse: [Function],
     setShowLinkToRespondAgain: [Function],
     setProgressBar: [Function],
     addScaleItem: [Function],
     addCheckboxItem: [Function],
     addCheckboxGridItem: [Function],
     addGridItem: [Function],
     addListItem: [Function],
     addMultipleChoiceItem: [Function],
     addPageBreakItem: [Function],
     addParagraphTextItem: [Function],
     addSectionHeaderItem: [Function],
     addTextItem: [Function],
     addDateItem: [Function],
     addDateTimeItem: [Function],
     addTimeItem: [Function],
     addDurationItem: [Function],
     addImageItem: [Function],
     addVideoItem: [Function],
     setAllowResponseEdits: [Function],
     canEditResponse: [Function],
     setCollectEmail: [Function],
     collectsEmail: [Function],
     setRequireLogin: [Function],
     hasProgressBar: [Function],
     getConfirmationMessage: [Function],
     getDestinationType: [Function],
     getSummaryUrl: [Function],
     getItemById: [Function],
     getPublishedUrl: [Function],
     isAcceptingResponses: [Function],
     isPublishingSummary: [Function],
     hasRespondAgainLink: [Function],
     removeDestination: [Function],
     setCustomClosedFormMessage: [Function],
     getCustomClosedFormMessage: [Function],
     setConfirmationMessage: [Function],
     setDestination: [Function],
     setPublishingSummary: [Function],
     setLimitOneResponsePerUser: [Function],
     hasLimitOneResponsePerUser: [Function],
     setShuffleQuestions: [Function],
     getShuffleQuestions: [Function],
     shortenFormUrl: [Function],
     getResponses: [Function],
     deleteAllResponses: [Function],
     deleteResponse: [Function],
     requiresLogin: [Function],
     isQuiz: [Function],
     deleteItem: [Function],
     getEditUrl: [Function],
     moveItem: [Function],
     setIsQuiz: [Function],
     setTitle: [Function],
     getDestinationId: [Function],
     getTitle: [Function],
     setDescription: [Function],
     getResponse: [Function],
     getItems: [Function],
     getDescription: [Function],
     removeEditor: [Function],
     addEditor: [Function],
     addEditors: [Function],
     getEditors: [Function] },
  triggerUid: '22652169' }
```

```javaScript:回答内容の取り出し方
function onSubmit(e){
  const itemResponse = e.response.getItemResponses()

  for (var i = 0; i < itemResponse.length; i++){    
    var formData = itemResponse[i]

    // 質問文
    var title = formData.getItem().getTitle()
    // 回答内容
    var response = formData.getResponse()
  }
}

// 変数eの中のresponseの中のgetItemResponses()に回答内容が入っている
```

:::note
質問種類ごとに受け取る回答の型が違う
:::

| 質問種類 | 回答の受け取り型 | 例 |
| --- | --- | --- |
| ラジオボタン | 文字列 | 選択肢 1 |
| チェックボックス | 配列 | [ '選択肢 1', '選択肢 2' ] |
| プルダウン | 文字列 | 選択肢 1 |
| 記述式 | 文字列 | HelloWorld |
| 段落 | 文字列 | HelloWorld |
| 均等目盛 | 文字列 | 3　←目盛名が入っている |
| 選択式（グリッド） | 配列 | [ '2 列目', '1 列目' ]　←列名が入っている |
| 日付 | 文字列 | 2024-01-01 |
| 時刻 | 文字列 | 00:00|

※配列は回答が1つでも必ず配列の形で変えされる

:::note
【Tips】
console.log()で確認すると便利（詳細は番外編で！）
:::

〜〜〜番外編〜〜〜
```javascript:console.log()について
console.log()

// console.logの引数に入れたものの中身を確認できる
// 変数などなんでもできる
// Logger.log()も同じ（少し違いはあるが大きな違いはないので割愛）
```

#### console.log()の結果の見方
1. 左側のタブから3本線のようなもの（実行数）を選択する
2. 実行された結果が一覧に出るので、結果を見たいものを選択しクリックする
3. console.log()の結果が表示される



# Slackと連携する方法
### 初期設定
1. Webでこのページ（[https://slack.com/intl/ja-jp/apps](https://slack.com/intl/ja-jp/apps)）を開く（ログインが必要な場合はログインする）
2. 右上のプルダウンから連携したいワークスペースを選択する
3. 右上の「管理」を押して新しい画面へ移る
4. 左側のタブの「カスタムインテグレーション」を選択する
5. 「Incoming Webhook」をクリックする（ない場合は、画面上部の検索欄から検索してる追加する）
6. 「Slackに追加」を押して、次の画面でチャンネルを選択し、「Incoming Webhook インテグレーションの追加」をクリック
7. 表示された「Webhook URL」をコピーする（これがとても大事）

### Slackへメッセージを送るコード
```js:Slackのチャンネルにメッセージを送る

function sendToSlack(){
  const url = "<上でコピーしたWebhook URLを入れる>"
  var message = "<送りたい文章を入れる>"

  var jsonData = {
    "text" : message
  }
  
  var options =
  {
    "method" : "POST",
    "contentType" : "application/json",
    "payload" : JSON.stringify(jsonData)
  }
  UrlFetchApp.fetch(url, options)
}

// 実行するとmessageに格納した内容がWebhook URLを発行した時に選択したチャンネルに流れる
```

```js:複数のオプションを使いこなす（チャンネルの選択、通知名、アイコンの変更）

function sendToSlack(){
  const url = "<上でコピーしたWebhook URLを入れる>"
  var message = "<送りたい文章を入れる>"
  var username = "<通知の時に表示したい名前を入れる>"
  var channel = "<送る先のチャンネルを入れる　ex)#general>"
  var icon = "<通知の時に表示したいアイコンを入れる　ex):white_check_mark:>"

  var jsonData = {
    "username" : username,
    "channel" : channel,
    "text" : content,
    "icon_emoji" : icon
  }
  
  var options =
  {
    "method" : "POST",
    "contentType" : "application/json",
    "payload" : JSON.stringify(jsonData)
  }
  UrlFetchApp.fetch(url, options)
}

// 絶対にusername、channel、iconを必ず埋めなければいけないわけではないので、
// 適宜削除しても大丈夫
```

:::note
アイコンは、Slackのスタンプならなんでも入る
:::
:::note
アイコンは、Slackのメッセージで絵文字の部分にカーソルを当ててコピーすると例のように「:」で囲まれた文字が取得でき、それを用いる
:::

```js:メッセージ内でメンションを含む方法
<@メンバーID>
// 出力：@Yamashita

// 以上の文字列を入れることでメンションできる
```
:::note
メンバーIDは、Slack上でメンバーIDを取得したい人のプロフィールを開き、右側にある「3つの点」をクリックして「メンバーID をコピー」を選択すると取得できる
:::
（↑わからなければググってみてください😭いい記事ありそうです…）

```js:メッセージ内で太文字などの装飾をする方法
// *太文字*
// _イタリック_
// ~取り消し線~
// `コード`
// ```コードブロック```
// >引用
// \n（これで改行できる）
```

詳細は、こちら（[https://slack.com/intl/ja-jp/help/articles/202288908-メッセージの書式設定](https://slack.com/intl/ja-jp/help/articles/202288908-メッセージの書式設定)）などを参考に〜〜



# Gmailを操作する方法
### 初期設定
1. [GASのホーム](https://script.google.com/home)から新しいプロジェクトを作成（Google SpreadSheetに紐づいたGAS等でも大丈夫！）

以上！（とりあえずプロジェクトを作成してください〜〜）

### Gmailを操作するためのコード
〜〜〜各種コード〜〜〜
```js:Gmailからメールを送信する方法
GmailApp.sendEmail("送信先メールアドレス", "メールのタイトル", "メールの本文")

// varとかつけなくて大丈夫です👍
```
:::note
上のコードを実行することで実行したGoogleアカウント（トリガーの場合はトリガーを設定したアカウント）からメールが送信される！
:::
:::note alert
【注意事項】
「実行の成功」 = 「送信成功」ではなく、送信できたかどうか別途確認する必要がある！
:::

```js:Gmailのメールを検索する方法
// 検索条件に合うメールのグループ一覧の取得（返信のメールなどの特定のメールに紐づいたメールは分割されずに1つのグループになって扱われる）
var threads = GmailApp.search("検索条件")

// グループになっているメールを1つづつ取り出していく
for(var i=0; i<threads.length; i++) {
    var messages = threads[i].getMessages();
    
    for(var j = 0; j < messages.length; j++) {
      // これが1つ1つのメール
      var message = messages[j]

      // 個々のメールのタイトル、本文、日付
      var subject = message.getSubject()
      var body = message.getPlainBody()
      var date = Utilities.formatDate(message.getDate(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss')
    }
}
```

【検索条件について】
1. [Gmail](https://mail.google.com/)を開いて検索窓の右側の3本線を開いて検索条件を入力する
![Screenshot 2024-02-27 at 20.10.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/349db7a9-688e-7eb2-7417-de2678ca60f7.png)

2. 検索を押すと検索窓に文字列が現れる←これです！！
![Screenshot 2024-02-27 at 20.11.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1280886/e08eb402-bc27-52bb-43e5-4fdff0106d10.png)

（Gamilの検索窓を使わずとも検索条件を設定できるが、検索窓を使うのが一番早いのでおすすめ👍）

:::note
メール送信同様、実行したGoogleアカウント（トリガーの場合はトリガーを設定したアカウント）かのメールボックスが参照される！
:::


〜〜〜番外編〜〜〜
```js:Gmailのメールが送信できているか確認する方法（簡易版）
// -------------------------------------------------------

// メール送信部分
var email = "送信先のメールアドレス"
var subject = "メールのタイトル"
var body = "メールの本文"

// メールを送信
GmailApp.sendEmail(email, subject, body)

// -------------------------------------------------------


// -------------------------------------------------------

// 送信確認の部分
var email = "送信先のメールアドレス"
var subject = "メールのタイトル"
var body = "メールの本文"

var query = "is:sent to:(" + email + ")"
// is:sentで送信済みのフォルダを検索できる

// 送信していたか判別するフラグ
var flag = false

var threads = GmailApp.search(query)
for(var i=0; i<threads.length; i++) {
  var messages = threads[i].getMessages();

  for(var j = 0; j < messages.length; j++) {
    // これが1つ1つのメール
    var message = messages[j]
  
    // 個々のメールのタイトル、本文、日付
    var messageSubject = message.getSubject()
    if(messageSubject == subject) {
      flag = true
    }
  }
}

console.log(flag)
// trueであれば送信済みでfalseであれば送信できていない
```

:::note
送信済みフォルダを検索することで送信できているか確認している
:::

:::note alert
【注意事項①】
送信直後（一度の実行でメール送信と送信済みメール探索を実行するなど）に送信済みメール探索を実行すると直後すぎてデータを取得できない可能性があるので少し時間を空けて実行すると良い
:::

:::note alert
【注意事項②】
上のコードは送信済みのフォルダの中で
・送信したメールアドレスと同じアドレス
・送信したメールと同じタイトル
のものを探しているので過去に同じタイトルで送信していると正確に判定することができない
:::
簡易版なので正確に送信判定ができない場合があるが、送信済みフォルダから取得したメールの送信時間も取得できるため送信時間も含めて判定するとより正確に判定できる

# 最後に
GASを用いた業務効率化で使えそうな内容をまとめてみました〜〜
気が向いたらいいねしてもらえるとめちゃくちゃ嬉しいです！！