您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ふたばちゃんねるのスレッド上で貼られたAmazonとDLsiteのURLからタイトルとあれば価格と画像を取得する
当前为
// ==UserScript== // @name buyNow! // @namespace http://2chan.net/ // @version 0.3.1 // @description ふたばちゃんねるのスレッド上で貼られたAmazonとDLsiteのURLからタイトルとあれば価格と画像を取得する // @author ame-chan // @match http://*.2chan.net/b/res/* // @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 // @connect dlsite.com // @connect bookwalker.jp // @connect c.bookwalker.jp // @license MIT // ==/UserScript== (function () { 'use strict'; const WHITE_LIST_URLS = [ 'https://amazon.co.jp/', 'https://www.amazon.co.jp/', 'https://amzn.to/', 'https://amzn.asia/', 'http://www.dlsite.com/', 'https://www.dlsite.com/', 'http://dlsite.com/', 'https://dlsite.com/', 'http://bookwalker.jp/', 'http://www.bookwalker.jp/', 'https://bookwalker.jp/', 'https://www.bookwalker.jp/', ]; const isAmazon = (path) => ['amazon.co.jp', 'amzn.to', 'amzn.asia'].some((url) => path.includes(url)); const isDLsite = (path) => path.includes('dlsite.com'); const isBookwalker = (path) => path.includes('bookwalker.jp'); const WHITE_LIST_SELECTORS = (() => WHITE_LIST_URLS.map((url) => `a[href^="${url}"]`).join(','))(); const isProductPage = (url) => /https:\/\/(www\.)?amazon\.co\.jp\/.*\/[A-Z0-9]{10}/.test(url) || /https:\/\/amzn.(asia|to)\//.test(url) || /https?:\/\/(www\.)?dlsite\.com\/.+?\/[A-Z0-9]{8,}\.html/.test(url) || /https?:\/\/bookwalker\.jp\/[a-z0-9]{10}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{12}/.test(url) || /https?:\/\/bookwalker\.jp\/series\/[0-9]+\/list/.test(url); const getBrandName = (url) => { if (isAmazon(url)) { return 'amazon'; } else if (isDLsite(url)) { return 'dlsite'; } else if (isBookwalker(url)) { return 'bookwalker'; } return ''; }; const addedStyle = `<style id="userjs-get-title-link"> .userjs-title { display: flex; flex-direction: row; margin: 8px 0 16px; padding: 16px; line-height: 1.6 !important; color: #ff3860 !important; background-color: #fff; border-radius: 4px; } .userjs-title-inner { width: 400px; } .userjs-link { padding-right: 24px; background-image: url('data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2038%2038%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20stroke%3D%22%23000%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cg%20transform%3D%22translate(1%201)%22%20stroke-width%3D%222%22%3E%3Ccircle%20stroke-opacity%3D%22.5%22%20cx%3D%2218%22%20cy%3D%2218%22%20r%3D%2218%22%2F%3E%3Cpath%20d%3D%22M36%2018c0-9.94-8.06-18-18-18%22%3E%20%3CanimateTransform%20attributeName%3D%22transform%22%20type%3D%22rotate%22%20from%3D%220%2018%2018%22%20to%3D%22360%2018%2018%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%2F%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; background-position: right center; } .userjs-image { margin-right: 16px; max-width: none !important; max-height: none !important; transition: all 0.3s ease-in-out; } .userjs-price { display: block; margin-top: 4px; color: #228b22 !important; font-weight: 700; } [data-id="userjs-loading"] { margin-left: 4px; } </style>`; 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 fetchData = (url, responseType) => new Promise((resolve) => { let options = { method: 'GET', url, timeout: 10000, onload: (result) => { if (result.status === 200) { return resolve(result.response); } return resolve(false); }, onerror: () => resolve(false), ontimeout: () => resolve(false), }; if (typeof responseType === 'string') { options = { ...options, responseType, }; } GM_xmlhttpRequest(options); }); const setFailedText = (linkElm) => { linkElm.insertAdjacentHTML('afterend', `<span class="userjs-title">データ取得失敗</span>`); }; const getPriceText = ({ targetDocument, brandName }) => { if (brandName === '') return ''; const targetElement = { amazon: () => { const priceRange = () => { const rangeElm = targetDocument.querySelector('.a-price-range'); if (!rangeElm) return 0; rangeElm.querySelectorAll('.a-offscreen').forEach((el) => el.remove()); return rangeElm.textContent?.replace(/[\s]+/g, ''); }; const price = targetDocument.querySelector('#twister-plus-price-data-price')?.value || targetDocument.querySelector('#kindle-price')?.textContent?.replace(/[\s¥]+/g, '') || targetDocument.querySelector('[name="displayedPrice"]')?.value; return Number(price) || priceRange() || 0; }, dlsite: () => { const url = targetDocument.querySelector('meta[property="og:url"]')?.content; const productId = url.split('/').pop()?.replace('.html', ''); const priceElm = targetDocument.querySelector(`[data-product_id="${productId}"][data-price]`); return parseInt(priceElm?.getAttribute('data-price') || '', 10); }, bookwalker: () => { const price = Number( targetDocument .querySelector('.m-tile-list .m-tile .m-book-item__price-num') ?.textContent?.replace(/,/g, ''), ) || Number(targetDocument.querySelector('#jsprice')?.textContent?.replace(/[円,]/g, '')); return Number.isInteger(price) && price > 0 ? price : 0; }, }; const price = targetElement[brandName](); let priceText = price; if (!price) return ''; if (typeof price === 'number' && Number.isInteger(price) && price > 0) { priceText = new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY', }).format(price); } return `<span class="userjs-price">${priceText}</span>`; }; const setTitleText = ({ targetDocument, linkElm, brandName }) => { const titleElm = targetDocument.querySelector('title'); if (!titleElm || !titleElm?.textContent) return; const priceText = getPriceText({ targetDocument, brandName, }); const nextSibling = linkElm.nextElementSibling; if (nextSibling && nextSibling instanceof HTMLElement && nextSibling.tagName.toLowerCase() === 'br') { nextSibling.style.display = 'none'; } linkElm?.insertAdjacentHTML( 'afterend', `<div class="userjs-title"> <span class="userjs-title-inner">${titleElm.textContent}${priceText}</span> </div>`, ); }; const setImageElm = async ({ targetDocument, titleTextElm, brandName }) => { if (brandName === '') return; const imageEventHandler = (e) => { const self = e.currentTarget; if (!(self instanceof HTMLImageElement)) return; if (self.width === 100) { self.width = 600; } else { self.width = 100; } }; const targetElement = { amazon: targetDocument.querySelector('#landingImage')?.src || targetDocument.querySelector('.unrolledScrollBox li:first-child img')?.src || targetDocument.querySelector('[data-a-image-name]')?.src || targetDocument.querySelector('#imgBlkFront')?.src, dlsite: targetDocument.querySelector('meta[property="og:image"]')?.content, bookwalker: targetDocument.querySelector('.m-tile-list .m-tile img')?.getAttribute('data-original') || targetDocument.querySelector('meta[property="og:image"]')?.content, }; const imagePath = targetElement[brandName]; if (typeof imagePath !== 'string') return; const blob = await fetchData(imagePath, 'blob'); if (!blob) return; const dataUrl = await new FileReaderEx().readAsDataURL(blob); const img = document.createElement('img'); img.src = dataUrl; img.width = 100; img.classList.add('userjs-image'); titleTextElm.querySelector('.userjs-title-inner')?.insertAdjacentElement('beforebegin', img); img.addEventListener('click', imageEventHandler); }; const setLoading = (linkElm) => { const parentElm = linkElm.parentElement; if ( parentElm instanceof HTMLFontElement || !isProductPage(linkElm.href) || parentElm?.querySelector('[data-id="userjs-loading"]') ) { return; } linkElm.classList.add('userjs-link'); }; const removeLoading = (targetElm) => targetElm.classList.remove('userjs-link'); const isAmazonConfirmAdultPage = (targetDocument) => targetDocument.querySelector('#black-curtain-warning') !== null; const getAmazonConfirmAdultPageHref = (targetDocument) => { const yesBtnLinkElm = targetDocument.querySelector('#black-curtain-yes-button a'); if (yesBtnLinkElm instanceof HTMLAnchorElement) { return `https://www.amazon.co.jp${yesBtnLinkElm.getAttribute('href')}`; } return false; }; const getAmazonAdultDocument = async (targetDocument, linkElm, parser) => { const newHref = getAmazonConfirmAdultPageHref(targetDocument); const htmlData = newHref && (await fetchData(newHref)); if (!htmlData) { setFailedText(linkElm); removeLoading(linkElm); return false; } return parser.parseFromString(htmlData, 'text/html'); }; const insertURLData = async (linkElm) => { const parentElm = linkElm.parentElement; if (parentElm instanceof HTMLFontElement || !isProductPage(linkElm.href)) { return; } const htmlData = await fetchData(linkElm.href); if (!htmlData) { setFailedText(linkElm); removeLoading(linkElm); return; } const parser = new DOMParser(); let targetDocument = parser.parseFromString(htmlData, 'text/html'); // アダルトページ確認画面スキップ if (isAmazonConfirmAdultPage(targetDocument)) { const amazonAdultDocument = await getAmazonAdultDocument(targetDocument, linkElm, parser); if (amazonAdultDocument) { targetDocument = amazonAdultDocument; } } const brandName = getBrandName(linkElm.href); setTitleText({ targetDocument, linkElm, brandName, }); const titleTextElm = linkElm.nextElementSibling; if (titleTextElm) { await setImageElm({ targetDocument, titleTextElm, brandName, }); } removeLoading(linkElm); }; const replaceDefaultURL = (targetElm) => { const linkElms = targetElm.querySelectorAll('a[href]'); const replaceUrl = (url) => { const regex = /http:\/\/www\.dlsite\.com\/(.+?)\/dlaf\/=\/link\/work\/aid\/[a-zA-Z]+\/id\/(RJ[0-9]+)\.html/; const newUrlFormat = 'https://www.dlsite.com/$1/work/=/product_id/$2.html'; return url.replace(regex, newUrlFormat); }; for (const linkElm of linkElms) { const brandName = getBrandName(linkElm.href); const href = linkElm.getAttribute('href'); if (brandName === 'dlsite') { linkElm.href = replaceUrl(href.replace('/bin/jump.php?', '')); } else { linkElm.href = href.replace('/bin/jump.php?', ''); } } }; 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; replaceDefaultURL(addedNode); searchLinkElements(addedNode); } } }; const threadElm = document.querySelector('.thre'); if (threadElm instanceof HTMLElement) { replaceDefaultURL(threadElm); searchLinkElements(threadElm); const observer = new MutationObserver(mutationLinkElements); observer.observe(threadElm, { childList: true, }); } })();