cs2tracker 2.1.11__py3-none-any.whl → 2.1.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cs2tracker might be problematic. Click here for more details.

@@ -0,0 +1,187 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const axios = require("axios");
4
+ const VDF = require("@node-steam/vdf");
5
+
6
+ const translationsLink =
7
+ "https://raw.githubusercontent.com/SteamDatabase/GameTracking-CS2/master/game/csgo/pak01_dir/resource/csgo_english.txt";
8
+ const itemsGameLink =
9
+ "https://raw.githubusercontent.com/SteamDatabase/GameTracking-CS2/master/game/csgo/pak01_dir/scripts/items/items_game.txt";
10
+
11
+ const translationsCacheFile = path.join(__dirname, "translations.json");
12
+ const itemsCacheFile = path.join(__dirname, "items.json");
13
+
14
+ class ItemNameConverter {
15
+ constructor() {
16
+ this.translations = {};
17
+ this.items = {};
18
+ this.prefabs = {};
19
+ this.paintKits = {};
20
+ this.stickerKits = {};
21
+ }
22
+
23
+ async initialize() {
24
+ await Promise.all([this.loadTranslations(), this.loadItemsGame()]);
25
+ }
26
+
27
+ async loadTranslations() {
28
+ if (
29
+ fs.existsSync(translationsCacheFile) &&
30
+ fs.statSync(translationsCacheFile).mtime.getDate() ===
31
+ new Date().getDate()
32
+ ) {
33
+ this.translations = require(translationsCacheFile);
34
+ return;
35
+ }
36
+
37
+ const res = await axios.get(translationsLink);
38
+ const lines = res.data.split(/\n/);
39
+ for (const line of lines) {
40
+ const match = line.match(/"(.+?)"\s+"(.+?)"/);
41
+ if (match) {
42
+ this.translations[match[1].toLowerCase()] = match[2];
43
+ }
44
+ }
45
+
46
+ fs.writeFileSync(
47
+ translationsCacheFile,
48
+ JSON.stringify(this.translations, null, 2),
49
+ );
50
+ }
51
+
52
+ async loadItemsGame() {
53
+ if (
54
+ fs.existsSync(itemsCacheFile) &&
55
+ fs.statSync(itemsCacheFile).mtime.getDate() === new Date().getDate()
56
+ ) {
57
+ this.items = require(itemsCacheFile).items;
58
+ this.prefabs = require(itemsCacheFile).prefabs;
59
+ this.paintKits = require(itemsCacheFile).paint_kits;
60
+ this.stickerKits = require(itemsCacheFile).sticker_kits;
61
+ return;
62
+ }
63
+
64
+ const res = await axios.get(itemsGameLink);
65
+ const parsed = VDF.parse(res.data).items_game;
66
+ this.items = parsed.items;
67
+ this.prefabs = parsed.prefabs;
68
+ this.paintKits = parsed.paint_kits;
69
+ this.stickerKits = parsed.sticker_kits;
70
+
71
+ fs.writeFileSync(itemsCacheFile, JSON.stringify(parsed, null, 2));
72
+ }
73
+
74
+ translate(key) {
75
+ if (!key) return "";
76
+ return this.translations[key.replace("#", "").toLowerCase()] || key;
77
+ }
78
+
79
+ getItemName(item) {
80
+ const def = this.items[item.def_index];
81
+ if (!def) return "";
82
+
83
+ let baseName = "";
84
+ if (def.item_name) {
85
+ baseName = this.translate(def.item_name);
86
+ } else if (def.prefab && this.prefabs[def.prefab]) {
87
+ baseName = this.translate(this.prefabs[def.prefab].item_name);
88
+ }
89
+
90
+ let stickerName = "";
91
+ if (baseName === "Sticker" && item.stickers && item.stickers.length === 1) {
92
+ stickerName = this.translate(
93
+ this.stickerKits[String(item.stickers[0].sticker_id)].item_name,
94
+ );
95
+ }
96
+
97
+ let skinName = "";
98
+ if (item.paint_index && this.paintKits[item.paint_index]) {
99
+ skinName = this.translate(
100
+ this.paintKits[item.paint_index].description_tag,
101
+ );
102
+ }
103
+
104
+ let wear = "";
105
+ if (item.paint_wear !== undefined) {
106
+ wear = this.getWearName(item.paint_wear);
107
+ }
108
+
109
+ // It is a knife / glove
110
+ if (item.quality === 3) {
111
+ baseName = "★ " + baseName;
112
+ }
113
+
114
+ // It is a stattrak or souvenir item
115
+ if (item.attribute && item.attribute.length > 0) {
116
+ for (let [_attributeName, attributeValue] of Object.entries(
117
+ item.attribute,
118
+ )) {
119
+ switch (attributeValue.def_index) {
120
+ case 80:
121
+ baseName = baseName.includes("StatTrak™")
122
+ ? baseName
123
+ : "StatTrak™ " + baseName;
124
+ break;
125
+ case 140:
126
+ baseName = baseName.includes("Souvenir")
127
+ ? baseName
128
+ : "Souvenir " + baseName;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+
134
+ if (baseName && skinName) {
135
+ return `${baseName} | ${skinName}${wear ? ` (${wear})` : ""}`;
136
+ } else if (baseName && stickerName) {
137
+ return `${baseName} | ${stickerName}`;
138
+ }
139
+
140
+ return baseName;
141
+ }
142
+
143
+ getWearName(paintWear) {
144
+ const wearLevels = [
145
+ { max: 0.07, name: "Factory New" },
146
+ { max: 0.15, name: "Minimal Wear" },
147
+ { max: 0.38, name: "Field-Tested" },
148
+ { max: 0.45, name: "Well-Worn" },
149
+ { max: 1, name: "Battle-Scarred" },
150
+ ];
151
+ return wearLevels.find((w) => paintWear <= w.max)?.name || "";
152
+ }
153
+
154
+ getItemType(item) {
155
+ const def = this.items[item.def_index];
156
+ if (!def) return "unknown";
157
+
158
+ if (def.item_name) {
159
+ let translatedName =
160
+ def.item_name.replace("#", "").toLowerCase() || def.item_name;
161
+ if (
162
+ translatedName.includes("crate_sticker_pack") ||
163
+ translatedName.includes("crate_signature_pack")
164
+ ) {
165
+ return "sticker capsule";
166
+ } else if (translatedName.includes("crate_community")) {
167
+ return "case";
168
+ } else if (translatedName.includes("csgo_tool_spray")) {
169
+ return "graffiti kit";
170
+ } else if (translatedName.includes("csgo_tool_sticker")) {
171
+ return "sticker";
172
+ }
173
+ }
174
+
175
+ return "other";
176
+ }
177
+
178
+ convertInventory(inventoryList) {
179
+ return inventoryList.map((item) => ({
180
+ ...item,
181
+ item_name: this.getItemName(item),
182
+ item_type: this.getItemType(item),
183
+ }));
184
+ }
185
+ }
186
+
187
+ module.exports = ItemNameConverter;
@@ -0,0 +1,148 @@
1
+ const SteamUser = require("steam-user");
2
+ const CS2 = require("globaloffensive");
3
+ const { argv } = require("process");
4
+ const fs = require("fs");
5
+
6
+ const ItemNameConverter = require("./convert_inventory.js");
7
+
8
+ process.stdin.setEncoding("utf-8");
9
+ process.stdout.setEncoding("utf-8");
10
+ process.stderr.setEncoding("utf-8");
11
+
12
+ const args = argv.slice(2);
13
+ const processedInventoryPath = args[0];
14
+ const importCases = args[1] === "True" ? true : false;
15
+ const importStickerCapsules = args[2] === "True" ? true : false;
16
+ const importStickers = args[3] === "True" ? true : false;
17
+ const importOthers = args[4] === "True" ? true : false;
18
+ const userName = args[5];
19
+ const password = args[6];
20
+ const twoFactorCode = args[7];
21
+
22
+ const paddedLog = (...args) => {
23
+ console.log(" [+] ", ...args);
24
+ };
25
+
26
+ const originalConsoleError = console.error;
27
+ console.error = (...args) => {
28
+ originalConsoleError(" [!] " + args.join(" "));
29
+ };
30
+
31
+ (async () => {
32
+ let user = new SteamUser();
33
+
34
+ paddedLog("Logging into Steam...");
35
+
36
+ user.logOn({
37
+ accountName: userName,
38
+ password: password,
39
+ twoFactorCode: twoFactorCode,
40
+ });
41
+
42
+ user.on("error", (err) => {
43
+ console.error("Steam Error: " + err);
44
+ user.logOff();
45
+ process.exit(1);
46
+ });
47
+
48
+ user.on("loggedOn", (_details, _parental) => {
49
+ paddedLog("Logged into Steam.");
50
+ user.gamesPlayed([730]);
51
+ });
52
+
53
+ let cs2 = new CS2(user);
54
+
55
+ paddedLog("Connecting to CS2 Game Coordinator...");
56
+
57
+ cs2.on("error", (err) => {
58
+ console.error("CS2 Error: " + err);
59
+ user.logOff();
60
+ process.exit(1);
61
+ });
62
+
63
+ let nameConverter = new ItemNameConverter();
64
+ await nameConverter.initialize();
65
+
66
+ cs2.on("connectedToGC", async () => {
67
+ paddedLog("Connected to CS2 Game Coordinator.");
68
+ await processInventory();
69
+ });
70
+
71
+ async function processInventory() {
72
+ let finalItemCounts = {};
73
+ try {
74
+ const storageUnitIds = getStorageUnitIds();
75
+ for (const [unitIndex, unitId] of storageUnitIds.entries()) {
76
+ const items = await getCasketContentsAsync(cs2, unitId);
77
+ const convertedItems = nameConverter.convertInventory(items, false);
78
+ const filteredItems = filterItems(convertedItems);
79
+ const itemCounts = countItems(filteredItems);
80
+ for (const [itemName, count] of Object.entries(itemCounts)) {
81
+ finalItemCounts[itemName] = (finalItemCounts[itemName] || 0) + count;
82
+ }
83
+ paddedLog(
84
+ `${filteredItems.length} items found in storage unit: ${unitIndex}/${storageUnitIds.length}`,
85
+ );
86
+ console.log(itemCounts);
87
+ }
88
+ paddedLog("Saving config...");
89
+ fs.writeFileSync(
90
+ processedInventoryPath,
91
+ JSON.stringify(finalItemCounts, null, 2),
92
+ );
93
+ paddedLog("Processing complete.");
94
+ paddedLog("You may close this window now.");
95
+ } catch (err) {
96
+ console.error("An error occurred during processing:", err);
97
+ } finally {
98
+ user.logOff();
99
+ process.exit(0);
100
+ }
101
+ }
102
+
103
+ function getStorageUnitIds() {
104
+ let storageUnitIds = [];
105
+ for (let item of cs2.inventory) {
106
+ if (item.casket_contained_item_count > 0) {
107
+ storageUnitIds.push(item.id);
108
+ }
109
+ }
110
+ return storageUnitIds;
111
+ }
112
+
113
+ function getCasketContentsAsync(cs2, unitId) {
114
+ return new Promise((resolve, reject) => {
115
+ cs2.getCasketContents(unitId, (err, items) => {
116
+ if (err) return reject(err);
117
+ resolve(items);
118
+ });
119
+ });
120
+ }
121
+
122
+ function filterItems(items) {
123
+ let filteredItems = [];
124
+ items.forEach((item) => {
125
+ if (
126
+ (item.item_type === "case" && importCases) ||
127
+ (item.item_type === "sticker capsule" && importStickerCapsules) ||
128
+ (item.item_type === "sticker" && importStickers) ||
129
+ (item.item_type === "other" && importOthers)
130
+ ) {
131
+ filteredItems.push(item);
132
+ }
133
+ });
134
+ return filteredItems;
135
+ }
136
+
137
+ function countItems(items) {
138
+ let itemCounts = {};
139
+ items.forEach((item) => {
140
+ if (itemCounts[item.item_name]) {
141
+ itemCounts[item.item_name]++;
142
+ } else {
143
+ itemCounts[item.item_name] = 1;
144
+ }
145
+ });
146
+ return itemCounts;
147
+ }
148
+ })();
cs2tracker/main.py CHANGED
@@ -1,11 +1,19 @@
1
1
  import sys
2
+ from subprocess import DEVNULL
2
3
 
3
4
  import urllib3
5
+ from nodejs import npm
4
6
 
5
7
  from cs2tracker.app import Application
6
- from cs2tracker.constants import AUTHOR_STRING, BANNER, OS, OSType
8
+ from cs2tracker.constants import (
9
+ AUTHOR_STRING,
10
+ BANNER,
11
+ INVENTORY_IMPORT_SCRIPT_DEPENDENCIES,
12
+ OS,
13
+ OSType,
14
+ )
7
15
  from cs2tracker.scraper import Scraper
8
- from cs2tracker.util import PaddedConsole
16
+ from cs2tracker.util import get_console
9
17
 
10
18
 
11
19
  def main():
@@ -23,13 +31,20 @@ def main():
23
31
  if OS == OSType.WINDOWS and sys.stdout is not None:
24
32
  sys.stdout.reconfigure(encoding="utf-8-sig") # type: ignore
25
33
 
26
- console = PaddedConsole()
34
+ console = get_console()
27
35
  console.print(f"[bold yellow]{BANNER}\n{AUTHOR_STRING}\n")
28
36
 
29
37
  if "--only-scrape" in sys.argv:
30
38
  scraper = Scraper()
31
39
  scraper.scrape_prices()
32
40
  else:
41
+ # Ensures that the necessary node modules are installed if a user wants
42
+ # to import their steam inventory via the cs2tracker/data/get_inventory.js Node.js script.
43
+ # This can be done in a daemon thread on application startup because it is not a very costly operation.
44
+ npm.Popen(
45
+ ["install"] + INVENTORY_IMPORT_SCRIPT_DEPENDENCIES, stdout=DEVNULL, stderr=DEVNULL
46
+ )
47
+
33
48
  application = Application()
34
49
  application.run()
35
50
 
@@ -9,7 +9,7 @@ from cs2tracker.constants import (
9
9
  RUNNING_IN_EXE,
10
10
  OSType,
11
11
  )
12
- from cs2tracker.util import PaddedConsole
12
+ from cs2tracker.util import get_console
13
13
 
14
14
  WIN_BACKGROUND_TASK_NAME = "CS2Tracker Daily Calculation"
15
15
  WIN_BACKGROUND_TASK_SCHEDULE = "DAILY"
@@ -18,7 +18,7 @@ WIN_BACKGROUND_TASK_CMD = (
18
18
  f"powershell -WindowStyle Hidden -Command \"Start-Process '{BATCH_FILE}' -WindowStyle Hidden\""
19
19
  )
20
20
 
21
- console = PaddedConsole()
21
+ console = get_console()
22
22
 
23
23
 
24
24
  class BackgroundTask:
@@ -85,14 +85,14 @@ class BackgroundTask:
85
85
  if return_code == 0:
86
86
  console.print("[bold green][+] Background task enabled.")
87
87
  else:
88
- console.print("[bold red][!] Failed to enable background task.")
88
+ console.error("Failed to enable background task.")
89
89
  else:
90
90
  cmd = ["schtasks", "/delete", "/tn", WIN_BACKGROUND_TASK_NAME, "/f"]
91
91
  return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
92
92
  if return_code == 0:
93
93
  console.print("[bold green][-] Background task disabled.")
94
94
  else:
95
- console.print("[bold red][!] Failed to disable background task.")
95
+ console.error("Failed to disable background task.")
96
96
 
97
97
  @classmethod
98
98
  def toggle(cls, enabled: bool):
@@ -1,13 +1,13 @@
1
1
  import requests
2
2
  from requests.exceptions import RequestException
3
3
 
4
- from cs2tracker.util import PaddedConsole, PriceLogs
4
+ from cs2tracker.util import PriceLogs, get_console
5
5
 
6
6
  DC_WEBHOOK_USERNAME = "CS2Tracker"
7
7
  DC_WEBHOOK_AVATAR_URL = "https://img.icons8.com/?size=100&id=uWQJp2tLXUH6&format=png&color=000000"
8
8
  DC_RECENT_HISTORY_LIMIT = 5
9
9
 
10
- console = PaddedConsole()
10
+ console = get_console()
11
11
 
12
12
 
13
13
  class DiscordNotifier:
@@ -81,6 +81,6 @@ class DiscordNotifier:
81
81
  response.raise_for_status()
82
82
  console.print("[bold steel_blue3][+] Discord notification sent.\n")
83
83
  except RequestException as error:
84
- console.print(f"[bold red][!] Failed to send Discord notification: {error}\n")
84
+ console.error(f"Failed to send Discord notification: {error}\n")
85
85
  except Exception as error:
86
- console.print(f"[bold red][!] An unexpected error occurred: {error}\n")
86
+ console.error(f"An unexpected error occurred: {error}\n")