forked from charlesmadere/CynanBotCommon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanalogueStoreRepository.py
262 lines (209 loc) · 9.2 KB
/
analogueStoreRepository.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
from datetime import datetime, timedelta
from enum import Enum, auto
from typing import List
import requests
from lxml import html
from requests import ConnectionError, HTTPError, Timeout
from urllib3.exceptions import MaxRetryError, NewConnectionError
import CynanBotCommon.utils as utils
class AnalogueProductType(Enum):
DAC = auto()
DUO = auto()
MEGA_SG = auto()
NT_MINI = auto()
OTHER = auto()
POCKET = auto()
SUPER_NT = auto()
@classmethod
def fromStr(cls, text: str):
if not utils.isValidStr(text):
return cls.OTHER
text = text.lower()
if 'dac' in text or 'dac' == text:
return cls.DAC
elif 'duo' in text or 'duo' == text:
return cls.DUO
elif 'mega sg -' in text or 'mega sg' == text:
return cls.MEGA_SG
elif 'nt mini' in text or 'nt mini' == text:
return cls.NT_MINI
elif 'pocket -' in text or 'pocket' == text:
return cls.POCKET
elif 'super nt -' in text or 'super nt' == text:
return cls.SUPER_NT
else:
return cls.OTHER
def toStr(self) -> str:
if self is self.DAC:
return 'DAC'
elif self is self.DUO:
return 'Duo'
elif self is self.MEGA_SG:
return 'Mega Sg'
elif self is self.NT_MINI:
return 'Nt mini'
elif self is self.OTHER:
return 'other'
elif self is self.POCKET:
return 'Pocket'
elif self is self.SUPER_NT:
return 'Super Nt'
else:
return 'other'
class AnalogueStoreEntry():
def __init__(
self,
productType: AnalogueProductType,
inStock: bool,
name: str,
price: str
):
if productType is None:
raise ValueError(f'productType argument is malformed: \"{productType}\"')
elif inStock is None:
raise ValueError(f'inStock argument is malformed: \"{inStock}\"')
elif not utils.isValidStr(name):
raise ValueError(f'name argument is malformed: \"{name}\"')
self.__productType = productType
self.__inStock = inStock
self.__name = name
self.__price = price
def getName(self) -> str:
return self.__name
def getPrice(self) -> str:
return self.__price
def getProductType(self) -> AnalogueProductType:
return self.__productType
def hasPrice(self) -> bool:
return utils.isValidStr(self.__price)
def inStock(self) -> bool:
return self.__inStock
def toStr(self, includePrice: bool = False, includeStockInfo: bool = False) -> str:
if includePrice is None:
raise ValueError(f'includePrice argument is malformed: \"{includePrice}\"')
elif includeStockInfo is None:
raise ValueError(f'includeStockInfo argument is malformed: \"{includeStockInfo}\"')
priceAndStockText = ''
if includePrice or includeStockInfo:
if includePrice and self.hasPrice():
if includeStockInfo:
if self.inStock():
priceAndStockText = f' (in stock, {self.__price})'
else:
priceAndStockText = f' (out of stock, {self.__price})'
else:
priceAndStockText = f' ({self.__price})'
elif includeStockInfo:
if self.inStock():
priceAndStockText = f' (in stock)'
else:
priceAndStockText = f' (out of stock)'
return f'{self.__name}{priceAndStockText}'
class AnalogueStoreStock():
def __init__(self, products: List[AnalogueStoreEntry]):
if products == None:
raise ValueError(f'products argument is malformed: \"{products}\"')
self.__products = products
def getProducts(self) -> List[AnalogueStoreEntry]:
return self.__products
def hasProducts(self) -> bool:
return utils.hasItems(self.__products)
def toStr(self, includePrices: bool = False, inStockProductsOnly: bool = True, delimiter: str = ', ') -> str:
if includePrices is None:
raise ValueError(f'includePrices argument is malformed: \"{includePrices}\"')
elif inStockProductsOnly is None:
raise ValueError(f'inStockProductsOnly argument is malformed: \"{inStockProductsOnly}\"')
elif delimiter is None:
raise ValueError(f'delimiter argument is malformed: \"{delimiter}\"')
if not self.hasProducts():
return '🍃 Analogue store is empty'
productStrings = list()
for product in self.__products:
if inStockProductsOnly:
if product.inStock():
productStrings.append(product.toStr(
includePrice = includePrices,
includeStockInfo = False
))
else:
productStrings.append(product.toStr(
includePrice = includePrices,
includeStockInfo = True
))
if inStockProductsOnly and not utils.hasItems(productStrings):
return '🍃 Analogue store has nothing in stock'
productsString = delimiter.join(productStrings)
if inStockProductsOnly:
return f'Analogue products in stock: {productsString}'
else:
return f'Analogue products: {productsString}'
class AnalogueStoreRepository():
def __init__(
self,
storeUrl: str = 'https://www.analogue.co/store',
cacheTimeDelta: timedelta = timedelta(hours = 1)
):
if not utils.isValidUrl(storeUrl):
raise ValueError(f'storeUrl argument is malformed: \"{storeUrl}\"')
elif cacheTimeDelta is None:
raise ValueError(f'cacheTimeDelta argument is malformed: \"{cacheTimeDelta}\"')
self.__storeUrl = storeUrl
self.__cacheTime = datetime.utcnow() - cacheTimeDelta
self.__cacheTimeDelta = cacheTimeDelta
self.__storeStock = None
def fetchStoreStock(self) -> AnalogueStoreStock:
if self.__cacheTime + self.__cacheTimeDelta < datetime.utcnow() or self.__storeStock is None:
self.__storeStock = self.__refreshStoreStock()
self.__cacheTime = datetime.utcnow()
return self.__storeStock
def getStoreUrl(self) -> str:
return self.__storeUrl
def __refreshStoreStock(self) -> AnalogueStoreStock:
print(f'Refreshing Analogue store stock... ({utils.getNowTimeText()})')
rawResponse = None
try:
rawResponse = requests.get(url = self.__storeUrl, timeout = utils.getDefaultTimeout())
except (ConnectionError, HTTPError, MaxRetryError, NewConnectionError, Timeout) as e:
print(f'Exception occurred when attempting to fetch Analogue store stock: {e}')
raise RuntimeError(f'Exception occurred when attempting to fetch Analogue store stock: {e}')
htmlTree = html.fromstring(rawResponse.content)
if htmlTree is None:
print(f'Analogue store\'s htmlTree is malformed: \"{htmlTree}\"')
raise ValueError(f'Analogue store\'s htmlTree is malformed: \"{htmlTree}\"')
productTrees = htmlTree.find_class('store_product-header__1rLY-')
if not utils.hasItems(productTrees):
print(f'Analogue store\'s productTrees list is malformed: \"{productTrees}\"')
raise ValueError(f'Analogue store\'s productTrees list is malformed: \"{productTrees}\"')
products = list()
for productTree in productTrees:
productTrees = productTree.find_class('store_title__3eCzb')
if productTrees is None or len(productTrees) != 1:
continue
nameAndPrice = utils.cleanStr(productTrees[0].text_content())
if len(nameAndPrice) == 0:
continue
elif '8BitDo'.lower() in nameAndPrice.lower():
# don't show 8BitDo products in the final stock listing
continue
name = None
price = None
indexOfDollar = nameAndPrice.find('$')
if indexOfDollar == -1:
name = utils.cleanStr(nameAndPrice)
else:
name = utils.cleanStr(nameAndPrice[0:indexOfDollar])
price = utils.cleanStr(nameAndPrice[indexOfDollar:len(nameAndPrice)])
if name[len(name) - 1] == '1':
name = name[0:len(name) - 1]
productType = AnalogueProductType.fromStr(name)
inStock = True
outOfStockElement = productTree.find_class('button_Disabled__2CEbR')
if utils.hasItems(outOfStockElement):
inStock = False
products.append(AnalogueStoreEntry(
productType = productType,
inStock = inStock,
name = name,
price = price
))
return AnalogueStoreStock(products = products)