見出し画像

【無料】noteのフォロー管理用スクリプトを作ってみたよ。

追記:2023年11月17日
200スキ集まった場合、Google拡張機能として利用できるツールを開発するよ。
開発する場合はみんなにも意見を募集するよ。
開発して欲しい人はSNSにシェアして拡散してくれたら嬉しいです。

はじめに

noteのフォロー管理をするツールを販売している人が居たから、noteのフォロー管理をするスクリプトを作成してみたよ。


ツールの仕様

開発者ツールのコンソールに貼り付けてエンターキーを押すことで利用できるよ。
必ずフォローリストを開いた状態で実行してね。


こんな感じで表示されるよ!!

機能

  1. フォロー中 / 相互フォロー / 片思いフォローに表示を切り替え出来るよ。

  2. 自動で全てのアカウントのリスト化をするよ。

  3. json形式での書き出しに対応しているよ。


無料で公開する理由

① 教育支援
前回はTwitterで自動いいねするスクリプトを公開したの。

今回はnoteを題材にしたってわけ。
JavaScriptの勉強に役立ってくれたら嬉しいわ。

② note運営に機能追加を求めたいから
noteは簡単に実装できる機能を追加していない事が多いから、機能追加を求める声を増やして、より使いやすいサービスになって欲しいと思うよ。

③ お金を払うまでも無いから
この手のツールやスクリプトは、ぼったくり価格で販売されているから、市場を健全化していきたいと考えたの。


スクリプト本体

