fpx-engine 0.1.0__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.
fpx/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ import logging
2
+ from .main import FunPayTools
3
+ from .classes.runner.subclasses.handler import Router
4
+
5
+ logger = logging.getLogger('fpx')
6
+ logger.addHandler(logging.NullHandler())
fpx/api/client.py ADDED
@@ -0,0 +1,134 @@
1
+ import json
2
+ import urllib.parse
3
+
4
+
5
+ class FunPayClient:
6
+ def __init__(self, account, http_client):
7
+ self._account = account
8
+ self.client = http_client
9
+
10
+ async def get_chats_page(self) -> str:
11
+ r = await self._account._request_engine.execute('GET', '/chat/')
12
+ return r.text
13
+
14
+ async def get_finance_page(self) -> str:
15
+ r = await self._account._request_engine.execute('GET', '/account/balance')
16
+ return r.text
17
+
18
+ async def send_message_request(self, node_name, last_msg, text):
19
+ request_data = {
20
+ "action": "chat_message",
21
+ "data": {
22
+ "node": node_name,
23
+ "last_message": last_msg,
24
+ "content": text
25
+ }
26
+ }
27
+ payload = {
28
+ 'request': json.dumps(request_data)
29
+ }
30
+ headers = {
31
+ "X-Requested-With": "XMLHttpRequest",
32
+ "Referer": f"https://funpay.com/chat/?node={node_name.split('-')[-1]}"
33
+ }
34
+ r = await self._account._request_engine.execute('POST', '/runner/', data=payload, headers=headers)
35
+ return r.json()
36
+
37
+ async def get_current_chat(self, chat_id):
38
+ r = await self._account._request_engine.execute('GET', f'/chat/?node={chat_id}')
39
+ return r.text
40
+
41
+ async def get_user_profile(self, user_id):
42
+ r = await self._account._request_engine.execute('GET', f'/users/{user_id}/')
43
+ return r.text
44
+
45
+ async def lot_menu_by_category(self, category_id):
46
+ r = await self._account._request_engine.execute('GET', f'/lots/{category_id}/trade')
47
+ return r.text
48
+
49
+ async def get_main_menu(self):
50
+ r = await self._account._request_engine.execute('GET', '/')
51
+ return r.text
52
+
53
+ async def raise_lot(self, node_id, game_id):
54
+ payload = {
55
+ 'game_id': game_id,
56
+ 'node_id': node_id
57
+ }
58
+ headers = {
59
+ "X-Requested-With": "XMLHttpRequest"
60
+ }
61
+ r = await self._account._request_engine.execute('POST', '/lots/raise', data=payload, headers=headers)
62
+ if "application/json" in r.headers.get("Content-Type", ""):
63
+ response = r.json()
64
+ return response.get('msg')
65
+ else:
66
+ return {"error": "not_json", "status": r.status_code}
67
+
68
+ async def get_lot_info(self, lot_id):
69
+ r = await self._account._request_engine.execute('GET', f'/lots/offer?id={lot_id}')
70
+ return r.text
71
+
72
+ async def get_my_sells(self):
73
+ r = await self._account._request_engine.execute('GET', '/orders/trade')
74
+ return r.text
75
+
76
+ async def refund_order(self, csrf_token, order_id):
77
+ url = f'/orders/refund'
78
+ payload = {
79
+ 'id': order_id
80
+ }
81
+ r = await self._account._request_engine.execute('POST', url, data=payload)
82
+ return r
83
+
84
+ async def get_order_info(self, order_id):
85
+ r = await self._account._request_engine.execute('GET', f'/orders/{order_id}/')
86
+ return r.text
87
+
88
+ async def get_lot_editor_data(self, lot_id):
89
+ r = await self._account._request_engine.execute('GET', f'/lots/offerEdit?offer={lot_id}')
90
+ return r.text
91
+
92
+ async def edit_lot(self, lot, active=None):
93
+ payload = {
94
+ 'form_created_at': lot.form_created_at,
95
+ 'offer_id': lot.offer_id,
96
+ 'node_id': lot.node_id,
97
+ 'location': lot.location if lot.location else 'offer',
98
+ 'deleted': lot.deleted,
99
+ }
100
+ payload.update(lot.fields)
101
+ payload.pop('query', None)
102
+ if active is not None:
103
+ if active:
104
+ payload['active'] = 'on'
105
+ else:
106
+ payload.pop('active', None)
107
+ headers = {
108
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
109
+ 'X-Requested-With': 'XMLHttpRequest',
110
+ 'Referer': f'https://funpay.com/lots/offerEdit?node={lot.node_id}&offer={lot.offer_id}&location=offer',
111
+ }
112
+ r = await self._account._request_engine.execute('POST', '/lots/offerSave', data=payload, headers=headers)
113
+ return r
114
+
115
+ async def answer_review(self, authorid: str, text: str, orderid: str):
116
+ payload = {
117
+ 'authorId': authorid,
118
+ 'text': text,
119
+ 'rating': '',
120
+ 'orderId': orderid
121
+ }
122
+ headers = {
123
+ "X-Requested-With": "XMLHttpRequest"
124
+ }
125
+ response = await self._account._request_engine.execute('POST', '/orders/review', data=payload, headers=headers)
126
+ return response
127
+
128
+ async def get_chip_category(self, chip_category_id):
129
+ r = await self._account._request_engine.execute('GET', f'/chips/{chip_category_id}/')
130
+ return r.text
131
+
132
+ async def get_lot_category(self, lot_category_id):
133
+ r = await self._account._request_engine.execute('GET', f'/lots/{lot_category_id}/')
134
+ return r.text
fpx/api/parsers.py ADDED
@@ -0,0 +1,373 @@
1
+
2
+ import json
3
+ import logging
4
+
5
+ from bs4 import BeautifulSoup
6
+
7
+ from fpx.models.chat import Chat
8
+ from fpx.models.account import Balance
9
+ from fpx.utils import errors as fpx_err
10
+
11
+ logger = logging.getLogger("fpx.parser")
12
+
13
+ class FunPayParser:
14
+ @staticmethod
15
+ def parse_chats_list(html_content: str) -> list[Chat]:
16
+ soup = BeautifulSoup(html_content, 'html.parser')
17
+ items = soup.find_all('a', class_='contact-item')
18
+ if not items:
19
+ raise fpx_err.FpxNullDataError('На странице не найдено ни одного чата')
20
+ chats = []
21
+ for item in items:
22
+ try:
23
+ href = item.get('href', '')
24
+ node_msg_id = item.get('data-node-msg', '0')
25
+ chat_id = href.split('node=')[-1] if 'node=' in href else ''
26
+ username = item.find('div', class_='media-user-name').text.strip()
27
+ last_msg = item.find('div', class_='contact-item-message').text.strip()
28
+ date = item.find('div', class_='contact-item-time').text.strip()
29
+ is_unread = 'unread' in item.get('class', [])
30
+ chats.append(Chat(
31
+ id=chat_id, node_msg_id=int(node_msg_id), username=username, last_msg=last_msg,
32
+ date=date, link=href, is_unread=is_unread
33
+ ))
34
+ except Exception as e:
35
+ logger.debug(f'Ошибка парсинга отдельного чата: {e}. Пропускаем элемент.')
36
+ continue
37
+ if not chats:
38
+ raise fpx_err.FpxParseError("Не удалось распарсить ни один чат, верстка полностью изменилась, или что-то сломалось.")
39
+ return chats
40
+
41
+ @staticmethod
42
+ def parse_finanses(html_content: str):
43
+ soup = BeautifulSoup(html_content, 'html.parser')
44
+ balances_container = soup.find('span', class_='balances-list')
45
+ if not balances_container:
46
+ raise fpx_err.FpxNullDataError('На странице финансов не найдено модуля баланса')
47
+ values = balances_container.find_all('span', class_='balances-value')
48
+ if not values:
49
+ raise fpx_err.FpxNullDataError('На странице финансов не найдено баланса')
50
+ clean_values = [v.text.strip() for v in values]
51
+ data = {}
52
+ for i in clean_values:
53
+ try:
54
+ value = i.replace('₽', '').replace('$', '').replace('€', '').replace(',', '.').strip()
55
+ num = float(value)
56
+ if '₽' in i: data['rub'] = num
57
+ elif '$' in i: data['usd'] = num
58
+ elif '€' in i: data['eur'] = num
59
+ except Exception as e:
60
+ logger.debug(f'Ошибка парсинга отдельной валюты: {e}. Пропускаем')
61
+ continue
62
+ if not data:
63
+ raise fpx_err.FpxParseError('Не удалось распарсить ни одну валюту, верстка полностью изменилась или что-то сломалось.')
64
+ return Balance(**data)
65
+
66
+ @staticmethod
67
+ def parse_chat(html_content: str):
68
+ soup = BeautifulSoup(html_content, 'html.parser')
69
+ result = {}
70
+ chat_div = soup.find('div', class_='chat')
71
+ body = soup.find('body')
72
+ if not chat_div or not body:
73
+ raise fpx_err.FpxNullDataError('На странице чата не найден блок переписки или тег body')
74
+
75
+ # парсинг чатов
76
+ chats = soup.find_all('div', class_='chat-msg-item chat-msg-with-head')
77
+ if chats:
78
+ try:
79
+ chat = chats[-1]
80
+ res = {}
81
+ res['is_system'] = False
82
+ res['message'] = chat.find('div', class_='chat-msg-text').get_text(separator='\n').strip()
83
+ author = chat.find('a', class_='chat-msg-author-link')
84
+ if not author:
85
+ res['sender'] = chat.find('span', class_='chat-msg-author-label')
86
+ if res['sender']:
87
+ res['sender'] = res['sender'].get_text(strip=True)
88
+ if res['sender'] == 'оповещение':
89
+ res['sender'] = 'FunPay'
90
+ res['is_system'] = True
91
+ else:
92
+ res['sender'] = author.get_text(strip=True)
93
+ result['last_message'] = res
94
+ except Exception as e:
95
+ logger.debug(f"Не удалось распарсить последнее сообщение в чате: {e}")
96
+ else:
97
+ result['last_message'] = None
98
+
99
+ # парсинг тех.данных
100
+ try:
101
+ result['data-name'] = chat_div.get('data-name', '')
102
+ app_data_str = body.get('data-app-data', '{}')
103
+ app_data = json.loads(app_data_str)
104
+ result['csrf-token'] = app_data.get('csrf-token', '')
105
+ result['user-id'] = app_data.get('userId', '')
106
+ except Exception as e:
107
+ logger.debug(f"Ошибка извлечения системных данных чата: {e}")
108
+ raise fpx_err.FpxParseError("Не удалось распарсить системные метаданные чата (CSRF/User ID)")
109
+ return result
110
+
111
+ @staticmethod
112
+ def parse_profile(html_content: str):
113
+ soup = BeautifulSoup(html_content, 'html.parser')
114
+ offer_list = soup.find_all('div', class_='offer')
115
+ review_list = soup.find_all('div', class_='review-compiled-review')
116
+ if not offer_list or not review_list:
117
+ logger.debug('На странице профиля не найден блок категорий или блок отзывов. Возможно ошибка или их просто не существует')
118
+ category_ids = set()
119
+ lots = []
120
+ for offer in offer_list:
121
+ links = offer.find_all('a', href=True)
122
+ if not links:
123
+ logger.debug('В блоке категории не найдено лотов. Возможно ошибка/Их просто не существует')
124
+ for link in links:
125
+ try:
126
+ href = link['href']
127
+ if '/lots/' in href and 'id=' not in href:
128
+ cat_id = href.strip('/').split('/')[-1]
129
+ if cat_id.isdigit():
130
+ category_ids.add(cat_id)
131
+ if 'id=' in href:
132
+ lot_id = href.split('id=')[-1].split('&')[0]
133
+ name_tag = link.find('div', class_='tc-desc-text')
134
+ name = name_tag.get_text(strip=True) if name_tag else "Unknown"
135
+ lots.append({'name': name, 'id': lot_id})
136
+ except Exception as e:
137
+ logger.debug(f'Ошибка парсинга отдельной категории: {e}')
138
+ continue
139
+ if not lots:
140
+ logger.debug('Не удалось распарсить ни один лот. Возможно изменилась вёрстка/у вас просто нет лотов.')
141
+ reviews = []
142
+ for review in review_list:
143
+ try:
144
+ rev = {}
145
+ rev['text'] = review.find('div', class_='review-item-text').get_text(strip=True)
146
+ rate_div = review.find('div', class_='rating').find('div', class_=True)
147
+ rating = rate_div['class'][0]
148
+ rev['stars'] = int(rating.replace('rating', ''))
149
+ rev['author'] = review.find('div', class_='media-user-name').get_text(strip=True)
150
+ rev['detail'] = review.find('div', class_='review-item-detail').get_text(strip=True)
151
+ reviews.append(rev)
152
+ except Exception as e:
153
+ logger.debug(f'При парсинге конкретного отзыва возникла ошибка: {e}')
154
+ continue
155
+ if not reviews:
156
+ logger.debug('Не удалось распарсить ни один отзыв, возможно их просто нет/возникла какая-то ошибка')
157
+ return {'category-ids': list(category_ids), 'lots': lots, 'reviews': reviews}
158
+
159
+ @staticmethod
160
+ def parse_lot_menu(html_content: str):
161
+ soup = BeautifulSoup(html_content, 'html.parser')
162
+ button = soup.find('button', class_='js-lot-raise')
163
+ if button:
164
+ data_game = button.get('data-game')
165
+ if data_game:
166
+ return data_game
167
+ raise fpx_err.FpxParseError('Атрибут data-game не найден внутри кнопки поднятия')
168
+ raise fpx_err.FpxNullDataError('На странице лотов не найдена кнопка для поднятия (возможно, у вас нет лотов)')
169
+
170
+ @staticmethod
171
+ def parse_main_menu(html_content: str):
172
+ soup = BeautifulSoup(html_content, 'html.parser')
173
+ user_link = soup.find('a', class_='user-link-dropdown')
174
+ result = {}
175
+ if user_link:
176
+ href = user_link.get('href', '')
177
+ user_id = href.strip('/').split('/')[-1]
178
+ if user_id.isdigit():
179
+ result['user-id'] = user_id
180
+ result['username'] = soup.find('div', class_='user-link-name').get_text(strip=True)
181
+ else:
182
+ raise fpx_err.FpxParseError('Не удалось извлечь цифровой ID юзера, возможно слетела сессия или изменилась вёрстка')
183
+ else:
184
+ raise fpx_err.FpxNullDataError('На главной странице не найдено информации о юзере. Возможно слетела сессия')
185
+ body = soup.find('body')
186
+ if not body:
187
+ raise fpx_err.FpxNullDataError('Тело главной страницы (body) не найдено')
188
+ try:
189
+ app_data_str = body.get('data-app-data', '{}')
190
+ app_data = json.loads(app_data_str)
191
+ result['csrf-token'] = app_data.get('csrf-token', '')
192
+ except Exception:
193
+ raise fpx_err.FpxParseError('Не удалось распарсить csrf_token из data-app-data.')
194
+ return result
195
+
196
+ @staticmethod
197
+ def parse_current_lot_menu(html_content):
198
+ result = {}
199
+ soup = BeautifulSoup(html_content, 'html.parser')
200
+ param_items = soup.find_all('div', class_='param-item')
201
+ if not param_items:
202
+ raise fpx_err.FpxNullDataError('Страница лота не найдена. Возможно, указана инвалидная ссылка или лот был удалён.')
203
+ else:
204
+ descriptions = {}
205
+ for item in param_items:
206
+ try:
207
+ header = item.find('h5').get_text(strip=True)
208
+ text = item.find('div').get_text(separator='\n', strip=True)
209
+ descriptions[header] = text
210
+ except Exception as e:
211
+ logger.debug(f'При парсинге конкретного объекта произошла ошибка: {e}')
212
+ result['short_desc'] = descriptions.get('Краткое описание')
213
+ result['description'] = descriptions.get('Подробное описание')
214
+ option = soup.find('option', value='21') or soup.find('option', attrs={'data-content': True})
215
+ if option:
216
+ inner_html = option.get('data-content')
217
+ if inner_html:
218
+ inner_soup = BeautifulSoup(inner_html, 'html.parser')
219
+ price_span = inner_soup.find('span', class_='payment-value')
220
+ if price_span:
221
+ raw_price = price_span.get_text(strip=True).replace(',', '.')
222
+ cleaned_price = "".join(c for c in raw_price if c.isdigit() or c == '.')
223
+ try:
224
+ result['price'] = float(cleaned_price) if cleaned_price else 0.0
225
+ except ValueError:
226
+ result['price'] = 0.0
227
+ return result
228
+ raise fpx_err.FpxNullDataError('Не удалось найти цену в скрытых атрибутах выбора оплаты.')
229
+
230
+ @staticmethod
231
+ def parse_my_sells(html_content):
232
+ result = []
233
+ soup = BeautifulSoup(html_content, 'html.parser')
234
+ tc_items = soup.find_all('a', class_='tc-item')
235
+ if not tc_items:
236
+ raise fpx_err.FpxNullDataError('На странице продаж не найдено объектов(tc-item)')
237
+ for item in tc_items:
238
+ try:
239
+ pre_result = {}
240
+ order_tag = item.find('div', class_='tc-order')
241
+ pre_result['order-id'] = order_tag.get_text(strip=True).replace('#', '') if order_tag else "Unknown"
242
+ time_tag = item.find('div', class_='tc-date-time')
243
+ pre_result['order-time'] = time_tag.get_text(strip=True) if time_tag else ""
244
+ status_tag = item.find('div', class_='tc-status')
245
+ pre_result['status'] = status_tag.get_text(strip=True) if status_tag else "Unknown"
246
+ client_tag = item.find('span', class_='pseudo-a') or item.find('div', class_='tc-user')
247
+ pre_result['client-name'] = client_tag.get_text(strip=True) if client_tag else "Unknown"
248
+ price_tag = item.find('div', class_='tc-price')
249
+ if price_tag:
250
+ raw_price = price_tag.get_text(strip=True).replace(',', '.')
251
+ cleaned_price = "".join(c for c in raw_price if c.isdigit() or c == '.')
252
+ pre_result['price'] = float(cleaned_price) if cleaned_price else 0.0
253
+ else:
254
+ pre_result['price'] = 0.0
255
+ order_desc = item.find('div', class_='order-desc')
256
+ if order_desc:
257
+ divs = order_desc.find_all('div')
258
+ pre_result['name'] = divs[0].get_text(strip=True) if len(divs) > 0 else "Unknown"
259
+ pre_result['category'] = divs[1].get_text(strip=True) if len(divs) > 1 else "Unknown"
260
+ else:
261
+ pre_result['name'] = "Unknown"
262
+ pre_result['category'] = "Unknown"
263
+ result.append(pre_result)
264
+ except Exception as e:
265
+ logger.debug(f'При парсинге конкретного объекта произошла ошибка: {e}')
266
+ continue
267
+ if not result:
268
+ raise fpx_err.FpxParseError('При парсинге не найдено ни одной продажи')
269
+ return result
270
+
271
+ @staticmethod
272
+ def parse_order_page(html_content):
273
+ soup = BeautifulSoup(html_content, 'html.parser')
274
+ result = {}
275
+ result['review'] = {}
276
+ try:
277
+ header = soup.find('h1', class_='page-header')
278
+ if not header:
279
+ raise fpx_err.FpxNullDataError('Страница заказа не найдена, возможно, указан неверный ID или слетела сессия.')
280
+ spans = header.find_all('span')
281
+ if not spans:
282
+ raise fpx_err.FpxParseError('Не удалось распарсить статус заказа из заголовка.')
283
+ result['status'] = " / ".join([span.get_text(strip=True) for span in spans])
284
+ review_container = soup.find('div', class_='review-container')
285
+ if review_container:
286
+ try:
287
+ text_tag = review_container.find('div', class_='review-item-text')
288
+ result['review']['text'] = text_tag.get_text(strip=True) if text_tag else ''
289
+ raw_stars = review_container.get('data-rating', '0')
290
+ result['review']['stars'] = int(raw_stars) if raw_stars.isdigit() else 0
291
+ answer_div = review_container.find('div', class_='review-item-answer')
292
+ if answer_div:
293
+ text_container = answer_div.find('div')
294
+ result['review']['answer'] = text_container.get_text('\n', strip=True) if text_container else ''
295
+ else:
296
+ result['review']['answer'] = ''
297
+ except Exception as e:
298
+ logger.debug(f'Ошибка парсинга деталей существующего отзыва: {e}')
299
+ result['review'].update({'text': '', 'stars': 0, 'answer': ''})
300
+ else:
301
+ result['review'] = {'text': '', 'stars': 0, 'answer': ''}
302
+ except fpx_err.FpxNullDataError:
303
+ raise
304
+ except fpx_err.FpxParseError:
305
+ raise
306
+ except Exception as e:
307
+ raise fpx_err.FpxParseError(f'Ошибка парсинга страницы заказа: {e}')
308
+ return result
309
+
310
+ @staticmethod
311
+ def parse_edit_lot_page(html_content):
312
+ soup = BeautifulSoup(html_content, 'html.parser')
313
+ result = {}
314
+ hidden_inputs = soup.find_all('input', type='hidden')
315
+ if not hidden_inputs:
316
+ raise fpx_err.FpxNullDataError('Не найдено вводных данных в редакторе лота. Возможно слетела сессия')
317
+ result = {tag.get('name'): tag.get('value', '') for tag in hidden_inputs if tag.get('name')}
318
+ selects = soup.find_all('select')
319
+ if not selects:
320
+ raise fpx_err.FpxNullDataError('Ни одна выборка в редакторе лотов не найдена. Проверьте актуальность сессии')
321
+ for s in selects:
322
+ try:
323
+ name = s.get('name')
324
+ if not name: continue
325
+ selected_option = s.find('option', selected=True)
326
+ if selected_option:
327
+ result[name] = selected_option.get('value', '')
328
+ else:
329
+ first_opt = s.find('option')
330
+ result[name] = first_opt.get('value', '') if first_opt else ''
331
+ except Exception as e:
332
+ logger.debug(f'При парсинге конкретной выборки произошла ошибка: {e}')
333
+ inputs = soup.find_all('input', class_='form-control')
334
+ if not inputs:
335
+ logger.debug('Ни одно поле для ввода в редакторе лотов не найдено. Возможно всё в порядке')
336
+ else:
337
+ for i in inputs:
338
+ try:
339
+ name = i.get('name')
340
+ if name:
341
+ result[name] = i.get('value', '')
342
+ except Exception as e:
343
+ logger.debug(f'При парсинге конкретного поля для ввода произошла ошибка: {e}')
344
+ textareas = soup.find_all('textarea')
345
+ if not textareas:
346
+ logger.debug('При парсинге редактора лотов не найдено ни одного текстового поля. Возможно всё в порядке')
347
+ else:
348
+ for t in textareas:
349
+ try:
350
+ name = t.get('name')
351
+ if name:
352
+ result[name] = t.string if t.string else ''
353
+ except Exception as e:
354
+ logger.debug(f'При парсинге конкретного текстового поля произошла ошибка: {e}')
355
+ return result
356
+
357
+ @staticmethod
358
+ def parse_category_page(html_content):
359
+ soup = BeautifulSoup(html_content, 'html.parser')
360
+ lots = soup.select('a.tc-item:not(.offer-promo)')
361
+ if not lots:
362
+ logger.debug('В категории не найдено лотов, возможно их нет или сайт недоступен')
363
+ result = {}
364
+ try:
365
+ lot = lots[0]
366
+ text_price = lot.find('div', class_='tc-price').get_text(strip=True)
367
+ result['price'] = float(''.join(c for c in text_price if c.isdigit() or c in '.,').replace(',', '.'))
368
+ result['offer_id'] = lot.get('href', '').split('=')[-1]
369
+ except Exception as e:
370
+ raise fpx_err.FpxParseError('Ошибка при парсинге категории')
371
+ if not result:
372
+ raise fpx_err.FpxParseError('У лота не найдено параметров')
373
+ return result
@@ -0,0 +1,34 @@
1
+
2
+ from fpx.api.client import FunPayClient
3
+ from fpx.api.parsers import FunPayParser
4
+ from fpx.classes.account.subclasses.chat import ChatManager
5
+ from fpx.classes.account.subclasses.addons import AddonsManager
6
+ from fpx.classes.account.subclasses.profile import ProfileManager
7
+ from fpx.classes.account.subclasses.order import OrderManager
8
+ from fpx.classes.account.subclasses.lot import LotManager
9
+ from fpx.classes.account.subclasses.editor import FunPayEditor
10
+ from fpx.classes.account.subclasses.review import ReviewManager
11
+ from fpx.classes.account.subclasses.category import CategoryManager
12
+ from fpx.middlewares.request_engine import RequestEngine
13
+
14
+ class Account:
15
+ '''
16
+ Взаимодействует с аккаунтом.
17
+ '''
18
+ def __init__(self, client):
19
+ self.http_client = client
20
+ self.client = FunPayClient(self, self.http_client)
21
+ self._request_engine = RequestEngine(self, self.http_client)
22
+ self.parser = FunPayParser()
23
+ self.username = None
24
+ self.user_id = None
25
+ self._csrf_token = None
26
+ self._node_names = {}
27
+ self.chat = ChatManager(self)
28
+ self.addons = AddonsManager(self)
29
+ self.profile = ProfileManager(self)
30
+ self.order = OrderManager(self)
31
+ self.lot = LotManager(self)
32
+ self.editor = FunPayEditor(self)
33
+ self.review = ReviewManager(self)
34
+ self.category = CategoryManager(self)
@@ -0,0 +1,20 @@
1
+
2
+
3
+
4
+ class AddonsManager:
5
+ def __init__(self, account):
6
+ self.account = account
7
+
8
+ async def get_game_id(self, category_id: str):
9
+ """
10
+ Получает game_id.
11
+
12
+ Args:
13
+ category_id (str | int): ID подкатегории.
14
+
15
+ Returns:
16
+ str | int: ID игры.
17
+ """
18
+ html = await self.account.client.lot_menu_by_category(category_id)
19
+ data = self.account.parser.parse_lot_menu(html)
20
+ return data
@@ -0,0 +1,39 @@
1
+
2
+
3
+ from fpx.models.lots import CategoryLastLot
4
+
5
+ class CategoryManager:
6
+ def __init__(self, account):
7
+ self._account = account
8
+
9
+ async def get_lot_category_last_lot(self, lot_category_id):
10
+ '''
11
+ Находит самый дешевый лот в категории.
12
+
13
+ Args:
14
+ lot_category_id (int | str): ID категории лота
15
+
16
+ Returns:
17
+ CategoryLastLot: Объект, содержащий в себе:
18
+ - price (float): Цена лота
19
+ - offer_id (str): ID лота
20
+ '''
21
+ html = await self._account.client.get_lot_category(lot_category_id)
22
+ data = self._account.parser.parse_category_page(html)
23
+ return CategoryLastLot(**data)
24
+
25
+ async def get_chip_category_last_lot(self, chip_category_id):
26
+ '''
27
+ Находит самый дешевый лот в категории.
28
+
29
+ Args:
30
+ lot_category_id (int | str): ID категории лота
31
+
32
+ Returns:
33
+ CategoryLastLot: Объект, содержащий в себе:
34
+ - price (float): Цена лота
35
+ - offer_id (str): ID лота
36
+ '''
37
+ html = await self._account.client.get_chip_category(chip_category_id)
38
+ data = self._account.parser.parse_category_page(html)
39
+ return CategoryLastLot(**data)