web漫画の更新を検知しdiscordに通知するプログラムをFirebaseにデプロイする

web漫画の更新を掲載をまとめているサイトはあるものの通知などを行っている所がなかったため作ってみました。（自分の読んでいる漫画のみ）

作るにあたって苦労した点を中心に書いていこうと思います。

# 全体構成
![comic_notificationのコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181033/22daa5fb-fa21-52f5-9dcf-e039f1b0b933.png)


# Cloud Functions | Pub/Sub
[公式ドキュメント](https://firebase.google.com/docs/functions/schedule-functions?hl=ja)を参考に設定していきます。
料金プランをBlazeプランにアップグレードし、プロジェクトで Google Cloud Pub/Sub と Cloud Scheduler API を有効にします。

設定ができたら実行したい関数に、1時間毎の場合下記のように記述します。

```javascript:index.js
exports.scrapeWebComic = functions.pubsub
  .schedule("every 1 hours")
  .timeZone("Asia/Tokyo")
  .onRun(async (context) => {
    console.log('This will be run every 1 hours!');
    return null;
  }
```


# スクレイピング

※各web漫画のページのデータを取得するコードを書くのはかなり骨が折れるので、web漫画の更新をまとめている某サイト様に条件付きで許可をいただきデータ取得を行わせていただいてます。

スクレイピングはpuppeteerで行います。

puppeteerをCloud Functionsで使う際に2つほどエラーと遭遇したのでその内容と解決方法を説明していきます。

### puppeteer ver3.0.0以降で起こるエラー

puppeteer 3.0.0以上ではlibgbm1をインストールする必要があります。
[こちら](https://github.com/puppeteer/puppeteer/issues/5704)のissueで話されているように、今はver3未満を使うことで解決できます。

```json:package.json
{
  "dependencies": {
    "puppeteer": "^2.1.1"
  }
}
```

### Error: memory limit exceeded. Function invocation was interrupted. 

メモリ制限を超えたため、関数の呼び出しを停止した。というエラーなのでCloud Funcstionsから使用メモリを変更することで解決します。

GCPのCloud Functionsから対象の関数を選び編集から割り当てられるメモリを256Mibから512Mibに設定します。
![スクリーンショット 2020-07-09 16.57.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181033/2b7b9ab5-ef28-c5dc-dad0-59884ad40399.png)

# Firestore
### データモデルの構成
| ドキュメント       |  場所 |   管理するコレクション  |
|:-----------------|:------------------|:------------------|
| comics           |データベースのルート    |  comicsコレクション   |
| episodes           |  comicsドキュメントの配下|     episodesコレクション      |

実際にはこのような構成なります。

![スクリーンショット 2020-07-10 2.00.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181033/974376f7-cbc5-dbea-422f-fde18f0a28e1.png)

データモデルが決まったので、データ追加時の処理について説明していきます。

スクレイピングで取得した配列データを、ｍapで処理しています。

```typescript
await Promise.all(data.map(async (d) => await setFirestore(d)));

const setFirestore = async (data: ComicData) => {
  const episodeRef = fireStore
    .collection("comics")
    .doc(data.titleData.name)
    .collection("episodes")
    .doc(data.nameNonSlash.episode);
  // 取得したデータをgetで取得しに行きます
  episodeRef
    .get()
    .then(async (doc: any) => {
  // Firestoreにデータがなかった場合（新規データ）だった場合の処理
      if (!doc.exists) {
        // firestoreに保存
        await episodeRef.set(data.episodeData);
      }
    })
    .catch((err: any) => {
      console.log("Error getting document", err);
    });
};
```

ここで注意点ですが、Firestoreのドキュメントにはスラッシュ(/)を含むことができないので、取得したデータのスラッシュを置換する必要があります。
今回はスラッシュをハイフンにすることにします。

```javascript

  comicEpisodeNonSlash = comicEpisodeEn.replace(/\//g, "-");
```

余談ですがスラッシュを含む文字列をFirestoreの関数の引数に渡すこともできますが以下のような動作なります。

```javascript
//以下２つは同義
let alovelaceDocumentRef = db.collection('users').doc('alovelace');
let alovelaceDocumentRef = db.doc('users/alovelace');
```

# webhookでDiscordに通知

Discordのチャンネル設定からｗebhookを作成しwebhookURLを取得します。
後は新規データ追加時に、通知したいデータを入れてaxiosでPOSTするだけです。


```javascript
  const discord_url = functions.config().discord.webhook;

  const config = {
    headers: {
      Accept: "application/json",
      "Content-type": "application/json",
    },
  };

  episodeRef
    .get()
    .then(async (doc: any) => {
      if (!doc.exists) {
        //送信するデータ
        const postData = {
          username: "webcomic BOT",
          content: `${data.titleData.name}\n${data.episodeData.name}\n${data.episodeData.url}`,
        };

        // web hookでdiscordに通知
        await axios.post(discord_url, postData, config);
        // firestoreに保存
        await episodeRef.set(data.episodeData);
      }
    })
```

Discord BOT等で検索しても対話型のBOTばかりヒットするのでwebhookにたどり着くまでが大変でした。


<img width="375" alt="IMG_CE44A46BA69C-1 2.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181033/437073b9-5963-2d46-9243-ad935b86ad68.jpeg">
## 終わりに

ざっくりと説明しましたがコードは[こちら](https://github.com/hironakazuki/webcomic-notification)になります。

私の読んでいる漫画のみの通知ですがもし入りたい方がいれば[discord](https://discord.gg/NHHMGBD)

# 参考リンク
https://qiita.com/kboy/items/47393940a9c8b16278dc
https://github.com/puppeteer/puppeteer/issues/5674
https://github.com/yamam00s/scraping-baseball-data/issues/4