您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Creates a table view of account(s) with selectable and copyable text
// ==UserScript== // @name My Meralco Accounts Table // @description Creates a table view of account(s) with selectable and copyable text // @namespace https://github.com/rainniel/tampermonkey-scripts // @supportURL https://github.com/rainniel/tampermonkey-scripts/issues // @version 1.0 // @author Rainniel // @license MIT // @match https://mymeralco.com.ph/* // @icon https://www.google.com/s2/favicons?sz=64&domain=mymeralco.com.ph // @grant none // ==/UserScript== (function () { 'use strict'; const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; const accountsApi = 'https://api-morevamp.meco-soldel.com/production/bill/overview'; XMLHttpRequest.prototype.open = function (method, url, ...args) { this._url = url; return originalOpen.call(this, method, url, ...args); }; XMLHttpRequest.prototype.send = function (...args) { this.addEventListener('load', function () { if (this._url.toLowerCase().startsWith(accountsApi.toLowerCase())) { let items; if (this.responseType === 'arraybuffer') { try { const decoder = new TextDecoder('utf-8'); const text = decoder.decode(this.response); const json = JSON.parse(text); if (json.result && Array.isArray(json.result)) { items = json.result.map(item => ({ alias: item.alias, can: item.cxe_can, amount: item.amount, due_date: item.due_date })); } } catch (e) { console.warn('Failed to decode or parse arraybuffer:', e); } } showAccountsTable(items); } }); return originalSend.apply(this, args); }; let accountsDivInjected = false; let accountsDiv; let accountsTableBody; function injectAccountsDiv() { if (accountsDivInjected) return; const style = document.createElement('style'); style.textContent = ` div.accounts-div { display: none; position: fixed; bottom: 20px; right: 20px; background: #fff; border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); z-index: 9999; color: #000; font-family: Arial, sans-serif; font-size: 14px; user-select: text !important; -webkit-user-select: text !important; } div.accounts-div-main { padding: 16px; } div.accounts-div-restore { display: none; align-items: center; justify-content: center; padding: 10px; cursor: pointer; } div.accounts-header { display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 0 0 10px 0; } div.accounts-title { font-weight: bold; font-size: 16px; } button.minimize-button { padding: 3px; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; border: 1px solid #ccc; border-radius: 3px; } table.accounts-table { width: 100%; border-collapse: collapse; } th.default-heading { text-align: left; padding: 6px; border-bottom: 1px solid #ccc; } th.amount-heading { text-align: right; padding: 6px; border-bottom: 1px solid #ccc; } td.default-cell { padding: 6px; } td.right-cell { padding: 6px; text-align: right; } td.amount-cell { padding: 6px 0 6px 6px; text-align: right; } td.amount-button { padding: 6px 6px 6px 0; } button.amount-button { width:24px; height:24px; margin-left:6px; padding:0; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 12px; border: 1px solid #ccc; border-radius: 3px; } tr.table-footer { font-weight: bold; } `; document.head.appendChild(style); const div = document.createElement('div'); div.innerHTML = ` <div class="accounts-div" id="accounts-div"> <div id="accounts-div-main" class="accounts-div-main"> <div class="accounts-header"> <div class="accounts-title">Accounts</div> <div> <button class="minimize-button" title="Minimize" onclick="showHideBox(false)"> <svg xmlns="http://www.w3.org/2000/svg" width="10px" height="10px" viewBox="0 0 512 512" fill="currentColor"> <path d="M32 416c-17.7 0-32 14.3-32 32s14.3 32 32 32h448c17.7 0 32-14.3 32-32s-14.3-32-32-32H32z"></path> </svg> </button> </div> </div> <table class="accounts-table"> <thead> <tr> <th class="default-heading">Alias</th> <th class="default-heading">CAN</th> <th class="amount-heading" colspan="2">Amount</th> <th class="default-heading">Due Date</th> </tr> </thead> <tbody id="accounts-table-body"></tbody> </table> </div> <div id="accounts-div-restore" class="accounts-div-restore" title="Show Accounts Table" onclick="showHideBox(true)"> <svg xmlns="http://www.w3.org/2000/svg" height="30px" width="30px" viewBox="0 0 24 24" stroke-width="0" stroke="currentColor"> <path d="M1.5 5.625c0-1.036.84-1.875 1.875-1.875h17.25c1.035 0 1.875.84 1.875 1.875v12.75c0 1.035-.84 1.875-1.875 1.875H3.375A1.875 1.875 0 0 1 1.5 18.375V5.625ZM21 9.375A.375.375 0 0 0 20.625 9h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0 3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5Zm0 3.75a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h7.5a.375.375 0 0 0 .375-.375v-1.5ZM10.875 18.75a.375.375 0 0 0 .375-.375v-1.5a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h7.5ZM3.375 15h7.5a.375.375 0 0 0 .375-.375v-1.5a.375.375 0 0 0-.375-.375h-7.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375Zm0-3.75h7.5a.375.375 0 0 0 .375-.375v-1.5A.375.375 0 0 0 10.875 9h-7.5A.375.375 0 0 0 3 9.375v1.5c0 .207.168.375.375.375Z"></path> </svg> </div> </div> `; document.body.appendChild(div); accountsDiv = document.getElementById('accounts-div'); accountsTableBody = document.getElementById('accounts-table-body'); accountsDivInjected = true; } function showAccountsTable(items) { injectAccountsDiv(); if (Array.isArray(items) && items.length > 0) { let tableBody = ''; let totalAmount = 0; items.forEach(item => { totalAmount += fixAmount(item.amount); tableBody += ` <tr> <td class="default-cell">${item.alias}</td> <td class="right-cell">${item.can}</td> <td class="amount-cell">${formatAmount(item.amount)}</td> <td class="amount-button"> <button class="amount-button" title="Copy Amount" data-copy="${fixAmount(item.amount)}" onclick="navigator.clipboard.writeText(this.dataset.copy)"> <svg xmlns="http://www.w3.org/2000/svg" height="12px" width="12px" viewBox="0 0 24 24" fill="currentColor"> <path d="M20 2H10c-1.103 0-2 .897-2 2v4H4c-1.103 0-2 .897-2 2v10c0 1.103.897 2 2 2h10c1.103 0 2-.897 2-2v-4h4c1.103 0 2-.897 2-2V4c0-1.103-.897-2-2-2zM4 20V10h10l.002 10H4zm16-6h-4v-4c0-1.103-.897-2-2-2h-4V4h10v10z"></path> </svg> </button> </td> <td class="default-cell">${formatDate(item.due_date)}</td> </tr> `; }); if (items.length > 1) { tableBody += ` <tr class="table-footer"> <td class="right-cell" colspan="2">Total</td> <td class="amount-cell">${formatAmount(totalAmount)}</td> </tr> `; } accountsTableBody.innerHTML = tableBody; accountsDiv.style.display = 'block'; } else { accountsDiv.style.display = 'none'; accountsTableBody.innerHTML = ''; } } window.showHideBox = function (show) { if (show) { document.getElementById('accounts-div-restore').style.display = 'none'; document.getElementById('accounts-div-main').style.display = 'block'; } else { document.getElementById('accounts-div-main').style.display = 'none'; document.getElementById('accounts-div-restore').style.display = 'flex'; } }; function fixAmount(amount) { const num = Number(amount); return isNaN(num) || num < 0 ? 0 : num; } function formatAmount(amount) { return fixAmount(amount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function formatDate(date) { const newDate = new Date(date); if (isNaN(newDate.getTime())) return "None"; return newDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } })();