Steam Inventory Items Table

Shows you all your items in a cool way.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Steam Inventory Items Table
// @namespace    https://github.com/Kostya12rus/steam_inventory_stack/
// @supportURL   https://github.com/Kostya12rus/steam_inventory_stack/issues
// @version      1.0.1
// @description  Shows you all your items in a cool way.
// @author       Kostya12rus
// @match        https://steamcommunity.com/profiles/*/inventory*
// @match        https://steamcommunity.com/id/*/inventory*
// @license      AGPL-3.0
// ==/UserScript==

class MarketDescription {
    constructor(descriptionDict = {}) {
        this.type = descriptionDict.type || '';
        this.value = descriptionDict.value || '';
    }
}
class MarketAssetDescription {
    constructor(assetDescriptionDict = {}) {
        this.appid = assetDescriptionDict.appid || 0;
        this.classid = assetDescriptionDict.classid || '';
        this.instanceid = assetDescriptionDict.instanceid || '';
        this.name = assetDescriptionDict.name || '';
        this.nameColor = assetDescriptionDict.name_color || '';
        this.marketName = assetDescriptionDict.market_name || '';
        this.marketHashName = assetDescriptionDict.market_hash_name || '';

        this.tradable = Boolean(assetDescriptionDict.tradable || false);
        this.marketable = Boolean(assetDescriptionDict.marketable || false);
        this.commodity = Boolean(assetDescriptionDict.commodity || false);

        this.marketTradableRestriction = assetDescriptionDict.market_tradable_restriction || -1;
        this.marketMarketableRestriction = assetDescriptionDict.market_marketable_restriction || -1;

        this.iconUrl = assetDescriptionDict.icon_url || '';
        this.iconUrlLarge = assetDescriptionDict.icon_url_large || '';

        this.currency = assetDescriptionDict.currency || 0;
        this.descriptions = (assetDescriptionDict.descriptions || []).map(d => new MarketDescription(d));
        this.type = assetDescriptionDict.type || '';
        this.backgroundColor = assetDescriptionDict.background_color || '';
    }
}
class MarketItem {
    constructor(itemDict = {}) {
        this.name = itemDict.name || ' ';
        this.hashName = itemDict.hash_name || '';

        this.sellListings = itemDict.sell_listings || 0;
        this.sellPrice = itemDict.sell_price || 0;
        this.sellPriceText = itemDict.sell_price_text || '';
        this.salePriceText = itemDict.sale_price_text || '';

        this.assetDescription = new MarketAssetDescription(itemDict.asset_description || {});

        this.appName = itemDict.app_name || '';
        this.appIcon = itemDict.app_icon || '';
    }

    loadSave(data) {
        this.name = data.name || ' ';
        this.hashName = data.hashName || '';

        this.sellListings = data.sellListings || 0;
        this.sellPrice = data.sellPrice || 0;
        this.sellPriceText = data.sellPriceText || '';
        this.salePriceText = data.salePriceText || '';

        this.assetDescription = new MarketAssetDescription(data.assetDescription || {});

        this.appName = data.appName || '';
        this.appName = data.appName || '';
        return this;
    }

    toString() {
        return `<${this.constructor.name}> name: ${this.name}, price: ${this.sellPriceText}, listings: ${this.sellPrice}`;
    }

    isBugItem() {
        return this.hashName !== this.assetDescription.marketHashName;
    }

    isEmpty() {
        return this.hashName === '';
    }

    iconUrl() {
        if (!this.assetDescription.iconUrl) return '';
        return `https://community.akamai.steamstatic.com/economy/image/${this.assetDescription.iconUrl}/330x192?allow_animated=1`;
    }

    marketUrl() {
        if (!this.assetDescription.appid || !this.assetDescription.marketHashName) return '';
        return `https://steamcommunity.com/market/listings/${this.assetDescription.appid}/${this.assetDescription.marketHashName}`;
    }

    marketHashName() {
        return this.assetDescription.marketHashName;
    }

    color() {
        return this.assetDescription.nameColor ? `#${this.assetDescription.nameColor}`.replace('##', '#') : '';
    }

    isCurrentGame(appId) {
        return String(this.assetDescription.appid) === String(appId);
    }

    replaceNumberInCurrency(newNumber) {
        return this.sellPriceText.replace(/\d{1,3}(?:\s?\d{3})*(?:[,.]\d+)?/, newNumber);
    }

