3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-09

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

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

全体構成

comic_notificationのコピー.png

Cloud Functions | Pub/Sub

公式ドキュメントを参考に設定していきます。
料金プランをBlazeプランにアップグレードし、プロジェクトで Google Cloud Pub/Sub と Cloud Scheduler API を有効にします。

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

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をインストールする必要があります。
こちらのissueで話されているように、今はver3未満を使うことで解決できます。

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

Firestore

データモデルの構成

ドキュメント 場所 管理するコレクション
comics データベースのルート comicsコレクション
episodes comicsドキュメントの配下 episodesコレクション

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

スクリーンショット 2020-07-10 2.00.30.png

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

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

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


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

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

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

webhookでDiscordに通知

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

  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_CE44A46BA69C-1 2.jpeg ## 終わりに

ざっくりと説明しましたがコードはこちらになります。

私の読んでいる漫画のみの通知ですがもし入りたい方がいればdiscord

参考リンク

https://qiita.com/kboy/items/47393940a9c8b16278dc
https://github.com/puppeteer/puppeteer/issues/5674
https://github.com/yamam00s/scraping-baseball-data/issues/4

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?