diff --git a/CHANGELOG.md b/CHANGELOG.md index 819dfec..610cbc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.15.2] - 2026-06-22 + +### Added + +- **eBay auction data** on the eBay client models: + - `SearchResult.current_bid` and `Item.current_bid` — an auction's current high bid (mirrors `price`). + - `Item.end_time_utc` / `Item.end_time_at` — the absolute auction end time (Unix float / ISO-8601 Z). + - `Item.buy_it_now_price` — the Buy It Now price for fixed-price listings, or an auction that also offers Buy It Now (`None` for pure auctions). + - `bids` (bid count) and `time_left` (relative remaining, e.g. `"12h 16m"`) are now reliably populated for auction listings. + ## [0.15.0] - 2026-06-21 ### Added diff --git a/pyproject.toml b/pyproject.toml index 684bd62..64bd7e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "scrapebadger" -version = "0.15.1" +version = "0.15.2" description = "Official Python SDK for ScrapeBadger - Async web scraping APIs for Twitter and more" readme = "README.md" license = { text = "MIT" } diff --git a/src/scrapebadger/__init__.py b/src/scrapebadger/__init__.py index d9d3435..89d5120 100644 --- a/src/scrapebadger/__init__.py +++ b/src/scrapebadger/__init__.py @@ -420,7 +420,7 @@ async def main(): Video as YoutubeVideo, ) -__version__ = "0.15.1" +__version__ = "0.15.2" __all__ = [ # TikTok core models diff --git a/src/scrapebadger/ebay/models.py b/src/scrapebadger/ebay/models.py index a85607e..8c7df0d 100644 --- a/src/scrapebadger/ebay/models.py +++ b/src/scrapebadger/ebay/models.py @@ -109,7 +109,8 @@ class SearchResult(_BaseModel): buying_format: str | None = None # "Buy It Now" | "Auction" | "Best Offer" is_auction: bool = False bids: int | None = None - time_left: str | None = None + current_bid: EbayPrice | None = None # auctions: the current high bid (mirrors ``price``) + time_left: str | None = None # relative remaining, e.g. "12h 16m" / "4d 8h" shipping: str | None = None shipping_cost: EbayPrice | None = None free_shipping: bool | None = None @@ -175,7 +176,11 @@ class Item(_BaseModel): buying_format: str | None = None is_auction: bool = False bids: int | None = None - time_left: str | None = None + current_bid: EbayPrice | None = None # auctions: the current high bid (mirrors ``price``) + time_left: str | None = None # relative remaining, e.g. "12h 16m" + end_time_utc: float | None = None # absolute auction end (Unix float) + end_time_at: str | None = None # absolute auction end (ISO-8601 Z) + buy_it_now_price: EbayPrice | None = None # BIN price (fixed-price, or auction-with-BIN) best_offer_enabled: bool | None = None brand: str | None = None mpn: str | None = None diff --git a/src/scrapebadger/youtube/models.py b/src/scrapebadger/youtube/models.py index e2f54d4..7be6df4 100644 --- a/src/scrapebadger/youtube/models.py +++ b/src/scrapebadger/youtube/models.py @@ -10,6 +10,8 @@ from __future__ import annotations +from typing import Any + from pydantic import BaseModel, ConfigDict, Field # ============================================================================= @@ -185,7 +187,7 @@ class Video(_BaseModel): title: str | None = None url: str | None = None description: str | None = None - description_links: list[dict] = Field(default_factory=list) + description_links: list[dict[str, Any]] = Field(default_factory=list) length_seconds: int | None = None duration: str | None = None # HH:MM:SS @@ -226,7 +228,7 @@ class Video(_BaseModel): # Media thumbnails: list[Thumbnail] = Field(default_factory=list) thumbnail: str | None = None - storyboards: dict | None = None + storyboards: dict[str, Any] | None = None chapters: list[Chapter] = Field(default_factory=list) heatmap: list[HeatMarker] = Field(default_factory=list) @@ -266,10 +268,10 @@ class Video(_BaseModel): # Rich shelves (from next) shopping_results: list[ShoppingResult] = Field(default_factory=list) - additional_info: list[dict] = Field(default_factory=list) - game_info: dict | None = None - end_screen_videos: list[dict] = Field(default_factory=list) - related_videos: list[dict] = Field(default_factory=list) + additional_info: list[dict[str, Any]] = Field(default_factory=list) + game_info: dict[str, Any] | None = None + end_screen_videos: list[dict[str, Any]] = Field(default_factory=list) + related_videos: list[dict[str, Any]] = Field(default_factory=list) related_videos_continuation: str | None = None # Sound (shorts) @@ -362,7 +364,7 @@ class SearchResponse(_BaseModel): did_you_mean: str | None = None showing_results_for: str | None = None refinements: list[str] = Field(default_factory=list) - related_searches: list[dict] = Field(default_factory=list) + related_searches: list[dict[str, Any]] = Field(default_factory=list) continuation: str | None = None @@ -470,8 +472,8 @@ class Channel(_BaseModel): tabs: list[str] = Field(default_factory=list) available_countries: list[str] = Field(default_factory=list) - related_channels: list[dict] = Field(default_factory=list) - latest_videos: list[dict] = Field(default_factory=list) + related_channels: list[dict[str, Any]] = Field(default_factory=list) + latest_videos: list[dict[str, Any]] = Field(default_factory=list) scraped_at: str | None = None scraped_utc: float | None = None @@ -596,7 +598,7 @@ class CommentsResponse(_BaseModel): video_id: str | None = None comments: list[Comment] = Field(default_factory=list) comment_count: int | None = None - sorting_tokens: list[dict] = Field(default_factory=list) + sorting_tokens: list[dict[str, Any]] = Field(default_factory=list) continuation: str | None = None @@ -631,8 +633,8 @@ class Transcript(_BaseModel): language_name: str | None = None type: str | None = None # asr | manual is_translatable: bool | None = None - available_transcripts: list[dict] = Field(default_factory=list) - translation_languages: list[dict] = Field(default_factory=list) + available_transcripts: list[dict[str, Any]] = Field(default_factory=list) + translation_languages: list[dict[str, Any]] = Field(default_factory=list) segments: list[TranscriptSegment] = Field(default_factory=list) full_text: str | None = None srt: str | None = None @@ -654,7 +656,7 @@ class CaptionsResponse(_BaseModel): video_id: str tracks: list[CaptionTrack] = Field(default_factory=list) - translation_languages: list[dict] = Field(default_factory=list) + translation_languages: list[dict[str, Any]] = Field(default_factory=list) # ============================================================================= @@ -718,7 +720,7 @@ class CommunityPost(_BaseModel): post_url: str | None = None post_type: str | None = None # text | poll | image | video | shared text: str | None = None - text_links: list[dict] = Field(default_factory=list) + text_links: list[dict[str, Any]] = Field(default_factory=list) channel_id: str | None = None channel_name: str | None = None channel_url: str | None = None @@ -731,8 +733,8 @@ class CommunityPost(_BaseModel): poll_choices: list[PollChoice] = Field(default_factory=list) poll_total_votes: int | None = None images: list[Thumbnail] = Field(default_factory=list) - attached_video: dict | None = None - shared_post: dict | None = None + attached_video: dict[str, Any] | None = None + shared_post: dict[str, Any] | None = None scraped_at: str | None = None scraped_utc: float | None = None @@ -783,7 +785,7 @@ class BatchResponse(_BaseModel): """Response for POST /videos/batch.""" videos: list[Video] = Field(default_factory=list) - errors: list[dict] = Field(default_factory=list) + errors: list[dict[str, Any]] = Field(default_factory=list) count: int = 0 diff --git a/tests/test_ebay.py b/tests/test_ebay.py index c9205e3..7f60c89 100644 --- a/tests/test_ebay.py +++ b/tests/test_ebay.py @@ -134,6 +134,39 @@ "scraped_at": "2026-06-03T00:00:00Z", } +SAMPLE_AUCTION_BID: dict[str, Any] = { + "value": 1430.0, + "currency": "USD", + "symbol": "$", + "raw": "US $1,430.00", +} + +SAMPLE_AUCTION_RESULT: dict[str, Any] = { + "position": 1, + "item_id": "168461969053", + "title": "Vintage Rolex Oysterdate Precision", + "price": SAMPLE_AUCTION_BID, + "buying_format": "Auction", + "is_auction": True, + "bids": 19, + "current_bid": SAMPLE_AUCTION_BID, + "time_left": "12h 5m", +} + +SAMPLE_AUCTION_ITEM: dict[str, Any] = { + "item_id": "168461969053", + "title": "Vintage Rolex Oysterdate Precision", + "price": SAMPLE_AUCTION_BID, + "buying_format": "Auction", + "is_auction": True, + "bids": 19, + "current_bid": SAMPLE_AUCTION_BID, + "time_left": "12h 5m", + "end_time_utc": 1782157851.0, + "end_time_at": "2026-06-22T19:50:51Z", + "buy_it_now_price": None, +} + SAMPLE_REVIEW: dict[str, Any] = { "title": "Great console", "body": "Works perfectly.", @@ -369,6 +402,34 @@ def test_item_full(self) -> None: assert item.seller.username == "musicmagpie" assert item.scraped_utc == 1751500000.0 + def test_search_result_auction(self) -> None: + result = SearchResult.model_validate(SAMPLE_AUCTION_RESULT) + assert result.is_auction is True + assert result.bids == 19 + assert result.time_left == "12h 5m" + assert result.current_bid is not None + assert result.current_bid.value == 1430.0 + + def test_item_auction(self) -> None: + item = Item.model_validate(SAMPLE_AUCTION_ITEM) + assert item.is_auction is True + assert item.bids == 19 + assert item.current_bid is not None + assert item.current_bid.value == 1430.0 + assert item.time_left == "12h 5m" + assert item.end_time_utc == 1782157851.0 + assert item.end_time_at == "2026-06-22T19:50:51Z" + assert item.buy_it_now_price is None + + def test_item_non_auction_has_no_auction_fields(self) -> None: + item = Item.model_validate(SAMPLE_ITEM) + assert item.is_auction is False + assert item.current_bid is None + assert item.end_time_utc is None + assert item.end_time_at is None + # A fixed-price listing exposes no separate Buy It Now price here. + assert item.buy_it_now_price is None + def test_item_minimal(self) -> None: item = Item(item_id="X") assert item.title is None diff --git a/uv.lock b/uv.lock index ca266d0..5f63840 100644 --- a/uv.lock +++ b/uv.lock @@ -752,7 +752,7 @@ wheels = [ [[package]] name = "scrapebadger" -version = "0.15.0" +version = "0.15.2" source = { editable = "." } dependencies = [ { name = "httpx" },