Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion funpaybotengine/client/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
PrivateChatPreview,
TransactionPreviewsBatch,
CurrentlyViewingOfferInfo,
RaiseOffersResponse
RaiseOffersResponse,
WithdrawCalcResult,
)
from funpaybotengine.utils import (
random_runner_tag,
Expand All @@ -44,6 +45,7 @@
MuteChat,
CalcChips,
CheckBanned,
WithdrawCalc,
GetChatPage,
GetMainPage,
RaiseOffers,
Expand Down Expand Up @@ -463,6 +465,36 @@ async def calc_lots(self, subcategory_id: int, price: float) -> CalcResult:
await CalcLots(subcategory_id=subcategory_id, price=price).execute(self)
).response_obj

async def calc_withdraw(
self,
currency_id: str,
ext_currency_id: str,
wallet: str,
amount_int: float | None = None,
amount_ext: float | None = None,
) -> WithdrawCalcResult:
"""
Calculate a withdrawal amount.

Exactly one of ``amount_int`` / ``amount_ext`` must be specified:
FunPay calculates the other one and returns it.

:param currency_id: balance currency ID (e.g. ``'rub'``).
:param ext_currency_id: withdrawal method ID (e.g. ``'card_rub'``, ``'fps'``).
:param wallet: card / phone / wallet number to withdraw to.
:param amount_int: amount to debit from the FunPay balance.
:param amount_ext: amount to credit to the wallet.
"""
return (
await WithdrawCalc(
currency_id=currency_id,
ext_currency_id=ext_currency_id,
wallet=wallet,
amount_int=amount_int,
amount_ext=amount_ext,
).execute(self)
).response_obj

async def logout(self) -> bool:
if self._logout_token is None:
await self.update()
Expand Down
1 change: 1 addition & 0 deletions funpaybotengine/methods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .get_main_page import *
from .get_purchases import *
from .upload_avatar import *
from .withdraw_calc import *
from .get_2fa_status import *
from .get_offer_page import *
from .get_order_page import *
Expand Down
100 changes: 100 additions & 0 deletions funpaybotengine/methods/withdraw_calc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from __future__ import annotations


__all__ = ('WithdrawCalc',)


import json
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, model_validator
from typing_extensions import Self

from funpaybotengine.types.enums import Language
from funpaybotengine.methods.base import FunPayMethod
from funpaybotengine.client.session import HTTPMethod
from funpaybotengine.types.withdraw import WithdrawCalcResult


if TYPE_CHECKING:
from funpaybotengine.client import Bot, RawResponse


class WithdrawCalc(FunPayMethod[WithdrawCalcResult], BaseModel):
"""
Calculate a withdrawal amount (``https://funpay.com/withdraw/calc``).

Exactly one of ``amount_int`` / ``amount_ext`` must be specified:
FunPay calculates the other one and returns it.

Returns ``funpaybotengine.types.withdraw.WithdrawCalcResult`` obj.
"""

currency_id: str
"""Balance currency ID (e.g. ``'rub'``)."""

ext_currency_id: str
"""Withdrawal method ID (e.g. ``'card_rub'``, ``'fps'``)."""

wallet: str
"""Card / phone / wallet number to withdraw to."""

amount_int: float | None = None
"""Amount to debit from the FunPay balance. Mutually exclusive with ``amount_ext``."""

amount_ext: float | None = None
"""Amount to credit to the wallet. Mutually exclusive with ``amount_int``."""

__model_to_build__ = WithdrawCalcResult

def __init__(
self,
currency_id: str,
ext_currency_id: str,
wallet: str,
amount_int: float | None = None,
amount_ext: float | None = None,
locale: Language | None = None,
) -> None:
super().__init__(
url='withdraw/calc',
method=HTTPMethod.POST,
locale=locale,
expected_status_codes=[200],
headers={'X-Requested-With': 'XMLHttpRequest'},
data=make_data,
allow_anonymous=False,
allow_uninitialized=False,
currency_id=currency_id,
ext_currency_id=ext_currency_id,
wallet=wallet,
amount_int=amount_int,
amount_ext=amount_ext,
)

@model_validator(mode='after')
def _check_amounts(self) -> Self:
if (self.amount_int is None) == (self.amount_ext is None):
raise ValueError(
'Exactly one of `amount_int` / `amount_ext` must be specified: '
'FunPay calculates the one that is not passed.',
)
return self

async def parse_result(self, response: RawResponse[WithdrawCalcResult]) -> dict[str, Any]:
return json.loads(response.raw_response) # type: ignore # always dict


async def make_data(method: WithdrawCalc, bot: Bot) -> dict[str, Any]:
# The amount that must be calculated is not passed at all:
# FunPay returns it in the response.
amount_field = 'amount_int' if method.amount_int is not None else 'amount_ext'
amount_value = method.amount_int if method.amount_int is not None else method.amount_ext

return {
'preview': '1',
'currency_id': method.currency_id,
'ext_currency_id': method.ext_currency_id,
'wallet': method.wallet,
amount_field: amount_value,
}
1 change: 1 addition & 0 deletions funpaybotengine/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
from .messages import *
from .sras import *
from .settings import *
from .withdraw import *
from .categories import *
from .common_page_elements import *
39 changes: 39 additions & 0 deletions funpaybotengine/types/withdraw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations


__all__ = ('WithdrawCalcResult',)


from typing import Any

from pydantic import BaseModel, field_validator

from funpaybotengine.types.base import FunPayObject


class WithdrawCalcResult(FunPayObject, BaseModel):
"""
Represents a result of a withdrawal amount calculation method (``withdraw/calc``).

FunPay calculates only the amount that was **not** passed in the request:
if ``amount_int`` is passed, ``amount_ext`` is calculated, and vice versa.

The field that was passed in the request is not present in the response,
and therefore is always ``None``.
"""

amount_int: float | None = None
"""Amount debited from the FunPay balance, if calculated."""

amount_ext: float | None = None
"""Amount credited to the wallet (i.e. fee already deducted), if calculated."""

@field_validator('amount_int', 'amount_ext', mode='before')
@classmethod
def _validate_amount(cls, value: Any) -> float | None:
if value is None or value == '':
return None
if isinstance(value, str):
normalized = value.replace('\xa0', '').replace(' ', '').replace(',', '.')
return float(normalized)
return float(value)