Google Apps Scriptで簡単作成!ブログにいいねボタンを設置する方法

更新  
公開
当サイトはアフィリエイト広告を使用しています

Blogger Google Apps Script web



フロントエンドはHTMLとJavaScript、バックエンドはGoogleスプレッドシートとGoogle Apps Script(以下GAS)を使用しました。

ボタンをクリックすると、記事のタイトルとURLがGAS経由でスプレッドシートに書き込まれます。 書き込まれたURLは集計され、webサイトを開いたときにその集計結果が表示されます。

以降、Googleスプレッドシート、GAS、HTML、JavaScriptの順で説明します。

目次

Googleスプレッドシート

Googleアカウントにログインした状態のブラウザで、Googleスプレッドシートを開きます。

シート名は初期値の「シート1」、「シート2」にしました。

シート名を変更した場合は、「シート2」に入力する関数のシート名も変更してください。

シート1

シート1は送られたデータが書き込まれます。

A1、B1、C1セルは見出しを入力します。

  • A1セル:DATE
  • B1セル:TITLE
  • C1セル:URL

シート2

シート2でシート1のデータを集計します。

A1、B1セルは見出しにしました。

  • A1セル:URL
  • B1セル:COUNT

次に、A2、B2セルに関数を入力します。

A2セル:

=UNIQUE('シート1'!C2:C)

式の内容:A列のA2セル以降に、シート1のC列にあるURLを重複なしで集計します。

B2セル:(2025.2.13:変更)

=ARRAYFORMULA(IF(A2:A="",,IFERROR(VLOOKUP(A2:A,QUERY(ARRAYFORMULA(LOWER('シート1'!C2:C)),"select Col1, count(Col1) group by Col1 label count(Col1) 'Count'",0),2,FALSE),0)))

式の内容:B列のB2セル以降に、シート1のC列にある各URLの出現回数をカウントし、シート2のA列にある対応するURLの回数を表示します。

URLは大文字小文字を区別しません。以下は同じURLとして扱われます。(2025.2.13:変更)

https://example.com/apple

https://example.com/APPLE

データ量がそれほど多くない場合は、COUNTIFS関数とFILTER関数を組み合わせる方法も有効です。

=ARRAYFORMULA(IF(A2:A="",,COUNTIFS(ARRAYFORMULA(LOWER(FILTER('シート1'!C2:C,'シート1'!C2:C<>""))),LOWER(A2:A))))

大文字小文字を区別する場合:(2025.2.13:追加)

処理速度が遅くなりますが

=ARRAYFORMULA(IF(A2:A="",,MAP(A2:A,LAMBDA(検索値, SUMPRODUCT(EXACT(FILTER('シート1'!C2:C,'シート1'!C2:C<>""),検索値))))))

C2:C1000など、シート1のC列の範囲を狭くすれば改善すると思います。

Google Apps Script(GAS)

GASは「デプロイ」という作業をします。はじめての方は、こちらのページを一読するとおおまかな手順が理解できます。😀

簡便なURLチェック機能を設けました。

「いいね」ボタンを設置したwebサイトのURLをYour_website_urlのところに入れてください。

例:https://example.com/の場合は、example.comと入力。

(最後のスラッシュ/もいりません。)

function checkDomain(url, allowedHostname = "Your_website_url")

function checkDomain(url, allowedHostname = "example.com")

Google Apps Script:(2025.2.13:変更)

/**
 * webサイトから送られた記事のタイトル、URL、およびプログラムで取得した日時をスプレッドシートに書き込みます。
 * 書き込まれたURLは集計され、webサイトを開いたときに集計された数が送信されます。
 *
 */
/*! Copyright:2025 sutajp | Released under the MIT license | https://sutasutashiki.blogspot.com/p/mit-license.html */

function createJsonResponse(object) {
  return ContentService.createTextOutput(JSON.stringify(object)).setMimeType(
    ContentService.MimeType.JSON
  );
}

function checkDomain(url, allowedHostname = "Your_website_url") {
  try {
    const match = url.match(/^https?:\/\/([^/]+)/);
    if (match) {
      const hostname = match[1];
      if (hostname === allowedHostname) {
        return true;
      } else {
        Logger.log(`Invalid domain: ${url} (Hostname: ${hostname})`);
        return false;
      }
    } else {
      Logger.log(`Invalid URL format: ${url}`);
      return false;
    }
  } catch (error) {
    Logger.log("URL処理エラー: " + error);
    return false;
  }
}

function doGet(e) {
  try {
    const url = e.parameter?.currentURL;
    if (!url) {
      return createJsonResponse({
        status: "error",
        message: "URLパラメータが不足しています。",
      });
    }
    if (!checkDomain(url)) {
      return createJsonResponse({
        status: "Invalid domain.",
        message: "不正なドメインです。",
      });
    }

    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
    const lastRow = sheet.getLastRow();
    let count = 0;

    if (lastRow >= 2) {
      const data = sheet.getRange(2, 1, lastRow - 1, 2).getValues();
      const urlMap = new Map(data.map((row) => [row[0], row[1]]));
      count = urlMap.get(url) || 0;
    }

    return createJsonResponse(count);
  } catch (error) {
    Logger.log("doGetエラー: " + error); // doGetのエラーログを明確化
    return createJsonResponse({
      status: "error",
      message: "doGet処理でエラーが発生しました。",
    }); // より一般的なエラーメッセージ
  }
}

