您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
显示 booth.pm 上日元价格对应的人民币价格,使用实时汇率 API
当前为
// ==UserScript== // @name booth.pm 人民币价格显示 // @namespace Violentmonkey Scripts // @match https://booth.pm/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect 60s-api.viki.moe // @version 2.7 // @author Viki <[email protected]> (https://github.com/vikiboss) // @description 显示 booth.pm 上日元价格对应的人民币价格,使用实时汇率 API // @license MIT // ==/UserScript== ;(async () => { // 在文档头部注入样式 const injectStyles = () => { const styleElement = document.createElement('style') styleElement.textContent = ` .booth-free-badge { display: inline-flex; align-items: center; font-weight: 600; font-size: 0.95em; padding: 0.15em 0.5em; border-radius: 4px; background: linear-gradient(135deg, #ff7043, #ff5252); color: white; box-shadow: 0 1px 3px rgba(0,0,0,0.12); text-shadow: 0 1px 1px rgba(0,0,0,0.1); margin: 0 0.15em; position: relative; overflow: hidden; transform: translateZ(0); } .booth-free-badge::before { content: ""; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); animation: shine 2s infinite; } @keyframes shine { 0% { left: -100%; } 20% { left: 100%; } 100% { left: 100%; } } .booth-jpy-note { font-size: 0.85em; opacity: 0.8; margin-left: 0.3em; } .booth-cny-price { display: inline-block; color: #119da4; font-weight: 500; font-size: 0.9em; } /* 价格筛选器样式 */ .booth-filter-cny { display: block; color: #119da4; font-size: 0.85em; margin-top: 2px; } ` document.head.appendChild(styleElement) } // 计算当天结束时间(23:59:59.999)的时间戳 const getTodayEndTimestamp = () => { const today = new Date() const todayEnd = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59, 999) return todayEnd.getTime() } // 获取汇率(优先使用缓存,每天更新一次) const getExchangeRate = () => { return new Promise((resolve) => { // 检查缓存是否存在且当天有效 const cachedRate = GM_getValue('jpy_to_cny_rate') const cacheTimestamp = GM_getValue('jpy_to_cny_rate_timestamp') const cacheExpiry = GM_getValue('jpy_to_cny_rate_expiry') // 如果有缓存且未过期 if (cachedRate && cacheExpiry && Date.now() < cacheExpiry) { console.log(`汇率数据(缓存): 1 JPY = ${cachedRate} CNY,获取时间: ${new Date(cacheTimestamp).toLocaleString()}`) resolve({ rate: parseFloat(cachedRate), isFromCache: true, timestamp: cacheTimestamp, }) return } // 缓存过期或不存在,请求新数据 const requestTime = Date.now() console.log(`请求最新汇率数据,时间: ${new Date(requestTime).toLocaleString()}`) GM_xmlhttpRequest({ method: 'GET', url: 'https://60s-api.viki.moe/v2/exchange_rate?currency=jpy', responseType: 'json', onload: (response) => { try { const data = response.response const responseTime = Date.now() if (data && data.code === 200) { // 查找日元到人民币的汇率 const cnyRate = data.data.rates.find((item) => item.currency === 'CNY')?.rate || 0.05 // 保存到GM缓存,设置到当天结束过期 GM_setValue('jpy_to_cny_rate', cnyRate.toString()) GM_setValue('jpy_to_cny_rate_timestamp', responseTime) GM_setValue('jpy_to_cny_rate_expiry', getTodayEndTimestamp()) console.log( `汇率数据(最新): 1 JPY = ${cnyRate} CNY,获取时间: ${new Date(responseTime).toLocaleString()}`, ) resolve({ rate: cnyRate, isFromCache: false, timestamp: responseTime, }) return } // API返回格式不正确,使用默认汇率 console.error('汇率API返回数据格式不正确,使用默认汇率') resolve({ rate: 0.05, isFromCache: false, timestamp: responseTime, }) } catch (error) { console.error('解析汇率数据失败:', error) resolve({ rate: 0.05, isFromCache: false, timestamp: Date.now(), }) } }, onerror: (error) => { console.error('汇率API请求失败:', error) resolve({ rate: 0.05, isFromCache: false, timestamp: Date.now(), }) }, ontimeout: () => { console.error('汇率API请求超时') resolve({ rate: 0.05, isFromCache: false, timestamp: Date.now(), }) }, timeout: 5000, // 5秒超时 }) }) } // 格式化日元转换为人民币的金额 const formatCnyAmount = (jpyAmount, exchangeRate) => { if (!jpyAmount || isNaN(jpyAmount)) return '0' return (jpyAmount * exchangeRate).toFixed(2).replace(/\.00$/, '') } // 提取数字金额 const extractAmountFromText = (text) => { // 提取纯数字部分,去除千位分隔符等 const matches = text.match(/\d+(?:,\d+)*/) if (matches && matches[0]) { return parseInt(matches[0].replace(/,/g, '')) } return 0 } // 检查元素是否已被处理 const isElementProcessed = (element) => { return element && element.dataset && element.dataset.priceProcessed === 'true' } // 注入样式 injectStyles() // 获取汇率 const { rate: exchangeRate } = await getExchangeRate() // 处理价格筛选器 const processPriceFilter = () => { try { // 查找价格筛选器部分 const priceFilters = document.querySelectorAll('.flex.w-full.justify-between') priceFilters.forEach((filterContainer) => { // 查找价格标签(一般是左右两个价格) const priceLabels = filterContainer.querySelectorAll('div') for (const priceLabel of priceLabels) { // 检查是否包含价格格式 "¥xxx" if (priceLabel.textContent.includes('¥')) { // 先移除已存在的人民币价格标签,避免重复添加 const existingCnyElement = priceLabel.querySelector('.booth-filter-cny') if (existingCnyElement) { existingCnyElement.remove() } const jpyText = priceLabel.textContent const jpyAmount = extractAmountFromText(jpyText) // 是否为0价格 if (jpyAmount === 0 && !jpyText.includes('Free')) { // 保存原始内容 const originalText = priceLabel.textContent // 清空内容以重建 priceLabel.innerHTML = '' if (originalText.trim() === '¥0') { // 创建"Free"徽章 const freeBadge = document.createElement('span') freeBadge.className = 'booth-free-badge' freeBadge.style.fontSize = '0.75em' freeBadge.style.padding = '0.1em 0.35em' freeBadge.textContent = 'FREE' priceLabel.appendChild(freeBadge) } else { // 可能是价格范围中的一部分,保留原文本 priceLabel.textContent = originalText } } else { // 非0价格,添加CNY价格 const cnyAmount = formatCnyAmount(jpyAmount, exchangeRate) // 保存原始文本内容 const originalText = priceLabel.textContent // 重建价格显示结构 priceLabel.innerHTML = '' // 原JPY价格 const jpySpan = document.createElement('span') jpySpan.textContent = originalText priceLabel.appendChild(jpySpan) // CNY价格(在下方显示) const cnySpan = document.createElement('span') cnySpan.className = 'booth-filter-cny' cnySpan.textContent = `¥${cnyAmount}` priceLabel.appendChild(cnySpan) } } } // 不再使用dataset标记,因为滑块变动时需要重新处理相同元素 }) } catch (error) { console.error('处理价格筛选器时出错:', error) } } // 处理普通的价格文本 const processRegularPrices = () => { try { // 匹配两种常见的价格格式 const jpyRegex = /^(\s*)(\d+(?:,\d+)*)(\s*)JPY(\s*)(~)?(\s*)$/i const yenRegex = /^(\s*)¥\s*(\d+(?:,\d+)*)(\s*)(~)?(\s*)$/ // 遍历文本节点 const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { // 检查节点及其父节点是否存在 if (!node || !node.parentNode) { return NodeFilter.FILTER_REJECT } // 跳过脚本、样式等标签内的文本 if (node.parentNode.tagName?.match(/^(SCRIPT|STYLE|TEXTAREA|OPTION)$/i)) { return NodeFilter.FILTER_REJECT } // 检查是否已处理 - 安全地访问dataset if (node.parentNode.dataset && node.parentNode.dataset.priceProcessed === 'true') { return NodeFilter.FILTER_REJECT } // 检查节点值是否存在 if (!node.nodeValue) { return NodeFilter.FILTER_REJECT } // 检查是否包含价格格式 if (jpyRegex.test(node.nodeValue) || yenRegex.test(node.nodeValue)) { return NodeFilter.FILTER_ACCEPT } return NodeFilter.FILTER_SKIP }, }) // 需要处理的节点 const nodesToProcess = [] let node while ((node = treeWalker.nextNode())) { if (node && node.parentNode) { nodesToProcess.push(node) } } // 处理收集的节点 for (const textNode of nodesToProcess) { // 再次检查节点是否有效 if (!textNode || !textNode.parentNode || !textNode.nodeValue) continue const text = textNode.nodeValue let match = text.match(jpyRegex) let isJPY = true if (!match) { match = text.match(yenRegex) isJPY = false if (!match) continue // 没有匹配到任何价格格式 } const [_, leading, amount, middle, trailing1, tilde, trailing2] = match const tildeStr = tilde || '' // 解析价格金额 const jpyAmount = parseInt(amount.replace(/,/g, '')) // 处理0金额 - 显示Free if (jpyAmount === 0) { const fragment = document.createDocumentFragment() // 添加前导空白 if (leading) { fragment.appendChild(document.createTextNode(leading)) } // 创建Free徽章 const freeBadge = document.createElement('span') freeBadge.className = 'booth-free-badge' freeBadge.textContent = 'FREE' fragment.appendChild(freeBadge) // 添加JPY标注(仅对JPY格式) if (isJPY) { const jpyNote = document.createElement('span') jpyNote.className = 'booth-jpy-note' jpyNote.textContent = '(0 JPY)' fragment.appendChild(jpyNote) } // 添加尾随文本 const trailing = `${trailing1 || ''}${tildeStr}${trailing2 || ''}` if (trailing) { fragment.appendChild(document.createTextNode(trailing)) } // 替换原节点 textNode.parentNode.replaceChild(fragment, textNode) } else { // 非0价格 - 添加CNY转换 const cnyAmount = formatCnyAmount(jpyAmount, exchangeRate) if (isJPY) { // JPY格式 textNode.nodeValue = `${leading}${amount}${middle}JPY (${cnyAmount} CNY)${trailing1}${tildeStr}${trailing2}` } else { // ¥格式 const fragment = document.createDocumentFragment() // 添加前导空白 if (leading) { fragment.appendChild(document.createTextNode(leading)) } // 原始价格 const jpySpan = document.createElement('span') jpySpan.textContent = `¥${amount}` fragment.appendChild(jpySpan) // CNY价格 const cnySpan = document.createElement('span') cnySpan.className = 'booth-cny-price' cnySpan.textContent = ` (¥${cnyAmount})` fragment.appendChild(cnySpan) // 添加尾随文本 const trailing = `${trailing1 || ''}${tildeStr}${trailing2 || ''}` if (trailing) { fragment.appendChild(document.createTextNode(trailing)) } // 替换原节点 textNode.parentNode.replaceChild(fragment, textNode) } } // 安全地标记为已处理 if (textNode.parentNode && textNode.parentNode.dataset) { textNode.parentNode.dataset.priceProcessed = 'true' } } } catch (error) { console.error('处理常规价格时出错:', error) } } // 处理所有价格 const processAllPrices = () => { try { // 先处理筛选器 processPriceFilter() // 再处理常规价格文本 processRegularPrices() } catch (error) { console.error('处理价格时发生未捕获错误:', error) } } // 监听DOM变化 const observer = new MutationObserver((mutations) => { // 延迟50ms执行处理,避免频繁触发 setTimeout(processAllPrices, 50) }) // 页面初始化 const initialize = () => { try { // 处理当前页面价格 processAllPrices() // 开始监听DOM变化 observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: false, }) // 对于AJAX加载内容,设置定期检查 setInterval(processAllPrices, 1500) } catch (error) { console.error('初始化脚本时出错:', error) } } // 根据页面加载状态决定何时初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize) } else { initialize() } })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址