    generateNumberInCurrency(newNumber) {
        return this.replaceNumberInCurrency((newNumber / 100).toFixed(2));
    }

    multiplyPriceInCurrency(count) {
        return this.generateNumberInCurrency(this.sellPrice * count);
    }

    calculateCommission(price = null) {
        return this.generateNumberInCurrency(this.calculateCommissionInteger(price));
    }

    calculateCommissionInteger(price = null) {
        if (!price) price = this.sellPrice;
        const commission = Math.abs(price - (price / 115 * 100));
        return price - commission;
    }
}

class InventoryDescription {
    constructor(descriptionDict = {}) {
        this.type = descriptionDict.type || '';
        this.value = descriptionDict.value || '';
    }
}
class InventoryTag {
    constructor(tagDict = {}) {
        this.category = tagDict.category || '';
        this.internalName = tagDict.internal_name || '';
        this.categoryName = tagDict.category_name || '';
        this.name = tagDict.name || '';
    }
}
class InventoryItem {
    constructor(itemDict = {}) {
        this.classid = itemDict.classid || '';
        this.instanceid = itemDict.instanceid || '';
        this.amount = itemDict.amount || '1';

        const rgDescriptions = itemDict.rgDescriptions || {};
        this.rgDescriptions = {
            appid: rgDescriptions.appid || '',
            classid: rgDescriptions.classid || '',
            instanceid: rgDescriptions.instanceid || '',
            iconUrl: rgDescriptions.icon_url || '',
            iconUrlLarge: rgDescriptions.icon_url_large || '',
            iconDragUrl: rgDescriptions.icon_drag_url || '',
            name: rgDescriptions.name || '',
            marketHashName: rgDescriptions.market_hash_name || '',
            marketName: rgDescriptions.market_name || '',
            nameColor: rgDescriptions.name_color || '',
            backgroundColor: rgDescriptions.background_color || '',
            type: rgDescriptions.type || '',
            tradable: Boolean(rgDescriptions.tradable || false),
            marketable: Boolean(rgDescriptions.marketable || false),
            commodity: Boolean(rgDescriptions.commodity || false),
            marketTradableRestriction: rgDescriptions.market_tradable_restriction || '-1',
            marketMarketableRestriction: rgDescriptions.market_marketable_restriction || '7',
            descriptions: (rgDescriptions.descriptions || []).map(desc => new InventoryDescription(desc)),
            tags: (rgDescriptions.tags || []).map(tag => new InventoryTag(tag))
        };
    }

    updateRgDescriptions(rgDescriptions) {
        this.rgDescriptions = {
            appid: rgDescriptions.appid || '',
            classid: rgDescriptions.classid || '',
            instanceid: rgDescriptions.instanceid || '',
            iconUrl: rgDescriptions.icon_url || '',
            iconUrlLarge: rgDescriptions.icon_url_large || '',
            iconDragUrl: rgDescriptions.icon_drag_url || '',
            name: rgDescriptions.name || '',
            marketHashName: rgDescriptions.market_hash_name || '',
            marketName: rgDescriptions.market_name || '',
            nameColor: rgDescriptions.name_color || '',
            backgroundColor: rgDescriptions.background_color || '',
            type: rgDescriptions.type || '',
            tradable: Boolean(rgDescriptions.tradable || false),
            marketable: Boolean(rgDescriptions.marketable || false),
            commodity: Boolean(rgDescriptions.commodity || false),
            marketTradableRestriction: rgDescriptions.market_tradable_restriction || '-1',
            marketMarketableRestriction: rgDescriptions.market_marketable_restriction || '7',
            descriptions: (rgDescriptions.descriptions || []).map(desc => new InventoryDescription(desc)),
            tags: (rgDescriptions.tags || []).map(tag => new InventoryTag(tag))
        };
    }

    name() {
        if (!this.rgDescriptions.name) return '';
        return this.rgDescriptions.name;
    }

    iconUrl() {
        if (!this.rgDescriptions.iconUrl) return '';
        return `https://community.akamai.steamstatic.com/economy/image/${this.rgDescriptions.iconUrl}/330x192?allow_animated=1`;
    }

    marketUrl() {
        if (!this.rgDescriptions.appid || !this.rgDescriptions.marketHashName) return '';
        return `https://steamcommunity.com/market/listings/${this.rgDescriptions.appid}/${this.rgDescriptions.marketHashName}`;
    }

