From bc638396ba9ffe74c7756ec821f502eded9d8a40 Mon Sep 17 00:00:00 2001 From: kasparasizi1 <132673909+kasparasizi1@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:38:17 +0300 Subject: [PATCH 1/2] =?UTF-8?q?feat(realtor):=20add=20Realtor=20client=20?= =?UTF-8?q?=E2=80=94=20US=20+=20CA=20real=20estate=20(0.15.4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit client.realtor with search/autocomplete/getProperty/listMarkets across realtor.com (US) and realtor.ca (CA) behind a single market param. Realtor*-prefixed exported types. Bumps 0.15.3 -> 0.15.4. (SCR-98) --- CHANGELOG.md | 6 + package-lock.json | 4 +- package.json | 2 +- src/client.ts | 5 + src/index.ts | 1 + src/realtor/client.ts | 59 +++++++ src/realtor/index.ts | 47 ++++++ src/realtor/properties.ts | 44 +++++ src/realtor/reference.ts | 38 +++++ src/realtor/search.ts | 86 ++++++++++ src/realtor/types.ts | 338 ++++++++++++++++++++++++++++++++++++++ tests/realtor.test.ts | 88 ++++++++++ 12 files changed, 715 insertions(+), 3 deletions(-) create mode 100644 src/realtor/client.ts create mode 100644 src/realtor/index.ts create mode 100644 src/realtor/properties.ts create mode 100644 src/realtor/reference.ts create mode 100644 src/realtor/search.ts create mode 100644 src/realtor/types.ts create mode 100644 tests/realtor.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ab182a8..a05db40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.15.4] - 2026-07-02 + +### Added + +- **Realtor client** (`client.realtor`) — real-estate listings across realtor.com (US) and realtor.ca (Canada) behind a single `market` param. Four endpoints: `search.search()`, `search.autocomplete()`, `properties.getProperty()`, `reference.listMarkets()`, with `Realtor*`-prefixed exported types (`RealtorSearchResponse`, `RealtorPropertyDetail`, …). (SCR-98) + ## [0.15.3] - 2026-06-30 ### Added diff --git a/package-lock.json b/package-lock.json index c43af3d..a9aec07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scrapebadger", - "version": "0.15.3", + "version": "0.15.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scrapebadger", - "version": "0.15.3", + "version": "0.15.4", "license": "MIT", "dependencies": { "ws": "^8.18.0" diff --git a/package.json b/package.json index e957141..f9509eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scrapebadger", - "version": "0.15.3", + "version": "0.15.4", "description": "Official Node.js SDK for ScrapeBadger - Async web scraping APIs for Twitter, Google, Vinted, Reddit, and more", "type": "module", "main": "./dist/index.js", diff --git a/src/client.ts b/src/client.ts index f52c5b7..016265a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -16,6 +16,7 @@ import { ShopeeClient } from "./shopee/client.js"; import { TikTokClient } from "./tiktok/client.js"; import { EbayClient } from "./ebay/client.js"; import { YoutubeClient } from "./youtube/client.js"; +import { RealtorClient } from "./realtor/client.js"; /** * ScrapeBadger API client. @@ -82,6 +83,9 @@ export class ScrapeBadger { /** YouTube scraper API client — 39 endpoints */ readonly youtube: YoutubeClient; + /** Realtor scraper API client — 4 endpoints across 2 markets (us, ca) */ + readonly realtor: RealtorClient; + /** * Create a new ScrapeBadger client. * @@ -131,5 +135,6 @@ export class ScrapeBadger { this.tiktok = new TikTokClient(this.baseClient); this.ebay = new EbayClient(this.baseClient); this.youtube = new YoutubeClient(this.baseClient); + this.realtor = new RealtorClient(this.baseClient); } } diff --git a/src/index.ts b/src/index.ts index 48dc9a6..21dd651 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,6 +87,7 @@ export * from "./ebay/index.js"; // Re-export YouTube module — YoutubeClient + Youtube*-prefixed sub-clients and types export * from "./youtube/index.js"; +export * from "./realtor/index.js"; // Re-export Google module — GoogleClient is exposed directly; sub-clients // are aliased with a Google* prefix to avoid collisions with other modules diff --git a/src/realtor/client.ts b/src/realtor/client.ts new file mode 100644 index 0000000..1fce1b3 --- /dev/null +++ b/src/realtor/client.ts @@ -0,0 +1,59 @@ +/** + * Realtor API client. + * + * Provides access to all Realtor API endpoints through specialized sub-clients. + */ + +import type { BaseClient } from "../internal/client.js"; +import { SearchClient } from "./search.js"; +import { PropertiesClient } from "./properties.js"; +import { ReferenceClient } from "./reference.js"; + +/** + * Realtor API client with access to all Realtor endpoints. + * + * Unified real-estate API over realtor.com (US) + realtor.ca (Canada). + * + * Provides sub-clients for different resource types: + * - `search` - Property search and location autocomplete + * - `properties` - Full property detail + * - `reference` - Reference data (markets) + * + * @example + * ```typescript + * const client = new ScrapeBadger({ apiKey: "key" }); + * + * // Search properties + * const results = await client.realtor.search.search("Austin, TX"); + * + * // Location autocomplete + * const suggestions = await client.realtor.search.autocomplete("Miami"); + * + * // Get property detail + * const detail = await client.realtor.properties.getProperty("1234567890"); + * + * // Reference data + * const markets = await client.realtor.reference.listMarkets(); + * ``` + */ +export class RealtorClient { + /** Client for property search and location autocomplete */ + readonly search: SearchClient; + + /** Client for full property detail */ + readonly properties: PropertiesClient; + + /** Client for reference data (markets) */ + readonly reference: ReferenceClient; + + /** + * Create a new Realtor client. + * + * @param client - The base HTTP client for making requests. + */ + constructor(client: BaseClient) { + this.search = new SearchClient(client); + this.properties = new PropertiesClient(client); + this.reference = new ReferenceClient(client); + } +} diff --git a/src/realtor/index.ts b/src/realtor/index.ts new file mode 100644 index 0000000..1fd4778 --- /dev/null +++ b/src/realtor/index.ts @@ -0,0 +1,47 @@ +/** + * Realtor API module. + * + * @module realtor + */ + +export { RealtorClient } from "./client.js"; +export { SearchClient as RealtorSearchClient } from "./search.js"; +export { PropertiesClient as RealtorPropertiesClient } from "./properties.js"; +export { ReferenceClient as RealtorReferenceClient } from "./reference.js"; + +// Export all types +export type { + // Shared + Coordinate as RealtorCoordinate, + Address as RealtorAddress, + Photo as RealtorPhoto, + Phone as RealtorPhone, + Office as RealtorOffice, + Agent as RealtorAgent, + OpenHouse as RealtorOpenHouse, + School as RealtorSchool, + TaxRecord as RealtorTaxRecord, + PriceEvent as RealtorPriceEvent, + Estimate as RealtorEstimate, + DetailGroup as RealtorDetailGroup, + Flags as RealtorFlags, + // Property + Property as RealtorProperty, + PropertyDetail as RealtorPropertyDetail, + // Autocomplete + Suggestion as RealtorSuggestion, + // Reference + MarketInfo as RealtorMarketInfo, + // Response envelopes + AutocompleteResponse as RealtorAutocompleteResponse, + SearchResponse as RealtorSearchResponse, + MarketsResponse as RealtorMarketsResponse, + // Param enums + RealtorMarket, + RealtorStatus, + RealtorSort, + // Request params + RealtorSearchOptions, + RealtorAutocompleteOptions, + RealtorPropertyOptions, +} from "./types.js"; diff --git a/src/realtor/properties.ts b/src/realtor/properties.ts new file mode 100644 index 0000000..be087ab --- /dev/null +++ b/src/realtor/properties.ts @@ -0,0 +1,44 @@ +/** + * Realtor Properties API client. + * + * Provides a method for fetching full property detail. + */ + +import type { BaseClient } from "../internal/client.js"; +import type { RealtorPropertyOptions, PropertyDetail } from "./types.js"; + +/** + * Client for the realtor property detail endpoint. + * + * @example + * ```typescript + * const client = new ScrapeBadger({ apiKey: "key" }); + * + * const detail = await client.realtor.properties.getProperty("1234567890"); + * console.log(detail.address?.line, detail.list_price); + * ``` + */ +export class PropertiesClient { + private readonly client: BaseClient; + + constructor(client: BaseClient) { + this.client = client; + } + + /** + * Get a single property's full detail. + * + * @param propertyId - The property id. + * @param options - Optional parameters (market). + * @returns Property detail including details, amenities, and history. + * @throws NotFoundError - If the property doesn't exist. + */ + async getProperty( + propertyId: string, + options: RealtorPropertyOptions = {} + ): Promise { + return this.client.request(`/v1/realtor/properties/${propertyId}`, { + params: { market: options.market }, + }); + } +} diff --git a/src/realtor/reference.ts b/src/realtor/reference.ts new file mode 100644 index 0000000..ad9c10a --- /dev/null +++ b/src/realtor/reference.ts @@ -0,0 +1,38 @@ +/** + * Realtor Reference Data API client. + * + * Provides a method for fetching the static market list. + */ + +import type { BaseClient } from "../internal/client.js"; +import type { MarketsResponse } from "./types.js"; + +/** + * Client for realtor reference data endpoints (markets). + * + * @example + * ```typescript + * const client = new ScrapeBadger({ apiKey: "key" }); + * + * const markets = await client.realtor.reference.listMarkets(); + * for (const m of markets.markets) { + * console.log(`${m.code}: ${m.domain} (${m.currency})`); + * } + * ``` + */ +export class ReferenceClient { + private readonly client: BaseClient; + + constructor(client: BaseClient) { + this.client = client; + } + + /** + * Get all supported realtor markets. + * + * @returns Markets response with all supported markets. + */ + async listMarkets(): Promise { + return this.client.request("/v1/realtor/markets"); + } +} diff --git a/src/realtor/search.ts b/src/realtor/search.ts new file mode 100644 index 0000000..365cc9a --- /dev/null +++ b/src/realtor/search.ts @@ -0,0 +1,86 @@ +/** + * Realtor Search API client. + * + * Provides methods for property search and location autocomplete. + */ + +import type { BaseClient } from "../internal/client.js"; +import type { + RealtorSearchOptions, + RealtorAutocompleteOptions, + SearchResponse, + AutocompleteResponse, +} from "./types.js"; + +/** + * Client for realtor search and autocomplete endpoints. + * + * @example + * ```typescript + * const client = new ScrapeBadger({ apiKey: "key" }); + * + * const results = await client.realtor.search.search("Austin, TX"); + * for (const p of results.results) { + * console.log(`${p.address?.line} — ${p.list_price_formatted}`); + * } + * + * const suggestions = await client.realtor.search.autocomplete("Miami"); + * ``` + */ +export class SearchClient { + private readonly client: BaseClient; + + constructor(client: BaseClient) { + this.client = client; + } + + /** + * Search a market for properties. + * + * @param location - Freetext location ("Austin, TX" / ZIP / "Toronto, ON"). + * Required unless a CA bounding box is provided. + * @param options - Filters, sorting, and pagination. + * @returns Search results with pagination metadata. + * @throws AuthenticationError - If the API key is invalid. + * @throws ValidationError - If the parameters are invalid. + */ + async search(location?: string, options: RealtorSearchOptions = {}): Promise { + return this.client.request("/v1/realtor/search", { + params: { + location, + market: options.market, + status: options.status, + price_min: options.priceMin, + price_max: options.priceMax, + beds_min: options.bedsMin, + baths_min: options.bathsMin, + sqft_min: options.sqftMin, + sqft_max: options.sqftMax, + property_type: options.propertyType, + sort: options.sort, + page: options.page, + limit: options.limit, + lat_min: options.latMin, + lat_max: options.latMax, + lng_min: options.lngMin, + lng_max: options.lngMax, + }, + }); + } + + /** + * Get location autocomplete suggestions. + * + * @param query - Partial location query. + * @param options - Optional parameters (market, limit). + * @returns Autocomplete suggestions. + */ + async autocomplete( + query: string, + options: RealtorAutocompleteOptions = {} + ): Promise { + return this.client.request("/v1/realtor/autocomplete", { + params: { query, market: options.market, limit: options.limit }, + }); + } +} diff --git a/src/realtor/types.ts b/src/realtor/types.ts new file mode 100644 index 0000000..e4e427e --- /dev/null +++ b/src/realtor/types.ts @@ -0,0 +1,338 @@ +/** + * TypeScript types for Realtor API responses. + * + * These interfaces mirror the backend `realtor_scraper` response schema + * field-for-field. Backend keys are snake_case (e.g. `property_id`, + * `list_price`, `last_sold_date_at`, `tax_history`) and are kept snake_case + * here to match the JSON exactly. All model fields are optional and nullable + * (`field?: type | null`); list fields default to `[]` on the backend and are + * typed as arrays. Every datetime field ships in BOTH `*_utc` (string) and + * `*_at` (string) form. + */ + +// ============================================================================= +// Shared Types +// ============================================================================= + +/** A lat/lon coordinate pair. */ +export interface Coordinate { + lat?: number | null; + lon?: number | null; +} + +/** A postal address with an optional coordinate. */ +export interface Address { + line?: string | null; + city?: string | null; + state?: string | null; + state_code?: string | null; + postal_code?: string | null; + country?: string | null; + neighborhood?: string | null; + county?: string | null; + coordinate?: Coordinate | null; +} + +/** A property photo with multiple resolutions. */ +export interface Photo { + href?: string | null; + href_high?: string | null; + href_med?: string | null; + href_low?: string | null; + tags?: string[]; + description?: string | null; +} + +/** A phone number attached to an agent or office. */ +export interface Phone { + number?: string | null; + type?: string | null; + ext?: string | null; + primary?: boolean | null; +} + +/** An agent's brokerage office. */ +export interface Office { + name?: string | null; + email?: string | null; + href?: string | null; + logo?: string | null; + phones?: Phone[]; + address?: Address | null; +} + +/** A listing agent. */ +export interface Agent { + agent_id?: string | null; + name?: string | null; + first_name?: string | null; + last_name?: string | null; + title?: string | null; + type?: string | null; + email?: string | null; + phones?: Phone[]; + photo?: string | null; + href?: string | null; + office?: Office | null; + broker?: string | null; + nrds_id?: string | null; + state_license?: string | null; +} + +/** A scheduled open house. */ +export interface OpenHouse { + start_utc?: string | null; + start_at?: string | null; + end_utc?: string | null; + end_at?: string | null; + description?: string | null; + time_zone?: string | null; + href?: string | null; +} + +/** A nearby school. */ +export interface School { + name?: string | null; + rating?: number | null; + education_levels?: string[]; + grades?: string | null; + distance_miles?: number | null; + district?: string | null; +} + +/** A single year's tax + assessment record. */ +export interface TaxRecord { + year?: number | null; + tax?: number | null; + assessment_building?: number | null; + assessment_land?: number | null; + assessment_total?: number | null; +} + +/** A single price-history event (list, sold, reduced, etc.). */ +export interface PriceEvent { + date_utc?: string | null; + date_at?: string | null; + event?: string | null; + price?: number | null; + price_per_sqft?: number | null; + source_listing_id?: string | null; +} + +/** A third-party value estimate. */ +export interface Estimate { + source?: string | null; + estimate?: number | null; + estimate_high?: number | null; + estimate_low?: number | null; + date_utc?: string | null; + date_at?: string | null; +} + +/** A named group of detail lines (e.g. "Interior Features"). */ +export interface DetailGroup { + category?: string | null; + text?: string[]; +} + +/** Boolean status flags for a listing. */ +export interface Flags { + is_new_listing?: boolean | null; + is_pending?: boolean | null; + is_contingent?: boolean | null; + is_foreclosure?: boolean | null; + is_new_construction?: boolean | null; + is_price_reduced?: boolean | null; + is_coming_soon?: boolean | null; +} + +// ============================================================================= +// Property (search results) +// ============================================================================= + +/** A property as returned by /search. */ +export interface Property { + property_id?: string | null; + listing_id?: string | null; + mls_number?: string | null; + market?: string | null; + country?: string | null; + url?: string | null; + status?: string | null; + transaction_type?: string | null; + currency?: string | null; + list_price?: number | null; + list_price_formatted?: string | null; + list_price_min?: number | null; + list_price_max?: number | null; + price_per_sqft?: number | null; + price_reduced_amount?: number | null; + last_sold_price?: number | null; + last_sold_date_utc?: string | null; + last_sold_date_at?: string | null; + hoa_fee?: number | null; + property_type?: string | null; + sub_type?: string | null; + beds?: number | null; + baths?: number | null; + baths_full?: number | null; + baths_half?: number | null; + sqft?: number | null; + lot_sqft?: number | null; + year_built?: number | null; + stories?: number | null; + garage?: number | null; + rooms?: number | null; + parking_spaces?: number | null; + address?: Address | null; + description_text?: string | null; + primary_photo?: string | null; + photo_count?: number | null; + photos?: Photo[]; + virtual_tours?: string[]; + videos?: string[]; + flags?: Flags | null; + tags?: string[]; + list_date_utc?: string | null; + list_date_at?: string | null; + last_update_utc?: string | null; + last_update_at?: string | null; + days_on_market?: number | null; + agents?: Agent[]; + source_mls_id?: string | null; + source_mls_name?: string | null; + open_houses?: OpenHouse[]; +} + +/** A property as returned by /properties/{property_id} (Property + extras). */ +export interface PropertyDetail extends Property { + details?: DetailGroup[]; + amenities?: string[]; + tax_history?: TaxRecord[]; + price_history?: PriceEvent[]; + schools?: School[]; + estimates?: Estimate[]; +} + +// ============================================================================= +// Autocomplete +// ============================================================================= + +/** A single location suggestion. */ +export interface Suggestion { + id?: string | null; + type?: string | null; + label?: string | null; + city?: string | null; + state_code?: string | null; + postal_code?: string | null; + country?: string | null; + slug_id?: string | null; + geo_id?: string | null; + coordinate?: Coordinate | null; + market?: string | null; +} + +// ============================================================================= +// Reference (markets) +// ============================================================================= + +/** A single supported market (for /markets). */ +export interface MarketInfo { + code: string; + domain: string; + country: string; + currency: string; + locale: string; + name: string; +} + +// ============================================================================= +// Response Envelopes (FLAT — no `data` wrapper) +// ============================================================================= + +/** Response from the /autocomplete endpoint. */ +export interface AutocompleteResponse { + market: string; + query: string; + suggestions: Suggestion[]; +} + +/** Response from the /search endpoint. */ +export interface SearchResponse { + market: string; + country: string; + total: number; + count: number; + page: number; + total_pages: number; + results: Property[]; +} + +/** Response from the /markets endpoint. */ +export interface MarketsResponse { + markets: MarketInfo[]; +} + +// ============================================================================= +// Request Parameter Types +// ============================================================================= + +/** Supported market codes. */ +export type RealtorMarket = "us" | "ca"; + +/** Listing status filter. */ +export type RealtorStatus = "for_sale" | "for_rent" | "sold" | "pending"; + +/** Sort order for search results. */ +export type RealtorSort = "relevant" | "newest" | "price_low" | "price_high" | "photo_count"; + +/** Options for the search endpoint. */ +export interface RealtorSearchOptions { + /** Market (default: "us") */ + market?: RealtorMarket; + /** Listing status (default: "for_sale") */ + status?: RealtorStatus; + /** Minimum list price */ + priceMin?: number; + /** Maximum list price */ + priceMax?: number; + /** Minimum bedrooms */ + bedsMin?: number; + /** Minimum bathrooms */ + bathsMin?: number; + /** Minimum square footage (US) */ + sqftMin?: number; + /** Maximum square footage (US) */ + sqftMax?: number; + /** Property type (US, CSV: single_family,condos,townhomes,multi_family,mobile,land) */ + propertyType?: string; + /** Sort order */ + sort?: RealtorSort; + /** Page number (default: 1) */ + page?: number; + /** Results per page (1-200) */ + limit?: number; + /** Bounding box min latitude (CA power-user) */ + latMin?: number; + /** Bounding box max latitude (CA power-user) */ + latMax?: number; + /** Bounding box min longitude (CA power-user) */ + lngMin?: number; + /** Bounding box max longitude (CA power-user) */ + lngMax?: number; +} + +/** Options for the autocomplete endpoint. */ +export interface RealtorAutocompleteOptions { + /** Market (default: "us") */ + market?: RealtorMarket; + /** Max suggestions (1-25, default: 10) */ + limit?: number; +} + +/** Options for the property detail endpoint. */ +export interface RealtorPropertyOptions { + /** Market (default: "us") */ + market?: RealtorMarket; +} diff --git a/tests/realtor.test.ts b/tests/realtor.test.ts new file mode 100644 index 0000000..39d6239 --- /dev/null +++ b/tests/realtor.test.ts @@ -0,0 +1,88 @@ +/** + * Tests for the Realtor API client. + * + * Uses vitest and a fetch mock that intercepts calls made by BaseClient. + */ + +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { ScrapeBadger } from "../src/client.js"; +import { RealtorClient } from "../src/realtor/client.js"; +import type { + RealtorSearchResponse, + RealtorPropertyDetail, + RealtorAutocompleteResponse, + RealtorMarketsResponse, +} from "../src/realtor/index.js"; + +function makeClient(): ScrapeBadger { + return new ScrapeBadger({ + apiKey: "test-api-key", + baseUrl: "https://api.scrapebadger.com", + maxRetries: 0, + }); +} + +function mockFetch(body: unknown, status = 200): void { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: status >= 200 && status < 300, + status, + headers: { get: () => "application/json" }, + json: () => Promise.resolve(body), + text: () => Promise.resolve(JSON.stringify(body)), + }) + ); +} + +function capturedUrl(): string { + const mock = vi.mocked(fetch); + expect(mock).toHaveBeenCalledOnce(); + return (mock.mock.calls[0] as [string, RequestInit])[0]; +} + +describe("RealtorClient", () => { + beforeEach(() => vi.restoreAllMocks()); + + it("is wired onto the top-level client", () => { + expect(makeClient().realtor).toBeInstanceOf(RealtorClient); + }); + + it("search() routes to /v1/realtor/search with params", async () => { + mockFetch({ market: "us", results: [{ property_id: "1", list_price: 500000 }] }); + const res: RealtorSearchResponse = await makeClient().realtor.search.search("Austin, TX", { + market: "us", + bedsMin: 3, + }); + const url = capturedUrl(); + expect(url).toContain("/v1/realtor/search"); + expect(url).toContain("location=Austin"); + expect(res.market).toBe("us"); + }); + + it("autocomplete() routes to /v1/realtor/autocomplete", async () => { + mockFetch({ market: "ca", query: "toronto", suggestions: [] }); + const res: RealtorAutocompleteResponse = await makeClient().realtor.search.autocomplete( + "toronto", + { market: "ca" } + ); + expect(capturedUrl()).toContain("/v1/realtor/autocomplete"); + expect(res.query).toBe("toronto"); + }); + + it("getProperty() routes to /v1/realtor/properties/{id}", async () => { + mockFetch({ property_id: "42" }); + const res: RealtorPropertyDetail = await makeClient().realtor.properties.getProperty("42", { + market: "us", + }); + expect(capturedUrl()).toContain("/v1/realtor/properties/42"); + expect(res.property_id).toBe("42"); + }); + + it("listMarkets() routes to /v1/realtor/markets", async () => { + mockFetch({ markets: [] }); + const res: RealtorMarketsResponse = await makeClient().realtor.reference.listMarkets(); + expect(capturedUrl()).toContain("/v1/realtor/markets"); + expect(res.markets).toEqual([]); + }); +}); From ca4bc555b732a873ac8d86719985b6192c5cf9d5 Mon Sep 17 00:00:00 2001 From: kasparasizi1 <132673909+kasparasizi1@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:40:02 +0300 Subject: [PATCH 2/2] fix(youtube): empty interfaces -> type aliases (unblock lint on main) 7 'interface X extends YoutubeRegionParams {}' tripped @typescript-eslint/no-empty-object-type, failing CI lint on every PR. Convert to equivalent type aliases. Pre-existing on main; not realtor. --- src/youtube/types.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/youtube/types.ts b/src/youtube/types.ts index 751ff96..65fb28c 100644 --- a/src/youtube/types.ts +++ b/src/youtube/types.ts @@ -922,10 +922,10 @@ export interface YoutubeSearchParams extends YoutubeRegionParams { } /** Options for the autocomplete endpoint. */ -export interface YoutubeAutocompleteParams extends YoutubeRegionParams {} +export type YoutubeAutocompleteParams = YoutubeRegionParams; /** Options for the video detail endpoint. */ -export interface YoutubeVideoParams extends YoutubeRegionParams {} +export type YoutubeVideoParams = YoutubeRegionParams; /** Options for the related-videos endpoint. */ export interface YoutubeRelatedParams extends YoutubeRegionParams { @@ -954,7 +954,7 @@ export interface YoutubeTranscriptParams extends YoutubeRegionParams { } /** Options for the captions endpoint. */ -export interface YoutubeCaptionsParams extends YoutubeRegionParams {} +export type YoutubeCaptionsParams = YoutubeRegionParams; /** Options for the streams endpoint. */ export interface YoutubeStreamsParams { @@ -985,7 +985,7 @@ export interface YoutubeOEmbedParams { } /** Options for the channel detail / about / subscriber_count endpoints. */ -export interface YoutubeChannelParams extends YoutubeRegionParams {} +export type YoutubeChannelParams = YoutubeRegionParams; /** Options for a channel tab (videos/shorts/streams/playlists/community) endpoint. */ export interface YoutubeChannelTabParams extends YoutubeRegionParams { @@ -1010,7 +1010,7 @@ export interface YoutubeResolveParams extends YoutubeRegionParams { } /** Options for the playlist detail / mix endpoints. */ -export interface YoutubePlaylistParams extends YoutubeRegionParams {} +export type YoutubePlaylistParams = YoutubeRegionParams; /** Options for the playlist items endpoint. */ export interface YoutubePlaylistItemsParams extends YoutubeRegionParams { @@ -1019,7 +1019,7 @@ export interface YoutubePlaylistItemsParams extends YoutubeRegionParams { } /** Options for the community post detail endpoint. */ -export interface YoutubePostParams extends YoutubeRegionParams {} +export type YoutubePostParams = YoutubeRegionParams; /** Options for the community post comments endpoint. */ export interface YoutubePostCommentsParams extends YoutubeRegionParams { @@ -1028,7 +1028,7 @@ export interface YoutubePostCommentsParams extends YoutubeRegionParams { } /** Options for the single-short endpoint. */ -export interface YoutubeShortParams extends YoutubeRegionParams {} +export type YoutubeShortParams = YoutubeRegionParams; /** Options for the shorts-by-sound endpoint. */ export interface YoutubeShortsBySoundParams extends YoutubeRegionParams {