// ==UserScript==
// @name YouTube 超級留言金額統計
// @namespace http://tampermonkey.net/
// @version 4.2
// @description 支援多分頁獨立統計的簡潔版貨幣統計工具,最多5個實例
// @match *://www.youtube.com/live_chat*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 貨幣匯率設定
const CURRENCY_RATES = {
'$': 1, // 新台幣
'US$': 33, // 美元
'SGD': 24.7, // 新加坡幣
'HK$': 4.25, // 港幣
'¥': 0.22, // 日圓
'£': 42, // 英鎊
'€': 35, // 歐元
'AU$': 20 // 澳幣
};
// 最大實例數量
const MAX_INSTANCES = 5;
// 生成隨機ID
function generateId() {
return 'sc-' + Math.random().toString(36).substr(2, 9);
}
// 獲取或創建實例ID
function getInstanceId() {
let instanceId = sessionStorage.getItem('sc-instance-id');
if (!instanceId) {
instanceId = generateId();
sessionStorage.setItem('sc-instance-id', instanceId);
cleanupOldInstances();
}
return instanceId;
}
// 清理舊實例數據
function cleanupOldInstances() {
try {
const allKeys = GM_listValues();
const instanceKeys = allKeys.filter(key => key.startsWith('sc-instance-'));
if (instanceKeys.length > MAX_INSTANCES) {
const sorted = instanceKeys.map(key => {
const data = JSON.parse(GM_getValue(key, '{}'));
return { key, timestamp: data.timestamp || 0 };
}).sort((a, b) => a.timestamp - b.timestamp);
for (let i = 0; i < instanceKeys.length - MAX_INSTANCES; i++) {
GM_deleteValue(sorted[i].key);
}
}
} catch (e) {
console.error('清理實例數據失敗:', e);
}
}
// 狀態管理
function loadState(instanceId) {
const saved = GM_getValue(`sc-instance-${instanceId}`, '{}');
const parsed = JSON.parse(saved);
return {
totalAmount: parsed.totalAmount || 0,
totalCount: parsed.totalCount || 0,
isActive: parsed.isActive !== undefined ? parsed.isActive : true,
processedIds: new Set(parsed.processedIds || []),
timestamp: Date.now()
};
}
function saveState(instanceId, state) {
const data = {
totalAmount: state.totalAmount,
totalCount: state.totalCount,
isActive: state.isActive,
processedIds: Array.from(state.processedIds),
timestamp: Date.now()
};
GM_setValue(`sc-instance-${instanceId}`, JSON.stringify(data));
}
// 創建UI
function createUI(instanceId, state) {
const style = document.createElement('style');
style.textContent = `
.sc-stats-container {
position: fixed;
top: 30px;
left: 0;
z-index: 9999;
display: flex;
gap: 10px;
align-items: center;
font: bold 14px Arial, sans-serif;
}
.sc-stats-window {
background: transparent;
border-radius: 5px;
padding: 8px;
display: ${state.isActive ? 'flex' : 'none'};
align-items: center;
gap: 10px;
}
.sc-stats-content {
color: white;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
white-space: nowrap;
}
.sc-stats-btn {
cursor: pointer;
opacity: 0.8;
color: white;
text-shadow: inherit;
}
.sc-stats-btn:hover {
opacity: 1;
}
.sc-stats-toggle {
width: 24px;
height: 24px;
background: transparent;
border-radius: 3px;
display: ${state.isActive ? 'none' : 'flex'};
align-items: center;
justify-content: center;
cursor: pointer;
}
`;
document.head.appendChild(style);
// 容器
const container = document.createElement('div');
container.className = 'sc-stats-container';
container.id = `sc-container-${instanceId}`;
// 主視窗
const window = document.createElement('div');
window.className = 'sc-stats-window';
window.id = `sc-window-${instanceId}`;
const dollarBtn = document.createElement('span');
dollarBtn.className = 'sc-stats-btn';
dollarBtn.textContent = '$';
dollarBtn.addEventListener('click', () => closeStats(instanceId));
const content = document.createElement('div');
content.className = 'sc-stats-content';
content.id = `sc-content-${instanceId}`;
content.textContent = `${state.totalAmount.toFixed(2)} (${state.totalCount})`;
const resetBtn = document.createElement('span');
resetBtn.className = 'sc-stats-btn';
resetBtn.textContent = '重置';
resetBtn.addEventListener('click', () => resetStats(instanceId));
window.append(dollarBtn, content, resetBtn);
// 切換按鈕
const toggle = document.createElement('div');
toggle.className = 'sc-stats-toggle';
toggle.id = `sc-toggle-${instanceId}`;
toggle.textContent = '$';
toggle.addEventListener('click', () => openStats(instanceId));
container.append(window, toggle);
document.body.appendChild(container);
}
// 金額解析器
function parseAmount(text) {
const cleanText = text.replace(/,|\s+/g, '');
const pattern = new RegExp(`(SGD|US\\$|HK\\$|AU\\$|\\$|¥|£|€)(\\d+\\.?\\d*)|(\\d+\\.?\\d*)(SGD|US\\$|HK\\$|AU\\$|\\$|¥|£|€)`);
const match = cleanText.match(pattern);
if (!match) return null;
const currency = match[1] || match[4];
const amount = parseFloat(match[2] || match[3]);
return currency in CURRENCY_RATES ? {
amount: amount,
currency: currency,
converted: amount * CURRENCY_RATES[currency]
} : null;
}
// 處理超級留言
function processSuperChats(instanceId, state) {
if (!state.isActive) return;
const elements = document.querySelectorAll(`
yt-live-chat-paid-message-renderer #purchase-amount,
yt-live-chat-paid-sticker-renderer #purchase-amount-chip
`);
let updated = false;
elements.forEach(el => {
const parent = el.closest('yt-live-chat-paid-message-renderer, yt-live-chat-paid-sticker-renderer');
if (!parent || state.processedIds.has(parent.id)) return;
const parsed = parseAmount(el.textContent);
if (!parsed) return;
state.processedIds.add(parent.id);
state.totalAmount += parsed.converted;
state.totalCount++;
updated = true;
});
if (updated) {
updateUI(instanceId, state);
saveState(instanceId, state);
}
// 清理記憶體
if (state.processedIds.size > 1000) {
state.processedIds = new Set([...state.processedIds].slice(-500));
}
}
function updateUI(instanceId, state) {
const content = document.getElementById(`sc-content-${instanceId}`);
if (content) content.textContent = `${state.totalAmount.toFixed(2)} (${state.totalCount})`;
}
function resetStats(instanceId) {
const state = loadState(instanceId);
state.totalAmount = 0;
state.totalCount = 0;
state.processedIds.clear();
updateUI(instanceId, state);
saveState(instanceId, state);
}
function closeStats(instanceId) {
const state = loadState(instanceId);
state.isActive = false;
document.getElementById(`sc-window-${instanceId}`).style.display = 'none';
document.getElementById(`sc-toggle-${instanceId}`).style.display = 'flex';
saveState(instanceId, state);
}
function openStats(instanceId) {
const state = loadState(instanceId);
state.isActive = true;
document.getElementById(`sc-window-${instanceId}`).style.display = 'flex';
document.getElementById(`sc-toggle-${instanceId}`).style.display = 'none';
saveState(instanceId, state);
}
function init() {
const instanceId = getInstanceId();
const state = loadState(instanceId);
createUI(instanceId, state);
const observer = new MutationObserver(() => processSuperChats(instanceId, state));
const chatContainer = document.querySelector('#chat');
if (chatContainer) {
observer.observe(chatContainer, {
childList: true,
subtree: true
});
}
// 定期檢查以確保不會漏掉任何訊息
setInterval(() => processSuperChats(instanceId, state), 250);
// 頁面卸載時保存狀態
window.addEventListener('beforeunload', () => {
saveState(instanceId, state);
});
}
// 啟動腳本
if (document.readyState === 'complete') {
init();
} else {
window.addEventListener('load', init);
}
})();