您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Collects shop wizard and trading post history for Virtupets.net
当前为
// ==UserScript== // @name GC - Virtupets Data Collector // @namespace https://gf.qytechs.cn/en/users/1278031-crystalflame // @match *://*.grundos.cafe/island/tradingpost/browse/ // @match *://*.grundos.cafe/island/tradingpost/lot/user/* // @match *://*.grundos.cafe/island/tradingpost/lot/* // @match *://*.grundos.cafe/island/tradingpost/quickbuy/* // @match *://*.grundos.cafe/island/tradingpost/acceptoffer/* // @match *://*.grundos.cafe/island/tradingpost/viewoffers/* // @match *://*.grundos.cafe/island/tradingpost/ // @match *://*.grundos.cafe/market/wizard/* // @match *://*.grundos.cafe/allevents/ // @match *://*.grundos.cafe/help/*/ // @run-at document-end // @icon https://www.google.com/s2/favicons?sz=64&domain=grundos.cafe // @require https://update.gf.qytechs.cn/scripts/514423/1554918/GC%20-%20Universal%20Userscripts%20Settings.js // @grant GM.info // @grant GM.getValue // @grant GM.setValue // @license MIT // @version 0.24 // @author CrystalFlame // @description Collects shop wizard and trading post history for Virtupets.net // ==/UserScript== 'use strict'; (async function () { const DEBUG = false; function extractTradeInformation(yourLots = false) { const tradeObjects = []; try { const lotElements = document.querySelectorAll('.trade-lot'); lotElements.forEach((lotElement) => { const tradeObject = {}; const lotNumberMatch = lotElement.querySelector(yourLots ? '.flex.space-between strong' : 'span strong').innerText.trim().match(/Lot #(\d+)/); tradeObject.id = parseInt(lotNumberMatch[1]); const username = yourLots ? document.querySelector('#userinfo a[href^="/userlookup/?user="]').innerText.trim() : lotElement.querySelector('span strong + a').innerText.trim(); tradeObject.username = username; const listedOnElement = lotElement.querySelector(yourLots ? 'span:nth-of-type(3)' : 'span:nth-child(2)'); tradeObject.time = createTimestamp(listedOnElement.innerText.trim().replace(/Listed On\s*:\s*/, '')); const itemElements = lotElement.querySelectorAll('.trade-item'); tradeObject.items = []; itemElements.forEach((itemElement) => { tradeObject.items.push( itemElement.querySelector('.item-info span:nth-child(1)').innerText.trim() ); }); const wishlist = lotElement.querySelector('span[id^="wishlist-text"]'); tradeObject.wishlist = wishlist.innerText.trim(); const quicksale = lotElement.querySelector('span[id^="quicksale-text"]'); if (quicksale) { tradeObject.quicksale = parseInt(quicksale.innerText.trim().replace(/\D/g, ''), 10); } tradeObjects.push(tradeObject); }); log(tradeObjects); } catch (error) { const message = "Unable to read the TP, there might be a conflicting script or an update to the layout."; logError(error, message, "extractTradeInformation"); throw new Error(message); } return tradeObjects.length > 0 ? tradeObjects : undefined; } function constructMessage(message, type) { const errorMessage = `<b>Failed to upload data to <a href="${formatUrl()}">Virtupets</a>. </b>`; const updateMessage = `Always make sure your <a href=\"https://gf.qytechs.cn/en/scripts/490596-gc-virtupets-data-collector\">script</a> is the latest version.` const bugReport = ` Submit a <a href="${formatUrl('/help/report')}">bug report</a> for support.`; return `<span>${type == 'error' ? errorMessage : ""}${message} ${type == 'warning' ? updateMessage : ""}${type == 'error' ? bugReport : ""}</span>`; } async function displayMessage(message, type = 'error') { message = message.replace(/^Error:\s*/, ''); let showMessage = true; try { showMessage = await shouldDisplayMessage(message); } catch (error) { logError(error, "Failed to get displayMessage setting."); } if (showMessage) { try { const element = document.getElementById('page_content').querySelector('h1') || document.getElementById('page_content').querySelector('p'); if (element) { const messageElement = document.createElement('div'); messageElement.innerHTML = constructMessage(message, type); messageElement.style.cssText = ` display: flex; align-items: center; margin-block-end: 7px; margin-block-start: 7px; background-color: ${type == 'error' ? "#ffe5e5" : "#f9ff8f"}; border: 2px solid #000; border-radius: 5px; padding: 10px; color: #000; `; const linkElement = document.createElement('a'); linkElement.href = formatUrl(); linkElement.target = '_blank'; const imgElement = document.createElement('img'); imgElement.src = formatUrl('/assets/images/vp.png'); imgElement.alt = 'Icon'; imgElement.style.cssText = `width: 40px; height: 40px; margin-right: 10px;`; linkElement.appendChild(imgElement); messageElement.insertAdjacentElement('afterbegin', linkElement); element.insertAdjacentElement('afterend', messageElement); if (type == 'error' || type == 'warning') { const dismissElement = document.createElement('a'); dismissElement.href = "#"; dismissElement.textContent = "[Dismiss]"; dismissElement.addEventListener('click', () => dismissMessage(messageElement, message)); messageElement.appendChild(dismissElement); } } } catch (error) { logError(error, `Failed to display message: ${message}.`, "displayMessage"); } } } async function dismissMessage(element, message) { await GM.setValue(message, new Date().getTime()); element.remove(); } async function shouldDisplayMessage(message) { try { const lastDismissed = await GM.getValue(message); if (!lastDismissed || new Date().getTime() - lastDismissed > 24 * 60 * 60 * 1000) { return true; } return false; } catch (error) { logError(error, "Failed to check if message should display.", "shouldDisplayMessage"); return true; } } async function logError(error, message, operation, statusCode = undefined) { try { log(message); const errorBody = { message: `${message}. Error: ${error.message.replace(/^Error:\s*/, '')}`, status_code: statusCode, route: `collector::${operation}` }; const options = await createPostRequest("0.1", GM.info.script.version, errorBody); fetch(formatUrl('/errors'), options); } catch (error) { console.error('Error sending error report:', error); } } function validateTable() { const header = document.querySelectorAll('.market_grid .header'); const check = ['owner', 'item', 'stock', 'price']; if (check.length != header.length) return false; for (let i = 0; i < header.length; i += 1) { const title = header[i].querySelector('strong').textContent.toLowerCase(); if (check[i] != title) { const message = `Unknown header named "${title}" in position ${i + 1}, expected "${check[i]}".`; const error = new Error(message); logError(error, "Validation Error.", "validateTable"); throw error; } } return true; } function validateSearchRange() { if (document.querySelector('main .center .mt-1 span')?.textContent?.toLowerCase() == '(searching between 1 and 99,999 np)') { return true; } return false; } function validateUnbuyable() { const notFoundMsg = "i did not find anything. :( please try again, and i will search elsewhere!"; const wrongHeaders = document.querySelectorAll('.market_grid .header').length > 0; const wrongMessage = document.querySelector('main p.center').textContent.toLowerCase() != notFoundMsg; if (wrongHeaders || wrongMessage) { return false; } return true; } function log(message) { if ((DEBUG) == true) { console.log(message); } } function extractShopPrices() { try { const tokens = document?.querySelector('.mt-1 strong')?.textContent?.split(" ... "); let body; const itemName = tokens?.length >= 2 ? tokens[1]?.trim() : undefined; if (!validateSearchRange() || !itemName) { log("Not a valid search!"); return body; } else if (validateTable()) { log("Valid search"); const dataElements = document.querySelectorAll('.market_grid .data'); const i32Max = 2147483647; let lowestPrice = i32Max; let totalStock = 0; let totalShops = 0; let includesOwnUnpricedItem = false; for (let i = 0; i < dataElements.length; i += 4) { //const owner = dataElements[i].querySelector('a').textContent; //const item = dataElements[i + 1].querySelector('span').textContent; const stock = parseInt(dataElements[i + 2].querySelector('span').textContent); const price = parseInt(dataElements[i + 3].querySelector('strong').textContent.replace(/[^0-9]/g, '')); if (price > 0) { lowestPrice = Math.min(price, lowestPrice); totalStock += stock; totalShops += 1; } else { includesOwnUnpricedItem = true; } } if (lowestPrice < i32Max && totalStock > 0 && dataElements.length > 0) { body = { item_name: itemName, price: lowestPrice, total_stock: totalStock, total_shops: totalShops } return body; } else if (includesOwnUnpricedItem && totalStock == 0 && dataElements.length == 4) { body = { item_name: itemName, total_stock: 0, total_shops: 0 } return body; } } else if (validateUnbuyable()) { log("Valid unbuyable"); body = { item_name: itemName, total_stock: 0, total_shops: 0 } } return body; } catch (error) { const message = "Unable to read the SW, there might be a conflicting script or an update to the layout."; logError(error, message, "extractShopPrices"); throw new Error(message); } } const monthMap = { jan: "1", feb: "2", mar: "3", apr: "4", may: "5", jun: "6", jul: "7", aug: "8", sep: "9", oct: "10", nov: "11", dec: "12" }; function createTimestamp(str) { try { const parts = str.split(" "); const month = monthMap[parts[0].slice(0, 3).toLowerCase()].padStart(2, '0'); const day = parts[1].replace(/\D/g, '').trim().padStart(2, '0'); let year = parts[2].replace(/\D/g, '').trim(); const yearCheck = year.match(/\d{4}/); const time = parts[3].split(':'); const ampm = parts[4].replace(/[^a-zA-Z]/g, '').trim().toLowerCase(); let hour = parseInt(time[0]); if (ampm === "pm" && hour < 12) { hour += 12; } else if (ampm === "am" && hour === 12) { hour = 0; } const convertedHour = String(hour).padStart(2, '0'); const minutes = time[1]?.padStart(2, '0') ?? "00"; const currentYear = new Date().getFullYear(); const currentMonth = new Date().getMonth(); if (!yearCheck) { year = month == "12" && currentMonth == 0 ? currentYear - 1 : currentYear; } return `${year}-${month}-${day}T${convertedHour}:${minutes}:00`; } catch (error) { const message = "Failed to create timestamp."; logError(error, message, "createTimestamp"); throw new Error(message); } } function formatUrl(route = '') { return `https://virtupets.net${route}`; } async function sendData(route, fetchOptions = {}, delay = 1000, tries = 4) { const url = formatUrl(route); async function onError(error) { if (tries > 0 && error.cause != 500) { return new Promise(resolve => { setTimeout(() => { resolve(sendData(route, fetchOptions, delay, tries - 1)); }, delay * 2 ** (5 - tries)); }); } else { throw error; } } try { const response = await fetch(url, fetchOptions); if (response.status == 500) { const body = await response.text(); console.error(`Data upload error: ${body}`); throw new Error(body, { cause: response.status }); } else if (!response.ok) { const body = await response.text(); if (route != 'health') { console.error(`Data upload error (retry ${5 - tries}/5): ${body}`); const healthCheck = await fetch(formatUrl('/health'), { method: 'GET' }); if (!healthCheck.ok) { console.log(`${formatUrl()} may be down for maintenance or you are experiencing connectivity issues, no data was sent.`); return { ok: true, body: '', down: true }; } } throw new Error(body, { cause: response.status }); } const body = await response.text(); return { ok: true, body }; } catch (error) { return onError(error); } } async function setupClientID() { let clientID; try { clientID = await GM.getValue('ClientID'); if (!clientID) { const id = crypto.randomUUID(); await GM.setValue('ClientID', crypto.randomUUID()); clientID = id; } } catch (error) { logError(error, "Failed to setup client ID.", "setupClientID"); clientID = ""; } return clientID } async function createPostRequest(apiVersion, clientVersion, body) { const clientID = await setupClientID(); return { method: "POST", headers: { "Content-Type": "application/json", "Version": apiVersion, "ClientVersion": clientVersion, "ClientID": clientID }, body: JSON.stringify(body), } } function extractTradeLotIDFromUrl(url) { const match = url.match(/tradingpost\/.*\/(\d+)\//); if (match) { const trade_lot = parseInt(match[1]); return trade_lot; } } function setupQuickBuyListeners(route, apiVersion, clientVersion) { const forms = document.querySelectorAll('form[action*="/island/tradingpost/quickbuy"]'); for (let form of forms) { const originalOnSubmit = form.onsubmit; // form's onsubmit is null on Safari + userscripts plugin, return. if (!originalOnSubmit) { return; } form.onsubmit = undefined; form.addEventListener('submit', async function (event) { event.preventDefault(); if (originalOnSubmit) { const confirmed = originalOnSubmit.call(form, event); if (!confirmed) { return; } } else { const message = `Unrecognized TP layout, action was prevented as a safeguard.`; logError(new Error(message), "Failed to setup quickbuy.", "setupQuickBuyListeners"); displayMessage(`${message}.Disable the script if needed until there is an update.`); return; } try { const actionUrl = form.getAttribute('action'); const match = actionUrl.trim().match(/\/island\/tradingpost\/quickbuy\/(\d+)\//); const formData = new FormData(this); if (match) { const trade_lot_id = parseInt(match[1]); const neopoints = parseInt(formData.get('price'), 10); if (!isNaN(trade_lot_id) && !isNaN(neopoints)) { const body = { id: trade_lot_id, neopoints }; await sendRequest(route, apiVersion, clientVersion, body); } } } catch (e) { logError(e, "Failed to setup quickbuy listeners.", "setupQuickBuyListeners"); } this.submit(); }); } } async function setupAcceptOfferListeners(route, apiVersion, clientVersion) { const forms = document.querySelectorAll('form[action*="/island/tradingpost/acceptoffer"]'); const trade_lot_id = extractTradeLotIDFromUrl(window.location.href); let addedCheckbox = false; for (let form of forms) { const originalOnSubmit = form.onsubmit; // form's onsubmit is null on Safari + userscripts plugin, return. if (!originalOnSubmit) { return; } if (!addedCheckbox) { const header = document.querySelector('.trading_post .header'); const defaultChecked = await getSettingOrDefault('verifyTradesChecked'); let checked = defaultChecked ? ' checked' : ''; // Uncheck if wishlist contains certain phrases const skipVerificationPhrases = await getSettingOrDefault('skipVerificationPhrases'); const wishlist = document.querySelector('.lot .flex-column span strong')?.parentElement?.textContent?.toLowerCase().replace('wishlist : ', ''); const skipVerificationPhrasesArray = skipVerificationPhrases.split(','); for (let i = 0; i < skipVerificationPhrasesArray.length; i++) { const phrase = skipVerificationPhrasesArray[i].trim().toLowerCase(); if (wishlist?.includes(phrase)) { checked = ''; displayMessage(`Wishlist phrase '${phrase}' detected! Virtupets.net verification automatically unchecked. Please only re-check verify if this is a complete and accurate trade for the item(s).`, 'message'); break; } } // Uncheck if trade has 10+ items or 10m+ neopoints if (checked != '') { const offers = document.querySelectorAll('.offer'); for (let i = 0; i < offers.length; i++) { const offer = offers[i]; const tradeItems = offer.querySelectorAll('.trade-item'); const neopointsElement = offer.querySelector('.item-info strong'); let neopoints = 0; if (neopointsElement) { const match = neopointsElement.textContent.trim().match(/[\d,]+/); if (match) { neopoints = parseInt(match[0].replace(/,/g, ''), 10); } } if (tradeItems.length >= 10 || neopoints >= 10000000) { checked = ''; displayMessage(`Large transfer detected! Virtupets.net verification automatically unchecked. Please only re-check verify if this is a complete and accurate trade for the item(s).`, 'message'); break; } } } const largeCheckbox = await getSettingOrDefault('largeVerifyTradesCheckbox'); const headerStyle = `"text-align: right; display: inline-block; position: relative; float: right;${largeCheckbox ? ' font-size: 24px;' : ''}"`; const headerImgStyle = `"width: ${largeCheckbox ? '2rem' : '19px'}; height: ${largeCheckbox ? '2rem' : '19px'}; vertical-align: middle; position: relative; float: left; left: -4px;"`; const labelStyle = `"margin-right: 5px; font-weight:bold; cursor:pointer; transform: scale(${largeCheckbox ? '2' : '1.5'}) !important;${largeCheckbox ? ' position: relative;' : ''}"`; const checkboxStyle = `"${largeCheckbox ? 'top: -3px; right: 4px; position: relative; transform: scale(2) !important;' : 'transform: scale(1.5) !important;'}"`; if (header) { header.insertAdjacentHTML('beforeend', ` <div id="virtupets-header" style=${headerStyle}> <a href="${formatUrl()}"><img src="${formatUrl('/assets/images/vp_crop.png')}" alt="VP" style=${headerImgStyle}></a> <label for="virtupets-verify-trade" style=${labelStyle} title="If checked, the accepted offer will be displayed on virtupets.net trade history. Avoid verifying trades that are item lends, junk transfers, or require additional context to upkeep the trade history.">Verify trade?</label> <input type="checkbox" style=${checkboxStyle} id="virtupets-verify-trade"${checked}> </div> `); } addedCheckbox = true; } form.onsubmit = undefined; form.addEventListener('submit', async function (event) { event.preventDefault(); if (originalOnSubmit) { const confirmed = originalOnSubmit.call(form, event); if (!confirmed) { return; } } else { const message = `Unrecognized TP layout, action was prevented as a safeguard.`; logError(new Error(message), "Failed to setup accept offers.", "setupAcceptOfferListeners"); displayMessage(`${message}. Disable the script if needed until there is an update.`); return; } try { if (document.getElementById('virtupets-verify-trade')?.checked) { const offer = this.parentElement.previousElementSibling.querySelector('.offer'); const tradeItems = offer.querySelectorAll('.trade-item'); const offer_id = parseInt(this.parentElement.parentElement.querySelector('strong > a').textContent.trim()); let items = []; let neopoints; tradeItems.forEach(tradeItem => { const itemInfo = tradeItem.querySelector('.item-info'); if (itemInfo) { const span = itemInfo.querySelector('span'); if (span) { items.push(span.textContent); } else { const strong = itemInfo.querySelector('strong'); if (strong) { const match = strong.textContent.trim().match(/[\d,]+/); if (match) { neopoints = parseInt(match[0].replace(/,/g, ''), 10); } } } } }); if (!isNaN(trade_lot_id) && (neopoints || items.length > 0)) { const body = { id: trade_lot_id, neopoints, items, offer_id }; await sendRequest(route, apiVersion, clientVersion, body); } } } catch (e) { logError(e, "Failed to setup accept offer listeners.", "setupAcceptOfferListeners"); } this.submit(); }); } } function quickSaleNotifications() { try { const tradeDetails = []; const rows = document.querySelectorAll('table tr'); for (const row of rows) { const timeCell = row.cells[1]; const descriptionCell = row.cells[2]; if (descriptionCell && timeCell) { const descriptionText = descriptionCell.innerText.toLowerCase(); const saleRegex = /bought your trade #(\d+)/; if (saleRegex.test(descriptionText)) { const tradeLotMatch = descriptionText.match(/trade #(\d+)/); const priceMatch = descriptionText.match(/for ([\d,]+) np/); const timeText = timeCell.innerText.toLowerCase(); if (tradeLotMatch && priceMatch && timeText) { const id = parseInt(tradeLotMatch[1]); const neopoints = parseInt(priceMatch[1].replace(/,/g, ''), 10); const timeSold = timeText.trim(); const time = createTimestamp(timeSold); tradeDetails.push({ id, neopoints, time }); } } } } log(tradeDetails); return tradeDetails.length > 0 ? tradeDetails : undefined; } catch (e) { console.error(e, "Failed to read event inbox."); logError(e, "Failed to read event inbox.", "quickSaleNotifications"); } } async function sendRequest(route, apiVersion, clientVersion, body) { return new Promise(async (resolve, reject) => { let promises = []; if (Array.isArray(body)) { if (body.length == 0) return; const size = 100; const numBatches = Math.ceil(body.length / size); for (let i = 0; i < numBatches; i++) { const startIndex = i * size; const endIndex = Math.min((i + 1) * size, body.length); const batchObjects = body.slice(startIndex, endIndex); const options = await createPostRequest(apiVersion, clientVersion, batchObjects); promises.push(sendData(route, options)); } } else { const options = await createPostRequest(apiVersion, clientVersion, body); promises.push(sendData(route, options)); } Promise.all(promises.map(p => p.catch(error => ({ ok: false, body: error })))).then(responses => { const warningResponses = responses.filter(response => response.ok && response.body.trim() != ''); const successfulResponses = responses.filter(response => response.ok); const offlineResponses = responses.filter(response => response.down); const errors = responses.filter(response => !response.ok); if (errors.length > 0) { reject(new Error(errors[0].body)); } else if (warningResponses.length > 0) { displayMessage(warningResponses[0].body, 'warning'); } if (successfulResponses.length > 0 && offlineResponses.length == 0) { console.log(`Data uploaded to ${formatUrl()}`); } resolve(); }); }); } async function addSettings() { try { const categoryName = 'Virtupets.net Data Collector'; let promises = []; const imgPath = formatUrl('/assets/images'); const imgStyle = `style="max-width: 100%; margin-top: 1rem;`; let labelTooltip = `When purchasing a trade lot using an autosale, verify the purchase on Virtupets.net.<img src="${imgPath}/buy_autosale.png" ${imgStyle}">`; promises.push(addCheckboxInput({ categoryName, labelText: 'Verify your Autosale Purchases', labelTooltip: labelTooltip, settingName: 'verifyAutoSalePurchases', defaultSetting: true })); labelTooltip = `When viewing your <a href="/allevents/">event inbox</a>, any autosale purchases will be verified on Virtupets.net.<img src="${imgPath}//event_inbox.png" ${imgStyle}">`; promises.push(addCheckboxInput({ categoryName, labelText: 'Verify your Autosale Sales from Event Inbox', labelTooltip: labelTooltip, settingName: 'verifyAutoSalesEventInbox', defaultSetting: true })); labelTooltip = `When viewing offers on one of your trade lots, automatically check the "Verify Trade" checkbox that will verify the trade on Virtupets.net.<img src="${imgPath}//default_checked.png" ${imgStyle}">`; promises.push(addCheckboxInput({ categoryName, labelText: 'Verify Trade Checked by Default', labelTooltip: labelTooltip, settingName: 'verifyTradesChecked', defaultSetting: true })); labelTooltip = `When viewing offers on one of your trade lots, display a larger "Verify trade?" checkbox and label.<img src="${imgPath}//large_checkbox.png" ${imgStyle}">`; promises.push(addCheckboxInput({ categoryName, labelText: 'Enlarge Trade Verification Checkbox', labelTooltip: labelTooltip, settingName: 'largeVerifyTradesCheckbox', defaultSetting: false })); labelTooltip = `When accepting offers, uncheck the verification box if the listed text is present in the wishlist. Enter words/phrases as a comma separated list. Capitilization is ignored.<img src="${imgPath}//wishlist_skip_verify.png" ${imgStyle}">`; promises.push(addTextInput({ categoryName, labelText: 'Wishlist Phrases to Uncheck Verify', labelTooltip: labelTooltip, settingName: 'skipVerificationPhrases', defaultSetting: 'lend,lending' })); await Promise.all(promises); } catch (error) { await logError(error, "Failed to add settings.", "addSettings"); console.error("Failed to add settings.", error); } } async function getSettingOrDefault(settingName) { const defaultMap = { verifyAutoSalePurchases: true, verifyAutoSalesEventInbox: true, verifyTradesChecked: true, largeVerifyTradesCheckbox: false, skipVerificationPhrases: 'lend,lending' }; return await GM.getValue(settingName) ?? defaultMap[settingName]; } async function main() { if (typeof GM === 'undefined' || typeof GM.setValue !== 'function' || typeof GM.getValue !== 'function') { displayMessage('Minimum requirements to run the Virtupets.net script were not met.'); console.logError('Minimum requirements to run the Virtupets.net script were not met.'); return; } let route; let body; let apiVersion; const sw = /market\/wizard/.test(window.location.href); const tp_qb_error = /tradingpost\/quickbuy/.test(window.location.href); const tp_ao_error = /tradingpost\/acceptoffer/.test(window.location.href); const tp_om = /tradingpost\/viewoffers\//.test(window.location.href); const tp_vo = /tradingpost\/viewoffers\/\d+/.test(window.location.href); const tp_yl = /\/island\/tradingpost\/(?:#.*)?$/.test(window.location.href); const settings = window.location.href.includes('/help/userscripts/'); const ei = /allevents/.test(window.location.href); const clientVersion = GM.info.script.version; try { if (sw) { route = "/shop-prices"; body = extractShopPrices(); apiVersion = "0.11"; } else if (tp_qb_error) { if (!await getSettingOrDefault('verifyAutoSalePurchases')) return; route = "/trade-lots/confirmations"; body = { id: extractTradeLotIDFromUrl(window.location.href), error: true }; apiVersion = "0.1"; } else if (tp_ao_error) { if (!await getSettingOrDefault('verifyAutoSalePurchases')) return; route = "/trade-lots/confirmations"; body = { offer_id: extractTradeLotIDFromUrl(window.location.href), error: true }; apiVersion = "0.1"; } else if (tp_vo) { await setupAcceptOfferListeners("/trade-lots/confirmations", "0.1", clientVersion); return; } else if (tp_om) { return; } else if (ei) { if (!await getSettingOrDefault('verifyAutoSalesEventInbox')) return; route = "/trade-lots/confirmations/bulk"; body = quickSaleNotifications(); apiVersion = "0.1"; } else if (settings) { await addSettings(); return; } else { route = "/trade-lots"; body = extractTradeInformation(tp_yl); apiVersion = "0.11"; if (!tp_yl && await getSettingOrDefault('verifyAutoSalePurchases')) setupQuickBuyListeners("/trade-lots/confirmations", "0.1", clientVersion); } if (route && body && apiVersion && clientVersion) { await sendRequest(route, apiVersion, clientVersion, body); } } catch (error) { displayMessage(error.message); } }; main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址