Slackで仮想本棚botを作って部署内コミュニケーションを促進したい！


[Ateam Brides Inc. Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ateam-brides)の23日目は
株式会社エイチームブライズ の馬渕 @MAB_cx が担当します！


# 私の所属するチーム「DX推進部」

- 私は、ビジネス職種でプログラミング未経験のよわよわ会社員です。
    - そんな会社員が紆余曲折あり、DX推進部に所属する事になりました。
    - DXって…！と思いながら早3年。このあたりの紆余曲折は、またどこかで記事にしたいと思います。
- 当社は、デザイナやエンジニアとの距離が近く、企画や事業活動に対して一緒に取り組む事が多く、<br>DX推進部も例外ではありません。
    - 企画・PM担当が50%、エンジニアが50%で構成されており、システムについて一緒に考える日々。
    - 勉強しながら、私自身も、Saasを用いたノーコード開発やGASでの効率化に夢中な今日この頃です。

# 今回のアドベントカレンダーは…

- DX推進に携わり、システムについて思いを馳せる事も多くなった3年前から、<br>アドベントカレンダーには毎年参加しています。
- 今年は、この機会に、コードの開発を疑似体験しようと思います！

# DX推進部が抱える由々しき事態

- 突然ですが、リモートだと、本の貸し借りって難しいですよね。
- DX推進部は、リモートワークが主流になる前は「DX文庫」と称して<br>本を持ち寄って、紹介や貸し借りを頻繁に行っていました。
    - ※DX推進部に限らず、会社全体でその文化は根強いです。

- DX文庫の一部　↓（カバーを外す癖のある私）
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/b96f9909-5d6f-4abf-6130-a9cd90f6e8ca.jpeg">
- DXな図書カード　↓　
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/f275d110-d202-4168-7dec-54962ce3a844.jpeg">

- リモートワークが主流になってしまい、貸し借りも難しい今、<br>誰かのおすすめ、やこの本よかった、というような<br>良本のインプットが困難になってしまったと思いませんか？



- テキストで、この本よかったよ～とお勧めする事はあっても、たいてい共有はURLです！
    - 後から本のタイトルを思い出せない…検索もできない…
    - これは由々しき事態です。
    - そこで決めました。

# 仮想本棚bot「DXな本棚」を作ろう！

- 目指す姿は4段階です。

### レベル１
- 定期でSlackでおすすめの本がレコメンドされるようにしたい
    - 目指すは部署内コミュニケーション活性化！インプット促進！
        - 当社はコミュニケーションにSlackを活用し始めたところで、色々と機能を試したいタイミングでもあります。
        - GASと組み合わせたbotも手軽に作れそうです。楽しそうです。

### レベル２
- Slackからおすすめの本を登録できるようにしたい
    - アウトプット促進！
    - コミュニケーションチャネルで投稿が出来る、というのは<br>URLで共有する＝後から検索できない、を防ぐ良い手立てのように思えました。
            - 私がSlackのワークフローを使ってみたい！という下心もあります。

## 今回目指す姿

- レベル2までを図示すると、こんな感じになります。

![advent (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/d1d26094-3f0f-3c3c-d7d5-eb750bfae4af.png)




## ここまで行けたら嬉しい姿

### レベル３ 

- 気になるキーワードをつぶやいたら、メンバーが登録した本がレコメンドされるようにしたい


### レベル４ 

- できれば登録ランキングを毎月流して登録＆読書を促進したい

---

- ここまで行きつけば、DX推進部をはじめとする当社の読書習慣は強固なものになる事でしょう。
    - ただし、ここまで作りこむには私には相当な時間とスキルの上達が必要と想定されるため、<Br>また別の機会にしたいと思います。


# 前提

- この記事は、初心者の私がGASやSlackの理解促進のため、<br>インプットと同時にアウトプットをすることによる定着を目指す記事となっています。
    - アドバイスやご指摘等お待ちしています
- そのため、回りくどいやり方をしていたり、参考にならない部分も多々あるかと思います。
- 何卒ご容赦ください。


- また、今回、先輩（@rf_p）に意味などを教えていただきながら作成しました。
    - 先輩は、かつて1人アドベントカレンダーなる修行を実践し、<br>「GoogleAppScript」にまつわる記事で埋め尽くした私の師匠です。

https://qiita.com/advent-calendar/2017/gas_beginner


- 今回は自分が学ぶ用に作っているため、<br>コードの中に師匠にヒアリングした意味などが入っている事があります。
    - ※メモを取り除いたコードも添えておきます。

# 今日はレベル１に挑戦！

- まずはデータが集まる所の構築を先に進めます。
    - Slackで集める代わりに、<br>何も設定しなくてもスプレッドシートにデータを蓄積してくれるGoogleフォームを代用します。

![level1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/d84ec3f4-1ec0-36f2-deaa-9313087e2921.png)



## GoogleBooksAPIでISBNコードから書式情報を取得する

まずはこの部分を作成します。
![level1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/f93b3c7c-e13b-7f29-02ac-ab482a9baa60.png)


- 登録を促進したいので、**登録する情報は少なければ少ないほどいい** という事を突き詰めた結果、<br>「ISBN」だけ入力してもらう、という方法を採用することに。
    - タイトル、著者名などは、GoogleBooksApiから取ってくる、という手法です。<br><br>
- こちらの記事を参考にさせていただきました。


https://qiita.com/mgmgmogumi/items/5e45ee88c217cd072ea4

最終的にはこのようなテーブルになります。
薄い青色の部分が、APIによって入力してもらう部分です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/1cbdabf2-cdeb-37e1-2dff-208484886b3c.png)




