Skip to content
Merged
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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" }
Expand Down
2 changes: 1 addition & 1 deletion src/scrapebadger/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ async def main():
Video as YoutubeVideo,
)

__version__ = "0.15.1"
__version__ = "0.15.2"

__all__ = [
# TikTok core models
Expand Down
9 changes: 7 additions & 2 deletions src/scrapebadger/ebay/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
36 changes: 19 additions & 17 deletions src/scrapebadger/youtube/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, ConfigDict, Field

# =============================================================================
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand All @@ -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)


# =============================================================================
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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


Expand Down
61 changes: 61 additions & 0 deletions tests/test_ebay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading