您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
small improvements for idlescape's marketplace
// ==UserScript== // @name Stonehub // @namespace http://tampermonkey.net/ // @version 1.2.1 // @description small improvements for idlescape's marketplace // @author weld, gamergeo, chrOn0os, godi // @include *://*idlescape.com/game* // @run-at document-start // @grant none // ==/UserScript== class Stonehub { /** * Stonehub is a tampermonkey extension ; its purpose is to add some QoL on the marketplace. * This works by hooking the current running socket and make simple call to the server. */ constructor() { /** * This dictionnary embed all the pair event-methods. * You can easily implement new corresponding methods by adding the event as the key, and a reference to the right method. */ this.event_to_action = { "get player marketplace items":this.convenients_marketplace_items_action, "get player auctions":this.convenients_sell_item_action, "update inventory":this.update_inventory_action }; // some macros this.extension_id = 'stonehub' this.stonehub_version = "V1.2.0"; this.status_refresh_time = 5000; this.socket_latency = 2000; this.auto_refresh_time = 1000; this.auto_refresh_auction_time = 5000; this.sockets = []; // Used for canceling order confirmation this.itemID = -1; this.inventory_item_id = -1; this.update_ui_rate = 500; // Used for minPrice Storage this.raw_item_id = -1; this.min_price = -1; this.latest_watched_itemID = -1; this.NUMBER_ATTEMPT = 1000; this.WAITING_TIMEOUT = 50; this.status_div; this.activated_extensions = { 'stonehub':false, 'updateui':false }; } message_handler(that, e) { /** * Here is the message handler. * When a new websocket message is emitted from the server * the extension hooks it and call the desired methods. * Check out this.event_to_action. * * All the methods are called with a reference to this/that and the datas the game threw. * Please keep at least a reference to that when creating a new method. */ let msg = e.data; msg = (msg.match(/^[0-9]+(\[.+)$/) || [])[1]; if(msg != null){ let msg_parsed = JSON.parse(msg); let [r, data] = msg_parsed; // if the event is stored in event_to_action, execute the function if(r in that.event_to_action) that.event_to_action[r](that, data); } } error_handler(that, e) { let alert_msg = "Something goes wrong with Stonehub ! \nError msg: " + e.message + "\nPlease reload the page or contact messenoire / Gamergeo"; console.log(e); alert(alert_msg); } start() { /** * Main part of the extension */ // keeping the track of this, since changing the scope of the function modify it. var that = this; /* src: https://stackoverflow.com/questions/59915987/get-active-websockets-of-a-website-possible */ /* Handle the current running socket */ const nativeWebSocket = window.WebSocket; window.WebSocket = function(...args){ const socket = new nativeWebSocket(...args); that.sockets.push(socket); return socket; }; // wait for loading to complete, then check which ext is activated and call the main handler let page_ready = setInterval(() =>{ if(document.readyState == 'complete'){ clearInterval(page_ready); that.set_status(that); that.retrieve_status_div(that) try{ if(that.sockets.length != 0){ //if it triggers the socket, listen to message that.sockets[0].addEventListener('message', (e) => this.message_handler(that, e)); // display a logo setTimeout(() => { var usersOnlineDiv = document.getElementById("usersOnline"); var spantext = document.createElement('span'); spantext.setAttribute("style","color:#54FF9F;text-shadow: 1px 1px 10px #39c70d;background-image:url(https://i.ibb.co/6m0vqhc/bg1.gif);"); spantext.appendChild(document.createTextNode(" | Stonehub " + that.stonehub_version)); usersOnlineDiv.appendChild(spantext); }, that.socket_latency); } else throw new Error('socket not initialized'); } catch(e) {that.error_handler(that, e);} } ; }, 200); } } Stonehub.prototype.create_status_div = function(that) { /** * <div id='stonehub_status'></div> */ const sdiv = document.createElement('div'); sdiv.id = 'stonehub_status'; sdiv.style.display = 'none'; document.body.appendChild(sdiv); return document.getElementById('stonehub_status'); } Stonehub.prototype.set_status = function(that) { if(!that.activated_extensions.stonehub){ that.status_div = that.status_div ?? that.create_status_div(that); let ext_status = document.createElement('div'); ext_status.id = that.extension_id; that.status_div.appendChild(ext_status); } } Stonehub.prototype.retrieve_status_div = function(that) { /** * Checks inside <div id='stonehub_status'></div> which script is activated * and update its state inside this.activated_extensions */ setInterval(() => { that.status_div = that.status_div ?? that.create_status_div(that); [...that.status_div.children].forEach(ext =>{ that.activated_extensions[ext.id] = true; }); //console.log(that.activated_extensions); }, that.status_refresh_time); } Stonehub.prototype.int_to_commas = function(x) { // src https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript // 10100 into 10,100 return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } Stonehub.prototype.commas_to_int = function(s) { // 10,100 into 10100 let result; if(typeof s == 'number') result = s else result = s.replace(/[^\d\.\-]/g, ""); return parseInt(result); } Stonehub.prototype.convenients_marketplace_items_action = function(that, data){ /** * We need to store the items info for other features */ that.min_price = data[0].price; that.raw_item_id = data[0].itemID; /** * This method add some convenients and small adjustements to an item page. * Current features : * Autorefresh */ // === AUTOREFRESH ==== // setTimeout(() => { let crafting_table_exists = document.getElementsByClassName('crafting-table marketplace-table'); if(crafting_table_exists.length != 0) that.sockets[0].send('42["get player marketplace items",'+data[0].itemID+']'); }, that.auto_refresh_time); } Stonehub.prototype.show_popup_sell_item = function(that, order_data) { let id = order_data.id; let itemID = order_data.itemID; let inventory_item_id = order_data.inventory_item_id; let initial_price = order_data.price; let initial_amount = order_data.stackSize; /** * This method implements a resell feature * Shows when you click on stone button */ let modify_auction_popup_html = `<div role="presentation" class="MuiDialog-root sell-item-dialog" style="position: fixed; z-index: 1300; right: 0px; bottom: 0px; top: 0px; left: 0px;"> <div class="MuiBackdrop-root" aria-hidden="true" style="opacity: 1; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;"></div> <div tabindex="0" data-test="sentinelStart"></div> <div class="MuiDialog-container MuiDialog-scrollPaper" role="none presentation" tabindex="-1" style="opacity: 1; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;"> <div class="MuiPaper-root MuiDialog-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthSm MuiPaper-elevation24 MuiPaper-rounded" role="dialog"> <div class="MuiDialogTitle-root"> <h5 class="MuiTypography-root MuiTypography-h6">Modify Auction</h5> </div> <div class="MuiDialogContent-root"> <p class="MuiTypography-root MuiDialogContentText-root MuiTypography-body1 MuiTypography-colorTextSecondary">How many do you want to sell?</p> <input id="amount" type="text" value="`+initial_amount+`"> <p class="MuiTypography-root MuiDialogContentText-root MuiTypography-body1 MuiTypography-colorTextSecondary">Price per item you wish to sell<br><span id="lowest-price">Current lowest price on market: <span id="min_price` + itemID + `">0</span> <img src="/images/gold_coin.png" alt="Gold coins" class="icon10"></span></p> <p class="MuiTypography-root MuiDialogContentText-root textography-body1 MuiTypography-colorTextSecondary"></p> <input id="price" type="text" value="`+ initial_price + `"> <div id='min_price_button_nok' variant="contained" color="secondary" class="item-dialogue-button idlescape-button idlescape-button-red">Adapt price</div> <div id='min_price_button_ok' style="display:none" variant="contained" color="secondary" class="item-dialogue-button idlescape-button idlescape-button-red">Adapt price</div> <p class="MuiTypography-root MuiDialogContentText-root MuiTypography-body1 MuiTypography-colorTextSecondary">You will receive: <span id='benefits'>0</span> <img src="/images/gold_coin.png" alt="" class="icon16"> <br>After the fee of : <span id='fees'>0</span> <img src="/images/gold_coin.png" alt="" class="icon16"></p> </div> <div class="MuiDialogActions-root MuiDialogActions-spacing"> <div id='close_button' variant="contained" color="secondary" class="item-dialogue-button idlescape-button idlescape-button-red">Close </div> <div id='sell_button' variant="contained" color="secondary" class="item-dialogue-button idlescape-button idlescape-button-green">Sell </div> </div> </div> </div> <div tabindex="0" data-test="sentinelEnd"></div> </div>` ; let modify_auction_popup = document.createElement('div'); modify_auction_popup.id = 'modify_auction_popup'; modify_auction_popup.innerHTML = modify_auction_popup_html; // Some CSS styles are not imported. We need to reimplet it var style = document.createElement('style'); style.innerHTML = `.MuiDialogActions-root { flex: 0 0 auto; display: flex; padding: 8px; align-items: center; justify-content: flex-end; } .MuiDialogActions-spacing { margin-left: 0!important; } .MuiDialogActions-spacing > :not(:first-child) { margin-left: 8px; }`; modify_auction_popup.appendChild(style) let body = document.getElementsByTagName('body')[0]; body.appendChild(modify_auction_popup); let min_price = -1; /** * Retrieving the min price and setting correct fields */ that.waiting_min_price(that, order_data.itemID) .then((min_price) => { if (document.getElementById('min_price' + itemID) != null) { document.getElementById('min_price' + itemID).innerHTML = that.int_to_commas(min_price) document.getElementById('min_price_button_nok').style.display = 'none'; document.getElementById('min_price_button_ok').style.display = null; // Adding Adapt price function document.getElementById('min_price_button_ok').addEventListener('click', () => {document.getElementById('price').value = min_price - 1;}); } }).catch((e) => {}); // smoother ui, add commas to numbers let price_changed = false; let amount_changed = false; let update_ui = setInterval(() => { that.update_prices_popup(that, price_changed, amount_changed); price_changed = true; amount_changed = true; }, that.update_ui_rate); document.getElementById('sell_button').addEventListener('click', () => { // cancel auction that.sockets[0].send('42["cancel my auction",'+id+']'); // make a new auction with the right id let price = that.commas_to_int(document.getElementById('price').value); let amount = that.commas_to_int(document.getElementById('amount').value); // wait to retrieve the inventory_item_id that.waiting_inventory_update(that, itemID) .then(tosell_id => { that.sockets[0].send('42["sell item marketplace",{"amount":'+amount+',"price":'+price+',"dbID":'+tosell_id+'}]'); }).catch(e => that.error_handler(that, e)); // if we can't find the inventory_item_id); // close popup && remove ui updaters clearInterval(update_ui); that.clean_popup(that); price_changed = false; amount_changed = false; }); // Waiting for boohi.... (NOK waiting message document.getElementById('min_price_button_nok').addEventListener('click', () => {alert("Waiting for Boohi...");}); document.getElementById('close_button').addEventListener('click', () => { // close popup && remove ui updaters clearInterval(update_ui); that.clean_popup(that); price_changed = false; amount_changed = false; }); } Stonehub.prototype.clean_popup = function(that) { document.getElementById('sell_button').removeEventListener('click'); document.getElementById('close_button').removeEventListener('click'); document.getElementById('min_price_button_ok').removeEventListener('click'); document.getElementById('modify_auction_popup').outerHTML = ''; that.sockets[0].send('42["get player auctions"]'); } /** * Update price and fees in custom sell pop-up */ Stonehub.prototype.update_prices_popup = function(that, price_changed, amount_changed) { let popup_still_exists = document.getElementById('modify_auction_popup'); if (popup_still_exists != null && popup_still_exists.length != 0) { let price = price_changed ? that.commas_to_int(document.getElementById('price').value) : parseInt(document.getElementById('price').value); let amount = amount_changed ? that.commas_to_int(document.getElementById('amount').value) : parseInt(document.getElementById('amount').value); let fees_percentage = 0.05; let to_bouilli = (price > 0 || typeof price == 'NaN') ? amount * price * fees_percentage : 1; let benefits = (price > 0 || typeof price == 'NaN') ? amount * price - to_bouilli : 0; document.getElementById('benefits').innerHTML = that.int_to_commas(Math.floor(benefits)); document.getElementById('fees').innerHTML = that.int_to_commas(Math.floor(to_bouilli) < 1 ? 1 : Math.floor(to_bouilli)); document.getElementById('price').value = that.int_to_commas(price); document.getElementById('amount').value = that.int_to_commas(amount); } } Stonehub.prototype.update_inventory_action = function(that, data) { /** * Update 2 memvars, see waiting_inventory_update for use */ // /!\ there are 3 items ID // id, inventory_item_id and itemID that.itemID = data.item.itemID; that.inventory_item_id = data.item.id; } Stonehub.prototype.waiting_inventory_update = function(that, itemID) { /** * This method returns a promise when called * It is used in this.show_popup_sell_item() to get * the corresponding IDs of the item being resold. * It waits for the right "update inventory" event, checking * if the itemID of an updated item is the same as the one passed as argument * If so, it resolves by passing inventory_item_id * Repeat until you got it, or reject if the process takes too much iteration */ return new Promise((resolve, reject) => { let c = 0; setTimeout(function check() { c = ++c; if(that.itemID == itemID) resolve(that.inventory_item_id); else { if(c >= that.NUMBER_ATTEMPT) reject(new Error('timeout waiting to update inventory')); else setTimeout(check, that.WAITING_TIMEOUT); } }, that.WAITING_TIMEOUT); }); } Stonehub.prototype.waiting_min_price = function(that, raw_item_id) { /** * This method returns a promise when called * It is used in this.show_popup_sell_item() to get * the corresponding min price of the item shown in the popup * It will sends for "get market manifest itemID" and waits for response. */ return new Promise((resolve, reject) => { that.sockets[0].send('42["get player marketplace items",' + raw_item_id + ']'); let c = 0; setTimeout(function check() { c = ++c; if(that.raw_item_id == raw_item_id) resolve(that.min_price); else { if(c >= that.NUMBER_ATTEMPT) reject(new Error('timeout waiting to retrieve min price')); else setTimeout(check, that.WAITING_TIMEOUT); } }, that.WAITING_TIMEOUT); }); } Stonehub.prototype.convenients_sell_item_action = function(that, data) { that.clean_auctions(); /** * This method add some convenients and small adjustements to the sell page. * Current features : * Button added for further features * Autorefresh when someone bought the item */ if(document.getElementsByClassName('crafting-table marketplace-table').length != 0) { // ==== AUTOREFRESH ==== // setTimeout(() => { let crafting_table_exists = document.getElementsByClassName('crafting-table marketplace-table'); if(crafting_table_exists.length != 0) that.sockets[0].send('42["get player auctions",[]]'); }, that.auto_refresh_auction_time); } // ==== STONE BUTTON ==== // let auction_table_tbody = document.getElementsByClassName('marketplace-my-auctions')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr'); let auction_table_tbody_ar= Array.prototype.slice.call(auction_table_tbody); // for each auction in the table auction_table_tbody_ar.forEach((element, index) => { // add button let modify_auction_button = document.createElement('td'); modify_auction_button.className = 'modify_auction_button'; modify_auction_button.id = data[index].itemID; // add the image let modify_auction_img = document.createElement('img'); modify_auction_img.src = 'https://idlescape.com/images/mining/bronze_pickaxe.png'; modify_auction_img.addEventListener('mouseenter', e => e.target.src = 'https://idlescape.com/images/mining/rune_pickaxe.png'); modify_auction_img.addEventListener('mouseleave', e => e.target.src = 'https://idlescape.com/images/mining/bronze_pickaxe.png'); modify_auction_button.appendChild(modify_auction_img); // listener, popup modify_auction_button.addEventListener('click', () => { that.show_popup_sell_item(that, data[index]); }); element.appendChild(modify_auction_button); }); } /** * Delete all preexisting modifying buttons? * Mandatory for update */ Stonehub.prototype.clean_auctions = function() { let auction_buttons = document.getElementsByClassName('modify_auction_button'); let auction_id = []; for (let i = 0; i < auction_buttons.length; i++) { auction_id[i] = auction_buttons[i].id; }; // WARNING : It's mandatory to act in 2 times for this auction_id.forEach((element, index) => { document.getElementById(auction_id[index]).remove(); }); } // ==== MAIN ==== // try { let sh = new Stonehub(); sh.start(); } catch(e) {new Stonehub().error_handler(that, e);}