// ==UserScript==
// @name 巴哈小瑪莉自動投注器
// @description 自動投注巴哈小瑪莉(含防破產保護、開獎統計)。
// @author 由Gemini產生
// @version 1.0
// @match https://now.gamer.com.tw/chat_list.php*
// @icon https://ani.gamer.com.tw/apple-touch-icon-144.jpg
// @require https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js
// @grant GM_addStyle
// @noframes
// @license MIT
// @namespace https://greasyfork.org/users/1308487
// ==/UserScript==
(function() {
'use strict';
// ===================================================================
// 1. 【設定與數據儲存區】
// ===================================================================
const STORAGE_KEY = 'maryBettingData';
const PLAYER_NAME = '巴哈小瑪莉'; // 固定的開獎機器人名稱
const INPUT_FIELD_ID = 'msg_input';
const SEND_BUTTON_SELECTOR = '.chat_inputarea .btn-send.is-ready';
// 預設投注指令,方便集中管理
const DEFAULT_BET_COMMAND = '/mary 0 10 10 0 0 0 0 0 0';
// 【修改】: 投注間隔,以秒為單位,用於設定 UI 預設值
const DEFAULT_BET_INTERVAL_SEC = 5;
// 【新增】: 轉換單位常數
const MS_PER_SECOND = 1000;
let isRunning = false;
let autoBetTimer = null;
let totalCommandsSent = 0;
let totalDraws = 0;
let initialBahbi = 0;
let currentBahbi = 0;
let isSafetyPaused = false;
// 動態儲存當前分析機器人名稱,不進行持久化儲存
let currentAnalyzerName = null;
// 預設安全水位線設定 (已修正註釋語法)
let safetyConfig = {
// 百分比虧損停止 (30%)
percentLoss: 30,
// 絕對金額虧損停止
absoluteLoss: 500,
// 最低庫存停止
minInventory: 1000
};
// 選項的固定順序鍵名
const OPTION_ORDER = [
'2', '5', '10', '15', '20-grape', '20-bell', '30', '40', '100'
];
// 賠率映射表
const PAYOUT_MAP = {
'2': 2, '5': 5, '10': 10, '15': 15,
'20-grape': 20, '20-bell': 20,
'30': 30, '40': 40, '100': 100
};
let drawingCounts = {
'2': 0, '5': 0, '10': 0, '15': 0, '20-grape': 0, '20-bell': 0, '30': 0, '40': 0, '100': 0
};
const DISPLAY_NAMES = {
'2': '🍒 (2)', '5': '🍎 (5)', '10': '🍊 (10)', '15': '🍇 (15)',
'20-grape': '🍉 (20)', '20-bell': '🔔 (20)',
'30': '⭐ (30)', '40': '7️⃣ (40)', '100': '🎰 (100)'
};
const SYMBOL_MAP = {
'🍒': '2', '🍎': '5', '🍊': '10', '🍇': '15',
'🍉': '20-grape', '🔔': '20-bell', '⭐': '30', '7️⃣': '40', '🎰': '100'
};
// 儲存 UI 位置 (預設右上角)
let uiPosition = { top: 20, left: window.innerWidth - 270 };
// ===================================================================
// 2. 【數據持久化函數】: 儲存與載入
// ===================================================================
function saveData() {
const data = {
counts: drawingCounts,
totalDraws: totalDraws,
totalCommandsSent: totalCommandsSent,
initialBahbi: initialBahbi,
currentBahbi: currentBahbi,
safetyConfig: safetyConfig,
isSafetyPaused: isSafetyPaused,
uiPosition: uiPosition
};
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) {
console.error('[數據] 儲存數據失敗:', e);
}
}
function loadData() {
try {
const storedData = localStorage.getItem(STORAGE_KEY);
if (storedData) {
const data = JSON.parse(storedData);
Object.keys(drawingCounts).forEach(key => {
if (data.counts && data.counts[key] !== undefined) {
drawingCounts[key] = data.counts[key];
}
});
totalDraws = data.totalDraws || 0;
totalCommandsSent = data.totalCommandsSent || 0;
initialBahbi = data.initialBahbi || 0;
currentBahbi = data.currentBahbi || 0;
if (data.safetyConfig) {
safetyConfig = data.safetyConfig;
}
isSafetyPaused = data.isSafetyPaused || false;
if (data.uiPosition) {
uiPosition = data.uiPosition;
} else {
uiPosition.left = window.innerWidth - 270;
}
console.log(`[數據] 成功載入歷史數據。總開獎次數: ${totalDraws}`);
}
} catch (e) {
console.error('[數據] 載入數據失敗或數據格式錯誤:', e);
}
}
function resetData() {
if (!confirm('確定要歸零所有開獎數據嗎?此操作不可撤銷!')) {
return;
}
Object.keys(drawingCounts).forEach(key => {
drawingCounts[key] = 0;
});
totalDraws = 0;
totalCommandsSent = 0;
initialBahbi = 0;
currentBahbi = 0;
isSafetyPaused = false;
// 偵測到的機器人名稱也重置
currentAnalyzerName = null;
saveData();
displayStats();
updateBahbiStatus();
console.log('[數據] 所有數據已歸零並儲存。');
}
// ===================================================================
// 3. 【數據分析與安全檢查】: 核心邏輯
// ===================================================================
/**
* 檢查是否觸發安全水位線
*/
function checkSafetyLimits() {
if (!isRunning || initialBahbi <= 0) return false;
const currentLoss = initialBahbi - currentBahbi;
const limitReached = document.getElementById('safety-limit-text');
const percentLimit = initialBahbi * (safetyConfig.percentLoss / 100);
if (currentLoss >= percentLimit && safetyConfig.percentLoss > 0) {
stopAutoBet(`[資金保護] 虧損達到 ${safetyConfig.percentLoss}% 水位線!`);
limitReached.textContent = `已觸發:虧損達 ${safetyConfig.percentLoss}%`;
isSafetyPaused = true;
return true;
}
if (currentLoss >= safetyConfig.absoluteLoss && safetyConfig.absoluteLoss > 0) {
stopAutoBet(`[資金保護] 虧損達到 ${safetyConfig.absoluteLoss.toLocaleString()} 元!`);
limitReached.textContent = `已觸發:虧損達 ${safetyConfig.absoluteLoss.toLocaleString()}`;
isSafetyPaused = true;
return true;
}
if (currentBahbi <= safetyConfig.minInventory && safetyConfig.minInventory > 0) {
stopAutoBet(`[資金保護] 庫存低於最低 ${safetyConfig.minInventory.toLocaleString()} 元!`);
limitReached.textContent = `已觸發:庫存低於 ${safetyConfig.minInventory.toLocaleString()}`;
isSafetyPaused = true;
return true;
}
if (isSafetyPaused) {
limitReached.textContent = `檢查中...`;
isSafetyPaused = false;
}
limitReached.textContent = '檢查中...';
return false;
}
/**
* 更新巴幣狀態顯示
*/
function updateBahbiStatus(newBahbi = null) {
const bahbiStatusEl = document.getElementById('bahbi-status');
if (!bahbiStatusEl) return;
if (newBahbi !== null) {
currentBahbi = newBahbi;
}
if (currentBahbi > 0 && initialBahbi > 0) {
const change = currentBahbi - initialBahbi;
const sign = change >= 0 ? '+' : '';
const formattedChange = `${sign}${change.toLocaleString()}`;
bahbiStatusEl.textContent = `巴幣: ${currentBahbi.toLocaleString()} (${formattedChange})`;
checkSafetyLimits();
} else if (initialBahbi > 0) {
bahbiStatusEl.textContent = `巴幣: ${initialBahbi.toLocaleString()} (+0)`;
} else {
bahbiStatusEl.textContent = `巴幣: - (未追蹤)`;
}
}
/**
* 計算並標記正期望值的項目
*/
function calculateExpectedValue() {
const dataTableBody = document.getElementById('data-table-body');
if (!dataTableBody || totalDraws === 0) return;
let maxExpectedValue = 0;
let bestOptionKey = null;
// 1. 計算每個選項的期望值 (Expected Value, EV)
const evMap = {};
OPTION_ORDER.forEach(key => {
const count = drawingCounts[key];
const payout = PAYOUT_MAP[key];
const probability = count / totalDraws;
const expectedValue = probability * payout; // EV = P * R
evMap[key] = expectedValue;
// 尋找最高的 EV
if (expectedValue > 1.0 && expectedValue > maxExpectedValue) {
maxExpectedValue = expectedValue;
bestOptionKey = key;
}
});
// 2. 遍歷表格行並進行標記
const rows = dataTableBody.querySelectorAll('tr');
rows.forEach((row, index) => {
const key = OPTION_ORDER[index];
const ev = evMap[key] || 0; // 確保有 EV 值
const probabilityCell = row.cells[2];
// 移除舊樣式
probabilityCell.style.backgroundColor = 'transparent';
probabilityCell.style.color = 'white';
probabilityCell.style.fontWeight = 'normal';
probabilityCell.style.border = 'none';
if (ev > 1.0) {
// 如果是正期望值 (> 1.0)
probabilityCell.style.backgroundColor = '#FFEB3B'; // 黃底
probabilityCell.style.color = 'black';
if (key === bestOptionKey) {
// 如果是最高的正期望值
probabilityCell.style.fontWeight = 'bold';
probabilityCell.style.border = '1px solid #FF9800';
}
}
});
}
/**
* 計算並在 UI 介面下方顯示統計數據
*/
function displayStats() {
let tableRowsHTML = '';
let totalCount = 0;
OPTION_ORDER.forEach(key => {
const displayName = DISPLAY_NAMES[key] || key;
const count = drawingCounts[key];
totalCount += count;
const probability = totalDraws > 0 ? (count / totalDraws * 100).toFixed(3) : '0.000';
tableRowsHTML += `
<tr>
<td>${displayName}</td>
<td style="text-align: right;">${count}</td>
<td class="probability-cell" style="text-align: right;">${probability}%</td>
</tr>
`;
});
const dataTableBody = document.getElementById('data-table-body');
const totalRow = document.getElementById('data-table-total');
if (dataTableBody) {
dataTableBody.innerHTML = tableRowsHTML;
// 數據載入後立即計算並標記正期望值
calculateExpectedValue();
}
if (totalRow) {
totalRow.innerHTML = `
<td>總計</td>
<td style="text-align: right;">${totalDraws}</td>
<td style="text-align: right;">100.000%</td>
`;
}
let statusText = isSafetyPaused ? '安全暫停' : (isRunning ? '執行中' : '已停止');
let statusColor = isSafetyPaused ? '#FF9800' : (isRunning ? '#4CAF50' : '#ffeb3b');
updateUIStatus(statusText, statusColor);
}
/**
* 解析單條訊息中的開獎結果
*/
function parseSingleResult(messageText, userName) {
const resultLineMatch = messageText.match(/開獎結果[::][\s\S]*?\([\s\S]*?(\d+)[\s\S]*?\)/);
if (!resultLineMatch) return false;
const resultLine = resultLineMatch[0];
let keyToUpdate = null;
for (const symbol in SYMBOL_MAP) {
if (resultLine.includes(symbol)) {
keyToUpdate = SYMBOL_MAP[symbol];
break;
}
}
if (keyToUpdate && drawingCounts[keyToUpdate] !== undefined) {
drawingCounts[keyToUpdate]++;
totalDraws++;
const bahbiRegex = /剩餘巴幣[::]\s*([\d,]+)/;
const bahbiMatch = messageText.match(bahbiRegex);
if (bahbiMatch) {
const remainingBahbi = parseInt(bahbiMatch[1].replace(/,/g, ''), 10);
if (remainingBahbi > 0) {
if (initialBahbi === 0) {
initialBahbi = remainingBahbi;
}
updateBahbiStatus(remainingBahbi);
}
}
// 第一次成功解析分析機器人發出的結果時,儲存其名稱
if (!currentAnalyzerName && userName !== PLAYER_NAME) {
currentAnalyzerName = userName;
console.log(`[偵測] 成功識別當前分析機器人為: ${currentAnalyzerName}`);
}
saveData();
displayStats(); // 每次開獎都更新並重新計算 EV
return true;
}
return false;
}
/**
* 檢查單一訊息是否為開獎結果,並確保不會重複計數
*/
function checkAndParseNewMessage(messageElement) {
if (messageElement.dataset.parsed === 'true') {
return false;
}
const userNameEl = messageElement.querySelector('.now_user-info .now_user-nickname');
const userName = userNameEl ? userNameEl.textContent.trim() : null;
const messageText = messageElement.textContent.trim();
if (!messageText) {
return false;
}
const isResult = messageText.includes('開獎結果');
if (!isResult) return false;
// 1. 判斷是否為「巴哈小瑪莉」發出的系統開獎結果
const dateRegex = /\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/;
const dateMatch = messageText.match(dateRegex);
const isSystemResult = (userName === PLAYER_NAME || !userNameEl) && dateMatch;
// 2. 判斷是否為分析機器人發出的結果
let isAnalyzerMessage = false;
if (userName && userName !== PLAYER_NAME) {
// 如果已經偵測到名稱,使用該名稱
if (currentAnalyzerName && userName === currentAnalyzerName) {
isAnalyzerMessage = true;
}
// 如果尚未偵測到名稱,或這是新的分析機器人 (非巴哈小瑪莉)
else if (!currentAnalyzerName || (currentAnalyzerName && userName !== currentAnalyzerName)) {
// 允許通過解析,並在 parseSingleResult 中更新 currentAnalyzerName
isAnalyzerMessage = true;
}
}
if (isSystemResult || isAnalyzerMessage) {
if (parseSingleResult(messageText, userName)) {
messageElement.dataset.parsed = 'true';
return true;
}
}
return false;
}
// ===================================================================
// 4. 【控制與執行函數】
// ===================================================================
function updateUIStatus(status, color) {
const statusElement = document.getElementById('ui-status');
if (statusElement) {
statusElement.textContent = status;
statusElement.style.color = color;
}
const startButton = document.getElementById('start-bet');
const stopButton = document.getElementById('stop-bet');
if (startButton) startButton.disabled = isRunning || isSafetyPaused;
if (stopButton) stopButton.disabled = !isRunning && !isSafetyPaused;
const commandCountElement = document.getElementById('command-count');
if (commandCountElement) commandCountElement.textContent = `發送指令數: ${totalCommandsSent}`;
}
function readCurrentBahbiFromLastMessage() {
const elements = document.querySelectorAll(".msg_container");
if (elements.length === 0) return 0;
const lastMessage = elements[elements.length - 1];
const messageText = lastMessage.textContent;
const bahbiRegex = /剩餘巴幣[::]\s*([\d,]+)/;
const bahbiMatch = messageText.match(bahbiRegex);
if (bahbiMatch) {
return parseInt(bahbiMatch[1].replace(/,/g, ''), 10);
}
return 0;
}
function placeBet() {
if (isSafetyPaused) {
console.log('[自動投注] 因觸發安全水位線,暫停投注。');
return;
}
const inputField = document.getElementById(INPUT_FIELD_ID);
const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
const command = document.getElementById('bet-command').value;
if (!inputField || !sendButton) {
console.error(`找不到輸入框或發送按鈕!無法發送指令。`);
stopAutoBet();
return;
}
if (inputField && sendButton) {
inputField.textContent = command;
inputField.dispatchEvent(new Event('input', { bubbles: true }));
sendButton.click();
totalCommandsSent++;
saveData();
console.log(`[自動投注] 已發送指令: ${command}`);
updateUIStatus('執行中 (等待結果)', '#4CAF50');
}
}
function startAutoBet() {
if (isRunning) return;
isSafetyPaused = false;
isRunning = true;
safetyConfig.percentLoss = parseInt(document.getElementById('safety-percent-loss').value) || 0;
safetyConfig.absoluteLoss = parseInt(document.getElementById('safety-absolute-loss').value.replace(/,/g, '')) || 0;
safetyConfig.minInventory = parseInt(document.getElementById('safety-min-inventory').value.replace(/,/g, '')) || 0;
const lastKnownBahbi = readCurrentBahbiFromLastMessage();
if (lastKnownBahbi > 0) {
initialBahbi = lastKnownBahbi;
currentBahbi = lastKnownBahbi;
updateBahbiStatus();
document.getElementById('safety-limit-text').textContent = '檢查中...';
console.log(`[巴幣追蹤] 啟動時記錄初始巴幣: ${initialBahbi.toLocaleString()}`);
} else {
initialBahbi = 0;
currentBahbi = 0;
updateBahbiStatus();
document.getElementById('safety-limit-text').textContent = '請等待第一筆開獎結果以設定初始巴幣。';
console.warn('[巴幣追蹤] 無法從當前聊天記錄中讀取剩餘巴幣。追蹤將從下次成功開獎後開始。');
}
const intervalSec = parseInt(document.getElementById('bet-interval-sec').value);
// 【修改】: 使用 MS_PER_SECOND 進行計算
const intervalMs = intervalSec * MS_PER_SECOND;
placeBet();
autoBetTimer = setInterval(placeBet, intervalMs);
console.log(`[控制台] 自動投注已啟動,間隔 ${intervalSec} 秒。`);
saveData();
}
function stopAutoBet(reason = '[控制台] 手動終止。') {
if (!isRunning && !isSafetyPaused) return;
clearInterval(autoBetTimer);
isRunning = false;
if (isSafetyPaused) {
isSafetyPaused = false;
reason = '[資金保護解除] 已手動清除安全鎖定。';
document.getElementById('safety-limit-text').textContent = '安全鎖定已解除,請點擊執行重新啟動。';
} else {
reason = '[控制台] 手動終止。';
}
console.log(reason);
updateUIStatus('已停止', '#ffeb3b');
updateBahbiStatus();
saveData();
}
// ===================================================================
// 5. 【核心 UI 函數】
// ===================================================================
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
const targetTagName = e.target.tagName;
if (targetTagName === 'INPUT' || targetTagName === 'BUTTON' || targetTagName === 'SELECT' || targetTagName === 'TEXTAREA' || e.target.closest('input, button, select, textarea')) {
return;
}
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
let newTop = element.offsetTop - pos2;
let newLeft = element.offsetLeft - pos1;
newTop = Math.max(0, Math.min(newTop, window.innerHeight - element.offsetHeight));
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - element.offsetWidth));
element.style.top = newTop + "px";
element.style.left = newLeft + "px";
element.style.right = "auto";
uiPosition.top = newTop;
uiPosition.left = newLeft;
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
saveData();
console.log(`[UI] 位置已更新: Top: ${uiPosition.top}px, Left: ${uiPosition.left}px`);
}
}
function createUI() {
const uiContainer = document.createElement('div');
uiContainer.id = 'tampermonkey-control-ui';
// 使用 DEFAULT_BET_COMMAND 和 DEFAULT_BET_INTERVAL_SEC 常數
uiContainer.innerHTML = `
<div style="padding: 10px; border-radius: 5px; background: rgba(0, 0, 0, 0.85); color: white; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); font-size: 13px;">
<h4 style="margin: 0 0 10px 0; font-size: 16px; border-bottom: 1px solid #444; padding-bottom: 5px;">自動投注控制台 (可拖曳)</h4>
<div style="margin-bottom: 8px;">
<label for="bet-command" style="display: block; font-size: 12px; margin-bottom: 3px;">投注指令</label>
<input type="text" id="bet-command" value="${DEFAULT_BET_COMMAND}" style="width: 100%; padding: 4px; border: none; border-radius: 3px; background: #333; color: white;">
</div>
<div style="margin-bottom: 12px; display: flex; align-items: center;">
<label for="bet-count" style="font-size: 12px; margin-right: 5px; white-space: nowrap;">投注間隔 (秒):</label>
<input type="number" id="bet-interval-sec" value="${DEFAULT_BET_INTERVAL_SEC}" style="width: 50px; padding: 4px; border: none; border-radius: 3px; background: #333; color: white; text-align: center;">
<span id="ui-status" style="margin-left: auto; font-size: 14px; font-weight: bold; color: #ffeb3b;">已停止</span>
</div>
<div style="display: flex; justify-content: space-between;">
<button id="start-bet" style="padding: 6px 10px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; flex-grow: 1; margin-right: 5px;">執行</button>
<button id="stop-bet" style="padding: 6px 10px; background: #F44336; color: white; border: none; border-radius: 3px; cursor: pointer; flex-grow: 1; margin-left: 5px;" disabled>終止</button>
</div>
<p id="command-count" style="font-size: 12px; margin: 10px 0 0 0; color: #ccc;">發送指令數: 0</p>
<p id="bahbi-status" style="font-size: 12px; margin: 0; color: #aaa;">巴幣: - (未追蹤)</p>
<p id="safety-limit-text" style="font-size: 12px; margin: 0; color: #ccc;">-</p>
<h4 style="margin: 15px 0 5px 0; font-size: 14px; border-top: 1px solid #444; padding-top: 10px;">
資金安全水位線 (Stop-Loss)
</h4>
<div class="safety-config-block" style="font-size: 12px; line-height: 1.5;">
<div class="safety-row">
<span class="safety-label">虧損達初始巴幣:</span>
<input type="number" id="safety-percent-loss" value="${safetyConfig.percentLoss}" min="0" max="100" class="safety-input percent-input">
<span class="safety-unit">% 時停止</span>
</div>
<div class="safety-row">
<span class="safety-label">虧損達絕對金額:</span>
<input type="text" id="safety-absolute-loss" value="${safetyConfig.absoluteLoss.toLocaleString()}" class="safety-input money-input">
<span class="safety-unit">元時停止</span>
</div>
<div class="safety-row">
<span class="safety-label">庫存低於:</span>
<input type="text" id="safety-min-inventory" value="${safetyConfig.minInventory.toLocaleString()}" class="safety-input money-input">
<span class="safety-unit">元時停止</span>
</div>
</div>
<h4 style="margin: 15px 0 5px 0; font-size: 14px; border-top: 1px solid #444; padding-top: 10px; display: flex; justify-content: space-between; align-items: center;">
<span>開獎數據分析</span>
<button id="reset-data" style="padding: 2px 5px; font-size: 10px; background: #607D8B; color: white; border: none; border-radius: 3px; cursor: pointer;">歸零數據</button>
</h4>
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
<thead style="background: #222;">
<tr>
<th style="padding: 4px; text-align: left;">選項</th>
<th style="padding: 4px; text-align: right;">次數</th>
<th style="padding: 4px; text-align: right;">機率</th>
</tr>
</thead>
<tbody id="data-table-body">
</tbody>
<tfoot style="border-top: 1px solid #444; font-weight: bold;">
<tr id="data-table-total">
</tr>
</tfoot>
</table>
</div>
`;
// 設置 UI 樣式,使用儲存的位置,並添加移動游標
uiContainer.style.cssText = `
position: fixed;
top: ${uiPosition.top}px;
left: ${uiPosition.left}px;
right: auto;
z-index: 9999;
width: 280px;
font-family: Arial, sans-serif;
cursor: move;
`;
document.body.appendChild(uiContainer);
// 啟用拖曳功能
makeDraggable(uiContainer);
// 綁定事件
document.getElementById('start-bet').addEventListener('click', startAutoBet);
document.getElementById('stop-bet').addEventListener('click', stopAutoBet);
document.getElementById('reset-data').addEventListener('click', resetData);
// 綁定安全設定的輸入事件,使其自動儲存設定
document.getElementById('safety-percent-loss').addEventListener('change', updateSafetyConfig);
document.getElementById('safety-absolute-loss').addEventListener('change', updateSafetyConfig);
document.getElementById('safety-min-inventory').addEventListener('change', updateSafetyConfig);
displayStats();
updateBahbiStatus();
document.getElementById('safety-limit-text').textContent = isSafetyPaused ? `安全暫停 (請點擊終止解除)` : '-';
}
/**
* 更新安全設定物件並儲存
*/
function updateSafetyConfig() {
safetyConfig.percentLoss = parseInt(document.getElementById('safety-percent-loss').value) || 0;
safetyConfig.absoluteLoss = parseInt(document.getElementById('safety-absolute-loss').value.replace(/,/g, '')) || 0;
safetyConfig.minInventory = parseInt(document.getElementById('safety-min-inventory').value.replace(/,/g, '')) || 0;
// 重新格式化顯示,保持千分位
document.getElementById('safety-absolute-loss').value = safetyConfig.absoluteLoss.toLocaleString();
document.getElementById('safety-min-inventory').value = safetyConfig.minInventory.toLocaleString();
saveData();
console.log('[設定] 資金安全設定已更新。');
if (isRunning) {
checkSafetyLimits();
}
}
function markExistingMessages() {
const existingMessages = document.querySelectorAll(".msg_container");
existingMessages.forEach(el => {
el.dataset.parsed = 'true';
});
console.log(`[啟動] 已標記 ${existingMessages.length} 條歷史訊息為已解析 (避免 F5 重複計數)。`);
}
function handleLayout() {
const elements = document.querySelectorAll(".msg_container");
if (elements.length > 0) {
const lastMessage = elements[elements.length - 1];
checkAndParseNewMessage(lastMessage);
}
}
GM_addStyle(`
.now_chatroom-container .chatroom .msg_container { margin-top: 12px !important; margin-bottom: 12px !important; }
.now_chatroom-container.is-bpage { height: 90vh !important; }
div.user-runes { display: none !important; }
.chatroom { overscroll-behavior: contain; }
#tampermonkey-control-ui table td, #tampermonkey-control-ui table th {
padding: 4px;
border-bottom: 1px dashed #333;
}
/* 針對控制台 UI 的樣式微調 */
#tampermonkey-control-ui input[type="number"], #tampermonkey-control-ui input[type="text"] {
box-sizing: border-box;
}
/* 新增:統一機率欄位樣式 */
#tampermonkey-control-ui .probability-cell {
padding: 4px;
transition: background-color 0.3s, font-weight 0.3s;
}
/* V6.0 修正:資金保護區塊樣式調整 */
.safety-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 5px;
}
.safety-label {
white-space: nowrap;
margin-right: 5px;
flex-shrink: 0;
/* 讓 Label 佔據更多空間,把右邊擠小 */
width: 125px;
}
.safety-input {
text-align: right;
background: #333;
color: white;
border: none;
padding: 2px;
flex-grow: 1;
/* 讓輸入框彈性縮放,但確保最小寬度 */
min-width: 40px;
}
.percent-input {
width: 40px !important;
flex-grow: 0 !important;
}
.money-input {
/* 允許長數字顯示,但給予一個合理的最大寬度 */
max-width: 70px;
width: auto;
}
.safety-unit {
white-space: nowrap;
width: 45px; /* 單位文字的固定寬度 */
text-align: right;
margin-left: 5px;
flex-shrink: 0;
}
`);
(async function () {
loadData();
createUI();
document.addEventListener("focus", function () {
if (document.hidden) return;
document.getElementById(INPUT_FIELD_ID)?.focus();
});
console.log('⏳ 腳本啟動延遲 2.5 秒,以等待頁面穩定和舊訊息載入...');
await new Promise(resolve => setTimeout(resolve, 2500));
const chatLog = document.querySelector("#BH-slave, .now_chatroom-container");
if (chatLog) {
markExistingMessages();
const observer = new MutationObserver(handleLayout);
observer.observe(chatLog, { childList: true, subtree: true });
console.log('✅ 數據分析腳本已穩定啟動,監聽聊天容器的 DOM 變動。');
} else {
console.error('❌ 找不到聊天記錄容器!請檢查選擇器 #BH-slave, .now_chatroom-container 是否正確。');
}
})();
})();