見出し画像

【感謝企画】公式サイトにフォロワー様のリンクをランダム表示する仕組みを作った話

■今回やったこと
・フォロワー様のnoteのリンクを、公式サイトにランダム表示できるようになりました!
・タイトルも記念に合わせてカッコよくしてみました!
(※Geminiマークはいつか消します😏)

タイトル、綺麗すぎて無限に見ていられる😍

■公式サイト

こんばんは、「まる。」です。

以前、フォロワー様1000人達成時の公約としてお約束していた、
開発中のゲーム『HorizonNotes』の公式サイトに

「noteのフォロワー様のリンクをランダムに表示する仕組み」

を遂に実装しました!!!😭

■今回の経緯については、よろしければ以下の記事もご覧ください。

本記事では、今回どのようにこの仕組みを実装したのか、
また今後の運用方法について記載していきます。

(※ホームページ側のソースコードについては、運用の保守・セキュリティの観点から公開は控えさせていただきます。
ご了承いただけますと幸いです。)



1.実装方針について考える

さて、今回の公約で決めていた実装に関する内容についてお話します。

【掲載の条件とルール】
・月初時点で私「まる。」をフォローしてくださっている方のみ、公式サイトのフッター付近にリンクを掲載する。
・公平性を保つため、表示されるアカウントはランダムにする。
・活動休止を明確に示している方、事前に掲載を希望しない旨を頂いている方、業者と思われるアカウントについては除外する。

これらを踏まえて、簡単に実装方法を検討しました。

【実装手順】

  1. フォロワー様の情報はスプレッドシート上で管理する。

  2. スプレッドシートのデータの中から、都度ランダムに抽出したユーザー様のリンクを表示する。


フォロワー様の情報はスプレッドシート上で管理する。

こちらについても、簡潔に実装の裏側をお話しします。


noteでフォロワー様全員の情報(URLやアカウント名など)を、
手動で確認しながらスプレッドシートに記載していったら……
確実に日が暮れてしまいます😅



なので、今後の運用も鑑みて、
可能であれば自動化する仕組みを検討しました。🤔


・APIを使用する
noteのAPIを使い、私のアカウントからフォロワーの情報を取得する方法です。

(※APIとは、簡単に言うと
「他のサービスを自分のサービスで使えるようにする仕組み」です)


APIが使えるのなら、わざわざ私の方で更新作業をしなくても簡単に運用することができます。

【問題点】
調べてみたところ、そもそも現在note側でAPIの提供は行われていないという結論に達しました😅
つまり、一番簡単で安全なこの自動化は行えません。


・スクレイピングで取得する
次に、noteのサーバーに直接アクセスし、プログラムで必要な情報を抽出(スクレイピング)する方法です。


この方法も、スクレイピングするプログラムさえ組んでしまえば、
あとはWindowsのスケジューラーなどで自動実行するように設定するだけで簡単に更新処理が完了します。


【問題点】
そもそも、note側がこのスクレイピングを認めているのか?
それを確認するために一度利用規約を見てみました。

一見、明示的に禁止はされていないように見えますが……

スクレイピングは、少なからず相手のサーバーに負荷をかけてしまう可能性があります。


規約に直接は書いていないものの、
グレーゾーンであることには違いありません。

今回は念には念を入れて除外しました😏


・手動でコピペして取得していく

こうなってくると、botやPythonを使った自動ツールなどによるアクセスは、少し危険を孕みます🤔


なので今回は、非常にアナログな方法ですが……
手動で画面をコピペしていき、
それをGAS(Google Apps Script)を使ってプログラムで整形する、
というやり方で運用することに決めました!👍


非常に手間ではありますが、
万が一アカウントがBAN対象になってしまったら元も子もないので……
致し方なしですね!😤


■ 必要な情報だけをJavaScriptで抽出する

さて、手動でコピペすると決めたものの、
ただ画面を全選択してコピーしていったら、
やはり時間が掛かりますし操作ミスをする可能性もあります。

こんな感じで、全体選択してコピペ…をしていったら結構大変!


なので、この作業を出来るだけ簡潔に、
そしてミスが無いように行うため、ブラウザ上でちょっとした工夫をします。


