cs2tracker 2.1.9__py3-none-any.whl → 2.1.10__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/application.py +341 -95
- cs2tracker/background_task.py +109 -0
- cs2tracker/constants.py +2 -3
- cs2tracker/data/config.ini +155 -156
- cs2tracker/discord_notifier.py +87 -0
- cs2tracker/price_logs.py +100 -0
- cs2tracker/scraper.py +45 -365
- cs2tracker/validated_config.py +117 -0
- {cs2tracker-2.1.9.dist-info → cs2tracker-2.1.10.dist-info}/METADATA +7 -4
- cs2tracker-2.1.10.dist-info/RECORD +20 -0
- cs2tracker-2.1.9.dist-info/RECORD +0 -16
- {cs2tracker-2.1.9.dist-info → cs2tracker-2.1.10.dist-info}/WHEEL +0 -0
- {cs2tracker-2.1.9.dist-info → cs2tracker-2.1.10.dist-info}/entry_points.txt +0 -0
- {cs2tracker-2.1.9.dist-info → cs2tracker-2.1.10.dist-info}/licenses/LICENSE.md +0 -0
- {cs2tracker-2.1.9.dist-info → cs2tracker-2.1.10.dist-info}/top_level.txt +0 -0
cs2tracker/data/config.ini
CHANGED
|
@@ -1,205 +1,204 @@
|
|
|
1
1
|
[User Settings]
|
|
2
|
-
|
|
3
|
-
discord_webhook_url
|
|
4
|
-
|
|
5
|
-
[App Settings]
|
|
6
|
-
use_proxy = False
|
|
7
|
-
discord_notifications = False
|
|
2
|
+
proxy_api_key ~
|
|
3
|
+
discord_webhook_url ~
|
|
8
4
|
|
|
9
5
|
[Custom Items]
|
|
10
|
-
copenhagen_flames_gold_2022 = 0 https://steamcommunity.com/market/listings/730/Sticker%20%7C%20Copenhagen%20Flames%20%28Gold%29%20%7C%20Antwerp%202022
|
|
11
6
|
|
|
12
7
|
[Cases]
|
|
13
|
-
revolution_case
|
|
14
|
-
recoil_case
|
|
15
|
-
dreams_and_nightmares_case
|
|
16
|
-
operation_riptide_case
|
|
17
|
-
snakebite_case
|
|
18
|
-
operation_broken_fang_case
|
|
19
|
-
fracture_case
|
|
20
|
-
chroma_case
|
|
21
|
-
chroma_2_case
|
|
22
|
-
chroma_3_case
|
|
23
|
-
clutch_case
|
|
24
|
-
csgo_weapon_case
|
|
25
|
-
csgo_weapon_case_2
|
|
26
|
-
csgo_weapon_case_3
|
|
27
|
-
cs20_case
|
|
28
|
-
danger_zone_case
|
|
29
|
-
esports_2013_case
|
|
30
|
-
esports_2013_winter_case
|
|
31
|
-
esports_2014_summer_case
|
|
32
|
-
falchion_case
|
|
33
|
-
gamma_case
|
|
34
|
-
gamma_2_case
|
|
35
|
-
glove_case
|
|
36
|
-
horizon_case
|
|
37
|
-
huntsman_case
|
|
38
|
-
operation_bravo_case
|
|
39
|
-
operation_breakout_case
|
|
40
|
-
operation_hydra_case
|
|
41
|
-
operation_phoenix_case
|
|
42
|
-
operation_vanguard_case
|
|
43
|
-
operation_wildfire_case
|
|
44
|
-
prisma_case
|
|
45
|
-
prisma_2_case
|
|
46
|
-
revolver_case
|
|
47
|
-
shadow_case
|
|
48
|
-
shattered_web_case
|
|
49
|
-
spectrum_case
|
|
50
|
-
spectrum_2_case
|
|
51
|
-
winter_offensive_case
|
|
52
|
-
kilowatt_case
|
|
53
|
-
gallery_case
|
|
54
|
-
fever_case
|
|
8
|
+
revolution_case ~ 0
|
|
9
|
+
recoil_case ~ 0
|
|
10
|
+
dreams_and_nightmares_case ~ 0
|
|
11
|
+
operation_riptide_case ~ 0
|
|
12
|
+
snakebite_case ~ 0
|
|
13
|
+
operation_broken_fang_case ~ 0
|
|
14
|
+
fracture_case ~ 0
|
|
15
|
+
chroma_case ~ 0
|
|
16
|
+
chroma_2_case ~ 0
|
|
17
|
+
chroma_3_case ~ 0
|
|
18
|
+
clutch_case ~ 0
|
|
19
|
+
csgo_weapon_case ~ 0
|
|
20
|
+
csgo_weapon_case_2 ~ 0
|
|
21
|
+
csgo_weapon_case_3 ~ 0
|
|
22
|
+
cs20_case ~ 0
|
|
23
|
+
danger_zone_case ~ 0
|
|
24
|
+
esports_2013_case ~ 0
|
|
25
|
+
esports_2013_winter_case ~ 0
|
|
26
|
+
esports_2014_summer_case ~ 0
|
|
27
|
+
falchion_case ~ 0
|
|
28
|
+
gamma_case ~ 0
|
|
29
|
+
gamma_2_case ~ 0
|
|
30
|
+
glove_case ~ 0
|
|
31
|
+
horizon_case ~ 0
|
|
32
|
+
huntsman_case ~ 0
|
|
33
|
+
operation_bravo_case ~ 0
|
|
34
|
+
operation_breakout_case ~ 0
|
|
35
|
+
operation_hydra_case ~ 0
|
|
36
|
+
operation_phoenix_case ~ 0
|
|
37
|
+
operation_vanguard_case ~ 0
|
|
38
|
+
operation_wildfire_case ~ 0
|
|
39
|
+
prisma_case ~ 0
|
|
40
|
+
prisma_2_case ~ 0
|
|
41
|
+
revolver_case ~ 0
|
|
42
|
+
shadow_case ~ 0
|
|
43
|
+
shattered_web_case ~ 0
|
|
44
|
+
spectrum_case ~ 0
|
|
45
|
+
spectrum_2_case ~ 0
|
|
46
|
+
winter_offensive_case ~ 0
|
|
47
|
+
kilowatt_case ~ 0
|
|
48
|
+
gallery_case ~ 0
|
|
49
|
+
fever_case ~ 0
|
|
55
50
|
|
|
56
51
|
[Katowice 2014 Sticker Capsule]
|
|
57
|
-
katowice_legends
|
|
58
|
-
katowice_challengers
|
|
52
|
+
katowice_legends ~ 0
|
|
53
|
+
katowice_challengers ~ 0
|
|
59
54
|
|
|
60
55
|
[Cologne 2014 Sticker Capsule]
|
|
61
|
-
cologne_legends
|
|
62
|
-
cologne_challengers
|
|
56
|
+
cologne_legends ~ 0
|
|
57
|
+
cologne_challengers ~ 0
|
|
63
58
|
|
|
64
59
|
[DreamHack 2014 Sticker Capsule]
|
|
65
|
-
dreamhack_legends
|
|
60
|
+
dreamhack_legends ~ 0
|
|
66
61
|
|
|
67
62
|
[Katowice 2015 Sticker Capsule]
|
|
68
|
-
katowice_legends
|
|
69
|
-
katowice_challengers
|
|
63
|
+
katowice_legends ~ 0
|
|
64
|
+
katowice_challengers ~ 0
|
|
70
65
|
|
|
71
66
|
[Cologne 2015 Sticker Capsule]
|
|
72
|
-
cologne_legends
|
|
73
|
-
cologne_challengers
|
|
67
|
+
cologne_legends ~ 0
|
|
68
|
+
cologne_challengers ~ 0
|
|
74
69
|
|
|
75
70
|
[Cluj-Napoca 2015 Sticker Capsule]
|
|
76
|
-
cluj_napoca_legends
|
|
77
|
-
cluj_napoca_challengers
|
|
78
|
-
cluj_napoca_legends_autographs
|
|
79
|
-
cluj_napoca_challengers_autographs
|
|
71
|
+
cluj_napoca_legends ~ 0
|
|
72
|
+
cluj_napoca_challengers ~ 0
|
|
73
|
+
cluj_napoca_legends_autographs ~ 0
|
|
74
|
+
cluj_napoca_challengers_autographs ~ 0
|
|
80
75
|
|
|
81
76
|
[Columbus 2016 Sticker Capsule]
|
|
82
|
-
columbus_legends
|
|
83
|
-
columbus_challengers
|
|
84
|
-
columbus_legends_autographs
|
|
85
|
-
columbus_challengers_autographs
|
|
77
|
+
columbus_legends ~ 0
|
|
78
|
+
columbus_challengers ~ 0
|
|
79
|
+
columbus_legends_autographs ~ 0
|
|
80
|
+
columbus_challengers_autographs ~ 0
|
|
86
81
|
|
|
87
82
|
[Cologne 2016 Sticker Capsule]
|
|
88
|
-
cologne_legends
|
|
89
|
-
cologne_challengers
|
|
90
|
-
cologne_legends_autographs
|
|
91
|
-
cologne_challengers_autographs
|
|
83
|
+
cologne_legends ~ 0
|
|
84
|
+
cologne_challengers ~ 0
|
|
85
|
+
cologne_legends_autographs ~ 0
|
|
86
|
+
cologne_challengers_autographs ~ 0
|
|
92
87
|
|
|
93
88
|
[Atlanta 2017 Sticker Capsule]
|
|
94
|
-
atlanta_legends
|
|
95
|
-
atlanta_challengers
|
|
96
|
-
atlanta_legends_autographs
|
|
97
|
-
atlanta_challengers_autographs
|
|
89
|
+
atlanta_legends ~ 0
|
|
90
|
+
atlanta_challengers ~ 0
|
|
91
|
+
atlanta_legends_autographs ~ 0
|
|
92
|
+
atlanta_challengers_autographs ~ 0
|
|
98
93
|
|
|
99
94
|
[Krakow 2017 Sticker Capsule]
|
|
100
|
-
krakow_legends
|
|
101
|
-
krakow_challengers
|
|
102
|
-
krakow_legends_autographs
|
|
103
|
-
krakow_challengers_autographs
|
|
95
|
+
krakow_legends ~ 0
|
|
96
|
+
krakow_challengers ~ 0
|
|
97
|
+
krakow_legends_autographs ~ 0
|
|
98
|
+
krakow_challengers_autographs ~ 0
|
|
104
99
|
|
|
105
100
|
[Boston 2018 Sticker Capsule]
|
|
106
|
-
boston_legends
|
|
107
|
-
boston_minor_challengers
|
|
108
|
-
boston_returning_challengers
|
|
109
|
-
boston_attending_legends
|
|
110
|
-
boston_minor_challengers_with_flash_gaming
|
|
111
|
-
boston_legends_autographs
|
|
112
|
-
boston_minor_challengers_autographs
|
|
113
|
-
boston_returning_challengers_autographs
|
|
114
|
-
boston_attending_legends_autographs
|
|
115
|
-
boston_minor_challengers_with_flash_gaming_autographs
|
|
101
|
+
boston_legends ~ 0
|
|
102
|
+
boston_minor_challengers ~ 0
|
|
103
|
+
boston_returning_challengers ~ 0
|
|
104
|
+
boston_attending_legends ~ 0
|
|
105
|
+
boston_minor_challengers_with_flash_gaming ~ 0
|
|
106
|
+
boston_legends_autographs ~ 0
|
|
107
|
+
boston_minor_challengers_autographs ~ 0
|
|
108
|
+
boston_returning_challengers_autographs ~ 0
|
|
109
|
+
boston_attending_legends_autographs ~ 0
|
|
110
|
+
boston_minor_challengers_with_flash_gaming_autographs ~ 0
|
|
116
111
|
|
|
117
112
|
[London 2018 Sticker Capsule]
|
|
118
|
-
london_legends
|
|
119
|
-
london_minor_challengers
|
|
120
|
-
london_returning_challengers
|
|
121
|
-
london_legends_autographs
|
|
122
|
-
london_minor_challengers_autographs
|
|
123
|
-
london_returning_challengers_autographs
|
|
113
|
+
london_legends ~ 0
|
|
114
|
+
london_minor_challengers ~ 0
|
|
115
|
+
london_returning_challengers ~ 0
|
|
116
|
+
london_legends_autographs ~ 0
|
|
117
|
+
london_minor_challengers_autographs ~ 0
|
|
118
|
+
london_returning_challengers_autographs ~ 0
|
|
124
119
|
|
|
125
120
|
[Katowice 2019 Sticker Capsule]
|
|
126
|
-
katowice_legends
|
|
127
|
-
katowice_minor_challengers
|
|
128
|
-
katowice_returning_challengers
|
|
129
|
-
katowice_legends_autographs
|
|
130
|
-
katowice_minor_challengers_autographs
|
|
131
|
-
katowice_returning_challengers_autographs
|
|
121
|
+
katowice_legends ~ 0
|
|
122
|
+
katowice_minor_challengers ~ 0
|
|
123
|
+
katowice_returning_challengers ~ 0
|
|
124
|
+
katowice_legends_autographs ~ 0
|
|
125
|
+
katowice_minor_challengers_autographs ~ 0
|
|
126
|
+
katowice_returning_challengers_autographs ~ 0
|
|
132
127
|
|
|
133
128
|
[Berlin 2019 Sticker Capsule]
|
|
134
|
-
berlin_legends
|
|
135
|
-
berlin_minor_challengers
|
|
136
|
-
berlin_returning_challengers
|
|
137
|
-
berlin_legends_autographs
|
|
138
|
-
berlin_minor_challengers_autographs
|
|
139
|
-
berlin_returning_challengers_autographs
|
|
129
|
+
berlin_legends ~ 0
|
|
130
|
+
berlin_minor_challengers ~ 0
|
|
131
|
+
berlin_returning_challengers ~ 0
|
|
132
|
+
berlin_legends_autographs ~ 0
|
|
133
|
+
berlin_minor_challengers_autographs ~ 0
|
|
134
|
+
berlin_returning_challengers_autographs ~ 0
|
|
140
135
|
|
|
141
136
|
[2020 RMR Sticker Capsule]
|
|
142
|
-
rmr_legends
|
|
143
|
-
rmr_challengers
|
|
144
|
-
rmr_contenders
|
|
137
|
+
rmr_legends ~ 0
|
|
138
|
+
rmr_challengers ~ 0
|
|
139
|
+
rmr_contenders ~ 0
|
|
145
140
|
|
|
146
141
|
[Stockholm 2021 Sticker Capsule]
|
|
147
|
-
stockholm_legends
|
|
148
|
-
stockholm_challengers
|
|
149
|
-
stockholm_contenders
|
|
150
|
-
stockholm_champions_autographs
|
|
151
|
-
stockholm_finalists_autographs
|
|
142
|
+
stockholm_legends ~ 0
|
|
143
|
+
stockholm_challengers ~ 0
|
|
144
|
+
stockholm_contenders ~ 0
|
|
145
|
+
stockholm_champions_autographs ~ 0
|
|
146
|
+
stockholm_finalists_autographs ~ 0
|
|
152
147
|
|
|
153
148
|
[Antwerp 2022 Sticker Capsule]
|
|
154
|
-
antwerp_legends
|
|
155
|
-
antwerp_challengers
|
|
156
|
-
antwerp_contenders
|
|
157
|
-
antwerp_champions_autographs
|
|
158
|
-
antwerp_challengers_autographs
|
|
159
|
-
antwerp_legends_autographs
|
|
160
|
-
antwerp_contenders_autographs
|
|
149
|
+
antwerp_legends ~ 0
|
|
150
|
+
antwerp_challengers ~ 0
|
|
151
|
+
antwerp_contenders ~ 0
|
|
152
|
+
antwerp_champions_autographs ~ 0
|
|
153
|
+
antwerp_challengers_autographs ~ 0
|
|
154
|
+
antwerp_legends_autographs ~ 0
|
|
155
|
+
antwerp_contenders_autographs ~ 0
|
|
161
156
|
|
|
162
157
|
[Rio 2022 Sticker Capsule]
|
|
163
|
-
rio_legends
|
|
164
|
-
rio_challengers
|
|
165
|
-
rio_contenders
|
|
166
|
-
rio_champions_autographs
|
|
167
|
-
rio_challengers_autographs
|
|
168
|
-
rio_legends_autographs
|
|
169
|
-
rio_contenders_autographs
|
|
158
|
+
rio_legends ~ 0
|
|
159
|
+
rio_challengers ~ 0
|
|
160
|
+
rio_contenders ~ 0
|
|
161
|
+
rio_champions_autographs ~ 0
|
|
162
|
+
rio_challengers_autographs ~ 0
|
|
163
|
+
rio_legends_autographs ~ 0
|
|
164
|
+
rio_contenders_autographs ~ 0
|
|
170
165
|
|
|
171
166
|
[Paris 2023 Sticker Capsule]
|
|
172
|
-
paris_legends
|
|
173
|
-
paris_challengers
|
|
174
|
-
paris_contenders
|
|
175
|
-
paris_champions_autographs
|
|
176
|
-
paris_challengers_autographs
|
|
177
|
-
paris_legends_autographs
|
|
178
|
-
paris_contenders_autographs
|
|
167
|
+
paris_legends ~ 0
|
|
168
|
+
paris_challengers ~ 0
|
|
169
|
+
paris_contenders ~ 0
|
|
170
|
+
paris_champions_autographs ~ 0
|
|
171
|
+
paris_challengers_autographs ~ 0
|
|
172
|
+
paris_legends_autographs ~ 0
|
|
173
|
+
paris_contenders_autographs ~ 0
|
|
179
174
|
|
|
180
175
|
[Copenhagen 2024 Sticker Capsule]
|
|
181
|
-
copenhagen_legends
|
|
182
|
-
copenhagen_challengers
|
|
183
|
-
copenhagen_contenders
|
|
184
|
-
copenhagen_champions_autographs
|
|
185
|
-
copenhagen_challengers_autographs
|
|
186
|
-
copenhagen_legends_autographs
|
|
187
|
-
copenhagen_contenders_autographs
|
|
176
|
+
copenhagen_legends ~ 0
|
|
177
|
+
copenhagen_challengers ~ 0
|
|
178
|
+
copenhagen_contenders ~ 0
|
|
179
|
+
copenhagen_champions_autographs ~ 0
|
|
180
|
+
copenhagen_challengers_autographs ~ 0
|
|
181
|
+
copenhagen_legends_autographs ~ 0
|
|
182
|
+
copenhagen_contenders_autographs ~ 0
|
|
188
183
|
|
|
189
184
|
[Shanghai 2024 Sticker Capsule]
|
|
190
|
-
shanghai_legends
|
|
191
|
-
shanghai_challengers
|
|
192
|
-
shanghai_contenders
|
|
193
|
-
shanghai_champions_autographs
|
|
194
|
-
shanghai_challengers_autographs
|
|
195
|
-
shanghai_legends_autographs
|
|
196
|
-
shanghai_contenders_autographs
|
|
185
|
+
shanghai_legends ~ 0
|
|
186
|
+
shanghai_challengers ~ 0
|
|
187
|
+
shanghai_contenders ~ 0
|
|
188
|
+
shanghai_champions_autographs ~ 0
|
|
189
|
+
shanghai_challengers_autographs ~ 0
|
|
190
|
+
shanghai_legends_autographs ~ 0
|
|
191
|
+
shanghai_contenders_autographs ~ 0
|
|
197
192
|
|
|
198
193
|
[Austin 2025 Sticker Capsule]
|
|
199
|
-
austin_legends
|
|
200
|
-
austin_challengers
|
|
201
|
-
austin_contenders
|
|
202
|
-
austin_champions_autographs
|
|
203
|
-
austin_challengers_autographs
|
|
204
|
-
austin_legends_autographs
|
|
205
|
-
austin_contenders_autographs
|
|
194
|
+
austin_legends ~ 0
|
|
195
|
+
austin_challengers ~ 0
|
|
196
|
+
austin_contenders ~ 0
|
|
197
|
+
austin_champions_autographs ~ 0
|
|
198
|
+
austin_challengers_autographs ~ 0
|
|
199
|
+
austin_legends_autographs ~ 0
|
|
200
|
+
austin_contenders_autographs ~ 0
|
|
201
|
+
|
|
202
|
+
[App Settings]
|
|
203
|
+
use_proxy ~ False
|
|
204
|
+
discord_notifications ~ False
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from requests.exceptions import RequestException
|
|
3
|
+
|
|
4
|
+
from cs2tracker.padded_console import PaddedConsole
|
|
5
|
+
from cs2tracker.price_logs import PriceLogs
|
|
6
|
+
|
|
7
|
+
DC_WEBHOOK_USERNAME = "CS2Tracker"
|
|
8
|
+
DC_WEBHOOK_AVATAR_URL = "https://img.icons8.com/?size=100&id=uWQJp2tLXUH6&format=png&color=000000"
|
|
9
|
+
DC_RECENT_HISTORY_LIMIT = 5
|
|
10
|
+
|
|
11
|
+
console = PaddedConsole()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DiscordNotifier:
|
|
15
|
+
@classmethod
|
|
16
|
+
def _construct_recent_calculations_embeds(cls):
|
|
17
|
+
"""
|
|
18
|
+
Construct the embeds for the Discord message that will be sent after a price
|
|
19
|
+
calculation has been made.
|
|
20
|
+
|
|
21
|
+
:return: A list of embeds for the Discord message.
|
|
22
|
+
"""
|
|
23
|
+
dates, usd_prices, eur_prices = PriceLogs.read()
|
|
24
|
+
dates, usd_prices, eur_prices = reversed(dates), reversed(usd_prices), reversed(eur_prices)
|
|
25
|
+
|
|
26
|
+
date_history, usd_history, eur_history = [], [], []
|
|
27
|
+
for date, usd_log, eur_log in zip(dates, usd_prices, eur_prices):
|
|
28
|
+
if len(date_history) >= DC_RECENT_HISTORY_LIMIT:
|
|
29
|
+
break
|
|
30
|
+
date_history.append(date.strftime("%Y-%m-%d"))
|
|
31
|
+
usd_history.append(f"${usd_log:.2f}")
|
|
32
|
+
eur_history.append(f"€{eur_log:.2f}")
|
|
33
|
+
|
|
34
|
+
date_history = "\n".join(date_history)
|
|
35
|
+
usd_history = "\n".join(usd_history)
|
|
36
|
+
eur_history = "\n".join(eur_history)
|
|
37
|
+
|
|
38
|
+
embeds = [
|
|
39
|
+
{
|
|
40
|
+
"title": "📊 Recent Price History",
|
|
41
|
+
"color": 5814783,
|
|
42
|
+
"fields": [
|
|
43
|
+
{
|
|
44
|
+
"name": "Date",
|
|
45
|
+
"value": date_history,
|
|
46
|
+
"inline": True,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "USD Total",
|
|
50
|
+
"value": usd_history,
|
|
51
|
+
"inline": True,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "EUR Total",
|
|
55
|
+
"value": eur_history,
|
|
56
|
+
"inline": True,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
return embeds
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def notify(cls, webhook_url):
|
|
66
|
+
"""
|
|
67
|
+
Notify users via Discord about recent price calculations.
|
|
68
|
+
|
|
69
|
+
:param webhook_url: The Discord webhook URL to send the notification to.
|
|
70
|
+
"""
|
|
71
|
+
embeds = cls._construct_recent_calculations_embeds()
|
|
72
|
+
try:
|
|
73
|
+
response = requests.post(
|
|
74
|
+
url=webhook_url,
|
|
75
|
+
json={
|
|
76
|
+
"embeds": embeds,
|
|
77
|
+
"username": DC_WEBHOOK_USERNAME,
|
|
78
|
+
"avatar_url": DC_WEBHOOK_AVATAR_URL,
|
|
79
|
+
},
|
|
80
|
+
timeout=10,
|
|
81
|
+
)
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
console.print("[bold steel_blue3][+] Discord notification sent.\n")
|
|
84
|
+
except RequestException as error:
|
|
85
|
+
console.print(f"[bold red][!] Failed to send Discord notification: {error}\n")
|
|
86
|
+
except Exception as error:
|
|
87
|
+
console.print(f"[bold red][!] An unexpected error occurred: {error}\n")
|
cs2tracker/price_logs.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from cs2tracker.constants import OUTPUT_FILE
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PriceLogs:
|
|
8
|
+
@classmethod
|
|
9
|
+
def _append_latest_calculation(cls, date, usd_total, eur_total):
|
|
10
|
+
"""Append the first price calculation of the day."""
|
|
11
|
+
with open(OUTPUT_FILE, "a", newline="", encoding="utf-8") as price_logs:
|
|
12
|
+
price_logs_writer = csv.writer(price_logs)
|
|
13
|
+
price_logs_writer.writerow([date, f"{usd_total:.2f}$", f"{eur_total:.2f}€"])
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def _replace_latest_calculation(cls, date, usd_total, eur_total):
|
|
17
|
+
"""Replace the last calculation of today with the most recent one of today."""
|
|
18
|
+
with open(OUTPUT_FILE, "r+", newline="", encoding="utf-8") as price_logs:
|
|
19
|
+
price_logs_reader = csv.reader(price_logs)
|
|
20
|
+
rows = list(price_logs_reader)
|
|
21
|
+
rows_without_today = rows[:-1]
|
|
22
|
+
price_logs.seek(0)
|
|
23
|
+
price_logs.truncate()
|
|
24
|
+
|
|
25
|
+
price_logs_writer = csv.writer(price_logs)
|
|
26
|
+
price_logs_writer.writerows(rows_without_today)
|
|
27
|
+
price_logs_writer.writerow([date, f"{usd_total:.2f}$", f"{eur_total:.2f}€"])
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def save(cls, usd_total, eur_total):
|
|
31
|
+
"""
|
|
32
|
+
Save the current date and total prices in USD and EUR to a CSV file.
|
|
33
|
+
|
|
34
|
+
This will append a new entry to the output file if no entry has been made for
|
|
35
|
+
today.
|
|
36
|
+
|
|
37
|
+
:param usd_total: The total price in USD to save.
|
|
38
|
+
:param eur_total: The total price in EUR to save.
|
|
39
|
+
:raises FileNotFoundError: If the output file does not exist.
|
|
40
|
+
:raises IOError: If there is an error writing to the output file.
|
|
41
|
+
"""
|
|
42
|
+
with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
|
|
43
|
+
price_logs_reader = csv.reader(price_logs)
|
|
44
|
+
rows = list(price_logs_reader)
|
|
45
|
+
last_log_date, _, _ = rows[-1] if rows else ("", "", "")
|
|
46
|
+
|
|
47
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
48
|
+
if last_log_date != today:
|
|
49
|
+
cls._append_latest_calculation(today, usd_total, eur_total)
|
|
50
|
+
else:
|
|
51
|
+
cls._replace_latest_calculation(today, usd_total, eur_total)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def read(cls):
|
|
55
|
+
"""
|
|
56
|
+
Parse the output file to extract dates, dollar prices, and euro prices. This
|
|
57
|
+
data is used for drawing the plot of past prices.
|
|
58
|
+
|
|
59
|
+
:return: A tuple containing three lists: dates, dollar prices, and euro prices.
|
|
60
|
+
:raises FileNotFoundError: If the output file does not exist.
|
|
61
|
+
:raises IOError: If there is an error reading the output file.
|
|
62
|
+
"""
|
|
63
|
+
dates, usd_prices, eur_prices = [], [], []
|
|
64
|
+
with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
|
|
65
|
+
price_logs_reader = csv.reader(price_logs)
|
|
66
|
+
for row in price_logs_reader:
|
|
67
|
+
date, price_usd, price_eur = row
|
|
68
|
+
date = datetime.strptime(date, "%Y-%m-%d")
|
|
69
|
+
price_usd = float(price_usd.rstrip("$"))
|
|
70
|
+
price_eur = float(price_eur.rstrip("€"))
|
|
71
|
+
|
|
72
|
+
dates.append(date)
|
|
73
|
+
usd_prices.append(price_usd)
|
|
74
|
+
eur_prices.append(price_eur)
|
|
75
|
+
|
|
76
|
+
return dates, usd_prices, eur_prices
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def validate_file(cls, log_file_path):
|
|
80
|
+
"""
|
|
81
|
+
Ensures that the provided price log file has the right format. This should be
|
|
82
|
+
used before importing a price log file to ensure it is valid.
|
|
83
|
+
|
|
84
|
+
:param log_file_path: The path to the price log file to validate.
|
|
85
|
+
:return: True if the price log file is valid, False otherwise.
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
with open(log_file_path, "r", encoding="utf-8") as price_logs:
|
|
89
|
+
price_logs_reader = csv.reader(price_logs)
|
|
90
|
+
for row in price_logs_reader:
|
|
91
|
+
date_str, price_usd, price_eur = row
|
|
92
|
+
datetime.strptime(date_str, "%Y-%m-%d")
|
|
93
|
+
float(price_usd.rstrip("$"))
|
|
94
|
+
float(price_eur.rstrip("€"))
|
|
95
|
+
except (FileNotFoundError, IOError, ValueError, TypeError):
|
|
96
|
+
return False
|
|
97
|
+
except Exception:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
return True
|