(function() {
  const modal = document.createElement('div');
  modal.innerHTML = `
      <div id="modalContainer" style="display: none;">
          <h4>noteフォロー整理ツール</h4>
          <div id="filterButtons">
              <button id="allFollows">全て</button>
              <button id="mutualFollows">相互フォロー (<span id="mutualCount">0</span>)</button>
              <button id="oneSidedFollows">片思いフォロー (<span id="oneSidedCount">0</span>)</button>
              <button id="jsonButton">JSON書き出し</button>
          </div>
          <span class="createdBy">作成者:<a href="https://note.com/honne_note/">ほんねのーと</a></span>
      </div>
  `;
  document.body.appendChild(modal);

  const style = document.createElement('style');
  style.textContent = `
      #modalContainer {
          position: fixed;
          right: 10px;
          bottom: 10px;
          padding: 15px;
          background-color: white;
          border: 1px solid #ccc;
          border-radius: 8px;
          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
          z-index: 1000;
      }

      #modalContainer h4 {
          margin-top: 0;
          margin-bottom: 10px;
          font-size: 16px;
          font-weight: bold;
          color: #238f76;
      }

      #modalContainer .createdBy {
          display: block;
          margin-bottom: 10px;
          font-size: 14px;
          color: #666;
      }

      #filterButtons {
          display: flex;
          flex-direction: column;
          gap: 10px;
          margin-bottom: 20px;
      }

      #filterButtons button {
          padding: 10px 15px;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          background-color: #238f76;
          color: white;
          font-size: 16px;
          font-weight: bold;
          transition: background-color 0.2s cubic-bezier(1, 0, 0, 1);
      }
  `;
  document.head.appendChild(style);

  document.getElementById('modalContainer').style.display = 'block';

	const updateCounts = () => {
		const items = document.querySelectorAll('.o-contentFollow__item');
		let mutualCount = 0;
		let oneSidedCount = 0;
		items.forEach(item => {
			if (item.querySelector('.m-userListItem__followed')) {
				mutualCount++;
			} else {
				oneSidedCount++;
			}
		});
		document.getElementById('mutualCount').innerText = mutualCount;
		document.getElementById('oneSidedCount').innerText = oneSidedCount;
	};

	const filterFollows = (type) => {
		const items = document.querySelectorAll('.o-contentFollow__item');
		items.forEach(item => {
			const isMutual = item.querySelector('.m-userListItem__followed') !==
				null;
			if (type === 'all') {
				item.style.display = 'block';
			} else if (type === 'mutual' && isMutual) {
				item.style.display = 'block';
			} else if (type === 'oneSided' && !isMutual) {
				item.style.display = 'block';
			} else {
				item.style.display = 'none';
			}
		});
	};

	document.getElementById('allFollows').addEventListener('click', () =>
		filterFollows('all'));
	document.getElementById('mutualFollows').addEventListener('click', () =>
		filterFollows('mutual'));
	document.getElementById('oneSidedFollows').addEventListener('click', () =>
		filterFollows('oneSided'));

	const observer = new MutationObserver(updateCounts);
	observer.observe(document.querySelector('.o-contentFollow__userList'), {
		childList: true
	});

	updateCounts();

	let currentPage = 1;
	let isLoading = false;
	const interval = 500;


	const paginationItems = document.querySelectorAll('.m-paginate__item');
	const maxPages = parseInt(paginationItems[paginationItems.length - 1].innerText);

	function modifyUserItems() {
		document.querySelectorAll('.o-contentFollow__item').forEach(item => {
			const followButton = item.querySelector('.m-userListItem__action');
			if (followButton) {
				followButton.remove();
			}
			const link = item.querySelector('.m-userListItem__link');
			if (link) {
				link.setAttribute('target', '_blank');
			}
		});
	};

	const loadMoreData = () => {
		if (isLoading || currentPage >= maxPages) {
			if (currentPage >= maxPages) {
				document.getElementById('loadingStatus').remove();
				showCompleteAlert();
			}
			return;
		}
		isLoading = true;
		currentPage++;
		const baseUrl = window.location.href.split('?')[0];
		fetch(`${baseUrl}?page=${currentPage}`)
			.then(response => response.text())
			.then(data => {
				const parser = new DOMParser();
				const doc = parser.parseFromString(data, 'text/html');
				const newItems = doc.querySelectorAll('.o-contentFollow__item');
				const container = document.querySelector('.o-contentFollow__userList');
				newItems.forEach(item => container.appendChild(item));
				isLoading = false;
				if (newItems.length > 0) {
					setTimeout(() => loadMoreData(), interval);
				}
			})
			.catch(error => console.error('Error loading more items:', error));
		updateLoadingStatus(maxPages);
		modifyUserItems();
	};

	loadMoreData();

	function updateLoadingStatus(maxPages) {
		let statusElement = document.getElementById('loadingStatus');
		if (!statusElement) {
			statusElement = document.createElement('div');
			statusElement.id = 'loadingStatus';
			statusElement.style.position = 'fixed';
			statusElement.style.bottom = '10px';
			statusElement.style.right = '10px';
			document.body.appendChild(statusElement);
		}
		statusElement.innerText = `Loading... (${currentPage} of ${maxPages})`;
	};

	const showCompleteAlert = () => {
    const statusElement = document.getElementById('loadingStatus');
    if (statusElement) {
      statusElement.remove();
    }
    alert('すべて取得しました');
  };

  document.getElementById('jsonButton').addEventListener('click', createAndDownloadJson);

	function createAndDownloadJson() {
		const items = document.querySelectorAll('.o-contentFollow__item');
		const data = Array.from(items).map(item => {
			const nameLabel = item.querySelector('.m-userListItem__nameLabel');
			const name = nameLabel ? nameLabel.innerText.trim() : '不明';
			const mutual = item.querySelector('.m-userListItem__followed') ? 1 : 0;
			const linkElement = item.querySelector('.m-userListItem__link');
			const link = linkElement ? linkElement.href : 'リンク不明';
			const imageElement = item.querySelector('.m-userListItem__avatar img');
			const imageUrl = imageElement ? (imageElement.getAttribute('data-src') ||
				imageElement.src) : '画像URL不明';

			return {
				name, mutual, link, imageUrl
			};
		});

		const jsonBlob = new Blob([JSON.stringify(data)], {
			type: 'application/json'
		});
		const url = URL.createObjectURL(jsonBlob);
		const a = document.createElement('a');
		a.href = url;
		a.download = 'followers.json';
		a.click();
		URL.revokeObjectURL(url);
	}
	loadMoreData();
})();

スクリプトの使い方

  1. noteにアクセス

  2. フォローリストを開く

  3. ブラウザの開発者ツールを起動する

  4. コンソールのタブを開いて、スクリプトをコピー&ペーストし、エンターキーを押す

  5. リストの読み込み完了まで待機する

  6. 読み込みが完了したら、右下のポップアップからボタンで選んで使ってね。


注意

このスクリプトは、Javascriptの教育用に無償公開しているものです。
スクリプト及び改造済みのスクリプトを第三者に譲渡 / 販売 / 公開 / 配布する行為は禁止とします。
万が一、スクリプトの実行によって何らかの被害にあったとしても、私は責任をとれませんので、自己責任で実行してください。


最後に

「こんなスクリプトを書いてみて欲しい」
「こんなものを作りたいけど作り方を教えて欲しい」
といった要望があれば、コメントにお願いね。

私が時間のある時に記事にして公開するよ!!

使い方で分からないことがあれば、それも教えるよ。

PR
noteのフォローをよろしくね!!
コメントした人にはフォローを返すよ!!

Twitterもやっているから、フォローしてくれたら嬉しいです!!

この記事が気に入ったらサポートをしてみませんか?