diff --git a/funpaybotengine/client/bot.py b/funpaybotengine/client/bot.py index c1cf20c..363d833 100644 --- a/funpaybotengine/client/bot.py +++ b/funpaybotengine/client/bot.py @@ -21,6 +21,7 @@ CalcResult, OfferFields, Subcategory, + ReviewsBatch, RunnerResponse, TransactionFilter, OrderPreviewsBatch, @@ -43,6 +44,7 @@ GetSales, MuteChat, CalcChips, + GetReviews, CheckBanned, GetChatPage, GetMainPage, @@ -730,6 +732,33 @@ async def get_purchases( return (await method.execute(self)).response_obj + async def get_reviews( + self, + user_id: int, + from_review_id: str = '', + filter: str = '', + ) -> ReviewsBatch: + """ + Fetch a batch of reviews left for the given user. + + If ``from_review_id`` is provided, the method retrieves reviews after the + specified review ID, enabling pagination. + + :param user_id: ID of the user whose reviews to fetch. + :param from_review_id: Optional. The review ID to start pagination from. + :param filter: Optional. Rating filter: ``''`` for all reviews, + or ``'1'`` ... ``'5'`` to only include reviews with the given amount of stars. + + :return: A batch of reviews (``ReviewsBatch``). + """ + return ( + await GetReviews( + user_id=user_id, + from_review_id=from_review_id, + filter=filter, + ).execute(self) + ).response_obj + @overload async def get_offer_fields( self, diff --git a/funpaybotengine/methods/__init__.py b/funpaybotengine/methods/__init__.py index 1ebb98c..8ee350d 100644 --- a/funpaybotengine/methods/__init__.py +++ b/funpaybotengine/methods/__init__.py @@ -8,6 +8,7 @@ from .get_sales import GetSales as GetSales from .mute_chat import MuteChat as MuteChat from .calc_chips import CalcChips as CalcChips +from .get_reviews import GetReviews as GetReviews from .check_banned import CheckBanned as CheckBanned from .raise_offers import RaiseOffers as RaiseOffers from .upload_image import UploadImage as UploadImage diff --git a/funpaybotengine/methods/get_reviews.py b/funpaybotengine/methods/get_reviews.py index d1834bf..7cf8278 100644 --- a/funpaybotengine/methods/get_reviews.py +++ b/funpaybotengine/methods/get_reviews.py @@ -4,6 +4,9 @@ __all__ = ['GetReviews'] +from typing import TYPE_CHECKING, Any + +from funpayparsers.types.reviews import ReviewsBatch as ParsedReviewsBatch from funpayparsers.parsers.reviews_parser import ReviewsParser from funpaybotengine.types import ReviewsBatch @@ -11,11 +14,15 @@ from funpaybotengine.client.session.http_methods import HTTPMethod +if TYPE_CHECKING: + from funpaybotengine.client import RawResponse + + class GetReviews(FunPayMethod[ReviewsBatch]): """ - Get a sales list (``https://funpay.com/orders/trade``). + Get a reviews list of a user (``https://funpay.com/users/reviews``). - Returns ``funpaybotengine.types.OrderPreviewsBatch`` obj. + Returns ``funpaybotengine.types.ReviewsBatch`` obj. """ url = 'users/reviews' @@ -27,3 +34,13 @@ class GetReviews(FunPayMethod[ReviewsBatch]): user_id: int from_review_id: str = '' filter: str = '' + + async def parse_result(self, response: RawResponse[Any]) -> ParsedReviewsBatch: + result: ParsedReviewsBatch = await super().parse_result(response) + + # FunPay omits the hidden ``user_id`` / ``filter`` inputs in a part of the + # ``users/reviews`` responses, so the parsed values are unreliable. The requested + # ones are known here and are what the next batch has to be asked with. + result.user_id = self.user_id + result.filter = self.filter + return result diff --git a/funpaybotengine/methods/get_transactions.py b/funpaybotengine/methods/get_transactions.py index 49d0e39..dc08f94 100644 --- a/funpaybotengine/methods/get_transactions.py +++ b/funpaybotengine/methods/get_transactions.py @@ -6,6 +6,9 @@ from typing import TYPE_CHECKING, Any from funpayparsers.parsers import TransactionPreviewsParser +from funpayparsers.types.finances import ( + TransactionPreviewsBatch as ParsedTransactionPreviewsBatch, +) from funpaybotengine.types import TransactionPreviewsBatch from funpaybotengine.types.enums import TransactionFilter @@ -14,7 +17,7 @@ if TYPE_CHECKING: - from funpaybotengine.client import Bot + from funpaybotengine.client import Bot, RawResponse def make_data(m: GetTransactions, bot: Bot) -> dict[str, Any]: @@ -40,3 +43,12 @@ class GetTransactions(FunPayMethod[TransactionPreviewsBatch]): filter: TransactionFilter = TransactionFilter.ALL from_transaction_id: int = 0 + + async def parse_result(self, response: RawResponse[Any]) -> ParsedTransactionPreviewsBatch: + result: ParsedTransactionPreviewsBatch = await super().parse_result(response) + + # FunPay omits the hidden ``filter`` input in a part of the ``users/transactions`` + # responses, so the parsed value is unreliable. The requested filter is known here + # and is what the next batch has to be asked with. + result.filter = self.filter.value + return result diff --git a/funpaybotengine/types/reviews.py b/funpaybotengine/types/reviews.py index e8f4d9e..19768ad 100644 --- a/funpaybotengine/types/reviews.py +++ b/funpaybotengine/types/reviews.py @@ -126,3 +126,22 @@ class ReviewsBatch(FunPayObject, BaseModel): If present, this value should be included in the next request to fetch the following batch of reviews. If ``None``, there are no more reviews to load. """ + + async def next_batch(self) -> ReviewsBatch: + """ + Fetch the next batch of reviews, keeping the filter of the current one. + + :raises ValueError: if this is the last batch, or if the reviewed user is unknown + (a batch taken from an order page carries no ``user_id`` to paginate with). + """ + if not self.next_review_id: + raise ValueError('Last batch.') + + if self.user_id is None: + raise ValueError('Unknown user id.') + + return await self.get_bound_bot().get_reviews( + user_id=self.user_id, + from_review_id=self.next_review_id, + filter=self.filter or '', + ) diff --git a/tests/pagination_filters_test.py b/tests/pagination_filters_test.py new file mode 100644 index 0000000..1d2de3a --- /dev/null +++ b/tests/pagination_filters_test.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +import asyncio +from typing import Any, cast +from http import HTTPStatus + +import pytest +from funpaybotengine.types.enums import TransactionFilter +from funpaybotengine.types.reviews import ReviewsBatch +from funpaybotengine.types.finances import TransactionPreviewsBatch +from funpaybotengine.client.session.base import RawResponse +from funpaybotengine.methods.get_reviews import GetReviews +from funpaybotengine.methods.get_transactions import GetTransactions + + +# Both ``users/reviews`` and ``users/transactions`` may answer without the hidden +# ``filter`` / ``user_id`` inputs, in which case the parser has nothing to read them from. +REVIEWS_HTML = """ +
+""" + +TRANSACTIONS_HTML = """ +