// ==UserScript==
// @name buyNow!
// @namespace http://2chan.net/
// @version 0.1.3
// @description ふたばちゃんねるのスレッド上で貼られたAmazonのURLからtitleとあれば画像を取得する
// @author ame-chan
// @match https://*.2chan.net/b/res/*
// @match https://kako.futakuro.com/futa/*
// @match https://tsumanne.net/si/data/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=2chan.net
// @grant GM_xmlhttpRequest
// @connect amazon.co.jp
// @connect www.amazon.co.jp
// @connect amzn.to
// @connect amzn.asia
// @license MIT
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
const WHITE_LIST_URLS = [
'https://amazon.co.jp/',
'https://www.amazon.co.jp/',
'https://amzn.to/',
'https://amzn.asia/',
];
const isAmazon = (path) => ['amazon.co.jp', 'amzn.to', 'amzn.asia'].some((url) => path.includes(url));
const WHITE_LIST_SELECTORS = (() => WHITE_LIST_URLS.map((url) => `a[href^="${url}"]`).join(','))();
const isProductPage = (url) =>
/https:\/\/(www\.)?amazon\.co\.jp\/.*(?:gp\/product|dp)\/[A-Z0-9]{10}/.test(url) ||
/https:\/\/amzn.(asia|to)\//.test(url);
const addedStyle = ``;
if (!document.querySelector('#userjs-get-title-link')) {
document.head.insertAdjacentHTML('beforeend', addedStyle);
}
class FileReaderEx extends FileReader {
constructor() {
super();
}
#readAs(blob, ctx) {
return new Promise((res, rej) => {
super.addEventListener('load', ({ target }) => target?.result && res(target.result));
super.addEventListener('error', ({ target }) => target?.error && rej(target.error));
super[ctx](blob);
});
}
readAsArrayBuffer(blob) {
return this.#readAs(blob, 'readAsArrayBuffer');
}
readAsDataURL(blob) {
return this.#readAs(blob, 'readAsDataURL');
}
}
const getHTMLData = (url) =>
new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url,
timeout: 10000,
onload: (result) => {
if (result.status === 200) {
return resolve(result.responseText);
}
return resolve(false);
},
onerror: (err) => err && resolve(false),
ontimeout: () => resolve(false),
});
});
const setFailedText = (linkElm) => {
linkElm.insertAdjacentHTML('afterend', `データ取得失敗`);
};
const setTitleText = (targetDocument, linkElm) => {
const titleElm = targetDocument.querySelector('title');
if (!titleElm || !titleElm?.textContent) return;
linkElm.insertAdjacentHTML('afterend', `${titleElm.textContent}`);
};
const setImageElm = async (targetDocument, titleTextElm) => {
const imageEventHandler = (e) => {
const self = e.currentTarget;
if (!(self instanceof HTMLImageElement)) return;
if (self.width === 100) {
self.width = 600;
} else {
self.width = 100;
}
};
const imageElm =
targetDocument.querySelector('#landingImage') ||
targetDocument.querySelector('.unrolledScrollBox li:first-child img');
if (!(imageElm instanceof HTMLImageElement)) return;
const blob = await (await fetch(imageElm.src)).blob();
const dataUrl = await new FileReaderEx().readAsDataURL(blob);
const img = document.createElement('img');
img.src = dataUrl;
img.width = 100;
img.classList.add('userjs-image');
titleTextElm.insertAdjacentElement('afterend', img);
img.addEventListener('click', imageEventHandler);
};
const setLoading = (linkElm) => {
const loadingSVG =
'';
const parentElm = linkElm.parentElement;
if (parentElm instanceof HTMLFontElement || !isProductPage(linkElm.href)) {
return;
}
linkElm.insertAdjacentHTML('afterend', loadingSVG);
};
const removeLoading = (targetElm) => targetElm.parentElement?.querySelector('[data-id="userjs-loading"]')?.remove();
const insertURLData = async (linkElm) => {
const parentElm = linkElm.parentElement;
if (parentElm instanceof HTMLFontElement || !isProductPage(linkElm.href)) {
return;
}
const htmlData = await getHTMLData(linkElm.href);
if (!htmlData) {
setFailedText(linkElm);
return;
}
const parser = new DOMParser();
const targetDocument = parser.parseFromString(htmlData, 'text/html');
setTitleText(targetDocument, linkElm);
const titleTextElm = parentElm?.querySelector('.userjs-title');
if (isAmazon(linkElm.href) && titleTextElm) {
await setImageElm(targetDocument, titleTextElm);
}
removeLoading(linkElm);
};
const searchLinkElements = (targetElm) => {
const linkElms = targetElm.querySelectorAll(WHITE_LIST_SELECTORS);
if (!linkElms.length) return;
for (const linkElm of linkElms) {
if (!(linkElm instanceof HTMLElement)) continue;
setLoading(linkElm);
void insertURLData(linkElm);
}
};
const mutationLinkElements = async (mutations) => {
for (const mutation of mutations) {
for (const addedNode of mutation.addedNodes) {
if (!(addedNode instanceof HTMLElement)) continue;
searchLinkElements(addedNode);
}
}
};
const threadElm = document.querySelector('.thre');
if (threadElm) {
searchLinkElements(threadElm);
const observer = new MutationObserver(mutationLinkElements);
observer.observe(threadElm, {
childList: true,
});
}
})();