cs2tracker 2.1.3__tar.gz → 2.1.5__tar.gz
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-2.1.3 → cs2tracker-2.1.5}/.gitignore +2 -1
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/.pylintrc +0 -1
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/PKG-INFO +14 -8
- cs2tracker-2.1.5/README.md +53 -0
- cs2tracker-2.1.5/assets/icons8-counter-strike-bubbles-96.png +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker/_version.py +2 -2
- cs2tracker-2.1.5/cs2tracker/application.py +199 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker/constants.py +63 -8
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker/data/config.ini +7 -7
- cs2tracker-2.1.5/cs2tracker/data/output.csv +0 -0
- cs2tracker-2.1.5/cs2tracker/main.py +34 -0
- cs2tracker-2.1.5/cs2tracker/padded_console.py +22 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker/scraper.py +63 -39
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker.egg-info/PKG-INFO +14 -8
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker.egg-info/SOURCES.txt +4 -1
- cs2tracker-2.1.3/README.md +0 -47
- cs2tracker-2.1.3/cs2tracker/application.py +0 -109
- cs2tracker-2.1.3/cs2tracker/main.py +0 -43
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/.flake8 +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/.isort.cfg +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/.pre-commit-config.yaml +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/LICENSE.md +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/MANIFEST.in +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker/__init__.py +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker/__main__.py +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker.egg-info/dependency_links.txt +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker.egg-info/entry_points.txt +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker.egg-info/requires.txt +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/cs2tracker.egg-info/top_level.txt +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/pyproject.toml +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/requirements.txt +0 -0
- {cs2tracker-2.1.3 → cs2tracker-2.1.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cs2tracker
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.5
|
|
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
|
|
@@ -40,11 +40,17 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
### Prerequisites
|
|
42
42
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
43
|
+
- Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required for Linux)
|
|
44
|
+
- Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
|
|
45
45
|
|
|
46
46
|
### Setup
|
|
47
47
|
|
|
48
|
+
#### Windows Executable (No color support)
|
|
49
|
+
|
|
50
|
+
- Simply download the [latest executable](https://github.com/ashiven/cs2tracker/releases) and run it.
|
|
51
|
+
|
|
52
|
+
#### Install via Pip
|
|
53
|
+
|
|
48
54
|
1. Install the program via pip:
|
|
49
55
|
|
|
50
56
|
```bash
|
|
@@ -58,11 +64,11 @@ Dynamic: license-file
|
|
|
58
64
|
|
|
59
65
|
### Options
|
|
60
66
|
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
67
|
+
- `Run!` to gather the current market prices of your items and calculate the total amount in USD and EUR.
|
|
68
|
+
- `Edit Config` to change the specific numbers of each item you own and then save the config file.
|
|
69
|
+
- `Show History` to see a price chart consisting of past calculations. A new data point is generated once a day upon running the program.
|
|
70
|
+
- `Daily Background Calculation` to automatically run a daily calculation of your investment in the background and save the results such that they can later be viewed via `Show History`.
|
|
71
|
+
- If you want to prevent your requests from being rate limited by the steamcommunity server, register for an API key on [Crawlbase](crawlbase.com) and enter it into the `API_Key` field in the config file. This will route every request through a different proxy server.
|
|
66
72
|
|
|
67
73
|
---
|
|
68
74
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://github.com/ashiven/cs2tracker/releases)
|
|
5
|
+
[](https://badge.fury.io/py/cs2tracker)
|
|
6
|
+
[](https://github.com/ashiven/cs2tracker/issues)
|
|
7
|
+
[](https://github.com/ashiven/cs2tracker/pulls)
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
## About
|
|
13
|
+
|
|
14
|
+
**CS2Tracker** is a tool that can be used to keep track of the steam market prices of your CS2 investment.
|
|
15
|
+
|
|
16
|
+
## Getting Started
|
|
17
|
+
|
|
18
|
+
### Prerequisites
|
|
19
|
+
|
|
20
|
+
- Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required for Linux)
|
|
21
|
+
- Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
|
|
22
|
+
|
|
23
|
+
### Setup
|
|
24
|
+
|
|
25
|
+
#### Windows Executable (No color support)
|
|
26
|
+
|
|
27
|
+
- Simply download the [latest executable](https://github.com/ashiven/cs2tracker/releases) and run it.
|
|
28
|
+
|
|
29
|
+
#### Install via Pip
|
|
30
|
+
|
|
31
|
+
1. Install the program via pip:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install --user cs2tracker
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
2. Start the program:
|
|
38
|
+
```bash
|
|
39
|
+
cs2tracker
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Options
|
|
43
|
+
|
|
44
|
+
- `Run!` to gather the current market prices of your items and calculate the total amount in USD and EUR.
|
|
45
|
+
- `Edit Config` to change the specific numbers of each item you own and then save the config file.
|
|
46
|
+
- `Show History` to see a price chart consisting of past calculations. A new data point is generated once a day upon running the program.
|
|
47
|
+
- `Daily Background Calculation` to automatically run a daily calculation of your investment in the background and save the results such that they can later be viewed via `Show History`.
|
|
48
|
+
- If you want to prevent your requests from being rate limited by the steamcommunity server, register for an API key on [Crawlbase](crawlbase.com) and enter it into the `API_Key` field in the config file. This will route every request through a different proxy server.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
> GitHub [@ashiven](https://github.com/Ashiven) ·
|
|
53
|
+
> Twitter [ashiven\_](https://twitter.com/ashiven_)
|
|
Binary file
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from subprocess import Popen
|
|
3
|
+
from threading import Thread
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
from matplotlib.axes import Axes
|
|
8
|
+
from matplotlib.dates import DateFormatter
|
|
9
|
+
|
|
10
|
+
from cs2tracker.constants import (
|
|
11
|
+
CONFIG_FILE,
|
|
12
|
+
OS,
|
|
13
|
+
OUTPUT_FILE,
|
|
14
|
+
PYTHON_EXECUTABLE,
|
|
15
|
+
RUNNING_IN_EXE,
|
|
16
|
+
TEXT_EDITOR,
|
|
17
|
+
OSType,
|
|
18
|
+
)
|
|
19
|
+
from cs2tracker.scraper import Scraper
|
|
20
|
+
|
|
21
|
+
WINDOW_TITLE = "CS2Tracker"
|
|
22
|
+
WINDOW_SIZE = "450x380"
|
|
23
|
+
BACKGROUND_COLOR = "#1e1e1e"
|
|
24
|
+
BUTTON_COLOR = "#3c3f41"
|
|
25
|
+
BUTTON_HOVER_COLOR = "#505354"
|
|
26
|
+
BUTTON_ACTIVE_COLOR = "#5c5f61"
|
|
27
|
+
FONT_STYLE = "Segoe UI"
|
|
28
|
+
FONT_COLOR = "white"
|
|
29
|
+
|
|
30
|
+
SCRAPER_WINDOW_TITLE = "CS2Tracker"
|
|
31
|
+
SCRAPER_WINDOW_HEIGHT = 40
|
|
32
|
+
SCRAPER_WINDOW_WIDTH = 80
|
|
33
|
+
SCRAPER_WINDOW_BACKGROUND_COLOR = "Black"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Application:
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.scraper = Scraper()
|
|
39
|
+
|
|
40
|
+
def run(self):
|
|
41
|
+
"""Run the main application window with buttons for scraping prices, editing the
|
|
42
|
+
configuration, showing history in a chart, and editing the log file.
|
|
43
|
+
"""
|
|
44
|
+
application_window = self._configure_window()
|
|
45
|
+
application_window.mainloop()
|
|
46
|
+
|
|
47
|
+
def _add_button(self, frame, text, command):
|
|
48
|
+
"""Create and style a button for the main application window."""
|
|
49
|
+
button_style = {
|
|
50
|
+
"font": (FONT_STYLE, 12),
|
|
51
|
+
"fg": FONT_COLOR,
|
|
52
|
+
"bg": BUTTON_COLOR,
|
|
53
|
+
"activebackground": BUTTON_ACTIVE_COLOR,
|
|
54
|
+
}
|
|
55
|
+
button = tk.Button(frame, text=text, command=command, **button_style)
|
|
56
|
+
button.pack(pady=5, fill="x")
|
|
57
|
+
button.bind("<Enter>", lambda _: button.config(bg=BUTTON_HOVER_COLOR))
|
|
58
|
+
button.bind("<Leave>", lambda _: button.config(bg=BUTTON_COLOR))
|
|
59
|
+
return button
|
|
60
|
+
|
|
61
|
+
def _configure_window(self):
|
|
62
|
+
"""Configure the main application window UI and add buttons for the main
|
|
63
|
+
functionalities.
|
|
64
|
+
"""
|
|
65
|
+
window = tk.Tk()
|
|
66
|
+
window.title(WINDOW_TITLE)
|
|
67
|
+
window.geometry(WINDOW_SIZE)
|
|
68
|
+
window.configure(bg=BACKGROUND_COLOR)
|
|
69
|
+
|
|
70
|
+
frame = tk.Frame(window, bg=BACKGROUND_COLOR, padx=30, pady=30)
|
|
71
|
+
frame.pack(expand=True, fill="both")
|
|
72
|
+
|
|
73
|
+
label = tk.Label(
|
|
74
|
+
frame,
|
|
75
|
+
text=f"Welcome to {WINDOW_TITLE}!",
|
|
76
|
+
font=(FONT_STYLE, 16, "bold"),
|
|
77
|
+
fg=FONT_COLOR,
|
|
78
|
+
bg=BACKGROUND_COLOR,
|
|
79
|
+
)
|
|
80
|
+
label.pack(pady=(0, 30))
|
|
81
|
+
|
|
82
|
+
self._add_button(frame, "Run!", self.scrape_prices)
|
|
83
|
+
self._add_button(frame, "Edit Config", self._edit_config)
|
|
84
|
+
self._add_button(frame, "Show History (Chart)", self._draw_plot)
|
|
85
|
+
self._add_button(frame, "Show History (File)", self._edit_log_file)
|
|
86
|
+
|
|
87
|
+
background_checkbox_value = tk.BooleanVar(value=self.scraper.identify_background_task())
|
|
88
|
+
background_checkbox = tk.Checkbutton(
|
|
89
|
+
frame,
|
|
90
|
+
text="Daily Background Calculation",
|
|
91
|
+
variable=background_checkbox_value,
|
|
92
|
+
command=lambda: self._toggle_background_task(background_checkbox_value.get()),
|
|
93
|
+
bg=BACKGROUND_COLOR,
|
|
94
|
+
fg=FONT_COLOR,
|
|
95
|
+
selectcolor=BUTTON_COLOR,
|
|
96
|
+
activebackground=BACKGROUND_COLOR,
|
|
97
|
+
font=(FONT_STYLE, 10),
|
|
98
|
+
)
|
|
99
|
+
background_checkbox.pack(pady=20)
|
|
100
|
+
|
|
101
|
+
return window
|
|
102
|
+
|
|
103
|
+
def _construct_scraper_command(self):
|
|
104
|
+
"""Construct the command to run the scraper in a new window."""
|
|
105
|
+
if OS == OSType.WINDOWS:
|
|
106
|
+
set_utf8_encoding = "[Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8;"
|
|
107
|
+
get_size = "$size = $Host.UI.RawUI.WindowSize;"
|
|
108
|
+
set_size = "$Host.UI.RawUI.WindowSize = $size;"
|
|
109
|
+
set_window_title = f"$Host.UI.RawUI.WindowTitle = '{SCRAPER_WINDOW_TITLE}';"
|
|
110
|
+
set_window_width = f"$size.Width = {SCRAPER_WINDOW_WIDTH};"
|
|
111
|
+
set_window_height = f"$size.Height = {SCRAPER_WINDOW_HEIGHT};"
|
|
112
|
+
set_background_color = (
|
|
113
|
+
f"$Host.UI.RawUI.BackgroundColor = '{SCRAPER_WINDOW_BACKGROUND_COLOR}';"
|
|
114
|
+
)
|
|
115
|
+
clear = "Clear-Host;"
|
|
116
|
+
|
|
117
|
+
if RUNNING_IN_EXE:
|
|
118
|
+
# The python executable is set as the executable itself in PyInstaller
|
|
119
|
+
scraper_cmd = f"{PYTHON_EXECUTABLE} --only-scrape | Out-Host -Paging"
|
|
120
|
+
else:
|
|
121
|
+
scraper_cmd = f"{PYTHON_EXECUTABLE} -m cs2tracker --only-scrape"
|
|
122
|
+
|
|
123
|
+
cmd = (
|
|
124
|
+
'start powershell -NoExit -Command "& {'
|
|
125
|
+
+ set_utf8_encoding
|
|
126
|
+
+ set_window_title
|
|
127
|
+
+ get_size
|
|
128
|
+
+ set_window_width
|
|
129
|
+
+ set_window_height
|
|
130
|
+
+ set_size
|
|
131
|
+
+ set_background_color
|
|
132
|
+
+ clear
|
|
133
|
+
+ scraper_cmd
|
|
134
|
+
+ '}"'
|
|
135
|
+
)
|
|
136
|
+
return cmd
|
|
137
|
+
else:
|
|
138
|
+
# TODO: Implement for Linux
|
|
139
|
+
return ""
|
|
140
|
+
|
|
141
|
+
def scrape_prices(self):
|
|
142
|
+
"""Scrape prices from the configured sources, print the total, and save the
|
|
143
|
+
results to a file.
|
|
144
|
+
"""
|
|
145
|
+
if OS == OSType.WINDOWS:
|
|
146
|
+
scraper_cmd = self._construct_scraper_command()
|
|
147
|
+
Popen(scraper_cmd, shell=True)
|
|
148
|
+
else:
|
|
149
|
+
# TODO: implement external window for Linux
|
|
150
|
+
self.scraper.scrape_prices()
|
|
151
|
+
|
|
152
|
+
def _edit_config(self):
|
|
153
|
+
"""Edit the configuration file using the specified text editor."""
|
|
154
|
+
_popen_and_call(
|
|
155
|
+
popen_args={"args": [TEXT_EDITOR, CONFIG_FILE], "shell": True},
|
|
156
|
+
callback=self.scraper.parse_config,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def _draw_plot(self):
|
|
160
|
+
"""Draw a plot of the scraped prices over time."""
|
|
161
|
+
dates, dollars, euros = self.scraper.read_price_log()
|
|
162
|
+
|
|
163
|
+
fig, ax_raw = plt.subplots(figsize=(10, 8), num="CS2Tracker Price History")
|
|
164
|
+
fig.suptitle("CS2Tracker Price History", fontsize=16)
|
|
165
|
+
fig.autofmt_xdate()
|
|
166
|
+
|
|
167
|
+
ax = cast(Axes, ax_raw)
|
|
168
|
+
ax.plot(dates, dollars, label="Dollars")
|
|
169
|
+
ax.plot(dates, euros, label="Euros")
|
|
170
|
+
ax.legend()
|
|
171
|
+
date_formatter = DateFormatter("%d-%m-%Y")
|
|
172
|
+
ax.xaxis.set_major_formatter(date_formatter)
|
|
173
|
+
|
|
174
|
+
plt.show()
|
|
175
|
+
|
|
176
|
+
def _edit_log_file(self):
|
|
177
|
+
"""Opens the file containing past price calculations."""
|
|
178
|
+
Popen([TEXT_EDITOR, OUTPUT_FILE], shell=True)
|
|
179
|
+
|
|
180
|
+
def _toggle_background_task(self, enabled: bool):
|
|
181
|
+
"""Toggle whether a daily price calculation should run in the background."""
|
|
182
|
+
self.scraper.toggle_background_task(enabled)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _popen_and_call(popen_args, callback):
|
|
186
|
+
"""
|
|
187
|
+
Runs the given args in a subprocess.Popen, and then calls the function callback when
|
|
188
|
+
the subprocess completes.
|
|
189
|
+
|
|
190
|
+
Source: https://stackoverflow.com/questions/2581817/python-subprocess-callback-when-cmd-exits
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def process_and_callback(popen_args, callback):
|
|
194
|
+
process = Popen(**popen_args)
|
|
195
|
+
process.wait()
|
|
196
|
+
callback()
|
|
197
|
+
|
|
198
|
+
thread = Thread(target=process_and_callback, args=(popen_args, callback), daemon=True)
|
|
199
|
+
thread.start()
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
import enum
|
|
1
2
|
import os
|
|
2
3
|
import sys
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from shutil import copy
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
try:
|
|
8
|
+
from cs2tracker._version import version # pylint: disable=E0611
|
|
9
|
+
except ImportError:
|
|
10
|
+
version = "0.0.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OSType(enum.Enum):
|
|
14
|
+
WINDOWS = "Windows"
|
|
15
|
+
LINUX = "Linux"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
OS = OSType.WINDOWS if sys.platform.startswith("win") else OSType.LINUX
|
|
19
|
+
TEXT_EDITOR = "notepad" if OS == OSType.WINDOWS else "nano"
|
|
5
20
|
PYTHON_EXECUTABLE = sys.executable
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
7
24
|
PROJECT_DIR = os.path.dirname(MODULE_DIR)
|
|
8
25
|
OUTPUT_FILE = os.path.join(MODULE_DIR, "data", "output.csv")
|
|
@@ -10,6 +27,44 @@ CONFIG_FILE = os.path.join(MODULE_DIR, "data", "config.ini")
|
|
|
10
27
|
BATCH_FILE = os.path.join(MODULE_DIR, "data", "cs2tracker_scraper.bat")
|
|
11
28
|
|
|
12
29
|
|
|
30
|
+
RUNNING_IN_EXE = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
|
|
31
|
+
|
|
32
|
+
if RUNNING_IN_EXE:
|
|
33
|
+
MEIPASS_DIR = sys._MEIPASS # type: ignore pylint: disable=protected-access
|
|
34
|
+
MODULE_DIR = MEIPASS_DIR
|
|
35
|
+
PROJECT_DIR = MEIPASS_DIR
|
|
36
|
+
CONFIG_FILE_SOURCE = os.path.join(MODULE_DIR, "data", "config.ini")
|
|
37
|
+
OUTPUT_FILE_SOURCE = os.path.join(MODULE_DIR, "data", "output.csv")
|
|
38
|
+
|
|
39
|
+
APP_DATA_DIR = os.path.join(os.path.expanduser("~"), "AppData", "Local")
|
|
40
|
+
DATA_DIR = os.path.join(APP_DATA_DIR, "cs2tracker", "data")
|
|
41
|
+
os.makedirs(DATA_DIR, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
CONFIG_FILE = os.path.join(DATA_DIR, "config.ini")
|
|
44
|
+
OUTPUT_FILE = os.path.join(DATA_DIR, "output.csv")
|
|
45
|
+
BATCH_FILE = os.path.join(DATA_DIR, "cs2tracker_scraper.bat")
|
|
46
|
+
if not os.path.exists(CONFIG_FILE):
|
|
47
|
+
copy(CONFIG_FILE_SOURCE, CONFIG_FILE)
|
|
48
|
+
if not os.path.exists(OUTPUT_FILE):
|
|
49
|
+
copy(OUTPUT_FILE_SOURCE, OUTPUT_FILE)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
BANNER = """
|
|
53
|
+
__ _____ _____ ______ ____ ____ __ __ _ ___ ____
|
|
54
|
+
/ ] / ___/| T| T| \\ / T / ]| l/ ] / _]| \\
|
|
55
|
+
/ / ( \\_ l__/ || || D )Y o | / / | ' / / [_ | D )
|
|
56
|
+
/ / \\__ T| __jl_j l_j| / | | / / | \\ Y _]| /
|
|
57
|
+
/ \\_ / \\ || / | | | | \\ | _ |/ \\_ | Y| [_ | \\
|
|
58
|
+
\\ | \\ || | | | | . Y| | |\\ || . || T| . Y
|
|
59
|
+
\\____j \\___jl_____j l__j l__j\\_jl__j__j \\____jl__j\\_jl_____jl__j\\_j
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
AUTHOR_STRING = (
|
|
64
|
+
f"Version: v{version} - {datetime.today().strftime('%Y/%m/%d')} - Jannik Novak @ashiven\n"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
13
68
|
RMR_CAPSULES = {
|
|
14
69
|
"page": "https://steamcommunity.com/market/search?q=2020+rmr",
|
|
15
70
|
"items": [
|
|
@@ -172,13 +227,13 @@ AUSTIN_CAPSULES = {
|
|
|
172
227
|
|
|
173
228
|
CAPSULE_INFO = {
|
|
174
229
|
"2020 RMR Sticker Capsule": RMR_CAPSULES,
|
|
175
|
-
"Stockholm Sticker Capsule": STOCKHOLM_CAPSULES,
|
|
176
|
-
"Antwerp Sticker Capsule": ANTWERP_CAPSULES,
|
|
177
|
-
"Rio Sticker Capsule": RIO_CAPSULES,
|
|
178
|
-
"Paris Sticker Capsule": PARIS_CAPSULES,
|
|
179
|
-
"Copenhagen Sticker Capsule": COPENHAGEN_CAPSULES,
|
|
180
|
-
"Shanghai Sticker Capsule": SHANGHAI_CAPSULES,
|
|
181
|
-
"Austin Sticker Capsule": AUSTIN_CAPSULES,
|
|
230
|
+
"Stockholm 2021 Sticker Capsule": STOCKHOLM_CAPSULES,
|
|
231
|
+
"Antwerp 2022 Sticker Capsule": ANTWERP_CAPSULES,
|
|
232
|
+
"Rio 2022 Sticker Capsule": RIO_CAPSULES,
|
|
233
|
+
"Paris 2023 Sticker Capsule": PARIS_CAPSULES,
|
|
234
|
+
"Copenhagen 2024 Sticker Capsule": COPENHAGEN_CAPSULES,
|
|
235
|
+
"Shanghai 2024 Sticker Capsule": SHANGHAI_CAPSULES,
|
|
236
|
+
"Austin 2025 Sticker Capsule": AUSTIN_CAPSULES,
|
|
182
237
|
}
|
|
183
238
|
|
|
184
239
|
|
|
@@ -7,14 +7,14 @@ RMR_Challengers = 0
|
|
|
7
7
|
RMR_Legends = 0
|
|
8
8
|
RMR_Contenders = 0
|
|
9
9
|
|
|
10
|
-
[Stockholm Sticker Capsule]
|
|
10
|
+
[Stockholm 2021 Sticker Capsule]
|
|
11
11
|
Stockholm_Challengers = 0
|
|
12
12
|
Stockholm_Legends = 0
|
|
13
13
|
Stockholm_Contenders = 0
|
|
14
14
|
Stockholm_Champions_Autographs = 0
|
|
15
15
|
Stockholm_Finalists_Autographs = 0
|
|
16
16
|
|
|
17
|
-
[Antwerp Sticker Capsule]
|
|
17
|
+
[Antwerp 2022 Sticker Capsule]
|
|
18
18
|
Antwerp_Challengers = 0
|
|
19
19
|
Antwerp_Legends = 0
|
|
20
20
|
Antwerp_Contenders = 0
|
|
@@ -23,7 +23,7 @@ Antwerp_Contenders_Autographs = 0
|
|
|
23
23
|
Antwerp_Challengers_Autographs = 0
|
|
24
24
|
Antwerp_Legends_Autographs = 0
|
|
25
25
|
|
|
26
|
-
[Rio Sticker Capsule]
|
|
26
|
+
[Rio 2022 Sticker Capsule]
|
|
27
27
|
Rio_Challengers = 0
|
|
28
28
|
Rio_Legends = 0
|
|
29
29
|
Rio_Contenders = 0
|
|
@@ -32,7 +32,7 @@ Rio_Contenders_Autographs = 0
|
|
|
32
32
|
Rio_Challengers_Autographs = 0
|
|
33
33
|
Rio_Legends_Autographs = 0
|
|
34
34
|
|
|
35
|
-
[Paris Sticker Capsule]
|
|
35
|
+
[Paris 2023 Sticker Capsule]
|
|
36
36
|
Paris_Challengers = 0
|
|
37
37
|
Paris_Legends = 0
|
|
38
38
|
Paris_Contenders = 0
|
|
@@ -41,7 +41,7 @@ Paris_Contenders_Autographs = 0
|
|
|
41
41
|
Paris_Challengers_Autographs = 0
|
|
42
42
|
Paris_Legends_Autographs = 0
|
|
43
43
|
|
|
44
|
-
[Copenhagen Sticker Capsule]
|
|
44
|
+
[Copenhagen 2024 Sticker Capsule]
|
|
45
45
|
Copenhagen_Challengers = 0
|
|
46
46
|
Copenhagen_Legends = 0
|
|
47
47
|
Copenhagen_Contenders = 0
|
|
@@ -50,7 +50,7 @@ Copenhagen_Contenders_Autographs = 0
|
|
|
50
50
|
Copenhagen_Challengers_Autographs = 0
|
|
51
51
|
Copenhagen_Legends_Autographs = 0
|
|
52
52
|
|
|
53
|
-
[Shanghai Sticker Capsule]
|
|
53
|
+
[Shanghai 2024 Sticker Capsule]
|
|
54
54
|
Shanghai_Challengers = 0
|
|
55
55
|
Shanghai_Legends = 0
|
|
56
56
|
Shanghai_Contenders = 0
|
|
@@ -59,7 +59,7 @@ Shanghai_Contenders_Autographs = 0
|
|
|
59
59
|
Shanghai_Challengers_Autographs = 0
|
|
60
60
|
Shanghai_Legends_Autographs = 0
|
|
61
61
|
|
|
62
|
-
[Austin Sticker Capsule]
|
|
62
|
+
[Austin 2025 Sticker Capsule]
|
|
63
63
|
Austin_Challengers = 0
|
|
64
64
|
Austin_Legends = 0
|
|
65
65
|
Austin_Contenders = 0
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import urllib3
|
|
4
|
+
|
|
5
|
+
from cs2tracker.application import Application
|
|
6
|
+
from cs2tracker.constants import AUTHOR_STRING, BANNER
|
|
7
|
+
from cs2tracker.padded_console import PaddedConsole
|
|
8
|
+
from cs2tracker.scraper import Scraper
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
"""
|
|
13
|
+
The main entry point for the CS2Tracker application.
|
|
14
|
+
|
|
15
|
+
Provides a console output with the application version and date, and initializes the
|
|
16
|
+
application.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Disable warnings for proxy requests
|
|
20
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
21
|
+
|
|
22
|
+
if "--only-scrape" in sys.argv:
|
|
23
|
+
scraper = Scraper()
|
|
24
|
+
scraper.console.print(f"[bold yellow]{BANNER}\n{AUTHOR_STRING}\n")
|
|
25
|
+
scraper.scrape_prices()
|
|
26
|
+
else:
|
|
27
|
+
console = PaddedConsole()
|
|
28
|
+
console.print(f"[bold yellow]{BANNER}\n{AUTHOR_STRING}\n")
|
|
29
|
+
application = Application()
|
|
30
|
+
application.run()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
main()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.padding import Padding
|
|
3
|
+
|
|
4
|
+
PADDING_BOTTOM = 0
|
|
5
|
+
PADDING_TOP = 0
|
|
6
|
+
PADDING_LEFT = 4
|
|
7
|
+
PADDING_RIGHT = 0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PaddedConsole:
|
|
11
|
+
def __init__(self, padding=(PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT)):
|
|
12
|
+
"""Initialize a PaddedConsole with specified padding."""
|
|
13
|
+
self.console = Console()
|
|
14
|
+
self.padding = padding
|
|
15
|
+
|
|
16
|
+
def print(self, text):
|
|
17
|
+
"""Print text with padding to the console."""
|
|
18
|
+
self.console.print(Padding(text, self.padding))
|
|
19
|
+
|
|
20
|
+
def __getattr__(self, attr):
|
|
21
|
+
"""Ensure console methods can be called directly on PaddedConsole."""
|
|
22
|
+
return getattr(self.console, attr)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import csv
|
|
2
2
|
import os
|
|
3
|
-
import sys
|
|
4
3
|
import time
|
|
5
4
|
from configparser import ConfigParser
|
|
6
5
|
from datetime import datetime
|
|
@@ -11,31 +10,44 @@ from bs4.element import Tag
|
|
|
11
10
|
from currency_converter import CurrencyConverter
|
|
12
11
|
from requests import RequestException, Session
|
|
13
12
|
from requests.adapters import HTTPAdapter, Retry
|
|
14
|
-
from rich.console import Console
|
|
15
13
|
from tenacity import RetryError, retry, stop_after_attempt
|
|
16
14
|
|
|
17
15
|
from cs2tracker.constants import (
|
|
16
|
+
AUTHOR_STRING,
|
|
17
|
+
BANNER,
|
|
18
18
|
BATCH_FILE,
|
|
19
19
|
CAPSULE_INFO,
|
|
20
20
|
CASE_HREFS,
|
|
21
21
|
CASE_PAGES,
|
|
22
22
|
CONFIG_FILE,
|
|
23
|
+
OS,
|
|
23
24
|
OUTPUT_FILE,
|
|
24
25
|
PROJECT_DIR,
|
|
25
26
|
PYTHON_EXECUTABLE,
|
|
27
|
+
RUNNING_IN_EXE,
|
|
28
|
+
OSType,
|
|
26
29
|
)
|
|
30
|
+
from cs2tracker.padded_console import PaddedConsole
|
|
27
31
|
|
|
28
32
|
MAX_LINE_LEN = 72
|
|
29
33
|
SEPARATOR = "-"
|
|
30
34
|
PRICE_INFO = "Owned: {} Steam market price: ${} Total: ${}\n"
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
|
|
36
|
+
HTTP_PROXY_URL = "http://{}:@smartproxy.crawlbase.com:8012"
|
|
37
|
+
HTTPS_PROXY_URL = "http://{}:@smartproxy.crawlbase.com:8012"
|
|
38
|
+
|
|
39
|
+
WIN_BACKGROUND_TASK_NAME = "CS2Tracker Daily Calculation"
|
|
40
|
+
WIN_BACKGROUND_TASK_SCHEDULE = "DAILY"
|
|
41
|
+
WIN_BACKGROUND_TASK_TIME = "12:00"
|
|
42
|
+
WIN_BACKGROUND_TASK_CMD = (
|
|
43
|
+
f"powershell -WindowStyle Hidden -Command \"Start-Process '{BATCH_FILE}' -WindowStyle Hidden\""
|
|
44
|
+
)
|
|
33
45
|
|
|
34
46
|
|
|
35
47
|
class Scraper:
|
|
36
48
|
def __init__(self):
|
|
37
49
|
"""Initialize the Scraper class."""
|
|
38
|
-
self.console =
|
|
50
|
+
self.console = PaddedConsole()
|
|
39
51
|
self.parse_config()
|
|
40
52
|
self._start_session()
|
|
41
53
|
|
|
@@ -111,21 +123,33 @@ class Scraper:
|
|
|
111
123
|
This will append a new entry to the output file if no entry has been made for
|
|
112
124
|
today.
|
|
113
125
|
"""
|
|
114
|
-
if not os.path.isfile(OUTPUT_FILE):
|
|
115
|
-
open(OUTPUT_FILE, "w", encoding="utf-8").close()
|
|
116
|
-
|
|
117
126
|
with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
|
|
118
127
|
price_logs_reader = csv.reader(price_logs)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
last_log_date, _ = row
|
|
128
|
+
rows = list(price_logs_reader)
|
|
129
|
+
last_log_date, _, _ = rows[-1] if rows else ("", "", "")
|
|
122
130
|
|
|
123
131
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
124
132
|
if last_log_date != today:
|
|
133
|
+
# Append first price calculation of the day
|
|
125
134
|
with open(OUTPUT_FILE, "a", newline="", encoding="utf-8") as price_logs:
|
|
126
135
|
price_logs_writer = csv.writer(price_logs)
|
|
127
|
-
price_logs_writer.writerow(
|
|
128
|
-
|
|
136
|
+
price_logs_writer.writerow(
|
|
137
|
+
[today, f"{self.usd_total:.2f}$", f"{self.eur_total:.2f}€"]
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
# Replace the last calculation of today with the most recent one of today
|
|
141
|
+
with open(OUTPUT_FILE, "r+", newline="", encoding="utf-8") as price_logs:
|
|
142
|
+
price_logs_reader = csv.reader(price_logs)
|
|
143
|
+
rows = list(price_logs_reader)
|
|
144
|
+
rows_without_today = rows[:-1]
|
|
145
|
+
price_logs.seek(0)
|
|
146
|
+
price_logs.truncate()
|
|
147
|
+
|
|
148
|
+
price_logs_writer = csv.writer(price_logs)
|
|
149
|
+
price_logs_writer.writerows(rows_without_today)
|
|
150
|
+
price_logs_writer.writerow(
|
|
151
|
+
[today, f"{self.usd_total:.2f}$", f"{self.eur_total:.2f}€"]
|
|
152
|
+
)
|
|
129
153
|
|
|
130
154
|
def read_price_log(self):
|
|
131
155
|
"""
|
|
@@ -134,23 +158,18 @@ class Scraper:
|
|
|
134
158
|
|
|
135
159
|
:return: A tuple containing three lists: dates, dollar prices, and euro prices.
|
|
136
160
|
"""
|
|
137
|
-
if not os.path.isfile(OUTPUT_FILE):
|
|
138
|
-
open(OUTPUT_FILE, "w", encoding="utf-8").close()
|
|
139
|
-
|
|
140
161
|
dates, dollars, euros = [], [], []
|
|
141
162
|
with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
|
|
142
163
|
price_logs_reader = csv.reader(price_logs)
|
|
143
164
|
for row in price_logs_reader:
|
|
144
|
-
date,
|
|
165
|
+
date, price_usd, price_eur = row
|
|
145
166
|
date = datetime.strptime(date, "%Y-%m-%d")
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# and we want the length of dates to match the lengths of dollars and euros
|
|
153
|
-
dates.append(date)
|
|
167
|
+
price_usd = float(price_usd.rstrip("$"))
|
|
168
|
+
price_eur = float(price_eur.rstrip("€"))
|
|
169
|
+
|
|
170
|
+
dates.append(date)
|
|
171
|
+
dollars.append(price_usd)
|
|
172
|
+
euros.append(price_eur)
|
|
154
173
|
|
|
155
174
|
return dates, dollars, euros
|
|
156
175
|
|
|
@@ -167,12 +186,13 @@ class Scraper:
|
|
|
167
186
|
"""
|
|
168
187
|
use_proxy = self.config.getboolean("Settings", "Use_Proxy", fallback=False)
|
|
169
188
|
api_key = self.config.get("Settings", "API_Key", fallback=None)
|
|
189
|
+
api_key = None if api_key in ("None", "") else api_key
|
|
170
190
|
if use_proxy and api_key:
|
|
171
191
|
page = self.session.get(
|
|
172
192
|
url=url,
|
|
173
193
|
proxies={
|
|
174
|
-
"http":
|
|
175
|
-
"https":
|
|
194
|
+
"http": HTTP_PROXY_URL.format(api_key),
|
|
195
|
+
"https": HTTPS_PROXY_URL.format(api_key),
|
|
176
196
|
},
|
|
177
197
|
verify=False,
|
|
178
198
|
)
|
|
@@ -310,8 +330,8 @@ class Scraper:
|
|
|
310
330
|
|
|
311
331
|
:return: True if a background task is found, False otherwise.
|
|
312
332
|
"""
|
|
313
|
-
if
|
|
314
|
-
cmd = ["schtasks", "/query", "/tn",
|
|
333
|
+
if OS == OSType.WINDOWS:
|
|
334
|
+
cmd = ["schtasks", "/query", "/tn", WIN_BACKGROUND_TASK_NAME]
|
|
315
335
|
return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
|
|
316
336
|
found = return_code == 0
|
|
317
337
|
return found
|
|
@@ -328,8 +348,13 @@ class Scraper:
|
|
|
328
348
|
"""
|
|
329
349
|
if enabled:
|
|
330
350
|
with open(BATCH_FILE, "w", encoding="utf-8") as batch_file:
|
|
331
|
-
|
|
332
|
-
|
|
351
|
+
if RUNNING_IN_EXE:
|
|
352
|
+
# The python executable is set to the executable itself
|
|
353
|
+
# for executables created with PyInstaller
|
|
354
|
+
batch_file.write(f"{PYTHON_EXECUTABLE} --only-scrape\n")
|
|
355
|
+
else:
|
|
356
|
+
batch_file.write(f"cd {PROJECT_DIR}\n")
|
|
357
|
+
batch_file.write(f"{PYTHON_EXECUTABLE} -m cs2tracker --only-scrape\n")
|
|
333
358
|
else:
|
|
334
359
|
if os.path.exists(BATCH_FILE):
|
|
335
360
|
os.remove(BATCH_FILE)
|
|
@@ -347,13 +372,13 @@ class Scraper:
|
|
|
347
372
|
"schtasks",
|
|
348
373
|
"/create",
|
|
349
374
|
"/tn",
|
|
350
|
-
|
|
375
|
+
WIN_BACKGROUND_TASK_NAME,
|
|
351
376
|
"/tr",
|
|
352
|
-
|
|
377
|
+
WIN_BACKGROUND_TASK_CMD,
|
|
353
378
|
"/sc",
|
|
354
|
-
|
|
379
|
+
WIN_BACKGROUND_TASK_SCHEDULE,
|
|
355
380
|
"/st",
|
|
356
|
-
|
|
381
|
+
WIN_BACKGROUND_TASK_TIME,
|
|
357
382
|
]
|
|
358
383
|
return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
|
|
359
384
|
if return_code == 0:
|
|
@@ -361,7 +386,7 @@ class Scraper:
|
|
|
361
386
|
else:
|
|
362
387
|
self.console.print("[bold red][!] Failed to enable background task.")
|
|
363
388
|
else:
|
|
364
|
-
cmd = ["schtasks", "/delete", "/tn",
|
|
389
|
+
cmd = ["schtasks", "/delete", "/tn", WIN_BACKGROUND_TASK_NAME, "/f"]
|
|
365
390
|
return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
|
|
366
391
|
if return_code == 0:
|
|
367
392
|
self.console.print("[bold green][-] Background task disabled.")
|
|
@@ -375,7 +400,7 @@ class Scraper:
|
|
|
375
400
|
:param enabled: If True, the task will be created; if False, the task will be
|
|
376
401
|
deleted.
|
|
377
402
|
"""
|
|
378
|
-
if
|
|
403
|
+
if OS == OSType.WINDOWS:
|
|
379
404
|
self._toggle_background_task_windows(enabled)
|
|
380
405
|
else:
|
|
381
406
|
# TODO: implement toggle for cron jobs
|
|
@@ -383,7 +408,6 @@ class Scraper:
|
|
|
383
408
|
|
|
384
409
|
|
|
385
410
|
if __name__ == "__main__":
|
|
386
|
-
# If this file is run as a script, create a Scraper instance and run the
|
|
387
|
-
# scrape_prices method.
|
|
388
411
|
scraper = Scraper()
|
|
412
|
+
scraper.console.print(f"[bold yellow]{BANNER}\n{AUTHOR_STRING}\n")
|
|
389
413
|
scraper.scrape_prices()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cs2tracker
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.5
|
|
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
|
|
@@ -40,11 +40,17 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
### Prerequisites
|
|
42
42
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
43
|
+
- Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required for Linux)
|
|
44
|
+
- Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
|
|
45
45
|
|
|
46
46
|
### Setup
|
|
47
47
|
|
|
48
|
+
#### Windows Executable (No color support)
|
|
49
|
+
|
|
50
|
+
- Simply download the [latest executable](https://github.com/ashiven/cs2tracker/releases) and run it.
|
|
51
|
+
|
|
52
|
+
#### Install via Pip
|
|
53
|
+
|
|
48
54
|
1. Install the program via pip:
|
|
49
55
|
|
|
50
56
|
```bash
|
|
@@ -58,11 +64,11 @@ Dynamic: license-file
|
|
|
58
64
|
|
|
59
65
|
### Options
|
|
60
66
|
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
67
|
+
- `Run!` to gather the current market prices of your items and calculate the total amount in USD and EUR.
|
|
68
|
+
- `Edit Config` to change the specific numbers of each item you own and then save the config file.
|
|
69
|
+
- `Show History` to see a price chart consisting of past calculations. A new data point is generated once a day upon running the program.
|
|
70
|
+
- `Daily Background Calculation` to automatically run a daily calculation of your investment in the background and save the results such that they can later be viewed via `Show History`.
|
|
71
|
+
- If you want to prevent your requests from being rate limited by the steamcommunity server, register for an API key on [Crawlbase](crawlbase.com) and enter it into the `API_Key` field in the config file. This will route every request through a different proxy server.
|
|
66
72
|
|
|
67
73
|
---
|
|
68
74
|
|
|
@@ -9,12 +9,14 @@ README.md
|
|
|
9
9
|
pyproject.toml
|
|
10
10
|
requirements.txt
|
|
11
11
|
setup.cfg
|
|
12
|
+
assets/icons8-counter-strike-bubbles-96.png
|
|
12
13
|
cs2tracker/__init__.py
|
|
13
14
|
cs2tracker/__main__.py
|
|
14
15
|
cs2tracker/_version.py
|
|
15
16
|
cs2tracker/application.py
|
|
16
17
|
cs2tracker/constants.py
|
|
17
18
|
cs2tracker/main.py
|
|
19
|
+
cs2tracker/padded_console.py
|
|
18
20
|
cs2tracker/scraper.py
|
|
19
21
|
cs2tracker.egg-info/PKG-INFO
|
|
20
22
|
cs2tracker.egg-info/SOURCES.txt
|
|
@@ -22,4 +24,5 @@ cs2tracker.egg-info/dependency_links.txt
|
|
|
22
24
|
cs2tracker.egg-info/entry_points.txt
|
|
23
25
|
cs2tracker.egg-info/requires.txt
|
|
24
26
|
cs2tracker.egg-info/top_level.txt
|
|
25
|
-
cs2tracker/data/config.ini
|
|
27
|
+
cs2tracker/data/config.ini
|
|
28
|
+
cs2tracker/data/output.csv
|
cs2tracker-2.1.3/README.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://github.com/ashiven/cs2tracker/releases)
|
|
5
|
-
[](https://badge.fury.io/py/cs2tracker)
|
|
6
|
-
[](https://github.com/ashiven/cs2tracker/issues)
|
|
7
|
-
[](https://github.com/ashiven/cs2tracker/pulls)
|
|
8
|
-

|
|
9
|
-
|
|
10
|
-
</div>
|
|
11
|
-
|
|
12
|
-
## About
|
|
13
|
-
|
|
14
|
-
**CS2Tracker** is a tool that can be used to keep track of the steam market prices of your CS2 investment.
|
|
15
|
-
|
|
16
|
-
## Getting Started
|
|
17
|
-
|
|
18
|
-
### Prerequisites
|
|
19
|
-
|
|
20
|
-
- Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/).
|
|
21
|
-
- Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
|
|
22
|
-
|
|
23
|
-
### Setup
|
|
24
|
-
|
|
25
|
-
1. Install the program via pip:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
pip install --user cs2tracker
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
2. Start the program:
|
|
32
|
-
```bash
|
|
33
|
-
cs2tracker
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Options
|
|
37
|
-
|
|
38
|
-
- `Run!` to gather the current market prices of your items and calculate the total amount in USD and EUR.
|
|
39
|
-
- `Edit Config` to change the specific numbers of each item you own and then save the config file.
|
|
40
|
-
- `Show History` to see a price chart consisting of past calculations. A new data point is generated once a day upon running the program.
|
|
41
|
-
- `Daily Background Calculation` to automatically run a daily calculation of your investment in the background and save the results such that they can later be viewed via `Show History`.
|
|
42
|
-
- If you want to prevent your requests from being rate limited by the steamcommunity server, register for an API key on [Crawlbase](crawlbase.com) and enter it into the `API_Key` field at the end of the config file. This will route every request through a different proxy server.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
> GitHub [@ashiven](https://github.com/Ashiven) ·
|
|
47
|
-
> Twitter [ashiven\_](https://twitter.com/ashiven_)
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import subprocess
|
|
3
|
-
import tkinter as tk
|
|
4
|
-
from typing import cast
|
|
5
|
-
|
|
6
|
-
import matplotlib.pyplot as plt
|
|
7
|
-
from matplotlib.axes import Axes
|
|
8
|
-
from matplotlib.dates import DateFormatter
|
|
9
|
-
|
|
10
|
-
from cs2tracker.constants import CONFIG_FILE, OUTPUT_FILE, TEXT_EDITOR
|
|
11
|
-
from cs2tracker.scraper import Scraper
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Application:
|
|
15
|
-
def __init__(self):
|
|
16
|
-
self.scraper = Scraper()
|
|
17
|
-
|
|
18
|
-
def run(self):
|
|
19
|
-
"""Run the main application window with buttons for scraping prices, editing the
|
|
20
|
-
configuration, showing history in a chart, and editing the log file.
|
|
21
|
-
"""
|
|
22
|
-
application_window = self._configure_window()
|
|
23
|
-
application_window.mainloop()
|
|
24
|
-
|
|
25
|
-
def _configure_window(self):
|
|
26
|
-
"""Configure the main application window layout with buttons for the various
|
|
27
|
-
actions.
|
|
28
|
-
"""
|
|
29
|
-
window = tk.Tk()
|
|
30
|
-
window.title("CS2Tracker")
|
|
31
|
-
window.geometry("450x450")
|
|
32
|
-
|
|
33
|
-
label = tk.Label(window, text="Welcome to CS2Tracker!")
|
|
34
|
-
run_button = tk.Button(window, text="Run!", command=self._scrape_prices)
|
|
35
|
-
edit_button = tk.Button(window, text="Edit Config", command=self._edit_config)
|
|
36
|
-
plot_button = tk.Button(window, text="Show History (Chart)", command=self._draw_plot)
|
|
37
|
-
plot_file_button = tk.Button(
|
|
38
|
-
window, text="Show History (File)", command=self._edit_log_file
|
|
39
|
-
)
|
|
40
|
-
background_checkbox_value = tk.BooleanVar(value=self.scraper.identify_background_task())
|
|
41
|
-
background_checkbox = tk.Checkbutton(
|
|
42
|
-
window,
|
|
43
|
-
text="Daily Background Calculation",
|
|
44
|
-
command=lambda: self._toggle_background_task(background_checkbox_value.get()),
|
|
45
|
-
variable=background_checkbox_value,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
label.grid(row=0, column=0, pady=50, sticky="NSEW")
|
|
49
|
-
run_button.grid(row=1, column=0, pady=10, sticky="NSEW")
|
|
50
|
-
edit_button.grid(row=2, column=0, pady=10, sticky="NSEW")
|
|
51
|
-
plot_button.grid(row=3, column=0, pady=10, sticky="NSEW")
|
|
52
|
-
plot_file_button.grid(row=4, column=0, pady=10, sticky="NSEW")
|
|
53
|
-
background_checkbox.grid(row=5, column=0, pady=10, sticky="NS")
|
|
54
|
-
|
|
55
|
-
window.grid_columnconfigure(0, weight=1)
|
|
56
|
-
window.grid_rowconfigure(1, weight=1)
|
|
57
|
-
window.grid_rowconfigure(2, weight=1)
|
|
58
|
-
window.grid_rowconfigure(3, weight=1)
|
|
59
|
-
window.grid_rowconfigure(4, weight=1)
|
|
60
|
-
window.grid_rowconfigure(5, weight=1)
|
|
61
|
-
|
|
62
|
-
label.grid_configure(sticky="NSEW")
|
|
63
|
-
run_button.grid_configure(sticky="NSEW")
|
|
64
|
-
edit_button.grid_configure(sticky="NSEW")
|
|
65
|
-
plot_button.grid_configure(sticky="NSEW")
|
|
66
|
-
plot_file_button.grid_configure(sticky="NSEW")
|
|
67
|
-
background_checkbox.grid_configure(sticky="NSEW")
|
|
68
|
-
|
|
69
|
-
return window
|
|
70
|
-
|
|
71
|
-
def _scrape_prices(self):
|
|
72
|
-
"""Scrape prices from the configured sources, print the total, and save the
|
|
73
|
-
results to a file.
|
|
74
|
-
"""
|
|
75
|
-
self.scraper.scrape_prices()
|
|
76
|
-
|
|
77
|
-
def _edit_config(self):
|
|
78
|
-
"""Edit the configuration file using the specified text editor."""
|
|
79
|
-
subprocess.call([TEXT_EDITOR, CONFIG_FILE])
|
|
80
|
-
self.scraper.parse_config()
|
|
81
|
-
|
|
82
|
-
def _draw_plot(self):
|
|
83
|
-
"""Draw a plot of the scraped prices over time."""
|
|
84
|
-
dates, dollars, euros = self.scraper.read_price_log()
|
|
85
|
-
|
|
86
|
-
fig, ax_raw = plt.subplots()
|
|
87
|
-
ax = cast(Axes, ax_raw)
|
|
88
|
-
|
|
89
|
-
ax.plot(dates, dollars, label="Dollars")
|
|
90
|
-
ax.plot(dates, euros, label="Euros")
|
|
91
|
-
ax.set_xlabel("Date")
|
|
92
|
-
ax.set_ylabel("Price")
|
|
93
|
-
ax.legend()
|
|
94
|
-
|
|
95
|
-
date_formatter = DateFormatter("%d-%m-%Y")
|
|
96
|
-
ax.xaxis.set_major_formatter(date_formatter)
|
|
97
|
-
fig.autofmt_xdate()
|
|
98
|
-
|
|
99
|
-
plt.show()
|
|
100
|
-
|
|
101
|
-
def _edit_log_file(self):
|
|
102
|
-
"""Opens the file containing past price calculations."""
|
|
103
|
-
if not os.path.isfile(OUTPUT_FILE):
|
|
104
|
-
open(OUTPUT_FILE, "w", encoding="utf-8").close()
|
|
105
|
-
subprocess.call([TEXT_EDITOR, OUTPUT_FILE])
|
|
106
|
-
|
|
107
|
-
def _toggle_background_task(self, enabled: bool):
|
|
108
|
-
"""Toggle whether a daily price calculation should run in the background."""
|
|
109
|
-
self.scraper.toggle_background_task(enabled)
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
|
|
3
|
-
import urllib3
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
|
|
6
|
-
from cs2tracker._version import version # pylint: disable=E0611
|
|
7
|
-
from cs2tracker.application import Application
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def main():
|
|
11
|
-
"""
|
|
12
|
-
The main entry point for the CS2Tracker application.
|
|
13
|
-
|
|
14
|
-
Provides a console output with the application version and date, and initializes the
|
|
15
|
-
application.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
# Disable warnings for proxy requests
|
|
19
|
-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
20
|
-
|
|
21
|
-
console = Console()
|
|
22
|
-
console.print(
|
|
23
|
-
"[bold yellow]"
|
|
24
|
-
+ """
|
|
25
|
-
__ _____ _____ ______ ____ ____ __ __ _ ___ ____
|
|
26
|
-
/ ] / ___/| T| T| \\ / T / ]| l/ ] / _]| \\
|
|
27
|
-
/ / ( \\_ l__/ || || D )Y o | / / | ' / / [_ | D )
|
|
28
|
-
/ / \\__ T| __jl_j l_j| / | | / / | \\ Y _]| /
|
|
29
|
-
/ \\_ / \\ || / | | | | \\ | _ |/ \\_ | Y| [_ | \\
|
|
30
|
-
\\ | \\ || | | | | . Y| | |\\ || . || T| . Y
|
|
31
|
-
\\____j \\___jl_____j l__j l__j\\_jl__j__j \\____jl__j\\_jl_____jl__j\\_j
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"""
|
|
35
|
-
+ f"Version: v{version} - {datetime.today().strftime('%Y/%m/%d')} - Jannik Novak @ashiven\n"
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
application = Application()
|
|
39
|
-
application.run()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if __name__ == "__main__":
|
|
43
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|