您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a price chart and Markethunt integration to the MH marketplace screen.
// ==UserScript== // @name Markethunt plugin for Mousehunt // @author Program // @namespace https://gf.qytechs.cn/en/users/886222-program // @license MIT // @version 1.7.0 // @description Adds a price chart and Markethunt integration to the MH marketplace screen. // @resource jq_confirm_css https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.css // @resource jq_toast_css https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.css // @require https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/highcharts/9.3.2/highstock.min.js // @include https://www.mousehuntgame.com/* // @grant GM_addStyle // @grant GM_getResourceText // // ==/UserScript== const markethuntDomain = 'markethunt.win'; const markethuntApiDomain = 'api.markethunt.win'; MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } const RoundToIntLocaleStringOpts = { maximumFractionDigits: 0 } const isDarkMode = (() => { let isDark = null; return () => { if (isDark == null) { isDark = !!getComputedStyle(document.documentElement).getPropertyValue('--mhdm-white'); } return isDark; } })(); /******************************* * * Plugin settings * *******************************/ class SettingsController { // TODO: make settings property private and convert init into static initializer once greasyfork adds support static settings; static init() { let settingsObj = {}; if (localStorage.markethuntSettings !== undefined) { settingsObj = JSON.parse(localStorage.markethuntSettings); } this.settings = new Proxy(settingsObj, { set(obj, prop, value) { obj[prop] = value; localStorage.markethuntSettings = JSON.stringify(obj); return true; } }); } static getStartChartAtZero() { if (this.settings.startChartAtZero === undefined) { return false; } else { return this.settings.startChartAtZero; } } static setStartChartAtZero(value) { this.settings.startChartAtZero = value; } static getEnablePortfolioButtons() { if (this.settings.enablePortfolioButtons === undefined) { return true; } else { return this.settings.enablePortfolioButtons; } } static setEnablePortfolioButtons(value) { this.settings.enablePortfolioButtons = value; } static getEnableFloatingVolumeLabels() { if (this.settings.enableFloatingVolumeLabels === undefined) { return false; } else { return this.settings.enableFloatingVolumeLabels; } } static setEnableFloatingVolumeLabels(value) { this.settings.enableFloatingVolumeLabels = value; } static getEnableChartAnimation() { if (this.settings.enableChartAnimation === undefined) { return true; } else { return this.settings.enableChartAnimation; } } static setEnableChartAnimation(value) { this.settings.enableChartAnimation = value; } } SettingsController.init(); function openPluginSettings() { $.alert({ title: 'Markethunt Plugin Settings', content: ` <div id="markethunt-settings-container"> <h1>Chart settings</h1> <label for="checkbox-start-chart-at-zero" class="cl-switch markethunt-settings-row"> <div class="markethunt-settings-row-input"> <input id="checkbox-start-chart-at-zero" type="checkbox" ${SettingsController.getStartChartAtZero() ? 'checked' : ''}> <span class="switcher"></span> </div> <div class="label markethunt-settings-row-description"> <b>Y-axis starts at 0</b><br> Make the Y-axis start at 0 gold/SB </div> </label> <label for="checkbox-enable-floating-volume-labels" class="cl-switch markethunt-settings-row"> <div class="markethunt-settings-row-input"> <input id="checkbox-enable-floating-volume-labels" type="checkbox" ${SettingsController.getEnableFloatingVolumeLabels() ? 'checked' : ''}> <span class="switcher"></span> </div> <div class="label markethunt-settings-row-description"> <b>Volume labels</b><br> Place floating labels indicating volume amount on the left side of the chart </div> </label> <label for="checkbox-enable-chart-animation" class="cl-switch markethunt-settings-row"> <div class="markethunt-settings-row-input"> <input id="checkbox-enable-chart-animation" type="checkbox" ${SettingsController.getEnableChartAnimation() ? 'checked' : ''}> <span class="switcher"></span> </div> <div class="label markethunt-settings-row-description"> <b>Chart animation</b><br> Enable chart animations </div> </label> <h1>Other settings</h1> <label for="checkbox-enable-portfolio-buttons" class="cl-switch markethunt-settings-row"> <div class="markethunt-settings-row-input"> <input id="checkbox-enable-portfolio-buttons" type="checkbox" ${SettingsController.getEnablePortfolioButtons() ? 'checked' : ''}> <span class="switcher"></span> </div> <div class="label markethunt-settings-row-description"> <b>Portfolio quick-add buttons</b><br> Place "Add to portfolio" buttons in your marketplace history and journal log </div> </label> </div> `, boxWidth: '450px', useBootstrap: false, closeIcon: true, draggable: true, onOpen: function(){ const startChartAtZeroCheckbox = document.getElementById("checkbox-start-chart-at-zero"); startChartAtZeroCheckbox.addEventListener('change', function(event) { SettingsController.setStartChartAtZero(event.currentTarget.checked); }); const enablePortfolioButtonsCheckbox = document.getElementById("checkbox-enable-portfolio-buttons"); enablePortfolioButtonsCheckbox.addEventListener('change', function(event) { SettingsController.setEnablePortfolioButtons(event.currentTarget.checked); }); const enableFloatingVolumeLabelsCheckbox = document.getElementById("checkbox-enable-floating-volume-labels"); enableFloatingVolumeLabelsCheckbox.addEventListener('change', function(event) { SettingsController.setEnableFloatingVolumeLabels(event.currentTarget.checked); }); const enableChartAnimationCheckbox = document.getElementById("checkbox-enable-chart-animation"); enableChartAnimationCheckbox.addEventListener('change', function(event) { SettingsController.setEnableChartAnimation(event.currentTarget.checked); }); } }); } /******************************* * * Chart functions * *******************************/ // chart vars const UtcTimezone = "T00:00:00+00:00" // style const primaryLineColor = "#4f52aa"; const secondaryLineColor = "#b91a05"; const sbiLineColor = "#00c000" const volumeColor = "#51cda0"; const volumeLabelColor = '#3ab28a'; const eventBandColor = "#f2f2f2"; const eventBandFontColor = "#888888"; // recommend to have same or close color as yGridLineColor for visual clarity const xGridLineColor = "#bbbbbb"; const yGridLineColor = "#aaaaaa"; const yGridLineColorLighter = "#dddddd"; const axisLabelColor = "#444444"; const crosshairColor = "#252525"; // dark mode style overrides const darkMode = { backgroundColor: "#222222", eventBandColor: "#303030", eventBandFontColor: "#909090", primaryLineColor: "#9e8dfc", crosshairColor: "#e0e0e0" } const chartFont = "tahoma,arial,sans-serif"; // set global opts Highcharts.setOptions({ chart: { style: { fontFamily: chartFont, }, spacingLeft: 0, spacingRight: 5, spacingTop: 7, spacingBottom: 6, }, lang: { rangeSelectorZoom :"" }, plotOptions: { series: { showInLegend: true, }, }, // must keep scrollbar enabled for dynamic scrolling, so hide the scrollbar instead scrollbar: { height: 0, buttonArrowColor: "#ffffff00", }, title: { enabled: false, }, credits: { enabled: false, }, rangeSelector: { buttonPosition: { y: 5, }, inputEnabled: false, labelStyle: { color: axisLabelColor, }, verticalAlign: 'top', x: -5.5, }, legend: { align: 'right', verticalAlign: 'top', y: -23, padding: 0, itemStyle: { color: '#000000', fontSize: "13px", }, }, tooltip: { animation: false, shared: true, split: false, headerFormat: '<span style="font-size: 11px; font-weight: bold">{point.key}</span><br/>', backgroundColor: 'rgba(255, 255, 255, 1)', hideDelay: 0, // makes tooltip feel more responsive when crossing gap between plots style: { color: '#000000', fontSize: '11px', fontFamily: chartFont, } }, navigator: { height: 25, margin: 0, maskInside: false, enabled: false, }, xAxis: { tickColor: xGridLineColor, gridLineColor: xGridLineColor, labels: { style: { color: axisLabelColor, fontSize: '11px', } } }, yAxis: { gridLineColor: yGridLineColor, labels: { style: { color: axisLabelColor, fontSize: '11px', }, y: 3, } } }); function setDarkThemeGlobalOpts() { Highcharts.setOptions({ chart: { backgroundColor: darkMode.backgroundColor }, legend: { itemStyle: { color: '#e0e0e0' }, itemHoverStyle: { color: '#f0f0f0' }, itemHiddenStyle: { color: '#777777' }, title: { style: { color: '#c0c0c0' } } }, xAxis: { gridLineColor: '#707070', labels: { style: { color: '#e0e0e0' } }, lineColor: '#707070', minorGridLineColor: '#505050', tickColor: '#707070', }, yAxis: { gridLineColor: '#707070', labels: { style: { color: '#e0e0e0' } }, lineColor: '#707070', minorGridLineColor: '#505050', tickColor: '#707070', }, tooltip: { backgroundColor: '#000000', style: { color: '#f0f0f0' } }, rangeSelector: { buttonTheme: { fill: '#444444', stroke: '#000000', style: { color: '#cccccc' }, states: { hover: { fill: '#707070', stroke: '#000000', style: { color: 'white' } }, select: { fill: '#000000', stroke: '#000000', style: { color: 'white' } } } }, }, }); } function UtcIsoDateToMillis(dateStr) { return (new Date(dateStr + UtcTimezone)).getTime(); } function formatSISuffix(num, decimalPlaces) { const suffixes = ["", "K", "M", "B"]; let order = Math.max(Math.floor(Math.log(num) / Math.log(1000)), 0); if (order > suffixes.length - 1) { order = suffixes.length - 1; } let significand = num / Math.pow(1000, order); return significand.toFixed(decimalPlaces) + suffixes[order]; } function eventBand(IsoStrFrom, IsoStrTo, labelText) { return { from: UtcIsoDateToMillis(IsoStrFrom), to: UtcIsoDateToMillis(IsoStrTo), color: isDarkMode() ? darkMode.eventBandColor : eventBandColor, label: { text: labelText, rotation: 270, textAlign: 'right', y: 5, // pixels from top of chart x: 4, // fix slight centering issue style: { color: isDarkMode() ? darkMode.eventBandFontColor : eventBandFontColor, fontSize: '12px', fontFamily: chartFont, }, }, } } function updateEventData() { $.getJSON(`https://${markethuntApiDomain}/events?plugin_ver=${GM_info.script.version}`, function (response) { localStorage.markethuntEventDatesV2 = JSON.stringify(response); localStorage.markethuntEventDatesV2LastRetrieval = Date.now(); }); } function renderChartWithItemId(itemId, containerId, forceRender = false) { const containerElement = document.getElementById(containerId); if (forceRender === false && containerElement.dataset.lastRendered) { return; } itemId = Number(itemId); let eventData = []; if (localStorage.markethuntEventDatesV2LastRetrieval !== undefined) { JSON.parse(localStorage.markethuntEventDatesV2).forEach(event => eventData.push(eventBand(event.start_date, event.end_date, event.short_name))); if (Date.now() - Number(localStorage.markethuntEventDatesV2LastRetrieval) > 2 * 86400 * 1000) { updateEventData(); } } else { updateEventData(); } function renderChart(response) { // set HUD if (response.market_data.length > 0) { const newestPrice = response.market_data[response.market_data.length - 1]; const utcTodayMillis = UtcIsoDateToMillis(new Date().toISOString().substring(0, 10)); const priceDisplay = document.getElementById("infoboxPrice"); const sbPriceDisplay = document.getElementById("infoboxSbPrice"); const tradeVolDisplay = document.getElementById("infoboxTradevol"); const goldVolDisplay = document.getElementById("infoboxGoldvol"); const weeklyVolDisplay = document.getElementById("infobox7dTradevol"); const weeklyGoldVolDisplay = document.getElementById("infobox7dGoldvol"); // set gold price priceDisplay.innerHTML = newestPrice.price.toLocaleString(); // set sb price try { let sbPriceText; let sbPrice = newestPrice.sb_price; if (sbPrice >= 100) { sbPriceText = Math.round(sbPrice).toLocaleString(); } else { sbPriceText = sbPrice.toFixed(2).toLocaleString(); } sbPriceDisplay.innerHTML = sbPriceText; } catch (e) { // do nothing } // set yesterday's trade volume let volText = '0'; if (utcTodayMillis - UtcIsoDateToMillis(newestPrice.date) <= 86400 * 1000 && newestPrice.volume !== null) { volText = newestPrice.volume.toLocaleString(); } tradeVolDisplay.innerHTML = volText; // set yesterday's gold volume let goldVolText = '0'; if (utcTodayMillis - UtcIsoDateToMillis(newestPrice.date) <= 86400 * 1000 && newestPrice.volume !== null) { goldVolText = formatSISuffix(newestPrice.volume * newestPrice.price, 2); } goldVolDisplay.innerHTML = goldVolText; // set last week's trade volume let weeklyVolText = response.market_data.reduce(function(sum, dataPoint) { if (utcTodayMillis - UtcIsoDateToMillis(dataPoint.date) <= 7 * 86400 * 1000) { return sum + (dataPoint.volume !== null ? dataPoint.volume : 0); } else { return sum; } }, 0); weeklyVolDisplay.innerHTML = weeklyVolText.toLocaleString(); // set last week's gold volume let weeklyGoldVol = response.market_data.reduce(function(sum, dataPoint) { if (utcTodayMillis - UtcIsoDateToMillis(dataPoint.date) <= 7 * 86400 * 1000) { return sum + (dataPoint.volume !== null ? dataPoint.volume * dataPoint.price : 0); } else { return sum; } }, 0); weeklyGoldVolDisplay.innerHTML = (weeklyGoldVol === 0) ? '0' : formatSISuffix(weeklyGoldVol, 2); } // process data for highcharts var dailyPrices = []; var dailyVolumes = []; var dailySbPrices = []; for (var i = 0; i < response.market_data.length; i++) { dailyPrices.push([ UtcIsoDateToMillis(response.market_data[i].date), Number(response.market_data[i].price) ]); dailyVolumes.push([ UtcIsoDateToMillis(response.market_data[i].date), Number(response.market_data[i].volume) ]); dailySbPrices.push([ UtcIsoDateToMillis(response.market_data[i].date), Number(response.market_data[i].sb_price) ]); } if (isDarkMode()) { setDarkThemeGlobalOpts(); } // Create the chart let chart = new Highcharts.stockChart(containerId, { chart: { // zoom animations animation: SettingsController.getEnableChartAnimation() ? { 'duration': 500 } : false, }, plotOptions: { series: { // initial animation animation: SettingsController.getEnableChartAnimation() ? { 'duration': 900 } : false, dataGrouping: { enabled: itemId === 114, units: [['day', [1]], ['week', [1]]], groupPixelWidth: 3, }, }, }, rangeSelector: { buttons: [ { type: 'month', count: 1, text: '1M' }, { type: 'month', count: 3, text: '3M' }, { type: 'month', count: 6, text: '6M' }, { type: 'year', count: 1, text: '1Y', }, { type: 'all', text: 'All' }, ], selected: 3, }, legend: { enabled: true }, tooltip: { xDateFormat: '%b %e, %Y', }, series: [ { name: 'Average price', id: 'dailyPrice', data: dailyPrices, lineWidth: 1.5, states: { hover: { lineWidthPlus: 0, halo: false, // disable translucent halo on marker hover } }, yAxis: 0, color: isDarkMode() ? darkMode.primaryLineColor : primaryLineColor, marker: { states: { hover: { lineWidth: 0, } }, }, tooltip: { pointFormatter: function() { return `<span style="color:${this.color}">\u25CF</span>` + ` ${this.series.name}:` + ` <b>${this.y.toLocaleString()}g</b><br/>`; }, }, zIndex: 1, }, { name: 'Volume', type: 'column', data: dailyVolumes, pointPadding: 0, // disable point and group padding to simulate column area chart groupPadding: 0, yAxis: 2, color: volumeColor, tooltip: { pointFormatter: function() { let volumeAmtText = this.y !== 0 ? this.y.toLocaleString() : 'n/a'; return `<span style="color:${this.color}">\u25CF</span>` + ` ${this.series.name}:` + ` <b>${volumeAmtText}</b><br/>`; }, }, zIndex: 0, }, { name: 'SB Price', id: 'sbi', data: dailySbPrices, visible: false, lineWidth: 1.5, states: { hover: { lineWidthPlus: 0, halo: false, // disable translucent halo on marker hover } }, yAxis: 1, color: sbiLineColor, marker: { states: { hover: { lineWidth: 0, } }, }, tooltip: { pointFormatter: function() { let sbiText; if (this.y >= 1000) { sbiText = Math.round(this.y).toLocaleString(); } else if (this.y >= 100) { sbiText = this.y.toFixed(1).toLocaleString(); } else if (this.y >= 10) { sbiText = this.y.toFixed(2).toLocaleString(); } else { sbiText = this.y.toFixed(3).toLocaleString(); } return `<span style="color:${this.color}">\u25CF</span>` + ` SB Index:` + ` <b>${sbiText} SB</b><br/>`; }, }, zIndex: 2, }, ], yAxis: [ { min: SettingsController.getStartChartAtZero() ? 0 : null, labels: { formatter: function() { return this.value.toLocaleString() + 'g'; }, x: -8, }, showLastLabel: true, // show label at top of chart crosshair: { dashStyle: 'ShortDot', color: isDarkMode() ? darkMode.crosshairColor : crosshairColor, }, opposite: false, alignTicks: false, // disabled, otherwise autoranger will create too large a Y-window }, { min: SettingsController.getStartChartAtZero() ? 0 : null, gridLineWidth: 0, labels: { formatter: function() { return this.value.toLocaleString() + ' SB'; }, x: 5, }, showLastLabel: true, // show label at top of chart opposite: true, alignTicks: false, }, { top: '75%', height: '25%', offset: 0, min: 0, opposite: false, tickPixelInterval: 35, allowDecimals: false, alignTicks: false, gridLineWidth: 0, labels: { enabled: SettingsController.getEnableFloatingVolumeLabels(), align: 'left', x: 0, style: { color: volumeLabelColor, }, }, showLastLabel: true, showFirstLabel: false, }], xAxis: { type: 'datetime', ordinal: false, // show continuous x axis if dates are missing plotBands: eventData, crosshair: { dashStyle: 'ShortDot', color: isDarkMode() ? darkMode.crosshairColor : crosshairColor, }, dateTimeLabelFormats:{ day: '%b %e', week: '%b %e, \'%y', month: '%b %Y', year: '%Y' }, tickPixelInterval: 120, }, }); containerElement.dataset.lastRendered = Date.now().toString(); } $.getJSON(`https://${markethuntApiDomain}/items/${itemId}?plugin_ver=${GM_info.script.version}`, function (response) { renderChart(response); }); } function renderStockChartWithItemId(itemId, containerId, forceRender = false) { const containerElement = document.getElementById(containerId); if (forceRender === false && containerElement.dataset.lastRendered) { return; } itemId = Number(itemId); let eventData = []; if (localStorage.markethuntEventDatesV2LastRetrieval !== undefined) { JSON.parse(localStorage.markethuntEventDatesV2).forEach(event => eventData.push(eventBand(event.start_date, event.end_date, event.short_name))); } function renderStockChart(response) { const bid_data = []; const ask_data = []; const supply_data = []; response.stock_data.forEach(x => { bid_data.push([x.timestamp, x.bid]); ask_data.push([x.timestamp, x.ask]); supply_data.push([x.timestamp, x.supply]); }) if (isDarkMode()) { setDarkThemeGlobalOpts(); } // Create the chart let chart = new Highcharts.stockChart(containerId, { chart: { // zoom animations animation: SettingsController.getEnableChartAnimation() ? { 'duration': 500 } : false, }, plotOptions: { series: { // initial animation animation: SettingsController.getEnableChartAnimation() ? { 'duration': 900 } : false, dataGrouping: { enabled: true, units: [['hour', [2, 4, 6]], ['day', [1]], ['week', [1]]], groupPixelWidth: 2, dateTimeLabelFormats: { hour: ['%b %e, %Y %H:%M UTC', '%b %e, %Y %H:%M UTC'], day: ['%b %e, %Y'] } }, }, }, rangeSelector: { buttons: [ { type: 'day', count: 7, text: '7D' }, { type: 'month', count: 1, text: '1M' }, { type: 'month', count: 3, text: '3M' }, { type: 'month', count: 6, text: '6M' }, { type: 'year', count: 1, text: '1Y' }, { type: 'all', text: 'All' }, ], selected: 1, }, legend: { enabled: true }, tooltip: { xDateFormat: '%b %e, %Y %H:%M UTC', }, series: [ { name: 'Ask', id: 'ask', data: ask_data, lineWidth: 1.5, states: { hover: { lineWidthPlus: 0, halo: false, // disable translucent halo on marker hover } }, yAxis: 0, color: isDarkMode() ? darkMode.primaryLineColor : primaryLineColor, marker: { states: { hover: { lineWidth: 0, } }, }, tooltip: { pointFormatter: function() { return `<span style="color:${this.color}">\u25CF</span>` + ` ${this.series.name}:` + ` <b>${this.y.toLocaleString(undefined, RoundToIntLocaleStringOpts)}g</b><br/>`; }, }, zIndex: 1, }, { name: 'Bid', id: 'bid', data: bid_data, lineWidth: 1.5, states: { hover: { lineWidthPlus: 0, halo: false, // disable translucent halo on marker hover } }, yAxis: 0, color: secondaryLineColor, marker: { states: { hover: { lineWidth: 0, } }, }, tooltip: { pointFormatter: function() { return `<span style="color:${this.color}">\u25CF</span>` + ` ${this.series.name}:` + ` <b>${this.y.toLocaleString(undefined, RoundToIntLocaleStringOpts)}g</b><br/>`; }, }, zIndex: 2, }, { name: 'Supply', id: 'supply', data: supply_data, type: 'area', lineWidth: 1.5, states: { hover: { lineWidthPlus: 0, halo: false, // disable translucent halo on marker hover } }, yAxis: 1, color: volumeColor, marker: { states: { hover: { lineWidth: 0, } }, }, tooltip: { pointFormatter: function() { return `<span style="color:${this.color}">\u25CF</span>` + ` ${this.series.name}:` + ` <b>${this.y.toLocaleString(undefined, RoundToIntLocaleStringOpts)}</b><br/>`; }, }, zIndex: 0, }, ], yAxis: [ { min: SettingsController.getStartChartAtZero() ? 0 : null, labels: { formatter: function() { return this.value.toLocaleString() + 'g'; }, x: -8, }, showLastLabel: true, // show label at top of chart opposite: false, alignTicks: false }, { top: '75%', height: '25%', offset: 0, min: 0, opposite: false, tickPixelInterval: 35, allowDecimals: false, alignTicks: false, gridLineWidth: 0, labels: { enabled: SettingsController.getEnableFloatingVolumeLabels(), align: 'left', x: 0, style: { color: volumeLabelColor, }, }, showLastLabel: true, showFirstLabel: false, }], xAxis: { type: 'datetime', ordinal: false, // show continuous x axis if dates are missing plotBands: eventData, crosshair: { dashStyle: 'ShortDot', color: isDarkMode() ? darkMode.crosshairColor : crosshairColor, }, dateTimeLabelFormats:{ day: '%b %e', week: '%b %e, \'%y', month: '%b %Y', year: '%Y' }, tickPixelInterval: 120, } }); containerElement.dataset.lastRendered = Date.now().toString(); } $.getJSON(`https://${markethuntApiDomain}/items/${itemId}/stock?&plugin_ver=${GM_info.script.version}`, function (response) { renderStockChart(response); }); } if (localStorage.markethuntEventDatesV2LastRetrieval === undefined) { updateEventData(); } /******************************* * * Marketplace view observer * *******************************/ // chart-icon-90px.png minified with TinyPNG then converted to base 64 const chartIconImageData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAMAAAAPdrEwAAAAVFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU4H24AAAAG3RSTlMABvTkbYjGz8kamvoO0EYg6H0koqabYVSqNDILUie0AAABU0lEQVRYw+2V2Y6DMAx" + "FyUIgwLB1mxn//3+OhEQd6o5EypUqVT6PoBwcbmIXiqIo78b42traG7w5ftFCFeE1L+bFja7b0x0PVtesDmC1ZbWFpS/V2PQnYgIyfXOmBA9MP3KG/HlI+r9uY46vpj+It7f1tQvWhryL3lNC2" + "wz/BFhn3/CuoS3taU4CPK2Pv7tcc++IYbkIsDxaMv+W+RpGG9wawQ1Q8lPcT27JF147BTuG69y0qZEDzOsYYeKSm3tEwxP52WR2DMb1BSPlU3bHECULeX6f86JkwWBfU9ep+dIhZ4olpsdOwj2" + "bNdVjCzWlowVXmmMDNFYPLbTkVeXBsW/8toW6JPli12Z3QwnFns2i1bxZvJqR6cPUMn2UWqY/gtWUoGqxTJwZlFqeGZhanhmwmhI+Xi3SR6hl+jC1TB+spgRVq1rVqla1qlUNUSuKooD4Az6O4" + "MtRLQLhAAAAAElFTkSuQmCC"; // sb.png minified with TinyPNG then converted to base 64 const sbImageData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAMAAAAMs7fIAAABcVBMVEX+/v/5///+/fzwyJTuxJD1////6cn416z31ab206SOi4ny///t///b" + "///J///2/P/9/Pj4+Pjq6/b8+PPw8PPh4+7+9+3K5s//7M7/6Mb+5cP63bfv17b53LP21an1zJzWuprzzJDRrovpuoHmuXzktXu6k3Rd0HOzi2vp///Q//+z//+t///n/v/0+P/q7v/t7v79" + "+PjW//P/+/P2+vHZ7PHn6e3/+eft5+f/9ubi8eT/9OPw59//8t377dzh3tz+7dXN7dTa09Sv1tP35tHO1czX4cu+3cLazcLCysL44L3Uw7vDuLj32rar3LGvtbHq167gyK6S16nlyanl06je" + "w6jy06fxz6Z8oqSooKDszJ7Ls56MypvoxZqel5fQsZblwJWVkJB0xY3nvI10y4vswIv30IrvwInRp4jxyYeIg4XdtYPQq4LNpH65mnt9e3u+sXr0xHjZq3dZwHTPonR1dXHgqnC6kGtpbGnh" + "qWEs0UvWjFe8AAAA4klEQVQY02PACvgYITSvlbo4mCEY4V9awZUf4+ieUqUOFmFK5OKKjMtKCioW9zPRBAowAhFIJUSnFhBrczMwAJGIkKiomQhIkFWHj0GXQc+An4df3yfPlRUoxMNgaGFv" + "6uTpHF1SpqIA0StWWaCqzBwlL8+RngFxhnlhSJiblxSbhCRzEViE1ShNWlaGnZMzIFU1HqLLWFGOnZOZmYWFRcUD6g1FFg52DrnY3HINIahIpnJ2jpqGmlJCsjdUJFBJIViGTZJNOjwUKiLr" + "KyXhYGtpbediAxURExYWYGIAQgGgDwEEwCDFO/6WiQAAAABJRU5ErkJggg=="; const settingsImageData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAACKElEQVR4nO2YMUucQRCGH9Cgoi" + "YgNolVqhAVm6SyNoGAyP0Q8TT+ABG0C3ZRbMU2gprC4vA/HNx5tQZTxSimCDHKJwsjHEe8b2bX1cV8DwzICTPvu+x+M7tQUFBwl3wFspzYI2G+Kwz8AjpJkCGF+Jt4Q4JMGQzMkiCLBgNfSIhhYAP4" + "bTBwBWwCr+5D4AegDiyI2Bu6gU/ApUF4a1wCK0BPy4IsSE1XO4gnQKOlaA1YAg4ChLdGQ3LW/vG70+DN3B2K9I1ZX/EDwEkCBk6BQR8DawmIzyRWreLdQfqbgPCs6bCPWQxUAorVZN+OAL0SI3Ke6g" + "F5K1rxJc8Cf4BpoKNNbve/GeDCs0ZJY+A1cOghfkK7QsA7DxOHok3FC6BqSO5W3krZkL8qmkw8A/aVe77dtrkNN1JrmuG+aPGiCziKOFl+zMl9JBqCOMsp0jwfWRlVNLFgrnKK9AXk7ld8/6MbcCJ8" + "eXofBk5zirgmlewW6lIcYtdhfZmPeYi1n9F6wGe0Eeszam1kbjyIedeoWhqZzyhxIeOBlvcxR4mSMXGziXLOo1WnrHzUYS50nK5Lhx2VHtEnf88H3qMr/E8XGuQalyUSn/G81P9IQPxP30s9Mmk+tI" + "EykR62NE1IG9+A5RgPW+2eFnvkWTDkaTEDtoHnsZ4WNbwE1o1G3IS7C4yTEFsGAzskyJzBgNsiyfHWYGCSBOkAzpUGmg9tUuwpxB8/tMiCgsfENevgYdmM/xZUAAAAAElFTkSuQmCC"; const kofiImageData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAABm1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAD/Xluzs7PIyMj/XV7/Wmz/W2b/XW" + "D//f3/WXH/Wm7/W2n/XGT8/Pz/+Pn/XGLQ0ND/W2q6urq3t7esrKympqb/V3j/WHX/+vr19fX/ydDCwsKwsLCioqKMjIx9fX1YWFhKSko7Ozv9/f3//Pz5+fn/9fXm5ub/2tzb29v/0dXNzc3Ex" + "MT/pqf/mqX/kKX/n56enp6RkZH/kpD/joz/i4mIiIj/h4b/hIJubm5iYmJNTU0jIyMeHh4PDw8MDAz/4+TS0tL/xcj/u8jFxcW+vr7/rbT/lqz/k6D/jZ3/gZz/i5r/ipmYmJiXl5f/mJaVlZX/" + "lJWUlJT/kJP/eYz/comCgoJ5eXl0dHRra2s0NDQsLCwnJycTExMRERH4jQN3AAAAKXRSTlMAiqaR9J+W+/h6dQ+5rI6Jg4BvZ1ZJNycH1svGwmM9MyshCe/ksqE/Gb0RW4gAAAH8SURBVEjH7dR" + "nVxpBGIbhiVnpKki1JWrq+4KKi7pgaAqIIGrsGks09vTee//ZbmFndhTQ73p9fu4zs+fsLrlwnvhdl2posnnthNN5DU8h1PuNQSNuh2oY+7jzDy1WwtTjItSWGT/Ahm5D0AOnmfuDJhrU1Qgy28" + "XirzEJYGYfvWcJelBRmgFIYOvxQJx/8XrlSRTE7Kuvb7JiOXD4PO1YmobJ3+ZOGiyo+5V0Oj0y8m32bWSgr+/OsqgFdYQE2jEP8B3dNBgGWVZeRyKRAWU9OBgOP6YBuYITACE08cGyYT00FOv/w" + "gK78BdgGBv44KW2Dqvr/t7eLRZ0C3s0YA/93LgOBoP3jl0pgSY+eCjfJKatVbM0sLdiCqCAbj6ApZg81tbqAVrQ1uG8jrsSTO9ZuojmZjkY/aSuNfdHQbGAMvNOBmANb7B36SloxQbdP4iCSlor" + "bK7OAUDu0OJnQQ400Q19fxc4k6lDs5voTPgM9GJd3W/xeym3i+ZmYgjioJv6oNw/yrbvN38U9xFbbnFfXAgocSm4zvawiDLB4QkQgyZMAiPOTwETQofHZyc8F+ahmnGkd2c68CdUIZXw6sngtnD" + "wqOoBbaQCG/4vJOLxeKL8X0kmk6lUfvXd5wkU6AEcqwUra/GRyrqaL+salb+j0+myWm02b4BcOK+OAN6AtFxkFAmAAAAAAElFTkSuQmCC"; const mpObserverTarget = document.querySelector("#overlayPopup"); function CurrentViewedItemId() { const itemIdRaw = mpObserverTarget.querySelector(".marketplaceView-item.view")?.dataset.itemId; return itemIdRaw ? Number(itemIdRaw) : null; } const mpObserver = new MutationObserver(function () { // Check if the Marketplace interface is open if (!mpObserverTarget.querySelector(".marketplaceView")) { return; } // detect item page and inject chart const backButton = mpObserverTarget.querySelector("a.marketplaceView-breadcrumb"); if (backButton) { const targetContainer = mpObserverTarget.querySelector(".marketplaceView-item-description"); if (targetContainer && !mpObserverTarget.querySelector("#chartArea")) { // Disconnect and reconnect later to prevent mutation loop mpObserver.disconnect(); // Setup chart divs const itemId = CurrentViewedItemId(); targetContainer.insertAdjacentHTML( "beforebegin", `<div id="chartArea" style="display: flex; padding: 0 20px 0 20px; height: 315px;"> <div style="flex-grow: 1; position: relative"> <div id="chartContainer" style="text-align: center; height: 100%; width: 100%"> <img style="opacity: 0.07; margin-top: 105px" src="${chartIconImageData}" alt="Chart icon" class="${isDarkMode() ? 'inverted' : ''}"> <div style="color: grey">Loading ...</div> </div> <div id="stockChartContainer" style="text-align: center; position: absolute; background-color: ${isDarkMode() ? darkMode.backgroundColor : 'white'}; top: 0; left: 0; width: 100%; height: 100%; display: none"> <img style="opacity: 0.07; margin-top: 105px" src="${chartIconImageData}" alt="Chart icon" class="${isDarkMode() ? 'inverted' : ''}"> <div style="color: grey">Loading ...</div> </div> </div> <div id="markethuntInfobox" style="text-align: center; display: flex; flex-direction: column; padding: 34px 0 12px 5px; position: relative;"> <div class="marketplaceView-item-averagePrice infobox-stat infobox-small-spans infobox-striped"> Trade volume:<br> <span id="infoboxTradevol">--</span><br> <span id="infoboxGoldvol" class="marketplaceView-goldValue">--</span> </div> <div class="marketplaceView-item-averagePrice infobox-stat infobox-small-spans"> 7-day trade volume:<br> <span id="infobox7dTradevol">--</span><br> <span id="infobox7dGoldvol" class="marketplaceView-goldValue">--</span> </div> <div style="user-select: none; text-align: left"> <label class="cl-switch" for="markethuntShowStockData" style="cursor: pointer"> <input type="checkbox" id="markethuntShowStockData"> <span class="switcher"></span> <span class="label">Stock chart</span> </label> </div> <div style="flex-grow: 1"></div> <!-- spacer div --> <div> <div style="display: flex;"> <div style="flex-grow: 1;"></div> <div> <a id="markethuntSettingsLink" href="#"> <img src="${settingsImageData}" class="markethunt-settings-btn-img ${isDarkMode() ? 'inverted' : ''}"> </a> </div> <div> <a href="https://ko-fi.com/vsong_program" target="_blank" alt="Donation Link"> <img src="${kofiImageData}" class="markethunt-settings-btn-img" alt="Settings"> </a> </div> <div style="flex-grow: 1;"></div> </div> <div style="font-size: 0.8em; color: grey">v${GM_info.script.version}</div> </div> </div> </div>` ); const itemPriceContainer = mpObserverTarget.querySelector(".marketplaceView-item-averagePrice"); itemPriceContainer.classList.add("infobox-stat"); itemPriceContainer.insertAdjacentHTML( "beforeend", `<br><span id="infoboxSbPrice" class="marketplaceView-sbValue">--</span><img style="vertical-align: bottom" src="${sbImageData}" alt="SB icon" />` ); const itemPriceDisplay = itemPriceContainer.querySelector("span"); itemPriceDisplay.id = "infoboxPrice"; const infoBox = document.getElementById("markethuntInfobox"); infoBox.prepend(itemPriceContainer); // Set infobox minimum width to prevent layout shifts, *then* reset price display const infoBoxInitialWidth = $(infoBox).width(); infoBox.style.minWidth = `${infoBoxInitialWidth}px`; itemPriceDisplay.innerHTML = "--"; // Set stock chart checkbox listener const stockChartCheckbox = document.getElementById('markethuntShowStockData'); stockChartCheckbox?.addEventListener('change', (e) => { if (e.target.checked) { document.getElementById('stockChartContainer').style.display = 'block'; renderStockChartWithItemId(itemId, 'stockChartContainer'); } else { document.getElementById('stockChartContainer').style.display = 'none'; renderChartWithItemId(itemId, 'chartContainer'); } }); // Set Plugin Settings listener const settingsLink = document.getElementById("markethuntSettingsLink"); settingsLink.addEventListener('click', openPluginSettings); // Render chart renderChartWithItemId(itemId, "chartContainer"); // Re-observe after mutation-inducing logic mpObserver.observe(mpObserverTarget, { childList: true, subtree: true }); } } // detect history page and inject portfolio buttons const historyTab = mpObserverTarget.querySelector("[data-tab=history].active"); if (SettingsController.getEnablePortfolioButtons() && historyTab) { mpObserver.disconnect(); let rowElem = mpObserverTarget.querySelectorAll(".marketplaceMyListings tr.buy"); rowElem.forEach(function(row) { if (!row.querySelector(".mousehuntActionButton.tiny.addPortfolio")) { let itemElem = row.querySelector(".marketplaceView-itemImage"); const itemId = itemElem.getAttribute("data-item-id"); let qtyElem = row.querySelector("td.marketplaceView-table-numeric"); const qty = Number(qtyElem.innerText.replace(/\D/g, '')); let priceElem = row.querySelector("td.marketplaceView-table-numeric .marketplaceView-goldValue"); const price = Number(priceElem.innerText.replace(/\D/g, '')); let buttonContainer = row.querySelector("td.marketplaceView-table-actions"); let addPortfolioBtn = document.createElement("a"); addPortfolioBtn.href = `https://${markethuntDomain}/portfolio.php?action=add_position&item_id=${itemId}&add_qty=${qty}&add_mark=${price}`; addPortfolioBtn.innerHTML = "<span>+ Portfolio</span>"; addPortfolioBtn.className = "mousehuntActionButton tiny addPortfolio lightBlue"; addPortfolioBtn.target = "_blank"; addPortfolioBtn.style.display = "block"; addPortfolioBtn.style.marginTop = "2px"; buttonContainer.appendChild(addPortfolioBtn); } }); mpObserver.observe(mpObserverTarget, { childList: true, subtree: true }); } }); // Initial observe mpObserver.observe(mpObserverTarget, { childList: true, subtree: true }); const marketplaceCssOverrides = ` .marketplaceView-item { padding-top: 10px; } .marketplaceView-item-content { padding-top: 10px; padding-bottom: 0px; min-height: 0px; } .marketplaceView-item-descriptionContainer { padding-bottom: 5px; padding-top: 5px; } .marketplaceView-item-averagePrice { margin-top: 5px; } .marketplaceView-item-footer { padding-top: 10px; padding-bottom: 10px; } .markethunt-cross-link { color: #000; font-size: 10px; background: #fff; display: block; padding: 0.1em 0; margin: 0.4em 0; box-shadow: #797979 1px 1px 1px 0; border-radius: 0.2em; border: 1px solid #a8a8a8; } .markethunt-cross-link:hover { color: white; background-color: ${primaryLineColor}; } .markethunt-settings-btn-img { height: 26px; padding: 3px; margin: 0 5px 0 5px; border-radius: 999px; box-shadow: 0px 0px 3px gray; } .markethunt-settings-btn-img:hover { box-shadow: 0px 0px 3px 1px gray; } .inverted { filter: invert(1); } .markethunt-settings-row-input { display: flex; align-items: center; padding-right: 5px; } .markethunt-settings-row { display: flex; padding: 5px; } .markethunt-settings-row-description { } .marketplaceView-item-averagePrice.infobox-stat { text-align: left; margin-bottom: 14px; white-space: nowrap; } .marketplaceView-item-leftBlock .marketplaceHome-block-viewAll { margin-top: 5px; } .infobox-striped { } .infobox-small-spans span { font-size: 11px; } .infobox-small-spans .marketplaceView-goldValue::after { width: 17px; height: 13px; } `; const materialSwitchCss = ` .cl-switch input[type="checkbox"] { display: none; visibility: hidden; } .cl-switch .switcher { display: inline-block; border-radius: 100px; width: 2.25em; height: 1em; background-color: #ccc; position: relative; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; vertical-align: middle; cursor: pointer; } .cl-switch .switcher:before { content: ""; display: block; width: 1.3em; height: 1.3em; background-color: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); border-radius: 50%; margin-top: -0.15em; position: absolute; top: 0; left: 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; margin-right: 0; -webkit-transition: all 0.2s; -moz-transition: all 0.2s; -ms-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .cl-switch .switcher:active:before { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.6), 0 0 0 1em rgba(63, 81, 181, 0.3); transition: all, 0.1s; } .cl-switch .label { cursor: pointer; vertical-align: middle; } .cl-switch input[type="checkbox"]:checked+.switcher { background-color: #8591d5; } .cl-switch input[type="checkbox"]:checked+.switcher:before { left: 100%; margin-left: -1.3em; background-color: #3f51b5; } .cl-switch [disabled]:not([disabled="false"])+.switcher { background: #ccc !important; } .cl-switch [disabled]:not([disabled="false"])+.switcher:active:before { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2) !important; } .cl-switch [disabled]:not([disabled="false"])+.switcher:before { background-color: #e2e2e2 !important; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2) !important; }`; /******************************* * * Journal observer * *******************************/ // add_portfolio_journal.png minified with TinyPNG then converted to base 64 const addPfolioBtnImgData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALBAMAAACEzBAKAAAAFVBMVEUAAAAAAAD/swD06DD6zhj///8PDgMsru0CAAAAAXRSTlMA" + "QObYZgAAADhJREFUCNdjCBQEAwEGYWMwMERmmBkbCoIZhkAobMgg4igo6Cjo4sggpKQIhEqKyAwgQGEwQk0GAIl6DBhSGEjXAAAAAElFTkSuQmCC"; function addJournalButtons(supplyTransferJournalEntries) { supplyTransferJournalEntries.forEach(function(supplyTransferEntry) { const journalActionsElem = supplyTransferEntry.querySelector(".journalactions"); const textElem = supplyTransferEntry.querySelector(".journaltext"); if (journalActionsElem.querySelector("a.actionportfolio")) { return; } if (textElem.textContent.includes("SUPER|brie+") || textElem.textContent.includes("Passing Parcel")) { return; } // Disable button on sending transfers until portfolio sending feature implemented if (textElem.textContent.includes("I sent")) { return; } const addPortfolioBtn = document.createElement("a"); addPortfolioBtn.href = "#"; addPortfolioBtn.className = "actionportfolio"; addPortfolioBtn.addEventListener('click', addSbTradeToPortfolio); journalActionsElem.prepend(addPortfolioBtn) }); } async function updateItemMetadata() { console.log("Retrieving marketplace item data"); return new Promise((resolve, reject) => { hg.utils.Marketplace.getMarketplaceData( function (response) { const itemMetadata = response.marketplace_items.reduce( function (items, item) { items[normalizeItemName(item.name)] = item.item_id; return items; }, {} ); localStorage.markethuntItemMetadata = JSON.stringify(itemMetadata); localStorage.markethuntItemMetadataLastRetrieval = Date.now(); resolve(itemMetadata); }, function (e) { reject(e); } ); }); } function normalizeItemName(name) { return name.trim(); } async function addSbTradeToPortfolio(event) { event.preventDefault(); // prevent scroll to top const targetTransferJournalEntry = event.target.parentNode.parentNode.parentNode; const textElem = targetTransferJournalEntry.querySelector(".journaltext"); const targetEntryId = Number(targetTransferJournalEntry.dataset.entryId); // group 1 = qty, group 2 = item name, group 3 = trade partner snuid const regex = /^I received (\d[\d,]*) (.+?) from <a href.+snuid=(\w+)/ // get item and partner data const targetEntryMatch = textElem.innerHTML.match(regex); const targetItemQty = Number(targetEntryMatch[1].replace(",", "")); const targetItemName = targetEntryMatch[2]; const partnerSnuid = targetEntryMatch[3]; const partnerName = textElem.querySelector('a').innerHTML; // get item ID let targetItemId = undefined; if (localStorage.markethuntItemMetadata !== undefined) { const itemMetadata = JSON.parse(localStorage.markethuntItemMetadata); targetItemId = itemMetadata[normalizeItemName(targetItemName)]; } if (targetItemId === undefined) { $.toast({ text: "Please wait ...", heading: localStorage.markethuntItemMetadata === undefined ? 'Downloading item data' : 'Reloading item data', icon: 'info', position: 'top-left', loader: false, // Whether to show loader or not. True by default }); const itemMetadata = await updateItemMetadata(); await sleep(600); // allow user to read toast before opening new tab targetItemId = itemMetadata[normalizeItemName(targetItemName)]; } // detect all sb send entries const allSupplyTransferJournalEntries = document.querySelectorAll("#journalContainer div.entry.supplytransferitem"); const matchingSbSendEntries = Array.from(allSupplyTransferJournalEntries).reduce( function(results, journalEntry) { const innerHTML = journalEntry.querySelector(".journaltext").innerHTML; if (!innerHTML.includes(partnerSnuid)) { return results; } const candidateSbMatch = innerHTML.match(/^I sent (\d[\d,]*) SUPER\|brie\+ to <a href/); if (!candidateSbMatch) { return results; } const candidateSbSent = Number(candidateSbMatch[1].replace(",", "")); const candidateEntryId = Number(journalEntry.dataset.entryId); results.push({sbSent: candidateSbSent, entryId: candidateEntryId}); return results; }, [] ); // choose best sb send entry let bestSbSendEntryMatch = null; let bestMatchDistance = null; matchingSbSendEntries.forEach(function(candidateEntry) { const entryPairDistance = Math.abs(targetEntryId - candidateEntry.entryId); if (bestMatchDistance === null || bestMatchDistance > entryPairDistance) { bestSbSendEntryMatch = candidateEntry; bestMatchDistance = entryPairDistance; } }); let avgSbPriceString = "none"; if (bestSbSendEntryMatch !== null) { const avgSbPrice = bestSbSendEntryMatch.sbSent / targetItemQty; avgSbPriceString = avgSbPrice.toFixed(2); } // prepare modal message let actionMsg = 'Markethunt plugin: '; if (bestSbSendEntryMatch !== null) { actionMsg += `Found a transfer of ${bestSbSendEntryMatch.sbSent.toLocaleString()} SB to ${partnerName}.` + ` Buy price has been filled in for you.`; } else { actionMsg += 'No matching SB transfer found. Please fill in buy price manually.'; } // open in new tab window.open(`https://${markethuntDomain}/portfolio.php?action=add_position` + `&action_msg=${encodeURIComponent(actionMsg)}` + `&item_id=${targetItemId}` + `&add_qty=${targetItemQty}` + `&add_mark=${avgSbPriceString}` + `&add_mark_type=sb`, '_blank'); } const journalObserverTarget = document.querySelector("#mousehuntContainer"); const journalObserver = new MutationObserver(function () { // Disconnect and reconnect later to prevent mutation loop journalObserver.disconnect(); const journalContainer = journalObserverTarget.querySelector("#journalContainer"); if (SettingsController.getEnablePortfolioButtons() && journalContainer) { // add portfolio buttons const supplyTransferJournalEntries = journalContainer.querySelectorAll("div.entry.supplytransferitem"); addJournalButtons(supplyTransferJournalEntries); } // Reconnect observer once all mutations done journalObserver.observe(journalObserverTarget, { childList: true, subtree: true }); }); // Initial observe journalObserver.observe(journalObserverTarget, { childList: true, subtree: true }); const journalCssOverrides = ` .journalactions a { display: inline-block; } .journalactions a.actionportfolio { margin-right: 5px; background: url('${addPfolioBtnImgData}'); width: 16px; } `; /******************************* * * Import Portfolio * *******************************/ function addTouchPoint() { if ($('.invImport').length === 0) { const invPages = $('.inventory .torn_pages'); //Inventory History Button const invImportElem = document.createElement('li'); invImportElem.classList.add('crafting'); invImportElem.classList.add('invImport'); const invImportBtn = document.createElement('a'); invImportBtn.href = "#"; invImportBtn.innerText = "Export to Markethunt"; invImportBtn.onclick = function () { onInvImportClick(); }; const icon = document.createElement("div"); icon.className = "icon"; invImportBtn.appendChild(icon); invImportElem.appendChild(invImportBtn); $(invImportElem).insertAfter(invPages); } } function submitInv() { if (!document.forms["import-form"].reportValidity()) { return; } const gold = Number($('.hud_gold').text().replaceAll(/[^\d]/g, '')); if (isNaN(gold)) { return; } const itemsToGet = ['weapon','base', 'trinket', 'bait', 'skin', 'crafting_item','convertible', 'potion', 'stat','collectible','map_piece','adventure']; //future proof this to allow for exclusions hg.utils.UserInventory.getItemsByClass(itemsToGet, true, function(data) { let importData = { itemsArray: [], inventoryGold: gold }; data.forEach(function(arrayItem, index) { importData.itemsArray[index] = [arrayItem.item_id, arrayItem.quantity]; }); $('#import-data').val(JSON.stringify(importData)); document.forms["import-form"].submit(); }) } function onInvImportClick(){ $.dialog({ title: 'Export inventory to Markethunt', content: ` <form id="import-form" name="import-form" action="https://${markethuntDomain}/import_portfolio.php" method="post" target="_blank"> <label for="import-portfolio-name">Portfolio name: <span style="color: red">*</span></label> <input type="text" id="import-portfolio-name" name="import-portfolio-name" required pattern=".+"/> <input type="hidden" id="import-data" name="import-data"/> </form> <div id="export-dialog-buttons" class="jconfirm-buttons" style="float: none; margin-top: 10px;"><button type="button" class="btn btn-primary">Export</button></div>`, boxWidth: '600px', useBootstrap: false, closeIcon: true, draggable: true, onOpen: function(){ $('#import-portfolio-name').val('Portfolio ' + (new Date()).toISOString().substring(0, 10)); this.$content.find('button').click(function(){ submitInv(); }); } }); } /******************************* * * Final setup and add css * *******************************/ $(document).ready(function() { GM_addStyle(GM_getResourceText("jq_confirm_css")); GM_addStyle(GM_getResourceText("jq_toast_css")); GM_addStyle(marketplaceCssOverrides); GM_addStyle(journalCssOverrides); GM_addStyle(materialSwitchCss); addTouchPoint(); const supplyTransferJournalEntries = document.querySelectorAll("#journalContainer div.entry.supplytransferitem"); addJournalButtons(supplyTransferJournalEntries); });
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址