cs2tracker 2.1.12__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.
- cs2tracker/_version.py +2 -2
- cs2tracker/app/application.py +12 -12
- cs2tracker/app/editor_frame.py +303 -76
- cs2tracker/app/scraper_frame.py +19 -2
- cs2tracker/constants.py +11 -133
- cs2tracker/data/config.ini +116 -116
- cs2tracker/data/convert_inventory.js +187 -0
- cs2tracker/data/get_inventory.js +148 -0
- cs2tracker/main.py +18 -3
- cs2tracker/scraper/background_task.py +4 -4
- cs2tracker/scraper/discord_notifier.py +4 -4
- cs2tracker/scraper/scraper.py +102 -70
- cs2tracker/util/__init__.py +2 -2
- cs2tracker/util/padded_console.py +13 -0
- cs2tracker/util/validated_config.py +55 -8
- cs2tracker-2.1.13.dist-info/METADATA +147 -0
- cs2tracker-2.1.13.dist-info/RECORD +27 -0
- cs2tracker-2.1.13.dist-info/licenses/LICENSE +402 -0
- cs2tracker-2.1.12.dist-info/METADATA +0 -82
- cs2tracker-2.1.12.dist-info/RECORD +0 -25
- cs2tracker-2.1.12.dist-info/licenses/LICENSE.md +0 -21
- {cs2tracker-2.1.12.dist-info → cs2tracker-2.1.13.dist-info}/WHEEL +0 -0
- {cs2tracker-2.1.12.dist-info → cs2tracker-2.1.13.dist-info}/entry_points.txt +0 -0
- {cs2tracker-2.1.12.dist-info → cs2tracker-2.1.13.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
|
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 =
|
|
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.
|
|
84
|
+
console.error(f"Failed to send Discord notification: {error}\n")
|
|
85
85
|
except Exception as error:
|
|
86
|
-
console.
|
|
86
|
+
console.error(f"An unexpected error occurred: {error}\n")
|