Skip to content

Commit 15f7d1a

Browse files
committed
v0.1.16 - ChipFields
1 parent b06ae3f commit 15f7d1a

File tree

3 files changed

+138
-19
lines changed

3 files changed

+138
-19
lines changed

FunPayAPI/account.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ def get_subcategory_public_lots(self, subcategory_type: enums.SubCategoryTypes,
308308
if subcategory_type is types.SubCategoryTypes.COMMON:
309309
price = float(tc_price["data-s"])
310310
else:
311-
price = float(tc_price.find("div").text.split()[0])
311+
price = float(tc_price.find("div").text.rsplit(maxsplit=1)[0].replace(" ", ""))
312312
if currency is None:
313313
currency = parse_currency(tc_price.find("span", class_="unit").text)
314314
if self.currency != currency:
@@ -1643,21 +1643,29 @@ def get_chat_by_id(self, chat_id: int, make_request: bool = False) -> types.Chat
16431643
self.add_chats(self.request_chats())
16441644
return self.get_chat_by_id(chat_id)
16451645

1646-
def calc(self, subcategory_type: enums.SubCategoryTypes, subcategory_id: int, price: int | float = 1000):
1646+
def calc(self, subcategory_type: enums.SubCategoryTypes, subcategory_id: int | None = None,
1647+
game_id: int | None = None, price: int | float = 1000):
16471648
if not self.is_initiated:
16481649
raise exceptions.AccountNotInitiatedError()
1649-
headers = {
1650-
"accept": "*/*",
1651-
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1652-
"x-requested-with": "XMLHttpRequest"
1653-
}
1654-
if subcategory_type.COMMON:
1650+
1651+
if subcategory_type == types.SubCategoryTypes.COMMON:
16551652
key = "nodeId"
16561653
type_ = "lots"
1654+
value = subcategory_id
16571655
else:
16581656
key = "game"
16591657
type_ = "chips"
1660-
r = self.method("post", f"{type_}/calc", headers, {key: subcategory_id, "price": price},
1658+
value = game_id
1659+
1660+
assert value is not None
1661+
1662+
headers = {
1663+
"accept": "*/*",
1664+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
1665+
"x-requested-with": "XMLHttpRequest"
1666+
}
1667+
1668+
r = self.method("post", f"{type_}/calc", headers, {key: value, "price": price},
16611669
raise_not_200=True)
16621670
json_resp = r.json()
16631671
if (error := json_resp.get("error")):
@@ -1667,7 +1675,7 @@ def calc(self, subcategory_type: enums.SubCategoryTypes, subcategory_id: int, pr
16671675
methods.append(PaymentMethod(method.get("name"), float(method["price"].replace(" ", "")),
16681676
parse_currency(method.get("unit")), method.get("sort")))
16691677
if "minPrice" in json_resp:
1670-
min_price, min_price_currency = json_resp["minPrice"].rsplirt(" ", maxsplit=1)
1678+
min_price, min_price_currency = json_resp["minPrice"].rsplit(" ", maxsplit=1)
16711679
min_price = float(min_price.replace(" ", ""))
16721680
min_price_currency = parse_currency(min_price_currency)
16731681
else:
@@ -1720,12 +1728,24 @@ def get_lot_fields(self, lot_id: int) -> types.LotFields:
17201728
float(result["price"]), None, types.Currency.UNKNOWN, currency)
17211729
return types.LotFields(lot_id, result, subcategory, currency, calc_result)
17221730

1723-
def save_lot(self, lot_fields: types.LotFields):
1731+
def get_chip_fields(self, subcategory_id: int) -> types.ChipFields:
1732+
if not self.is_initiated:
1733+
raise exceptions.AccountNotInitiatedError()
1734+
headers = {}
1735+
response = self.method("get", f"chips/{subcategory_id}/trade", headers, {}, raise_not_200=True)
1736+
1737+
html_response = response.content.decode()
1738+
bs = BeautifulSoup(html_response, "lxml")
1739+
result = {field["name"]: field.get("value") or "" for field in bs.find_all("input") if field["name"] != "query"}
1740+
result.update({field["name"]: "on" for field in bs.find_all("input", {"type": "checkbox"}, checked=True)})
1741+
return types.ChipFields(self.id, subcategory_id, result)
1742+
1743+
def save_offer(self, offer_fields: types.LotFields | types.ChipFields):
17241744
"""
17251745
Сохраняет лот на FunPay.
17261746
1727-
:param lot_fields: объект с полями лота.
1728-
:type lot_fields: :class:`FunPayAPI.types.LotFields`
1747+
:param offer_fields: объект с полями лота.
1748+
:type offer_fields: :class:`FunPayAPI.types.LotFields`
17291749
"""
17301750
if not self.is_initiated:
17311751
raise exceptions.AccountNotInitiatedError()
@@ -1734,19 +1754,32 @@ def save_lot(self, lot_fields: types.LotFields):
17341754
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
17351755
"x-requested-with": "XMLHttpRequest",
17361756
}
1737-
lot_fields.csrf_token = self.csrf_token
1738-
fields = lot_fields.renew_fields().fields
1739-
fields["location"] = "trade"
1757+
offer_fields.csrf_token = self.csrf_token
17401758

1741-
response = self.method("post", "lots/offerSave", headers, fields, raise_not_200=True)
1759+
if isinstance(offer_fields, types.LotFields):
1760+
id_ = offer_fields.lot_id
1761+
fields = offer_fields.renew_fields().fields
1762+
fields["location"] = "trade"
1763+
api_method = "lots/offerSave"
1764+
else:
1765+
id_ = offer_fields.subcategory_id
1766+
fields = offer_fields.renew_fields().fields
1767+
api_method = "chips/saveOffers"
1768+
response = self.method("post", api_method, headers, fields, raise_not_200=True)
17421769
json_response = response.json()
17431770
errors_dict = {}
17441771
if (errors := json_response.get("errors")) or json_response.get("error"):
17451772
if errors:
17461773
for k, v in errors:
17471774
errors_dict.update({k: v})
17481775

1749-
raise exceptions.LotSavingError(response, json_response.get("error"), lot_fields.lot_id, errors_dict)
1776+
raise exceptions.LotSavingError(response, json_response.get("error"), id_, errors_dict)
1777+
1778+
def save_chip(self, chip_fields: types.ChipFields):
1779+
self.save_offer(chip_fields)
1780+
1781+
def save_lot(self, lot_fields: types.LotFields):
1782+
self.save_offer(lot_fields)
17501783

17511784
def delete_lot(self, lot_id: int) -> None:
17521785
"""

FunPayAPI/types.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
В данном модуле описаны все типы пакета FunPayAPI
33
"""
44
from __future__ import annotations
5+
6+
import re
57
from typing import Literal, overload, Optional
68

79
import FunPayAPI.common.enums
@@ -783,6 +785,90 @@ def renew_fields(self) -> LotFields:
783785
return self
784786

785787

788+
class ChipOffer:
789+
def __init__(self, lot_id: str, active: bool = False, server: str | None = None,
790+
side: str | None = None, price: float | None = None, amount: int | None = None):
791+
self.lot_id = lot_id
792+
self.active = active
793+
self.server = server
794+
self.side = side
795+
self.price = price
796+
self.amount = amount
797+
798+
@property
799+
def key(self):
800+
s = "".join([f"[{i}]" for i in self.lot_id.split("-")[3:]])
801+
return f"offers{s}"
802+
803+
804+
class ChipFields:
805+
def __init__(self, account_id: int, subcategory_id: int, fields: dict[str, str]):
806+
self.subcategory_id = subcategory_id
807+
self.__fields = fields
808+
809+
self.min_sum = float(i) if (i := self.__fields.get("options[chip_min_sum]")) else None
810+
self.account_id: int = account_id
811+
"""ID аккаунта FunPay"""
812+
self.game_id = int(self.__fields.get("game"))
813+
"""ID игры"""
814+
self.csrf_token: str | None = self.__fields.get("csrf_token")
815+
"""CSRF-токен"""
816+
817+
self.chip_offers: dict[str, ChipOffer] = {}
818+
self.__parse_offers()
819+
820+
@property
821+
def fields(self) -> dict[str, str]:
822+
"""
823+
Возвращает все поля лота в виде словаря.
824+
825+
:return: все поля лота в виде словаря.
826+
:rtype: :obj:`dict` {:obj:`str`: :obj:`str`}
827+
"""
828+
return self.__fields
829+
830+
def renew_fields(self) -> ChipFields:
831+
"""
832+
Обновляет :py:obj:`~__fields` (возвращается в методе :meth:`FunPayAPI.types.ChipFields.get_fields`),
833+
основываясь на свойствах экземпляра.
834+
Необходимо вызвать перед сохранением лота на FunPay после изменения любого свойства экземпляра.
835+
836+
:return: экземпляр класса :class:`FunPayAPI.types.ChipFields` с новыми полями лота.
837+
:rtype: :class:`FunPayAPI.types.ChipFields`
838+
"""
839+
self.__fields["game"] = str(self.game_id)
840+
self.__fields["chip"] = str(self.subcategory_id)
841+
self.__fields["options[chip_min_sum]"] = str(self.min_sum) if self.min_sum is not None else ""
842+
self.__fields["csrf_token"] = self.csrf_token
843+
for chip_offer in self.chip_offers.values():
844+
key = chip_offer.key
845+
self.__fields[f"{key}[amount]"] = str(chip_offer.amount) if chip_offer.amount is not None else ""
846+
self.__fields[f"{key}[price]"] = str(chip_offer.price) if chip_offer.price is not None else ""
847+
if chip_offer.active:
848+
self.__fields[f"{key}[active]"] = "on"
849+
else:
850+
self.__fields.pop(f"{key}[active]", None)
851+
return self
852+
853+
def __parse_offers(self):
854+
for k, v in self.__fields.items():
855+
if not k.startswith("offers"):
856+
continue
857+
nums = re.findall(r'\d+', k)
858+
key = "-".join(list(map(str, nums)))
859+
offer_id = f"{self.account_id}-{self.game_id}-{self.subcategory_id}-{key}"
860+
if offer_id not in self.chip_offers:
861+
self.chip_offers[offer_id] = ChipOffer(offer_id)
862+
chip_offer = self.chip_offers[offer_id]
863+
field = k.split("[")[-1].rstrip("]")
864+
if field == "active":
865+
chip_offer.active = v == "on"
866+
elif field == "price":
867+
chip_offer.price = float(v) if v else None
868+
elif field == "amount":
869+
chip_offer.amount = int(v) if v else None
870+
871+
786872
class LotPage:
787873
"""
788874
Класс, описывающий поля лота со страницы лота (https://funpay.com/lots/offer?id=XXXXXXXXXX).

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
......................','.",`............ ..................
5757
............................................................"""
5858

59-
VERSION = "0.1.15.19"
59+
VERSION = "0.1.16"
6060

6161
Utils.cardinal_tools.set_console_title(f"FunPay Cardinal v{VERSION}")
6262

0 commit comments

Comments
 (0)