    color() {
        return this.rgDescriptions.nameColor ? `#${this.rgDescriptions.nameColor}`.replace('##', '#') : '';
    }
}
class InventoryManager {
    constructor(items = {}) {
        this.rgDescriptions = items.descriptions || {};
        if (typeof this.rgDescriptions !== 'object') {
            this.rgDescriptions = {};
        }
        this.rgInventory = items.assets || [];
        if (typeof this.rgInventory !== 'object') {
            this.rgInventory = [];
        }
        this.success = Boolean(items.success || false);
        this.inventory = [];

        this.parseInventory();
    }

    loadSaveInventory(oldData) {
        this.rgDescriptions = oldData.rgDescriptions || {};
        this.rgInventory = oldData.rgInventory || [];
        this.inventory = oldData.inventory || [];
        this.parseInventory();
        return this;
    }

    addNextInvent(nextInventory) {
        if (!(nextInventory instanceof InventoryManager)) return;
        this.rgInventory.push(...nextInventory.rgInventory);
        for (const [key, value] of Object.entries(nextInventory.rgDescriptions)) {
            this.rgDescriptions[key] = value;
        }
        this.parseInventory();
    }

    parseInventory() {
        this.inventory = [];

        for (const [key, item] of Object.entries(this.rgInventory)) {
            const inventoryItem = new InventoryItem(item);
            this.inventory.push(inventoryItem);
        }

        for (const item of this.inventory) {
            const classid = item.classid || 0;
            if (classid === 0) continue;

            const instanceid = item.instanceid || 0;
            for (const [key, itemDescription] of Object.entries(this.rgDescriptions)) {
                const classidD = itemDescription.classid || 0;
                if (classid !== classidD) continue;

                const instanceidD = itemDescription.instanceid || 0;
                if (instanceid !== instanceidD) continue;

                item.updateRgDescriptions(itemDescription);
                break;
            }
        }
    }
}

class TotalItem {
    constructor(classid) {
        this.classid = classid;
        this.items = [];
        this.marketData = null
    }
    addItem(item) {
        this.items.push(item);
    }
    setMarketData(marketData) {
        this.marketData = marketData;
    }

    getCount() {
        return this.items.reduce((total, _item) => total + parseInt(_item.amount), 0);
    }
    getConsolePrice(){
        if (!this.marketData) return 0;
        return this.marketData.sellPrice * this.getCount();
    }
    getConsolePriceOne(){
        if (!this.marketData) return 0;
        return this.marketData.sellPrice;
    }
    getOtherPrice(price = 0){
        if (!this.marketData) return null;
        return this.marketData.generateNumberInCurrency(price);
    }
    getPrice() {
        if (!this.marketData) return '';
        return this.marketData.multiplyPriceInCurrency(this.getCount());
    }
    getOnePrice() {
        if (!this.marketData) return '';
        return this.marketData.multiplyPriceInCurrency(1);
    }
    getIconUrl() {
        if (this.marketData)
        {
            return this.marketData.iconUrl();
        }
        else
        {
            const item = this.items[0];
            if (item)
            {
                return item.iconUrl();
            }
        }
        return '';
    }
    getMarketUrl() {
        if (this.marketData)
        {
            return this.marketData.marketUrl();
        }
        else
        {
            const item = this.items[0];
            if (item)
            {
                return item.marketUrl();
            }
        }
        return '';
    }
    getName() {
        if (this.marketData)
        {
            return this.marketData.name;
        }
        else
        {
            const item = this.items[0];
            if (item)
            {
                return item.name();
            }
        }
        return '';
    }
    getColor() {
        if (this.marketData)
        {
            return this.marketData.color();
        }
        else
        {
            const item = this.items[0];
            if (item)
            {
                return item.color();
            }
        }
        return '';
    }
}
class TotalItemsManager {
    constructor() {
        this.items = {};
        this.inventoryManager = new InventoryManager();
        this.marketItems = [];
        this.cachedData = {};
    }
    async loadTotalItems() {
        const { m_appid, m_contextid, m_steamid } = g_ActiveInventory;

        this.appid = m_appid;
        this.contextid = m_contextid;
        this.steamid = m_steamid;

        if (!this.cachedData) { this.cachedData = {}; }

        const cacheKey = `inventory_${this.steamid}_${this.appid}`;
        const now = new Date().getTime();

        if (this.cachedData[cacheKey] && this.cachedData[cacheKey].expiry > now) {
            this.inventoryManager = this.cachedData[cacheKey].inventoryManager;
            this.marketItems = this.cachedData[cacheKey].marketItems;
            this.parseItems();
            return;
        }

        this.inventoryManager = await this.getFullInventory();
        this.marketItems = await this.getGameMarketList();
        this.parseItems();

        if (!this.cachedData[cacheKey]) { this.cachedData[cacheKey] = {}; }

        this.cachedData[cacheKey].inventoryManager = this.inventoryManager;
        this.cachedData[cacheKey].marketItems = this.marketItems;
        this.cachedData[cacheKey].expiry = now + 10 * 60 * 1000;
    }
    parseItems() {
        this.items = [];
        for (const item of this.inventoryManager.inventory) {
            const classid = item.classid || 0;
            if (!this.items[classid]) {
                this.items[classid] = new TotalItem(classid);
            }
            this.items[classid].addItem(item);
        }
        for (const item of this.marketItems) {
            const classid = item.assetDescription.classid || 0;
            if (!this.items[classid]) {
                continue;
            }
            this.items[classid].setMarketData(item);
        }
    }