```javascript
const ISBN_COLUMN = 5; 
//ISBNが入っているセルは5列目に位置する 
//constは定数。再代入はできない。
//letは変数。代入も可能。
//varも変数だが、古い。

function onEdit2(e){　//eに編集されたデータが入っている
  // 編集カラムが5列目であればsetBookInfo関数を呼び出す
  const range = e.range;
  for (let row = range.getRow(); row <= range.getLastRow(); row++) {
    // for文は繰り返しを意味する構文。forは1行で書くのが良い。
      // getRowで初めの行をとり、getLastRowで最終の行をとる、row++はループを回すごとに＋１を足す
    for (let column = range.getColumn(); column <= range.getLastColumn(); column++) { 
      // getColumnで初めの行をとり、getLastColumnで最終の行をとる、column++はループを回すごとに＋１を足す
      if (column == ISBN_COLUMN) {
        setBookInfo(row); //setBookInfoのなかに、rowの値を渡す
      }
    }
  }
}

function setBookInfo(row){
  Logger.log(row);
  const sheet = SpreadsheetApp.getActiveSheet(); // 開いているシートのオブジェクトを取得
  const isbn = sheet.getRange(row, ISBN_COLUMN).getValue(); // 更新した行の5列目（ISBNが入っているセル）の値を取得し、変数isbnに代入

  // Google Books APIを叩いてレスポンスを取得  
  const response = UrlFetchApp.fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:' + isbn + '&country=JP');
  const data = JSON.parse(response.getContentText()); //JSON形式のレスポンスをオブジェクトとしてパース
  const bookInfo = data.items[0].volumeInfo; //本の情報を取得

  sheet.getRange(row,6).setValue(bookInfo.title); // 8列目にタイトルを挿入
  sheet.getRange(row,7).setValue(bookInfo.canonicalVolumeLink); // 9列目にリンクを挿入
  sheet.getRange(row,8).setValue(bookInfo.authors.join()); // 10列目に著者を挿入
  sheet.getRange(row,9).setValue(bookInfo.publishedDate); //11列目に発行日を挿入

  }


```

#### トリガー設定はこちら

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/6cdf7c86-74fc-a984-fc15-3d86fcdfd75b.png">

## メモがないバージョン


<details><summary>こちらをクリック</summary><div>

```javascript
const ISBN_COLUMN = 5; 

function onEdit2(e){　
  const range = e.range;
  for (let row = range.getRow(); row <= range.getLastRow(); row++) {
    for (let column = range.getColumn(); column <= range.getLastColumn(); column++) { 
      if (column == ISBN_COLUMN) {
        setBookInfo(row); 
      }
    }
  }
}

function setBookInfo(row){
  Logger.log(row);
  const sheet = SpreadsheetApp.getActiveSheet(); 
  const isbn = sheet.getRange(row, ISBN_COLUMN).getValue(); 

  const response = UrlFetchApp.fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:' + isbn + '&country=JP');
  const data = JSON.parse(response.getContentText()); 
  const bookInfo = data.items[0].volumeInfo; 

  sheet.getRange(row,6).setValue(bookInfo.title);
  sheet.getRange(row,7).setValue(bookInfo.canonicalVolumeLink);
  sheet.getRange(row,8).setValue(bookInfo.authors.join());
  sheet.getRange(row,9).setValue(bookInfo.publishedDate); 

  }

```

</div></details>

# 詰まったところ

Rangeの感覚がつかめないなど、苦戦したところもありますが、
特に難しかったのは↓のエラーです。

:::note alert
エラー Exception: UrlFetchApp.fetch を呼び出す権限がありません。
必要な権限: https://www.googleapis.com/auth/script.external_request
:::

https://www.tksoft.work/archives/5014

この記事で原因は特定できたものの
標準のイベントをトリガーにしていたため、なかなかエラーから抜け出す事ができませんでした。
結局、onEditからonEdit2に変更しました。
（上記コードがonEdit2になっているのはその理由です）

>onOpen , onEdit など 標準のイベント から UrlFetchApp.fetch が呼びされている場合は何をしてもエラーとなる。この場合は ソースで onEdit2 など別名に変更し，「トリガー」に登録させる。onEditのままトリガーに登録させることもできるが，この場合2回実行されてしまい片方がエラーとなる。


## 師匠（@rf_p）からフィードバック頂いた、残り考慮すべき部分

1. エラーが出たとき（responseの中身が欠けている時）の回避が定義できていない。
    - レベル２でバージョンアップできれば挑戦！
1. functionの中に詰め込みすぎると、メンテがしづらいので分割したほうが良い。
1. sheet.getRange(row,6).setValue(bookInfo.title) は通信数が多い。
    - 重くなる可能性があるので、setValuesでまとめて処理する方が良い。
        - （初心者がそこまでやってたらビビる）
    - 今回はリアルタイム性を求めていない＆処理数も多くないので妥協とします。


# レベル１、終了！

まさか動くとは…という感動もさておき、ここから折り返しです！

レベル２は追って追記します！
①Slackから投稿
②Slackへ通知　にチャレンジします！

![advent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224146/87342c1f-7bfc-20cd-e6bc-a1ea9ab4654e.png)




[Ateam Brides Inc. Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ateam-brides)の24日目は、
我らが代表が聖夜にふさわしいスペシャルな記事をお送りします！！