您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Aggregates the number of listings per account name and displays a whisper button for each account name in the Path Of Exile trade site.
// ==UserScript== // @name Path Of Exile Trade Aggregator // @namespace Violentmonkey Scripts // @match https://www.pathofexile.com/trade* // @grant none // @version 1.0.2.1 // @author CerikNguyen // @license MIT // @description Aggregates the number of listings per account name and displays a whisper button for each account name in the Path Of Exile trade site. // ==/UserScript== // ------------------------------------------------- helper functions ------------------------------------------------- // helper function to add an element to the DOM function addElement(parent, elm) { const tmp = parent.querySelector(`#${elm.id}`); if (tmp) { tmp.remove(); } parent.appendChild(elm); } function resetCountByListing(accountName, listingKey) { delete accountData[accountName][listingKey]; } function resetCount(accountName) { delete accountData[accountName]; } function resetAllCounts() { for (const accountName in accountData) { resetCount(accountName); } listings.clear(); } function extractResultsDiv() { const results = document.body.querySelectorAll('.resultset'); if (results.length === 0) { return null; } // Collect arrays of child nodes const childNodesArrays = Array.from(results).map(result => Array.from(result.childNodes)); // Flatten the array of arrays into a single array const flattened = childNodesArrays.flat(); return flattened; } function extractResults() { const res = extractResultsDiv(); if (!res) { return []; } return res; } // Object to hold the counts and whisper button links of each account name const accountData = {}; // Set to hold listing keys to avoid duplicates const listings = new Set(); // Extract the logged-in user's account name, preventing aggregated search of own listings const loggedInUserElement = document.querySelector('.loggedInStatus .profile-link a'); const loggedInUsername = loggedInUserElement ? loggedInUserElement.textContent : null; // ------------------------------------------------- element initialization ------------------------------------------------ // styling css const style = document.createElement('style'); style.id = 'aggregator-style'; style.innerHTML = ` #aggregator { position: fixed; top: 0; right: 0; background-color: rgba(0, 0, 0, 0.7); padding: 5px; z-index: 1000; transition: right 0.2s ease 0s; } #showAggregator { position: fixed; top: 50px; right: 0; z-index: 1001; transition: right 0.2s ease 0s; } /* compatibility with Better Trading */ .bt-body > #aggregator, .bt-body > #showAggregator { right: 400px; /* Adjust based on the width of the other extension */ top: 100px; } .bt-is-collapsed > #aggregator, .bt-is-collapsed > #showAggregator { right: 0; /* Move it back when the other extension is collapsed */ top: 100px; } #results-table { border-spacing: 0 0.4em; border-collapse: separate; } .actions-cell { display: flex; justify-content: center; padding-left: 5px; padding-right: 5px; } .text-cell { text-align: center; padding-left: 5px; padding-right: 5px; } .action-button{ margin-left: 2px; margin-right: 2px; } .thead-cell{ padding: 5px; } tbody tr:nth-child(even) { background-color: rgba(50, 50, 50, 0.7); } #hide-about { margin: 5px; } .aboutDiv { position: absolute; top: 0; left: 0; background-color: rgba(0, 0, 0, 1); padding: 5px; z-index: 1000; display: none; } .hidden { display: none; } ul { display: block; list-style-type: disc; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 0px; margin-inline-end: 0px; padding-inline-start: 40px; } `; addElement(document.head, style); //initializing the main injecting div const aggregator = document.createElement('div'); aggregator.id = 'aggregator'; aggregator.classList.add('aggregator', 'results', 'bt-body'); const aggregatorInnerHTML = ` <button id="hide" class="btn btn-default">Hide</button> <button id="clear-all" class="btn btn-default">Clear All</button> <button id="refresh" class="btn btn-default">Refresh</button> <button id="about" class="btn btn-default">About</button> <div class="table-responsive" style="margin: 5px"> <table id="results-table" class="table"> <thead> <tr> <th class="thead-cell">Account Name</th> <th class="thead-cell">Amount Listed</th> <th class="thead-cell">Count</th> <th class="thead-cell">Total</th> <th class="thead-cell">Actions</th> </tr> </thead> <tbody id="results-list"> </tbody> </table> </div> `; addElement(document.body, aggregator); const aboutDiv = document.createElement('div'); aboutDiv.id = 'aboutDiv'; aboutDiv.classList.add('aboutDiv'); aboutDiv.innerHTML = ` <button id="hide-about" class="btn btn-default">Close</button> <h3> Path Of Exile Trade Aggregator </h3> <br/> <span> This extension aggregates the all listings under the same account name and displays a whisper button for each account name in the Path Of Exile trade site. </span> <br/> <br/> <span> Changelog: </span> <br/> <ul> <li> Minor UI tweaks </li> <li> "Whisper" now becomes "Whispered" after clicking </li> <li> Alternate row color for better readability </li> <li> Added an about section </li> <li> Added a Kofi link for donation. Thank you for your support! </li> </ul> <br/> <a href='https://ko-fi.com/H2H4WPVOX' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a> `; aboutDiv.querySelector('#hide-about').addEventListener('click', () => { aboutDiv.style.display = 'none'; }); const showButton = document.createElement('button'); showButton.id = 'showAggregator'; showButton.classList.add('btn', 'btn-default'); showButton.textContent = 'Show Aggregator'; showButton.addEventListener('click', () => { aggregator.classList.remove('hidden'); showButton.classList.add('hidden'); localStorage.setItem('aggregatorState', 'open'); // Update localStorage }); addElement(document.body, showButton); function initAggregator() { aggregator.innerHTML = aggregatorInnerHTML; addElement(aggregator, aboutDiv); aggregator.querySelector('#about').addEventListener('click', () => { aboutDiv.style.display = 'block'; }); // Check localStorage for the aggregator's state const aggregatorState = localStorage.getItem('aggregatorState'); if (aggregatorState === 'closed') { aggregator.classList.add('hidden'); showButton.classList.remove('hidden'); } else { // By default or if the state is 'open', the aggregator is visible aggregator.classList.remove('hidden'); showButton.classList.add('hidden'); } document.getElementById('hide').addEventListener('click', () => { aggregator.classList.add('hidden'); showButton.classList.remove('hidden'); localStorage.setItem('aggregatorState', 'closed'); }); document.getElementById('clear-all').addEventListener('click', () => { resetAllCounts(); updateAggregator(); }); document.getElementById('refresh').addEventListener('click', () => { // Reset the counts and reprocess the nodes to get the latest counts resetAllCounts(); const results = extractResults(); // console.log(results); processNodes(results); updateAggregator(); }); // Initial check in case the page has already loaded processNodes(extractResults()); updateAggregator(); } // Function to update the floating div with the latest counts and whisper buttons function updateAggregator() { const resultsList = document.getElementById('results-list'); if (!resultsList) { initAggregator(); return; } resultsList.innerHTML = ''; // Clear previous results // Calculate total listings per account const accountsTotalListings = Object.entries(accountData).map(([account, listings]) => { const totalListings = Object.values(listings).reduce((sum, { count }) => sum + count, 0); return { account, totalListings, listings }; }); // Sort accounts by total listings and keep only the top 10 const topAccounts = accountsTotalListings.sort((a, b) => b.totalListings - a.totalListings).slice(0, 10); // Iterate and display sorted accounts topAccounts.forEach(({ account, listings }) => { Object.entries(listings).forEach(([listingKey, data]) => { const row = document.createElement('tr'); const accountCell = document.createElement('td'); accountCell.classList.add('text-cell'); accountCell.textContent = account; const amountListedCell = document.createElement('td'); amountListedCell.classList.add('text-cell'); amountListedCell.textContent = listingKey; const countCell = document.createElement('td'); countCell.classList.add('text-cell'); countCell.textContent = data.count; const totalCell = document.createElement('td'); totalCell.classList.add('text-cell'); listingPrice = Number.parseFloat(listingKey.split(" ")[0]); //get currency as the rest of the string listingCurrency = listingKey.split(" ").slice(1).join(" "); totalCell.textContent = `${listingPrice * data.count} ${listingCurrency}`; const actionsCell = document.createElement('td'); actionsCell.classList.add('actions-cell'); const whisperButton = document.createElement('button'); whisperButton.classList.add('btn', 'btn-xs', 'btn-default', 'action-button'); whisperButton.textContent = 'Whisper'; whisperButton.addEventListener('click', () => { row.classList.add('whispered'); whisperButton.textContent = 'Whispered'; data.whisperButton.click(); }); const resetButton = document.createElement('button'); resetButton.classList.add('btn', 'btn-xs', 'btn-default', 'action-button'); resetButton.textContent = 'Clear'; resetButton.addEventListener('click', () => { delete accountData[account][listingKey]; updateAggregator(); }); actionsCell.appendChild(whisperButton); actionsCell.appendChild(resetButton); row.appendChild(accountCell); row.appendChild(amountListedCell); row.appendChild(countCell); row.appendChild(totalCell); row.appendChild(actionsCell); resultsList.appendChild(row); }); }); } // Flag to check if the aggregator has been updated var updated = false; // Function to process added nodes function processNodes(addedNodes) { addedNodes.forEach(node => { if (node.parentElement && !node.parentElement.classList.contains('resultset')) return; if (listings.has(node)) { return; // Skip nodes that have already been processed } listings.add(node); const profileLink = node.querySelector ? node.querySelector('span.profile-link a') : null; const whisperButton = node.querySelector ? node.querySelector('button.direct-btn') : null; const priceField = node.querySelector ? node.querySelector('div.price span[data-field="price"]') : null; if (!priceField) return; const priceSpan = priceField.querySelector ? priceField.childNodes[3] : null; const currencySpan = priceField.querySelector ? priceField.childNodes[5] : null; const errorSpan = node.querySelector ? node.querySelector('span.error') : null; if (errorSpan) { return; } if (profileLink && whisperButton && priceSpan && currencySpan) { const accountName = profileLink.textContent.trim(); if (accountName === loggedInUsername) return; const quantity = priceSpan.textContent.trim(); const currencyType = currencySpan.textContent.trim(); const listingKey = `${quantity} ${currencyType}`; if (!accountData[accountName]) { accountData[accountName] = {}; } if (!accountData[accountName][listingKey]) { // console.log("Adding new listing for " + accountName + " " + listingKey); accountData[accountName][listingKey] = { count: 1, whisperButton: whisperButton }; } else { // console.log("Incrementing count for " + accountName + " " + listingKey); accountData[accountName][listingKey].count += 1; } updated = true; } }); } // observer to watch for changes in the DOM const observer = new MutationObserver(mutations => { updated = false; mutations.forEach(mutation => { processNodes(mutation.addedNodes); }); if (updated) { updateAggregator(); } }); // Configuration of the observer: const config = { childList: true, subtree: true }; // Start observing the body for changes observer.observe(document.body, config); initAggregator();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址