    async getGameMarketList(start = 0, count = 100) {
        const searchParams = new URLSearchParams({
            start: start,
            count: count,
            search_descriptions: 0,
            sort_column: 'popular',
            sort_dir: 'desc',
            appid: this.appid,
            norender: 1
        });

        const searchUrl = `https://steamcommunity.com/market/search/render/?${searchParams.toString()}`;
        let _marketItems = [];

        try {
            const marketResponse = await fetch(searchUrl, { method: 'GET', timeout: 10000 });
            if (marketResponse.ok) {
                const responseData = await marketResponse.json();
                if (responseData.success) {
                    const items = responseData.results.map(itemData => new MarketItem(itemData));
                    _marketItems = _marketItems.concat(items);
                    const totalItemsAvailable = responseData.total_count || 0;
                    if (totalItemsAvailable > start + count && start + count < 1000) {
                        start += count;
                        const additionalItems = await this.getGameMarketList(start, count);
                        _marketItems = _marketItems.concat(additionalItems);
                    }
                    return _marketItems;
                }
            }
        } catch (e) {
            console.error(`getGameMarketList failed: ${e.message}`);
        }
        return _marketItems;
    }
    async  getFullInventory() {
        try {
            const inventoryManager = new InventoryManager();
            return await this.getInventoryItems(inventoryManager);
        } catch (error) {
            console.error("Ошибка при получении предметов инвентаря:", error);
        }
        return this.inventoryManager;
    }
    getInventoryItems(inventoryManager, start_assetid = null) {
        const searchParams = new URLSearchParams({
            count: 2000,
        });
        if (start_assetid) {
            searchParams.set('start_assetid', start_assetid);
        }
        const url = `https://steamcommunity.com/inventory/${this.steamid}/${this.appid}/${this.contextid}?${searchParams.toString()}`;
        return fetch(url, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        })
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            if (!data.success) {
                throw new Error("Не удалось получить данные инвентаря.");
            }
            inventoryManager.addNextInvent(new InventoryManager(data));
            const more_items = data.more_items;
            if (Number.isInteger(more_items) && more_items > 0) {
                return this.getInventoryItems(inventoryManager, data.last_assetid);
            }
            return inventoryManager;
        })
        .catch(error => {
            console.error("Ошибка проверки инвентаря:", error);
            throw error;
        });
    }
}

class ModalWindow {
    constructor(userNickname, userAvatar) {
        this.userNickname = userNickname
        this.userAvatar = userAvatar
        this.items = [];

        this.overlay = document.createElement('div');
        this.modal = document.createElement('div');
        this.closeButton = document.createElement('button');

        this.settingModal();
    }