開発者ツール(コンソール)を開き、
手動でJavaScriptのコマンドを打ち込んで、HTMLの中から必要な情報だけを一括で抽出・コピーしていくことにしました。


STEP 1 :コンソールを開く

まずは、noteのフォロワー一覧ページを開いた状態で、
キーボードの「F12」キーを押します。

こんな感じでコンソールを開きます。

すると、画面の右側にこんな感じの画面(開発者ツール)が開きます。

今回は、この右下にある「Console(コンソール)」タブの入力欄(画像の赤線部分)を使っていきます👍



STEP 2:コピペ入力受付モードにする

実はコンソールを開いた直後は、
ブラウザのセキュリティ機能によって、プログラムのコピペ(貼り付け)が制限されています。

このままではコピペで作業ができないので、
まずは以下の文字だけを「手打ち」で入力してEnterキーを押します。

// 手打ちで入力
allow pasting



STEP 3 : コピーした内容を保存する格納先を作成する

次に、これから抽出していくデータを一時的にしまっておくための
「箱(変数)」を、ブラウザ上に用意します。

以下のコードをコピーして、
コンソールの入力欄に貼り付け、Enterキーを押します。

sessionStorage.setItem('note_temp_data', '');
console.log('準備完了!');

STEP4 : 表示されているページの情報を保存していく

次に、現在開いているページに表示されているフォロワーの情報を、
STEP 3で作った「保存箱」に追加(保存)していきます。

以下のコマンドをコンソールに貼り付けて、Enterキーを押します。

var saved = sessionStorage.getItem('note_temp_data');
sessionStorage.setItem('note_temp_data', saved + '\n' + document.body.innerText);
console.log('✅ 保存しました! 次のページへ進んでください。');

あとは、noteの画面で

「次のページへ進む」→「再度このコマンドを実行する」

という操作を、最後のページまで全ページを対象に繰り返していきます。


STEP5 : 全ページ分のデータをコピーしてスプレッドシートへ

全ページ分の保存が終わったら、最後に以下のコマンドを実行します。


これで、変数(保存箱)にため込んだテキストの全文が、
パソコンのクリップボードにコピーされます!

copy(sessionStorage.getItem('note_temp_data'));
console.log('🌟 クリップボードに全ページ分をコピーしました!');

コンソールに星マークのメッセージが表示されたらコピー完了です。

あとは、用意しておいたスプレッドシートにそのまま貼り付け(ペースト)します!


GASでフォロワー情報を自動整形する仕組みを作る

次に、Google Apps Script(GAS)を使って、
先ほどスプレッドシートに貼り付けた「文字だらけの生データ(RawData)」を、
綺麗なリストに自動整形するプログラムを書きます。

function extractFollowers() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const rawSheet = ss.getSheetByName("RawData");
  const outSheet = ss.getSheetByName("FollowersData");
  const blackListSheet = ss.getSheetByName("Blacklist"); // Blacklistシートも見る
  
  if (!rawSheet) {
    SpreadsheetApp.getUi().alert("「RawData」シートが見つかりません。");
    return;
  }

  // --- 非表示にすべきIDをすべて集める ---
  let hiddenUserIds = new Set();

  // 1. BlacklistシートにあるIDを読み込む
  if (blackListSheet) {
    const bData = blackListSheet.getDataRange().getValues();
    bData.forEach(row => {
      if (row[0]) hiddenUserIds.add(String(row[0]).trim());
    });
  }

  // 2. 前回の実行結果(FollowersData)で手動でFALSEにされた人を読み込む
  if (outSheet) {
    const currentData = outSheet.getDataRange().getValues();
    for (let i = 1; i < currentData.length; i++) {
      const userIdFromUrl = String(currentData[i][2]).replace("https://note.com/", "");
      const displayValue = String(currentData[i][4]).toLowerCase();
      if (displayValue === "false") {
        hiddenUserIds.add(userIdFromUrl);
      }
    }
  }

  // --- 生データの抽出処理 ---
  const data = rawSheet.getRange("A:A").getValues();
  const followersMap = new Map(); 
  
  for (let i = 2; i < data.length; i++) {
    const text = String(data[i][0]).trim();
    if (text === "フォローされています") {
      const userId = String(data[i-1][0]).trim();
      const accountName = String(data[i-2][0]).trim();
      
      if (userId && accountName) {
        // Blacklistにあるか、前回FALSEだったなら、今回もFalseにする
        const isHidden = hiddenUserIds.has(userId) ? "False" : "True";
        followersMap.set(userId, { name: accountName, hidden: isHidden });
      }
    }
  }
  
  // 出力用データの組み立て
  const outputData = [["name", "desc", "url", "img", "display"]]; 
  for (const [userId, info] of followersMap.entries()) {
    outputData.push([
      info.name,
      "HorizonNotesを応援してくれているフォロワーさんです!",
      "https://note.com/" + userId,
      "https://horizonnotes.netlify.app/gallery/Followr_Card.png",
      info.hidden
    ]);
  }
  
  // シートを更新
  if (!outSheet) {
    ss.insertSheet("FollowersData").getRange(1, 1, outputData.length, 5).setValues(outputData);
  } else {
    outSheet.clear().getRange(1, 1, outputData.length, 5).setValues(outputData);
  }
  
  SpreadsheetApp.getUi().alert(`抽出完了! 合計 ${hiddenUserIds.size} 人の非表示設定(Blacklist含む)を適用しました。`);
}

