From e79505f9f32c199c67206d1880a476aa7a57744f Mon Sep 17 00:00:00 2001 From: 0xGhostCasper <132673909+kasparasizi1@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:46:39 +0300 Subject: [PATCH] feat(immobiliare): add Immobiliare.it client (0.16.0) --- README.md | 1 + package-lock.json | 4 +- package.json | 2 +- src/client.ts | 5 + src/immobiliare/client.ts | 202 ++++++++++++++++++++ src/immobiliare/index.ts | 50 +++++ src/immobiliare/types.ts | 391 ++++++++++++++++++++++++++++++++++++++ src/index.ts | 3 + src/internal/version.ts | 2 +- 9 files changed, 656 insertions(+), 4 deletions(-) create mode 100644 src/immobiliare/client.ts create mode 100644 src/immobiliare/index.ts create mode 100644 src/immobiliare/types.ts diff --git a/README.md b/README.md index ac63bb9..c3a9554 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ const client = new ScrapeBadger(); | **TikTok** | 26 endpoints — user profile/videos/followers/following/liked/reposts, video detail/comments/replies/related/transcript, oEmbed, hashtag detail/videos, music detail/videos, search (general/videos/users/hashtags), trending (videos/hashtags/songs), ad library, regions | [TikTok Guide](docs/tiktok.md) | | **eBay** | 12 endpoints across 18 markets — search, completed/sold listings, item detail, item reviews, seller profile/items/feedback, category browse, categories, autocomplete, markets | [eBay Guide](docs/ebay.md) | | **YouTube** | 39 endpoints — search, autocomplete, video detail/related/comments/replies/transcript/captions/streams/live chat/batch, channel detail/videos/shorts/streams/playlists/community/about/subscriber count/search/resolve, playlist detail/items, mixes, trending/shorts, hashtags, home, shorts detail/by sound, community posts/comments, music search, oEmbed, categories/languages/regions/markets | [YouTube Guide](docs/youtube.md) | +| **Immobiliare** | 8 endpoints across 4 markets (it, es, gr, lu) — autocomplete, search, listing detail, agency profile/listings, price stats, markets, reference | [Immobiliare Guide](docs/immobiliare.md) | ## Error Handling diff --git a/package-lock.json b/package-lock.json index a9aec07..a11e281 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scrapebadger", - "version": "0.15.4", + "version": "0.16.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scrapebadger", - "version": "0.15.4", + "version": "0.16.0", "license": "MIT", "dependencies": { "ws": "^8.18.0" diff --git a/package.json b/package.json index 598168d..99cf471 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scrapebadger", - "version": "0.15.6", + "version": "0.16.0", "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 129a0fa..b375086 100644 --- a/src/client.ts +++ b/src/client.ts @@ -19,6 +19,7 @@ import { YoutubeClient } from "./youtube/client.js"; import { RealtorClient } from "./realtor/client.js"; import { LeboncoinClient } from "./leboncoin/client.js"; import { ZillowClient } from "./zillow/client.js"; +import { ImmobiliareClient } from "./immobiliare/client.js"; /** * ScrapeBadger API client. @@ -94,6 +95,9 @@ export class ScrapeBadger { /** Zillow scraper API client — 5 endpoints (search, property, agent, autocomplete, markets) */ readonly zillow: ZillowClient; + /** Immobiliare scraper API client — 8 endpoints across 4 markets (it, es, gr, lu) */ + readonly immobiliare: ImmobiliareClient; + /** * Create a new ScrapeBadger client. * @@ -146,5 +150,6 @@ export class ScrapeBadger { this.realtor = new RealtorClient(this.baseClient); this.leboncoin = new LeboncoinClient(this.baseClient); this.zillow = new ZillowClient(this.baseClient); + this.immobiliare = new ImmobiliareClient(this.baseClient); } } diff --git a/src/immobiliare/client.ts b/src/immobiliare/client.ts new file mode 100644 index 0000000..13a7e09 --- /dev/null +++ b/src/immobiliare/client.ts @@ -0,0 +1,202 @@ +/** + * Immobiliare API client. + * + * Immobiliare endpoints: autocomplete (resolve a free-text place to geography + * ids), search (list listings by location or explicit ids), getListing (full + * single-listing detail), getAgency / getAgencyListings (agency profile + + * active listings), priceStats (€/m² time series per area), markets, and + * reference (filter enums). Markets: it, es, gr, lu (the Immobiliare Group). + */ + +import type { BaseClient } from "../internal/client.js"; +import type { + ImmobiliareAutocompleteParams, + ImmobiliareSearchParams, + ImmobiliareListingParams, + ImmobiliareAgencyParams, + ImmobiliareAgencyListingsParams, + ImmobiliarePriceStatsParams, + SuggestResponse, + SearchResponse, + Listing, + AgencyProfile, + AgencyListingsResponse, + PriceStatsResponse, + MarketsResponse, + ReferenceResponse, +} from "./types.js"; + +/** + * Client for all Immobiliare API operations. + * + * @example + * ```typescript + * const client = new ScrapeBadger({ apiKey: "key" }); + * + * // Resolve a place name to geography ids + * const hits = await client.immobiliare.autocomplete("Milano"); + * const city = hits.suggestions[0]; + * + * // Search listings + * const results = await client.immobiliare.search({ + * city_id: city.city_id ?? undefined, + * price_max: 500000, + * }); + * for (const listing of results.listings) { + * console.log(`${listing.id}: ${listing.title}`); + * } + * + * // Full single-listing detail + * const detail = await client.immobiliare.getListing(123456789); + * console.log(detail.price?.formatted); + * + * // Supported markets + * const markets = await client.immobiliare.markets(); + * ``` + */ +export class ImmobiliareClient { + private readonly client: BaseClient; + + constructor(client: BaseClient) { + this.client = client; + } + + /** + * Resolve a free-text place name into geography ids for search. + * + * @param query - Free-text place name, e.g. "Milano". + * @param options - Optional parameters (market). + * @returns Suggest response with region/province/city/zone id candidates. + */ + async autocomplete( + query: string, + options: ImmobiliareAutocompleteParams = {} + ): Promise { + return this.client.request("/v1/immobiliare/autocomplete", { + params: { query, market: options.market }, + }); + } + + /** + * Search Immobiliare-group listings. + * + * Scope the search by `location` (free text, auto-resolved) OR explicit + * `region_id` / `province_id` / `city_id` from {@link autocomplete}. + * + * @param options - Search parameters (market, location/ids, contract, + * category, price/surface/room/bathroom filters, sort, page). + * @returns Search response with `listings` and pagination metadata. + * @throws AuthenticationError - If the API key is invalid. + * @throws ValidationError - If the parameters are invalid. + */ + async search(options: ImmobiliareSearchParams = {}): Promise { + return this.client.request("/v1/immobiliare/search", { + params: { + market: options.market, + location: options.location, + region_id: options.region_id, + province_id: options.province_id, + city_id: options.city_id, + contract: options.contract, + category: options.category, + price_min: options.price_min, + price_max: options.price_max, + surface_min: options.surface_min, + surface_max: options.surface_max, + rooms_min: options.rooms_min, + rooms_max: options.rooms_max, + bathrooms_min: options.bathrooms_min, + sort: options.sort, + page: options.page, + }, + }); + } + + /** + * Get the full detail for a single Immobiliare listing. + * + * @param listingId - The Immobiliare listing id. + * @param options - Optional parameters (market). + * @returns The full listing detail. + * @throws NotFoundError - If the listing doesn't exist. + */ + async getListing(listingId: number, options: ImmobiliareListingParams = {}): Promise { + return this.client.request(`/v1/immobiliare/listings/${listingId}`, { + params: { market: options.market }, + }); + } + + /** + * Get an agency/advertiser profile. + * + * @param agencyId - The Immobiliare agency id. + * @param options - Optional parameters (market). + * @returns The full agency profile. + * @throws NotFoundError - If the agency doesn't exist. + */ + async getAgency(agencyId: number, options: ImmobiliareAgencyParams = {}): Promise { + return this.client.request(`/v1/immobiliare/agencies/${agencyId}`, { + params: { market: options.market }, + }); + } + + /** + * Get an agency's active listings (25 per page). + * + * @param agencyId - The Immobiliare agency id. + * @param options - Optional parameters (market, page). + * @returns Agency-listings response with `listings` and pagination. + */ + async getAgencyListings( + agencyId: number, + options: ImmobiliareAgencyListingsParams = {} + ): Promise { + return this.client.request( + `/v1/immobiliare/agencies/${agencyId}/listings`, + { + params: { market: options.market, page: options.page }, + } + ); + } + + /** + * Get the historical average €/m² time series for an area. + * + * @param regionId - Region id, e.g. "lom" (required). + * @param options - Optional parameters (province_id, city_id, market, contract). + * @returns Price-stats response with monthly `points` (label + EUR/m² value). + */ + async priceStats( + regionId: string, + options: ImmobiliarePriceStatsParams = {} + ): Promise { + return this.client.request("/v1/immobiliare/market-insights/prices", { + params: { + market: options.market, + region_id: regionId, + province_id: options.province_id, + city_id: options.city_id, + contract: options.contract, + }, + }); + } + + /** + * Get all supported Immobiliare-group markets (it, es, gr, lu). + * + * @returns A bare array of supported markets (code, domain, country, + * locale, currency, name). + */ + async markets(): Promise { + return this.client.request("/v1/immobiliare/markets"); + } + + /** + * Get the filter enums accepted by {@link search}. + * + * @returns Reference response with `contracts`, `categories`, and `sorts`. + */ + async reference(): Promise { + return this.client.request("/v1/immobiliare/reference"); + } +} diff --git a/src/immobiliare/index.ts b/src/immobiliare/index.ts new file mode 100644 index 0000000..1efd7ea --- /dev/null +++ b/src/immobiliare/index.ts @@ -0,0 +1,50 @@ +/** + * Immobiliare API module. + * + * @module immobiliare + */ + +export { ImmobiliareClient } from "./client.js"; + +// Export all types +export type { + // Shared / nested + Photo as ImmobiliarePhoto, + Price as ImmobiliarePrice, + Location as ImmobiliareLocation, + Feature as ImmobiliareFeature, + Agency as ImmobiliareAgency, + Agent as ImmobiliareAgent, + PropertyUnit as ImmobiliarePropertyUnit, + // Listing + Listing as ImmobiliareListing, + // Agency profile + AgencyAgent as ImmobiliareAgencyAgent, + AgencyProfile as ImmobiliareAgencyProfile, + // Autocomplete + Suggestion as ImmobiliareSuggestion, + // Market insights + PriceStatsPoint as ImmobiliarePriceStatsPoint, + // Markets + Market as ImmobiliareMarket, + // Response envelopes + RelatedSearch as ImmobiliareRelatedSearch, + SuggestResponse as ImmobiliareSuggestResponse, + SearchResponse as ImmobiliareSearchResponse, + AgencyListingsResponse as ImmobiliareAgencyListingsResponse, + PriceStatsResponse as ImmobiliarePriceStatsResponse, + MarketsResponse as ImmobiliareMarketsResponse, + ReferenceResponse as ImmobiliareReferenceResponse, + // Param enums + ImmobiliareMarketCode, + ImmobiliareContract, + ImmobiliareCategory, + ImmobiliareSort, + // Request params + ImmobiliareAutocompleteParams, + ImmobiliareSearchParams, + ImmobiliareListingParams, + ImmobiliareAgencyParams, + ImmobiliareAgencyListingsParams, + ImmobiliarePriceStatsParams, +} from "./types.js"; diff --git a/src/immobiliare/types.ts b/src/immobiliare/types.ts new file mode 100644 index 0000000..1088cc5 --- /dev/null +++ b/src/immobiliare/types.ts @@ -0,0 +1,391 @@ +/** + * TypeScript types for Immobiliare API responses. + * + * These interfaces mirror the backend `immobiliare_scraper` response schema, + * which normalises Immobiliare's internal `api-next` shapes into a clean, + * market-agnostic snake_case schema. Optional / nullable backend fields are + * typed as `Type | null`; list fields default to `[]` and are typed as + * arrays. Markets: it (immobiliare.it), es (indomio.es), gr (indomio.gr), + * lu (immotop.lu) — all EUR. + */ + +// ============================================================================= +// Shared nested types +// ============================================================================= + +/** A single listing photo in three sizes. */ +export interface Photo { + id: number | null; + caption: string | null; + small: string | null; + medium: string | null; + large: string | null; +} + +/** A listing price (`price_per_sqm` / `loan_from` are detail-only). */ +export interface Price { + value: number | null; + formatted: string | null; + min_value: string | null; + max_value: string | null; + currency: string; + visible: boolean; + price_per_sqm: string | null; + loan_from: string | null; +} + +/** The geographic location block of a listing. */ +export interface Location { + address: string | null; + latitude: number | null; + longitude: number | null; + marker: string | null; + region: string | null; + province: string | null; + city: string | null; + macrozone: string | null; + microzone: string | null; + zipcode: string | null; + nation_code: string | null; + nation_name: string | null; +} + +/** A single listing feature/amenity. */ +export interface Feature { + type: string | null; + label: string | null; + compact_label: string | null; +} + +/** The advertising agency summary attached to a listing. */ +export interface Agency { + id: number | null; + type: string | null; + display_name: string | null; + label: string | null; + url: string | null; + is_paid: boolean | null; + guaranteed: boolean | null; + show_logo: boolean | null; + image_small: string | null; + image_large: string | null; + phones: string[]; +} + +/** The individual agent behind a listing. */ +export interface Agent { + type: string | null; + display_name: string | null; + label: string | null; + image_gender: string | null; + image_url: string | null; + phones: string[]; +} + +/** One unit within a listing (a listing may bundle several — a "project"). */ +export interface PropertyUnit { + is_main: boolean; + surface: string | null; + surface_value: number | null; + rooms: string | null; + bathrooms: string | null; + bedrooms: string | null; + floor: string | null; + elevator: boolean | null; + garage: string | null; + heating: string | null; + energy_class: string | null; + condominium_fees: string | null; + typology: string | null; + category: string | null; + caption: string | null; + description: string | null; + price: Price | null; + features: Feature[]; + ga4_features: string[]; + views: string[]; + photos: Photo[]; +} + +// ============================================================================= +// Listing (search card + /listings/{id} detail) +// ============================================================================= + +/** A normalised Immobiliare listing (search card or detail). */ +export interface Listing { + id: number; + uuid: string | null; + url: string | null; + title: string | null; + /** "sale" | "rent" */ + contract: string | null; + is_new: boolean | null; + luxury: boolean | null; + is_project: boolean | null; + is_mosaic: boolean | null; + visibility: string | null; + typology: string | null; + category: string | null; + price: Price | null; + location: Location | null; + /** Flattened convenience fields (from the main property unit) */ + surface: string | null; + rooms: string | null; + bathrooms: string | null; + floor: string | null; + energy_class: string | null; + description: string | null; + photo_count: number | null; + has_virtual_tour: boolean | null; + photos: Photo[]; + agency: Agency | null; + agent: Agent | null; + properties_count: number | null; + properties: PropertyUnit[]; + /** Detail-only fields (populated by GET /listings/{id}) */ + creation_date: string | null; + last_modified_utc: number | null; + last_modified_at: string | null; + features_full: Feature[]; +} + +// ============================================================================= +// Agency profile (/agencies/{id}) +// ============================================================================= + +/** A single agent on an agency profile. */ +export interface AgencyAgent { + id: number | null; + name: string | null; + surname: string | null; + gender: string | null; + thumbnail: string | null; +} + +/** Full agency/advertiser profile (rendered from the agency page). */ +export interface AgencyProfile { + id: number; + type: string | null; + name: string | null; + url: string | null; + address: string | null; + description: string | null; + website: string | null; + image: string | null; + is_paid: boolean | null; + partnership: string | null; + real_estate_ads: number | null; + real_estate_sales: number | null; + region: string | null; + province: string | null; + city: string | null; + macrozone: string | null; + latitude: number | null; + longitude: number | null; + phones: string[]; + opening_hours: string[]; + agents: AgencyAgent[]; + market: string; +} + +// ============================================================================= +// Autocomplete (/autocomplete) +// ============================================================================= + +/** A geography autocomplete candidate → usable as a search location. */ +export interface Suggestion { + id: string | null; + label: string | null; + /** nation | region | province | comune | zone */ + type: string | null; + region_id: string | null; + province_id: string | null; + city_id: string | null; + macrozone_ids: string[]; + url: string | null; +} + +// ============================================================================= +// Market insights (/market-insights/prices) +// ============================================================================= + +/** One point in the €/m² time series. */ +export interface PriceStatsPoint { + label: string; + value: number | null; +} + +// ============================================================================= +// Markets (/markets) +// ============================================================================= + +/** A single supported Immobiliare-group market. */ +export interface Market { + code: string; + domain: string; + country_code: string; + locale: string; + currency: string; + name: string; +} + +// ============================================================================= +// Response envelopes +// ============================================================================= + +/** A related-search suggestion returned alongside search results. */ +export interface RelatedSearch { + label: string | null; + url: string | null; +} + +/** Response for /autocomplete. */ +export interface SuggestResponse { + suggestions: Suggestion[]; + market: string; +} + +/** Response for /search. */ +export interface SearchResponse { + listings: Listing[]; + count: number | null; + total_ads: number | null; + current_page: number | null; + max_pages: number | null; + is_results_limit_reached: boolean | null; + agencies: Agency[]; + related_searches: RelatedSearch[]; + market: string; + source: string; +} + +/** Response for /agencies/{id}/listings. */ +export interface AgencyListingsResponse { + agency_id: number; + listings: Listing[]; + count: number | null; + page: number; + market: string; +} + +/** Response for /market-insights/prices (€/m² time series). */ +export interface PriceStatsResponse { + contract: string; + unit: string; + points: PriceStatsPoint[]; + market: string; +} + +/** Response for /markets (a bare array of markets). */ +export type MarketsResponse = Market[]; + +/** Response for /reference (filter enums accepted by /search). */ +export interface ReferenceResponse { + contracts: string[]; + categories: string[]; + sorts: string[]; +} + +// ============================================================================= +// Request parameter types +// ============================================================================= + +/** Supported Immobiliare-group market codes. */ +export type ImmobiliareMarketCode = "it" | "es" | "gr" | "lu"; + +/** Contract type for the /search endpoint. */ +export type ImmobiliareContract = "sale" | "rent"; + +/** Property category for the /search endpoint. */ +export type ImmobiliareCategory = + | "residential" + | "commercial" + | "garages" + | "offices" + | "land" + | "buildings" + | "warehouses"; + +/** Sort order for the /search endpoint. */ +export type ImmobiliareSort = + | "relevance" + | "price_asc" + | "price_desc" + | "newest" + | "oldest" + | "surface_desc" + | "surface_asc"; + +/** Parameters for the autocomplete() method. */ +export interface ImmobiliareAutocompleteParams { + /** Market (default: "it") */ + market?: ImmobiliareMarketCode; +} + +/** Parameters for the search() method. */ +export interface ImmobiliareSearchParams { + /** Market (default: "it") */ + market?: ImmobiliareMarketCode; + /** Free-text place (auto-resolved to geography ids) */ + location?: string; + /** Region id (fkRegione) from autocomplete() */ + region_id?: string; + /** Province id (idProvincia) from autocomplete() */ + province_id?: string; + /** City id (idComune) from autocomplete() */ + city_id?: string; + /** "sale" | "rent" (default: "sale") */ + contract?: ImmobiliareContract; + /** residential | commercial | garages | offices | land | buildings | warehouses (default: "residential") */ + category?: ImmobiliareCategory; + /** Minimum price filter (EUR) */ + price_min?: number; + /** Maximum price filter (EUR) */ + price_max?: number; + /** Minimum surface filter (m²) */ + surface_min?: number; + /** Maximum surface filter (m²) */ + surface_max?: number; + /** Minimum number of rooms */ + rooms_min?: number; + /** Maximum number of rooms */ + rooms_max?: number; + /** Minimum number of bathrooms */ + bathrooms_min?: number; + /** relevance | price_asc | price_desc | newest | oldest | surface_desc | surface_asc (default: "relevance") */ + sort?: ImmobiliareSort; + /** Page number (>= 1, default: 1) */ + page?: number; +} + +/** Parameters for the getListing() method. */ +export interface ImmobiliareListingParams { + /** Market (default: "it") */ + market?: ImmobiliareMarketCode; +} + +/** Parameters for the getAgency() method. */ +export interface ImmobiliareAgencyParams { + /** Market (default: "it") */ + market?: ImmobiliareMarketCode; +} + +/** Parameters for the getAgencyListings() method. */ +export interface ImmobiliareAgencyListingsParams { + /** Market (default: "it") */ + market?: ImmobiliareMarketCode; + /** Page number (>= 1, default: 1) */ + page?: number; +} + +/** Parameters for the priceStats() method. */ +export interface ImmobiliarePriceStatsParams { + /** Province id, e.g. "MI" */ + province_id?: string; + /** City id (idComune) */ + city_id?: string; + /** Market (default: "it") */ + market?: ImmobiliareMarketCode; + /** "sale" or "rent" (default: "sale") */ + contract?: "sale" | "rent"; +} diff --git a/src/index.ts b/src/index.ts index 2243f2a..e3bbfb4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -90,6 +90,9 @@ export * from "./youtube/index.js"; export * from "./realtor/index.js"; export * from "./zillow/index.js"; +// Re-export Immobiliare module — ImmobiliareClient + Immobiliare*-prefixed types +export * from "./immobiliare/index.js"; + // Re-export Leboncoin module — LeboncoinClient + Leboncoin*-prefixed sub-clients and types export * from "./leboncoin/index.js"; diff --git a/src/internal/version.ts b/src/internal/version.ts index 964b072..1223b7a 100644 --- a/src/internal/version.ts +++ b/src/internal/version.ts @@ -6,4 +6,4 @@ * * @internal */ -export const SDK_VERSION = "0.13.1"; +export const SDK_VERSION = "0.16.0";