LoginSignup
3
3

Amazon Vineがあまりにも使いにくいから拡張機能でカバーした

Last updated at Posted at 2023-10-08

はじめに

Amazon Vine先取りプログラム」というものがあります。

ものすごくざっくりいうと「商品をもらって忌憚のない意見をレビューする」といった感じです。
つい先日そのレビュアーとして登録できたので、ワクワクしながら商品一覧ページを見たのですが・・。

商品ページが見にくい

image.png
僕が使ってて感じる欠点として、こんな感じ。

  • 検索機能がない
  • 新しく追加された商品がわからない(必ず並びの一番上に来るわけでもなく、中途半端なところに追加されてたりする)
  • 名前が省略されているため、ページ内検索のワード次第では該当する商品も引っかからない

そして同じような商品がいくつも並ぶこともあります。
image.png

こうした色々な要素が組み合わさり、とてもとても見にくいんです!!

Google拡張機能を作って対策した

幸いにもプログラマーの端くれなので自分でなんとかすることにしてみました。
あまりにも汚いコードで辛いのですが、頑張れば色々便利になるんだな・・という程度の参考にどうぞ。

コード

let exclusionWords = [];

window.onload = function() {

	// 商品一覧を取得
	const itemContainer = document.querySelector('#vvp-items-grid');
	if (!itemContainer){
		return;
	}
	const itemTiles= itemContainer.querySelectorAll('.vvp-item-tile');
	
	checkForSeenItem();

	// 除外ワードを取得
	const exclusionWordStr = localStorage.vine_exclusionWord;
	exclusionWords = exclusionWordStr?.split(',');
	
	createExclusionWordForm();
	
	for (const itemTile of itemTiles) {
		settingItemTile(itemTile);
	}
	
	unsetGridHeight();
	
	clonePagination();
}

/**
 * indexedDBに接続して、既に確認したことのある商品かチェック
 */
function checkForSeenItem() {

	// indexedDBに接続
	const openReq = indexedDB.open('VineAddon');
	openReq.onupgradeneeded = function(event) {
		let db = event.target.result;
		
		db.createObjectStore('Item', {keyPath: 'asin'});
	}

	openReq.onsuccess = function(event) {

		// Itemオブジェクトストアを開く
		let db = event.target.result;
		let trans = db.transaction('Item', 'readwrite');
		let itemStore = trans.objectStore('Item');
		
		// 画面に表示されている商品をチェック
		const itemButtons = document.querySelectorAll('.vvp-details-btn');
		for (const itemButton of itemButtons) {
			const asin = itemButton.querySelector('.a-button-input').dataset.asin;
			
			// DBに登録済か確認
			const getReq = itemStore.get(asin);
			getReq.onsuccess = function(event) {

				// 登録済なら背景色を白にする
				if (!!event.target.result) {
					const itemTile = document.querySelector(`[data-asin="${event.target.result.asin}"]`).parentElement.parentElement.parentElement.parentElement;
					if (itemTile.style.backgroundColor != 'gainsboro') {
						itemTile.style.backgroundColor = 'white'
					}
				}
			}
			
			// DBにasinを登録
			itemStore.put({asin: asin});
		}
	}
}

/**
 * 除外ワードフォームの作成
 */
function createExclusionWordForm() {
	
	// テキストインプット
	let input = document.createElement('input');
	input.onblur = onblurExclusionWord;
	
	// 折りたたみ
	let details = document.createElement('details');
	details.style.marginTop = '7px';
	let summary = document.createElement('summary');
	summary.textContent = '除外ワード';
	details.append(summary);
	details.append(input);
	
	// 除外ワード一覧
	let wordContainer = document.createElement('div');
	wordContainer.setAttribute('id', 'vine-addon_word-container');
	wordContainer.style.marginTop = '4px';

	details.append(wordContainer);
	createWordButtons(wordContainer);
	
	let bsContainer = document.querySelector('.vvp-items-button-and-search-container');
	bsContainer.style.display = 'block'
	bsContainer.append(details);
}

/**
 * 除外ワードボタンを生成
 */
function createWordButtons(wordContainer) {
	
	// 一覧をクリア
	const clone = wordContainer.cloneNode(false);
	wordContainer.parentNode.replaceChild(clone, wordContainer);
	
	// ボタン生成
	for (const exclusionWord of exclusionWords) {
		let button = document.createElement('button');
		button.style.margin = '2px';
		button.style.backgroundColor = 'white';
		button.style.border = '1px solid silver';
		button.style.borderRadius = '5px';
		button.textContent = exclusionWord;
		button.onclick = onclickWordButton;
		clone.append(button);
	}
}

/**
 * 除外ワードを登録
 */
function onblurExclusionWord(event) {
	
	if (!event.target.value) {
		return;
	}
	
	exclusionWords.push(event.target.value);
	localStorage.vine_exclusionWord = exclusionWords.join(',');
	
	createWordButtons(document.querySelector('#vine-addon_word-container'));
	
	event.target.value = '';
}

/**
 * 除外ワードを削除
 */
function onclickWordButton(event) {
	exclusionWords = exclusionWords.filter(word => word != event.target.textContent);
	
	localStorage.vine_exclusionWord = exclusionWords.join(',');
	
	createWordButtons(document.querySelector('#vine-addon_word-container'));
}

/**
 * itemTileに色々処理
 */
function settingItemTile(itemTile) {

	// フルネームを表示するspanを別途追加
	const truncateFull = itemTile.querySelector('.a-truncate-full');

	// 既に処理済のitemTileの場合、スキップ
	if (!truncateFull) { return; }

	let span = document.createElement('span');
	span.textContent = truncateFull.textContent;
	itemTile.querySelector('.a-link-normal').append(span);
	
	// 名前部分の高さ上限を解除
	itemTile.style.maxHeight = null;
	
	// 既存の名前spanを削除
	truncateFull.remove();
	const truncateCut = itemTile.querySelector('.a-truncate-cut');
	truncateCut.remove();
	
	// 除外ワードが含まれているか確認
	for (const exclusionWord of exclusionWords) {
		if (span.textContent.toLowerCase().includes(exclusionWord.toLowerCase())) {
			// 背景色をグレーにして、一番下に並び替える
			itemTile.style.backgroundColor = 'gainsboro';
			itemTile.remove();
			document.querySelector('#vvp-items-grid').append(itemTile);
			break;
		} else {
			itemTile.style.backgroundColor = 'antiquewhite'; // 背景色を設定(チェック済の商品の場合、後で白に上書きする)
		}
	}
}

/**
 * 高さの上限設定を解除
 */
function unsetGridHeight() {

	let grids = document.querySelectorAll('.vvp-item-product-title-container');
	for (let grid of grids) {
		grid.style.height = 'auto';
	}
}

/**
 * ページングの部品を上にも追加
 */
function clonePagination() {

	const originPagination = document.querySelector('.a-pagination');
	document.querySelector('#vvp-items-grid-container').prepend(originPagination.parentElement.cloneNode(true));
}

この拡張機能を使うことで、こんな事ができるようになります。

  • ページングの部品がページ上部にも出てくるようになる
  • 初めて出てきた商品は背景色がクリーム色になる
  • 除外ワードを登録し、それにマッチする商品は下の方に並び替えられる & 背景色がグレーになる
  • 名前が省略されず、フルで表示される

image.png
image.png

おわりに

コードこそ汚くなりましたが、自分でよく使うページを便利にするのってめちゃくちゃテンション上がります。
「ふおおお!プログラマーやっててよかった!!!」みたいな。

いずれ・・コードをきれいにしたいですね・・・。

3
3
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
3
3