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.

@@ -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.parsers import CSGOTrader, PriceSource
13
- from cs2tracker.util import PriceLogs, get_config, get_console
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: {"usd": 0.0, "eur": 0.0} for price_source in self.parser.SOURCES
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 _add_parser(self, parser):
71
- """Add a parser for a specific page where item prices should be scraped."""
72
- self.parser = parser
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 _print_error(self):
75
- """Print the last error message from the error stack, if any."""
76
- last_error = self.error_stack[-1] if self.error_stack else None
77
- if last_error:
78
- console.error(f"{last_error.message}")
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 EUR, 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.error_stack.append(ConfigError())
90
- self._print_error()
118
+ self._error(ConfigError())
91
119
  return
92
120
 
93
- # Reset totals from the previous run and clear the error stack
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
- for price_source, totals in self.totals.items():
105
- usd_total = totals["usd"]
106
- eur_total = CurrencyConverter().convert(usd_total, "USD", "EUR")
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
- if update_sheet_callback:
110
- update_sheet_callback(["", ""] + ["", ""] * len(self.parser.SOURCES))
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
- self._print_total()
124
- self._send_discord_notification()
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
- # TODO: modify price logs, charts etc for multiple sources (only use steam as source for now)
127
- steam_usd_total = self.totals[PriceSource.STEAM]["usd"]
128
- steam_eur_total = self.totals[PriceSource.STEAM]["eur"]
129
- PriceLogs.save(steam_usd_total, steam_eur_total)
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 _print_total(self):
132
- """Print the total prices in USD and EUR, formatted with titles and
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.get("usd")
138
- console.print(f"{price_source.value.title():<10}: ${usd_total:.2f}")
157
+ usd_total = totals["USD"]
158
+ console.print(f"{price_source.name.title():<10}: ${usd_total:.2f}")
139
159
 
140
- console.title("EUR Total", "green")
160
+ console.title(f"{self.conversion_currency} Total", "green")
141
161
  for price_source, totals in self.totals.items():
142
- eur_total = totals.get("eur")
143
- console.print(f"{price_source.value.title():<10}: €{eur_total:.2f}")
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
- console.separator("green")
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
- discord_notifications = config.getboolean(
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 webhook_url:
157
- DiscordNotifier.notify(webhook_url)
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
- use_proxy = config.getboolean("App Settings", "use_proxy", fallback=False)
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.error_stack.append(PageLoadError(page.status_code))
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 self.parser.SOURCES:
236
+ for price_source in Parser.SOURCES:
206
237
  try:
207
- item_page_url = self.parser.get_item_page_url(item_href, price_source)
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 = self.parser.parse_item_price(item_page, item_href, price_source)
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]["usd"] += price_usd_owned
243
+ self.totals[price_source]["USD"] += price_usd_owned
213
244
 
214
245
  prices += [price_usd, price_usd_owned]
215
246
  console.price(
216
- self.parser.PRICE_INFO,
247
+ Parser.PRICE_INFO,
217
248
  owned,
218
- price_source.value.title(),
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.error_stack.append(ParsingError(error))
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(self.error_stack[-1], RequestLimitExceededError):
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
- update_sheet_callback([item_name, owned] + prices)
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.error_stack.append(RequestLimitExceededError())
261
- self._print_error()
292
+ self._error(RequestLimitExceededError())
262
293
  except Exception as error:
263
- self.error_stack.append(UnexpectedError(error))
264
- self._print_error()
294
+ self._error(UnexpectedError(error))
265
295
 
266
296
 
267
297
  if __name__ == "__main__":
@@ -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.14
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
  [![PyPI version](https://badge.fury.io/py/cs2tracker.svg)](https://badge.fury.io/py/cs2tracker)
42
42
  [![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/ashiven/cs2tracker)](https://github.com/ashiven/cs2tracker/issues)
43
43
  [![GitHub Issues or Pull Requests](https://img.shields.io/github/issues-pr/ashiven/cs2tracker)](https://github.com/ashiven/cs2tracker/pulls)
44
- ![GitHub Repo stars](https://img.shields.io/github/stars/ashiven/cs2tracker)
45
44
 
46
- <img src="https://github.com/user-attachments/assets/9585afb2-bf1a-473c-be5d-cccbb3349b9a"/>
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 CS2 Steam Market prices
63
+ - 🔍 Track prices on Steam, Buff163, CSFloat
65
64
  - 📈 View investment price history
66
- - 🧾 Export/Import price data
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
- #### Option 1: Windows Executable
74
+ #### Method 1: Executable
82
75
 
83
- - Simply [download the latest executable](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip) and run it.
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
- #### Option 2: Install via Pip
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 EUR.
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,,