【GAS】はてブエゴサ（監視・通知）システムつくった

自分のサイトのはてなブックマーク、どれぐらいついたのか監視したくなりました。
ので作りました。


## 基本方針の確認
先人の知恵を借りましょう。
おおよそのロジックは [うわっ…私のはてブ数、低すぎ…?GASを使って、はてブ数を監視\(通知\)する方法\! \| コンパス](https://routecompass.net/auto-hatena/)
 という記事の実装がよかったので参考にしました。つまり…

- はてブのRSSを叩いてパースし、シートに書き入れる
- **なんらかの手段で** はてブ数をカウントする
- すでに記入済みのものとの差分をメッセージとしてスタックする
- 取得したカウント数でシートを更新する
- Slackで通知する

上述のような流れです。
ここで、前述の記事では **はてブAPI** を使っています。
が、HTTPS化に伴ってCORS許可がされてないようなのでエラーになってしまいます。
（公開APIとは……なにかの間違いであってほしいのですが）


そこで、 [Qiitaの指標をGASで集計してNotion Chartsで可視化する \- Qiita](https://qiita.com/ryo2132/items/74a582aaa5b04d848654) この記事を参考にはてブ数を取得します。


## やってみた
<img width="680" alt="スクリーンショット 2020-08-05 23.33.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122390/4061c064-5346-f29d-4694-fd5d615531c7.png">


## ソースコード
Googleスプレッドシートを作り、hatenaというシートを作ります。
スクリプトエディタへソースコードをコピペし、
シートのURLとSlackの incoming webhookのURL、対象URLを差し替えてください。
hatenaChecker 関数を時間トリガーで実行するようにしてください。
私は1時間毎に実行しています。

```gas.js
// 書き込み先シート名
const sheetName = "hatena"; 
// 自分のシートのURLに合わせる
const sheet = SpreadsheetApp.openByUrl(
  "https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxx/edit#gid=0"
).getSheetByName(sheetName); 
// Slackのincoming WebhookのURLに書き換える
 const incomingWebhookURL =
 "https://hooks.slack.com/services/XXXXXXXX/XXXXXXX/xxxxxxxxxxxxxxxxxx";

// 配列で複数指定できます
const targetUrls = [
//  "https://qiita.com/fruitriin",
//  "https://note.com/fruitriin",
];

const header = "■はてブ増減チェック\n";

function hatenaChecker() {
  addUrl();
  const message = createMessage();
  sendSlack(message);
}

function createMessage() {
  const rows = sheet.getSheetValues(1, 1, sheet.getLastRow(), 3);
  let message = "";
  let rowIndex = 1;
  for (let row of rows) {
    const url = row[0];
    const title = row[1];
    const newCount = fetchBookmarkCount(url);
    const oldCount = row[2];

    // 変化がなければスキップ
    if (oldCount == newCount) {
      rowIndex++;
      continue;
    }

    // 新規追加
    if (oldCount == "") {
      message += `新規：${newCount} |${slackLink(title, url)}\n`;
    } else {
      // 増減
      if (newCount > oldCount) {
        message += `+${newCount - oldCount} |${slackLink(title, url)}\n`;
      } else {
        message += `-${oldCount - newCount} |${slackLink(title, url)}\n`;
      }
    }
    sheet.getRange(rowIndex, 3).setValue(newCount);
    rowIndex++;
  }
  return message;
}
function slackLink(text, url) {
  return `<${url}|${text}>`;
}

function addUrl(url) {
  //urlを自動で追加
  const atom = XmlService.getNamespace("http://purl.org/rss/1.0/");
  let items = [];
  for (let url of targetUrls) {
    const feed_url =
      "http://b.hatena.ne.jp/entrylist?url=" + url + "&mode=rss&sort=eid";

    //たまにエラーになるらしい
    try {
      var document = XmlService.parse(
        UrlFetchApp.fetch(feed_url).getContentText()
      );
    } catch (e) {
      //新規取得URL取得は諦めて続行
      return;
    }

    items = [...items, ...document.getRootElement().getChildren("item", atom)];
  }

  const hatebus = [];
  for (let item of items) {
    hatebus.push({
      url: item.getChildText("link", atom),
      title: item.getChildText("title", atom),
    }); //urlを取得
  }

  const last_row = sheet.getLastRow();
  const sheet_url = [];

  // すでにシートに書き込まれているURLを配列に詰める
  for (let i = 1; i <= last_row; i++) {
    sheet_url.push(sheet.getRange(i, 1).getValue());
  }
  // 新規のURLについてはシートに書き込む
  for (let hatebu of hatebus) {
    if (sheet_url.indexOf(hatebu.url) == -1) {
      sheet.getRange(sheet.getLastRow() + 1, 1).setValue(hatebu.url);
      sheet.getRange(sheet.getLastRow(), 2).setValue(hatebu.title);
    }
  }
}

// ブックマークカウントの集計
function fetchBookmarkCount(url) {
  const redirectUrl = getRedirectUrl(`http://b.hatena.ne.jp/bc/${url}`);
  // `https://b.st-hatena.com/images/counter/default/00/00/0000653.gif` の形式で
  // ブクマ数が書かれたgif画像のURLを取得できるので、そこからブクマ数だけを抽出する
  const bookmarkCount = redirectUrl.match(
    /https:\/\/b.st-hatena\.com\/images\/counter\/default\/\d+\/\d+\/(\d+).gif/
  )[1];

  return Number(bookmarkCount);
}

// リダイレクトURLの取得
function getRedirectUrl(url) {
  try {
    const response = UrlFetchApp.fetch(url, {
      followRedirects: false,
      muteHttpExceptions: false,
    });
    const redirectUrl = response.getHeaders()["Location"];
    if (redirectUrl) {
      const nextRedirectUrl = this.getRedirectUrl(redirectUrl);
      return nextRedirectUrl;
    } else {
      return url;
    }
  }catch (e ){
    return;
  }
}

function sendSlack(message) {
  if (message == "") return;

  var payload = {
    text: header + message,
  };

  const params = {
    contentType: "application/json",
    method: "POST",
    payload: JSON.stringify(payload),
  };

  var response = UrlFetchApp.fetch(incomingWebhookURL, params);
}
```

## 雑感
まとめて範囲選択して二重配列で読み書きを行うとパフォーマンスが段違いに上がるのですが
動いたところで満足してしまったので修正してません
なんかたまに例外を吐いて死にます。try catchが全部できてないらしい。
JSで書いてますがTSのほうがGAS書くの簡単だと思います。

## 近い話題の記事
- [Google App Script\(GAS\)をローカルで快適に編集して同期しよう！ \- Qiita](https://qiita.com/fruitriin/items/62120f102d50dce5c51a)
- [IFTTTで（特にTwitterを）エゴサする工夫 \- Qiita](https://qiita.com/fruitriin/items/9206bb1849b9051e0ec2)