Steam Inventory Items Table

Shows you all your items in a cool way.

  1. // ==UserScript==
  2. // @name Steam Inventory Items Table
  3. // @namespace https://github.com/Kostya12rus/steam_inventory_stack/
  4. // @supportURL https://github.com/Kostya12rus/steam_inventory_stack/issues
  5. // @version 1.0.1
  6. // @description Shows you all your items in a cool way.
  7. // @author Kostya12rus
  8. // @match https://steamcommunity.com/profiles/*/inventory*
  9. // @match https://steamcommunity.com/id/*/inventory*
  10. // @license AGPL-3.0
  11. // ==/UserScript==
  12.  
  13. class MarketDescription {
  14. constructor(descriptionDict = {}) {
  15. this.type = descriptionDict.type || '';
  16. this.value = descriptionDict.value || '';
  17. }
  18. }
  19. class MarketAssetDescription {
  20. constructor(assetDescriptionDict = {}) {
  21. this.appid = assetDescriptionDict.appid || 0;
  22. this.classid = assetDescriptionDict.classid || '';
  23. this.instanceid = assetDescriptionDict.instanceid || '';
  24. this.name = assetDescriptionDict.name || '';
  25. this.nameColor = assetDescriptionDict.name_color || '';
  26. this.marketName = assetDescriptionDict.market_name || '';
  27. this.marketHashName = assetDescriptionDict.market_hash_name || '';
  28.  
  29. this.tradable = Boolean(assetDescriptionDict.tradable || false);
  30. this.marketable = Boolean(assetDescriptionDict.marketable || false);
  31. this.commodity = Boolean(assetDescriptionDict.commodity || false);
  32.  
  33. this.marketTradableRestriction = assetDescriptionDict.market_tradable_restriction || -1;
  34. this.marketMarketableRestriction = assetDescriptionDict.market_marketable_restriction || -1;
  35.  
  36. this.iconUrl = assetDescriptionDict.icon_url || '';
  37. this.iconUrlLarge = assetDescriptionDict.icon_url_large || '';
  38.  
  39. this.currency = assetDescriptionDict.currency || 0;
  40. this.descriptions = (assetDescriptionDict.descriptions || []).map(d => new MarketDescription(d));
  41. this.type = assetDescriptionDict.type || '';
  42. this.backgroundColor = assetDescriptionDict.background_color || '';
  43. }
  44. }
  45. class MarketItem {
  46. constructor(itemDict = {}) {
  47. this.name = itemDict.name || ' ';
  48. this.hashName = itemDict.hash_name || '';
  49.  
  50. this.sellListings = itemDict.sell_listings || 0;
  51. this.sellPrice = itemDict.sell_price || 0;
  52. this.sellPriceText = itemDict.sell_price_text || '';
  53. this.salePriceText = itemDict.sale_price_text || '';
  54.  
  55. this.assetDescription = new MarketAssetDescription(itemDict.asset_description || {});
  56.  
  57. this.appName = itemDict.app_name || '';
  58. this.appIcon = itemDict.app_icon || '';
  59. }
  60.  
  61. loadSave(data) {
  62. this.name = data.name || ' ';
  63. this.hashName = data.hashName || '';
  64.  
  65. this.sellListings = data.sellListings || 0;
  66. this.sellPrice = data.sellPrice || 0;
  67. this.sellPriceText = data.sellPriceText || '';
  68. this.salePriceText = data.salePriceText || '';
  69.  
  70. this.assetDescription = new MarketAssetDescription(data.assetDescription || {});
  71.  
  72. this.appName = data.appName || '';
  73. this.appName = data.appName || '';
  74. return this;
  75. }
  76.  
  77. toString() {
  78. return `<${this.constructor.name}> name: ${this.name}, price: ${this.sellPriceText}, listings: ${this.sellPrice}`;
  79. }
  80.  
  81. isBugItem() {
  82. return this.hashName !== this.assetDescription.marketHashName;
  83. }
  84.  
  85. isEmpty() {
  86. return this.hashName === '';
  87. }
  88.  
  89. iconUrl() {
  90. if (!this.assetDescription.iconUrl) return '';
  91. return `https://community.akamai.steamstatic.com/economy/image/${this.assetDescription.iconUrl}/330x192?allow_animated=1`;
  92. }
  93.  
  94. marketUrl() {
  95. if (!this.assetDescription.appid || !this.assetDescription.marketHashName) return '';
  96. return `https://steamcommunity.com/market/listings/${this.assetDescription.appid}/${this.assetDescription.marketHashName}`;
  97. }
  98.  
  99. marketHashName() {
  100. return this.assetDescription.marketHashName;
  101. }
  102.  
  103. color() {
  104. return this.assetDescription.nameColor ? `#${this.assetDescription.nameColor}`.replace('##', '#') : '';
  105. }
  106.  
  107. isCurrentGame(appId) {
  108. return String(this.assetDescription.appid) === String(appId);
  109. }
  110.  
  111. replaceNumberInCurrency(newNumber) {
  112. return this.sellPriceText.replace(/\d{1,3}(?:\s?\d{3})*(?:[,.]\d+)?/, newNumber);
  113. }
  114.  
  115. generateNumberInCurrency(newNumber) {
  116. return this.replaceNumberInCurrency((newNumber / 100).toFixed(2));
  117. }
  118.  
  119. multiplyPriceInCurrency(count) {
  120. return this.generateNumberInCurrency(this.sellPrice * count);
  121. }
  122.  
  123. calculateCommission(price = null) {
  124. return this.generateNumberInCurrency(this.calculateCommissionInteger(price));
  125. }
  126.  
  127. calculateCommissionInteger(price = null) {
  128. if (!price) price = this.sellPrice;
  129. const commission = Math.abs(price - (price / 115 * 100));
  130. return price - commission;
  131. }
  132. }
  133.  
  134. class InventoryDescription {
  135. constructor(descriptionDict = {}) {
  136. this.type = descriptionDict.type || '';
  137. this.value = descriptionDict.value || '';
  138. }
  139. }
  140. class InventoryTag {
  141. constructor(tagDict = {}) {
  142. this.category = tagDict.category || '';
  143. this.internalName = tagDict.internal_name || '';
  144. this.categoryName = tagDict.category_name || '';
  145. this.name = tagDict.name || '';
  146. }
  147. }
  148. class InventoryItem {
  149. constructor(itemDict = {}) {
  150. this.classid = itemDict.classid || '';
  151. this.instanceid = itemDict.instanceid || '';
  152. this.amount = itemDict.amount || '1';
  153.  
  154. const rgDescriptions = itemDict.rgDescriptions || {};
  155. this.rgDescriptions = {
  156. appid: rgDescriptions.appid || '',
  157. classid: rgDescriptions.classid || '',
  158. instanceid: rgDescriptions.instanceid || '',
  159. iconUrl: rgDescriptions.icon_url || '',
  160. iconUrlLarge: rgDescriptions.icon_url_large || '',
  161. iconDragUrl: rgDescriptions.icon_drag_url || '',
  162. name: rgDescriptions.name || '',
  163. marketHashName: rgDescriptions.market_hash_name || '',
  164. marketName: rgDescriptions.market_name || '',
  165. nameColor: rgDescriptions.name_color || '',
  166. backgroundColor: rgDescriptions.background_color || '',
  167. type: rgDescriptions.type || '',
  168. tradable: Boolean(rgDescriptions.tradable || false),
  169. marketable: Boolean(rgDescriptions.marketable || false),
  170. commodity: Boolean(rgDescriptions.commodity || false),
  171. marketTradableRestriction: rgDescriptions.market_tradable_restriction || '-1',
  172. marketMarketableRestriction: rgDescriptions.market_marketable_restriction || '7',
  173. descriptions: (rgDescriptions.descriptions || []).map(desc => new InventoryDescription(desc)),
  174. tags: (rgDescriptions.tags || []).map(tag => new InventoryTag(tag))
  175. };
  176. }
  177.  
  178. updateRgDescriptions(rgDescriptions) {
  179. this.rgDescriptions = {
  180. appid: rgDescriptions.appid || '',
  181. classid: rgDescriptions.classid || '',
  182. instanceid: rgDescriptions.instanceid || '',
  183. iconUrl: rgDescriptions.icon_url || '',
  184. iconUrlLarge: rgDescriptions.icon_url_large || '',
  185. iconDragUrl: rgDescriptions.icon_drag_url || '',
  186. name: rgDescriptions.name || '',
  187. marketHashName: rgDescriptions.market_hash_name || '',
  188. marketName: rgDescriptions.market_name || '',
  189. nameColor: rgDescriptions.name_color || '',
  190. backgroundColor: rgDescriptions.background_color || '',
  191. type: rgDescriptions.type || '',
  192. tradable: Boolean(rgDescriptions.tradable || false),
  193. marketable: Boolean(rgDescriptions.marketable || false),
  194. commodity: Boolean(rgDescriptions.commodity || false),
  195. marketTradableRestriction: rgDescriptions.market_tradable_restriction || '-1',
  196. marketMarketableRestriction: rgDescriptions.market_marketable_restriction || '7',
  197. descriptions: (rgDescriptions.descriptions || []).map(desc => new InventoryDescription(desc)),
  198. tags: (rgDescriptions.tags || []).map(tag => new InventoryTag(tag))
  199. };
  200. }
  201.  
  202. name() {
  203. if (!this.rgDescriptions.name) return '';
  204. return this.rgDescriptions.name;
  205. }
  206.  
  207. iconUrl() {
  208. if (!this.rgDescriptions.iconUrl) return '';
  209. return `https://community.akamai.steamstatic.com/economy/image/${this.rgDescriptions.iconUrl}/330x192?allow_animated=1`;
  210. }
  211.  
  212. marketUrl() {
  213. if (!this.rgDescriptions.appid || !this.rgDescriptions.marketHashName) return '';
  214. return `https://steamcommunity.com/market/listings/${this.rgDescriptions.appid}/${this.rgDescriptions.marketHashName}`;
  215. }
  216.  
  217. color() {
  218. return this.rgDescriptions.nameColor ? `#${this.rgDescriptions.nameColor}`.replace('##', '#') : '';
  219. }
  220. }
  221. class InventoryManager {
  222. constructor(items = {}) {
  223. this.rgDescriptions = items.descriptions || {};
  224. if (typeof this.rgDescriptions !== 'object') {
  225. this.rgDescriptions = {};
  226. }
  227. this.rgInventory = items.assets || [];
  228. if (typeof this.rgInventory !== 'object') {
  229. this.rgInventory = [];
  230. }
  231. this.success = Boolean(items.success || false);
  232. this.inventory = [];
  233.  
  234. this.parseInventory();
  235. }
  236.  
  237. loadSaveInventory(oldData) {
  238. this.rgDescriptions = oldData.rgDescriptions || {};
  239. this.rgInventory = oldData.rgInventory || [];
  240. this.inventory = oldData.inventory || [];
  241. this.parseInventory();
  242. return this;
  243. }
  244.  
  245. addNextInvent(nextInventory) {
  246. if (!(nextInventory instanceof InventoryManager)) return;
  247. this.rgInventory.push(...nextInventory.rgInventory);
  248. for (const [key, value] of Object.entries(nextInventory.rgDescriptions)) {
  249. this.rgDescriptions[key] = value;
  250. }
  251. this.parseInventory();
  252. }
  253.  
  254. parseInventory() {
  255. this.inventory = [];
  256.  
  257. for (const [key, item] of Object.entries(this.rgInventory)) {
  258. const inventoryItem = new InventoryItem(item);
  259. this.inventory.push(inventoryItem);
  260. }
  261.  
  262. for (const item of this.inventory) {
  263. const classid = item.classid || 0;
  264. if (classid === 0) continue;
  265.  
  266. const instanceid = item.instanceid || 0;
  267. for (const [key, itemDescription] of Object.entries(this.rgDescriptions)) {
  268. const classidD = itemDescription.classid || 0;
  269. if (classid !== classidD) continue;
  270.  
  271. const instanceidD = itemDescription.instanceid || 0;
  272. if (instanceid !== instanceidD) continue;
  273.  
  274. item.updateRgDescriptions(itemDescription);
  275. break;
  276. }
  277. }
  278. }
  279. }
  280.  
  281. class TotalItem {
  282. constructor(classid) {
  283. this.classid = classid;
  284. this.items = [];
  285. this.marketData = null
  286. }
  287. addItem(item) {
  288. this.items.push(item);
  289. }
  290. setMarketData(marketData) {
  291. this.marketData = marketData;
  292. }
  293.  
  294. getCount() {
  295. return this.items.reduce((total, _item) => total + parseInt(_item.amount), 0);
  296. }
  297. getConsolePrice(){
  298. if (!this.marketData) return 0;
  299. return this.marketData.sellPrice * this.getCount();
  300. }
  301. getConsolePriceOne(){
  302. if (!this.marketData) return 0;
  303. return this.marketData.sellPrice;
  304. }
  305. getOtherPrice(price = 0){
  306. if (!this.marketData) return null;
  307. return this.marketData.generateNumberInCurrency(price);
  308. }
  309. getPrice() {
  310. if (!this.marketData) return '';
  311. return this.marketData.multiplyPriceInCurrency(this.getCount());
  312. }
  313. getOnePrice() {
  314. if (!this.marketData) return '';
  315. return this.marketData.multiplyPriceInCurrency(1);
  316. }
  317. getIconUrl() {
  318. if (this.marketData)
  319. {
  320. return this.marketData.iconUrl();
  321. }
  322. else
  323. {
  324. const item = this.items[0];
  325. if (item)
  326. {
  327. return item.iconUrl();
  328. }
  329. }
  330. return '';
  331. }
  332. getMarketUrl() {
  333. if (this.marketData)
  334. {
  335. return this.marketData.marketUrl();
  336. }
  337. else
  338. {
  339. const item = this.items[0];
  340. if (item)
  341. {
  342. return item.marketUrl();
  343. }
  344. }
  345. return '';
  346. }
  347. getName() {
  348. if (this.marketData)
  349. {
  350. return this.marketData.name;
  351. }
  352. else
  353. {
  354. const item = this.items[0];
  355. if (item)
  356. {
  357. return item.name();
  358. }
  359. }
  360. return '';
  361. }
  362. getColor() {
  363. if (this.marketData)
  364. {
  365. return this.marketData.color();
  366. }
  367. else
  368. {
  369. const item = this.items[0];
  370. if (item)
  371. {
  372. return item.color();
  373. }
  374. }
  375. return '';
  376. }
  377. }
  378. class TotalItemsManager {
  379. constructor() {
  380. this.items = {};
  381. this.inventoryManager = new InventoryManager();
  382. this.marketItems = [];
  383. this.cachedData = {};
  384. }
  385. async loadTotalItems() {
  386. const { m_appid, m_contextid, m_steamid } = g_ActiveInventory;
  387.  
  388. this.appid = m_appid;
  389. this.contextid = m_contextid;
  390. this.steamid = m_steamid;
  391.  
  392. if (!this.cachedData) { this.cachedData = {}; }
  393.  
  394. const cacheKey = `inventory_${this.steamid}_${this.appid}`;
  395. const now = new Date().getTime();
  396.  
  397. if (this.cachedData[cacheKey] && this.cachedData[cacheKey].expiry > now) {
  398. this.inventoryManager = this.cachedData[cacheKey].inventoryManager;
  399. this.marketItems = this.cachedData[cacheKey].marketItems;
  400. this.parseItems();
  401. return;
  402. }
  403.  
  404. this.inventoryManager = await this.getFullInventory();
  405. this.marketItems = await this.getGameMarketList();
  406. this.parseItems();
  407.  
  408. if (!this.cachedData[cacheKey]) { this.cachedData[cacheKey] = {}; }
  409.  
  410. this.cachedData[cacheKey].inventoryManager = this.inventoryManager;
  411. this.cachedData[cacheKey].marketItems = this.marketItems;
  412. this.cachedData[cacheKey].expiry = now + 10 * 60 * 1000;
  413. }
  414. parseItems() {
  415. this.items = [];
  416. for (const item of this.inventoryManager.inventory) {
  417. const classid = item.classid || 0;
  418. if (!this.items[classid]) {
  419. this.items[classid] = new TotalItem(classid);
  420. }
  421. this.items[classid].addItem(item);
  422. }
  423. for (const item of this.marketItems) {
  424. const classid = item.assetDescription.classid || 0;
  425. if (!this.items[classid]) {
  426. continue;
  427. }
  428. this.items[classid].setMarketData(item);
  429. }
  430. }
  431.  
  432. async getGameMarketList(start = 0, count = 100) {
  433. const searchParams = new URLSearchParams({
  434. start: start,
  435. count: count,
  436. search_descriptions: 0,
  437. sort_column: 'popular',
  438. sort_dir: 'desc',
  439. appid: this.appid,
  440. norender: 1
  441. });
  442.  
  443. const searchUrl = `https://steamcommunity.com/market/search/render/?${searchParams.toString()}`;
  444. let _marketItems = [];
  445.  
  446. try {
  447. const marketResponse = await fetch(searchUrl, { method: 'GET', timeout: 10000 });
  448. if (marketResponse.ok) {
  449. const responseData = await marketResponse.json();
  450. if (responseData.success) {
  451. const items = responseData.results.map(itemData => new MarketItem(itemData));
  452. _marketItems = _marketItems.concat(items);
  453. const totalItemsAvailable = responseData.total_count || 0;
  454. if (totalItemsAvailable > start + count && start + count < 1000) {
  455. start += count;
  456. const additionalItems = await this.getGameMarketList(start, count);
  457. _marketItems = _marketItems.concat(additionalItems);
  458. }
  459. return _marketItems;
  460. }
  461. }
  462. } catch (e) {
  463. console.error(`getGameMarketList failed: ${e.message}`);
  464. }
  465. return _marketItems;
  466. }
  467. async getFullInventory() {
  468. try {
  469. const inventoryManager = new InventoryManager();
  470. return await this.getInventoryItems(inventoryManager);
  471. } catch (error) {
  472. console.error("Ошибка при получении предметов инвентаря:", error);
  473. }
  474. return this.inventoryManager;
  475. }
  476. getInventoryItems(inventoryManager, start_assetid = null) {
  477. const searchParams = new URLSearchParams({
  478. count: 2000,
  479. });
  480. if (start_assetid) {
  481. searchParams.set('start_assetid', start_assetid);
  482. }
  483. const url = `https://steamcommunity.com/inventory/${this.steamid}/${this.appid}/${this.contextid}?${searchParams.toString()}`;
  484. return fetch(url, {
  485. method: 'GET',
  486. headers: {
  487. 'Content-Type': 'application/json'
  488. }
  489. })
  490. .then(response => {
  491. if (!response.ok) {
  492. throw new Error(`HTTP error! status: ${response.status}`);
  493. }
  494. return response.json();
  495. })
  496. .then(data => {
  497. if (!data.success) {
  498. throw new Error("Не удалось получить данные инвентаря.");
  499. }
  500. inventoryManager.addNextInvent(new InventoryManager(data));
  501. const more_items = data.more_items;
  502. if (Number.isInteger(more_items) && more_items > 0) {
  503. return this.getInventoryItems(inventoryManager, data.last_assetid);
  504. }
  505. return inventoryManager;
  506. })
  507. .catch(error => {
  508. console.error("Ошибка проверки инвентаря:", error);
  509. throw error;
  510. });
  511. }
  512. }
  513.  
  514. class ModalWindow {
  515. constructor(userNickname, userAvatar) {
  516. this.userNickname = userNickname
  517. this.userAvatar = userAvatar
  518. this.items = [];
  519.  
  520. this.overlay = document.createElement('div');
  521. this.modal = document.createElement('div');
  522. this.closeButton = document.createElement('button');
  523.  
  524. this.settingModal();
  525. }
  526.  
  527. settingModal() {
  528. this.overlay.style.position = 'fixed';
  529. this.overlay.style.top = '0';
  530. this.overlay.style.left = '0';
  531. this.overlay.style.width = '100%';
  532. this.overlay.style.height = '100%';
  533. this.overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
  534. this.overlay.style.zIndex = '9999';
  535. this.overlay.style.display = 'flex';
  536. this.overlay.style.justifyContent = 'center';
  537. this.overlay.style.alignItems = 'center';
  538. this.overlay.style.opacity = '0';
  539. this.overlay.style.transition = 'opacity 0.3s ease-in-out';
  540. this.overlay.addEventListener('click', this.closeModal.bind(this));
  541.  
  542. this.modal.style.padding = '30px';
  543. this.modal.style.backgroundColor = '#242424';
  544. this.modal.style.borderRadius = '12px';
  545. this.modal.style.boxShadow = '0 8px 16px rgba(0, 0, 0, 0.5)';
  546. this.modal.style.color = '#e0e0e0';
  547. this.modal.style.width = '800px';
  548. this.modal.style.maxHeight = '90vh';
  549. this.modal.style.overflowY = 'auto';
  550. this.modal.style.position = 'relative';
  551. this.modal.style.transform = 'scale(0.9)';
  552. this.modal.style.opacity = '0';
  553. this.modal.style.transition = 'transform 0.3s ease-in-out, opacity 0.3s ease-in-out';
  554. this.modal.addEventListener('click', function(event) { event.stopPropagation(); });
  555.  
  556. this.closeButton.innerText = '✖';
  557. this.closeButton.style.position = 'absolute';
  558. this.closeButton.style.top = '10px';
  559. this.closeButton.style.right = '10px';
  560. this.closeButton.style.background = 'none';
  561. this.closeButton.style.border = 'none';
  562. this.closeButton.style.color = '#fff';
  563. this.closeButton.style.fontSize = '20px';
  564. this.closeButton.style.cursor = 'pointer';
  565. this.closeButton.addEventListener('click', this.closeModal.bind(this));
  566.  
  567. this.modal.appendChild(this.closeButton);
  568.  
  569. this.addProfileInfo();
  570. this.addInventoryTable();
  571.  
  572. this.overlay.appendChild(this.modal);
  573. document.body.appendChild(this.overlay);
  574.  
  575. requestAnimationFrame(() => {
  576. this.overlay.style.opacity = '1';
  577. this.modal.style.transform = 'scale(1)';
  578. this.modal.style.opacity = '1';
  579. });
  580. }
  581. addProfileInfo() {
  582. this.profileContainer = document.createElement('div');
  583. this.profileContainer.style.display = 'flex';
  584. this.profileContainer.style.alignItems = 'center';
  585. this.profileContainer.style.justifyContent = 'center';
  586. this.profileContainer.style.marginBottom = '10px';
  587. this.profileContainer.style.color = '#ffffff';
  588.  
  589. this.avatar = document.createElement('img');
  590. this.avatar.src = this.userAvatar;
  591. this.avatar.alt = 'Profile Avatar';
  592. this.avatar.style.width = '60px';
  593. this.avatar.style.height = '60px';
  594. this.avatar.style.borderRadius = '50%';
  595. this.avatar.style.marginRight = '15px';
  596.  
  597. this.infoContainer = document.createElement('div');
  598. this.infoContainer.style.display = 'flex';
  599. this.infoContainer.style.alignItems = 'center';
  600.  
  601. this.nickname = document.createElement('h2');
  602. this.nickname.innerText = this.userNickname;
  603. this.nickname.style.margin = '0 20px 0 0';
  604. this.nickname.style.fontSize = '20px';
  605. this.nickname.style.fontWeight = 'bold';
  606. this.nickname.style.color = '#6a5acd';
  607.  
  608. this.detailsContainer = document.createElement('div');
  609. this.detailsContainer.style.display = 'flex';
  610. this.detailsContainer.style.alignItems = 'center';
  611.  
  612. const itemCountContainer = document.createElement('div');
  613. itemCountContainer.style.marginRight = '20px';
  614. const itemCountLabel = document.createElement('p');
  615. itemCountLabel.innerText = 'Количество вещей';
  616. itemCountLabel.style.fontSize = '14px';
  617. itemCountLabel.style.color = '#b0c4de';
  618. itemCountLabel.style.margin = '0';
  619. itemCountLabel.style.textAlign = 'center';
  620. this.itemCountValue = document.createElement('p');
  621. this.itemCountValue.innerText = `123`;
  622. this.itemCountValue.style.margin = '0';
  623. this.itemCountValue.style.fontSize = '16px';
  624. this.itemCountValue.style.fontWeight = 'bold';
  625. this.itemCountValue.style.textAlign = 'center';
  626. this.itemCountValue.style.color = '#ffffff';
  627.  
  628. itemCountContainer.appendChild(itemCountLabel);
  629. itemCountContainer.appendChild(this.itemCountValue);
  630.  
  631. const inventoryValueContainer = document.createElement('div');
  632. const inventoryValueLabel = document.createElement('p');
  633. inventoryValueLabel.innerText = 'Сумма инвентаря';
  634. inventoryValueLabel.style.fontSize = '14px';
  635. inventoryValueLabel.style.color = '#b0c4de';
  636. inventoryValueLabel.style.margin = '0';
  637. inventoryValueLabel.style.textAlign = 'center';
  638. this.inventoryValue = document.createElement('p');
  639. this.inventoryValue.innerText = ``;
  640. this.inventoryValue.style.margin = '0';
  641. this.inventoryValue.style.fontSize = '16px';
  642. this.inventoryValue.style.fontWeight = 'bold';
  643. this.inventoryValue.style.textAlign = 'center';
  644. this.inventoryValue.style.color = '#ffffff';
  645.  
  646. inventoryValueContainer.appendChild(inventoryValueLabel);
  647. inventoryValueContainer.appendChild(this.inventoryValue);
  648.  
  649. this.detailsContainer.appendChild(itemCountContainer);
  650. this.detailsContainer.appendChild(inventoryValueContainer);
  651.  
  652. this.infoContainer.appendChild(this.nickname);
  653. this.infoContainer.appendChild(this.detailsContainer);
  654.  
  655. this.profileContainer.appendChild(this.avatar);
  656. this.profileContainer.appendChild(this.infoContainer);
  657.  
  658. this.modal.appendChild(this.profileContainer);
  659. }
  660. addInventoryTable() {
  661. this.table = document.createElement('table');
  662. this.table.style.width = '100%';
  663. this.table.style.borderCollapse = 'separate';
  664. this.table.style.borderSpacing = '0';
  665. this.table.style.borderRadius = '8px';
  666. this.table.style.overflow = 'hidden';
  667. this.table.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)';
  668.  
  669. const thead = document.createElement('thead');
  670. const headerRow = document.createElement('tr');
  671.  
  672. const headers = [' ', 'Название предмета', 'Количество', 'Цена за штуку', 'Цена за все'];
  673. const textAligns = ['left', 'left', 'center', 'right', 'right'];
  674.  
  675. headers.forEach((headerText, index) => {
  676. const th = document.createElement('th');
  677. th.innerText = headerText;
  678. th.style.borderBottom = '1px solid #ccc';
  679. th.style.textAlign = textAligns[index];
  680. th.style.cursor = 'pointer';
  681. th.setAttribute('data-order', 'asc');
  682. th.addEventListener('click', () => this.sortTableByColumn(index));
  683. headerRow.appendChild(th);
  684. });
  685.  
  686. thead.appendChild(headerRow);
  687. this.table.appendChild(thead);
  688.  
  689. this.tbody = document.createElement('tbody');
  690. this.table.appendChild(this.tbody);
  691.  
  692. this.modal.appendChild(this.table);
  693. }
  694. sortTableByColumn(columnIndex) {
  695. const rows = Array.from(this.tbody.querySelectorAll('tr'));
  696. const isNumeric = columnIndex !== 1;
  697. const header = this.table.rows[0].cells[columnIndex];
  698.  
  699. const order = header.getAttribute('data-order') === 'desc' ? 'asc' : 'desc';
  700. header.setAttribute('data-order', order);
  701.  
  702. rows.sort((a, b) => {
  703. let aValue = a.cells[columnIndex].dataset.sort;
  704. let bValue = b.cells[columnIndex].dataset.sort;
  705.  
  706. if (isNumeric) {
  707. aValue = parseFloat(aValue);
  708. bValue = parseFloat(bValue);
  709. }
  710.  
  711. if (order === 'asc') {
  712. return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
  713. } else {
  714. return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
  715. }
  716. });
  717.  
  718. while (this.tbody.firstChild) {
  719. this.tbody.removeChild(this.tbody.firstChild);
  720. }
  721.  
  722. rows.forEach(row => this.tbody.appendChild(row));
  723. }
  724. addItemToTable(item) {
  725. const row = document.createElement('tr');
  726.  
  727. const imgCell = document.createElement('td');
  728. const img = document.createElement('img');
  729. img.src = item.getIconUrl();
  730. img.alt = item.getName();
  731. img.style.height = '30px';
  732. img.style.width = 'auto';
  733. imgCell.appendChild(img);
  734. row.appendChild(imgCell);
  735.  
  736. const nameCell = document.createElement('td');
  737. nameCell.style.color = item.getColor();
  738. nameCell.dataset.sort = item.getName();
  739.  
  740. const nameLink = document.createElement('a');
  741. nameLink.href = item.getMarketUrl();
  742. nameLink.innerText = item.getName();
  743. nameLink.style.color = 'inherit';
  744. nameLink.style.textDecoration = 'none';
  745. nameLink.target = "_blank";
  746. nameCell.appendChild(nameLink);
  747. row.appendChild(nameCell);
  748.  
  749. const quantityCell = document.createElement('td');
  750. quantityCell.innerText = item.getCount();
  751. quantityCell.style.textAlign = 'center';
  752. quantityCell.dataset.sort = item.getCount();
  753. row.appendChild(quantityCell);
  754.  
  755. const pricePerItemCell = document.createElement('td');
  756. pricePerItemCell.innerText = item.getOnePrice();
  757. pricePerItemCell.style.textAlign = 'right';
  758. pricePerItemCell.dataset.sort = item.getConsolePriceOne();
  759. row.appendChild(pricePerItemCell);
  760.  
  761. const totalPriceCell = document.createElement('td');
  762. totalPriceCell.innerText = item.getPrice();
  763. totalPriceCell.style.textAlign = 'right';
  764. totalPriceCell.dataset.sort = item.getConsolePrice();
  765. row.appendChild(totalPriceCell);
  766.  
  767. this.tbody.appendChild(row);
  768. }
  769.  
  770. addItem(newItem) {
  771. if (!newItem || newItem.getName() === '') { return; }
  772.  
  773. this.items.push(newItem);
  774. this.addItemToTable(newItem);
  775.  
  776. const totalCount = this.items.reduce((total, item) => total + item.getCount(), 0);
  777. this.itemCountValue.innerText = `${totalCount}`;
  778.  
  779. const totalPriceFloat = this.items.reduce((total, item) => total + item.getConsolePrice(), 0);
  780. const itemWithMarketData = this.items.find(_item => _item.marketData);
  781. if (itemWithMarketData) {
  782. this.inventoryValue.innerText = `${itemWithMarketData.getOtherPrice(totalPriceFloat)}`;
  783. }
  784. }
  785.  
  786. closeModal() {
  787. this.overlay.style.opacity = '0';
  788. this.modal.style.transform = 'scale(0.9)';
  789. this.modal.style.opacity = '0';
  790.  
  791. setTimeout(() => {
  792. document.body.removeChild(this.overlay);
  793. }, 300);
  794. }
  795. }
  796.  
  797. (function() {
  798. 'use strict';
  799. const appInventoryManager = new TotalItemsManager();
  800. createButton();
  801.  
  802. async function localAppData() {
  803. await appInventoryManager.loadTotalItems()
  804. if (Object.keys(appInventoryManager.items).length === 0) {
  805. alert('Не удалось получить список предметов. Пожалуйста, попробуйте позже');
  806. return;
  807. }
  808.  
  809. const nickNameElement = document.querySelector('.profile_small_header_name > a');
  810. const avatarUrlElement = document.querySelector('.profile_small_header_avatar .playerAvatar > img');
  811.  
  812. const nickName = nickNameElement ? nickNameElement.textContent.trim() : '';
  813. const avatarUrl = avatarUrlElement ? avatarUrlElement.src : '';
  814.  
  815. const modalWindow = new ModalWindow(nickName, avatarUrl);
  816. const sortedItemsList = Object.entries(appInventoryManager.items)
  817. .sort(([keyA], [keyB]) => keyA - keyB)
  818. .map(([key, value]) => value);
  819.  
  820. sortedItemsList.forEach(item => modalWindow.addItem(item));
  821.  
  822. }
  823.  
  824. function createButton() {
  825. const button = document.createElement("button");
  826. button.innerText = "All Items Table";
  827. button.classList.add("btn_darkblue_white_innerfade");
  828. button.style.width = "100%";
  829. button.style.height = "30px";
  830. button.style.lineHeight = "30px";
  831. button.style.fontSize = "15px";
  832. button.style.position = "relative";
  833. button.style.zIndex = "2";
  834.  
  835. button.addEventListener("click", async function() {
  836. if (button.disabled) return;
  837. button.disabled = true;
  838. try { await localAppData(); }
  839. catch (error) { console.error(error); }
  840. button.disabled = false;
  841. });
  842. function updateButtonText() {
  843. const gameNameElement = document.querySelector('.name_game');
  844. if (gameNameElement) {
  845. button.disabled = true;
  846. let remainingTime = 5;
  847. const gameName = gameNameElement.textContent.trim();
  848.  
  849. button.innerText = `All Items Table in ${gameName} (wait ${remainingTime} sec)`;
  850. const timer = setInterval(() => {
  851. if (gameName !== gameNameElement.textContent.trim()) {
  852. clearInterval(timer);
  853. return;
  854. }
  855.  
  856. remainingTime--;
  857. button.innerText = `All Items Table in ${gameName} (wait ${remainingTime} sec)`;
  858.  
  859. if (remainingTime <= 0) {
  860. clearInterval(timer);
  861. button.innerText = `All Items Table in ${gameName}`;
  862. button.disabled = false;
  863. }
  864. }, 1000);
  865. }
  866. }
  867. function waitForElement(selector) {
  868. return new Promise((resolve) => {
  869. const observer = new MutationObserver((mutations, observer) => {
  870. if (document.querySelector(selector)) {
  871. observer.disconnect();
  872. resolve(document.querySelector(selector));
  873. }
  874. });
  875. observer.observe(document.body, { childList: true, subtree: true });
  876. });
  877. }
  878. const observer = new MutationObserver((mutations) => {
  879. mutations.forEach((mutation) => {
  880. if (mutation.type === 'childList' || mutation.type === 'characterData') {
  881. updateButtonText();
  882. }
  883. });
  884. });
  885. waitForElement('.name_game').then((target) => {
  886. observer.observe(target, { childList: true, subtree: true, characterData: true });
  887. updateButtonText();
  888. });
  889. const referenceElement = document.querySelector('#tabcontent_inventory');
  890. if (referenceElement) {
  891. referenceElement.parentNode.insertBefore(button, referenceElement);
  892. updateButtonText();
  893. }
  894. }
  895. })();