// ==UserScript==
// @name Show points on Amazon.co.jp wishlist
// @version 23.5.0.1
// @description Amazon.co.jpの欲しいものリストと検索ページで、Kindleの商品にポイントを表示しようとします
// @namespace https://greasyfork.org/ja/users/165645-agn5e3
// @author Nathurru
// @match https://www.amazon.co.jp/*/wishlist/*
// @match https://www.amazon.co.jp/wishlist/*
// @match https://www.amazon.co.jp/*/dp/*
// @match https://www.amazon.co.jp/dp/*
// @match https://www.amazon.co.jp/*/gp/*
// @match https://www.amazon.co.jp/gp/*
// @match https://www.amazon.co.jp/s*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_xmlhttpRequest
// @compatible firefox
// @license Apache-2.0
// ==/UserScript==
/**********************************************************************************************************************
NOTICE:このアプリケーションは国立国会図書館サーチAPI( https://iss.ndl.go.jp/information/api/ )と、openBDAPI( https://openbd.jp/ )を利用しています
**********************************************************************************************************************/
(function () {
'use strict';
const domParser = new DOMParser();
const CACHE_LIFETIME = 1209600000;
const RESCAN_INTERVAL = 10800000;
const AUTOMATIC_CLEAN_FACTOR = 100;
const TAX = 0.1;
const PROCESSES = 3;
const COMMERCIAL_PUBLISHERS = [
'集英社',
'講談社',
'KADOKAWA',
'小学館',
'日経BP',
'東京書籍',
'学研プラス',
'文藝春秋',
'SBクリエイティブ',
'インプレス',
'DeNA',
'スクウェア・エニックス',
'ダイヤモンド社',
'ドワンゴ',
'一迅社',
'技術評論社',
'近代科学社',
'幻冬舎',
'秋田書店',
'少年画報社',
'新潮社',
'双葉社',
'早川書房',
'竹書房',
'筑摩書房',
'朝日新聞出版',
'東洋経済新報社',
'徳間書店',
'日本文芸社',
'白泉社',
'扶桑社',
'芳文社',
'翔泳社',
];
const taxIncluded = listPrice => Math.floor(listPrice * (1 + TAX));
const isNull = value => value === null;
const isUndefined = value => value === undefined;
const hasValue = value => !isNull(value) && !isUndefined(value);
const rate = ((numerator, denominator) => denominator === 0 ? 0 : numerator / denominator * 100);
const random = max => Math.floor(Math.random() * Math.floor(max));
const sleep = ms => new Promise((resolve) => setTimeout(resolve, ms));
const rateColor = rate => {
if (rate < 0) {
return {
color: '#9B1D1E',
bgColor: 'initial',
};
} else if (rate < 20) {
return {
color: 'initial',
bgColor: 'initial',
};
} else if (rate < 50) {
return {
color: 'initial',
bgColor: '#F7D44A',
};
} else if (rate < 80) {
return {
color: '#FFFFFF',
bgColor: '#FE7E03',
};
} else {
return {
color: '#FFFFFF',
bgColor: '#9B1D1E',
};
}
};
const url = {
ndl(isbn) {
return 'https://iss.ndl.go.jp/api/sru?operation=searchRetrieve&recordSchema=dcndl&recordPacking=xml&query=isbn=' + isbn;
},
ndlPublisher(publisher) {
return 'https://iss.ndl.go.jp/api/sru?operation=searchRetrieve&recordSchema=dcndl&recordPacking=xml&maximumRecords=1&mediatype=1&query=publisher=' + publisher;
},
amazon(asin) {
return 'https://www.amazon.co.jp/dp/' + asin;
},
openbd(isbn) {
return 'https://api.openbd.jp/v1/get?isbn=' + isbn;
},
}
const storage = {
async save(key, data) {
console.log('SAVE: ' + key);
if (!hasValue(key) || !hasValue(data)) {
return null;
}
GM_setValue(key, JSON.stringify(data));
console.log('SAVED: ' + key, data);
},
load(key) {
console.log('LOAD: ' + key);
if (!hasValue(key)) {
return null;
}
const data = GM_getValue(key);
console.log('LOADED: ' + key, data);
if (!hasValue(data)) {
return null;
}
return JSON.parse(data);
},
exists(key) {
return hasValue(GM_getValue(key));
},
async delete(key) {
console.log('DELETE: ' + key);
GM_deleteValue(key);
},
list() {
return GM_listValues();
},
clean() {
const keys = this.list();
const now = Date.now();
for (const key of keys) {
if (key === 'SETTINGS' || key === 'PUBLISHERS') {
continue;
}
const data = this.load(key);
if (now - data.updatedAt > CACHE_LIFETIME) {
this.delete(key);
}
}
},
isCacheActive(asin) {
if (!storage.exists(asin)) {
return false;
} else {
return Date.now() - storage.load(asin)?.updatedAt <= RESCAN_INTERVAL;
}
},
}
const storageClean = (() => {
if (random(AUTOMATIC_CLEAN_FACTOR) === 0) {
storage.clean();
}
})
const isIsbn = ((isbn) => {
let c = 0;
if (isbn.match(/^4[0-9]{8}[0-9X]?$/)) {
for (let i = 0; i < 9; ++i) {
c += (10 - i) * Number(isbn.charAt(i));
}
c = (11 - c % 11) % 11;
c = (c === 10) ? 'X' : String(c);
return c === isbn.charAt(9);
} else if (isbn.match(/^9784[0-9]{9}?$/)) {
for (let i = 0; i < 12; ++i) {
c += Number(isbn.charAt(i)) * ((i % 2) ? 3 : 1);
}
c = ((10 - c % 10) % 10);
return String(c) === isbn.charAt(12);
} else {
return false;
}
});
const get = (async (URL) => {
console.log('GET: ' + URL);
return new Promise((resolve, reject) => {
const xhr = window.GM_xmlhttpRequest;
xhr({
onabort: reject,
onerror: reject,
onload: resolve,
ontimeout: reject,
method: 'GET',
url: URL,
withCredentials: true,
});
});
});
const parser = {
async isKindlePage(dom) {
const element = dom.querySelector('#title');
if (isNull(element)) {
return false;
}
return /kindle版/i.test(element.innerText)
},
async isAgeVerification(dom) {
const element = dom.querySelector('#black-curtain-warning');
return !isNull(element);
},
async isKindleUnlimited(dom) {
const element = dom.querySelector('#tmm-ku-upsell');
return !isNull(element);
},
async isbns(dom) {
let isbns = [];
const elements = dom.querySelectorAll('#tmmSwatches a');
for (const element of elements) {
const href = element.getAttribute("href");
if (isNull(href)) {
continue;
}
const m = href.match(/\/(4[0-9]{8}[0-9X])/);
if (!isNull(m) && isIsbn(m[1])) {
isbns.push(m[1]);
}
}
return Array.from(new Set(isbns));
},
async isBought(dom) {
const element = dom.querySelector('#booksInstantOrderUpdate_feature_div');
if (isNull(element)) {
return false;
}
return /購入/.test(element.innerText);
},
async isKdp(dom) {
const elements = dom.querySelectorAll("#detailBullets_feature_div .a-list-item");
for (const element of elements) {
if (/出版社/.test(element.innerText)) {
const m = element.querySelector('span:nth-child(2)').innerText.match(/^[^;(]*/);
if (isNull(m) && hasValue(m[0])) {
return true;
}
const publisher = m[0].trim();
console.log('publisher:' + publisher);
const findIndex = COMMERCIAL_PUBLISHERS.findIndex(item => new RegExp(item).test(publisher));
if (findIndex !== -1) {
return false;
}
let publishers = storage.load('PUBLISHERS');
if (isNull(publishers)) {
publishers = {};
} else if (!isUndefined(publishers[publisher])) {
return !publishers[publisher];
}
const res = await get(url.ndlPublisher(publisher));
const hasPublisher = await parser.hasPublisher(res.responseXML);
publishers[publisher] = hasPublisher;
await storage.save('PUBLISHERS', publishers);
return !hasPublisher;
}
}
return true;
},
async asin(dom) {
const element = dom.querySelector("#ASIN");
if (isNull(element)) {
return null;
}
return element.value;
},
async kindlePrice(dom) {
let element = dom.querySelector(".kindle-price");
if (!isNull(element)) {
return parseInt(element.innerText.match(/[0-9,]+/)[0].replace(/,/, ''));
}
element = dom.querySelector("span.extra-message.olp-link");
if (!isNull(element)) {
return parseInt(element.innerText.match(/[0-9,]+/)[0].replace(/,/, ''));
}
return null;
},
async pointReturn(dom) {
let point = 0;
const elements = dom.querySelectorAll(".swatchElement");
if (elements.length !== 0) {
for (const element of elements) {
if (!/Kindle/.test(element.innerText)) {
continue;
}
const m = element.innerText.match(/([0-9,]+)pt/);
if (!isNull(m)) {
point = parseInt(m[1].replace(/,/, ''));
break;
}
}
} else {
const element = dom.querySelector(".loyalty-points");
if (isNull(element)) {
point = 0;
} else {
point = parseInt(element.innerText.match(/[0-9,]+/)[0].replace(/,/, ''));
}
}
return isNaN(point) ? 0 : point;
},
async price(xml) {
const element = xml.querySelector("price");
if (isNull(element)) {
return null;
}
const price = parseInt(element.innerHTML
.replace(/[0-9]/g, s => String.fromCharCode(s.charCodeAt(0) - 0xfee0))
.match(/[0-9]+/)[0]);
return isNaN(price) ? null : price;
},
async hasPublisher(xml) {
const element = xml.querySelector("numberOfRecords");
if (isNull(element)) {
return null;
}
return element.innerHTML !== '0';
},
async campaigns(dom) {
return [];
const elements = dom.querySelectorAll('span > div.a-section.a-spacing-none > div');
let tmp = [];
for (const element of elements) {
const spanTags = element.getElementsByTagName('span');
tmp.push(spanTags[0].innerText);
}
return tmp;
},
wishlist: {
async itemTitle(dom) {
const element = dom.querySelector('a[id^="itemName_"]');
if (isNull(element)) {
return null;
}
return element.innerText;
},
async itemAsin(dom) {
const element = dom.querySelector('.price-section');
if (isNull(element)) {
return undefined;
}
const attribute = element.getAttribute('data-item-prime-info');
if (isNull(attribute)) {
return undefined;
}
return JSON.parse(attribute).asin
},
async isKindleItem(dom) {
return /Kindle版/.test(dom.innerText);
},
async isItemProcessed(dom) {
return dom.classList.contains('SPAW_PROCESSED');
},
},
search: {
async isKindleItem(dom) {
const elements = dom.querySelectorAll('a.a-text-bold');
if (elements.length !== 0) {
for (const element of elements) {
if (/^Kindle版/.test(element.innerHTML.trim())) {
return true;
}
}
}
return false;
},
async title(dom) {
const title = dom.querySelector("h2 > a");
if (isNull(title)) {
return null;
}
return title.innerText.trim();
},
async asin(dom) {
return dom.getAttribute("data-asin");
},
async isBulkBuy(dom) {
return /まとめ買い/.test(dom.innerText);
}
},
}
const lowPriceBook = (async (isbns) => Promise.all(isbns.map(async (isbn) => {
try {
let price = await getOpenBdPrice(isbn);
if (hasValue(price)) {
return {
isbn: isbn,
price: price,
};
}
price = await getNdlPrice(url.ndl(isbn));
return {
isbn: isbn,
price: price,
}
} catch (e) {
return {
isbn: isbn,
price: null,
}
}
})).then((prices) => {
return prices.reduce((a, b) => a.price < b.price ? a : b);
})
);
const getNdlPrice = (async (isbn) => {
const res = await get(url.ndl(isbn));
return await parser.price(res.responseXML);
});
const getOpenBdPrice = (async (isbn) => {
const res = await get(url.openbd(isbn));
const json = JSON.parse(res.responseText);
try {
return json[0]['onix']['ProductSupply']['SupplyDetail']['Price'][0]['PriceAmount'];
} catch (e) {
return null;
}
});
const itemPage = {
async itemInfo(dom) {
if (await parser.isAgeVerification(dom)) {
throw new Error('年齢確認が必要です');
}
if (!await parser.isKindlePage(dom)) {
return null;
}
const asin = await parser.asin(dom);
if (!hasValue(asin)) {
throw new Error('ASINが見つかりません');
}
const data = storage.load(asin);
return Promise.all([
this.bookInfo(dom, data),
this.kindleInfo(dom, data),
]).then(([bookInfo, kindleInfo]) => {
return {
asin: asin,
isbn: bookInfo.isbn,
paperPrice: bookInfo.price,
kindlePrice: kindleInfo.price,
pointReturn: kindleInfo.point,
isBought: kindleInfo.isBought,
isKdp: kindleInfo.isKdp,
isKindleUnlimited: kindleInfo.isKindleUnlimited,
campaigns: kindleInfo.campaigns,
updatedAt: Date.now(),
};
});
},
async kindleInfo(dom, data) {
return Promise.all([
data,
parser.kindlePrice(dom),
parser.pointReturn(dom),
parser.isBought(dom),
parser.isKdp(dom),
parser.isKindleUnlimited(dom),
parser.campaigns(dom)
]).then(([data, kindlePrice, pointReturn, isBought, isKdp, isKindleUnlimited, campaigns]) => {
const info = {
price: isNull(kindlePrice) ? data.kindlePrice : kindlePrice,
point: pointReturn,
isBought: isBought,
isKdp: isKdp,
isKindleUnlimited: isKindleUnlimited,
campaigns: campaigns,
};
console.log('KINDLE INFO: ', info)
return info;
});
},
async bookInfo(dom, data) {
if (hasValue(data) && hasValue(data.paperPrice)) {
return {
isbn: data.isbn,
price: data.paperPrice,
}
}
const isbns = await parser.isbns(dom);
console.log('ISBN: ', isbns);
if (isbns.length === 0) {
return {
isbn: null,
price: null,
};
}
const book = await lowPriceBook(isbns);
console.log('LOW: ', book)
return {
isbn: book.isbn,
price: book.price,
}
},
clickPrompt(dom) {
let prompt = dom.querySelector('#buyOneClick .a-expander-prompt');
if (!isNull(prompt)) {
prompt.click();
}
},
async addPaperPrice(dom, paperPrice, kindlePrice) {
if (isNull(paperPrice) || paperPrice === 0) {
return;
}
paperPrice = taxIncluded(paperPrice);
const off = paperPrice - kindlePrice;
const offRate = Math.round(rate(off, paperPrice));
let html = '<tr class="print-list-price">' +
'<td class="a-span1 a-color-secondary a-size-small a-text-left a-nowrap">' +
' 紙の本の価格:' +
'</td>' +
'<td class="a-color-base a-align-bottom a-text-strike">' +
' ¥' + paperPrice +
'</td>' +
'</tr>' +
'<tr class="savings">' +
'<td class="a-span1 a-color-secondary a-text-left a-nowrap">' +
' 割引:' +
'</td>' +
'<td class="a-color-base a-align-bottom">' +
' ¥' + off + '(' + offRate + '%)' +
'</td>' +
'</tr>' +
'<tr>' +
'<td colspan="2" class="a-span1 a-color-secondary">' +
' <hr class="a-spacing-small a-spacing-top-small a-divider-normal">' +
'</td>' +
'</tr>';
let element = dom.querySelector('#buybox tbody');
let childNode = dom.querySelector('.print-list-price');
if (!isNull(childNode)) {
childNode.parentNode.removeChild(childNode);
}
if (isNull(element)) {
element = dom.querySelector("#buyOneClick tbody");
}
if (!isNull(element)) {
element.insertAdjacentHTML('afterbegin', html);
}
},
async addPoint(dom, price, point) {
if (!isNull(dom.querySelector('.loyalty-points')) || point === 0) {
return;
}
const pointRate = Math.round(rate(point, price));
const html = '<tr class="loyalty-points">' +
'<td class="a-span6 a-color-secondary a-size-base a-text-left">' +
' <div class="a-section a-spacing-top-small">獲得ポイント:</div>' +
'</td>' +
'<td class="a-align-bottom">' +
' <div class="a-section a-spacing-top-small">' +
' <span>' +
' <span class="a-size-base a-color-price a-text-bold">' + point + 'ポイント</span>' +
' <span class="a-size-base a-color-price">(' + pointRate + '%)</span>' +
' </span>' +
' </div>' +
' </td>' +
'</tr>';
let element = dom.querySelector('#buybox tbody');
if (isNull(element)) {
element = dom.querySelector("#buyOneClick tbody");
}
if (!isNull(element)) {
element.insertAdjacentHTML('beforeend', html);
}
},
async emphasisPrice(dom) {
const elements = dom.querySelectorAll("tr.kindle-price td")
const label = dom.querySelector("tr.kindle-price td")
const price = dom.querySelector("tr.kindle-price span");
if (isNull(label) || isNull(price)) {
return;
}
label.classList.remove('a-color-secondary', 'a-size-small');
label.classList.add('a-color-price', 'a-text-bold', 'a-size-medium');
price.classList.remove('a-color-secondary', 'a-size-small');
price.classList.add('a-color-price', 'a-text-bold', 'a-size-medium');
}
};
const wishlistPage = {
discoveries: [],
observer: null,
async push(nodes) {
for (const dom of Array.from(nodes).filter((element, index) => element.nodeName === "LI")) {
const title = await parser.wishlist.itemTitle(dom);
const asin = await parser.wishlist.itemAsin(dom);
if (!await parser.wishlist.isKindleItem(dom) || isUndefined(asin)) {
console.log('DROP:[' + asin + ']' + title);
continue;
}
console.log('PUSH:[' + asin + ']' + title);
await this.processStart(dom);
this.discoveries.push(dom);
}
},
async initialize(dom) {
await this.push(dom.querySelectorAll(".g-item-sortable"));
this.observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
this.push(mutation.addedNodes);
}
});
});
this.observer.observe(document.querySelector("#g-items"), {
childList: true,
});
await this.run();
},
async run() {
let runCount = 0;
for (; ;) {
while (this.discoveries.length > 0) {
if (runCount < PROCESSES) {
++runCount;
this.listItem(this.discoveries.shift()).finally(() => --runCount);
} else {
await sleep(200);
}
}
await sleep(1000);
}
},
async listItem(dom) {
dom.querySelector('.SPAW_PROCESSING').textContent = '取得中';
const title = await parser.wishlist.itemTitle(dom);
const asin = await parser.wishlist.itemAsin(dom);
console.log('ITEM:[' + asin + ']' + title);
if (await parser.wishlist.isItemProcessed(dom)) {
await this.processEnd(dom);
return;
}
let data;
if (storage.isCacheActive(asin)) {
console.log('CACHE LOAD:[' + asin + ']' + title);
data = storage.load(asin);
} else {
console.log('CACHE EXPIRE:[' + asin + ']' + title);
const res = await get(url.amazon(asin));
try {
data = await itemPage.itemInfo(domParser.parseFromString(res.response, 'text/html'));
} catch (e) {
await this.processEnd(dom, e.message);
return;
}
}
if (isNull(data)) {
await this.processEnd(dom);
return;
}
await storage.save(data.asin, data);
await this.viewPrice(dom, data);
await this.processEnd(dom);
console.log('END:[' + asin + ']' + title);
},
async processStart(dom) {
const element = dom.querySelector('div[id^="itemInfo_"]');
if (!isNull(element)) {
element.insertAdjacentHTML('afterbegin', '<div class="a-row SPAW_PROCESSING" style="color:#EE0077">取得待ち</div>');
}
},
async processEnd(dom, message) {
if (message) {
dom.querySelector('.SPAW_PROCESSING').innerHTML = '<div class="a-row SPAW_PROCESSING" style="color:red">' + message + '</div>';
} else {
dom.querySelector('.SPAW_PROCESSING').remove();
dom.classList.add("SPAW_PROCESSED");
}
},
async viewPrice(dom, data) {
const paperPrice = taxIncluded(data.paperPrice);
const kindlePrice = data.kindlePrice;
const off = paperPrice - kindlePrice;
const offRate = rate(off, paperPrice)
const offRateColor = rateColor(offRate);
const point = data.pointReturn;
const pointRate = rate(point, kindlePrice)
const pointRateColor = rateColor(pointRate);
let html = '<div>';
if (!isNull(data.paperPrice)) {
html += '<div>' +
'<span class="a-price-symbol">紙の本:¥</span>' +
'<span class="a-price-whole">' + paperPrice + '</span>' +
'</div>';
} else if (isNull(data.isbn)) {
html += '<span class="a-price-symbol" style="color:#ff3c00">紙の本:無し</span>';
}
html += '<div>' +
'<span class="a-price-symbol a-color-price a-size-large">価格:¥</span>' +
'<span class="a-price-whole a-color-price a-size-large">' + kindlePrice + '</span>' +
'</div>';
if (!isNull(data.paperPrice)) {
html += '<div style="color:' + offRateColor.color + ';background-color:' + offRateColor.bgColor + '">' +
'<span class="a-price-symbol">割り引き:</span>' +
'<span class="a-price-whole">' + off + '円( ' + Math.round(offRate) + '%割引)</span>' +
'</div>';
}
html += '<div style="color:' + pointRateColor.color + ';background-color:' + pointRateColor.bgColor + '">' +
'<span class="a-price-symbol">ポイント:</span>' +
'<span class="a-price-whole">' + point + 'ポイント(' + Math.round(pointRate) + '%還元)</span>' +
'</div>';
if (data?.isKindleUnlimited) {
html += '<div>' +
'<span class="a-price-symbol a-text-bold">Kindle Unlimited対象</span>' +
'</div>';
}
if (data?.campaigns) {
for (const campaign of data.campaigns) {
html += '<div>' +
'<span class="a-price-symbol a-text-bold">' + campaign + '</span>' +
'</div>';
}
}
html += '</div>';
dom.querySelector(".price-section").innerHTML = html;
},
}
const searchPage = {
discoveries: [],
observer: null,
async push(nodes) {
for (const dom of Array.from(nodes)) {
const title = await parser.search.title(dom);
const asin = await parser.search.asin(dom);
if (!await parser.search.isKindleItem(dom) || isUndefined(asin) || await parser.search.isBulkBuy(dom)) {
console.log('DROP:[' + asin + ']' + title);
continue;
}
console.log('PUSH:[' + asin + ']' + title);
this.processStart(dom);
this.discoveries.push(dom);
}
},
async initialize(dom) {
await this.push(dom.querySelectorAll("[data-asin]"));
this.observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
this.push(mutation.addedNodes);
}
});
});
this.observer.observe(document.querySelector('div.s-search-results'), {
childList: true,
});
await this.run();
},
async processStart(dom) {
const element = dom.querySelector("h2 > a");
if (!isNull(element)) {
element.insertAdjacentHTML('afterbegin', '<div class="a-row SPAW_PROCESSING" style="color:#EE0077">取得待ち</div>');
}
},
async processEnd(dom, message) {
if (message) {
dom.querySelector('.SPAW_PROCESSING').innerHTML = '<div class="a-row SPAW_PROCESSING" style="color:red">' + message + '</div>';
} else {
dom.querySelector('.SPAW_PROCESSING').remove();
dom.classList.add("SPAW_PROCESSED");
}
},
async run() {
let runCount = 0;
for (; ;) {
while (this.discoveries.length > 0) {
if (runCount < PROCESSES) {
++runCount;
this.item(this.discoveries.shift()).finally(() => --runCount);
} else {
await sleep(200);
}
}
await sleep(1000);
}
},
async item(dom) {
dom.querySelector('.SPAW_PROCESSING').textContent = '取得中';
const title = await parser.search.title(dom);
const asin = await parser.search.asin(dom);
console.log('ITEM:[' + asin + ']' + title);
let data;
if (this.isCacheActive(asin)) {
console.log('CACHE LOAD:[' + asin + ']' + title);
data = storage.load(asin);
} else {
console.log('CACHE EXPIRE:[' + asin + ']' + title);
const res = await get(url.amazon(asin));
try {
data = await itemPage.itemInfo(domParser.parseFromString(res.response, 'text/html'));
} catch (e) {
await this.processEnd(dom, e.message);
return;
}
}
if (isNull(data)) {
await this.processEnd(dom);
return;
}
await storage.save(data.asin, data);
await this.viewPrice(dom, data);
await this.processEnd(dom);
console.log('END:[' + asin + ']' + title);
},
isCacheActive(asin) {
return storage.isCacheActive(asin);
},
async viewPrice(dom, data) {
const paperPrice = taxIncluded(data.paperPrice);
const kindlePrice = data.kindlePrice;
const off = paperPrice - kindlePrice;
const offRate = rate(off, paperPrice)
const offRateColor = rateColor(offRate);
const point = data.pointReturn;
const pointRate = rate(point, kindlePrice)
const pointRateColor = rateColor(pointRate);
let html = '<div>';
if (data.isBought) {
html += '<i class="a-icon a-icon-success" role="presentation"></i><span class="a-size-medium a-color-success"> 購入済み </span>';
html += '<div>' +
'<span class="a-size-base a-color-secondary">価格:¥</span>' +
'<span class="a-size-base a-color-secondary">' + kindlePrice + '</span>' +
'</div>';
const buyButton = dom.querySelector(".a-spacing-top-mini");
if (!isNull(buyButton)) {
buyButton.remove();
}
} else {
if (!isNull(data.paperPrice)) {
html += '<div>' +
'<span class="a-price-symbol">紙の本:¥</span>' +
'<span class="a-price-whole">' + paperPrice + '</span>' +
'</div>';
} else if (data.isKdp) {
html += '<div class="a-size-medium" style="color:#FFFFFF;background-color:#ff0000">KDP</div>';
} else if (isNull(data.isbn)) {
html += '<div class="a-size-medium" style="color:#ff3c00">ISBN不明</div>';
}
html += '<div>' +
'<span class="a-price-symbol a-color-price a-size-large">価格:¥</span>' +
'<span class="a-price-whole a-color-price a-size-large">' + kindlePrice + '</span>' +
'</div>';
if (!isNull(data.paperPrice)) {
html += '<div style="color:' + offRateColor.color + ';background-color:' + offRateColor.bgColor + '">' +
'<span class="a-price-symbol">割り引き:</span>' +
'<span class="a-price-whole">' + off + '円( ' + Math.round(offRate) + '%割引)</span>' +
'</div>';
}
html += '<div style="color:' + pointRateColor.color + ';background-color:' + pointRateColor.bgColor + '">' +
'<span class="a-price-symbol">ポイント:</span>' +
'<span class="a-price-whole">' + point + 'ポイント(' + Math.round(pointRate) + '%還元)</span>' +
'</div>';
if (data?.campaigns) {
for (const campaign of data.campaigns) {
html += '<div>' +
'<span class="a-price-symbol a-text-bold">' + campaign + '</span>' +
'</div>';
}
}
}
html += '</div>';
let isChanged = false;
dom.querySelectorAll("div.a-row.a-size-base").forEach(element => {
if (/ポイント/.test(element.innerText) || /税込/.test(element.innerText) || /購入/.test(element.innerText)) {
element.remove();
} else if (/[¥\\]/.test(element.innerText)) {
if (!isChanged) {
element.innerHTML = html;
isChanged = true;
}
}
});
},
}
const main = (async () => {
const url = location.href;
const dom = document
storageClean();
if (/\/(dp|gp)\//.test(url) && await parser.isKindlePage(dom)) {
console.log('ITEM PAGE');
await itemPage.emphasisPrice(dom);
itemPage.clickPrompt(dom);
await itemPage.itemInfo(dom).then((data) => {
storage.save(data.asin, data);
itemPage.addPaperPrice(dom, data.paperPrice, data.kindlePrice);
itemPage.addPoint(dom, data.kindlePrice, data.pointReturn);
});
} else if (/\/wishlist\//.test(url)) {
console.log('WISHLIST PAGE');
await wishlistPage.initialize(dom);
} else if (/\/s[?\/]/.test(url)) {
console.log('SEARCH PAGE');
await searchPage.initialize(dom);
}
});
main();
})();