Add links to Keepa and Sakura Checker to the Amazon.co.jp product screen.
// ==UserScript==
// @name Amazon_Keepa_Sakura_Button
// @name:ja Amazonの商品画面に価格履歴とサクラチェックのボタンを追加
// @namespace https://greasyfork.org/users/1324207
// @match https://www.amazon.co.jp/dp/*
// @match https://www.amazon.co.jp/*/dp/*
// @match https://www.amazon.co.jp/gp/product/*
// @match https://www.amazon.co.jp/exec/obidos/ASIN/*
// @match https://www.amazon.co.jp/o/ASIN/*
// @match https://www.amazon.co.jp/gp/aw/d/*
// @version 1.5.0
// @author Lark8037
// @description Add links to Keepa and Sakura Checker to the Amazon.co.jp product screen.
// @description:ja Amazonの商品画面にKeepaとサクラチェッカーへのリンクを追加します。
// @run-at document-idle
// @license MIT
// @noframes
// @icon https://www.amazon.co.jp/favicon.ico
// ==/UserScript==
((d, l, h, w) => {
'use strict';
const CID = 'checker-links',
SID = 'checker-style',
DA = 'data-asin',
SEC = 'div.a-section',
K = 'https://keepa.com/#!product/5-',
S = 'https://sakura-checker.jp/search/',
T = '" target="_blank" rel="noopener noreferrer">',
RE = /[A-Z0-9]{10}/i,
QE = /(?:^|[?&])asin=([A-Z0-9]{10})(?=&|$)/i;
let cur = '',
lastForm = '',
lastUrl = '',
timer = 0,
gen = 0;
const norm = v => {
v = v && RE.exec(v);
return v ? v[0].toUpperCase() : '';
};
function formASIN() {
let e = d.getElementById('ASIN'),
n,
i,
v;
if ((v = e && norm(e.value))) return v;
n = d.getElementsByName('ASIN');
for (i = 0; i < n.length; i++) {
if ((v = norm(n[i].value))) return v;
}
n = d.getElementsByName('ASIN.0');
for (i = 0; i < n.length; i++) {
if ((v = norm(n[i].value))) return v;
}
return '';
}
function urlASIN() {
const m = QE.exec(l.search);
return m ? m[1].toUpperCase() : norm(l.pathname);
}
function getASIN() {
const f = formASIN(),
u = urlASIN(),
r = cur
? f && f !== lastForm
? f
: u && u !== lastUrl
? u
: f === cur || u === cur
? cur
: f || u
: f || u;
lastForm = f;
lastUrl = u;
return r || '';
}
function style() {
d.getElementById(SID) || (d.head || d.documentElement).insertAdjacentHTML(
'beforeend',
`<style id="${SID}">
#checker-links{contain:content}
#checker-links>a,.checker>a{display:block;border:0;height:4ex;line-height:4ex;margin-bottom:1.2ex;width:100%;text-align:center;color:#000;border-radius:10em;text-decoration:none;font-size:1em}
#checker-links>.price-history-link,.checker>.price-history-link{background:deepskyblue}
#checker-links>.price-history-link:hover,.checker>.price-history-link:hover{background:dodgerblue}
#checker-links>.sakura-checker-link,.checker>.sakura-checker-link{background:deeppink}
#checker-links>.sakura-checker-link:hover,.checker>.sakura-checker-link:hover{background:crimson}
@media(max-width:768px){#checker-links>a,.checker>a{height:5.5ex;line-height:5.5ex}}
</style>`
);
}
const links = a =>
'<a class="price-history-link" href="' + K + a + T + '価格履歴</a>' +
'<a class="sakura-checker-link" href="' + S + a + '/' + T + 'サクラチェック</a>';
function target() {
return d.getElementById('buyNow') ||
d.getElementById('add-to-cart-button') ||
d.getElementById('buybox')?.getElementsByClassName('a-button-stack')[0] ||
d.getElementById('add-to-cart-button-ubb') ||
d.getElementById('buybox-see-all-buying-choices') ||
d.getElementById('buybox-see-all-buying-choices-announce') ||
d.getElementById('rcx-subscribe-submit-button-announce') ||
d.getElementById('dealsAccordionRow') ||
d.getElementById('outOfStock');
}
function setLinks(c, a) {
const x = c.children;
c.setAttribute(DA, a);
if (x.length > 1) {
x[0].href = K + a;
x[1].href = S + a + '/';
} else {
c.innerHTML = links(a);
}
}
function place(c) {
const e = target(),
p = e?.closest?.(SEC) || e?.parentNode;
if (p?.parentNode && c.previousElementSibling !== p) {
p.parentNode.insertBefore(c, p.nextSibling);
}
return !!p;
}
function draw(a) {
const c = d.getElementById(CID);
let e,
p;
if (c) {
if (c.getAttribute(DA) !== a) {
setLinks(c, a);
place(c);
}
cur = a;
return 1;
}
if (!(e = target()) || !(p = e.closest?.(SEC) || e.parentNode)) return 0;
style();
p.insertAdjacentHTML(
'afterend',
'<div id="' + CID + '" class="checker" ' + DA + '="' + a + '">' + links(a) + '</div>'
);
cur = a;
return 1;
}
function sync() {
const a = getASIN();
return a ? draw(a) : 0;
}
function pulse() {
let i = 0;
const g = ++gen,
f = () => {
if (g !== gen) return;
sync();
timer = ++i < 7
? w.setTimeout(f, i < 2 ? 64 : i < 3 ? 160 : i < 4 ? 400 : i < 5 ? 900 : 1800)
: 0;
};
if (timer) w.clearTimeout(timer);
timer = w.setTimeout(f, 0);
}
function boot(n, t) {
if (!sync() && n) {
w.setTimeout(() => {
boot(n - 1, t < 1024 ? t << 1 : t);
}, t);
}
}
function hook(name) {
const fn = h[name];
if (typeof fn !== 'function') return;
h[name] = function () {
const r = fn.apply(this, arguments);
pulse();
return r;
};
}
hook('pushState');
hook('replaceState');
w.addEventListener('popstate', pulse, true);
d.addEventListener('click', pulse, true);
d.addEventListener('change', pulse, true);
boot(16, 32);
})(document, location, history, window);