您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Manage your CS2 storage units and inspect items
// ==UserScript== // @name Counter-Strike 2 Script // @namespace https://github.com/Citrinate // @author Citrinate // @description Manage your CS2 storage units and inspect items // @license Apache-2.0 // @version 1.0.0.2 // @match https://steamcommunity.com/id/*/inventory* // @match https://steamcommunity.com/profiles/*/inventory* // @match https://steamcommunity.com/market/listings/* // @connect localhost // @connect 127.0.0.1 // @connect * // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant unsafeWindow // @homepageURL https://github.com/Citrinate/CS2Script // @supportURL https://github.com/Citrinate/CS2Script/issues // ==/UserScript== (() => { // src/core/settings.js var SETTING_ASF_SERVER = "SETTING_ASF_SERVER"; var SETTING_ASF_PORT = "SETTING_ASF_PORT"; var SETTING_ASF_PASSWORD = "SETTING_ASF_PASSWORD"; var SETTING_INSPECT_ITEMS = "SETTING_INSPECT_ITEMS"; var SETTING_INSPECT_CACHE_TIME_HOURS = "SETTING_INSPECT_CACHE_TIME_HOURS"; var SETTING_INTERFACE_AUTOSTOP_MINUTES = "SETTING_INTERFACE_AUTOSTOP_MINUTES"; var DEFAULT_SETTINGS = { SETTING_ASF_SERVER: "http://localhost", SETTING_ASF_PORT: "1242", SETTING_ASF_PASSWORD: "", SETTING_INSPECT_ITEMS: true, SETTING_INSPECT_CACHE_TIME_HOURS: -1, SETTING_INTERFACE_AUTOSTOP_MINUTES: 15 }; function GetSetting(name) { return GM_getValue(name, DEFAULT_SETTINGS[name]); } function SetSetting(name, value) { GM_setValue(name, value); } // src/core/asf.js var asf_default = { Send: async function(operation, path, http_method, bot, data) { let payload = null; let parameters = ""; if (data) { if (http_method === "GET") { parameters = "?" + new URLSearchParams(data).toString(); } else if (http_method === "POST") { payload = JSON.stringify(data); } } const xhrResponse = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ url: `${GetSetting(SETTING_ASF_SERVER)}:${GetSetting(SETTING_ASF_PORT)}/Api/${operation}/${bot}/${path}${parameters}`, method: http_method, data: payload, responseType: "json", headers: { "Accept": "application/json", "Content-Type": "application/json", "Authentication": GetSetting(SETTING_ASF_PASSWORD) }, onload: (response) => { if (typeof response.response === "string") { try { response.response = JSON.parse(response.response); } catch { ; } } resolve(response); }, onerror: (e) => { const error = new Error(`(${e.status}) Request error from /Api/${operation}/${path}`); error.code = e.status; reject(error); }, ontimeout: (e) => { const error = new Error(`(${e.status}) Request timed out on /Api/${operation}/${path}`); error.code = e.status; reject(error); } }); }); if (xhrResponse.status === 401) { const error = new Error(`(401) Missing or incorrect ASF IPC password. Please check your settings and verify your ASF IPC password.`); error.code = xhrResponse.status; error.response = xhrResponse.response; throw error; } if (xhrResponse.status === 403) { let errorMessage; if (!GetSetting(SETTING_ASF_SERVER).includes("127.0.0.1") && !GetSetting(SETTING_ASF_SERVER).toLowerCase().includes("localhost") && !GetSetting(SETTING_ASF_PASSWORD)) { errorMessage = "(403) You must use an ASF IPC password when connecting to ASF remotely."; } else { errorMessage = "(403) The ASF IPC password you entered was incorrect. Please wait or restart ASF, and then try again."; } const error = new Error(errorMessage); error.code = xhrResponse.status; error.response = xhrResponse.response; throw error; } if (!xhrResponse.response || xhrResponse.status !== 200) { let errorMessage = `(${xhrResponse.status}) ASF request error from /Api/${operation}/${path}`; if (xhrResponse.response?.Message) { errorMessage += `: ${xhrResponse.response?.Message}`; } else if (xhrResponse.status >= 500) { errorMessage += `: Please check your ASF logs for errors`; } const error = new Error(errorMessage); error.code = xhrResponse.status; error.response = xhrResponse.response; throw error; } if (!xhrResponse.response.Success) { let errorMessage = `(${xhrResponse.status}) ASF response error from /Api/${operation}/${path}`; if (xhrResponse.response.Message) { errorMessage += `: ${xhrResponse.response.Message}`; } const error = new Error(errorMessage); error.code = xhrResponse.status; error.response = xhrResponse.response; throw error; } return xhrResponse.response.Result ?? xhrResponse.response; }, GetBot: async function(steamID, includePluginStatus = true) { if (steamID === false) { return; } const bots = await this.Send("Bot", "", "GET", "ASF"); let pluginStatus; if (includePluginStatus) { pluginStatus = await this.GetPluginStatus(); } const mergedBots = Object.fromEntries( Object.entries(bots).map(([key, value]) => [ key, { ASF: value, Plugin: pluginStatus?.[key] } ]) ); if (steamID) { return Object.values(mergedBots).find((bot) => bot.ASF.SteamID == steamID); } return mergedBots; }, GetPluginStatus: async function(botName) { const bots = await this.Send("CS2Interface", "Status", "GET", "ASF", { "refreshAutoStop": "true" }); if (botName) { return bots[botName]; } return bots; } }; // src/cs2/constants.js var CS2_APPID = 730; var INVENTORY_ITEM_LIMIT = 1e3; var STORAGE_UNIT_ITEM_LIMIT = 1e3; var STICKER_MAX_COUNT = 5; var KEYCHAIN_MAX_COUNT = 1; var SEED_RANGE = { min: 0, max: 1e5 }; var FLOAT_RANGE = { min: 0, max: 1 }; var WEARS = [ { min: 0, max: 0.07, name: "FN", nameLong: "Factory New" }, { min: 0.07, max: 0.15, name: "MW", nameLong: "Minimum Wear" }, { min: 0.15, max: 0.38, name: "FT", nameLong: "Field-Tested" }, { min: 0.38, max: 0.45, name: "WW", nameLong: "Well-Worn" }, { min: 0.45, max: 1, name: "BS", nameLong: "Battle-Scarred" } ]; // src/utils/cache.js var Cache = class { static #dbName = "cs2_script"; static #storeName = "kvp"; static #version = 1; static #initPromise = null; static #keyName = "DB_ENCRYPTION_KEY"; static #key = null; static async #Init() { if (this.#initPromise) { return this.#initPromise; } this.#initPromise = new Promise((resolve, reject) => { const request = indexedDB.open(this.#dbName, this.#version); request.onupgradeneeded = () => { const db = request.result; if (!db.objectStoreNames.contains(this.#storeName)) { db.createObjectStore(this.#storeName); } }; request.onsuccess = async () => { const db = request.result; const testKey = "cache_validity_test"; const testValue = 42; const storedKey = GM_getValue(this.#keyName, null); if (storedKey) { try { const raw = Uint8Array.from(atob(storedKey), (c) => c.charCodeAt(0)); this.#key = await crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]); await this.#Encrypt({ test: true }); if (await this.#Get(db, testKey) !== testValue) { throw new Error("Cache failed to validate"); } } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e, new Error("Clearing cache")); this.#key = null; } } if (!this.#key) { const rawKey = crypto.getRandomValues(new Uint8Array(32)); this.#key = await crypto.subtle.importKey("raw", rawKey, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]); GM_setValue(this.#keyName, btoa(String.fromCharCode(...rawKey))); await this.#Clear(db); } await this.#Set(db, testKey, testValue); resolve(db); }; request.onerror = () => { reject(request.error); }; }); return this.#initPromise; } static async #Encrypt(data) { const encoded = new TextEncoder().encode(JSON.stringify(data)); const iv = crypto.getRandomValues(new Uint8Array(12)); const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, this.#key, encoded); const combined = new Uint8Array(iv.byteLength + ciphertext.byteLength); combined.set(iv, 0); combined.set(new Uint8Array(ciphertext), iv.byteLength); return combined.buffer; } static async #decrypt(buffer) { const combined = new Uint8Array(buffer); const iv = combined.slice(0, 12); const ciphertext = combined.slice(12); const encoded = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, this.#key, ciphertext); return JSON.parse(new TextDecoder().decode(encoded)); } static async #Clear(db) { return new Promise((resolve, reject) => { const tx = db.transaction(this.#storeName, "readwrite"); const store = tx.objectStore(this.#storeName); const req = store.clear(); req.onsuccess = () => { resolve(); }; req.onerror = () => { reject(req.error); }; }); } static async #Get(db, key, defaultValue = null) { return new Promise((resolve, reject) => { const tx = db.transaction(this.#storeName, "readonly"); const store = tx.objectStore(this.#storeName); const req = store.get(key); req.onsuccess = async () => { if (!req.result) { resolve(defaultValue); return; } try { resolve(await this.#decrypt(req.result)); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); resolve(defaultValue); } }; req.onerror = () => { reject(req.error); }; }); } static async #Set(db, key, value) { const encrypted = await this.#Encrypt(value); return new Promise((resolve, reject) => { const tx = db.transaction(this.#storeName, "readwrite"); const store = tx.objectStore(this.#storeName); const req = store.put(encrypted, key); req.onsuccess = () => { resolve(); }; req.onerror = () => { reject(req.error); }; }); } static async GetValue(key, defaultValue = null) { return this.#Get(await this.#Init(), key, defaultValue); } static async SetValue(key, value) { return this.#Set(await this.#Init(), key, value); } }; // src/utils/helpers.js function Request(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.onload = function() { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.responseText); } else { reject(new Error(`Request failed with status: ${xhr.status}`)); } }; xhr.onerror = function() { reject(new Error("Network error occurred")); }; xhr.ontimeout = function() { reject(new Error("Request timed out")); }; xhr.send(); }); } function CreateElement(tag, options) { const el = document.createElement(tag); if (options) { for (const [key, value] of Object.entries(options)) { if (key === "class") { el.className = value; } else if (key === "html") { el.innerHTML = value; } else if (key === "text") { el.innerText = value; } else if (key === "hide" && value) { el.hide(); } else if (key === "style") { Object.assign(el.style, value); } else if (key === "vars") { for (const [varName, varValue] of Object.entries(value)) { el.style.setProperty(`--${varName}`, varValue); } } else if (key === "dataset") { Object.assign(el.dataset, value); } else if (key === "disabled") { el.disabled = value; } else if (key === "selected") { el.selected = value; } else if (key.startsWith("on") && typeof value === "function") { el.addEventListener(key.slice(2).toLowerCase(), value); } else if (key === "htmlChildren") { for (const child of value) { if (!child) { continue; } el.insertAdjacentHTML("beforeend", child); } } else if (key === "children") { for (const child of value) { if (!child) { continue; } el.append(child instanceof Node ? child : document.createTextNode(child)); } } else { el.setAttribute(key, value); } } } return el; } function CreateCachedAsyncFunction(asyncFunction) { let cache = null; let inProgress = null; const wrapped = async () => { if (cache !== null) { return cache; } if (inProgress) { return inProgress; } inProgress = asyncFunction().then((result) => { cache = result; return result; }); return inProgress; }; wrapped.willReturnImmediately = () => { return cache !== null; }; return wrapped; } function BindTooltip(element, text) { if (element.unbindTooltip) { element.unbindTooltip(); } const tooltip = CreateElement("div", { class: "cs2s_tooltip", text }); let fadeOutAnimation = null; element.classList.add(`cs2s_has_tooltip`); function onMouseEnter() { if (fadeOutAnimation) { fadeOutAnimation.cancel(); fadeOutAnimation = null; } const rect = element.getBoundingClientRect(); document.body.appendChild(tooltip); void tooltip.offsetWidth; tooltip.style.top = `${rect.bottom + window.scrollY + 8}px`; tooltip.style.left = `${rect.left + window.scrollX + rect.width / 2 - tooltip.offsetWidth / 2}px`; Fade(tooltip, { from: 0, to: 1, duration: 200 }); } function onMouseLeave() { fadeOutAnimation = Fade(tooltip, { from: 1, to: 0, duration: 200, onfinish: () => { tooltip.isConnected && tooltip.remove(); } }); } element.addEventListener("mouseenter", onMouseEnter); element.addEventListener("mouseleave", onMouseLeave); element.unbindTooltip = () => { element.removeEventListener("mouseenter", onMouseEnter); element.removeEventListener("mouseleave", onMouseLeave); element.classList.remove(`cs2s_has_tooltip`); element.unbindTooltip = null; tooltip.isConnected && tooltip.remove(); }; return tooltip; } function Fade(element, options) { const to = options.to ?? 1; if (typeof options.from !== "undefined") { element.style.opacity = options.from; } const animation = element.animate({ opacity: to }, { duration: options.duration ?? 250, easing: "ease", fill: "forwards" }); if (typeof options.onfinish === "function") { animation.onfinish = () => { options.onfinish(); }; } return animation; } function Sleep(milliseconds) { if (milliseconds <= 0) { return Promise.resolve(); } return new Promise((resolve) => { setTimeout(resolve, milliseconds); }); } function Random(min, max) { return Math.random() * (max - min) + min; } // src/cs2/items/inventory.js var Inventory = class _Inventory { items; storedItems; loadedFromCache; static iconURLsCacheID = "icon_urls"; static iconURLs = null; constructor(items, loadedFromCache = false) { this.items = items; this.storedItems = []; this.loadedFromCache = loadedFromCache; } async LoadCrateContents(progressCallback) { _Inventory.iconURLs = await Cache.GetValue(_Inventory.iconURLsCacheID, {}); let cratesOpened = 0; const numCrates = this.items.filter((item) => item.iteminfo.def_index === 1201).length; progressCallback(`Loading Storage Unit Contents (${cratesOpened}/${numCrates})`, cratesOpened / numCrates); for (const item of this.items) { if (item.iteminfo.def_index == 1201) { const storedItems = await this.#OpenCrate(item); if (storedItems) { this.storedItems = this.storedItems.concat(storedItems); } cratesOpened++; progressCallback(`Loading Storage Unit Contents (${cratesOpened}/${numCrates})`, cratesOpened / numCrates); } if (!_Inventory.iconURLs[item.full_name]) { const asset = unsafeWindow.g_rgAppContextData[CS2_APPID].rgContexts[2].inventory.m_rgAssets[item.iteminfo.id]; if (asset) { _Inventory.iconURLs[item.full_name] = asset.description.icon_url; } } } Cache.SetValue(_Inventory.iconURLsCacheID, _Inventory.iconURLs); const itemsToGetIconsFor = /* @__PURE__ */ new Set(); const commodityItemsToGetIconsFor = /* @__PURE__ */ new Set(); for (const item of [...this.items, ...this.storedItems]) { item.id = item.iteminfo.id; item.name = !!item.wear_name && item.full_name.includes(item.wear_name) ? item.full_name.slice(0, -(item.wear_name.length + 3)) : item.full_name; item.name_normalized = item.name.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase(); item.collection_name = item.set_name ?? item.crate_name?.replace(/( Autograph Capsule)$/, " Autographs").replace(/( Capsule)$/, ""); item.collection = item.collection_name?.replace(/^(The )/, "").replace(/( Collection)$/, "").replace(/^(Operation )/, "").replace(/( Autographs)$/, ""); item.rarity = item.collection || item.iteminfo.rarity > 1 ? item.iteminfo.rarity : void 0; item.seed = item.attributes["set item texture seed"] ? Math.floor(item.attributes["set item texture seed"]) : item.attributes["keychain slot 0 seed"]; if (item.iteminfo.quality == 3) { item.quality = 3 + Number(item.stattrak === true); } else if (item.iteminfo.quality == 12) { item.quality = 2; } else { item.quality = Number(item.stattrak === true) * 2; } if (item.casket_id) { item.casket_name = this.items.find((x) => x.iteminfo.id == item.casket_id)?.attributes["custom name attr"] ?? item.casket_id; } if (item.stickers) { for (const sticker of Object.values(item.stickers)) { if (_Inventory.iconURLs[sticker.full_name] || _Inventory.iconURLs[sticker.full_name] === null) { continue; } commodityItemsToGetIconsFor.add(sticker.full_name); } } if (item.keychains) { for (const keychain of Object.values(item.keychains)) { if (_Inventory.iconURLs[keychain.full_name] || _Inventory.iconURLs[keychain.full_name] === null) { continue; } itemsToGetIconsFor.add(keychain.full_name); } } if (!item.moveable || _Inventory.iconURLs[item.full_name] || _Inventory.iconURLs[item.full_name] === null) { continue; } if (item.commodity) { commodityItemsToGetIconsFor.add(item.full_name); } else { itemsToGetIconsFor.add(item.full_name); } } await this.#FetchCommodityIcons(commodityItemsToGetIconsFor, progressCallback); await this.#FetchIcons(/* @__PURE__ */ new Set([...itemsToGetIconsFor, ...commodityItemsToGetIconsFor]), progressCallback); } async #OpenCrate(item) { if (item.iteminfo.def_index != 1201) { return; } const assetID = item.iteminfo.id; const attributes = item.attributes; const cache_id = `crate_${assetID}`; const cache = await Cache.GetValue(cache_id, null); if (cache) { if (this.loadedFromCache || cache.attributes["modification date"] == attributes["modification date"]) { return cache.items; } } if (this.loadedFromCache) { const error2 = new Error(`Failed to load crate ${assetID} from cache`); error2.OPERATION_ERROR = OPERATION_ERROR.INTERFACE_NOT_CONNECTED; throw error2; } for (let attempt = 0; attempt < 3; attempt++) { try { const storedItems = await asf_default.Send("CS2Interface", `GetCrateContents/${assetID}`, "GET", script_default.Bot.ASF.BotName); if (!storedItems) { break; } const crate = { attributes, items: storedItems }; Cache.SetValue(cache_id, crate); await Sleep(2e3); return storedItems; } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, e); if (e.code === 504) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, new Error("Timed out while opening storage unit, reconnecting to interface")); if (!await script_default.RestartInterface({ showProgress: false, errorLevel: ERROR_LEVEL.LOW })) { break; } } } } const error = new Error(`Failed to open crate ${assetID}`); error.OPERATION_ERROR = OPERATION_ERROR.INVENTORY_FAILED_TO_LOAD; throw error; } async #FetchIcons(hashes, progressCallback) { if (hashes.size == 0) { return; } const iconsToFetch = hashes.size; let iconsFetched = 0; progressCallback("Fetching Item Icons", 0); for (const hash of hashes) { let success = false; const url = `${window.location.origin}/market/listings/${CS2_APPID}/${encodeURIComponent(hash)}`; for (let attempt = 0; attempt < 5; attempt++) { try { const listingPage = await Request(url); if (listingPage.includes("g_rgAssets = []")) { if (!listingPage.includes("Market_LoadOrderSpread")) { success = true; _Inventory.iconURLs[hash] = null; hashes.delete(hash); Cache.SetValue(_Inventory.iconURLsCacheID, _Inventory.iconURLs); continue; } await Sleep(Random(1e3, 2e3)); continue; } const matches = listingPage.match(/g_rgAssets\s*=\s*({.*?});/); if (!matches) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, new Error(`Failed to find g_rgAssets at ${url}`)); console.log(listingPage); return; } if (matches.length > 1) { let assets; try { assets = JSON.parse(matches[1]); } catch { script_default.ShowError({ level: ERROR_LEVEL.LOW }, new Error(`Failed to parse g_rgAssets at ${url}`)); console.log(matches); return; } const asset = Object.values(assets?.[CS2_APPID]?.[2] ?? assets?.[CS2_APPID]?.[0] ?? {})?.[0]; if (asset?.icon_url) { success = true; _Inventory.iconURLs[hash] = asset?.icon_url; hashes.delete(hash); Cache.SetValue(_Inventory.iconURLsCacheID, _Inventory.iconURLs); } } break; } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, e); await Sleep(Random(1e3, 2e3)); } } if (!success) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, new Error(`Failed to get item icon at ${url}`)); } iconsFetched++; progressCallback(`Fetching Item Icons (${iconsFetched}/${iconsToFetch})`, iconsFetched / iconsToFetch); } } async #FetchCommodityIcons(hashes, progressCallback) { if (hashes.size == 0) { return; } const itemLimit = 25; const chunkedHashes = Array.from( { length: Math.ceil(hashes.size / itemLimit) }, (_, index) => [...hashes].slice(index * itemLimit, (index + 1) * itemLimit) ); const iconsToFetch = hashes.size; let iconsFetched = 0; progressCallback("Fetching Commodity Item Icons", 0); for (const chunk of chunkedHashes) { const query = new URLSearchParams({ appid: String(CS2_APPID), contextid: "2" }); for (const hash of chunk) { query.append("items[]", hash); } const url = `${window.location.origin}/market/multisell?${query.toString()}`; let success = false; for (let attempt = 0; attempt < 5; attempt++) { try { const multiSellPage = await Request(url); if (multiSellPage.includes("error_ctn")) { continue; } const matches = multiSellPage.match(/g_rgAssets\s*=\s*({.*?});/); if (!matches) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, new Error(`Failed to find g_rgAssets at ${url}`)); console.log(multiSellPage); return; } if (matches.length > 1) { let assets; try { assets = JSON.parse(matches[1]); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, e, new Error(`Failed to parse g_rgAssets at ${url}`)); console.log(matches); return; } for (const asset of Object.values(assets?.[CS2_APPID]?.[2])) { for (const hash of chunk) { if (asset?.description?.market_hash_name == hash && asset?.description?.icon_url) { _Inventory.iconURLs[hash] = asset.description.icon_url; hashes.delete(hash); break; } } } success = true; Cache.SetValue(_Inventory.iconURLsCacheID, _Inventory.iconURLs); } break; } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, e); await Sleep(Random(1e3, 2e3)); } } if (!success) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, new Error(`Failed to get item icons at ${url}`)); } iconsFetched += chunk.length; progressCallback(`Fetching Commodity Item Icons (${iconsFetched}/${iconsToFetch})`, iconsFetched / iconsToFetch); } } async StoreItem(asset, crateAsset) { await asf_default.Send("CS2Interface", `StoreItem/${crateAsset.iteminfo.id}/${asset.iteminfo.id}`, "GET", script_default.Bot.ASF.BotName); } async RetrieveItem(asset) { await asf_default.Send("CS2Interface", `RetrieveItem/${asset.casket_id}/${asset.iteminfo.id}`, "GET", script_default.Bot.ASF.BotName); } }; // src/components/popup.js var Popup = class _Popup { static #numPopups = 0; #popoverMode; #onopen; #onclose; #fade; #popupContainer; #background; #visible = false; constructor(options) { this.#popoverMode = options.popoverMode ?? false; this.#onopen = options.onopen ?? false; this.#onclose = options.onclose ?? false; this.#fade = options.fade ?? true; _Popup.#numPopups++; const title = CreateElement("div", { class: "cs2s_popup_title", text: options.title ?? "", children: options.titleChildren ?? [] }); const simpleMode = options.simpleMode ?? false; const disableClose = options.disableClose ?? false; const closeButton = !simpleMode && CreateElement("div", { class: "cs2s_popup_close_button", onclick: () => { this.Hide(); } }); const popupBody = CreateElement("div", { class: `cs2s_popup_body`, children: [ !disableClose && closeButton, title, ...options.body ?? [] ] }); if (simpleMode) { popupBody.classList.add("cs2s_popup_body_simple"); } if (this.#popoverMode) { popupBody.classList.add("cs2s_popup_body_popover"); } this.#background = CreateElement("div", { class: "cs2s_popup_background", style: { zIndex: 1e3 + _Popup.#numPopups } }); this.#popupContainer = CreateElement("div", { class: "cs2s_popup_container", style: { zIndex: 1e3 + _Popup.#numPopups }, children: [ popupBody ] }); if (!disableClose) { this.#popupContainer.addEventListener("dblclick", (event) => { const box = popupBody.getBoundingClientRect(); const style = getComputedStyle(popupBody); if (event.clientY < box.top || event.clientX < box.left || event.clientY > box.bottom + parseInt(style.marginBottom) || event.clientX > box.right) { this.Hide(); } }); document.addEventListener("keydown", (event) => { if (!this.#visible) { return; } if (event.key === "Escape") { event.preventDefault(); this.Hide(); } }); } } Show() { if (this.#visible) { return; } this.#visible = true; if (typeof this.#onopen === "function") { this.#onopen(); } unsafeWindow.document.body.append(this.#background, this.#popupContainer); if (!this.#popoverMode) { if (this.#fade) { Fade(this.#background, { from: 0, to: getComputedStyle(this.#background).opacity }); } unsafeWindow.document.body.classList.add("cs2s_popup_opened"); } } Hide() { if (!this.#visible) { return; } this.#visible = false; if (typeof this.#onclose === "function") { this.#onclose(); } if (this.#fade) { Fade(this.#background, { from: getComputedStyle(this.#background).opacity, to: 0, onfinish: () => { this.#background.isConnected && this.#background.remove(); } }); } else { this.#background.isConnected && this.#background.remove(); } this.#popupContainer.isConnected && this.#popupContainer.remove(); if (!this.#popoverMode) { unsafeWindow.document.body.classList.remove("cs2s_popup_opened"); } } }; // src/core/script.js var OPERATION_ERROR = { INTERFACE_NOT_CONNECTED: 0, INVENTORY_FAILED_TO_LOAD: 1 }; var ERROR_LEVEL = { HIGH: 0, MEDIUM: 1, LOW: 2 }; var Script = class { Bot; AccountsConnected = 0; #inventory = null; #statusUpdateListeners = []; #navigationButton; #navigationStatus; #navigationMenu; #errorTableBody; constructor() { const globalNavigation = unsafeWindow.document.getElementById(`account_dropdown`); if (!globalNavigation) { return; } this.#navigationStatus = CreateElement("span", { class: "account_name", text: "???" }); this.#navigationButton = CreateElement("span", { class: "popup_menu_item cs2s_navigation_popup_menu_item", onmouseenter: () => { this.#ShowNavigationMenu(); }, onmouseleave: () => { this.#HideNavigationMenu(); }, children: [ CreateElement("span", { html: ( /*html*/ ` <span class="cs2s_navigation_icon"> <svg width="173.27321" height="42.757812" viewBox="0 0 173.27321 42.757812" fill="currentColor" preserveAspectRatio="xMinYMin"> <path d="m 79.808179,0 c -6.1207,1e-7 -11.646256,3.6370293 -14.035156,9.2363278 l -1.595704,3.7402352 -1.140625,2.667969 c -2.1334,4.9951 1.555679,10.533203 7.017579,10.533203 h 2.800781 22.875 l -2.935547,6.835937 H 58.10896 c -1.5238,0 -2.898,0.901969 -3.5,2.292969 l -3.222656,7.451172 h 39.164062 c 6.105704,0 11.625494,-3.621172 14.021494,-9.201172 l 2.87109,-6.683594 c 2.147,-4.9966 -1.54372,-10.548828 -7.01172,-10.548828 H 74.780835 l 1.884766,-4.402344 -4.792969,-2.3105472 h 40.464848 c 1.528,0 2.91081,-0.906287 3.50781,-2.304687 L 118.97029,0 Z M 24.497632,0.00195 C 18.410132,4.7e-4 12.905279,3.5995237 10.495679,9.1542936 L 0.6167727,32.216794 c -2.139226,4.9966 1.5490919,10.541016 7.0136719,10.541016 H 39.798413 c 1.5267,0 2.904906,-0.905381 3.503906,-2.300781 l 3.197266,-7.441404 H 12.644116 L 21.696851,11.923828 16.780835,9.6152348 h 37.253906 c 1.5267,0 2.904907,-0.905482 3.503906,-2.300782 L 60.679273,0.0058594 Z M 127.8824,0.00976 123.79451,9.6191351 h 37.17188 l -2.85157,6.7109369 h -27.21289 c -6.0365,0 -11.49792,3.620914 -13.86914,9.197266 l -0.0742,0.175781 -7.24804,17.052735 h 49.26758 l 1.89843,-4.466797 v -0.002 l 0.0742,-0.173828 v -0.01367 c 0.88057,-2.42168 -0.94013,-5.083985 -3.54101,-5.083985 h -31.46289 l 2.90625,-6.835937 h 32.1914 0.15039 0.01 c 2.95431,-0.06268 5.61224,-1.859402 6.77539,-4.597656 l 0.0742,-0.175782 4.61328,-10.851562 C 174.77935,5.5862411 171.10481,0.0097657 165.73201,0.0097657 Z" /> </svg> </span> ` ) }), "CS2Script: ", this.#navigationStatus ] }); globalNavigation.children[0].append(this.#navigationButton); GM_registerMenuCommand("Set ASF IPC Password", () => { const password = prompt("Enter ASF IPC Password", GetSetting(SETTING_ASF_PASSWORD)); if (password !== null) { SetSetting(SETTING_ASF_PASSWORD, password); window.location.reload(); } }); } async #UpdateConnectionStatus() { try { const status = await asf_default.GetBot(); if (this.Bot) { const oldBot = this.Bot; this.Bot = status[this.Bot.ASF.BotName]; for (const listener of this.#statusUpdateListeners) { listener(this.Bot, oldBot); } } const currentAccountConnected = this.Bot && status[this.Bot.ASF.BotName].Plugin?.Connected; const numOtherAccountsConnected = Object.values(status).filter((x) => x.Plugin?.Connected).length - currentAccountConnected; const numOtherAccounts = Object.values(status).length - Number(this.Bot !== null); this.AccountsConnected = currentAccountConnected ? numOtherAccountsConnected + 1 : numOtherAccountsConnected; if (!this.#navigationStatus.tooltip) { this.#navigationStatus.tooltip = BindTooltip(this.#navigationStatus, ""); } this.#navigationStatus.innerText = currentAccountConnected ? "1" : "0"; this.#navigationStatus.tooltip.innerHTML = "Interface status for this account: "; if (currentAccountConnected) { this.#navigationStatus.tooltip.innerHTML += "<strong>Connected</strong>"; } else { this.#navigationStatus.tooltip.innerHTML += "<strong>Not Connected</strong>"; } if (numOtherAccounts > 0) { this.#navigationStatus.innerText += ` + ${numOtherAccountsConnected}`; this.#navigationStatus.tooltip.innerHTML += `<br>Interface is connected on <strong>${numOtherAccountsConnected}/${numOtherAccounts}</strong> other accounts`; } } catch (e) { this.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); } } AddStatusUpdateListener(listener) { this.#statusUpdateListeners.push(listener); } RemovetatusUpdateListener(listener) { this.#statusUpdateListeners = this.#statusUpdateListeners.filter((x) => x !== listener); } #ShowNavigationMenu(fade = true) { if (this.#navigationMenu && this.#navigationMenu.isConnected) { if (this.#navigationMenu.fade) { this.#navigationMenu.fade.cancel(); this.#navigationMenu.fade = null; } Fade(this.#navigationMenu, { to: 1, duration: 200 }); return; } const errorButton = CreateElement("a", { class: "popup_menu_item", text: "View Errors", onclick: () => { unsafeWindow.document.body.click(); this.ShowErrors(); } }); if (this.#navigationButton.classList.contains("cs2s_navigation_status_error_glow")) { errorButton.classList.add("cs2s_navigation_status_error_glow"); } const botConnected = this.Bot?.Plugin?.Connected; const interfaceToggleButton = CreateElement("a", { class: "popup_menu_item", text: !botConnected ? "Start Interface" : "Stop Interface", onclick: () => { unsafeWindow.document.body.click(); if (!botConnected) { this.StartInterface(); } else { this.StopInterface(); } } }); const settingsButton = CreateElement("a", { class: "popup_menu_item", text: "Settings", onclick: () => { unsafeWindow.document.body.click(); this.ShowSettings(); } }); this.#navigationMenu = CreateElement("div", { class: "popup_block_new", children: [ CreateElement("div", { class: "popup_body popup_menu", children: [ interfaceToggleButton, settingsButton, this.#errorTableBody && errorButton ] }) ] }); this.#navigationButton.append(this.#navigationMenu); this.#navigationMenu.style.top = `${this.#navigationButton.offsetTop}px`; this.#navigationMenu.style.left = `-${this.#navigationMenu.offsetWidth}px`; if (fade) { Fade(this.#navigationMenu, { from: 0, to: 1, duration: 200 }); } } #HideNavigationMenu() { if (!this.#navigationMenu) { return; } this.#navigationMenu.fade = Fade(this.#navigationMenu, { to: 0, duration: 200, onfinish: () => { this.#navigationMenu.isConnected && this.#navigationMenu.remove(); } }); } async VerifyConnection() { try { await asf_default.GetBot(null, false); } catch (e) { this.ShowError({ level: ERROR_LEVEL.MEDIUM }, e, new Error('ArchiSteamFarm is not running or cannot be reached. Please verify that ASF is running. Under "Settings", verify that your ASF server, port, and password settings are all correct.')); return false; } try { await asf_default.GetPluginStatus(); } catch (e) { this.ShowError({ level: ERROR_LEVEL.MEDIUM }, e, new Error("CS2 Interface plugin is not installed")); return false; } try { const bot = await asf_default.GetBot(unsafeWindow.g_steamID); if (!bot) { throw new Error("ASF bot for this account was not found. If ASF was recently started, please wait until your bots come online and then reload the page."); } this.Bot = bot; } catch (e) { this.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return false; } this.#UpdateConnectionStatus(); setInterval(() => { if (unsafeWindow.document.visibilityState === "hidden") { return; } this.#UpdateConnectionStatus(); }, 1e3); return true; } ShowError(options, ...errors) { if (!this.#errorTableBody) { this.#errorTableBody = CreateElement("tbody"); } for (const error of errors) { console.log(error); this.#errorTableBody.prepend( CreateElement("tr", { children: [ CreateElement("td", { text: (/* @__PURE__ */ new Date()).toLocaleString() }), CreateElement("td", { text: options.level == ERROR_LEVEL.HIGH ? "High" : options.level == ERROR_LEVEL.MEDIUM ? "Medium" : "Low" }), CreateElement("td", { text: error.message }) ] }) ); } if (this.#errorTableBody.isConnected) { return; } if (options.level === ERROR_LEVEL.HIGH) { const popup = new Popup({ title: "Counter-Strike 2 Script Error", simpleMode: true, popoverMode: true, fade: false, body: [ CreateElement("div", { class: "cs2s_action_body", children: [ CreateElement("div", { class: "cs2s_action_message_tall cs2s_action_multi_message", children: [ ...errors.map( (error) => CreateElement("div", { class: "cs2s_action_message", text: error.message }) ) ] }), CreateElement("div", { class: "cs2s_action_buttons", children: [ CreateElement("div", { class: "cs2s_grey_long_button", text: "Close", onclick: () => { popup.Hide(); } }) ] }) ] }) ] }); popup.Show(); } else if (options.level === ERROR_LEVEL.MEDIUM) { const globalNavigationButton = unsafeWindow.document.getElementById(`account_pulldown`); this.#navigationButton.classList.add("cs2s_navigation_status_error_glow"); globalNavigationButton && globalNavigationButton.classList.add("cs2s_navigation_status_error_pulse"); } } ShowErrors() { const globalNavigationButton = unsafeWindow.document.getElementById(`account_pulldown`); this.#navigationButton.classList.remove("cs2s_navigation_status_error_glow"); globalNavigationButton && globalNavigationButton.classList.remove("cs2s_navigation_status_error_pulse"); const popup = new Popup({ title: "Counter-Strike 2 Script Errors", body: [ CreateElement("div", { text: "More detailed information can be found in your browser's developer console", style: { padding: "0px 0px 16px 16px", fontStyle: "italic" } }), CreateElement("div", { class: "cs2s_table_container cs2s_error_table_container", children: [ CreateElement("table", { class: "cs2s_table", children: [ CreateElement("thead", { children: [ CreateElement("tr", { children: [ CreateElement("th", { text: "Time" }), CreateElement("th", { text: "Severity" }), CreateElement("th", { text: "Error" }) ] }) ] }), this.#errorTableBody ] }) ] }) ] }); popup.Show(); } ShowSettings() { const form = CreateElement("form", { class: "cs2s_settings_form", html: ( /*html*/ ` <div class="cs2s_settings_form_group_title"> ASF Settings </div> <div class="cs2s_settings_form_group"> <div class="cs2s_settings_form_group_item"> <label for="${SETTING_ASF_SERVER}"> ASF Server </label> <input type="text" name="${SETTING_ASF_SERVER}" placeholder="${DEFAULT_SETTINGS[SETTING_ASF_SERVER]}" value="${GetSetting(SETTING_ASF_SERVER)}"> </div> <div class="cs2s_settings_form_group_item"> <label for="${SETTING_ASF_PORT}"> ASF Port </label> <input type="number" name="${SETTING_ASF_PORT}" placeholder="${DEFAULT_SETTINGS[SETTING_ASF_PORT]}" min="0" value="${GetSetting(SETTING_ASF_PORT)}"> </div> <div class="cs2s_settings_form_group_item"> <label> ASF IPC Password </label> <div class="cs2s_settings_form_message"> This setting can be configured from your userscript manager's popup menu, found in your browser's extensions toolbar </div> </div> </div> <div class="cs2s_settings_form_group_title"> Script Features </div> <div class="cs2s_settings_form_group"> <div class="cs2s_settings_form_group_item cs2s_settings_form_group_item_checkbox"> <input type="checkbox" name="${SETTING_INSPECT_ITEMS}" id="${SETTING_INSPECT_ITEMS}" ${GetSetting(SETTING_INSPECT_ITEMS) ? "checked" : ""}> <label for="${SETTING_INSPECT_ITEMS}"> Inspect Items </label> </div> </div> <div class="cs2s_settings_form_group_title"> Script Settings </div> <div class="cs2s_settings_form_group"> <div class="cs2s_settings_form_group_item"> <label for="${SETTING_INTERFACE_AUTOSTOP_MINUTES}"> Auto-stop interface if inactive for (minutes; 0 = never auto-stop${this.Bot?.Plugin?.Connected ? "; changes will apply on next start" : ""}) </label> <input type="number" name="${SETTING_INTERFACE_AUTOSTOP_MINUTES}" placeholder="${DEFAULT_SETTINGS[SETTING_INTERFACE_AUTOSTOP_MINUTES]}" min="0" value="${GetSetting(SETTING_INTERFACE_AUTOSTOP_MINUTES)}"> </div> <div class="cs2s_settings_form_group_item"> <label for="${SETTING_INSPECT_CACHE_TIME_HOURS}"> Re-inspect items after (hours; -1 = never re-inspect) </label> <input type="number" name="${SETTING_INSPECT_CACHE_TIME_HOURS}" placeholder="${DEFAULT_SETTINGS[SETTING_INSPECT_CACHE_TIME_HOURS]}" min="-1" value="${GetSetting(SETTING_INSPECT_CACHE_TIME_HOURS)}"> </div> </div> <div class="cs2s_settings_form_submit_group"> <button class="cs2s_blue_long_button" type="submit">Save</button> <button class="cs2s_grey_long_button" id="form_cancel" type="button">Cancel</button> </div> ` ), onsubmit: (event) => { event.preventDefault(); for (const element of event.target) { if (!element.name || !element.value && !element.placeholder) { continue; } const value = element.type === "checkbox" ? element.checked : element.value || element.placeholder; SetSetting(element.name, value); } window.location.reload(); } }); const popup = new Popup({ title: "Counter-Strike 2 Script Settings", body: [form] }); form.querySelector("#form_cancel").onclick = () => { popup.Hide(); }; popup.Show(); } async StartInterface(options = {}) { const showProgress = options.showProgress ?? true; const errorLevel = options.errorLevel ?? ERROR_LEVEL.HIGH; if (!this.Bot) { this.ShowError({ level: errorLevel }, new Error("Cannot start interface. Check the error log for more information.")); return false; } const loadingBody = CreateElement("div", { class: "cs2s_action_body", children: [ CreateElement("div", { class: "cs2s_action_spinner" }) ] }); const successButton = CreateElement("div", { class: "cs2s_blue_long_button", text: "OK" }); const successBody = CreateElement("div", { class: "cs2s_action_body", children: [ CreateElement("div", { class: "cs2s_action_message cs2s_action_message_tall", text: "Interface successfully started" }), successButton ] }); let interfaceStarted = false; const popup = new Popup({ simpleMode: true, disableClose: true, popoverMode: options.popoverMode ?? false, fade: false, title: "Starting Interface", body: [ loadingBody, successBody ], onopen: options.onopen, onclose: () => { if (typeof options.onclose === "function") { options.onclose(); } if (interfaceStarted) { if (typeof options.onconnected === "function") { options.onconnected(); return interfaceStarted; } window.location.reload(); } } }); successBody.hide(); successButton.onclick = () => { popup.Hide(); }; if (showProgress) { popup.Show(); } try { const response = await asf_default.Send("CS2Interface", `Start`, "GET", this.Bot.ASF.BotName, { autoStop: GetSetting(SETTING_INTERFACE_AUTOSTOP_MINUTES) }); if (!response || !response[this.Bot.ASF.BotName]?.Success) { popup.Hide(); this.ShowError({ level: errorLevel }, new Error("Interface failed to start")); return interfaceStarted; } let status = await asf_default.GetPluginStatus(this.Bot.ASF.BotName); while (status && status.Connected && !status.InventoryLoaded) { await Sleep(1e3); status = await asf_default.GetPluginStatus(this.Bot.ASF.BotName); } if (!status || !status.Connected || !status.InventoryLoaded) { popup.Hide(); this.ShowError({ level: errorLevel }, new Error("Interface failed to start: Interface stopped while waiting for inventory to loaded")); return interfaceStarted; } } catch (e) { popup.Hide(); this.ShowError({ level: errorLevel }, new Error(e.response?.Result?.[this.Bot.ASF.BotName]?.Message ?? e.message)); return interfaceStarted; } interfaceStarted = true; if (options.autoClose) { popup.Hide(); return interfaceStarted; } loadingBody.hide(); successBody.show(); return interfaceStarted; } async StopInterface(options = {}) { const showProgress = options.showProgress ?? true; const errorLevel = options.errorLevel ?? ERROR_LEVEL.HIGH; const loadingBody = CreateElement("div", { class: "cs2s_action_body", children: [ CreateElement("div", { class: "cs2s_action_spinner" }) ] }); const successButton = CreateElement("div", { class: "cs2s_blue_long_button", text: "OK" }); const successBody = CreateElement("div", { class: "cs2s_action_body", children: [ CreateElement("div", { class: "cs2s_action_message cs2s_action_message_tall", text: "Interface successfully stopped" }), successButton ] }); let interfaceStopped = false; const popup = new Popup({ simpleMode: true, title: "Stopping Interface", body: [ loadingBody, successBody ], onclose: () => { if (interfaceStopped) { window.location.reload(); } } }); successBody.hide(); successButton.onclick = () => { popup.Hide(); }; if (showProgress) { popup.Show(); } try { const response = await asf_default.Send("CS2Interface", `Stop`, "GET", this.Bot.ASF.BotName); if (!response || !response[this.Bot.ASF.BotName]?.Success) { popup.Hide(); this.ShowError({ level: errorLevel }, new Error("Interface failed to stop")); return interfaceStopped; } } catch (e) { popup.Hide(); this.ShowError({ level: errorLevel }, e); return interfaceStopped; } interfaceStopped = true; loadingBody.hide(); successBody.show(); return interfaceStopped; } async RestartInterface(options = {}) { return await this.StopInterface(options) && await this.StartInterface(options); } ShowStartInterfacePrompt(options = {}) { const popup = new Popup({ title: "Start Interface?", simpleMode: true, popoverMode: options.popoverMode ?? false, onopen: options.onopen, onclose: options.onclose, fade: options.fade, body: [ CreateElement("div", { class: "cs2s_action_body", children: [ CreateElement("div", { class: "cs2s_action_message cs2s_action_message_tall", text: options.message ?? "Start the interface?" }), CreateElement("div", { class: "cs2s_action_buttons", children: [ CreateElement("div", { class: "cs2s_blue_long_button", text: "Start Interface", onclick: () => { popup.Hide(); this.StartInterface(options); } }), CreateElement("div", { class: "cs2s_grey_long_button", text: "Cancel", onclick: () => { popup.Hide(); } }) ] }) ] }) ] }); popup.Show(); } async GetInventory(options = {}) { if (this.#inventory === null) { const progressMessage = CreateElement("div", { class: "cs2s_action_message" }); const progressBar = CreateElement("div", { class: "cs2s_action_progress_bar" }); this.GetInventory.closeButton = CreateElement("div", { class: "cs2s_grey_long_button", text: "Close" }); this.GetInventory.progressBody = CreateElement("div", { class: "cs2s_action_body", children: [ progressMessage, progressBar, this.GetInventory.closeButton ] }); this.#inventory = CreateCachedAsyncFunction(async () => { const cache_id = `inventory_${unsafeWindow.g_steamID}`; const cache = await Cache.GetValue(cache_id, null); let inventory; if (this.Bot) { try { let status = await asf_default.GetPluginStatus(this.Bot.ASF.BotName); if (status && status.Connected && status.InventoryLoaded) { if (!status.InventoryLoaded) { do { await Sleep(1e3); status = await asf_default.GetPluginStatus(this.Bot.ASF.BotName); } while (status && status.Connected && !status.InventoryLoaded); } if (status && status.Connected && status.InventoryLoaded) { const itemList = await asf_default.Send("CS2Interface", "Inventory", "GET", this.Bot.ASF.BotName); if (itemList) { inventory = new Inventory(itemList); Cache.SetValue(cache_id, itemList); } } } } catch (e) { this.ShowError({ level: ERROR_LEVEL.LOW }, e); } } if (!inventory) { if (!cache) { return OPERATION_ERROR.INTERFACE_NOT_CONNECTED; } inventory = new Inventory(cache, true); } try { await inventory.LoadCrateContents((message, progress) => { progressMessage.innerText = message; progressBar.style.setProperty("--percentage", `${(progress * 100).toFixed(0)}%`); }); return inventory; } catch (e) { this.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return e.OPERATION_ERROR ?? OPERATION_ERROR.INVENTORY_FAILED_TO_LOAD; } }); } let cancelled = false; const popup = new Popup({ title: "Loading Inventory", body: [this.GetInventory.progressBody], simpleMode: true, onclose: () => { cancelled = true; } }); this.GetInventory.closeButton.onclick = () => { popup.Hide(); }; const alreadyFinished = this.#inventory.willReturnImmediately(); if (options.showProgress && !alreadyFinished) { popup.Show(); } const success = await this.#inventory() !== void 0; if (cancelled || !success) { return; } if (options.showProgress && !alreadyFinished) { await Sleep(500); popup.Hide(); } return await this.#inventory(); } }; var instance = new Script(); var script_default = instance; // src/utils/worker.js var Worker = class { #queue = []; #activeTasks = 0; #concurrentLimit; #delay; #running = false; cancelled = false; constructor(options = {}) { const { concurrentLimit = 1, delay = 0 } = options; this.#concurrentLimit = typeof concurrentLimit === "function" ? concurrentLimit : () => concurrentLimit; this.#delay = delay; } Add(task, options = {}) { if (this.cancelled) { return; } if (options.priority ?? false) { this.#queue.unshift(task); } else { this.#queue.push(task); } } Run() { if (this.#running) { return; } this.#running = true; (async () => { while (this.#queue.length > 0) { if (this.#activeTasks < this.#concurrentLimit()) { const task = this.#queue.shift(); this.#activeTasks++; (async () => { try { await task(); } finally { this.#activeTasks--; } })(); } else { await Sleep(50); } if (this.#queue.length > 0) { await Sleep(this.#delay); } } this.#running = false; })(); } async Finish() { while (this.#activeTasks > 0 || this.#queue.length > 0) { await Sleep(50); } } async Cancel() { this.cancelled = true; this.#queue.length = 0; await this.Finish(); } }; // src/cs2/items/assets/asset.js var Asset = class _Asset { _assetid; _type; _inspectLink; _inspectData; _wearData; static _inspectionWorker = new Worker({ concurrentLimit: () => { return script_default.AccountsConnected; } }); static TYPE = { WEARABLE: 0, KEYCHAIN: 1, STORAGE_UNIT: 2, OTHER: 3 }; ShouldInspect() { return this._type == _Asset.TYPE.WEARABLE && this._inspectLink != "undefined"; } async _Inspect(options = {}) { if (!this.ShouldInspect() || !this._assetid) { return; } const cacheOnly = options.cacheOnly ?? false; const cache_id = `item_${this._assetid}`; const cache = await Cache.GetValue(cache_id, null); if (cache) { const ageHours = (+/* @__PURE__ */ new Date() - cache.created) / 36e5; const maxAgeHours = GetSetting(SETTING_INSPECT_CACHE_TIME_HOURS); const cacheExpired = maxAgeHours >= 0 && ageHours >= maxAgeHours; if (!cacheExpired) { this._inspectData = cache; } } if (!this._inspectData) { if (cacheOnly) { return false; } let inspectData; for (let attempt = 0; attempt < 3; attempt++) { try { inspectData = await asf_default.Send("CS2Interface", "InspectItem", "GET", "ASF", { url: this._inspectLink }); break; } catch (e) { if (e.code === 504) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, e); } else { throw e; } } } if (!inspectData) { throw new Error(`Failed to inspect item: ${this._inspectLink}`); } if (!inspectData.iteminfo) { console.log(inspectData); throw new Error(`Invalid inspect data, check browser logs, ${this._inspectLink}`); } this._inspectData = { created: +/* @__PURE__ */ new Date() }; if (typeof inspectData.wear !== "undefined" && typeof inspectData.wear_min !== "undefined" && typeof inspectData.wear_max !== "undefined") { this._inspectData.wear = inspectData.wear; this._inspectData.wearMin = inspectData.wear_min; this._inspectData.wearMax = inspectData.wear_max; } if (typeof inspectData.iteminfo.paintseed !== "undefined") { this._inspectData.seed = inspectData.iteminfo.paintseed; } if (typeof inspectData.iteminfo.rarity !== "undefined") { this._inspectData.rarity = inspectData.iteminfo.rarity; } if (typeof inspectData.iteminfo.quality !== "undefined") { this._inspectData.quality = inspectData.iteminfo.quality; } if (inspectData.stattrak === true) { this._inspectData.stattrak = true; } if ((inspectData.iteminfo.stickers?.length ?? 0) > 0) { this._inspectData.stickers = inspectData.iteminfo.stickers.map((sticker) => sticker.wear ?? 0); } if ((inspectData.iteminfo.keychains?.length ?? 0) > 0) { this._inspectData.charm = inspectData.iteminfo.keychains[0].pattern; } Cache.SetValue(cache_id, this._inspectData); } if (typeof this._inspectData.wear !== "undefined") { this._wearData = _Asset.GetWear(this._inspectData.wear); if (!this._wearData) { throw new Error(`Invalid item wear: ${this._inspectData.wear}, ${this._inspectLink}`); } } return true; } static GetWear(wearValue) { for (const wear of WEARS) { if (wearValue >= wear.min && wearValue <= wear.max) { return wear; } } } static GetPercentileElement(wear, wearValue, wearMin, wearMax, options = {}) { const showTooltip = options.showTooltip ?? false; const showRounded = options.rounded ?? true; const exteriorWearMin = Math.max(wearMin, wear.min); const exteriorWearMax = Math.min(wearMax, wear.max); const percentile = (1 - (wearValue - exteriorWearMin) / (exteriorWearMax - exteriorWearMin)) * 100; const percentileRounded = Math.min(99, Math.round(percentile)); const percentileFixed = Math.min(99.99, parseFloat(percentile.toFixed(2))); const bestWear = _Asset.GetWear(wearMin); const worstWear = _Asset.GetWear(wearMax); let rank; if (wear == bestWear) { if (percentileFixed >= 99.9) { rank = "gold"; } else if (percentileFixed >= 99.5) { rank = "silver"; } else if (percentileRounded >= 99) { rank = "bronze"; } } else if (wear == worstWear && percentileRounded == 0) { rank = "rust"; } const percentileElement = CreateElement("span", { text: showRounded ? `(${percentileRounded}%)` : `(${percentileFixed}%)` }); if (rank) { percentileElement.classList.add(`cs2s_asset_rank_${rank}`); } if (showTooltip) { BindTooltip(percentileElement, `Better than ${percentileFixed}% of ${wear.nameLong} floats`); } return percentileElement; } static GetNumCosmetics(item) { if (typeof item.cosmetics !== "undefined") { return item.cosmetics; } let count = 0; if (item.keychains) { count++; } if (item.stickers) { for (let slotNum = 0; slotNum < STICKER_MAX_COUNT; slotNum++) { let stickerID = item.attributes[`sticker slot ${slotNum} id`]; if (stickerID) { count++; } } } item.cosmetics = count; return item.cosmetics; } _GetPercentileElement(options = {}) { if (!this._inspectData || !this._wearData) { return; } return _Asset.GetPercentileElement(this._wearData, this._inspectData.wear, this._inspectData.wearMin, this._inspectData.wearMax, options); } _GetWearRangeElement(highlightHalfwayPoint = false) { if (!this._inspectData.wear) { return; } const wearRangeElement = CreateElement("div", { class: "descriptor cs2s_asset_wear_range" }); for (const wear of WEARS) { const { wearMin, wearMax, wear: actualWear } = this._inspectData; const range = wear.max - wear.min; const isMinWear = wearMin > 0 && wearMin >= wear.min && wearMin < wear.max; const isMaxWear = wearMax < 1 && wearMax > wear.min && wearMax <= wear.max; const isRollableWear = wearMax > wear.min && wearMin < wear.max; const wearGroupElement = CreateElement("div", { class: `cs2s_asset_wear_range_${wear.name.toLowerCase()}` }); wearRangeElement.append(wearGroupElement); if (isMinWear) { const percentage = (1 - (wearMin - wear.min) / range) * 100; wearGroupElement.classList.add("cs2s_asset_wear_range_right"); wearGroupElement.style.setProperty("--wear-percentage", `${percentage.toFixed(0)}%`); wearGroupElement.append( CreateElement("div", { class: "cs2s_asset_wear_range_low", wear_value: wearMin.toFixed(2), vars: { "wear-percentage": `${percentage.toFixed(0)}%` } }) ); } if (isMaxWear) { const percentage = (wearMax - wear.min) / range * 100; wearGroupElement.classList.add("cs2s_asset_wear_range_left"); wearGroupElement.style.setProperty("--wear-percentage", `${percentage.toFixed(0)}%`); wearGroupElement.append( CreateElement("div", { class: "cs2s_asset_wear_range_high", wear_value: wearMax.toFixed(2), vars: { "wear-percentage": `${percentage.toFixed(0)}%` } }) ); } if (isRollableWear && !isMinWear && !isMaxWear) { wearGroupElement.classList.add("cs2s_asset_wear_range_full"); } if (!isRollableWear) { wearGroupElement.classList.add("cs2s_asset_wear_range_empty"); } if (this._wearData == wear) { let percentage; if (highlightHalfwayPoint) { const halfWayPoint = (Math.min(wear.max, wearMax) + Math.max(wear.min, wearMin)) / 2; percentage = (halfWayPoint - wear.min) / range * 100; } else { percentage = (actualWear - wear.min) / range * 100; } wearGroupElement.append( CreateElement("div", { class: "cs2s_asset_wear_range_marker", vars: { "wear-percentage": `${percentage.toFixed(0)}%` } }) ); } } return wearRangeElement; } }; // src/components/table.js var Table = class _Table { #mode; #casket; #multiCasket; #data; #filteredData; #inventory; #selectionLimit; #rowElements = []; #selectedRows = /* @__PURE__ */ new Set(); #selectedRowsSaved = /* @__PURE__ */ new Set(); #lastStartRow; #lastRowClicked = null; #lastRowSelected = null; #inventoryChanged = false; #sortColumns = null; #sortDirection = null; #filterables = null; #filter = null; #searchQuery = null; static ROW_HEIGHT = 69; static BUFFER_ROWS = 3; #VISIBLE_ROWS; get #NUM_ROW_ELEMENTS() { return this.#VISIBLE_ROWS + _Table.BUFFER_ROWS * 2; } #popup; #tableContainer; #table; #tableBody; #spacer; #selectionLimitCount; #selectionCount; #clearSelectionButton; #filterCount; #actionButton; static MODE = { STORE: 0, RETRIEVE: 1 }; static SORT_DIRECTION = { ASC: 0, DESC: 1 }; constructor(data, inventory, options) { this.#data = data; this.#filteredData = data; this.#inventory = inventory; this.#mode = options.mode; this.#casket = options.casket ?? null; this.#multiCasket = options.multiCasket ?? false; this.#VISIBLE_ROWS = Math.max(1, Math.floor(unsafeWindow.innerHeight * 0.66 / _Table.ROW_HEIGHT)); this.#data.map((item) => delete item.element); this.#tableBody = CreateElement("tbody"); this.#spacer = CreateElement("div"); this.#table = CreateElement("table", { class: "cs2s_table", children: [ CreateElement("thead", { children: [ CreateElement("tr", { children: [ CreateElement("th", { class: "cs2s_table_image_column" }), CreateElement("th", { class: "cs2s_table_name_column", children: [ CreateElement("span", { class: "cs2s_table_column", text: "Name", children: [ CreateElement("div", { class: "cs2s_table_column_sort" }) ], onclick: (event) => { this.#SortRows({ event, columns: ["name", "wear"] }); } }), CreateElement("span", { class: "cs2s_table_column_search cs2s_resizable_input", children: [ CreateElement("input", { type: "search", placeholder: "Search", oninput: (event) => { event.target.style.width = "0px"; event.target.parentNode.dataset.value = event.target.value || event.target.placeholder; event.target.style.width = `${event.target.parentNode.clientWidth}px`; this.#searchQuery = event.currentTarget.value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase(); this.#FilterRows(false); } }) ] }) ] }), CreateElement("th", { class: "cs2s_table_collection_column", children: [ CreateElement("span", { class: "cs2s_table_column", text: "Quality", children: [ CreateElement("div", { class: "cs2s_table_column_sort" }) ], onclick: (event) => { this.#SortRows({ event, columns: ["rarity", "quality", "collection", "name", "wear"] }); } }), CreateElement("span", { class: "cs2s_table_column", text: "Collection", children: [ CreateElement("div", { class: "cs2s_table_column_sort" }) ], onclick: (event) => { this.#SortRows({ event, columns: ["collection", "rarity", "quality", "name", "wear"] }); } }) ] }), CreateElement("th", { class: "cs2s_table_float_column", children: [ CreateElement("span", { class: "cs2s_table_column", text: "Float", children: [ CreateElement("div", { class: "cs2s_table_column_sort" }) ], onclick: (event) => { this.#SortRows({ event, columns: ["wear"] }); } }) ] }), CreateElement("th", { class: "cs2s_table_seed_column", children: [ CreateElement("span", { class: "cs2s_table_column", text: "Seed", children: [ CreateElement("div", { class: "cs2s_table_column_sort" }) ], onclick: (event) => { this.#SortRows({ event, columns: ["seed"] }); } }) ] }), this.#multiCasket && CreateElement("th", { class: "cs2s_table_crate_column", children: [ CreateElement("span", { class: "cs2s_table_column", text: "Storage Unit", children: [ CreateElement("div", { class: "cs2s_table_column_sort" }) ], onclick: (event) => { this.#SortRows({ event, columns: ["casket_name", "id"] }); } }) ] }) ] }) ] }), this.#tableBody, this.#spacer ] }); this.#tableContainer = CreateElement("div", { class: "cs2s_table_container", style: { height: `${(this.#VISIBLE_ROWS + 1) * _Table.ROW_HEIGHT}px` }, onscroll: () => { this.#UpdateRows(); }, children: [this.#table] }); if (this.#mode === _Table.MODE.RETRIEVE) { this.#selectionLimit = INVENTORY_ITEM_LIMIT - inventory.items.length; } else { this.#selectionLimit = STORAGE_UNIT_ITEM_LIMIT - inventory.storedItems.filter((x) => x.casket_id == this.#casket.iteminfo.id).length; } const onStatusUpdate = (status) => { if (this.#mode !== _Table.MODE.RETRIEVE || typeof status.Plugin?.InventorySize === "undefined") { return; } this.#selectionLimit = INVENTORY_ITEM_LIMIT - status.Plugin.InventorySize; this.#UpdateFooter(); }; this.#filterCount = CreateElement("span", { class: "cs2s_table_footer_selection_count", text: this.#data.length.toLocaleString() }); this.#selectionLimitCount = CreateElement("span", { class: "cs2s_table_footer_selection_count", text: this.#selectionLimit.toLocaleString() }); this.#selectionCount = CreateElement("span", { class: "cs2s_table_footer_selection_count", text: 0 }); this.#clearSelectionButton = CreateElement("a", { class: "cs2s_table_footer_action_link", // text: "Clear", onclick: () => { this.#DeselectAll(); this.#tableContainer.focus(); }, children: [ CreateElement("span", { htmlChildren: [ /*html*/ ` <svg width="16" height="16" viewBox="0 0 32 32" aria-hidden="true" stroke="none" fill="currentColor"> <path d="m 15.5,29.5 c -7.18,0 -13,-5.82 -13,-13 0,-7.18 5.82,-13 13,-13 7.18,0 13,5.82 13,13 0,7.18 -5.82,13 -13,13 z m 6.438,-13.562 c 0,-0.552 -0.448,-1 -1,-1 h -11 c -0.553,0 -1,0.448 -1,1 v 1 c 0,0.553 0.447,1 1,1 h 11 c 0.552,0 1,-0.447 1,-1 z"></path> </svg> ` ], children: [ "Clear" ] }) ] }); this.#actionButton = CreateElement("a", { class: "cs2s_green_button cs2s_button_disabled", html: "<span>Proceed...</span>", onclick: () => { if (this.#actionButton.classList.contains("cs2s_button_disabled")) { return; } if (!script_default.Bot?.Plugin?.Connected) { script_default.ShowStartInterfacePrompt({ message: this.#mode === _Table.MODE.RETRIEVE ? "Interface must running to retrieve items" : "Interface must running to store items", autoClose: true, popoverMode: true, fade: false, onclose: () => { this.#tableContainer.focus(); }, onconnected: () => { this.#inventoryChanged = true; this.#ProcessSelected(); } }); return; } this.#ProcessSelected(); } }); const footerContainer = CreateElement("div", { class: "cs2s_table_footer cs2s_popup_footer", children: [ CreateElement("div", { class: "cs2s_table_footer_element_left", children: [ CreateElement("span", { children: [ CreateElement("a", { class: "cs2s_table_footer_action_link", onclick: (event) => { this.#ShowFilters(event.currentTarget); }, children: [ CreateElement("span", { htmlChildren: [ /*html*/ ` <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"> <path d="m 3,5 h 18 a 1,1 0 1 1 0,2 H 3 A 1,1 0 1 1 3,5 Z m 3,6 h 12 a 1,1 0 1 1 0,2 H 6 a 1,1 0 1 1 0,-2 z m 3,6 h 6 a 1,1 0 1 1 0,2 H 9 a 1,1 0 1 1 0,-2 z"/> </svg> ` ], children: [ "Filter" ] }) ] }), this.#filterCount, " Item(s)" ] }), CreateElement("span", { children: [ CreateElement("form", { style: { display: "inline-block" }, onsubmit: (event) => { event.preventDefault(); const countInput = event.target.elements["count"]; const count = parseInt(countInput.value || countInput.placeholder); this.#SelectFirst(count); }, children: [ CreateElement("button", { class: "cs2s_table_footer_action_link", children: [ CreateElement("span", { htmlChildren: [ /*html*/ ` <svg width="16" height="16" viewBox="0 0 32 32" aria-hidden="true" stroke="none" fill="currentColor"> <path d="M15.5 29.5c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13zM21.938 15.938c0-0.552-0.448-1-1-1h-4v-4c0-0.552-0.447-1-1-1h-1c-0.553 0-1 0.448-1 1v4h-4c-0.553 0-1 0.448-1 1v1c0 0.553 0.447 1 1 1h4v4c0 0.553 0.447 1 1 1h1c0.553 0 1-0.447 1-1v-4h4c0.552 0 1-0.447 1-1v-1z"></path> </svg> ` ], children: [ "Select" ] }) ] }), CreateElement("span", { onclick: (event) => { const input = event.target.querySelector("input"); input && input.focus(); }, children: [ "First ", CreateElement("span", { class: "cs2s_table_footer_input cs2s_resizable_input", children: [ CreateElement("input", { type: "number", name: "count", min: 0, placeholder: 0, style: { width: "10px" }, oninput: (event) => { event.target.style.width = "0px"; event.target.parentNode.dataset.value = event.target.value || event.target.placeholder; event.target.style.width = `${event.target.parentNode.clientWidth}px`; } }) ] }), " Item(s)" ] }) ] }) ] }) ] }), CreateElement("div", { class: "cs2s_table_footer_element_right", children: [ CreateElement("span", { children: [ this.#selectionLimitCount, " Space(s) Available" ] }), CreateElement("span", { children: [ this.#clearSelectionButton, this.#selectionCount, " Item(s) Selected" ] }), this.#actionButton ] }) ] }); const popupTitle = this.#mode === _Table.MODE.RETRIEVE ? "Select items to retrieve from " : "Select items to move into "; const cachedNotification = CreateElement("span", { text: "(Cached)" }); BindTooltip(cachedNotification, "The information below was loaded from cache and may no longer be accurate."); const popupTitleCrateName = CreateElement("span", { class: "cs2s_table_title_casket", text: this.#multiCasket ? options.casketName : `"${options.casketName}"`, children: [ this.#inventory.loadedFromCache && " ", this.#inventory.loadedFromCache && cachedNotification ] }); if (this.#multiCasket) { popupTitleCrateName.classList.add("cs2s_table_title_casket_multiple"); } script_default.AddStatusUpdateListener(onStatusUpdate); this.#popup = new Popup({ title: popupTitle, titleChildren: [ popupTitleCrateName ], body: [this.#tableContainer, footerContainer], onclose: () => { script_default.RemovetatusUpdateListener(onStatusUpdate); if (this.#inventoryChanged) { window.location.reload(); } } }); } Show() { this.#popup.Show(); this.#UpdateTable(); this.#tableContainer.focus(); this.#tableContainer.style.width = `${this.#tableContainer.offsetWidth}px`; this.#tableContainer.querySelectorAll("thead th").forEach((th) => { th.style.width = getComputedStyle(th).width; }); } #GetRowElement(item, buildIfDoesntExist = true) { if (item.element) { return item.element; } if (!buildIfDoesntExist) { return; } const cosmetics = CreateElement("div", { class: "cs2s_table_image_column_cosmetics" }); if (item.keychains) { let keychainID = item.attributes[`keychain slot 0 id`]; if (keychainID) { cosmetics.append(CreateElement("img", { src: `https://community.fastly.steamstatic.com/economy/image/${Inventory.iconURLs[item.keychains[keychainID].full_name]}/25fx19f` })); } } if (item.stickers) { for (let slotNum = 0; slotNum < STICKER_MAX_COUNT; slotNum++) { let stickerID = item.attributes[`sticker slot ${slotNum} id`]; if (stickerID) { cosmetics.append(CreateElement("img", { src: `https://community.fastly.steamstatic.com/economy/image/${Inventory.iconURLs[item.stickers[stickerID].full_name]}/25fx19f` })); } } } const imageTD = CreateElement("td", { class: "cs2s_table_image_column", children: [ CreateElement("img", { src: `https://community.fastly.steamstatic.com/economy/image/${Inventory.iconURLs[item.full_name]}/93fx62f` }), cosmetics ] }); const nameTD = CreateElement("td", { class: "cs2s_table_name_column", text: item.name, children: [ CreateElement("a", { href: `https://steamcommunity.com/market/listings/${CS2_APPID}/${encodeURIComponent(item.full_name)}`, target: "_blank", html: ( /*html*/ ` <svg viewBox="0 0 64 64" stroke-width="3" stroke="currentColor" fill="none"> <path d="M55.4,32V53.58a1.81,1.81,0,0,1-1.82,1.82H10.42A1.81,1.81,0,0,1,8.6,53.58V10.42A1.81,1.81,0,0,1,10.42,8.6H32"/> <polyline points="40.32 8.6 55.4 8.6 55.4 24.18"/> <line x1="19.32" y1="45.72" x2="54.61" y2="8.91"/> </svg> ` ) }) ] }); const collectionTD = CreateElement("td", { class: "cs2s_table_collection_column" }); if (item.collection) { collectionTD.innerText = item.collection; } if (item.collection || item.rarity > 1) { collectionTD.classList.add("cs2s_table_collection_column_has_rarity"); collectionTD.classList.add(`cs2s_table_collection_column_rarity_${item.rarity}`); collectionTD.classList.add(`cs2s_table_collection_column_quality_${item.iteminfo.quality}`); if (item.stattrak) { collectionTD.classList.add(`cs2s_table_collection_column_stattrak`); } } const floatTD = CreateElement("td", { class: "cs2s_table_float_column" }); if (typeof item.wear !== "undefined") { const wearData = Asset.GetWear(item.wear); floatTD.classList.add("cs2s_table_float_column_has_float"); floatTD.classList.add(`cs2s_table_float_column_float_${wearData.name.toLowerCase()}`); floatTD.innerText = item.wear.toFixed(14); floatTD.append(" "); floatTD.append(Asset.GetPercentileElement(wearData, item.wear, item.wear_min, item.wear_max)); } const seedTD = CreateElement("td", { class: "cs2s_table_seed_column", text: item.seed ?? "" }); const casketTD = this.#multiCasket && CreateElement("td", { class: "cs2s_table_crate_column", text: item.casket_name }); item.element = CreateElement("tr", { onmousedown: (event) => { if (event.target.nodeName === "A" || event.target.parentElement.nodeName === "A" || event.button !== 0) { return; } this.#SelectItem(event, item); }, children: [ imageTD, nameTD, collectionTD, floatTD, seedTD, casketTD ] }); if (this.#selectedRows.has(item.iteminfo.id)) { item.element.classList.add("cs2s_table_row_selected"); } return item.element; } #UpdateTable() { this.#lastStartRow = Number.POSITIVE_INFINITY; for (let i = 0; i < this.#rowElements.length; i++) { this.#rowElements[i].remove(); } this.#rowElements = []; for (let i = 0; i < this.#NUM_ROW_ELEMENTS; i++) { if (i >= this.#filteredData.length) { break; } const rowElement = this.#GetRowElement(this.#filteredData[i]); this.#rowElements.push(rowElement); this.#tableBody.append(rowElement); } this.#spacer.style.height = "0px"; this.#spacer.style.height = `${this.#filteredData.length * _Table.ROW_HEIGHT - this.#table.clientHeight + 31}px`; this.#UpdateRows(); this.#UpdateFooter(); if (this.#data.length == 0) { this.#tableBody.append(CreateElement("tr", { children: [ CreateElement("td", { class: "cs2s_table_empty", colspan: 6, text: this.#mode == _Table.MODE.RETRIEVE ? "Storage Unit is empty" : "Inventory has no storable items" }) ] })); } } #UpdateRows() { const startRow = Math.max( 0, Math.min( this.#filteredData.length - this.#NUM_ROW_ELEMENTS, Math.floor(this.#tableContainer.scrollTop / _Table.ROW_HEIGHT) - _Table.BUFFER_ROWS ) ); if (startRow == this.#lastStartRow) { return; } const diff = Math.max( -this.#NUM_ROW_ELEMENTS, Math.min( this.#NUM_ROW_ELEMENTS, startRow - this.#lastStartRow ) ); this.#lastStartRow = startRow; if (diff > 0) { for (let i = 0; i < diff; i++) { const dataIndex = startRow + this.#NUM_ROW_ELEMENTS - diff + i; if (dataIndex >= this.#filteredData.length || dataIndex < 0) { continue; } const oldRow = this.#rowElements.shift(); oldRow.remove(); const newRow = this.#GetRowElement(this.#filteredData[dataIndex]); this.#rowElements.push(newRow); this.#tableBody.append(newRow); } } else { for (let i = 0; i < Math.abs(diff); i++) { const dataIndex = startRow - diff - i - 1; if (dataIndex >= this.#filteredData.length || dataIndex < 0) { continue; } const oldRow = this.#rowElements.pop(); oldRow.remove(); const newRow = this.#GetRowElement(this.#filteredData[dataIndex]); this.#rowElements.unshift(newRow); this.#tableBody.prepend(newRow); } } this.#tableBody.style.transform = `translate3d(0, ${startRow * _Table.ROW_HEIGHT}px, 0)`; } #UpdateFooter() { this.#selectionLimitCount.innerText = this.#selectionLimit.toLocaleString(); this.#selectionCount.innerText = this.#selectedRows.size.toLocaleString(); this.#filterCount.innerText = this.#filteredData.length.toLocaleString(); if (this.#selectedRows.size <= 0) { this.#actionButton.classList.add("cs2s_button_disabled"); BindTooltip(this.#actionButton, "No items selected"); } else if (this.#selectedRows.size > this.#selectionLimit) { this.#actionButton.classList.add("cs2s_button_disabled"); BindTooltip(this.#actionButton, "Too many items selected"); } else { this.#actionButton.classList.remove("cs2s_button_disabled"); this.#actionButton.unbindTooltip && this.#actionButton.unbindTooltip(); } if (this.#selectedRows.size > 0) { this.#clearSelectionButton.show(); } else { this.#clearSelectionButton.hide(); } } #SelectItem(event, item) { if (event.shiftKey) { if (!this.#lastRowClicked || this.#lastRowClicked == item || this.#lastRowSelected === null) { return; } const start = this.#filteredData.indexOf(this.#lastRowClicked); const end = this.#filteredData.indexOf(item); if (start < 0 || end < 0) { return; } const from = Math.min(start, end); const to = Math.max(start, end); for (let i = from; i <= to; i++) { const rowItem = this.#filteredData[i]; const row = this.#GetRowElement(rowItem, false); const assetID = rowItem.iteminfo.id; if (!this.#lastRowSelected && this.#selectedRows.has(assetID)) { this.#selectedRows.delete(assetID); row && row.classList.remove("cs2s_table_row_selected"); } else if (this.#lastRowSelected) { this.#selectedRows.add(assetID); row && row.classList.add("cs2s_table_row_selected"); } } } else { const row = this.#GetRowElement(item); const assetID = item.iteminfo.id; if (this.#selectedRows.has(assetID)) { this.#lastRowSelected = false; this.#selectedRows.delete(assetID); row.classList.remove("cs2s_table_row_selected"); } else { this.#lastRowSelected = true; this.#selectedRows.add(assetID); row.classList.add("cs2s_table_row_selected"); } } this.#lastRowClicked = item; this.#UpdateFooter(); } #SelectFirst(count) { for (let i = 0; i < count; i++) { if (i >= this.#filteredData.length) { break; } const rowItem = this.#filteredData[i]; const row = this.#GetRowElement(rowItem, false); const assetID = rowItem.iteminfo.id; if (!this.#selectedRows.has(assetID)) { this.#selectedRows.add(assetID); row && row.classList.add("cs2s_table_row_selected"); } } this.#lastRowClicked = null; this.#UpdateFooter(); } #DeselectAll() { for (const item of this.#data) { const assetID = item.iteminfo.id; if (!this.#selectedRows.has(assetID)) { continue; } this.#selectedRows.delete(assetID); const row = this.#GetRowElement(item, false); row && row.classList.remove("cs2s_table_row_selected"); } this.#lastRowClicked = null; this.#UpdateFooter(); } #SortRows(options = {}) { if (this.#data.length == 0) { return; } if (options.columns) { if (this.#sortDirection != null && this.#sortColumns[0] != options.columns[0]) { this.#sortDirection = null; } this.#sortColumns = options.columns; } let resetSort = false; if (options.event) { if (this.#sortDirection === _Table.SORT_DIRECTION.DESC) { this.#sortColumns = ["casket_id", "id"]; this.#sortDirection = _Table.SORT_DIRECTION.DESC; resetSort = true; } else if (this.#sortDirection === _Table.SORT_DIRECTION.ASC) { this.#sortDirection = _Table.SORT_DIRECTION.DESC; } } if (!this.#sortColumns) { return; } if (!this.#sortDirection) { this.#sortDirection = _Table.SORT_DIRECTION.ASC; } const asc = this.#sortDirection === _Table.SORT_DIRECTION.ASC; this.#filteredData.sort((a, b) => { for (const column of this.#sortColumns) { let valueA = a[column]; let valueB = b[column]; if (valueA === valueB) { continue; } if (typeof valueA === "undefined") { return 1; } if (typeof valueB === "undefined") { return -1; } if (typeof valueA === "string") { return asc ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA); } return asc ? valueA - valueB : valueB - valueA; } return 0; }); if (options.event) { document.querySelectorAll(".cs2s_table_column_sort_asc").forEach((el) => { el.classList.remove("cs2s_table_column_sort_asc"); }); document.querySelectorAll(".cs2s_table_column_sort_desc").forEach((el) => { el.classList.remove("cs2s_table_column_sort_desc"); }); if (!resetSort) { if (asc) { options.event.target.querySelector(".cs2s_table_column_sort").classList.add("cs2s_table_column_sort_asc"); } else { options.event.target.querySelector(".cs2s_table_column_sort").classList.add("cs2s_table_column_sort_desc"); } } } this.#UpdateTable(); } #FilterRows(updateSelected = true) { if (this.#data.length == 0) { return; } const inRange = (value, { min, max }) => { return !(typeof value === "undefined" || min != null && value < min || max != null && value > max); }; const matches = (item) => { if (this.#filter?.selected && !this.#selectedRowsSaved.has(item.iteminfo.id)) { return false; } if (this.#filter?.float && !inRange(item.wear, this.#filter.float)) { return false; } if (this.#filter?.seed && !inRange(item.seed, this.#filter.seed)) { return false; } if (this.#filter?.cosmetics && !inRange(item.cosmetics, this.#filter.cosmetics)) { return false; } if (this.#filter?.quality != null && item.quality !== this.#filter.quality) { return false; } if (this.#filter?.rarity) { switch (this.#filter.rarity.key) { case "weapons": if (typeof item.wear === "undefined" || item.type_name === "Gloves") { return false; } break; case "agents": if (item.type_name !== "Agent") { return false; } break; case "other": if (typeof item.wear !== "undefined" && item.type_name !== "Gloves" || item.type_name === "Agent") { return false; } break; } if (item.iteminfo.rarity !== this.#filter.rarity.value) { return false; } } if (this.#filter?.type) { switch (this.#filter.type.key) { case "weapons": if (item.weapon_name !== this.#filter.type.value) { return false; } break; case "other": if (item.type_name !== this.#filter.type.value) { return false; } break; } } if (this.#filter?.collection && item.collection_name !== this.#filter.collection) { return false; } if (this.#searchQuery && !item.name_normalized.includes(this.#searchQuery)) { return false; } return true; }; if (updateSelected) { this.#selectedRowsSaved = new Set(this.#selectedRows); } if (!this.#searchQuery && !this.#filter) { this.#filteredData = this.#data; } else { this.#filteredData = this.#data.filter(matches); } this.#tableContainer.scrollTop = 0; this.#SortRows(); this.#UpdateTable(); } #ShowFilters(button) { if (this.#filterables == null) { this.#filterables = { types: { weapons: /* @__PURE__ */ new Set(), other: /* @__PURE__ */ new Set() }, qualities: {}, rarities: { weapons: {}, agents: {}, other: {} }, float: { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY, wears: {} }, seed: { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY }, cosmetics: { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY }, collections: { empty_exists: false, weapons: /* @__PURE__ */ new Set(), stickers: /* @__PURE__ */ new Set(), charms: /* @__PURE__ */ new Set(), agents: /* @__PURE__ */ new Set(), patches: /* @__PURE__ */ new Set(), graffiti: /* @__PURE__ */ new Set(), other: /* @__PURE__ */ new Set() } }; for (const item of this.#data) { if (item.type_name) { this.#filterables.types.other.add(item.type_name); } if (item.weapon_name) { this.#filterables.types.weapons.add(item.weapon_name); } if (item.quality_name) { this.#filterables.qualities[item.quality_name] = item.quality; } if (item.rarity_name) { if (typeof item.wear !== "undefined" && item.type_name !== "Gloves") { this.#filterables.rarities.weapons[item.rarity_name] = item.iteminfo.rarity; } else if (item.type_name === "Agent") { this.#filterables.rarities.agents[item.rarity_name] = item.iteminfo.rarity; } else { this.#filterables.rarities.other[item.rarity_name] = item.iteminfo.rarity; } } if (typeof item.wear != "undefined") { this.#filterables.float.min = Math.min(this.#filterables.float.min, item.wear); this.#filterables.float.max = Math.max(this.#filterables.float.max, item.wear); this.#filterables.float.wears[Asset.GetWear(item.wear).name] = true; } if (typeof item.seed != "undefined") { this.#filterables.seed.min = Math.min(this.#filterables.seed.min, item.seed); this.#filterables.seed.max = Math.max(this.#filterables.seed.max, item.seed); } { const count = Asset.GetNumCosmetics(item); this.#filterables.cosmetics.min = Math.min(this.#filterables.cosmetics.min, count); this.#filterables.cosmetics.max = Math.max(this.#filterables.cosmetics.max, count); } if (item.collection_name) { if (typeof item.wear !== "undefined") { this.#filterables.collections.weapons.add(item.collection_name); } else if (item.type_name === "Sticker") { this.#filterables.collections.stickers.add(item.collection_name); } else if (item.type_name === "Charm") { this.#filterables.collections.charms.add(item.collection_name); } else if (item.type_name === "Agent") { this.#filterables.collections.agents.add(item.collection_name); } else if (item.type_name === "Patch") { this.#filterables.collections.patches.add(item.collection_name); } else if (item.type_name === "Graffiti") { this.#filterables.collections.graffiti.add(item.collection_name); } else { this.#filterables.collections.other.add(item.collection_name); } } else { this.#filterables.collections.empty_exists = true; } } this.#filterables.float.min = Math.floor(this.#filterables.float.min * 100) / 100; this.#filterables.float.max = Math.ceil(this.#filterables.float.max * 100) / 100; } const floatMinValue = FLOAT_RANGE.min; const floatMaxValue = FLOAT_RANGE.max; const seedMinValue = SEED_RANGE.min; const seedMaxValue = SEED_RANGE.max; const cosmeticsMinValue = 0; const cosmeticsMaxValue = STICKER_MAX_COUNT + KEYCHAIN_MAX_COUNT; const type = CreateElement("select", { id: "type", disabled: Object.values(this.#filterables.types).reduce((sum, set) => sum + (set.size ?? 0), 0) < 2, children: [ CreateElement("option", { value: "" }), ...[ { key: "other", label: "Base Types" }, { key: "weapons", label: "Weapon Types" } ].filter(({ key }) => this.#filterables.types[key].size > 0).map( ({ key, label }) => CreateElement("optgroup", { label, children: [...this.#filterables.types[key]].sort().map( (name) => CreateElement("option", { text: name, value: name, selected: this.#filter?.type?.key === key && this.#filter?.type?.value === name, dataset: { key } }) ) }) ) ] }); if (type.disabled) { type.selectedIndex = type.options.length - 1; } const quality = CreateElement("select", { id: "quality", disabled: Object.keys(this.#filterables.qualities).length < 2, children: [ CreateElement("option", { value: "" }), ...Object.entries(this.#filterables.qualities).sort(([, v1], [, v2]) => v1 - v2).map( ([qualityName, qualityValue]) => CreateElement("option", { text: qualityValue == 0 ? "Normal" : qualityName, value: qualityValue, selected: this.#filter?.quality === qualityValue, class: `cs2s_color_quality_${qualityValue}` }) ) ] }); if (quality.disabled) { quality.selectedIndex = quality.options.length - 1; } const rarity = CreateElement("select", { id: "rarity", disabled: Object.values(this.#filterables.rarities).reduce((sum, obj) => sum + Object.keys(obj).length, 0) < 2, children: [ CreateElement("option", { value: "" }), ...[ { key: "weapons", label: "Weapon Qualities" }, { key: "agents", label: "Agent Qualities" }, { key: "other", label: "Base Qualities" } ].filter(({ key }) => Object.keys(this.#filterables.rarities[key]).length > 0).map( ({ key, label }) => CreateElement("optgroup", { label, children: Object.entries(this.#filterables.rarities[key]).sort(([, v1], [, v2]) => v1 - v2).map( ([rarityName, rarityValue]) => CreateElement("option", { text: rarityName, value: rarityValue, selected: this.#filter?.rarity?.key === key && this.#filter?.rarity?.value === rarityValue, class: `cs2s_color_rarity_${rarityValue}`, dataset: { key } }) ) }) ) ] }); if (rarity.disabled) { rarity.selectedIndex = rarity.options.length - 1; } const collection = CreateElement("select", { id: "collection", disabled: Object.values(this.#filterables.collections).reduce((sum, set) => sum + (set.size ?? 0), 0) < 2 - this.#filterables.collections.empty_exists, children: [ CreateElement("option", { value: "" }), ...[ { key: "weapons", label: "Weapon Collections" }, { key: "agents", label: "Agent Collections" }, { key: "stickers", label: "Sticker Collections" }, { key: "patches", label: "Patch Collections" }, { key: "charms", label: "Charm Collections" }, { key: "graffiti", label: "Graffiti Collections" }, { key: "other", label: "Other Collections" } ].filter(({ key }) => this.#filterables.collections[key].size > 0).map( ({ key, label }) => CreateElement("optgroup", { label, children: [...this.#filterables.collections[key]].sort().map( (name) => CreateElement("option", { text: name, value: name, selected: this.#filter?.collection === name }) ) }) ) ] }); if (collection.disabled) { collection.selectedIndex = collection.options.length - 1; } const floatMin = CreateElement("input", { id: "float_min", type: "number", step: 0.01, min: floatMinValue, max: floatMaxValue, placeholder: this.#filterables.float.min === Number.POSITIVE_INFINITY ? "" : this.#filterables.float.min, disabled: this.#filterables.float.min === Number.POSITIVE_INFINITY, oninput: () => { float.value = "custom"; const value = floatMin.value; if (floatMin.checkValidity()) { if (value === "") { floatMax.min = floatMinValue; } else { floatMax.min = value; } } } }); const floatMax = CreateElement("input", { id: "float_max", type: "number", step: 0.01, min: floatMinValue, max: floatMaxValue, placeholder: this.#filterables.float.max === Number.NEGATIVE_INFINITY ? "" : this.#filterables.float.max, disabled: this.#filterables.float.max === Number.NEGATIVE_INFINITY, oninput: () => { float.value = "custom"; const value = floatMax.value; if (floatMax.checkValidity()) { if (value === "") { floatMin.max = floatMaxValue; } else { floatMin.max = value; } } } }); if (typeof this.#filter?.float?.min !== "undefined" && this.#filter.float.min !== null) { floatMin.value = floatMax.min = this.#filter.float.min; } if (typeof this.#filter?.float?.max !== "undefined" && this.#filter.float.max !== null) { floatMax.value = floatMin.max = this.#filter.float.max; } const float = CreateElement("select", { id: "float", disabled: this.#filterables.float.max === Number.NEGATIVE_INFINITY, children: [ CreateElement("option", { value: "" }), CreateElement("option", { hidden: true, value: "custom" }), ...WEARS.filter((wear) => this.#filterables.float.wears[wear.name] === true).map((wear) => CreateElement("option", { value: wear.name, text: wear.nameLong, selected: this.#filter?.wear === wear.name, class: `cs2s_color_wear_${wear.name.toLowerCase()}` })) ], onchange: (event) => { const value = event.target.value; if (value === "") { floatMin.value = ""; floatMax.value = ""; floatMin.max = floatMaxValue; floatMax.min = floatMinValue; return; } for (const wear of WEARS) { if (wear.name === value) { floatMin.value = wear.min; floatMax.value = wear.max; floatMin.max = floatMax.value; floatMax.min = floatMin.value; return; } } } }); const seedMin = CreateElement("input", { id: "seed_min", type: "number", step: 1, min: seedMinValue, max: seedMaxValue, placeholder: this.#filterables.seed.min === Number.POSITIVE_INFINITY ? "" : this.#filterables.seed.min, disabled: this.#filterables.seed.min === Number.POSITIVE_INFINITY, oninput: () => { const value = seedMin.value; if (seedMin.checkValidity()) { if (value === "") { seedMax.min = seedMinValue; } else { seedMax.min = value; } } } }); const seedMax = CreateElement("input", { id: "seed_max", type: "number", step: 1, min: seedMinValue, max: seedMaxValue, placeholder: this.#filterables.seed.max === Number.NEGATIVE_INFINITY ? "" : this.#filterables.seed.max, disabled: this.#filterables.seed.max === Number.NEGATIVE_INFINITY, oninput: () => { const value = seedMax.value; if (seedMax.checkValidity()) { if (value === "") { seedMin.max = seedMaxValue; } else { seedMin.max = value; } } } }); if (typeof this.#filter?.seed?.min !== "undefined" && this.#filter.seed.min !== null) { seedMin.value = seedMax.min = this.#filter.seed.min; } if (typeof this.#filter?.seed?.max !== "undefined" && this.#filter.seed.max !== null) { seedMax.value = seedMin.max = this.#filter.seed.max; } const cosmeticsMin = CreateElement("input", { id: "cosmetics_min", type: "number", step: 1, min: cosmeticsMinValue, max: cosmeticsMaxValue, placeholder: this.#filterables.cosmetics.max === Number.NEGATIVE_INFINITY || this.#filterables.cosmetics.max === 0 ? "" : this.#filterables.cosmetics.min, disabled: this.#filterables.cosmetics.max === Number.NEGATIVE_INFINITY || this.#filterables.cosmetics.max === 0, oninput: () => { const value = cosmeticsMin.value; if (cosmeticsMin.checkValidity()) { if (value === "") { cosmeticsMax.min = cosmeticsMinValue; } else { cosmeticsMax.min = value; } } } }); const cosmeticsMax = CreateElement("input", { id: "cosmetics_max", type: "number", step: 1, min: cosmeticsMinValue, max: cosmeticsMaxValue, placeholder: this.#filterables.cosmetics.max === Number.NEGATIVE_INFINITY || this.#filterables.cosmetics.max === 0 ? "" : this.#filterables.cosmetics.max, disabled: this.#filterables.cosmetics.max === Number.NEGATIVE_INFINITY || this.#filterables.cosmetics.max === 0, oninput: () => { const value = cosmeticsMax.value; if (cosmeticsMax.checkValidity()) { if (value === "") { cosmeticsMin.max = cosmeticsMaxValue; } else { cosmeticsMin.max = value; } } } }); if (typeof this.#filter?.cosmetics?.min !== "undefined" && this.#filter.cosmetics.min !== null) { cosmeticsMin.value = cosmeticsMax.min = this.#filter.cosmetics.min; } if (typeof this.#filter?.cosmetics?.max !== "undefined" && this.#filter.cosmetics.max !== null) { cosmeticsMax.value = cosmeticsMin.max = this.#filter.cosmetics.max; } const selected = CreateElement("input", { id: "selected", type: "checkbox", disabled: this.#selectedRows.size == 0 }); if (this.#filter?.selected && this.#selectedRows.size > 0) { selected.checked = true; } const form = CreateElement("form", { class: "cs2s_settings_form cs2s_settings_filter", onreset: () => { floatMin.max = floatMaxValue; floatMax.min = floatMinValue; seedMin.max = seedMaxValue; seedMax.min = seedMinValue; cosmeticsMin.max = cosmeticsMaxValue; cosmeticsMax.min = cosmeticsMinValue; }, children: [ CreateElement("div", { class: "cs2s_settings_form_group_title" + (type.disabled && quality.disabled && rarity.disabled ? " cs2s_settings_form_disabled" : ""), text: "Base Filters" }), CreateElement("div", { class: "cs2s_settings_form_group", children: [ CreateElement("div", { class: "cs2s_settings_form_group_row", children: [ CreateElement("div", { class: "cs2s_settings_form_group_item" + (type.disabled ? " cs2s_settings_form_disabled" : ""), children: [ CreateElement("label", { text: "Type", for: "type" }), type ] }), CreateElement("div", { class: "cs2s_settings_form_group_item" + (quality.disabled ? " cs2s_settings_form_disabled" : ""), children: [ CreateElement("label", { text: "Category", for: "quality" }), quality ] }), CreateElement("div", { class: "cs2s_settings_form_group_item" + (rarity.disabled ? " cs2s_settings_form_disabled" : ""), children: [ CreateElement("label", { text: "Quality", for: "rarity" }), rarity ] }) ] }) ] }), CreateElement("div", { class: "cs2s_settings_form_group_title" + (floatMax.disabled && seedMax.disabled && cosmeticsMax.disabled ? " cs2s_settings_form_disabled" : ""), text: "Skin Filters" }), CreateElement("div", { class: "cs2s_settings_form_group", children: [ CreateElement("div", { class: "cs2s_settings_form_group_row cs2s_settings_form_group_collection" + (floatMax.disabled ? " cs2s_settings_form_disabled" : ""), children: [ CreateElement("div", { class: "cs2s_settings_form_group_item", children: [ CreateElement("label", { text: "Exterior", for: "float" }), float ] }), CreateElement("div", { class: "cs2s_settings_form_group_item", children: [ CreateElement("label", { text: "Float Min", for: "float_min" }), floatMin ] }), CreateElement("div", { class: "cs2s_settings_form_separator", text: "\u2014" }), CreateElement("div", { class: "cs2s_settings_form_group_item", children: [ CreateElement("label", { text: "Float Max", for: "float_max" }), floatMax ] }) ] }), CreateElement("div", { class: "cs2s_settings_form_group_row", children: [ CreateElement("div", { class: "cs2s_settings_form_group_row cs2s_settings_form_group_collection" + (seedMax.disabled ? " cs2s_settings_form_disabled" : ""), children: [ CreateElement("div", { class: "cs2s_settings_form_group_item", children: [ CreateElement("label", { text: "Seed Min", for: "seed_min" }), seedMin ] }), CreateElement("div", { class: "cs2s_settings_form_separator", text: "\u2014" }), CreateElement("div", { class: "cs2s_settings_form_group_item", children: [ CreateElement("label", { text: "Seed Max", for: "seed_max" }), seedMax ] }) ] }), CreateElement("div", { class: "cs2s_settings_form_separator", text: " " }), CreateElement("div", { class: "cs2s_settings_form_group_row cs2s_settings_form_group_collection" + (cosmeticsMax.disabled ? " cs2s_settings_form_disabled" : ""), children: [ CreateElement("div", { class: "cs2s_settings_form_group_item", children: [ CreateElement("label", { text: "Num Cosmetics Min", for: "cosmetics_min" }), cosmeticsMin ] }), CreateElement("div", { class: "cs2s_settings_form_separator", text: "\u2014" }), CreateElement("div", { class: "cs2s_settings_form_group_item", children: [ CreateElement("label", { text: "Num Cosmetics Max", for: "cosmetics_max" }), cosmeticsMax ] }) ] }) ] }) ] }), CreateElement("div", { class: "cs2s_settings_form_group_title" + (collection.disabled && selected.disabled ? " cs2s_settings_form_disabled" : ""), text: "Group Filters" }), CreateElement("div", { class: "cs2s_settings_form_group", children: [ CreateElement("div", { class: "cs2s_settings_form_group_item" + (collection.disabled ? " cs2s_settings_form_disabled" : ""), children: [ CreateElement("label", { text: "Collection", for: "collection" }), collection ] }), CreateElement("div", { class: "cs2s_settings_form_group_item cs2s_settings_form_group_item_checkbox" + (selected.disabled ? " cs2s_settings_form_disabled" : ""), children: [ selected, CreateElement("label", { text: "Show only selected items", for: "selected" }) ] }) ] }), CreateElement("div", { class: "cs2s_settings_form_submit_group", children: [ CreateElement("button", { class: "cs2s_blue_long_button", type: "submit", text: "Filter" }), CreateElement("button", { class: "cs2s_grey_long_button", type: "reset", text: "Reset" }), CreateElement("button", { class: "cs2s_grey_long_button", id: "form_cancel", text: "Cancel" }) ] }) ] }); const popup = new Popup({ title: "Filter Items", body: [form], popoverMode: true, onclose: () => { this.#tableContainer.focus(); } }); form.querySelector("#form_cancel").onclick = () => { popup.Hide(); }; form.onsubmit = (event) => { event.preventDefault(); this.#filter = { type: type.disabled || type.selectedIndex == 0 ? null : { key: type.selectedOptions[0].dataset.key, value: type.value }, quality: quality.disabled || quality.selectedIndex == 0 ? null : parseInt(quality.value), rarity: rarity.disabled || rarity.selectedIndex == 0 ? null : { key: rarity.selectedOptions[0].dataset.key, value: parseInt(rarity.value) }, wear: float.value == "" ? null : float.value, float: floatMin.value == "" && floatMax.value == "" ? null : { min: floatMin.value == "" ? null : parseFloat(floatMin.value), max: floatMax.value == "" ? null : parseFloat(floatMax.value) }, seed: seedMin.value == "" && seedMax.value == "" ? null : { min: seedMin.value == "" ? null : parseInt(seedMin.value), max: seedMax.value == "" ? null : parseInt(seedMax.value) }, cosmetics: cosmeticsMin.value == "" && cosmeticsMax.value == "" ? null : { min: cosmeticsMin.value == "" ? null : parseInt(cosmeticsMin.value), max: cosmeticsMax.value == "" ? null : parseInt(cosmeticsMax.value) }, collection: collection.disabled || collection.selectedIndex == 0 ? null : collection.value, selected: selected.checked ? true : null }; if (Object.values(this.#filter).every((value) => value === null)) { this.#filter = null; } if (this.#filter === null) { button.classList.remove("cs2s_table_footer_action_link_active"); } else { button.classList.add("cs2s_table_footer_action_link_active"); } popup.Hide(); this.#FilterRows(); }; popup.Show(); } async #ProcessSelected() { if (this.#selectedRows.size == 0) { return; } const numItemsToProcess = this.#selectedRows.size; const itemWindow = CreateElement("div", { class: "cs2s_action_item_window" }); const progressMessage = CreateElement("div", { class: "cs2s_action_message", text: this.#mode == _Table.MODE.RETRIEVE ? "Retrieving Items" : "Storing Items" }); const progressBar = CreateElement("div", { class: "cs2s_action_progress_bar", vars: { "percentage": "0%" } }); const closeButton = CreateElement("div", { class: "cs2s_grey_long_button", text: "Cancel" }); const popupBody = CreateElement("div", { class: "cs2s_action_body", children: [ itemWindow, progressMessage, progressBar, closeButton ] }); const worker = new Worker({ concurrentLimit: 6, delay: 1e3 / 6 }); const popup = new Popup({ title: this.#mode == _Table.MODE.RETRIEVE ? "Retrieving From Storage Unit" : "Moving To Storage Unit", body: [popupBody], simpleMode: true, popoverMode: true, onclose: () => { this.#tableContainer.focus(); worker.Cancel(); } }); closeButton.onclick = () => { popup.Hide(); }; popup.Show(); let numItemsProcessed = 0; for (const assetID of this.#selectedRows) { worker.Add(async () => { const item = this.#data.find((item2) => item2.iteminfo.id == assetID); const itemImage = CreateElement("div", { class: "cs2s_action_item_window_image", html: this.#GetRowElement(item).children[0].innerHTML }); const maxAttempts = 3; for (let attempt = 1; attempt <= maxAttempts; attempt++) { if (worker.cancelled) { return; } try { if (this.#mode == _Table.MODE.RETRIEVE) { await this.#inventory.RetrieveItem(item); } else { await this.#inventory.StoreItem(item, this.#casket); } break; } catch (e) { if (worker.cancelled || e.code === 504 && attempt < maxAttempts) { script_default.ShowError({ level: ERROR_LEVEL.LOW }, e); await Sleep(Random(1e3, 2e3)); continue; } worker.Cancel(); popup.Hide(); script_default.ShowError({ level: ERROR_LEVEL.HIGH }, e, new Error(`Failed to ${this.#mode == _Table.MODE.RETRIEVE ? "retrieve" : "store"} "${item.full_name}". If errors persist, reload the page and try again.`)); return; } } const dataIndex = this.#data.findIndex((item2) => item2.iteminfo.id == assetID); this.#data.splice(dataIndex, 1); const filteredDataIndex = this.#filteredData.findIndex((item2) => item2.iteminfo.id == assetID); filteredDataIndex != -1 && this.#filteredData.splice(filteredDataIndex, 1); this.#selectedRows.delete(assetID); this.#selectionLimit--; this.#inventoryChanged = true; this.#UpdateTable(); numItemsProcessed++; progressMessage.innerText = (this.#mode == _Table.MODE.RETRIEVE ? "Retrieving Items" : "Storing Items") + ` (${numItemsProcessed}/${numItemsToProcess})`; progressBar.style.setProperty("--percentage", `${(numItemsProcessed / numItemsToProcess * 100).toFixed(2)}%`); itemWindow.append(itemImage); const transform = getComputedStyle(itemImage).transform; itemImage.style.transformOrigin = "50% 700%"; const rotateStart = Random(-20, -10); const rotateEnd = Random(10, 20); const scaleEnd = Random(0.7, 0.8); const translateYEnd = Random(-215, -165); const duration = Random(700, 800); const itemAnimationIn = itemImage.animate([ { transform: `${transform} rotate(${rotateStart}deg)`, opacity: 0.75, offset: 0 }, { opacity: 1, filter: "brightness(1)", offset: 0.5 }, { transform: `${transform} rotate(${rotateEnd}deg) scale(${scaleEnd}) translateY(${translateYEnd}%)`, opacity: 0.5, filter: "brightness(0.1)", offset: 1 } ], { duration, easing: "cubic-bezier(.15, .50, .85, .50)" }); itemAnimationIn.onfinish = () => { itemImage.remove(); }; }); } worker.Run(); await worker.Finish(); await Sleep(1e3); popup.Hide(); } }; // src/cs2/items/assets/inventory_asset.js var InventoryAsset = class extends Asset { _asset; constructor(asset) { super(); this._assetid = asset.assetid; this._asset = asset; if (asset.description.market_hash_name == "Storage Unit") { this._type = Asset.TYPE.STORAGE_UNIT; } else { for (const tag of asset.description.tags) { if (tag.category == "Weapon" || tag.internal_name == "Type_Hands") { this._type = Asset.TYPE.WEARABLE; break; } else if (tag.internal_name == "CSGO_Tool_Keychain") { this._type = Asset.TYPE.KEYCHAIN; break; } } } if (typeof this._type == "undefined") { this._type = Asset.TYPE.OTHER; } if (this._type == Asset.TYPE.WEARABLE) { for (const action of asset.description.actions) { if (action.link.includes("steam://rungame")) { this._inspectLink = action.link.replace("%owner_steamid%", unsafeWindow.g_ActiveUser.strSteamId).replace("%assetid%", asset.assetid); break; } } } } async BuildInventoryUI() { if (this.ShouldInspect() && GetSetting(SETTING_INSPECT_ITEMS)) { const build = () => { if (!this._inspectData) { return; } if (this._inspectData.wear && this._wearData) { this._asset.element.append( CreateElement("div", { class: `cs2s_asset_wear cs2s_asset_wear_${this._wearData.name.toLowerCase()}`, text: this._inspectData.wear.toFixed(6), children: [ " ", this._GetPercentileElement() // createElement("div", { // class: "cs2s_asset_wear_name", // text: this._wearData.name // }) ] }) ); } if (this._inspectData.seed) { this._asset.element.append( CreateElement("div", { class: "cs2s_asset_seed", text: this._inspectData.seed }) ); } if (this._inspectData.rarity) { const el = CreateElement("div", { class: `cs2s_asset_rarity cs2s_asset_rarity_${this._inspectData.rarity} cs2s_asset_quality_${this._inspectData.quality}` }); if (this._inspectData.stattrak) { el.classList.add("cs2s_asset_stattrak"); } this._asset.element.append(el); } const cosmetics = []; for (const description of this._asset.description.descriptions) { if (description.name == "sticker_info" || description.name == "keychain_info") { const parser = new DOMParser(); const doc = parser.parseFromString(description.value, "text/html"); for (const img of doc.querySelectorAll("img")) { cosmetics.push(CreateElement("img", { src: img.src })); } } } if (cosmetics.length > 0) { this._asset.element.append( CreateElement("div", { class: "cs2s_asset_cosmetics", children: cosmetics }) ); } }; let cached; try { cached = await this._Inspect({ cacheOnly: true }); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return; } if (cached) { build(); } else { Asset._inspectionWorker.Add(async () => { try { await this._Inspect(); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return; } build(); }); Asset._inspectionWorker.Run(); } } else if (this._type == Asset.TYPE.KEYCHAIN && GetSetting(SETTING_INSPECT_ITEMS)) { let template; for (const description of this._asset.description.descriptions) { if (description.name == "attr: keychain slot 0 seed") { const matches = description.value.match(/\d+/); if (matches) { template = matches[0]; } break; } } if (template) { this._asset.element.appendChild( CreateElement("div", { class: "cs2s_asset_seed", text: template }) ); } } else if (this._type == Asset.TYPE.STORAGE_UNIT) { let nameTag; let itemCount; for (const description of this._asset.description.descriptions) { if (description.name == "nametag") { const matches = description.value.match(/.*?''(.*?)''/); if (matches) { nameTag = matches[1]; } } else if (description.name == "attr: items count") { const matches = description.value.match(/\d+/); if (matches) { itemCount = matches[0]; } } } if (nameTag) { this._asset.element.append( CreateElement("div", { class: "cs2s_asset_name", text: nameTag }) ); } if (itemCount) { this._asset.element.append( CreateElement("div", { class: "cs2s_asset_quantity", text: itemCount }) ); } } } async BuildSelectedUI() { const selectedItem = unsafeWindow.iActiveSelectView; const descriptionsElement = unsafeWindow.document.getElementById(`iteminfo${selectedItem}_item_descriptors`); const stickerElements = descriptionsElement.getElementsBySelector("#sticker_info img"); const charmElements = descriptionsElement.getElementsBySelector("#keychain_info img"); const ownerActionsElement = unsafeWindow.document.getElementById(`iteminfo${selectedItem}_item_owner_actions`); if (this.ShouldInspect() && GetSetting(SETTING_INSPECT_ITEMS)) { const build = () => { if (selectedItem != unsafeWindow.iActiveSelectView || this._asset != unsafeWindow.g_ActiveInventory.selectedItem || !this._inspectData) { return; } if (this._inspectData.wear && this._inspectData.seed) { descriptionsElement.prepend( this._GetWearRangeElement(), CreateElement("div", { class: "descriptor", text: `Float: ${this._inspectData.wear.toFixed(14)}`, children: [ " ", this._GetPercentileElement({ showTooltip: true, rounded: false }) ] }), CreateElement("div", { class: "descriptor", text: `Seed: ${this._inspectData.seed}` }), CreateElement("div", { class: "descriptor", text: "\xA0" }) ); } if (this._inspectData.stickers) { for (let i = 0; i < this._inspectData.stickers.length; i++) { if (typeof stickerElements[i] == "undefined") { break; } stickerElements[i].wrap( CreateElement("span", { class: "cs2s_asset_sticker_wear", wear: Math.round(this._inspectData.stickers[i] * 100) }) ); } } if (this._inspectData.charm) { if (typeof charmElements[0] != "undefined") { charmElements[0].wrap( CreateElement("span", { class: "cs2s_asset_charm_template", template: this._inspectData.charm }) ); } } }; let cached; try { cached = await this._Inspect({ cacheOnly: true }); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return; } if (cached) { build(); } else { Asset._inspectionWorker.Add(async () => { try { await this._Inspect(); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return; } build(); }, { priority: true }); Asset._inspectionWorker.Run(); } } else if (this._type == Asset.TYPE.STORAGE_UNIT) { ownerActionsElement.style.display = "block"; ownerActionsElement.append( CreateElement("a", { class: "btn_small btn_grey_white_innerfade", html: "<span>Retrieve Items</span>", onclick: async () => { const inventory = await script_default.GetInventory({ showProgress: true }); if (inventory === OPERATION_ERROR.INTERFACE_NOT_CONNECTED) { script_default.ShowStartInterfacePrompt({ message: "Interface must be running to fetch stored items", fade: false }); return; } if (inventory === OPERATION_ERROR.INVENTORY_FAILED_TO_LOAD) { script_default.ShowError({ level: ERROR_LEVEL.HIGH }, new Error("Inventory failed to load, check error logs and refresh the page to try again")); return; } if (!(inventory instanceof Inventory)) { return; } const casket = inventory.items.find((x) => x.iteminfo.id == this._assetid); const table = new Table(inventory.storedItems.filter((x) => x.casket_id == this._assetid), inventory, { mode: Table.MODE.RETRIEVE, casket, casketName: casket.attributes["custom name attr"] }); table.Show(); } }), CreateElement("a", { class: "btn_small btn_grey_white_innerfade", html: "<span>Deposit Items</span>", onclick: async () => { const inventory = await script_default.GetInventory({ showProgress: true }); if (inventory === OPERATION_ERROR.INTERFACE_NOT_CONNECTED) { script_default.ShowStartInterfacePrompt({ message: "Interface must be running to fetch inventory items" }); return; } if (inventory === OPERATION_ERROR.INVENTORY_FAILED_TO_LOAD) { script_default.ShowError({ level: ERROR_LEVEL.HIGH }, new Error("Inventory failed to load, check error logs and refresh the page to try again")); return; } if (!(inventory instanceof Inventory)) { return; } const casket = inventory.items.find((x) => x.iteminfo.id == this._assetid); const table = new Table(inventory.items.filter((x) => x.moveable), inventory, { mode: Table.MODE.STORE, casket, casketName: casket.attributes["custom name attr"] }); table.Show(); } }) ); } } }; // src/cs2/items/assets/market_asset.js var MarketAsset = class _MarketAsset extends Asset { _listingid; static #builtItemPageUI = false; constructor(asset, listing) { super(); this._assetid = asset.id; this._listingid = listing?.listingid; for (const description of asset.descriptions) { if (description.name == "exterior_wear") { this._type = Asset.TYPE.WEARABLE; break; } } if (typeof this._type == "undefined") { this._type = Asset.TYPE.OTHER; } if (this._type == Asset.TYPE.WEARABLE) { for (const action of asset.market_actions) { if (action.link.includes("steam://rungame")) { this._inspectLink = action.link.replace("%assetid%", this._assetid); break; } } } } async BuildListingUI() { if (this.ShouldInspect() && GetSetting(SETTING_INSPECT_ITEMS)) { const build = () => { if (!this._inspectData) { return; } const listingDetailsElement = unsafeWindow.document.getElementById(`listing_${this._listingid}_details`); if (!listingDetailsElement) { return; } if (listingDetailsElement.getElementsBySelector(".cs2s_listing_info").length != 0) { return; } const stickerElements = listingDetailsElement.getElementsBySelector("#sticker_info img"); const charmElements = listingDetailsElement.getElementsBySelector("#keychain_info img"); if (this._inspectData.wear && this._inspectData.seed) { if (!_MarketAsset.#builtItemPageUI) { _MarketAsset.#builtItemPageUI = true; this.#BuiltItemPageUI(); } listingDetailsElement.prepend( CreateElement("div", { class: "cs2s_listing_info", children: [ CreateElement("div", { text: `Float: ${this._inspectData.wear.toFixed(14)}`, children: [ " ", this._GetPercentileElement({ showTooltip: true, rounded: false }) ] }), CreateElement("div", { text: `Seed: ${this._inspectData.seed}` }) ] }) ); } if (this._inspectData.stickers) { for (let i = 0; i < this._inspectData.stickers.length; i++) { if (typeof stickerElements[i] == "undefined") { break; } stickerElements[i].wrap( CreateElement("span", { class: "cs2s_asset_sticker_wear cs2s_asset_cosmetic_small", wear: Math.round(this._inspectData.stickers[i] * 100) }) ); } } if (this._inspectData.charm) { if (typeof charmElements[0] != "undefined") { charmElements[0].wrap( CreateElement("span", { class: "cs2s_asset_charm_template cs2s_asset_cosmetic_small", template: this._inspectData.charm }) ); } } }; let cached; try { cached = await this._Inspect({ cacheOnly: true }); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return; } if (cached) { build(); } else { Asset._inspectionWorker.Add(async () => { const listingDetailsElement = unsafeWindow.document.getElementById(`listing_${this._listingid}_details`); if (!listingDetailsElement) { return; } try { await this._Inspect(); } catch (e) { script_default.ShowError({ level: ERROR_LEVEL.MEDIUM }, e); return; } build(); }); Asset._inspectionWorker.Run(); } } } #BuiltItemPageUI() { if (this._inspectData.wear && this._inspectData.seed) { const descriptionsElement = unsafeWindow.document.getElementById(`largeiteminfo_item_descriptors`); if (descriptionsElement) { descriptionsElement.prepend(this._GetWearRangeElement(true)); } } } }; // src/css/style.css var style_default = `:root { --cs2s-wear-fn-color: #35cdff; --cs2s-wear-mw-color: #0bb52f; --cs2s-wear-ft-color: #e8c805; --cs2s-wear-ww-color: #e86f24; --cs2s-wear-bs-color: #ea2828; --cs2s-stattrak-color: #cf6a32; --cs2s-quality-3-color: #8650ac; --cs2s-quality-12-color: #ffd700; --cs2s-rarity-7-color: #e4ae33; --cs2s-rarity-6-color: #eb4b4b; --cs2s-rarity-5-color: #d32ce6; --cs2s-rarity-4-color: #8847ff; --cs2s-rarity-3-color: #4b69ff; --cs2s-rarity-2-color: #5e98d9; --cs2s-rarity-1-color: #b0c3d9; } .cs2s_color_wear_fn { color: var(--cs2s-wear-fn-color) !important; } .cs2s_color_wear_mw { color: var(--cs2s-wear-mw-color) !important; } .cs2s_color_wear_ft { color: var(--cs2s-wear-ft-color) !important; } .cs2s_color_wear_ww { color: var(--cs2s-wear-ww-color) !important; } .cs2s_color_wear_bs { color: var(--cs2s-wear-bs-color) !important; } .cs2s_color_rarity_7 { color: var(--cs2s-rarity-7-color) !important; } .cs2s_color_rarity_6 { color: var(--cs2s-rarity-6-color) !important; } .cs2s_color_rarity_5 { color: var(--cs2s-rarity-5-color) !important; } .cs2s_color_rarity_4 { color: var(--cs2s-rarity-4-color) !important; } .cs2s_color_rarity_3 { color: var(--cs2s-rarity-3-color) !important; } .cs2s_color_rarity_2 { color: var(--cs2s-rarity-2-color) !important; } .cs2s_color_rarity_1 { color: var(--cs2s-rarity-1-color) !important; } .cs2s_color_quality_1 { color: var(--cs2s-stattrak-color) !important; } .cs2s_color_quality_2 { color: var(--cs2s-quality-12-color) !important; } .cs2s_color_quality_3 { color: var(--cs2s-quality-3-color) !important; } .cs2s_color_quality_4 { color: var(--cs2s-quality-3-color) !important; } .cs2s_asset_wear { position: absolute; bottom: 0px; right: 0px; left: 0px; padding-right: 4px; text-align: right; text-overflow: ellipsis; font-size: 8pt; max-width: 100%; color: #b7becd; &::after { content: ""; position: absolute; top: 0; right: 2px; left: 2px; height: 4px; border-radius: 4px; opacity: 85%; } .cs2s_asset_wear_name { position: absolute; z-index: 1; bottom: 12px; right: 6px; font-size: 8pt; font-weight: 800; opacity: 95%; text-shadow: 0px 2px 4px #000000bf; &::before { content: ''; position: absolute; top: 4px; left: -6px; width: 0; height: 0; border: 8px solid transparent; border-radius: 100%; transform: rotate(315deg); } } &.cs2s_asset_wear_fn { color: color-mix(in srgb, var(--cs2s-wear-fn-color), #cdcdcd 66%); text-shadow: -3px -1px 10px var(--cs2s-wear-fn-color); background: repeating-linear-gradient(transparent, color-mix(in srgb, var(--cs2s-wear-fn-color), transparent 85%) 2px, transparent, color-mix(in srgb, var(--cs2s-wear-fn-color), transparent 85%) 2px); &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-fn-color), transparent 40%), transparent); } .cs2s_asset_wear_name { color: var(--cs2s-wear-fn-color); &::before { border-top-color: color-mix(in srgb, var(--cs2s-wear-fn-color), transparent 40%); border-right-color: color-mix(in srgb, var(--cs2s-wear-fn-color), transparent 65%); } &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-fn-color), transparent 40%), transparent); } } } &.cs2s_asset_wear_mw { color: color-mix(in srgb, var(--cs2s-wear-mw-color), #cdcdcd 66%); text-shadow: -3px -1px 10px var(--cs2s-wear-mw-color); background: repeating-linear-gradient(transparent, color-mix(in srgb, var(--cs2s-wear-mw-color), transparent 85%) 2px, transparent, color-mix(in srgb, var(--cs2s-wear-mw-color), transparent 85%) 2px); &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-mw-color), transparent 40%), transparent); } .cs2s_asset_wear_name { color: var(--cs2s-wear-mw-color); &::before { border-top-color: color-mix(in srgb, var(--cs2s-wear-mw-color), transparent 40%); border-right-color: color-mix(in srgb, var(--cs2s-wear-mw-color), transparent 65%); } &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-mw-color), transparent 40%), transparent); } } } &.cs2s_asset_wear_ft { color: color-mix(in srgb, var(--cs2s-wear-ft-color), #cdcdcd 66%); text-shadow: -3px -1px 10px var(--cs2s-wear-ft-color); background: repeating-linear-gradient(transparent, color-mix(in srgb, var(--cs2s-wear-ft-color), transparent 85%) 2px, transparent, color-mix(in srgb, var(--cs2s-wear-ft-color), transparent 85%) 2px); &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-ft-color), transparent 40%), transparent); } .cs2s_asset_wear_name { color: var(--cs2s-wear-ft-color); &::before { border-top-color: color-mix(in srgb, var(--cs2s-wear-ft-color), transparent 40%); border-right-color: color-mix(in srgb, var(--cs2s-wear-ft-color), transparent 65%); } &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-ft-color), transparent 40%), transparent); } } } &.cs2s_asset_wear_ww { color: color-mix(in srgb, var(--cs2s-wear-ww-color), #cdcdcd 66%); text-shadow: -3px -1px 10px var(--cs2s-wear-ww-color); background: repeating-linear-gradient(transparent, color-mix(in srgb, var(--cs2s-wear-ww-color), transparent 85%) 2px, transparent, color-mix(in srgb, var(--cs2s-wear-ww-color), transparent 85%) 2px); &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-ww-color), transparent 40%), transparent); } .cs2s_asset_wear_name { color: var(--cs2s-wear-ww-color); &::before { border-top-color: color-mix(in srgb, var(--cs2s-wear-ww-color), transparent 40%); border-right-color: color-mix(in srgb, var(--cs2s-wear-ww-color), transparent 65%); } &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-ww-color), transparent 40%), transparent); } } } &.cs2s_asset_wear_bs { color: color-mix(in srgb, var(--cs2s-wear-bs-color), #cdcdcd 66%); text-shadow: -3px -1px 10px var(--cs2s-wear-bs-color); background: repeating-linear-gradient(transparent, color-mix(in srgb, var(--cs2s-wear-bs-color), transparent 85%) 2px, transparent, color-mix(in srgb, var(--cs2s-wear-bs-color), transparent 85%) 2px); &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-bs-color), transparent 40%), transparent); } .cs2s_asset_wear_name { color: var(--cs2s-wear-bs-color); &::before { border-top-color: color-mix(in srgb, var(--cs2s-wear-bs-color), transparent 40%); border-right-color: color-mix(in srgb, var(--cs2s-wear-bs-color), transparent 65%); } &::after { background: linear-gradient(180deg, color-mix(in srgb, var(--cs2s-wear-bs-color), transparent 40%), transparent); } } } } .cs2s_asset_seed { position: absolute; top: 0px; right: 0px; padding-right: 4px; font-size: 8pt; max-width: 100%; } .cs2s_asset_rarity { position: absolute; top: 0px; left: 0px; bottom: 11px; right: 6px; &::before { content: ''; position: absolute; top: 0; width: 0; height: 0; border-style: solid; border-width: 15px 20px 0 0; border-color: transparent; } &::after { position: absolute; left: 1px; top: 0; padding: 0px 0px 1px 3px; font-size: 8pt; font-weight: 500; text-shadow: 0px 2px 4px #000000bf; } &.cs2s_asset_stattrak { &::before { border-top-color: color-mix(in srgb, var(--cs2s-stattrak-color), transparent 65%) !important; } &::after { color: var(--cs2s-stattrak-color) !important; } } &.cs2s_asset_quality_3 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-quality-3-color), transparent 65%) !important; } &::after { content: '\u2605' !important; color: var(--cs2s-quality-3-color) !important; } } &.cs2s_asset_quality_12 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-quality-12-color), transparent 65%) !important; } &::after { color: var(--cs2s-quality-12-color) !important; } } &.cs2s_asset_rarity_7 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-rarity-7-color), transparent 65%); } &::after { content: '7'; color: var(--cs2s-rarity-7-color); } } &.cs2s_asset_rarity_6 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-rarity-6-color), transparent 65%); } &::after { content: '6'; color: var(--cs2s-rarity-6-color); } } &.cs2s_asset_rarity_5 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-rarity-5-color), transparent 65%); } &::after { content: '5'; color: var(--cs2s-rarity-5-color); } } &.cs2s_asset_rarity_4 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-rarity-4-color), transparent 65%); } &::after { content: '4'; color: var(--cs2s-rarity-4-color); } } &.cs2s_asset_rarity_3 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-rarity-3-color), transparent 65%); } &::after { content: '3'; color: var(--cs2s-rarity-3-color); } } &.cs2s_asset_rarity_2 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-rarity-2-color), transparent 65%); } &::after { content: '2'; color: var(--cs2s-rarity-2-color); } } &.cs2s_asset_rarity_1 { &::before { border-top-color: color-mix(in srgb, var(--cs2s-rarity-1-color), transparent 65%); } &::after { content: '1'; color: var(--cs2s-rarity-1-color); } } } .cs2s_asset_quantity { position: absolute; top: 0px; left: 0px; padding-left: 4px; font-size: 8pt; max-width: 100%; } .cs2s_asset_name { position: absolute; bottom: 0px; left: 0px; right: 0px; padding: 1px 4px 0px 4px; overflow: hidden; margin: 4px; border-radius: 2px; border-bottom: 2px solid #5f6a76; font-size: 9pt; max-width: 100%; color: #b7becd; text-shadow: 0px 2px 4px #000000bf; text-overflow: ellipsis; text-align: center; background: #505050bf; background-image: radial-gradient( #b7becd0d 1px, transparent 0); background-size: 4px 4px; background-position: -4px -3px; } .cs2s_asset_cosmetics { position: absolute; bottom: 14px; left: 0px; img { height: 19px; width: 25px !important; filter: drop-shadow(0px 0px 0px #1c1c23); } &:has(> :nth-child(2)):has(> :nth-child(-n+5):last-child) img:nth-child(n+2) { margin-left: -8px; } &:has(> :nth-child(6)) img:nth-child(n+2) { margin-left: -12px; } } .cs2s_asset_sticker_wear, .cs2s_asset_charm_template { position: relative; display: inline-block; padding-bottom: 16px; &::before { position: absolute; bottom: 0px; left: 0px; right: 0px; } &.cs2s_asset_cosmetic_small { padding-bottom: 0px; top: -6px; &::before { bottom: -12px; font-size: 11px; text-align: center; } } } .cs2s_asset_sticker_wear::before { content: attr(wear) '%'; } .cs2s_asset_charm_template::before { content: attr(template); } .cs2s_asset_wear_range { margin: 0px 0px 8px 4px; & > div { width: 50px; height: 4px; display: inline-block; position: relative; background: linear-gradient(360deg, rgb(50 50 50 / 40%), transparent); &:not(:last-child) { border-right: 1px solid #1d1d1dbf; } &.cs2s_asset_wear_range_left, &.cs2s_asset_wear_range_right, &.cs2s_asset_wear_range_empty { background-color: #91919180 !important; } &.cs2s_asset_wear_range_left::before, &.cs2s_asset_wear_range_right::before { content: ""; width: var(--wear-percentage); height: 100%; display: block; position: absolute; background: linear-gradient(360deg, rgb(50 50 50 / 40%), transparent); } &.cs2s_asset_wear_range_left::before { left: 0px; } &.cs2s_asset_wear_range_right::before { right: 0px; } &.cs2s_asset_wear_range_fn { border-radius: 4px 0px 0px 4px; background-color: var(--cs2s-wear-fn-color); &::before { background-color: var(--cs2s-wear-fn-color); } & .cs2s_asset_wear_range_low, .cs2s_asset_wear_range_high { background-color: var(--cs2s-wear-fn-color); } } &.cs2s_asset_wear_range_mw { background-color: var(--cs2s-wear-mw-color); &::before { background-color: var(--cs2s-wear-mw-color); } & .cs2s_asset_wear_range_low, .cs2s_asset_wear_range_high { background-color: var(--cs2s-wear-mw-color); } } &.cs2s_asset_wear_range_ft { background-color: var(--cs2s-wear-ft-color); &::before { background-color: var(--cs2s-wear-ft-color); } & .cs2s_asset_wear_range_low, .cs2s_asset_wear_range_high { background-color: var(--cs2s-wear-ft-color); } } &.cs2s_asset_wear_range_ww { background-color: var(--cs2s-wear-ww-color); &::before { background-color: var(--cs2s-wear-ww-color); } & .cs2s_asset_wear_range_low, .cs2s_asset_wear_range_high { background-color: var(--cs2s-wear-ww-color); } } &.cs2s_asset_wear_range_bs { border-radius: 0px 4px 4px 0px; background-color: var(--cs2s-wear-bs-color); &::before { background-color: var(--cs2s-wear-bs-color); } & .cs2s_asset_wear_range_low, .cs2s_asset_wear_range_high { background-color: var(--cs2s-wear-bs-color); } } } & .cs2s_asset_wear_range_low, .cs2s_asset_wear_range_high { position: absolute; top: -2px; bottom: -2px; width: 2px; &::before { content: attr(wear_value); position: absolute; bottom: 6px; left: -2px; font-size: 8pt; } } & .cs2s_asset_wear_range_low { right: var(--wear-percentage); transform: translate(50%, 0%); } & .cs2s_asset_wear_range_high { left: var(--wear-percentage); transform: translate(-50%, 0%); } & .cs2s_asset_wear_range_marker { position: absolute; top: 3px; left: var(--wear-percentage); transform: translate(-50%, 0%); width: 0; height: 0; border-style: solid; border-width: 0px 5px 5px 5px; border-color: transparent; border-bottom-color: white; } } .cs2s_asset_rank_gold { background: linear-gradient( #ffe34b 50%, #ff8f00); background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 1px 1px 10px #ffe34b40, 1px 1px 10px #ff8f0040; border-color: #ffe34b; } .cs2s_asset_rank_silver { background: linear-gradient( #ffffff 50%, #fdfdfd40); background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 1px 1px 10px #ffffff40, 1px 1px 10px #fdfdfd40; border-color: #ffffff; } .cs2s_asset_rank_bronze { background: linear-gradient( #e7913d 50%, #bb2f0f); background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 1px 1px 10px #e7913d40, 1px 1px 10px #bb2f0f40; border-color: #e7913d; } .cs2s_asset_rank_rust { background: linear-gradient( #ff5716 50%, #daddb2); background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 1px 1px 10px #ff57161a, 1px 1px 10px #daddb21a, #9d242480 -1px -1px 0px; border-color: #ff5716; } .cs2s_listing_info { width: 250px; padding: 10px 8px 0px 0px; font-size: 14px; & + br + div { border-left: 1px solid #404040; padding-left: 16px !important; } } .cs2s_has_tooltip { border-bottom-width: 1px; border-bottom-style: dashed; } .cs2s_tooltip { position: absolute; pointer-events: none; opacity: 0; transition: opacity 0.2s; z-index: 9999; background: #c2c2c2; color: #3d3d3f; font-size: 11px; border-radius: 3px; padding: 5px; max-width: 300px; white-space: normal; box-shadow: 0 0 3px #000000; } .cs2s_popup_opened { overflow: hidden; } .cs2s_popup_background { position: fixed; top: 0px; right: 0px; bottom: 0px; left: 0px; background: #000000; opacity: 80%; } .cs2s_popup_container { position: fixed; top: 0px; right: 0px; bottom: 0px; left: 0px; display: flex; justify-content: center; align-items: center; } .cs2s_popup_body { position: fixed; background: radial-gradient(circle at top left, rgba(74, 81, 92, 0.4) 0%, rgba(75, 81, 92, 0) 60%), #25282e; &:not(.cs2s_popup_body_simple) { padding-top: 3px; &::after { content: ""; position: absolute; top: 0px; left: 0px; right: 0px; height: 1px; background: linear-gradient(to right, #00ccff, #3366ff); } .cs2s_popup_title { padding: 24px; font-size: 22px; font-weight: 700; color: #ffffff; } } &.cs2s_popup_body_simple .cs2s_popup_title { min-width: 400px; padding: 16px; font-size: 20px; font-weight: 500; text-align: center; color: #fff; background: #1b1b2280; } &.cs2s_popup_body_popover { box-shadow: 0px 0px 16px 8px #00000060; } &:has(.cs2s_popup_footer) { border-radius: 0px 0px 10px 10px; margin-bottom: 64px; } } .cs2s_popup_close_button { float: right; height: 16px; width: 16px; margin-top: 9px; margin-right: 9px; cursor: pointer; opacity: 70%; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjVBREZBRDlBNzdCNzExRTI5ODAzRUE3MDU0Mzk5MjM5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjVBREZBRDlCNzdCNzExRTI5ODAzRUE3MDU0Mzk5MjM5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NUFERkFEOTg3N0I3MTFFMjk4MDNFQTcwNTQzOTkyMzkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NUFERkFEOTk3N0I3MTFFMjk4MDNFQTcwNTQzOTkyMzkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4iulRzAAABPElEQVR42mI8cuQIAyWABUqbAXEylH0fiDtwqI8FYhsoewEQH4cZcBaI64DYG8p/AVWADDyAeD4QMwPxJiA+BRJkgkr+BeJoIL4D5U8DYi0kzepAvAKq+TJU7V9kA0DgIxD7Q2lOIF4NpfmBeAuUfg3EPkD8BaaJCc2Z14A4EcoGuWAC1NkqQPwLqvkRsgYmLAG1HoiboOw0IA6EshNh/iZkAAjUA/FBJP5KIF6GTSEuAwygUQsDflAxogwQAuJ10AAExcpNKHsjEIsSMoAZGl2K0EALBeIIKFsOKSpxGtACxK5Qdi4QX4DiAqiYExB34TIgBIgrkAJtFpLcdKgYCBQBcRRMghGamUBxfhKIeaB+NkFOLFAAkjsDTZXfgdgK5DpYXvBGiqYpWDQzQMVAYZKDlDcuMFKanQECDAAqw0LA+GRiqAAAAABJRU5ErkJggg==); &:hover { opacity: 100%; } } .cs2s_table_title_casket { color: #919191; &::before { content: ""; background: url(https://community.fastly.steamstatic.com/economy/image/-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXX7gNTPcUxqAhWSVieFOX71szWCgwsdlZRsuz0L1M1iqrOIGUauNiyzdmKxKWsMrnXkjlQsIthhO5eh9dfdg/66x45); width: 66px; height: 45px; display: inline-block; vertical-align: middle; filter: drop-shadow(0px 0px 0px #bdbdbd) drop-shadow(2px 4px 6px #1c1c23); } &.cs2s_table_title_casket_multiple { position: relative; margin-left: 16px; &::before { margin-right: 12px; } &::after { content: ""; background: url(https://community.fastly.steamstatic.com/economy/image/-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXX7gNTPcUxqAhWSVieFOX71szWCgwsdlZRsuz0L1M1iqrOIGUauNiyzdmKxKWsMrnXkjlQsIthhO5eh9dfdg/45x31); width: 82px; height: 31px; display: inline-block; vertical-align: middle; filter: drop-shadow(0px 0px 0px #bdbdbd) drop-shadow(2px 4px 6px #1c1c23); position: absolute; left: -14px; top: -1px; z-index: -1; opacity: 85%; } } } .cs2s_table_container { outline: 0; overflow-y: scroll; border-radius: 0px 0px 10px 10px; } .cs2s_table { user-select: none; width: 100%; border-spacing: 0px; .cs2s_table_image_column { width: 93px; } .cs2s_table_name_column { width: 350px; } .cs2s_table_collection_column { width: 250px; } .cs2s_table_float_column { width: 275px; } .cs2s_table_seed_column { width: 150px; } .cs2s_table_crate_column { width: 230px; padding-right: 20px; white-space: nowrap; } td { border-bottom: 1px solid #1c1c23; &:nth-child(n+2) { padding-left: 8px; } } thead { tr { height: 30px; } th { position: sticky; top: 1px; background: #1c1c23; z-index: 2; text-align: left; &:nth-child(n+2) { padding-left: 8px; border-left: 1px solid #3d3d3d; } &:before { content: ""; position: absolute; top: -1px; left: -1px; right: 0px; height: 1px; background: #1c1c23; border-left: 1px solid #3d3d3d; } } } tbody { will-change: transform; } tbody tr { height: 69px; color: #bdbdbd; user-select: none; &.cs2s_table_row_selected { background: #a3cbe3; color: #3c3f44; td { border-color: #badbdb } } .cs2s_table_image_column { position: relative; padding-left: 4px; img { height: 62px; width: 93px; display: block; } .cs2s_table_image_column_cosmetics { position: absolute; bottom: -2px; left: 2px; white-space: nowrap; img { width: 25px; height: 19px; display: inline-block; } &:has(> :nth-child(2)):has(> :nth-child(-n+5):last-child) img:nth-child(n+2) { margin-left: -8px; } &:has(> :nth-child(6)) img:nth-child(n+2) { margin-left: -12px; } } } &:not(.cs2s_table_row_selected) .cs2s_table_image_column { & > img { filter: drop-shadow(0px 0px 0px #bdbdbd) drop-shadow(2px 4px 6px #1c1c23); clip-path: rect(0px 100px 66px 0px); } .cs2s_table_image_column_cosmetics img { filter: drop-shadow(0px 0px 0px #bdbdbd) drop-shadow(4px 0px 4px #1c1c23); } } .cs2s_table_name_column { a { position: relative; top: 1px; left: 2px; opacity: 75%; color: #ffffff; svg { height: 12px; width: 12px; } } } &.cs2s_table_row_selected .cs2s_table_name_column a { color: #000000; } .cs2s_table_collection_column { padding-left: 30px !important; &.cs2s_table_collection_column_has_rarity { position: relative; &::after{ position: absolute; font-size: 8pt; font-weight: 800; left: 5px; top: 50%; width: 21px; text-align: center; transform: translate(0%, -50%); } &.cs2s_table_collection_column_rarity_7::after { content: "7"; color: var(--cs2s-rarity-7-color); } &.cs2s_table_collection_column_rarity_6::after { content: "6"; color: var(--cs2s-rarity-6-color); } &.cs2s_table_collection_column_rarity_5::after { content: "5"; color: var(--cs2s-rarity-5-color); } &.cs2s_table_collection_column_rarity_4::after { content: "4"; color: var(--cs2s-rarity-4-color); } &.cs2s_table_collection_column_rarity_3::after { content: "3"; color: var(--cs2s-rarity-3-color); } &.cs2s_table_collection_column_rarity_2::after { content: "2"; color: var(--cs2s-rarity-2-color); } &.cs2s_table_collection_column_rarity_1::after { content: "1"; color: var(--cs2s-rarity-1-color); } &.cs2s_table_collection_column_stattrak::after { color: var(--cs2s-stattrak-color); } &.cs2s_table_collection_column_quality_3::after { content: "\u2605"; color: var(--cs2s-quality-3-color); } &.cs2s_table_collection_column_quality_12::after { color: var(--cs2s-quality-12-color); } } } &.cs2s_table_row_selected .cs2s_table_collection_column_has_rarity::after { color: #3c3f44 !important; } &:not(.cs2s_table_row_selected) .cs2s_table_collection_column_has_rarity::after { text-shadow: 2px 4px 6px #1c1c23; } .cs2s_table_float_column { padding-left: 36px !important; font-variant-numeric: tabular-nums; &.cs2s_table_float_column_has_float { position: relative; &::after{ position: absolute; font-size: 8pt; font-weight: 800; top: 50%; left: 8px; width: 22px; text-align: center; transform: translate(0%, -50%); } &.cs2s_table_float_column_float_fn::after { content: "FN"; color: var(--cs2s-wear-fn-color); } &.cs2s_table_float_column_float_mw::after { content: "MW"; color: var(--cs2s-wear-mw-color); } &.cs2s_table_float_column_float_ft::after { content: "FT"; color: var(--cs2s-wear-ft-color); } &.cs2s_table_float_column_float_ww::after { content: "WW"; color: var(--cs2s-wear-ww-color); } &.cs2s_table_float_column_float_bs::after { content: "BS"; color: var(--cs2s-wear-bs-color); } } } &.cs2s_table_row_selected .cs2s_table_float_column_has_float::after { color: #3c3f44 !important; } &:not(.cs2s_table_row_selected) .cs2s_table_float_column_has_float::after { text-shadow: 2px 4px 6px #1c1c23; } .cs2s_table_empty { height: 150px; font-size: 20px; font-weight: 500; text-align: center; border-bottom-width: 0px; } } .cs2s_table_column { cursor: pointer; user-select: none; &:hover { color: #ffffff; } &:has(.cs2s_table_column_sort_asc), &:has(.cs2s_table_column_sort_desc) { color: #3a8dde; &:hover { color: #4aa6ff; } } } .cs2s_table_column_sort { position: relative; display: inline-block; height: 15px; width: 15px; vertical-align: text-bottom; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23595959' width='800px' height='800px' viewBox='0 0 36.678 36.678' %3E%3Cg%3E%3Cpath d='M29.696,20.076c0.088,0.16,0.08,0.354-0.021,0.51L19.395,36.449c-0.091,0.139-0.241,0.224-0.407,0.229 c-0.004,0-0.008,0-0.015,0c-0.157,0-0.31-0.076-0.403-0.205L6.998,20.609c-0.11-0.15-0.127-0.354-0.041-0.521 c0.085-0.168,0.257-0.272,0.444-0.272h21.855C29.443,19.814,29.609,19.914,29.696,20.076z M7.401,16.865h21.855 c0.008,0,0.017,0,0.021,0c0.275,0,0.5-0.225,0.5-0.5c0-0.156-0.07-0.295-0.184-0.388L18.086,0.205 C17.989,0.072,17.821,0.002,17.668,0c-0.165,0.005-0.315,0.09-0.406,0.229L6.982,16.094c-0.101,0.152-0.105,0.35-0.021,0.512 C7.05,16.765,7.218,16.865,7.401,16.865z'/%3E%3C/g%3E%3C/svg%3E"); background-size: 15px 15px; margin: 0px 4px; pointer-events: none; &.cs2s_table_column_sort_asc::before, &.cs2s_table_column_sort_desc::before { content: ""; position: absolute; inset: 0; display: block; height: 7px; width: 15px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%233a8dde' width='800px' height='800px' viewBox='0 0 36.678 36.678' %3E%3Cg%3E%3Cpath d='M29.696,20.076c0.088,0.16,0.08,0.354-0.021,0.51L19.395,36.449c-0.091,0.139-0.241,0.224-0.407,0.229 c-0.004,0-0.008,0-0.015,0c-0.157,0-0.31-0.076-0.403-0.205L6.998,20.609c-0.11-0.15-0.127-0.354-0.041-0.521 c0.085-0.168,0.257-0.272,0.444-0.272h21.855C29.443,19.814,29.609,19.914,29.696,20.076z M7.401,16.865h21.855 c0.008,0,0.017,0,0.021,0c0.275,0,0.5-0.225,0.5-0.5c0-0.156-0.07-0.295-0.184-0.388L18.086,0.205 C17.989,0.072,17.821,0.002,17.668,0c-0.165,0.005-0.315,0.09-0.406,0.229L6.982,16.094c-0.101,0.152-0.105,0.35-0.021,0.512 C7.05,16.765,7.218,16.865,7.401,16.865z'/%3E%3C/g%3E%3C/svg%3E"); background-size: 15px 15px; } &.cs2s_table_column_sort_desc::before { top: 8px; background-position-y: 7px; } } .cs2s_table_column_search { position: relative; margin-left: 2px; font-weight: normal; font-size: 13px; max-width: 275px; &::before { content: ""; display: inline-block; position: absolute; top: 4px; left: 4px; height: 13px; width: 13px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%2396969696' width='800px' height='800px' viewBox='0 0 24 24'%3E%3Cpath d='M23.46,20.38l-5.73-5.73a9.52,9.52,0,1,0-2.83,2.83l5.73,5.73a1,1,0,0,0,1.41,0l1.41-1.41A1,1,0,0,0,23.46,20.38ZM9.75,15a5.5,5.5,0,1,1,5.5-5.5A5.51,5.51,0,0,1,9.75,15Z'/%3E%3C/svg%3E"); background-size: 13px; pointer-events: none; } &::after { padding-left: 42px; } input { background: transparent; border: none; outline: none; color: #969696; border-bottom: 1px dashed #96969696; padding-left: 22px; font-family: inherit; width: 100px; min-width: 100px; &::-webkit-search-cancel-button { -webkit-appearance: none; position: relative; top: 1px; height: 8px; width: 8px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M1 1L11 11M11 1L1 11' stroke='%233a8dde' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); cursor: pointer; } } &:has(input:not(:placeholder-shown)) { &::before { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%233a8dde' width='800px' height='800px' viewBox='0 0 24 24'%3E%3Cpath d='M23.46,20.38l-5.73-5.73a9.52,9.52,0,1,0-2.83,2.83l5.73,5.73a1,1,0,0,0,1.41,0l1.41-1.41A1,1,0,0,0,23.46,20.38ZM9.75,15a5.5,5.5,0,1,1,5.5-5.5A5.51,5.51,0,0,1,9.75,15Z'/%3E%3C/svg%3E"); } input { color: #3a8dde; border-color: #3a8dde; } } } } .cs2s_table_footer { height: 64px; position: absolute; top: 100%; left: 0px; right: 0px; color: #bdbdbd; display: flex; justify-content: space-between; align-items: center; &::before { content: ""; position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; background: radial-gradient(ellipse at 68% 40%, #1c1c2380 -24%, #05080b80 66%); backdrop-filter: blur(10px); z-index: -1; mask: linear-gradient(180deg, rgba(0, 0, 0, 0.9) 40%, transparent); } a, button { border-radius: 20px; & > span { border-radius: 20px; } } } .cs2s_table_footer_element_left > * { margin-right: 16px; } .cs2s_table_footer_element_right > * { margin-left: 16px; } .cs2s_table_footer_selection_count { font-weight: bold; } .cs2s_table_footer_action_link { display: inline-block; padding: 0px 18px 0px 12px; margin-right: 8px; border: 0px; line-height: 28px; background: #d7d7d7; color: #272727; font-size: 14px; font-weight: bold; text-shadow: none; opacity: 95%; cursor: pointer; user-select: none; &.cs2s_table_footer_action_link_active { background: #3a8dde; color: #fff; &:hover { background: #43a2ff; } } &:hover { background: #fff; } span { display: inline-block; svg { height: 21px; width: 21px; display: inline-block; vertical-align: top; margin-right: 2px; margin-top: 3px; } } } .cs2s_table_footer_input { font-weight: bold; input { outline: none; padding: 0px; border-bottom: 1px dashed !important; color: inherit !important; &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } } } .cs2s_resizable_input { display: inline-grid; input { font-size: inherit; font-weight: inherit; font-family: inherit; grid-area: 1 / 2; appearance: textfield; } &:after { content: attr(data-value); visibility: hidden; grid-area: 1 / 2; white-space: nowrap; } } .cs2s_error_table_container { max-height: 80vh; width: 1250px; user-select: auto; table, tbody, tr, th, td { user-select: auto !important; } th:first-child, td:first-child { width: 250px; padding-left: 16px; white-space: nowrap; } th:nth-child(2), td:nth-child(2) { width: 150px; } } .cs2s_action_body { padding: 16px; display: flex; flex-direction: column; gap: 16px; & > div { margin: 0px auto; } } .cs2s_action_item_window { height: 120px; width: 120px; margin: 0 auto; display: flex; justify-content: center; align-items: center; position: relative; border-radius: 100%; overflow: hidden; background: #1b1b2280; } .cs2s_action_item_window_image { display: inline-flex; position: absolute; left: 0px; transform: translate(13px, 0px); & > img { filter: drop-shadow(0px 0px 0px #bdbdbd) drop-shadow(2px 4px 6px #1c1c23); } .cs2s_table_image_column_cosmetics { position: absolute; bottom: -5px; left: -2px; white-space: nowrap; img { width: 25px; height: 19px; display: inline-block; } &:has(> :nth-child(2)):has(> :nth-child(-n+5):last-child) img:nth-child(n+2) { margin-left: -8px; } &:has(> :nth-child(6)) img:nth-child(n+2) { margin-left: -12px; } } } .cs2s_action_message { color: #969696; font-size: 16px; } .cs2s_action_message_tall { padding-top: 8px; padding-bottom:12px; } .cs2s_action_multi_message { max-width: 500px; padding-left: 16px; padding-right: 16px; display: flex; flex-direction: column; gap: 16px; } .cs2s_action_buttons { div:not(:first-child) { margin-left: 16px; } } .cs2s_action_progress_bar { background: #1b1b2280; padding: 6px; border-radius: 12px; width: 300px; height: 25px; &::before { content: ""; display: block; height: 100%; width: var(--percentage); min-width: 1px; border-radius: 6px; background: linear-gradient(to right, #47bfff 0%, #1a44c2 60%); background-position: 25%; background-size: 330% 100%; transition: width 0.3s ease-in-out; } } .cs2s_action_spinner { width: 50px; height: 50px; border: 5px solid #a6a6ad80; border-top-color: transparent; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .cs2s_settings_form { overflow: scroll; padding: 0px 24px 24px 24px; width: 900px; max-height: 85vh; } .cs2s_settings_filter { input::placeholder { opacity: .75; } input[type=text]:not(:placeholder-shown), input[type=number]:not(:placeholder-shown), select:has(option:checked:not([value=""]):not([value="custom"])) { outline: 1px solid #3a8dde; } } .cs2s_settings_form_group_title { padding: 0px 0px 6px 10px; border-bottom: 2px solid rgba(228, 228, 228, .1); font-size: 16px; line-height: 28px; text-transform: uppercase; letter-spacing: 0; color: #e4e4e4; } .cs2s_settings_form_group { flex: 1; padding: 24px 20px; align-items: center; } .cs2s_settings_form_group_row { display: flex; justify-content: space-between; flex-direction: row; gap: 32px; .cs2s_settings_form_group_item { flex: 1; } } .cs2s_settings_form_group_half_row { display: flex; justify-content: flex-start; flex-direction: row; gap: 32px; } .cs2s_settings_form_group_collection { flex: 1; position: relative; margin: 8px 0px 10px; &::before { content: ""; position: absolute; z-index: -1; top: -4px; bottom: 12px; left: -12px; right: -12px; background-color: #2e3137; } } .cs2s_settings_form_submit_group { display: flex; flex-direction: row-reverse; button { margin: 2px 0 2px 12px; } } .cs2s_settings_form_group_item { display: flex; flex-direction: column; margin-bottom: 22px; label { display: block; margin-bottom: 4px; font-size: 13px; font-weight: 300; line-height: 19px; color: #acb2b8; text-transform: uppercase; letter-spacing: initial; user-select: none; } &.cs2s_settings_form_group_item_checkbox { flex-direction: row; label { flex: 1; margin: 0; padding: 4px 0px; cursor: pointer; white-space: nowrap; } } input[type=text], input[type=number] { display: block; padding: 10px; font-size: 14px; line-height: 22px; color: #909090; background-color: rgba(0, 0, 0, .25); border: none; box-shadow: inset 1px 1px 0px #000a; outline: 0; &:disabled { appearance: textfield; } } input[type=checkbox] { float: left; width: 22px; height: 22px; margin-right: 8px; padding: 0; border-radius: 2px; appearance: none; background-color: #0004; box-shadow: inset 1px .5px 3px rgba(1, 1, 1, .4); cursor: pointer; &:not(:disabled):hover { background-color: #090909; } &:checked { background-image: url('data:image/svg+xml,<svg version="1.1" id="base" xmlns="http://www.w3.org/2000/svg" class="SVGIcon_Button SVGIcon_DialogCheck" x="0px" y="0px" width="18px" height="18px" viewBox="0 0 256 256"><defs><linearGradient id="svgid_0" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="%2300ccff"></stop><stop offset="100%" stop-color="%232d73ff"></stop></linearGradient><filter id="svgid_1" x="0" y="0" width="200%" height="200%"><feOffset result="offOut" in="SourceAlpha" dx="20" dy="20"></feOffset><feGaussianBlur result="blurOut" in="offOut" stdDeviation="10"></feGaussianBlur><feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend></filter></defs><path fill="none" stroke="url(%23svgid_0)" stroke-width="24" stroke-linecap="round" stroke-linejoin="miter" stroke-miterlimit="10" d="M206.5,45.25L95,210.75l-45.5-63" stroke-dasharray="365.19 365.19" stroke-dashoffset="0.00"></path><path fill="none" opacity=".2" filter="url(%23svgid_1)" stroke="url(%23svgid_0)" stroke-width="24" stroke-linecap="round" stroke-linejoin="miter" stroke-miterlimit="10" d="M206.5,45.25L95,210.75l-45.5-63" stroke-dasharray="365.19 365.19" stroke-dashoffset="0.00"></path></svg>'); background-repeat: no-repeat; background-position: 50% 50%; } } select { display: block; padding: 10px; font-size: 14px; height: 42px; color: #909090; background-color: rgba(0, 0, 0, .25); border: none; border-right: 4px solid transparent; box-shadow: inset 1px 1px 0px #000a; outline: 0; option, optgroup { color: #909090; background-color: #262931; } } } .cs2s_settings_form_message { font-style: italic; color: #909090; } .cs2s_settings_form_separator { align-content: center; font-weight: 800; opacity: 0.5; user-select: none; } .cs2s_settings_form_disabled { opacity: 0.5; input, select, label { cursor: default !important; outline: 0 !important; } } .popup_menu_item { display: block; } .cs2s_navigation_icon { display: inline-block; vertical-align: text-top; height: 16px; width: 47px; svg { height: 11px; width: 50px; } } .cs2s_navigation_status_error_glow { animation: cs2s_glow 2s infinite ease-in-out; } @keyframes cs2s_glow { 0%, 100% { box-shadow: 0 0 5px rgba(102, 192, 244, 0.4); } 20% { box-shadow: 0 0 10px rgba(102, 192, 244, 0.6); } 40% { box-shadow: 0 0 20px rgba(102, 192, 244, 0.9); } 60% { box-shadow: 0 0 5px rgba(102, 192, 244, 0.3); } 80% { box-shadow: 0 0 15px rgba(102, 192, 244, 0.8); } } .cs2s_navigation_status_error_pulse { animation: cs2s_pulse 2s infinite ease-in-out; } @keyframes cs2s_pulse { 0% { transform: scale(1); opacity: 0.75; box-shadow: 0 0 0 0 rgba(102, 192, 244, 0.75); } 50% { transform: scale(1.1); opacity: 1; box-shadow: 0 0 10px 10px rgba(102, 192, 244, 0.3); color: #66c0f4 } 100% { transform: scale(1); opacity: 0.75; box-shadow: 0 0 0 0 rgba(102, 192, 244, 0); } } .cs2s_grey_long_button { display: inline-block; width: 200px; padding: 0; line-height: 32px; text-align: center; font-size: 14px; color: #dfe3e6; background-color: #3d4450; border: 0; border-radius: 2px; cursor: pointer; &:hover { color: #ffffff; background-color: #464d58; } } .cs2s_blue_long_button { display: inline-block; width: 200px; padding: 0; line-height: 32px; text-align: center; font-size: 14px; color: #dfe3e6; background: linear-gradient(to right, #47bfff 0%, #1a44c2 60%); background-position: 25%; background-size: 330% 100%; border: 0; border-radius: 2px; cursor: pointer; &:hover { background: linear-gradient(to right, #47bfff 0%, #1a44c2 60%); background-position: 0%; background-size: 330% 100%; } } .cs2s_green_button { padding: 1px; display: inline-block; cursor: pointer; color: #D2E885; background: linear-gradient(to bottom, #07d03f 5%, #04692f 95%); border-bottom-width: 0px; span { display: block; padding: 0 24px; font-size: 15px; line-height: 30px; background: linear-gradient(to bottom, #059917 5%, #04693b 95%); } &:not(.cs2s_button_disabled) { &:hover { color: #ffffff; background: linear-gradient(to bottom, #08d92b 5%, #06a030 95%); span { background: linear-gradient(to bottom, #07bf2b 5%, #06a05e 95%); } } } &.cs2s_button_disabled { opacity: 45%; cursor: default; } } `; // src/main.js { const HAS_GM = typeof GM !== "undefined"; const NEW_GM = ((scope, GM2) => { if (GM_info.scriptHandler !== "Tampermonkey" || compareVersions(GM_info.version, "5.3.2") < 0) return; const GM_xmlhttpRequestOrig = GM_xmlhttpRequest; const GM_xmlHttpRequestOrig = GM2.xmlHttpRequest; function compareVersions(v1, v2) { const parts1 = v1.split(".").map(Number); const parts2 = v2.split(".").map(Number); const length = Math.max(parts1.length, parts2.length); for (let i = 0; i < length; i++) { const num1 = parts1[i] || 0; const num2 = parts2[i] || 0; if (num1 > num2) return 1; if (num1 < num2) return -1; } return 0; } function GM_xmlhttpRequestWrapper(odetails) { if (odetails.redirect !== void 0) { return GM_xmlhttpRequestOrig(odetails); } if (odetails.onprogress || odetails.fetch === false) { console.warn("Fetch mode does not support onprogress in the background."); } const { onload, onloadend, onerror, onabort, ontimeout, ...details } = odetails; const handleRedirects = (initialDetails) => { const request = GM_xmlhttpRequestOrig({ ...initialDetails, redirect: "manual", onload: function(response) { if (response.status >= 300 && response.status < 400) { const m = response.responseHeaders.match(/Location:\s*(\S+)/i); const redirectUrl = m && m[1]; if (redirectUrl) { const absoluteUrl = new URL(redirectUrl, initialDetails.url).href; handleRedirects({ ...initialDetails, url: absoluteUrl }); return; } } if (onload) onload.call(this, response); if (onloadend) onloadend.call(this, response); }, onerror: function(response) { if (onerror) onerror.call(this, response); if (onloadend) onloadend.call(this, response); }, onabort: function(response) { if (onabort) onabort.call(this, response); if (onloadend) onloadend.call(this, response); }, ontimeout: function(response) { if (ontimeout) ontimeout.call(this, response); if (onloadend) onloadend.call(this, response); } }); return request; }; return handleRedirects(details); } function GM_xmlHttpRequestWrapper(odetails) { let abort; const p = new Promise((resolve, reject) => { const { onload, ontimeout, onerror, ...send } = odetails; send.onerror = function(r) { if (onerror) { resolve(r); onerror.call(this, r); } else { reject(r); } }; send.ontimeout = function(r) { if (ontimeout) { resolve(r); ontimeout.call(this, r); } else { reject(r); } }; send.onload = function(r) { resolve(r); if (onload) onload.call(this, r); }; const a = GM_xmlhttpRequestWrapper(send).abort; if (abort === true) { a(); } else { abort = a; } }); p.abort = () => { if (typeof abort === "function") { abort(); } else { abort = true; } }; return p; } GM_xmlhttpRequest = GM_xmlhttpRequestWrapper; scope.GM_xmlhttpRequestOrig = GM_xmlhttpRequestOrig; const gopd = Object.getOwnPropertyDescriptor(GM2, "xmlHttpRequest"); if (gopd && gopd.configurable === false) { return { __proto__: GM2, xmlHttpRequest: GM_xmlHttpRequestWrapper, xmlHttpRequestOrig: GM_xmlHttpRequestOrig }; } else { GM2.xmlHttpRequest = GM_xmlHttpRequestWrapper; GM2.xmlHttpRequestOrig = GM_xmlHttpRequestOrig; } })(window, HAS_GM ? GM : {}); if (HAS_GM && NEW_GM) GM = NEW_GM; } (async function() { GM_addStyle(style_default); await script_default.VerifyConnection(); const PAGE_INVENTORY = 0; const PAGE_MARKET_LISTING = 1; let currentPage = null; if (window.location.href.includes("/market/listings")) { currentPage = PAGE_MARKET_LISTING; } else if (window.location.href.includes("/inventory")) { currentPage = PAGE_INVENTORY; } if (currentPage == PAGE_INVENTORY) { const IS_OWN_INVENTORY = unsafeWindow.g_ActiveUser.strSteamId == unsafeWindow.g_steamID; if (IS_OWN_INVENTORY) { const initInventory = () => { if (initInventory.initialized) { return; } initInventory.initialized = true; script_default.GetInventory(); }; const allCratesButton = CreateElement("a", { class: "btn_darkblue_white_innerfade btn_medium", style: { marginRight: "12px" }, html: "<span>Retrieve All Stored Items</span>", onclick: async () => { const inventory = await script_default.GetInventory({ showProgress: true }); if (inventory === OPERATION_ERROR.INTERFACE_NOT_CONNECTED) { script_default.ShowStartInterfacePrompt({ message: "Interface must be running to fetch stored items", fade: false }); return; } if (inventory === OPERATION_ERROR.INVENTORY_FAILED_TO_LOAD) { script_default.ShowError({ level: ERROR_LEVEL.HIGH }, new Error("Inventory failed to load, check error logs and refresh the page to try again")); return; } if (!(inventory instanceof Inventory)) { return; } const table = new Table(inventory.storedItems.slice(), inventory, { mode: Table.MODE.RETRIEVE, casketName: "All Storage Units", multiCasket: true }); table.Show(); } }); const originalShowItemInventory = unsafeWindow.ShowItemInventory; unsafeWindow.ShowItemInventory = function(appid) { const result = originalShowItemInventory.call(this, ...arguments); if (appid == CS2_APPID) { initInventory(); !allCratesButton.isConnected && unsafeWindow.document.getElementsByClassName("inventory_rightnav")[0].prepend(allCratesButton); } else { allCratesButton.isConnected && allCratesButton.remove(); } return result; }; if (unsafeWindow.g_ActiveInventory.appid == CS2_APPID) { initInventory(); unsafeWindow.document.getElementsByClassName("inventory_rightnav")[0].prepend(allCratesButton); } } { const handleInventoryAssets = () => { if (!handleInventoryAssets.handledAssetIDs) { handleInventoryAssets.handledAssetIDs = /* @__PURE__ */ new Set(); } if (!unsafeWindow.g_rgAppContextData[CS2_APPID].rgContexts[2].inventory?.m_rgAssets) { return; } for (const element of unsafeWindow.g_rgAppContextData[CS2_APPID].rgContexts[2].inventory.m_rgItemElements) { const asset = element?.[0]?.rgItem; if (!asset) { continue; } const assetid = asset.assetid; if (handleInventoryAssets.handledAssetIDs.has(assetid)) { continue; } handleInventoryAssets.handledAssetIDs.add(assetid); const inventoryAsset = new InventoryAsset(asset); inventoryAsset.BuildInventoryUI(); } }; const originalAddInventoryData = unsafeWindow.CInventory.prototype.AddInventoryData; unsafeWindow.CInventory.prototype.AddInventoryData = function(data) { const result = originalAddInventoryData.call(this, ...arguments); if (data?.assets?.[0]?.appid == CS2_APPID) { handleInventoryAssets(); } return result; }; handleInventoryAssets(); } { const handleSelectedAsset = (asset) => { const inventoryAsset = new InventoryAsset(asset); inventoryAsset.BuildSelectedUI(); }; const originalSelectItem = unsafeWindow.CInventory.prototype.SelectItem; unsafeWindow.CInventory.prototype.SelectItem = function(a, b, asset) { const result = originalSelectItem.call(this, ...arguments); if (asset.appid == CS2_APPID) { handleSelectedAsset(asset); } return result; }; if (unsafeWindow.g_ActiveInventory.selectedItem?.appid == CS2_APPID) { handleSelectedAsset(unsafeWindow.g_ActiveInventory.selectedItem); } } } else if (currentPage == PAGE_MARKET_LISTING) { const IS_CS2_ITEM = !!unsafeWindow.g_rgAppContextData?.[CS2_APPID]; const HAS_INDIVIDUAL_LISTINGS = Object.values(unsafeWindow.g_rgAssets?.[CS2_APPID]?.[2])[0]?.commodity == 0; if (!IS_CS2_ITEM || !HAS_INDIVIDUAL_LISTINGS) { return; } { const handleMarketAssets = () => { if (!unsafeWindow.g_rgAssets?.[CS2_APPID]?.[2] || !unsafeWindow.g_rgListingInfo) { return; } for (const listing of Object.values(unsafeWindow.g_rgListingInfo)) { const asset = unsafeWindow.g_rgAssets[CS2_APPID][2][listing.asset?.id]; if (!asset) { continue; } const marketAsset = new MarketAsset(asset, listing); marketAsset.BuildListingUI(); } }; const originalOnResponseRenderResults = unsafeWindow.CAjaxPagingControls.prototype.OnResponseRenderResults; unsafeWindow.CAjaxPagingControls.prototype.OnResponseRenderResults = function() { const result = originalOnResponseRenderResults.call(this, ...arguments); handleMarketAssets(); return result; }; handleMarketAssets(); } } })(); })();