    settingModal() {
        this.overlay.style.position = 'fixed';
        this.overlay.style.top = '0';
        this.overlay.style.left = '0';
        this.overlay.style.width = '100%';
        this.overlay.style.height = '100%';
        this.overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
        this.overlay.style.zIndex = '9999';
        this.overlay.style.display = 'flex';
        this.overlay.style.justifyContent = 'center';
        this.overlay.style.alignItems = 'center';
        this.overlay.style.opacity = '0';
        this.overlay.style.transition = 'opacity 0.3s ease-in-out';
        this.overlay.addEventListener('click', this.closeModal.bind(this));

        this.modal.style.padding = '30px';
        this.modal.style.backgroundColor = '#242424';
        this.modal.style.borderRadius = '12px';
        this.modal.style.boxShadow = '0 8px 16px rgba(0, 0, 0, 0.5)';
        this.modal.style.color = '#e0e0e0';
        this.modal.style.width = '800px';
        this.modal.style.maxHeight = '90vh';
        this.modal.style.overflowY = 'auto';
        this.modal.style.position = 'relative';
        this.modal.style.transform = 'scale(0.9)';
        this.modal.style.opacity = '0';
        this.modal.style.transition = 'transform 0.3s ease-in-out, opacity 0.3s ease-in-out';
        this.modal.addEventListener('click', function(event) { event.stopPropagation(); });

        this.closeButton.innerText = '✖';
        this.closeButton.style.position = 'absolute';
        this.closeButton.style.top = '10px';
        this.closeButton.style.right = '10px';
        this.closeButton.style.background = 'none';
        this.closeButton.style.border = 'none';
        this.closeButton.style.color = '#fff';
        this.closeButton.style.fontSize = '20px';
        this.closeButton.style.cursor = 'pointer';
        this.closeButton.addEventListener('click', this.closeModal.bind(this));

        this.modal.appendChild(this.closeButton);

        this.addProfileInfo();
        this.addInventoryTable();

        this.overlay.appendChild(this.modal);
        document.body.appendChild(this.overlay);

        requestAnimationFrame(() => {
            this.overlay.style.opacity = '1';
            this.modal.style.transform = 'scale(1)';
            this.modal.style.opacity = '1';
        });
    }
    addProfileInfo() {
        this.profileContainer = document.createElement('div');
        this.profileContainer.style.display = 'flex';
        this.profileContainer.style.alignItems = 'center';
        this.profileContainer.style.justifyContent = 'center';
        this.profileContainer.style.marginBottom = '10px';
        this.profileContainer.style.color = '#ffffff';

        this.avatar = document.createElement('img');
        this.avatar.src = this.userAvatar;
        this.avatar.alt = 'Profile Avatar';
        this.avatar.style.width = '60px';
        this.avatar.style.height = '60px';
        this.avatar.style.borderRadius = '50%';
        this.avatar.style.marginRight = '15px';

        this.infoContainer = document.createElement('div');
        this.infoContainer.style.display = 'flex';
        this.infoContainer.style.alignItems = 'center';

        this.nickname = document.createElement('h2');
        this.nickname.innerText = this.userNickname;
        this.nickname.style.margin = '0 20px 0 0';
        this.nickname.style.fontSize = '20px';
        this.nickname.style.fontWeight = 'bold';
        this.nickname.style.color = '#6a5acd';

        this.detailsContainer = document.createElement('div');
        this.detailsContainer.style.display = 'flex';
        this.detailsContainer.style.alignItems = 'center';

        const itemCountContainer = document.createElement('div');
        itemCountContainer.style.marginRight = '20px';
        const itemCountLabel = document.createElement('p');
        itemCountLabel.innerText = 'Количество вещей';
        itemCountLabel.style.fontSize = '14px';
        itemCountLabel.style.color = '#b0c4de';
        itemCountLabel.style.margin = '0';
        itemCountLabel.style.textAlign = 'center';
        this.itemCountValue = document.createElement('p');
        this.itemCountValue.innerText = `123`;
        this.itemCountValue.style.margin = '0';
        this.itemCountValue.style.fontSize = '16px';
        this.itemCountValue.style.fontWeight = 'bold';
        this.itemCountValue.style.textAlign = 'center';
        this.itemCountValue.style.color = '#ffffff';

        itemCountContainer.appendChild(itemCountLabel);
        itemCountContainer.appendChild(this.itemCountValue);

        const inventoryValueContainer = document.createElement('div');
        const inventoryValueLabel = document.createElement('p');
        inventoryValueLabel.innerText = 'Сумма инвентаря';
        inventoryValueLabel.style.fontSize = '14px';
        inventoryValueLabel.style.color = '#b0c4de';
        inventoryValueLabel.style.margin = '0';
        inventoryValueLabel.style.textAlign = 'center';
        this.inventoryValue = document.createElement('p');
        this.inventoryValue.innerText = ``;
        this.inventoryValue.style.margin = '0';
        this.inventoryValue.style.fontSize = '16px';
        this.inventoryValue.style.fontWeight = 'bold';
        this.inventoryValue.style.textAlign = 'center';
        this.inventoryValue.style.color = '#ffffff';

        inventoryValueContainer.appendChild(inventoryValueLabel);
        inventoryValueContainer.appendChild(this.inventoryValue);

        this.detailsContainer.appendChild(itemCountContainer);
        this.detailsContainer.appendChild(inventoryValueContainer);

        this.infoContainer.appendChild(this.nickname);
        this.infoContainer.appendChild(this.detailsContainer);

        this.profileContainer.appendChild(this.avatar);
        this.profileContainer.appendChild(this.infoContainer);

        this.modal.appendChild(this.profileContainer);
    }
    addInventoryTable() {
        this.table = document.createElement('table');
        this.table.style.width = '100%';
        this.table.style.borderCollapse = 'separate';
        this.table.style.borderSpacing = '0';
        this.table.style.borderRadius = '8px';
        this.table.style.overflow = 'hidden';
        this.table.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';

        const thead = document.createElement('thead');
        const headerRow = document.createElement('tr');

        const headers = [' ', 'Название предмета', 'Количество', 'Цена за штуку', 'Цена за все'];
        const textAligns = ['left', 'left', 'center', 'right', 'right'];

        headers.forEach((headerText, index) => {
            const th = document.createElement('th');
            th.innerText = headerText;
            th.style.borderBottom = '1px solid #ccc';
            th.style.textAlign = textAligns[index];
            th.style.cursor = 'pointer';
            th.setAttribute('data-order', 'asc');
            th.addEventListener('click', () => this.sortTableByColumn(index));
            headerRow.appendChild(th);
        });

        thead.appendChild(headerRow);
        this.table.appendChild(thead);

        this.tbody = document.createElement('tbody');
        this.table.appendChild(this.tbody);

        this.modal.appendChild(this.table);
    }
    sortTableByColumn(columnIndex) {
        const rows = Array.from(this.tbody.querySelectorAll('tr'));
        const isNumeric = columnIndex !== 1;
        const header = this.table.rows[0].cells[columnIndex];

        const order = header.getAttribute('data-order') === 'desc' ? 'asc' : 'desc';
        header.setAttribute('data-order', order);

        rows.sort((a, b) => {
            let aValue = a.cells[columnIndex].dataset.sort;
            let bValue = b.cells[columnIndex].dataset.sort;

            if (isNumeric) {
                aValue = parseFloat(aValue);
                bValue = parseFloat(bValue);
            }

            if (order === 'asc') {
                return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
            } else {
                return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
            }
        });

        while (this.tbody.firstChild) {
            this.tbody.removeChild(this.tbody.firstChild);
        }

        rows.forEach(row => this.tbody.appendChild(row));
    }
    addItemToTable(item) {
        const row = document.createElement('tr');

        const imgCell = document.createElement('td');
        const img = document.createElement('img');
        img.src = item.getIconUrl();
        img.alt = item.getName();
        img.style.height = '30px';
        img.style.width = 'auto';
        imgCell.appendChild(img);
        row.appendChild(imgCell);

        const nameCell = document.createElement('td');
        nameCell.style.color = item.getColor();
        nameCell.dataset.sort = item.getName();

        const nameLink = document.createElement('a');
        nameLink.href = item.getMarketUrl();
        nameLink.innerText = item.getName();
        nameLink.style.color = 'inherit';
        nameLink.style.textDecoration = 'none';
        nameLink.target = "_blank";
        nameCell.appendChild(nameLink);
        row.appendChild(nameCell);

        const quantityCell = document.createElement('td');
        quantityCell.innerText = item.getCount();
        quantityCell.style.textAlign = 'center';
        quantityCell.dataset.sort = item.getCount();
        row.appendChild(quantityCell);

        const pricePerItemCell = document.createElement('td');
        pricePerItemCell.innerText = item.getOnePrice();
        pricePerItemCell.style.textAlign = 'right';
        pricePerItemCell.dataset.sort = item.getConsolePriceOne();
        row.appendChild(pricePerItemCell);

        const totalPriceCell = document.createElement('td');
        totalPriceCell.innerText = item.getPrice();
        totalPriceCell.style.textAlign = 'right';
        totalPriceCell.dataset.sort = item.getConsolePrice();
        row.appendChild(totalPriceCell);

        this.tbody.appendChild(row);
    }

