Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Dota 2 & CSGO Lounge item price displayer
- // @namespace http://www.enygma.ro
- // @version 3.0.2
- // @author Enygma
- // @description Displays an item's price information from the Steam Community Market and helps to copy an item's name.
- // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
- // @include /^http(s)?://(www.)?dota2lounge.com//
- // @include /^http(s)?://(www.)?csgolounge.com//
- // @updateURL https://github.com/Enygma2002/d2l-price-displayer/raw/master/Dota_2_Lounge_item_price_displayer.user.js
- // @downloadURL https://github.com/Enygma2002/d2l-price-displayer/raw/master/Dota_2_Lounge_item_price_displayer.user.js
- // @supportURL https://github.com/Enygma2002/d2l-price-displayer/issues
- // @contributionURL https://github.com/Enygma2002/d2l-price-displayer#donations
- // @contributionAmount $5.00 / Game / Items
- // @grant GM_xmlhttpRequest
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // ==/UserScript==
- // Determine on which site is the script being executed (dota2lounge or csgolounge)
- if (document.URL.match(/^http(s)?:\/\/(www.)?dota2lounge.com\//)) {
- // Dota2 app ID on Steam's community market website.
- appID = 570;
- // Generic item placeholder names used by the d2l website and not existing in the Steam Market.
- genericItemPlaceholderNames = ["Offers", "Any Common", "Any Uncommon", "Any Rare", "Any Mythical", "Any Legendary",
- "Any Ancient", "Any Immortal", "Real Money", "+ More", "Any Set"];
- } else if (document.URL.match(/^http(s)?:\/\/(www.)?csgolounge.com\//)) {
- // CS:GO app ID on Steam's community market website.
- appID = 730;
- // Generic item placeholder names used by the csgolounge website and not existing in the Steam Market.
- genericItemPlaceholderNames = ["Any Offers", "Real Money", "Dota Items", "TF2 Items"];
- }
- // A mapping of available currencies that the Steam Market supports and their individual features.
- var availableCurrencies = {
- "$" : { symbol: "USD", decimal: '.', id: 1 },
- "£" : { symbol: "GBP", decimal: '.', id: 2 },
- "€" : { symbol: "EUR", decimal: ',', id: 3 },
- "pуб." : { symbol: "RUB", decimal: ',', id: 5 },
- "R$" : { symbol: "BRL", decimal: ',', id: 7 },
- "¥" : { symbol: "JPY", decimal: '.', id: 8 },
- "kr" : { symbol: "NOK", decimal: ',', id: 9 },
- "Rp" : { symbol: "IDR", decimal: '.', id: 10 },
- "RM" : { symbol: "MYR", decimal: '.', id: 11 },
- "P" : { symbol: "PHP", decimal: '.', id: 12 },
- "S$" : { symbol: "SGD", decimal: '.', id: 13 },
- "฿" : { symbol: "THB", decimal: '.', id: 14 },
- "₫" : { symbol: "VND", decimal: '.', id: 15 },
- "₩" : { symbol: "KRW", decimal: '.', id: 16 },
- "TL" : { symbol: "TRY", decimal: ',', id: 17 },
- "₴" : { symbol: "UAH", decimal: ',', id: 18 },
- "Mex$" : { symbol: "MXN", decimal: '.', id: 19 },
- "CDN$" : { symbol: "CAD", decimal: '.', id: 20 },
- "A$" : { symbol: "AUD", decimal: '.', id: 21 },
- "NZ$" : { symbol: "NZD", decimal: '.', id: 22 }
- // Add more as Steam makes them available.
- };
- // Validate a currency, otherwise default to USD.
- var validateCurrency = function(currency) {
- var result = "$";
- if (availableCurrencies[currency]) {
- result = currency;
- }
- return result;
- };
- // Get the user selected currency, if available, otherwise (or if invalid) default on USD.
- var currentCurrency = validateCurrency(GM_getValue("currency"));
- // Registers the currency selection box in the top right of the website.
- var attachCurrencySelector = function() {
- var headerElement = document.querySelector('header');
- var currencySelector = document.createElement('div');
- currencySelector.setAttribute("class", "ddbtn currency");
- var currencySelectorHTML = "<div>";
- // Add the currently selected currency.
- var currentCurrencyInfo = availableCurrencies[currentCurrency];
- currencySelectorHTML += "<a id='" + currentCurrency + "'>" + currentCurrencyInfo.symbol + "</a>";
- // Add all the other available currencies to select from.
- var availableCurrencyCodes = Object.keys(availableCurrencies);
- for (var i=0; i<availableCurrencyCodes.length; i++) {
- var currencyCode = availableCurrencyCodes[i];
- // Skip the currently selected currency to avoid displaying it twice.
- if (currentCurrency === currencyCode) {
- continue;
- }
- var currencyInfo = availableCurrencies[currencyCode];
- currencySelectorHTML += "<a id='" + currencyCode + "'>" + currencyInfo.symbol + "</a>";
- }
- // Add the donations button.
- currencySelectorHTML += "<a id='donate' title='Show your appreciation for the script' ";
- currencySelectorHTML += "href='https://github.com/Enygma2002/d2l-price-displayer#donations' target='_blank'>Donate</a>";
- currencySelectorHTML += "</div>";
- currencySelector.innerHTML = currencySelectorHTML;
- headerElement.appendChild(currencySelector);
- // Attach click event listeners for all the available languages in the list.
- var availableCurrencyElements = currencySelector.querySelectorAll("a:not([id='donate'])");
- for (var i=0; i<availableCurrencyElements.length; i++) {
- var availableCurrencyElement = availableCurrencyElements[i];
- availableCurrencyElement.addEventListener('click', function(mouseEvent) {
- // Set the selected currency.
- var selectedCurrency = mouseEvent.target.id;
- setCurrentCurrency(selectedCurrency);
- // Refresh the list of available languages to reflect the new selection.
- headerElement.removeChild(currencySelector);
- attachCurrencySelector();
- // Refresh any already retrieved prices to use the newly selected currency.
- refreshExistingPrices();
- });
- }
- };
- attachCurrencySelector();
- // Set the new current currency and save it so that it is available next time we reload the page.
- var setCurrentCurrency = function(newCurrentCurrency) {
- // Make sure we set a valid currency.
- var validatedNewCurrency = validateCurrency(newCurrentCurrency);
- // Set the cached value used by the script.
- currentCurrency = validatedNewCurrency;
- // Save the new value.
- GM_setValue("currency", validatedNewCurrency);
- };
- // Refresh all the currently retrieved prices (that were hovered at some point) so that they are displayed consistenly
- // using the actual currentCurrency and not the currency that was active then they were first retrieved (hovered).
- var refreshExistingPrices = function() {
- var itemNameElements = document.querySelectorAll(".oitm > .name");
- for (var i=0; i<itemNameElements.length; i++) {
- var itemElement = itemNameElements[i].parentNode;
- if (itemElement.querySelector('.extraPanel')) {
- // Retrieve and override new values.
- getLowestPrice(itemElement, true);
- }
- }
- };
- // Main event listener for hovering items.
- document.addEventListener("mouseover", function (event) {
- var itemElement = getItemElement(event);
- if (!itemElement) {
- return;
- }
- attachExtraPanelAndListeners(itemElement);
- getLowestPrice(itemElement);
- });
- // Get the hovered item, if any.
- var getItemElement = function(mouseEvent) {
- var targetElement = mouseEvent.target;
- var itemElement = null;
- // Check the hovered element to see if it's an item element or one of it's children (image or rarity section).
- // Old d2l website layout that used directly the "item" display and within it the "name" panel;
- // It is now only present on csgolounge but we still want to support it in order to work with both sites.
- if (hasClass(targetElement, "item") && targetElement.querySelector(".name")) {
- // Hover the item display (contains image and rarity section)
- itemElement = targetElement;
- } else if (hasClass(targetElement.parentNode, "item") && targetElement.parentNode.querySelector(".name")) {
- // Hover an individual item display's children (image or rarity section)
- itemElement = targetElement.parentNode;
- } else
- // New d2l website layout, that uses an "oitm" container for the "item" display and the "name" panel
- if (hasClass(targetElement, "oitm")) {
- // Hover the main container (contains item display and item popup).
- itemElement = targetElement;
- } else if (hasClass(targetElement.parentNode, "oitm")) {
- // Hover the item display (contains image and rarity section)
- itemElement = targetElement.parentNode;
- } else if (hasClass(targetElement.parentNode, "item")) {
- // Hover an individual item display's children (image or rarity section)
- itemElement = targetElement.parentNode.parentNode;
- } else {
- // Hovered an uninteresting element of the UI.
- return null;
- }
- // Avoid returning empty item slots.
- var itemNameElement = itemElement.querySelector(".name");
- if (!itemNameElement) {
- return null;
- }
- // Avoid returning generic item placeholders.
- var itemName = getItemName(itemElement);
- if (genericItemPlaceholderNames.indexOf(itemName) > -1) {
- return null;
- }
- return itemElement;
- };
- // Add to the specified item element an extra panel that contains the price information and a click handler to facilitate copying the item's name
- var attachExtraPanelAndListeners = function(itemElement) {
- var itemNamePanel = itemElement.querySelector(".name");
- // If the extra panel already exists, stop here.
- var extraPanel = itemNamePanel.querySelector(".extraPanel");
- if (extraPanel) {
- return;
- }
- // Otherwise, create our own panel to append...
- extraPanel = document.createElement('div');
- extraPanel.innerHTML = "<span class='scriptStatus'>Ready</span>" +
- "<button type='button' class='extraButton refreshButton' title='Refresh'/>";
- extraPanel.setAttribute("class", "extraPanel");
- // ...and append it.
- // Note: In case we are in the "My Trades" view, make sure to insert before the remove button.
- var insertBeforeElement = itemNamePanel.querySelector("a.button");
- itemNamePanel.insertBefore(extraPanel, insertBeforeElement);
- // Set click event handler for the item's name panel so that the item name can be copied to the clipboard easier.
- itemNamePanel.addEventListener("click", copyItemNameHandler, false);
- // Set click event handler for the refresh button that re-fetches the item's price.
- var refreshButton = extraPanel.querySelector(".refreshButton");
- refreshButton.addEventListener("click", function(event) {
- event.stopPropagation();
- getLowestPrice(itemElement, true);
- }, false);
- };
- // Get the lowest price for an item from the Steam market.
- var getLowestPrice = function(itemElement, override) {
- var itemNameElement = itemElement.querySelector(".name");
- // Don`t try to get the price if we've already retrieved it.
- if (!override && itemNameElement.querySelector(".scriptStatus").innerHTML != "Ready") {
- return;
- }
- itemNameElement.querySelector(".scriptStatus").innerHTML = "Loading...";
- var itemName = getItemName(itemElement);
- var url = getSteamMarketListingsURL(itemName);
- GM_xmlhttpRequest({
- method: "GET",
- url: url,
- onload: function (response) {
- var jsonResponse = response.responseText;
- var priceObj = eval("(" + jsonResponse + ")");
- var result = "<span class='itemNotMarketable'>Not Marketable</span>";
- // Get only valid responses, since we can sometimes see JSONs with success: true and nothing else.
- if (priceObj.success && priceObj.lowest_price) {
- // Extra information to be displayed when hovering the price.
- var extraInfo = "Median price: " + (priceObj.median_price || "N/A") + "\n";
- extraInfo += "Daily transactions: " + (priceObj.volume || "N/A");
- // Determine the trend, if possible.
- var lowestPrice = getPriceValue(priceObj.lowest_price);
- var medianPrice = getPriceValue(priceObj.median_price);
- var trend = null;
- if (lowestPrice && medianPrice) {
- if (lowestPrice > medianPrice) {
- // Ascending trend.
- trend = "↗";
- } else if (lowestPrice < medianPrice) {
- // Descending trend.
- trend = "↘";
- } else {
- // Stagnating trend.
- trend = "→";
- }
- }
- // Determine price safety
- var priceSafety = getPriceSafety(priceObj.volume);
- // Build the result.
- result = "<span class='itemMarketable " + priceSafety + "' title='" + extraInfo + "'>";
- result += priceObj.lowest_price;
- if (trend) {
- result += " " + trend;
- }
- result += "</span>";
- }
- // Set the result.
- itemNameElement.querySelector(".scriptStatus").innerHTML = result;
- },
- onerror: function (response) {
- // Determine the error and build the result.
- var reason = response.statusText;
- var result = "<span class='error' title='" + reason + "'>Error</span>";
- // Set the result.
- itemNameElement.querySelector(".scriptStatus").innerHTML = result;
- }
- });
- };
- // Cached RegExps used by getPriceValue.
- var currencySymbolRegex = new RegExp("&#[0-9]+;");
- var numberSanitizingRegex = new RegExp("[^0-9]");
- // Extract the numeric value from a currency-value string combination.
- var getPriceValue = function(priceWithCurrency) {
- // Some prices are not available for certain items.
- // Example: Dota2's Voracious Greevil displays only lowest_price, nothing else.
- if (!priceWithCurrency) {
- return null;
- }
- // Sanitize the input string since it is html escaped in the returned JSON.
- priceWithCurrency = unescapeHtml(priceWithCurrency);
- // Strip out the currency symbol and any spaces.
- var priceString = priceWithCurrency.replace(currentCurrency, '').trim();
- // Strip out any thousand marker from the price, since parseFloat might get confused on some locales.
- var currencyInfo = availableCurrencies[currentCurrency];
- var decimalIndex = priceString.lastIndexOf(currencyInfo.decimal);
- var integerValue = priceString.substring(0, decimalIndex).replace(numberSanitizingRegex, '');
- var fractionalValue = priceString.substring(decimalIndex + 1);
- // When rebuilding the cleaned priceString, always use '.' since that is the only thing parseFloat understands.
- priceString = integerValue + '.' + fractionalValue;
- // Parse and return the value.
- return parseFloat(priceString);
- };
- // Helper method to unescape an escaped HTML string, such as what we get from the price JSON.
- var unescapeHtml = function(escapedStr) {
- var div = document.createElement("div");
- div.innerHTML = escapedStr;
- var child = div.childNodes[0];
- return child ? child.nodeValue : "";
- };
- // Cached RegExp used in getPriceSafety.
- var thousandSeparatorRegex = new RegExp("[,.]");
- /*
- * If price volume information is available (nr. of daily transactions), we determine price "safety" based on how many
- * transactions are for the item. Few transactions (<= 10) means the item is not popular and it will be harder to get rid of.
- *
- * If no volume information exists, the default is "priceUnsafe".
- */
- var getPriceSafety = function(priceVolumeString) {
- var result = "priceUnsafe";
- if (priceVolumeString) {
- // Make sure to clean the volume string of any thousand separator that can cause problems in the conversion.
- var dailyTransactions = parseInt(priceVolumeString.replace(thousandSeparatorRegex, ""));
- // This is where we actually determine the "safety" of a price.
- if (dailyTransactions <= 10) {
- result = "priceUnsafe";
- } else if (dailyTransactions <= 50) {
- result = "priceWarning";
- } else {
- result = "priceSafe";
- }
- }
- return result;
- };
- // Computes the URL used to access the Steam market listings for a given item.
- var getSteamMarketListingsURL = function(itemName) {
- var itemNameEncoded = encodeURIComponent(itemName);
- var currencyInfo = availableCurrencies[currentCurrency];
- var url = "http://steamcommunity.com/market/priceoverview/?appid=" + (appID);
- url += "&market_hash_name=" + itemNameEncoded;
- url += "¤cy=" + currencyInfo.id;
- return url;
- };
- // Extract the item's name from a DOM item element.
- var getItemName = function(itemElement) {
- var itemNameElement = itemElement.querySelector(".name");
- var itemName = itemNameElement.querySelector("b").innerHTML.trim();
- return itemName;
- };
- // Event handler to facilitate copying an item's name.
- var copyItemNameHandler = function(event) {
- var clickedElement = event.target;
- // Avoid executing this handler if the "Remove item" button is clicked in a trade.
- if (excludedTags.indexOf(clickedElement.tagName) > -1 || excludedTags.indexOf(clickedElement.parentNode.tagName) > -1) {
- return;
- }
- // Stop the element's parent (item) from getting the click event. This stops the item from being selected.
- event.stopPropagation();
- // Make sure we select the item name element.
- var itemNameElement = clickedElement;
- while (!hasClass(itemNameElement, "name")) {
- itemNameElement = itemNameElement.parentNode;
- }
- // Get and display the item's name.
- var itemName = itemNameElement.querySelector("b").innerHTML.trim();
- window.prompt("Press CTRL+C to copy the item's name:", itemName);
- };
- // Tags that, if clicked on in an item name panel, should not execute the copyItemNameHandler.
- var excludedTags = ["A", "IMG"];
- // Helper method to check if an element has the specified class name.
- var hasClass = function(element, cls) {
- return element && (" " + element.className + " ").indexOf(" " + cls + " ") > -1;
- };
- // Style.
- // The two websites currently have diferent styles, so we need to tweak a bit the price colors to make them properly readable.
- var priceSafeColor;
- var priceWarningColor;
- if (appID == 570) {
- // D2L - lighter colors to go with the darker name panel background.
- priceSafeColor = "#8EC13E"; // same used on the "Market" link
- priceWarningColor = "orange";
- } else {
- // CSGOLounge - darker colors to contrast with the lighter name panel background.
- priceSafeColor = "#32D732";
- priceWarningColor = "#FF1919";
- }
- GM_addStyle(".currency a { color: white; font-weight: bold; margin-top: 0.25em; }");
- GM_addStyle(".priceSafe { color: " + priceSafeColor + " }");
- GM_addStyle(".priceWarning { color : " + priceWarningColor + " }");
- GM_addStyle(".priceUnsafe { color : red }");
- GM_addStyle(".itemNotMarketable { color : red }");
- GM_addStyle(".error { color : red }");
- GM_addStyle(".extraButton { margin-left: 0.3em; vertical-align: top; margin-top: -0.1em; border: 0; padding: 0; width: 16px; height: 16px; }");
- GM_addStyle(".refreshButton { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABOUlEQVQ4jc2RsUoDQRCGv32CXECzdjaWRiOCVSA+RdqAL6BFesUXOPUFbCWKJ2thkcRgxCa3cJUEQuCwExRjCi1sxiKXsElO6wz81e58888/sPhlESwxlhNaeP/+zRnO/wCMNaBDIbVZG/ztppLcLYdpgK3uSFgGc05WAnbX7pTcD5FCQ8lyMDOlQ4mQaO8lcRI6Q7wATxsGR32k9YUc9RFtiL1gZsoTq1jk7D3JxLEeFNtKLj6ZqNhWkppHSOvxO3GRFlb3J3mc2VEb/I2mktM3Jtp5UKINgUuProYJoMO+C8jWyGhDXO0hl0Ok2hutma2RcR1UsMjx6ySoA9fJkqGUryu5+UDydSW5azbn1wiJyjFSjp3bO4lrg19opJzacZEhJMIi688juYBkFT+9eRpUGYOmbr6Q9QvwBrFqSdh8NgAAAABJRU5ErkJggg==) no-repeat left center; }");
- GM_addStyle(".ddbtn a#donate { font-size: 0.7em; margin-top: 1.5em; color: orange; }");
- GM_addStyle(".ddbtn a#donate:before { color: red; content: '\u2665'; display: block; font-size: 1.5em; margin-bottom: 0.2em; text-align: center; text-shadow: 0 1px 1px #000; }");
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement