您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Misc helper tools for Zed City
// ==UserScript== // @name ZedHelper // @description Misc helper tools for Zed City // @version 0.4.14 // @namespace kvassh.zedhelper // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=zed.city // @homepage https://greasyfork.org/en/scripts/527868-zedhelper // @author Kvassh // @match https://www.zed.city/* // @run-at document-end // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant window.onurlchange // @connect api.zed.city // ==/UserScript== /** * ZedHelper * * Features: * - Displays market value for items in inventory * - Calculates your inventory networth based on current market values * - Extra nav menu with some useful shortcuts (togglable in settings) * - Autopopulates gym train input fields to use maximum energy * - Autopopulates input field for junk shop with 360 item buy qty * - Show value of trades at Radio Tower * - Show timer for various features (Raid, Junk Store limit) * * If you have any questions, feel free to reach out to Kvassh [12853] in Zed City * * Changelog: * - 0.4.14: Another fix for the duplicate timer bar issue, hopefully 100% fixed now. * - 0.4.13: Fix error with duplicate timer bar appearing. * Add link to respective functions for timers also when not ready * - 0.4.12: Fix another bug in time parsing for timer bar * - 0.4.11: Fix bug in time parsing for timer bar * - 0.4.10: Fix correct link for Raid timer shortcut * - 0.4.9: Add timer for Raid * - 0.4.8: Add timer for Junk Store limit * - 0.4.7: Fix container width for mobile in Settings page. * - 0.4.6: Add ZH icon to statusbar that points to new Settings page. * Add setting for toggling extra nav menu on or off. * Include cash on hand when calculating networth. * - 0.4.5: Avoid duplicate inventory networth elements * Less padding for item values in inventory to fit better on mobile. * - 0.4.4: Change homepage and downloadURL to use greasyfork.org + change icon to zed.city favicon. * - 0.4.3: Use navigation navigate eventlistener instead to detect page change. * - 0.4.2: Try to force window eventlistener for urlchange to work on mobile. * - 0.4.1: Show warning if market values has not been cached yet. * Show warning on Radio Tower if the cached data is old. * Indicate if the trade is good or bad with checkmark on Radio Tower. * Fixed bug on inventory page where it would potentially not update prices if changing to next page in inventorylist. * - 0.4: Add value of trades at Radio Tower. * - 0.3: Fix bug in gym autopopulate + add new autopopulate in junk store for 360 items. * - 0.2: Add feature to autopopulate gym input fields. * - 0.1: Initial release. */ (function() { 'use strict'; // Add CSS for displaying prices (optional, but makes it look nicer) GM_addStyle(` .market-price { color:#999999; float:right; position:absolute; top:18px; right:100px; } .green { color: #00cc66; } .red { color: #ff6666; } .gray { color: #888; } .zedhelper-networth { text-align: center; margin: 10px auto; color: #ccc; font-size: 1.6rem; } .zedhelper-inventory-warning { text-align: center; margin: 10px auto; color: #ccc; font-size: 0.8rem; } .radio-warning { text-align: center; } .zedhelper-timer-bar { margin-top:0px; } .zedhelper-timer-span { padding: 0 10px; } .zedhelper-timer-span a { text-decoration:none; } `); const baseApiUrl = 'https://api.zed.city'; /** Dont modify anything below this line */ let module = "index"; let checkForInventoryUpdates = null; /** Utils */ function get(key) { return localStorage.getItem(`kvassh.zedhelper.${key}`); } function set(key, value) { localStorage.setItem(`kvassh.zedhelper.${key}`, value); } function log(msg) { const spacer = " "; const ts = new Date(); console.log("ZedHelper (" + ts.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' } )+ ") " + "[" + module + "]" + ((module.length < spacer.length) ? spacer.substring(0, spacer.length - module.length) : "") + ": " + (typeof msg === 'object' ? JSON.stringify(msg) : msg)); } function waitForElement(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 observer.observe(document.body, { childList: true, subtree: true }); }); } function getCodename(itemName) { let codename = itemName.toString().toLowerCase().replace(' ', '_').trim().split(/\n/)[0]; let nametable = { "arrows": "ammo_arrows", "bows": "ammo_bows", "logs": "craft_log", "nails": "craft_nails", "rope": "craft_rope", "scrap": "craft_scrap", "wire": "craft_wire", "army_helmet": "defense_army_helmet", "camo_hat": "defense_camo_hat", "camo_vest": "defense_camo_vest", "e-cola": "ecola", "lighter":"misc_lighter", "lockpick":"misc_lockpick", "pickaxe":"misc_pickaxe", "security_card":"defense_security_card", "zed_coin": "points", "baseball_bat": "weapon_baseball_bat", "bow":"weapon_bow", "chainsaw":"weapon_chainsaw", "spear":"weapon_spear", "switchblade":"weapon_switchblade", }; for (const [key, value] of Object.entries(nametable)) { if (codename === key) { return value; } } return codename; } function formatNumber(number) { const formatter = new Intl.NumberFormat('nb-NO', { maximumFractionDigits: 0, }); return formatter.format(number); } /** XHR Interceptor */ const originalXHR = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function (...args) { this.addEventListener('load', function () { const url = this.responseURL; // if (url.includes("/getOffers")) { // const item = JSON.parse(this.responseText)[0]; // log(`Caching market value for: ${item['name']} (${item['codename']})`); // set(`mv_${item["codename"]}`, JSON.stringify({ "name": item["name"], "marketValue": item["market_price"], "tz": Date.now() })); // } if (url.endsWith("/getMarket")) { const items = JSON.parse(this.responseText).items; let itemsCached = 0; for (let item of items) { let codename = getCodename(item["name"]); set(`mv_${codename}`, JSON.stringify({ "name": item["name"], "marketValue": item["market_price"], "tz": Date.now() })); itemsCached++; } set(`mv_lastupdate`, Date.now()); log(`Cached market value for ${itemsCached} items.`); } else if (url.endsWith("/loadItems")) { const data = JSON.parse(this.responseText); const items = data.items; let networthVendor = 0; let networthMarket = 0; for (let item of items) { networthVendor += item.value * item.quantity; const codename = item.codename; if(get(`mv_${codename}`)) { const mv = JSON.parse(get(`mv_${codename}`)); networthMarket += mv.marketValue * item.quantity; } else { networthMarket += item.value * item.quantity; } } set(`mv_networth_vendor`, networthVendor); set(`mv_networth_market`, networthMarket); log(`cached inventory networth (vendor: ${networthVendor}, market: ${networthMarket})`); } else if (url.endsWith("/getStats")) { const data = JSON.parse(this.responseText); set(`energy`, data.energy); set(`morale`, data.morale); set(`rad`, data.rad); set(`refills`, data.refills); set(`money`, data.money); set(`xpUntilNextRank`, parseInt(data.xp_end-data.experience)); set(`raidCooldownSecondsLeft`, data.raid_cooldown); set(`raidCooldownTime`, Date.now()); } else if (url.endsWith("/getRadioTower")) { const data = JSON.parse(this.responseText); saveCurrentTradeValues(data); set(`radio_lastupdate`, Date.now()); } else if (url.endsWith("/getStore?store_id=junk")) { const data = JSON.parse(this.responseText); if (data.hasOwnProperty('limits')) { set(`junkStoreLimitSecondsLeft`, data.limits.reset_time); set(`junkStoreLimitTime`, Date.now()); } else { set(`junkStoreLimitSecondsLeft`, 0); set(`junkStoreLimitTime`, 0); } } }); originalXHR.apply(this, args); }; /** Main script */ log("Starting up ZedHelper!"); let navigationTimeout = null; let urlChangeHandler = async () => { if (navigationTimeout === null) { const page = location.pathname; // Ensure we dont watch for inventory updates after changing subpage clearInterval(checkForInventoryUpdates); checkForInventoryUpdates = null; // Update the timer bar addZedHelperIconAndTimerBar(); if (page.includes("inventory")) { module = "inventory"; // log("Waiting for inventory list..."); waitForElement("#q-app > div > div.q-page-container > main > div > div:nth-child(4) > div > div.grid-cont.no-padding").then(() => { waitForElement(".item-row").then(() => { log("Inventory list loaded! Adding market prices..."); addMarketPrices(); }); }); waitForElement("#q-app > div > div.q-page-container > main > div").then(() => { showNetworth(); }); } else if (page.includes("market-listings")) { module = "market"; log("Navigated to Market Listings - Watching for element to add new listing..."); waitForElement("div > div > button.q-btn.q-btn-item.bg-positive").then(() => { log("Detected form for adding new market listing... showing market values for inventory!"); addMarketPrices(); }) } else if (page.includes("stronghold/2375014")) { module = "gym"; log("Navigated to Gym"); autoPopulateTrainInput(); } else if (page.includes("stronghold/2375016")) { module = "crafting"; log("Navigated to Crafting Bench"); } else if (page.includes("stronghold/2375017")) { module = "furnace"; log("Navigated to Furnace"); } else if (page.includes("stronghold/2375019")) { module = "radio"; log("Navigated to Radio Tower"); showTradeValues(); } else if (page.includes("/store/junk")) { module = "store"; log("Setting up auto input for junk store - 360 items"); autoPopulate360Items(); } else if (page.includes("/zedhelper")) { showSettingsPage(); } else { module = "unknown"; log(`Unknown subpage: ${page}`); } navigationTimeout = setTimeout(() => { clearTimeout(navigationTimeout); navigationTimeout = null; }, 250); } } // try { // window.addEventListener('urlchange', async (event) => { // urlChangeHandler(); // }); // } catch (error) { // log("FATAL ERROR: Could not add EventListener for window urlchange: " + JSON.stringify(error)); // } try { navigation.addEventListener('navigate', () => { setTimeout(() => { urlChangeHandler(); },100); }); } catch (error) { log("FATAL ERROR: Could not add EventListener for navigation navigate: " + JSON.stringify(error)); } /** Add a second nav menu with some useful shortcuts */ // document.querySelector("#q-app > div > header > div:nth-child(2) > div > div > div").app const secondNavBar = document.createElement('div'); secondNavBar.innerHTML = ` <div> <div class="gt-xs bg-grey-3 text-grey-5 text-h6"> <div class="q-tabs row no-wrap items-center q-tabs--not-scrollable q-tabs--horizontal q-tabs__arrows--inside q-tabs--mobile-with-arrows q-tabs--dense" role="tablist" inside-arrows=""> <div class="q-tabs__content scroll--mobile row no-wrap items-center self-stretch hide-scrollbar relative-position q-tabs__content--align-center"> <a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--full q-focusable q-hoverable cursor-pointer menu-link" tabindex="0" role="tab" aria-selected="false" href="/stronghold/2375017"> <div class="q-focus-helper" tabindex="-1"></div> <div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"> <div class="q-tab__label">Furnace</div> </div> <div class="q-tab__indicator absolute-bottom text-transparent"></div> </a> <a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--full q-focusable q-hoverable cursor-pointer menu-link" tabindex="0" role="tab" aria-selected="false" href="/stronghold/2375014"> <div class="q-focus-helper" tabindex="-1"></div> <div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"> <div class="q-tab__label">Gym</div> </div> <div class="q-tab__indicator absolute-bottom text-transparent"></div> </a> <a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--full q-focusable q-hoverable cursor-pointer menu-link" tabindex="0" role="tab" aria-selected="false" href="/scavenge/2"> <div class="q-focus-helper" tabindex="-1"></div> <div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"> <div class="q-tab__label">Scrapyard</div> </div> <div class="q-tab__indicator absolute-bottom text-transparent"></div> </a> <a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--full q-focusable q-hoverable cursor-pointer menu-link" tabindex="0" role="tab" aria-selected="false" href="/market"> <div class="q-focus-helper" tabindex="-1"></div> <div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"> <div class="q-tab__label">Market</div> </div> <div class="q-tab__indicator absolute-bottom text-transparent"></div> </a> <a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--full q-focusable q-hoverable cursor-pointer menu-link" tabindex="0" role="tab" aria-selected="false" href="/store/junk"> <div class="q-focus-helper" tabindex="-1"></div> <div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"> <div class="q-tab__label">Junk Store</div> </div> <div class="q-tab__indicator absolute-bottom text-transparent"></div> </a> <a class="q-tab relative-position self-stretch flex flex-center text-center q-tab--inactive q-tab--full q-focusable q-hoverable cursor-pointer menu-link" tabindex="0" role="tab" aria-selected="false" href="/zedhelper"> <div class="q-focus-helper" tabindex="-1"></div> <div class="q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable column"> <div class="q-tab__label">Settings</div> </div> <div class="q-tab__indicator absolute-bottom text-transparent"></div> </a> </div> </div> </div> `; if(get('extraNavMenu') && (get('extraNavMenu') === 'true' || get('extraNavMenu') === true)) { log("Enabling extra navigation menu"); waitForElement("#q-app > div > header").then(() => { document.querySelector("#q-app > div > header").appendChild(secondNavBar); }); } /** Add icon for ZedHelper settings + timer bar */ function addZedHelperIconAndTimerBar() { const zedHelperIcon = document.createElement('div'); zedHelperIcon.classList = 'zedhelper-icon-bar'; zedHelperIcon.innerHTML = ` <div class="row items-center"> <b><a href="/zedhelper" title="ZedHelper Settings" style="color:dodgerblue;text-decoration:none;font-weight:bold;">ZH</a></b> </div> `; const timerBar = document.createElement('div'); timerBar.classList = "row q-col-gutter-md justify-center items-center zedhelper-timer-bar"; let timeDiff = 0; let timeLeft = 0; let timeLeftFormatted = ""; let html = `<div class="q-tab__label">`; const raidCooldownSecondsLeft = get('raidCooldownSecondsLeft'); const raidCooldownTime = get('raidCooldownTime'); if (raidCooldownSecondsLeft && raidCooldownTime) { timeDiff = (Date.now() - raidCooldownTime)/1000; timeLeft = raidCooldownSecondsLeft - Math.round(timeDiff); try { timeLeftFormatted = new Date(timeLeft * 1000).toISOString().substr(11, 5); } catch (error) { timeLeftFormatted = "00:00"; } } html += `<span class="zedhelper-timer-span">Raid: <a href="/raids">${timeLeft > 0 ? `<span class="red">${timeLeftFormatted}</span>` : `<span class="green">Ready</span>`}</a></span>`; const junkStoreLimitSecondsLeft = get('junkStoreLimitSecondsLeft'); const junkStoreLimitTime = get('junkStoreLimitTime'); if (junkStoreLimitSecondsLeft && junkStoreLimitTime) { timeDiff = (Date.now() - junkStoreLimitTime)/1000; timeLeft = junkStoreLimitSecondsLeft - Math.round(timeDiff); try { timeLeftFormatted = new Date(timeLeft * 1000).toISOString().substr(11, 5); } catch (error) { timeLeftFormatted = "00:00"; } } html += `<span class="zedhelper-timer-span">Junk Store: <a href="/store/junk">${timeLeft > 0 ? `<span class="red">${timeLeftFormatted}</span>` : `<span class="green">Ready</span>`}</a></span>`; html += `</div>`; timerBar.innerHTML = html; const selector = ".q-col-gutter-md.justify-center.items-center.currency-stats"; log("Searching for statusbar..."); waitForElement(selector).then((el) => { try { document.querySelectorAll('.zedhelper-icon-bar').entries().forEach((entry) => { log("Remove entry of icon bar: " + entry[1]); entry[1].remove(); }); document.querySelectorAll('.zedhelper-timer-bar').entries().forEach((entry) => { log("Remove entry of timer bar: " + entry[1]); entry[1].remove(); }); } catch (error) { // eat exception } log("Appending ZedHelper icon to statusbar + adding new bar for timers!"); el.appendChild(zedHelperIcon); el.parentElement.appendChild(timerBar); // document.querySelector(selector).parentElement.appendChild(zedHelperIcon); }); } // addZedHelperIconAndTimerBar(); /** Settings page */ function showSettingsPage() { const selector = "#q-app > div > div.q-page-container > div"; waitForElement(selector).then((el) => { el.style.top = "40%"; el.style.width = "90%"; el.style.border = "2px inset #333"; el.style.padding = "10px"; el.innerHTML = ` <h3>ZedHelper Settings</h3> <p>Userscript written by <a href="https://www.zed.city/profile/12853">Kvassh</a><br> For any questions or feedback, please reach out to me in Zed City or Discord.</p> <br><br><hr><br> <div style="text-align:left;"> <label>Enable extra nav menu? <input type="checkbox" id="extraNavMenu" name="extraNavMenu" value="true" ${get('extraNavMenu') === true | get('extraNavMenu') === 'true' ? 'checked' : ''}></label> </style> <br><br> <div id="zedhelper-settings-output" style="height:50px; display:block;"> </div> `; setTimeout(() => { document.querySelector("#extraNavMenu").addEventListener('change', (event) => { set('extraNavMenu', event.target.checked); document.querySelector('#zedhelper-settings-output').innerHTML = '<b class="green">Settings saved! ✓<br>You might need to refresh page for some settings like the extra nav menu.</b>'; setTimeout(() => { document.querySelector('#zedhelper-settings-output').innerHTML = " "; },1000); }); }, 100); }); } /** Gym functions */ function autoPopulateTrainInput() { const energy = get("energy"); if (energy > 5) { const trainsAvailable = Math.floor(energy/5); log(`Current energy: ${energy} - Autopopulating ${trainsAvailable} into the input fields`); waitForElement("input.q-field__native").then(() => { const inputs = document.querySelectorAll("input.q-field__native"); for (let input of inputs) { input.value = trainsAvailable; input.dispatchEvent(new Event("input", { bubbles: true })); } }); } else { log("Current energy is 5 or lower, don't autopopulate input fields"); } } /** Radio Tower functions */ function showTradeValues() { try { const timeDiff = get("radio_lastupdate") ? (Date.now() - get("radio_lastupdate"))/1000 : 0; log(`Trade values last updated: ${timeDiff} sec ago`); if (timeDiff > 60*60*12) { log("Trade values are old. Please visit the Radio Tower to cache new values."); const el = document.createElement('div'); el.classList.add('radio-warning'); waitForElement("div.overlay-cont").then(() => { const container = document.querySelector("div.overlay-cont"); // document.querySelector("#q-app > div > div.q-page-container > main > div > div:nth-child(11) > div.overlay-cont > div > div > div > div > div.text-center.text-no-bg-light.subtext-large.q-my-md") el.innerHTML = `Radio trades data are old - please refresh <a href="stronghold/2375019">Radio Tower</a> to cache new values.`; container.prepend(el); }); return; } const trades = JSON.parse(get(`tradeValues`)); // [{"give":96,"return":460},{"give":1425,"return":11900},{"give":3000,"return":2380}] log("Current trades to show:"); log(trades); waitForElement(".q-pa-md").then(() => { const tradeContainers = document.querySelectorAll(".q-pa-md"); let i = 0; for (let tradeContainer of tradeContainers) { const valueEl = document.createElement('div'); valueEl.classList.add('trade-value'); valueEl.innerHTML = ` <div style="float:left;"> ${trades[i].giveqty} items worth<br><span class="red">$</span> ${formatNumber(trades[i].give)} </div> <div style="float:right;"> ${trades[i].returnqty} items worth<br><span class="green">$</span> ${formatNumber(trades[i].return)} </div> <div style="clear:both;font-size:1.2rem;"> ${parseInt(trades[i].return) > parseInt(trades[i].give) ? '<span class="green">✓</span>' : '<span class="red">✗</span>'} </div> `; tradeContainer.appendChild(valueEl); i++; } }); } catch(error) { log("No trade values found"); } } function saveCurrentTradeValues(data) { try { const trades = []; for (let trade of data.items) { // trade -> vars -> items -> <item_requirement_1> -> codename/req_qty // trade -> vars -> output -> <item_list-1> -> codename/quantity let worthGive = 0; let worthReturn = 0; let qtyGive = 0; let qtyReturn = 0; const items = trade.vars.items; Object.keys(items).forEach( (key,val) => { const marketValue = JSON.parse(get(`mv_${items[key].codename}`)).marketValue; worthGive += (marketValue*items[key].req_qty); qtyGive += items[key].req_qty; }); const output = trade.vars.output; Object.keys(output).forEach( (key,val) => { const marketValue = JSON.parse(get(`mv_${output[key].codename}`)).marketValue; worthReturn += (marketValue*output[key].quantity); qtyReturn += output[key].quantity; }); log(`Trade: ${trade.name} - Give: ${worthGive} - Return: ${worthReturn}`); trades.push({ "give": worthGive, "return": worthReturn, "giveqty": qtyGive, "returnqty": qtyReturn }); } set(`tradeValues`, JSON.stringify(trades)); } catch(error) { log("Error saving trade values"); set(`tradeValues`, null); } } /** Store functions */ function autoPopulate360Items() { const selector = "input[type=number].q-placeholder"; waitForElement(selector).then(() => { const el = document.querySelector(selector); el.value = 360; el.dispatchEvent(new Event("input", { bubbles: true })); }); } /** Functions related to market/inventory */ // async function fetchMarketPrice(itemCodename) { // try { // const response = await fetch(`${baseApiUrl}/getOffers?item=${itemCodename}`, { // "headers": { // "accept": "application/json, text/plain, */*", // "accept-language": "en-US,en;q=0.9,no;q=0.8", // "content-type": "application/json", // "cookie": document.cookie, // Use current cookies from the browser // "Referer": "https://www.zed.city/" // }, // "body": "{\"page\":1,\"descending\":false}", // "method": "POST" // }); // if (!response.ok) { // log(`Error fetching price for ${itemCodename}: ${response.status}`); // return null; // } // const data = await response.json(); // if (data && data.length > 0) { // Check if there are any offers // return data[0].market_price; // Return the first offer's price // } else { // return null; // No offers found // } // } catch (error) { // log(`Error fetching price for ${itemCodename}: ${error}`); // return null; // } // } // Function to process inventory items and add prices async function addMarketPrices() { const items = document.querySelectorAll('.item-row'); if (!items) { log("No inventory items found. Check your selectors."); return; } const mvLastUpdateEl = document.createElement('div'); mvLastUpdateEl.classList.add('zedhelper-inventory-warning'); const mvLastUpdated = get('mv_lastupdate'); if (mvLastUpdated) { const timeDiff = (Date.now() - mvLastUpdated)/1000; log(`Market values last updated: ${timeDiff} sec ago`); if (timeDiff > 60*60*24) { log("Market values are older than 24 hours. Please visit the market page to cache new values."); mvLastUpdateEl.innerHTML = `Market values are older than a day - please visit the <a href="market">Market</a> page to cache new values.`; } } else { log("Market values not cached. Please visit the market page to cache values."); mvLastUpdateEl.innerHTML = ` Market value has not been cached yet.<br> Please visit the <a href="market">Market</a> page first to calculate worth on your inventory. `; } const selector = "#q-app > div > div.q-page-container > main > div > div:nth-child(2)"; waitForElement(selector).then(() => { document.querySelector(selector).prepend(mvLastUpdateEl); }); // Delete any existing market value elements const existingMarketValues = document.querySelectorAll('.market-price'); for (let mvEl of existingMarketValues) { mvEl.remove(); } for (let item of items) { const codename = getCodename(item.querySelector('.q-item__label').innerText); let qty = 1; try { qty = item.querySelector('.item-qty').innerText; if (qty.includes("%")) { qty = 1; } else { qty = parseInt(qty.replace(/[^0-9]/g, '')); } } catch (error) { // eat exception } if (Number.isNaN(qty)) { qty = 1; } log(`Adding market value for ${codename} x ${qty}`); let data = null; if(get(`mv_${codename}`)) { data = JSON.parse(get(`mv_${codename}`)); } const priceElement = document.createElement('span'); priceElement.classList.add('market-price'); if (data !== null) { const datetime = new Date(data.tz).toISOString(); priceElement.innerHTML = `<span title="${datetime}"> <b class="green">$</b> ${formatNumber(data.marketValue * qty)} <small>(<b class="green">$</b> ${formatNumber(data.marketValue)})</small> </span>`; } else { priceElement.innerHTML = `<span class="gray">N/A</span>`; } item.querySelector('.q-item__label').appendChild(priceElement); } // Setup interval to check if inventory list changes let firstItemRowCodename = ""; try { firstItemRowCodename = getCodename(items[0].querySelector('.q-item__label').innerText); } catch (error) { // eat exception } checkForInventoryUpdates = setInterval(() => { let newItems = document.querySelectorAll('.item-row'); if (newItems.length !== items.length) { log("Inventory list has changed. Updating prices..."); clearInterval(checkForInventoryUpdates); checkForInventoryUpdates = null; addMarketPrices(); return; } let newFirstItemRowCodename = ""; try { newFirstItemRowCodename = getCodename(newItems[0].querySelector('.q-item__label').innerText); } catch (error) { // eat exception } if (firstItemRowCodename != newFirstItemRowCodename) { log("Inventory list has changed. Updating prices..."); clearInterval(checkForInventoryUpdates); checkForInventoryUpdates = null; addMarketPrices(); return; } },250); } function showNetworth() { const networthVendor = get(`mv_networth_vendor`) || 0; const networthMarket = get(`mv_networth_market`) || 0; const networthCash = get(`money`) || 0; const networth = parseInt(networthMarket) + parseInt(networthCash); const existingElement = document.querySelector('.zedhelper-networth'); if (existingElement) { existingElement.remove8(); } const el = document.createElement('div'); el.classList.add('zedhelper-networth'); el.innerHTML = ` Networth: <span title="Value of items only if sold to vendor: $ ${formatNumber(networthVendor)}"> <b class="green">$</b> ${formatNumber(networth)} </span> `; const selector = "#q-app > div > div.q-page-container > main > div > div:nth-child(2)"; waitForElement(selector).then(() => { document.querySelector(selector).prepend(el); }); } })(); /* EXAMPLE RESPONSE /getOffers: [ { "name":"Advanced Tools", "codename":"advanced_tools", "type":"resources_craft_basic", "quantity":2, "value":10, "vars":{ "buy":10,"sell":5,"desc":"","weight":"1","ash_value":"20" }, "market_id":15490, "market_price":19500, "quantity_sold":3, "user":{ "id":11703,"username":"bump","avatar":"","online":1739285402 } }, ] */ /* EXAMPLE RESPONSE /getMarket { "items": [ { "name":"Advanced Tools", "codename":"advanced_tools", "type":"resources_craft_basic", "quantity":35, "value":10, "vars": { "buy":10, "sell":5, "desc":"", "weight":"1", "ash_value":"20" }, "market_id":14020, "market_price":19500, "quantity_sold":0 }, ] } */