function doPost(e) {
  try {
    const title = e.parameter?.sendTitle;
    const url = e.parameter?.sendURL;
    if (!title || !url) {
      // titleまたはurlがない場合のエラー処理を追加
      return createJsonResponse({
        status: "error",
        message: "TitleまたはURLパラメータが不足しています。",
      });
    }

    if (!checkDomain(url)) {
      return createJsonResponse({
        status: "Invalid domain.",
        message: "不正なドメインです。",
      });
    }

    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
    const lastRow = sheet.getLastRow();
    const nextRow = lastRow + 1;

    const formattedDate = Utilities.formatDate(
      new Date(),
      Session.getScriptTimeZone(),
      "yyyy/MM/dd HH:mm:ss"
    );

    sheet.getRange(nextRow, 1, 1, 3).setValues([[formattedDate, title, url]]);

    return createJsonResponse({
      status: "success",
      message: "いいね!を送信しました。",
    });
  } catch (error) {
    Logger.log("doPostエラー: " + error); // doPostのエラーログを明確化
    return createJsonResponse({
      status: "error",
      message: "doPost処理でエラーが発生しました。",
    }); // より一般的なエラーメッセージ
  }
}

HTML

ボタンを表示する要素と、「いいね」の数を表示する要素です。

id="upVoteButton"id="upVoteCounter"がJavaScriptに紐づいています。

id名を変更する場合は、JavaScriptの修正もお願いします。

HTMLの例です、好みの場所に設置してください。

<button id="upVoteButton" class="upvote-button" title="この記事にいいね" aria-label="この記事にいいね" aria-pressed="false" type="button">いいね</button>
<div id="upVoteCounter" class="upvote-counter" aria-live="polite"></div>

JavaScript

gas_WebApps_URLのところにデプロイしたウェブアプリのURLをコピペしてください。

scriptURL = "gas_WebApps_URL",

scriptURL = "https://script.google.com/.../exec",

BloggerなどHTML(XML)ファイルに直接書くときはJavaScriptのコードを<script>タグではさんでください

<script>

JavaScriptのコード

</script>

また、Bloggerは<b:if cond='data:view.isSingleItem'>ではさむと記事(投稿)のみで実行します。

<b:if cond='data:view.isSingleItem'>
  <script>
    
  JavaScriptのコード

  </script>  
</b:if>

設置場所は</body>の上付近を想定しています。

コードの一番最後setupUpVote();でJavaScriptを実行しています。

bodyタグの上の方に設置する場合はsetupUpVote();を下のコードに書き換えることを検討してください。

document.addEventListener("DOMContentLoaded", function () {
  setupUpVote();
});

JavaScript:

/**
 * ボタンがクリックされたとき、記事のタイトルとURLをGoogle Apps Script(GAS)経由でスプレッドシートに書き込みます。
 * 書き込まれたURLは集計され、webサイトを開いたときにその集計された数が表示されます。
 *
 * @param {Object} options - オプションオブジェクト
 * @param {string} options.buttonId - ボタン要素のID
 * @param {string} options.counterId - いいねの数を表示する要素のID
 * @param {string} options.scriptURL - GASスクリプトのURL
 */
/*! Copyright:2025 sutajp | Released under the MIT license | https://sutasutashiki.blogspot.com/p/mit-license.html */

function setupUpVote(options) {
  "use strict";
  const {
    buttonId = "upVoteButton",
    counterId = "upVoteCounter",
    scriptURL = "gas_WebApps_URL",
  } = options || {};

  const currentURL = window.location.origin + window.location.pathname;
  const upVoteButton = document.getElementById(buttonId);
  const upVoteCounter = document.getElementById(counterId);

  async function getUpVoteCount() {
    const param = { currentURL };
    const query = new URLSearchParams(param);

    try {
      const response = await fetch(`${scriptURL}?${query}`);

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const count = await response.json();
      upVoteCounter.textContent = count;
    } catch (error) {
      console.error("Fetch Error:", error);
      upVoteCounter.textContentL = "通信エラーが発生しました。";
    }
  }

  async function sendTitleAndURL() {
    if (upVoteButton.disabled) return;
    upVoteButton.disabled = true;
    upVoteButton.ariaPressed = true;
    let count = parseInt(upVoteCounter.textContent, 10);
    upVoteCounter.textContent = count + 1; // 楽観的更新

    const params = new URLSearchParams({
      sendTitle: document.title,
      sendURL: currentURL,
    });

    try {
      const response = await fetch(scriptURL, {
        method: "POST",
        headers: {
          "Accept": "application/json", //JSON形式のレスポンスを明示
          "Content-Type": "application/x-www-form-urlencoded",
        },
        body: params,
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      console.log("いいね送信成功");
    } catch (error) {
      console.error("Error sending like:", error);
      upVoteCounter.textContent = count;
      //alert("いいね!の送信に失敗しました。");
    }
  }

  getUpVoteCount();
  upVoteButton.addEventListener("click", sendTitleAndURL);
}

// 初期化処理
setupUpVote();

setupUpVote() 関数にオプション引数(options)を追加し、ボタンのID、カウンターのID、スクリプトURLを外部から設定できるようにしました。

これにより、複数のいいねボタンを設置する場合などに、コードを再利用しやすくなります。

ボタンIDやカウンターID、スクリプトURLを変更する場合は、以下のようにオプションオブジェクトを渡します。

setupUpVote({
  buttonId: "myLikeButton",
  counterId: "myLikeCounter",
  scriptURL: "https://script.google.com/.../exec"
});


検索

お知らせ

カテゴリー

Random Picks

すたすた式

Enjoy!👍

QooQ