// サイトからのアクセスを受け取り、FollowersDataシートの中身を返す処理
function doGet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName("FollowersData"); 
  
  if (!sheet) {
    return ContentService.createTextOutput("[]").setMimeType(ContentService.MimeType.JSON);
  }

  const data = sheet.getDataRange().getValues();
  const headers = data[0];
  const result = [];
  
  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    if (!row[0]) continue; 

    // 5列目(インデックス4)が "False" なら、リストに追加せずスキップする
    if (String(row[4]) === "False") continue;

    let obj = {};
    for (let j = 0; j < headers.length; j++) {
      obj[headers[j]] = row[j];
    }
    result.push(obj);
  }
  
  return ContentService.createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON);
}

少し長いですが、やっていることをざっくりと説明すると、以下のようになります。

  1. 必要な情報だけを抽出する
    文字だらけの生データから「アカウント名」と「URL」など、表示に必要な情報だけを綺麗に抜き出します。

  2. 除外対象(ブラックリスト)の管理
    表示したくないアカウント(活動休止中や業者など)には「False」という目印を付けて、サイト側に表示されないように除外設定をします。

  3. サイトにデータを渡す準備(doGet 関数)
    ゲームの公式サイトから「フォロワーのデータをちょうだい!」とアクセスがあった時に、
    整形したスプレッドシートの情報を渡してあげる処理を行っています。


まとめ

今回はざっくりとした説明になりましたが、これで皆さまのアカウントへのリンクを公式サイトに連携する機能の完成です!👍

よろしければ、
ぜひ直接公式サイトへアクセスして、その目で確かめてみてくださいね。


■公式サイト

本来であれば孤独なはずの個人ゲーム開発ですが、
noteというコミュニティで出会った皆さまとの繋がりに、日々支えられていると実感して感慨深いです。


なお、表示されるフォロワー様のリストについては、
毎月【月初】のタイミングで更新(追加・削除等の反映)を行っていく予定です。


改めて、フォロワーの皆さま。


いつも支えていただき、本当にありがとうございます。
皆さまの創作活動にも多くの幸せが訪れますよう、
いちクリエイターとして心から願っております。🍀


もしこの記事が「参考になった」「開発の苦労が伝わった!」「続きが気になる!」と思ったら、
記事の下にある「スキ(ハートマーク)」を押していただけると嬉しいです!
皆様の応援こそが、毎日の開発と更新の何よりの励みになります。
最後までお読みいただき、ありがとうございました!
また次回の記事でお会いしましょう!

#UE5 #ゲーム開発 #個人開発 #インディーゲーム #HorizonNotes #開発日誌 #GAS #GoogleAppsScript #自動化 #note運用 #1000人記念 #フォロワー感謝 #スキしてみて


いいなと思ったら応援しよう!

まる。 よろしければ応援お願いします! いただいたチップはクリエイターとしてのゲーム開発に使わせていただきます! ※チップと共にやって欲しい事があったら言ってください! 可能な範囲であれば頑張ります

この記事が参加している募集