【出欠確認に疲れた若手必見】 3ステップでできる! slackで出欠確認コマンド


この記事は[モチベーションクラウドシリーズアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/mcs)の4日目の記事です。

---

どうも初めまして、リンクアンドモチベーションでエンジニアをしている伊藤と申します。
完全に別件の宣伝ですが、2020/12/18に弊社がスポンサーにもなっている[【オンライン開催】銀座Rails#28](https://ginza-rails.connpass.com/event/196658/) で「**モチベーションクラウドを支える非同期処理の変遷**」というテーマで発表します。
今回のゲストスピーカーはあの**まつもとゆきひろ**([@yukihiro_matz](https://twitter.com/yukihiro_matz))さんなので興味があればぜひ!!

# はじめに
業務効率化のために [slack api](https://api.slack.com/reference)と [google app script](https://developers.google.com/apps-script/reference?hl=ja)を使ったslack appを作りました。

なぜ作ったか から作成の手順を3つのステップに分けて解説します。

# こんなことありませんか？


**〜オンライン会議にて〜**

**上司**　「おい、若手！全員いるか確認しろ！」
**自分**　「はい！」
...
**自分**　「(顔だけじゃわかんないし、人数的には足りないけど誰いないかわかんないよ...)」
**上司**　「誰いない？いない人声かけて！」
**自分**　「...(いないフリ)」
**上司**　「ん？？？」

**自分**　🥺

-----

![online_kaigi_man.png](https://1.bp.blogspot.com/-6pDrDpYKUCg/Xor0RTjWgtI/AAAAAAABYL0/-y57hak6vKEhfrtHwOashooPxNMUaYJ8ACNcBGAsYHQ/s400/online_kaigi_man.png)

新しい生活様式になり、ガラリと変わった会議の形 = オンライン会議 での若手の**あるある**・**イライラ**の一つである、

### **「オンライン会議、誰がいないか全然わからない問題」**

を解決するために！

出欠を報告してもらう
**slack用アプリ (スラッシュコマンド)** 

を作成しました！

### **若手でも作れます！！**


# 何を作ったか


![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/58df8d22-ac21-aa6f-76f3-4df6801aa5dc.gif)


使い方は簡単!!

(「出欠確認して」と言われる前にやっておくこと！)　← こういうのが若手のうちはとっても大事

1. 全員がいるslackチャンネルで `/attendance` と入力する
2. 参加者がボタンをおす
3. 名前が残っている人を読み上げて、参加しているか確認する

ほら、**いない人が一目瞭然！！！**

もうこれで

「**誰がいないか画面とにらめっこ**」

なんてしなくていいですね！！


今回はslack apiを活用しながら、[Google Apps Script](https://developers.google.com/apps-script?hl=ja) (以降 gas) で実装していきます。

いいね！と思った人はぜひ**LGTM**お願いします！！

# 作り方
解説はいくつか飛ばしますがgasは活用記事が多かったり、[本家のReference](https://developers.google.com/apps-script/reference?hl=ja)も整備されているのでそちらも合わせて参照ください。

## 全体像

`/attendance`を実行してから、欠席者の名前が出るまでのフローは以下のようになっています

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/65068b55-8284-6cbb-c251-588b28c101bb.png" width=100%>


なので

1. **slack appの設定**
1. **スラッシュコマンドの作成**
1. **`参加`ボタン用apiの作成**

の3つに分けて説明していきます！

## 1. slack appの設定
### まずはslack appを登録していきます

[Slack API](https://api.slack.com/)へアクセスし下記の「Start Building」をおします

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/8462ec14-545b-0ce1-46ac-098397155c27.png" width=80%>

すると次のようなポップアップが出てくるので

  - App Nameはお好みで (出欠確認bot とか)
  - Development Slack Workspace は導入する予定のslackのworkspaceを選択

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/52543686-3ba0-ee3d-c2ce-4fbcf781ae50.png" width=50%>

**早速アプリが作成できました！**

さらに、`Settings > Basic Information > Display Information` より、作ったアプリの見た目をいい感じにいじることができます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/6d8794dd-a4fd-d834-4daf-f3e9b95826a8.png" width=30%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/cf141976-8552-9149-d929-de142a3be1ef.png" width=50%>

### 次にbotとして発信もさせたいので、botの登録をします

`Features > App Home > Your App's Presence in Slack`より **Edit**をおします
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/f53c0e1b-6b18-5ed3-d7c0-38b60fe92eb7.png" width=50%>

ポップアップが出てくるのでいい感じに名前を付けます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/83f0da17-ab70-aad5-141d-4e42dbd73d2d.png" width=50%>

これで完了です！

### アプリのインストールとtokenの確認

`Features > OAuth & Permissions > OAuth Tokens & Redirect URLs`　で 「Install to Workspace」を押して、**許可する**を押しましょう

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/f0ec714b-728a-3579-1532-d95999009620.png" width=50%>

すると`Features > OAuth & Permissions > OAuth Tokens & Redirect URLs`に
**OAuth Access Token**
が表示されるので、メモしておいてください (あとで使用します)

**(このtokenはセキュリティ的に重要なので、扱いには気をつけてください)**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/c69731a5-7f51-d3e0-ec39-0b422c5c95df.png" width=50%>

また、`Settings > App Credentials`より 
**Verification Token** 
もメモしてください (こちらもあとで使用します)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/1b763c28-3da0-1202-d213-853eb36681ca.png" width=50%>

これで一旦slack appの設定は完了です
このあとちょくちょくslack側での設定を行うので、このslack appのページは閉じないで下さい


## 2. スラッシュコマンドの作成
次はslackのスラッシュコマンドをgasで作成します
### スラッシュコマンドって？
slack上で使える簡単なコマンドです

`/remind`とかは使ったことのあるひとも多いのではないでしょうか?

`/remind ごみを捨てる tommorow`と送信すると...

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/45d05f1a-8436-42ff-b692-fee72ed588dc.png" width=50%>

ってできるやつです！
[参考](https://slack.com/intl/ja-jp/help/articles/208423427-%E3%83%AA%E3%83%9E%E3%82%A4%E3%83%B3%E3%83%80%E3%83%BC%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)

ここでは `/attendance`って入力するとメンバーの一覧が投稿される機能を作っていきます！

### 今回の作り方
流れはこのような形になっています

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/025f4754-46b3-a600-3077-067876d76878.png" >

なので、手順としては、

- google スプレッドシートにメンバーリストを用意する
- スプレッドシートにメンバーリストを送信するスクリプトを用意する
- slackからメンバーリストを使ってボタン付き出欠確認メッセージを送信できるようにする

の流れで作っていきます

### google スプレッドシートにメンバーリストを用意する
gasを始める前に会議に参加する人の一覧を作っておきましょう

- 新しいスプレッドシートは http://spreadsheet.new/ で作れます
- 今回は「ユーザー一覧 (users)」 と 「所属一覧 (groups)」の2つのシートを作ります
- ユーザー一覧には、`slack id`, `名前`, `所属 (グループ名とか)`の3列を用意します
- 所属一覧には、`所属一覧`の1列を用意します
- slack idの調べ方は[こちら](https://auto-worker.com/blog/?p=27)などを参考にcsvなどでどかっととってきましょう

**シートのイメージ**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/2e3f5924-80af-ed9c-6b02-2e67365f7dbf.png" width=50%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/6a7ab3d4-f71d-f03a-811b-52a3d8eafd6c.png" width=50%>



**この時以下の2点に注意してください**

- ユーザー一覧シートは
    - 一行目に 、`slack id`, `名前`, `所属`をいれる
    - シートの名前を`users`にする
- 所属一覧シートは
    - 一行目に 、`所属一覧`をいれる
    - シートの名前を`groups`にする

### スプレッドシートにメンバーリストを送信するスクリプトを用意する

まずはgasを開きましょう
先ほどのシートの `ツール > スクリプトエディタ `を選択します

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/c99ac4f1-a2a4-ba47-733c-999433b6aacd.png" width=50%>

するとこんなコードエディターが開きます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/3300a109-82c2-b9d8-d700-e0509baece36.png" width=50%>

ここに以下をコピペして保存します (コードの詳しい解説はskipします！！)


<details><summary> **コード.gs(ここをクリックして展開)** </summary><div>

```js:コード.gs
const slackToken = PropertiesService.getScriptProperties().getProperty("slack_token")
const verificationToken = PropertiesService.getScriptProperties().getProperty("verification_token")
const NotVerificateMessage = "許可されていません"
const usersSheetName = "users" // ユーザー一覧シート名
const groupsSheetName = "groups" //所属一覧シート名
const buttonValue = "attend"

// 以下の変数は適宜変更してください
const buttonText = "参加"
const notAttendComment = "まだ参加していない方↓"
const defaultMessage = "参加していますか？"
const responseData = "出欠確認をしました"
const textMessage = "出欠をとっています"

function doPost(e) {
  const params = e.parameter
  const token = params.token
  if (token != verificationToken) {
    return ContentService.createTextOutput(NotVerificateMessage).setMimeType(ContentService.MimeType.JSON);
  }
  const userName = params.user_name
  const channelId = params.channel_id
  let message = params.text
  if (!message) {
    message = defaultMessage
  }
  postAttendaceMessage(channelId, message, userName)
  return ContentService.createTextOutput(responseData).setMimeType(ContentService.MimeType.JSON);
}

function postAttendaceMessage(channelId, message) {
  // userのリストをとってきて
  const usersHash = usersHashByGroupname()
  // blockを作成して
  const blocks = makeBlockFromUsersHash(usersHash, message)
  // 決められたchannelに投稿する
  return postSlackByChannelwithBlocks(channelId, blocks)
}

// return {group_name: [user_id, user_id], group_name: [user_id,...], ..}
function usersHashByGroupname() {
  const rawSheetValues = getSpreadSheetByName(usersSheetName)
  let groupedHash = {}
  const rowGroupSheetValues = getSpreadSheetByName(groupsSheetName)
  rowGroupSheetValues.forEach(function (value) {
    if (value[0]) {
      groupedHash[value[0]] = []
    }
  })
  rawSheetValues.forEach(function (value) {
    if (value[0] && value[1] && value[2]) {
      groupedHash[value[2]].push("<@" + value[0] + ">")
    }
  })
  return groupedHash
}

function getSpreadSheetByName(sheetName) {
  //spreadsheetの値を返す
  const ss = SpreadsheetApp.getActiveSpreadsheet(); //スプレッドシートを取得
  const sheet = ss.getSheetByName(sheetName); //シート名でシートを取得
  const lastRow = sheet.getLastRow(); //最終行取得
  const lastCol = sheet.getLastColumn(); //最終行取得
  const firstRow = 2
  const firstCol = 1
  const range = sheet.getRange(firstRow, firstCol, lastRow, lastCol)
  const values = range.getValues()
  return values
}

function postSlackByChannelwithBlocks(channelId, blocks) {
  const postMessageurl = "https://slack.com/api/chat.postMessage"
  const payload = {
    "token": slackToken,
    "channel": channelId,
    "text": textMessage,
    "blocks": blocks
  }
  const params = {
    methods: 'post',
    payload: payload
  }
  return UrlFetchApp.fetch(postMessageurl, params)
}

function makeBlockFromUsersHash(usersHash, message) {
  const head = {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": message
    },
    "accessory": {
      "type": "button",
      "text": {
        "type": "plain_text",
        "emoji": true,
        "text": buttonText
      },
      "value": buttonValue
    }
  }
  const divider = {
    "type": "divider"
  }
  const notAttendMessage = {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": notAttendComment
    }
  }
  const mainBlocks = makeMainBlocksbyUserHash(usersHash)

  let blocks = []
  blocks.push(head)
  blocks.push(divider)
  blocks.push(notAttendMessage)
  blocks = blocks.concat(mainBlocks)
  blocks.push(divider)
  return JSON.stringify(blocks)
}

function makeMainBlocksbyUserHash(usersHash) {
  let mainBlocks = []
  Object.keys(usersHash).forEach(function (groupName) {
    if (usersHash[groupName].length > 0) {
      const context = {
        "type": "context",
        "elements": [{
          "type": "mrkdwn",
          "text": groupName
        }]
      }
      const usersSection = {
        "type": "section",
        "text": {
          "type": "mrkdwn",
          "text": usersHash[groupName].join(" ")
        }
      }
      mainBlocks.push(context)
      mainBlocks.push(usersSection)
    }
  })
  return mainBlocks
}
```

</div></details>



保存したら `ファイル > プロジェクトのプロパティ` を開きます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/603527dc-3f77-b880-16e3-b871f1e1f8c3.png" width=25%>

さらに `スクリプトのプロパティ`のタブに二つの値を設定します

|プロパティ|値|
|---|---|
|slack_token|`xoxp-`から始まる先ほどメモした **OAuth Access Token**|
|verification_token|先ほどメモした **Verification Token**|

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/7c18b10e-f3bf-c3fc-c81a-aa37fa9f412d.png" width=50%>

設定したら保存を押します

最後にapiとして公開します

`公開 > ウェブアプリケーションとして導入`を選択します

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/0e84b214-8412-18b4-ea98-6b25bad4c9e5.png" width=50%>

`Deploy as web app`というポップアップが出てくるので、以下のように設定します

|Project version|New (**ここをNewにしないと最新の更新が反映されないので注意！！**)|
|---|---|
|**Execute the app as**|**Me**|
|**Who has access to the app**|**Anyone, even anonymous**|

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/5f447e34-b0ee-04b2-514e-1ff1a7491467.png" width=50%>

そして**Deploy**を押すと初回は権限を確認されるので**許可を確認**を押します
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/faa595d7-af61-054d-957c-71fa14feaef5.png" width=50%>

アカウント選択画面が出るので、自分のアカウントを選択します

すると警告メッセージが出ますが、`詳細` (1) を押すと `script名 (安全ではないページに移動)` (2) が出てくるのでクリックします

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/8a8ee33a-eb77-e43e-6b51-31336e887835.png" width=50%>

`script名 が Google アカウントへのアクセスをリクエストしています`と表示されるので `許可`を押します

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/375d704a-54a5-e9e8-51ec-f9ec5db835a9.png" width=50%>

すると公開が完了します

完了後、`Deploy as web app`というポップアップと共に今回作成したapiのurlが表示されるのでこれを**メモします**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/492c4818-ec5f-ba1f-c39b-44a677b4a4d9.png" width=50%>

これでapiの作成が完了しました！！

### slackからメンバーリストを使ってボタン付き出欠確認メッセージを送信できるようにする
次にslackにスラッシュコマンドを用意します

slack apiの`Features > Slash Commands`より、**Create New Commands**を押します
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/415f4760-37cf-b805-68df-e53302ec8b23.png" width=50%>

そしたらそれぞれこんな感じで埋めていき、保存します

- Command : `/attendance`
- Request URL : さっき公開したURL
- Short Description, Usage Hint : いい感じに！！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/4c0f75d0-e2a1-17c1-4ac3-852404c03106.png" width=50%>


**これでスラッシュコマンドの登録が完了です！**

↓この段階でこんな感じのスラッシュコマンドが使えるようになっているはずです
![gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/12917275-49e2-55e8-5ef4-8a26bcbb3dfe.gif)

## 3.`参加`ボタン用apiの作成
さて残るはボタンを押した時のアクションのみです

**参加**ボタンを押すとslackのメッセージが変わるアクションを作っていきます
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/15598e2c-6c94-5e73-658e-b940500888e4.gif">

ボタンを押してからメッセージが更新される流れはこんな感じです

![flow](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/258669d4-4781-689b-3598-30759c955a88.png)



### apiの作成
ボタンアクション用のapiもgasで作っていきます
先ほどとは別の新しいscriptを用意しましょう => http://script.new/

ここに以下をコピペして保存します (コードの詳しい解説はskipします！！)



<details><summary> **コード.gs(ここをクリックして展開)**</summary><div>

```js:コード.gs
const slackToken = PropertiesService.getScriptProperties().getProperty("slack_token")
const verificationToken = PropertiesService.getScriptProperties().getProperty("verification_token")

const replaceEmoji = ":white_check_mark:" // 好きな絵文字に置き換える！
const attendButtonValue = "attend"
function doPost(e) {
  const payload = JSON.parse(e["parameter"]["payload"])
  const token = payload.token
  if (token != verificationToken) {
    return
  }
  const action = payload.actions[0]["value"]
  if (action === attendButtonValue) {
    return clickedAttendButton(payload)
  }
}


function clickedAttendButton(payload) {
  const originalBlocks = JSON.stringify(payload.message.blocks)
  const messageTs = payload.container.message_ts
  const channelId = payload.container.channel_id
  const userId = payload.user.id
  const user = "<@" + userId + ">"
  const replacedBlocks = originalBlocks.replace(user, replaceEmoji)
  const chatUpdateUrl = "https://slack.com/api/chat.update"
  const updatePayload = {
    "token": slackToken,
    "channel": channelId,
    "ts": messageTs,
    "blocks": replacedBlocks
  }
  const updateParams = {
    methods: 'post',
    payload: updatePayload
  }
  UrlFetchApp.fetch(chatUpdateUrl, updateParams)
  const params = {
    "replace_original": false,
    "text": ""
  }
  return ContentService.createTextOutput(JSON.stringify(params)).setMimeType(ContentService.MimeType.JSON);
}


```
</div></details>



保存したら先ほどと同様にtokenの設定を行います
 `ファイル > プロジェクトのプロパティ` を開きます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/603527dc-3f77-b880-16e3-b871f1e1f8c3.png" width=50%>
 
さらに `スクリプトのプロパティ`のタブに二つの値を設定します

|プロパティ|値|
|---|---|
|slack_token|`xoxp-`から始まる先ほどメモした **OAuth Access Token**|
|verification_token|先ほどメモした **Verification Token**|


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/7c18b10e-f3bf-c3fc-c81a-aa37fa9f412d.png" width=50%>

**完了したらスラッシュコマンド作成時と同様にapiとして公開してください** 
(この記事の` 2. スプレッドシートにメンバーリストを送信するスクリプトを用意する` を参考にしてください)

### slack apiへの登録
早速作成したapiをslackに登録していきます

`Features > Interactivity & Shortcuts`のページにいきます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/8b13632a-af51-abf0-62b7-990a2b218a59.png" width=50%>

この`Interactivity`を **Off** → **On** にするといろいろなメニューが展開されます

そして`Interactivity`の`Request URL`に先ほど作成したapiのurlを貼り付けます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/38ccac50-eb81-d09e-05bc-301fea01819f.png" width=50%>

そして、右下の **Save Changes**を押せば完了です！！

これで**参加**ボタンを押すと自分の名前が ✅ に変更されるようになりました！！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/15598e2c-6c94-5e73-658e-b940500888e4.gif">

### 完成！！

長かったですね...
お疲れ様です！！

# 応用編
## 毎週月曜日の朝9時に出欠をとりたい！
[この記事](https://tonari-it.com/gas-trigger-set/)が参考になるかと思います

今回作成した、`/attendance`用コードを応用しましょう

例えば、下記のような関数を作成し、上記の記事のようにtriggerを設定してあげれば定例オンライン会議の度に`/attendance`をしなくてもよくなります！! (**さらなる無駄の解消**)

```js:コード.gs
function postAttendaceMessageEveryMonday() {
  const channelId = "channel id"
  const message = "月曜日の定例へ参加お願いします！ https://meet.google.com/xxx-xxxx-xxx"
  postAttendaceMessage(channelId, message)
}
```

## 出欠確認メッセージをもっとモチベーションがあがる形にしたい！
slackが用意している [Block Kit Builder](https://app.slack.com/block-kit-builder/)を使えばアウトプットをイメージしながら作ることができます！

## ボタンを押したらもっと可愛い絵文字になるようにしたい！
自分のワークスペースでカスタム絵文字を設定して見ましょう

`参加`ボタン用api のソース内部の `replaceEmoji`という変数を追加したカスタム絵文字にすることでより愛着が湧きます


参考:
- [slackでカスタム絵文字を設定する](https://slack.com/intl/ja-jp/help/articles/206870177-%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AB%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E7%B5%B5%E6%96%87%E5%AD%97%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)
- [カスタム絵文字ジェネレーター](https://emoji-gen.ninja/)


↓弊社ではなぜか`参加`ボタンを押すと自分が**坊主**になります...???
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/885081/f3e08265-2a25-415a-afd3-ac46a54a0182.png)


# 終わりに
書き終わって見ると、この記事が想像の3倍の量になって驚いています...
slack appだったり、google app scriptはリファレンスを読んでいると、「あれ、面倒だったこれもアプリにやらせるんじゃないか？？」がいろいろ湧いてきて楽しいですね。
面倒なルーティーンはアプリに任せて、自分は新しいことを勉強する時間に使っていきたいですね！！

# 参考
- [google app script のReference](https://developers.google.com/apps-script/reference?hl=ja)
- [slack api のReference](https://api.slack.com/reference)
- [Slackワークスペースに属するユーザーのメンバーIDを調べる方法・手順を解説](https://auto-worker.com/blog/?p=27)
- [リマインダーを設定する](https://slack.com/intl/ja-jp/help/articles/208423427-%E3%83%AA%E3%83%9E%E3%82%A4%E3%83%B3%E3%83%80%E3%83%BC%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)
- [ワークスペースにカスタム絵文字を追加する](https://slack.com/intl/ja-jp/help/articles/206870177-%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AB%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E7%B5%B5%E6%96%87%E5%AD%97%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)
- [Google Apps Script で毎日決まった時刻にスクリプトを実行するトリガー設定](https://tonari-it.com/gas-trigger-set/)