19
15

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.

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

Last updated at Posted at 2020-08-05

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

基本方針の確認

先人の知恵を借りましょう。
おおよそのロジックは うわっ…私のはてブ数、低すぎ…?GASを使って、はてブ数を監視(通知)する方法! | コンパス
という記事の実装がよかったので参考にしました。つまり…

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

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

そこで、 Qiitaの指標をGASで集計してNotion Chartsで可視化する - Qiita この記事を参考にはてブ数を取得します。

やってみた

スクリーンショット 2020-08-05 23.33.46.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書くの簡単だと思います。

近い話題の記事

19
15
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
19
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?