cs2tracker 2.1.14__py3-none-any.whl → 2.1.16__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/__init__.py +0 -3
- cs2tracker/app/{application.py → app.py} +77 -45
- cs2tracker/app/editor_frame.py +223 -108
- cs2tracker/app/history_frame.py +61 -0
- cs2tracker/app/scraper_frame.py +13 -7
- cs2tracker/{util/validated_config.py → config.py} +97 -52
- cs2tracker/constants.py +20 -3
- cs2tracker/data/config.ini +24 -21
- cs2tracker/data/convert_inventory.js +108 -28
- cs2tracker/data/get_inventory.js +34 -13
- cs2tracker/logs.py +143 -0
- cs2tracker/main.py +3 -3
- cs2tracker/scraper/__init__.py +0 -9
- cs2tracker/scraper/background_task.py +80 -6
- cs2tracker/scraper/discord_notifier.py +34 -32
- cs2tracker/scraper/{parsers.py → parser.py} +35 -32
- cs2tracker/scraper/scraper.py +113 -83
- cs2tracker/util/__init__.py +0 -9
- cs2tracker/util/currency_conversion.py +84 -0
- cs2tracker/util/padded_console.py +5 -0
- cs2tracker/util/tkinter.py +55 -0
- {cs2tracker-2.1.14.dist-info → cs2tracker-2.1.16.dist-info}/METADATA +15 -16
- cs2tracker-2.1.16.dist-info/RECORD +31 -0
- cs2tracker/util/price_logs.py +0 -100
- cs2tracker-2.1.14.dist-info/RECORD +0 -28
- {cs2tracker-2.1.14.dist-info → cs2tracker-2.1.16.dist-info}/WHEEL +0 -0
- {cs2tracker-2.1.14.dist-info → cs2tracker-2.1.16.dist-info}/entry_points.txt +0 -0
- {cs2tracker-2.1.14.dist-info → cs2tracker-2.1.16.dist-info}/licenses/LICENSE +0 -0
- {cs2tracker-2.1.14.dist-info → cs2tracker-2.1.16.dist-info}/top_level.txt +0 -0
cs2tracker/scraper/scraper.py
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
|
-
from currency_converter import CurrencyConverter
|
|
5
4
|
from requests import RequestException
|
|
6
5
|
from requests.adapters import HTTPAdapter, Retry
|
|
7
6
|
from requests_cache import CachedSession
|
|
8
7
|
from tenacity import RetryError, retry, stop_after_attempt
|
|
9
8
|
|
|
9
|
+
from cs2tracker.config import get_config
|
|
10
10
|
from cs2tracker.constants import AUTHOR_STRING, BANNER
|
|
11
|
+
from cs2tracker.logs import PriceLogs
|
|
11
12
|
from cs2tracker.scraper.discord_notifier import DiscordNotifier
|
|
12
|
-
from cs2tracker.scraper.
|
|
13
|
-
from cs2tracker.util import
|
|
13
|
+
from cs2tracker.scraper.parser import Parser
|
|
14
|
+
from cs2tracker.util.currency_conversion import convert, to_symbol
|
|
15
|
+
from cs2tracker.util.padded_console import get_console
|
|
14
16
|
|
|
15
17
|
HTTP_PROXY_URL = "http://{}:@smartproxy.crawlbase.com:8012"
|
|
16
18
|
HTTPS_PROXY_URL = "http://{}:@smartproxy.crawlbase.com:8012"
|
|
@@ -44,15 +46,28 @@ class UnexpectedError:
|
|
|
44
46
|
self.message = f"An unexpected error occurred: {error}"
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
class SheetNotFoundError:
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.message = "Could not find sheet to update."
|
|
52
|
+
|
|
53
|
+
|
|
47
54
|
class Scraper:
|
|
48
55
|
def __init__(self):
|
|
49
56
|
"""Initialize the Scraper class."""
|
|
50
57
|
self._start_session()
|
|
51
|
-
self._add_parser(CSGOTrader)
|
|
52
|
-
|
|
53
58
|
self.error_stack = []
|
|
59
|
+
|
|
60
|
+
# We set the conversion currency as an attribute of the Scraper instance
|
|
61
|
+
# and only update it from the config at the start of the scraping process.
|
|
62
|
+
# This allows us to use the same conversion currency throughout the scraping
|
|
63
|
+
# process and prevents issues with changing the conversion currency while scraping.
|
|
64
|
+
self.conversion_currency = config.conversion_currency
|
|
54
65
|
self.totals = {
|
|
55
|
-
price_source: {
|
|
66
|
+
price_source: {
|
|
67
|
+
"USD": 0.0,
|
|
68
|
+
self.conversion_currency: 0.0,
|
|
69
|
+
}
|
|
70
|
+
for price_source in Parser.SOURCES
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
def _start_session(self):
|
|
@@ -67,94 +82,112 @@ class Scraper:
|
|
|
67
82
|
self.session.mount("http://", HTTPAdapter(max_retries=retries))
|
|
68
83
|
self.session.mount("https://", HTTPAdapter(max_retries=retries))
|
|
69
84
|
|
|
70
|
-
def
|
|
71
|
-
"""Add
|
|
72
|
-
|
|
85
|
+
def _error(self, error):
|
|
86
|
+
"""Add an error to the error stack and print the last error message from the
|
|
87
|
+
error stack.
|
|
88
|
+
"""
|
|
89
|
+
self.error_stack.append(error)
|
|
90
|
+
console.error(f"{error.message}")
|
|
73
91
|
|
|
74
|
-
def
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
def _prepare_new_run(self):
|
|
93
|
+
"""
|
|
94
|
+
Reset totals for the next run and get the most recent conversion currency from
|
|
95
|
+
the config.
|
|
96
|
+
|
|
97
|
+
This way, we don't have to create a new Scraper instance for each run.
|
|
98
|
+
"""
|
|
99
|
+
self.error_stack.clear()
|
|
100
|
+
self.conversion_currency = config.conversion_currency
|
|
101
|
+
self.totals = {
|
|
102
|
+
price_source: {
|
|
103
|
+
"USD": 0.0,
|
|
104
|
+
self.conversion_currency: 0.0,
|
|
105
|
+
}
|
|
106
|
+
for price_source in Parser.SOURCES
|
|
107
|
+
}
|
|
79
108
|
|
|
80
109
|
def scrape_prices(self, update_sheet_callback=None):
|
|
81
110
|
"""
|
|
82
|
-
Scrape prices for capsules and cases, calculate totals in USD and
|
|
83
|
-
print/save the results.
|
|
111
|
+
Scrape prices for capsules and cases, calculate totals in USD and conversion
|
|
112
|
+
currency, and print/save the results.
|
|
84
113
|
|
|
85
114
|
:param update_sheet_callback: Optional callback function to update a tksheet
|
|
86
115
|
that is displayed in the GUI with the latest scraper price calculation.
|
|
87
116
|
"""
|
|
88
117
|
if not config.valid:
|
|
89
|
-
self.
|
|
90
|
-
self._print_error()
|
|
118
|
+
self._error(ConfigError())
|
|
91
119
|
return
|
|
92
120
|
|
|
93
|
-
|
|
94
|
-
self.error_stack.clear()
|
|
95
|
-
self.totals = {
|
|
96
|
-
price_source: {"usd": 0.0, "eur": 0.0} for price_source in self.parser.SOURCES
|
|
97
|
-
}
|
|
121
|
+
self._prepare_new_run()
|
|
98
122
|
|
|
99
123
|
for section in config.sections():
|
|
100
124
|
if section in ("User Settings", "App Settings"):
|
|
101
125
|
continue
|
|
102
126
|
self._scrape_item_prices(section, update_sheet_callback)
|
|
103
127
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
self.totals.update({price_source: {"usd": usd_total, "eur": eur_total}}) # type: ignore
|
|
128
|
+
self._convert_totals()
|
|
129
|
+
self._print_totals(update_sheet_callback)
|
|
130
|
+
self._send_discord_notification()
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
for price_source, totals in self.totals.items():
|
|
112
|
-
usd_total = totals["usd"]
|
|
113
|
-
eur_total = totals["eur"]
|
|
114
|
-
update_sheet_callback(
|
|
115
|
-
[
|
|
116
|
-
f"[{datetime.now().strftime('%Y-%m-%d')}] {price_source.value.title()} Total:",
|
|
117
|
-
f"${usd_total:.2f}",
|
|
118
|
-
f"€{eur_total:.2f}",
|
|
119
|
-
"",
|
|
120
|
-
]
|
|
121
|
-
)
|
|
132
|
+
usd_totals = [self.totals[price_source]["USD"] for price_source in Parser.SOURCES]
|
|
133
|
+
PriceLogs.save(usd_totals)
|
|
122
134
|
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
def _convert_totals(self):
|
|
136
|
+
"""
|
|
137
|
+
Convert the total prices from USD to the configured conversion currency and
|
|
138
|
+
update the totals dictionary.
|
|
125
139
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
with the converted totals.
|
|
141
|
+
"""
|
|
142
|
+
for price_source, totals in self.totals.items():
|
|
143
|
+
usd_total = totals["USD"]
|
|
144
|
+
converted_total = convert(usd_total, "USD", self.conversion_currency)
|
|
145
|
+
self.totals.update({price_source: {"USD": usd_total, self.conversion_currency: converted_total}}) # type: ignore
|
|
130
146
|
|
|
131
|
-
def
|
|
132
|
-
"""
|
|
147
|
+
def _print_totals(self, update_sheet_callback=None):
|
|
148
|
+
"""
|
|
149
|
+
Print the total prices in USD and converted currency, formatted with titles and
|
|
133
150
|
separators.
|
|
151
|
+
|
|
152
|
+
:param update_sheet_callback: Optional callback function to update a tksheet
|
|
153
|
+
with the final totals.
|
|
134
154
|
"""
|
|
135
155
|
console.title("USD Total", "green")
|
|
136
156
|
for price_source, totals in self.totals.items():
|
|
137
|
-
usd_total = totals
|
|
138
|
-
console.print(f"{price_source.
|
|
157
|
+
usd_total = totals["USD"]
|
|
158
|
+
console.print(f"{price_source.name.title():<10}: ${usd_total:.2f}")
|
|
139
159
|
|
|
140
|
-
console.title("
|
|
160
|
+
console.title(f"{self.conversion_currency} Total", "green")
|
|
141
161
|
for price_source, totals in self.totals.items():
|
|
142
|
-
|
|
143
|
-
console.print(
|
|
162
|
+
converted_total = totals[self.conversion_currency]
|
|
163
|
+
console.print(
|
|
164
|
+
f"{price_source.name.title():<10}: {to_symbol(self.conversion_currency)}{converted_total:.2f}"
|
|
165
|
+
)
|
|
144
166
|
|
|
145
|
-
|
|
167
|
+
if update_sheet_callback and not (
|
|
168
|
+
self.error_stack and isinstance(self.error_stack[-1], SheetNotFoundError)
|
|
169
|
+
):
|
|
170
|
+
update_sheet_callback(["", ""] + ["", ""] * len(Parser.SOURCES))
|
|
171
|
+
for price_source, totals in self.totals.items():
|
|
172
|
+
usd_total = totals["USD"]
|
|
173
|
+
converted_total = totals[self.conversion_currency]
|
|
174
|
+
update_sheet_callback(
|
|
175
|
+
[
|
|
176
|
+
f"[{datetime.now().strftime('%Y-%m-%d')}] {price_source.name.title()} Total:",
|
|
177
|
+
f"${usd_total:.2f}",
|
|
178
|
+
f"{to_symbol(self.conversion_currency)}{converted_total:.2f}",
|
|
179
|
+
"",
|
|
180
|
+
]
|
|
181
|
+
)
|
|
146
182
|
|
|
147
183
|
def _send_discord_notification(self):
|
|
148
184
|
"""Send a message to a Discord webhook if notifications are enabled in the
|
|
149
185
|
config file and a webhook URL is provided.
|
|
150
186
|
"""
|
|
151
|
-
|
|
152
|
-
"App Settings", "discord_notifications", fallback=False
|
|
153
|
-
)
|
|
154
|
-
webhook_url = config.get("User Settings", "discord_webhook_url", fallback=None)
|
|
187
|
+
discord_webhook_url = config.discord_webhook_url
|
|
155
188
|
|
|
156
|
-
if discord_notifications and
|
|
157
|
-
DiscordNotifier.notify(
|
|
189
|
+
if config.discord_notifications and discord_webhook_url:
|
|
190
|
+
DiscordNotifier.notify(discord_webhook_url)
|
|
158
191
|
|
|
159
192
|
@retry(stop=stop_after_attempt(10))
|
|
160
193
|
def _get_page(self, url):
|
|
@@ -167,10 +200,9 @@ class Scraper:
|
|
|
167
200
|
:raises RequestException: If the request fails.
|
|
168
201
|
:raises RetryError: If the retry limit is reached.
|
|
169
202
|
"""
|
|
170
|
-
|
|
171
|
-
proxy_api_key = config.get("User Settings", "proxy_api_key", fallback=None)
|
|
203
|
+
proxy_api_key = config.proxy_api_key
|
|
172
204
|
|
|
173
|
-
if use_proxy and proxy_api_key:
|
|
205
|
+
if config.use_proxy and proxy_api_key:
|
|
174
206
|
page = self.session.get(
|
|
175
207
|
url=url,
|
|
176
208
|
proxies={
|
|
@@ -183,8 +215,7 @@ class Scraper:
|
|
|
183
215
|
page = self.session.get(url)
|
|
184
216
|
|
|
185
217
|
if not page.ok or not page.content:
|
|
186
|
-
self.
|
|
187
|
-
self._print_error()
|
|
218
|
+
self._error(PageLoadError(page.status_code))
|
|
188
219
|
raise RequestException(f"Failed to load page: {url}")
|
|
189
220
|
|
|
190
221
|
return page
|
|
@@ -202,27 +233,26 @@ class Scraper:
|
|
|
202
233
|
:raises ValueError: If the parser could not find the item
|
|
203
234
|
"""
|
|
204
235
|
prices = []
|
|
205
|
-
for price_source in
|
|
236
|
+
for price_source in Parser.SOURCES:
|
|
206
237
|
try:
|
|
207
|
-
item_page_url =
|
|
238
|
+
item_page_url = Parser.get_item_page_url(item_href, price_source)
|
|
208
239
|
item_page = self._get_page(item_page_url)
|
|
209
|
-
price_usd =
|
|
240
|
+
price_usd = Parser.parse_item_price(item_page, item_href, price_source)
|
|
210
241
|
|
|
211
242
|
price_usd_owned = round(float(int(owned) * price_usd), 2)
|
|
212
|
-
self.totals[price_source]["
|
|
243
|
+
self.totals[price_source]["USD"] += price_usd_owned
|
|
213
244
|
|
|
214
245
|
prices += [price_usd, price_usd_owned]
|
|
215
246
|
console.price(
|
|
216
|
-
|
|
247
|
+
Parser.PRICE_INFO,
|
|
217
248
|
owned,
|
|
218
|
-
price_source.
|
|
249
|
+
price_source.name.title(),
|
|
219
250
|
price_usd,
|
|
220
251
|
price_usd_owned,
|
|
221
252
|
)
|
|
222
253
|
except ValueError as error:
|
|
223
254
|
prices += [0.0, 0.0]
|
|
224
|
-
self.
|
|
225
|
-
self._print_error()
|
|
255
|
+
self._error(ParsingError(error))
|
|
226
256
|
|
|
227
257
|
return prices
|
|
228
258
|
|
|
@@ -238,7 +268,9 @@ class Scraper:
|
|
|
238
268
|
that is displayed in the GUI with the latest scraper price calculation.
|
|
239
269
|
"""
|
|
240
270
|
for item_href, owned in config.items(section):
|
|
241
|
-
if self.error_stack and isinstance(
|
|
271
|
+
if self.error_stack and isinstance(
|
|
272
|
+
self.error_stack[-1], (RequestLimitExceededError, SheetNotFoundError)
|
|
273
|
+
):
|
|
242
274
|
break
|
|
243
275
|
if int(owned) == 0:
|
|
244
276
|
continue
|
|
@@ -249,19 +281,17 @@ class Scraper:
|
|
|
249
281
|
prices = self._scrape_prices_from_all_sources(item_href, owned)
|
|
250
282
|
|
|
251
283
|
if update_sheet_callback:
|
|
252
|
-
|
|
284
|
+
try:
|
|
285
|
+
update_sheet_callback([item_name, owned] + prices)
|
|
286
|
+
except Exception:
|
|
287
|
+
self._error(SheetNotFoundError())
|
|
253
288
|
|
|
254
|
-
if
|
|
255
|
-
not config.getboolean("App Settings", "use_proxy", fallback=False)
|
|
256
|
-
and self.parser.NEEDS_TIMEOUT
|
|
257
|
-
):
|
|
289
|
+
if not config.use_proxy and Parser.NEEDS_TIMEOUT:
|
|
258
290
|
time.sleep(1)
|
|
259
291
|
except RetryError:
|
|
260
|
-
self.
|
|
261
|
-
self._print_error()
|
|
292
|
+
self._error(RequestLimitExceededError())
|
|
262
293
|
except Exception as error:
|
|
263
|
-
self.
|
|
264
|
-
self._print_error()
|
|
294
|
+
self._error(UnexpectedError(error))
|
|
265
295
|
|
|
266
296
|
|
|
267
297
|
if __name__ == "__main__":
|
cs2tracker/util/__init__.py
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
from cs2tracker.util.padded_console import ( # noqa: F401 # pylint:disable=unused-import
|
|
2
|
-
get_console,
|
|
3
|
-
)
|
|
4
|
-
from cs2tracker.util.price_logs import ( # noqa: F401 # pylint:disable=unused-import
|
|
5
|
-
PriceLogs,
|
|
6
|
-
)
|
|
7
|
-
from cs2tracker.util.validated_config import ( # noqa: F401 # pylint:disable=unused-import
|
|
8
|
-
get_config,
|
|
9
|
-
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from currency_converter import CurrencyConverter
|
|
2
|
+
|
|
3
|
+
from cs2tracker.config import get_config
|
|
4
|
+
|
|
5
|
+
config = get_config()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
converter = CurrencyConverter()
|
|
9
|
+
CURRENCY_SYMBOLS = {
|
|
10
|
+
"EUR": "€",
|
|
11
|
+
"KRW": "₩",
|
|
12
|
+
"ISK": "kr",
|
|
13
|
+
"HKD": "HK$",
|
|
14
|
+
"SKK": "Sk",
|
|
15
|
+
"SEK": "kr",
|
|
16
|
+
"NOK": "kr",
|
|
17
|
+
"HUF": "Ft",
|
|
18
|
+
"LTL": "Lt",
|
|
19
|
+
"ZAR": "R",
|
|
20
|
+
"PHP": "₱",
|
|
21
|
+
"GBP": "£",
|
|
22
|
+
"MXN": "$",
|
|
23
|
+
"CYP": "£",
|
|
24
|
+
"LVL": "Ls",
|
|
25
|
+
"DKK": "kr",
|
|
26
|
+
"NZD": "NZ$",
|
|
27
|
+
"TRY": "₺",
|
|
28
|
+
"USD": "$",
|
|
29
|
+
"RON": "lei",
|
|
30
|
+
"RUB": "₽",
|
|
31
|
+
"EEK": "kr",
|
|
32
|
+
"CHF": "CHF",
|
|
33
|
+
"MYR": "RM",
|
|
34
|
+
"ILS": "₪",
|
|
35
|
+
"PLN": "zł",
|
|
36
|
+
"BRL": "R$",
|
|
37
|
+
"BGN": "лв",
|
|
38
|
+
"THB": "฿",
|
|
39
|
+
"INR": "₹",
|
|
40
|
+
"ROL": "lei",
|
|
41
|
+
"AUD": "A$",
|
|
42
|
+
"CNY": "¥",
|
|
43
|
+
"HRK": "kn",
|
|
44
|
+
"MTL": "Lm",
|
|
45
|
+
"IDR": "Rp",
|
|
46
|
+
"JPY": "¥",
|
|
47
|
+
"CAD": "C$",
|
|
48
|
+
}
|
|
49
|
+
CURRENCY_SYMBOLS = {currency: symbol for currency, symbol in CURRENCY_SYMBOLS.items() if currency in converter.currencies} # type: ignore
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def convert(amount, source_currency, target_currency):
|
|
53
|
+
"""
|
|
54
|
+
Convert an amount from source currency to target currency.
|
|
55
|
+
|
|
56
|
+
:param amount: The amount to convert.
|
|
57
|
+
:param source_currency: The currency to convert from.
|
|
58
|
+
:param target_currency: The currency to convert to.
|
|
59
|
+
:return: The converted amount in the target currency.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
if target_currency == "EUR":
|
|
63
|
+
converted_amount = converter.convert(amount, source_currency, target_currency)
|
|
64
|
+
else:
|
|
65
|
+
# The currency converter always needs the target or origin currency to be EUR
|
|
66
|
+
# Therefore we need an intermediate conversion step if the target currency is not EUR
|
|
67
|
+
intermediate_amount = converter.convert(amount, source_currency, "EUR")
|
|
68
|
+
converted_amount = converter.convert(intermediate_amount, "EUR", target_currency)
|
|
69
|
+
|
|
70
|
+
return round(converted_amount, 2)
|
|
71
|
+
except Exception:
|
|
72
|
+
return 0.0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def to_symbol(currency):
|
|
76
|
+
"""
|
|
77
|
+
Convert a currency code to its symbol.
|
|
78
|
+
|
|
79
|
+
:param currency: The currency code to convert.
|
|
80
|
+
:return: The symbol of the currency.
|
|
81
|
+
"""
|
|
82
|
+
if currency in CURRENCY_SYMBOLS:
|
|
83
|
+
return CURRENCY_SYMBOLS[currency]
|
|
84
|
+
return currency
|
|
@@ -21,6 +21,11 @@ class PaddedConsole:
|
|
|
21
21
|
"""Print text with padding to the console."""
|
|
22
22
|
self.console.print(Padding(text, self.padding))
|
|
23
23
|
|
|
24
|
+
def info(self, text):
|
|
25
|
+
"""Print info text with padding to the console."""
|
|
26
|
+
text = "[bold green][+] " + text
|
|
27
|
+
self.print(text)
|
|
28
|
+
|
|
24
29
|
def error(self, text):
|
|
25
30
|
"""Print error text with padding to the console."""
|
|
26
31
|
text = "[bold red][!] " + text
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import sv_ttk
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def centered(window, geometry):
|
|
5
|
+
"""Convert a regular WidthxHeight geometry into one that is centered."""
|
|
6
|
+
w, h = geometry.split("x")
|
|
7
|
+
w, h = int(w), int(h)
|
|
8
|
+
|
|
9
|
+
ws = window.winfo_screenwidth()
|
|
10
|
+
hs = window.winfo_screenheight()
|
|
11
|
+
x = (ws / 2) - (w / 2)
|
|
12
|
+
y = (hs / 2) - (h / 2)
|
|
13
|
+
|
|
14
|
+
x, y = int(x), int(y)
|
|
15
|
+
|
|
16
|
+
return f"{w}x{h}+{x}+{y}"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def size_info(geometry):
|
|
20
|
+
"""Extract the window width and height from a geometry string."""
|
|
21
|
+
width, height = geometry.split("x")
|
|
22
|
+
width, height = int(width), int(height)
|
|
23
|
+
|
|
24
|
+
return width, height
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def fix_sv_ttk(style):
|
|
28
|
+
"""
|
|
29
|
+
Fixes the themed text entry widget in sv_ttk.
|
|
30
|
+
|
|
31
|
+
Source: https://github.com/Jesse205/TtkText?tab=readme-ov-file
|
|
32
|
+
"""
|
|
33
|
+
if sv_ttk.get_theme() == "light":
|
|
34
|
+
style.configure("ThemedText.TEntry", fieldbackground="#fdfdfd", textpadding=5)
|
|
35
|
+
style.map(
|
|
36
|
+
"ThemedText.TEntry",
|
|
37
|
+
fieldbackground=[
|
|
38
|
+
("hover", "!focus", "#f9f9f9"),
|
|
39
|
+
],
|
|
40
|
+
foreground=[
|
|
41
|
+
("pressed", style.lookup("TEntry", "foreground")),
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
else:
|
|
45
|
+
style.configure("ThemedText.TEntry", fieldbackground="#292929", textpadding=5)
|
|
46
|
+
style.map(
|
|
47
|
+
"ThemedText.TEntry",
|
|
48
|
+
fieldbackground=[
|
|
49
|
+
("hover", "!focus", "#2f2f2f"),
|
|
50
|
+
("focus", "#1c1c1c"),
|
|
51
|
+
],
|
|
52
|
+
foreground=[
|
|
53
|
+
("pressed", style.lookup("TEntry", "foreground")),
|
|
54
|
+
],
|
|
55
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cs2tracker
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.16
|
|
4
4
|
Summary: Tracking the steam market prices of CS2 items
|
|
5
5
|
Home-page: https://github.com/ashiven/cs2tracker
|
|
6
6
|
Author: Jannik Novak
|
|
@@ -41,17 +41,16 @@ Dynamic: license-file
|
|
|
41
41
|
[](https://badge.fury.io/py/cs2tracker)
|
|
42
42
|
[](https://github.com/ashiven/cs2tracker/issues)
|
|
43
43
|
[](https://github.com/ashiven/cs2tracker/pulls)
|
|
44
|
-

|
|
45
44
|
|
|
46
|
-
<img src="
|
|
45
|
+
<img src="./assets/demo.gif"/>
|
|
47
46
|
</div>
|
|
48
47
|
|
|
49
48
|
## Table of Contents
|
|
50
49
|
|
|
51
50
|
- [Features](#features)
|
|
52
51
|
- [Getting Started](#getting-started)
|
|
53
|
-
- [Prerequisites](#prerequisites)
|
|
54
52
|
- [Installation](#installation)
|
|
53
|
+
- [Additional Setup](#additional-setup)
|
|
55
54
|
- [Usage](#usage)
|
|
56
55
|
- [Configuration](#configuration)
|
|
57
56
|
- [Advanced Features](#advanced-features)
|
|
@@ -61,28 +60,24 @@ Dynamic: license-file
|
|
|
61
60
|
## Features
|
|
62
61
|
|
|
63
62
|
- ⚡ Rapidly import your Storage Units
|
|
64
|
-
- 🔍 Track
|
|
63
|
+
- 🔍 Track prices on Steam, Buff163, CSFloat
|
|
65
64
|
- 📈 View investment price history
|
|
66
|
-
- 🧾 Export/Import
|
|
65
|
+
- 🧾 Export/Import history data
|
|
67
66
|
- 📤 Discord notifications on updates
|
|
68
67
|
- 📅 Daily background calculations
|
|
69
68
|
- 🛡️ Proxy support to avoid rate limits
|
|
70
69
|
|
|
71
70
|
## Getting Started
|
|
72
71
|
|
|
73
|
-
### Prerequisites
|
|
74
|
-
|
|
75
|
-
- Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required on Linux)
|
|
76
|
-
- Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
|
|
77
|
-
- Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
|
|
78
|
-
|
|
79
72
|
### Installation
|
|
80
73
|
|
|
81
|
-
####
|
|
74
|
+
#### Method 1: Executable
|
|
82
75
|
|
|
83
|
-
|
|
76
|
+
Simply download the program and run it:
|
|
77
|
+
- [Windows](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip)
|
|
78
|
+
- [Linux](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-linux.zip)
|
|
84
79
|
|
|
85
|
-
####
|
|
80
|
+
#### Method 2: Install via Pip
|
|
86
81
|
|
|
87
82
|
1. Install the program:
|
|
88
83
|
|
|
@@ -95,10 +90,14 @@ Dynamic: license-file
|
|
|
95
90
|
```bash
|
|
96
91
|
cs2tracker
|
|
97
92
|
```
|
|
93
|
+
### Additional Setup
|
|
94
|
+
|
|
95
|
+
- Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
|
|
96
|
+
- Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
|
|
98
97
|
|
|
99
98
|
## Usage
|
|
100
99
|
|
|
101
|
-
- Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and
|
|
100
|
+
- Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and your selected currency.
|
|
102
101
|
- The generated Excel sheet can be saved by right-clicking and then selecting **Save Sheet**.
|
|
103
102
|
- Use **Edit Config** to specify the numbers of items owned in the configuration.
|
|
104
103
|
- Click **Show History** to see a price chart consisting of past calculations.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
cs2tracker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
cs2tracker/__main__.py,sha256=Ub--oSMv48YzfWF1CZqYlkn1-HvZ7Bhxoc7urn1oY6o,249
|
|
3
|
+
cs2tracker/_version.py,sha256=rudGT5E5yeJvX5boBvUebroVntBjuBLjwwGBYa7WCrQ,513
|
|
4
|
+
cs2tracker/config.py,sha256=iab4WqKLl8-rfV7_MAd-96lfp_edMe6QQHW_m76FvD8,11011
|
|
5
|
+
cs2tracker/constants.py,sha256=QqjApGtPd5SEP3ONMp4WdDOSr02sKbnSpPFFUkXFxKk,7228
|
|
6
|
+
cs2tracker/logs.py,sha256=K6uLoGjhHTlb6HLKIj6C5mz6R6bnZgltA6xwNVJ7Z8Y,6004
|
|
7
|
+
cs2tracker/main.py,sha256=9Jjn8-Hv9AzQLYjCjA6pwAmThE4HH1u3akF_c7atyl4,1046
|
|
8
|
+
cs2tracker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
cs2tracker/app/app.py,sha256=kUiZdrkIqwbbbf4h2MTQ04hHvO6kfGihj5M1yKF0VSE,10577
|
|
10
|
+
cs2tracker/app/editor_frame.py,sha256=eulOzywwMXoHhu3AQ6pEypY32zK6kpoQZDswRERioMU,26089
|
|
11
|
+
cs2tracker/app/history_frame.py,sha256=_QJdomghK10k0q0q4hyNm_RtzrbWa5jw3Rbq72smGT0,2001
|
|
12
|
+
cs2tracker/app/scraper_frame.py,sha256=GDRytrNe1gUMXA_ZcbTEcOVx2N81pQJGGYI-Fv-ww6o,4233
|
|
13
|
+
cs2tracker/data/config.ini,sha256=qTmBG8Qo6BYEBA9qEb_D8c_TW8Lf_0MuusmGjdP6S1Q,15093
|
|
14
|
+
cs2tracker/data/convert_inventory.js,sha256=CbMXnw52cOxhN2Ee2-kP3qX6pgwxrv4pi6_I1Acci88,7772
|
|
15
|
+
cs2tracker/data/get_inventory.js,sha256=OMntZhDE_x4qsqCWqcU7SqDFuMWz_VK_umxt_4i2znQ,6304
|
|
16
|
+
cs2tracker/data/output.csv,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
cs2tracker/scraper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
cs2tracker/scraper/background_task.py,sha256=4m1mr9veS3PUErTJhJVQNuBvmK1B8G9rPyVF_d8Cb9Q,6443
|
|
19
|
+
cs2tracker/scraper/discord_notifier.py,sha256=1DaGQVHfeE9AsQ09uKvVyDEbIdQwhVEh7MonJK0n0So,3074
|
|
20
|
+
cs2tracker/scraper/parser.py,sha256=s1kY8cogKExWmG5X0OKuvTbqZebFrEjgUbQJ-1We_iY,7025
|
|
21
|
+
cs2tracker/scraper/scraper.py,sha256=D_NlJTZyIXDhEkHGqjyrc5LYakcfiaT7Dl7dWLSC02k,11377
|
|
22
|
+
cs2tracker/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
cs2tracker/util/currency_conversion.py,sha256=h_VdipCFPzcgybWZ2VYknOd1VjHiqsFbEVed95FOe7U,2199
|
|
24
|
+
cs2tracker/util/padded_console.py,sha256=ZEbU5MxDA7Xbd-_SXIyezwGvb349OgAtjNPjFT_wrZw,1774
|
|
25
|
+
cs2tracker/util/tkinter.py,sha256=yR6rog4P7t_rDgH6ctssfQeJYFbqFXboiRv1V9c1vk4,1518
|
|
26
|
+
cs2tracker-2.1.16.dist-info/licenses/LICENSE,sha256=doPNswWMPXbkhplb9cnZLwJoqqS72pJPhkSib8kIF08,19122
|
|
27
|
+
cs2tracker-2.1.16.dist-info/METADATA,sha256=6lI3JbZeL8_DH9Yxl1iNmAvMmsP4Jbc-oEY1Dnc_d2M,5619
|
|
28
|
+
cs2tracker-2.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
cs2tracker-2.1.16.dist-info/entry_points.txt,sha256=K8IwDIkg8QztSB9g9c89B9jR_2pG4QyJGrNs4z5RcZw,63
|
|
30
|
+
cs2tracker-2.1.16.dist-info/top_level.txt,sha256=2HB4xDDOxaU5BDc_yvdi9UlYLgL768n8aR-hRhFM6VQ,11
|
|
31
|
+
cs2tracker-2.1.16.dist-info/RECORD,,
|