    addItem(newItem) {
        if (!newItem || newItem.getName() === '') { return; }

        this.items.push(newItem);
        this.addItemToTable(newItem);

        const totalCount = this.items.reduce((total, item) => total + item.getCount(), 0);
        this.itemCountValue.innerText = `${totalCount}`;

        const totalPriceFloat = this.items.reduce((total, item) => total + item.getConsolePrice(), 0);
        const itemWithMarketData = this.items.find(_item => _item.marketData);
        if (itemWithMarketData) {
            this.inventoryValue.innerText = `${itemWithMarketData.getOtherPrice(totalPriceFloat)}`;
        }
    }

    closeModal() {
        this.overlay.style.opacity = '0';
        this.modal.style.transform = 'scale(0.9)';
        this.modal.style.opacity = '0';

        setTimeout(() => {
            document.body.removeChild(this.overlay);
        }, 300);
    }
}

(function() {
    'use strict';
    const appInventoryManager = new TotalItemsManager();
    createButton();

    async function localAppData() {
        await appInventoryManager.loadTotalItems()
        if (Object.keys(appInventoryManager.items).length === 0) {
            alert('Не удалось получить список предметов. Пожалуйста, попробуйте позже');
            return;
        }

        const nickNameElement = document.querySelector('.profile_small_header_name > a');
        const avatarUrlElement = document.querySelector('.profile_small_header_avatar .playerAvatar > img');

        const nickName = nickNameElement ? nickNameElement.textContent.trim() : '';
        const avatarUrl = avatarUrlElement ? avatarUrlElement.src : '';

        const modalWindow = new ModalWindow(nickName, avatarUrl);
        const sortedItemsList = Object.entries(appInventoryManager.items)
            .sort(([keyA], [keyB]) => keyA - keyB)
            .map(([key, value]) => value);

        sortedItemsList.forEach(item => modalWindow.addItem(item));

    }

    function createButton() {
        const button = document.createElement("button");
        button.innerText = "All Items Table";
        button.classList.add("btn_darkblue_white_innerfade");
        button.style.width = "100%";
        button.style.height = "30px";
        button.style.lineHeight = "30px";
        button.style.fontSize = "15px";
        button.style.position = "relative";
        button.style.zIndex = "2";

        button.addEventListener("click", async function() {
            if (button.disabled) return;
            button.disabled = true;
            try { await localAppData(); }
            catch (error) { console.error(error); }
            button.disabled = false;
        });
        function updateButtonText() {
            const gameNameElement = document.querySelector('.name_game');
            if (gameNameElement) {
                button.disabled = true;
                let remainingTime = 5;
                const gameName = gameNameElement.textContent.trim();

                button.innerText = `All Items Table in ${gameName} (wait ${remainingTime} sec)`;
                const timer = setInterval(() => {
                    if (gameName !== gameNameElement.textContent.trim()) {
                        clearInterval(timer);
                        return;
                    }

                    remainingTime--;
                    button.innerText = `All Items Table in ${gameName} (wait ${remainingTime} sec)`;

                    if (remainingTime <= 0) {
                        clearInterval(timer);
                        button.innerText = `All Items Table in ${gameName}`;
                        button.disabled = false;
                    }
                }, 1000);
            }
        }
        function waitForElement(selector) {
            return new Promise((resolve) => {
                const observer = new MutationObserver((mutations, observer) => {
                    if (document.querySelector(selector)) {
                        observer.disconnect();
                        resolve(document.querySelector(selector));
                    }
                });
                observer.observe(document.body, { childList: true, subtree: true });
            });
        }
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' || mutation.type === 'characterData') {
                    updateButtonText();
                }
            });
        });
        waitForElement('.name_game').then((target) => {
            observer.observe(target, { childList: true, subtree: true, characterData: true });
            updateButtonText();
        });
        const referenceElement = document.querySelector('#tabcontent_inventory');
        if (referenceElement) {
            referenceElement.parentNode.insertBefore(button, referenceElement);
            updateButtonText();
        }
    }
})();