Skip to main content

Client591

Struct Client591 

Source
pub struct Client591 { /* private fields */ }
Expand description

Client for the 591 rental platform public API.

Uses native HTTP (reqwest) — no browser required.

Implementations§

Source§

impl Client591

Source

pub fn new() -> Result<Self, TailFinError>

Create a new client.

Source

pub async fn hot( &self, region_id: u32, limit: usize, ) -> Result<Vec<Community>, TailFinError>

Fetch the hot community list for a given region.

region_id is the 591 region code (1 = Taipei City). Returns up to limit communities (the API ignores the limit param, so slicing is done client-side).

Source

pub async fn community( &self, id: u64, ) -> Result<Option<CommunityDetail>, TailFinError>

Fetch detailed info for a community by ID.

Source

pub async fn price_history( &self, id: u64, limit: usize, ) -> Result<Vec<PriceRecord>, TailFinError>

Fetch transaction price history for a community.

Returns recent actual sale prices recorded in the ROC government registry.

Source

pub async fn nearby( &self, id: u64, limit: usize, ) -> Result<Vec<NearbyCommunity>, TailFinError>

Fetch nearby communities (geographically close to id).

591 returns up to ~5 communities with sale-side stats (price per ping, min sale price, distance). Useful for “what else is in the same neighbourhood” queries; orthogonal to the rent-hot list (which is region-keyed, not community-keyed).

Source

pub async fn sale_list( &self, region_id: u32, first_row: usize, limit: usize, ) -> Result<SaleHousePage, TailFinError>

Paginated sale listings for a region.

Hits bff-house.591.com.tw/v1/web/sale/list. 591 paginates by firstRow (0-indexed offset, page size 30). The total in the returned SaleHousePage is the count of all matching listings across pages.

region_id is the same scheme as hot() (1 = Taipei).

Source

pub async fn newhouse_list( &self, region_id: u32, page: u32, ) -> Result<NewhousePage, TailFinError>

Paginated new-construction (newhouse) projects for a region.

Hits bff-newhouse.591.com.tw/v1/list-search. Pagination uses page (1-indexed); 591 returns 20 per page.

Source

pub async fn newhouse_base_info( &self, hid: u64, ) -> Result<NewhouseHousing, TailFinError>

Newhouse project detail core fields (build name, address, price, area, room layouts, build type, manager fees, dates, licenses, …).

Hits bff-newhouse.591.com.tw/v1/detail/base-info. Returns the curated NewhouseHousing subset of the wire data.housing block (80+ fields → ~25 most useful for project shopping). hid is the project ID — same as NewhouseProject.hid from newhouse_list.

Source

pub async fn newhouse_module_info( &self, hid: u64, ) -> Result<NewhouseModules, TailFinError>

Newhouse project module-info: floor plans, market history, listed sales agents.

Hits bff-newhouse.591.com.tw/v1/detail/module-info. Returns the curated NewhouseModules aggregate of layout, market, and sales. The news and report wire keys are dropped (mostly empty across the projects we sampled).

Source

pub async fn newhouse_photos( &self, hid: u64, ) -> Result<Vec<NewhousePhotoCategory>, TailFinError>

Newhouse project photo gallery, organized by category.

Hits bff-newhouse.591.com.tw/v1/detail/photos. Returns a Vec of NewhousePhotoCategory (cover / floor plan / traffic / 3D / real-life / environment) — empty Vec if the project has no photos uploaded.

Source

pub async fn newhouse_surrounding( &self, hid: u64, ) -> Result<NewhouseSurrounding, TailFinError>

Newhouse project’s surrounding POIs (transit, schools, life amenities) plus building/sales-office geo coordinates.

Hits bff-newhouse.591.com.tw/v1/detail/surrounding.

Source

pub async fn newhouse_nearby_market( &self, hid: u64, ) -> Result<NewhouseNearbyMarket, TailFinError>

Newhouse project’s nearby resale comps (other communities) and business districts (商圈) with average prices.

Hits bff-newhouse.591.com.tw/v1/detail/nearby-market. Note the URL param is hid (not id) on this endpoint — separate query semantics from the id-keyed siblings.

Source

pub async fn newhouse_price_list( &self, hid: u64, ) -> Result<NewhousePriceList, TailFinError>

Newhouse project price-list (per-unit catalogue + sale-control metadata).

Hits bff-newhouse.591.com.tw/v1/price/list with trans_type fixed at 1 (sale price; trans_type=2 returns rental-side data for the same projects, not exercised here). Often less rich than newhouse_module_info().market for active pre-sale projects — call both and prefer module-info’s market block for actual transaction history.

Source

pub async fn newhouse_detail(&self, hid: u64) -> NewhouseDetail

Fetch the full newhouse-detail bundle in parallel.

Calls all 6 detail sub-endpoints (base-info, module-info, photos, surrounding, nearby-market, price/list) concurrently via tokio::join!. Each sub-call’s success or failure lands independently in the corresponding bundle field — partial failures don’t fail the whole bundle. Returns once all 6 calls have settled.

Typical wall-clock: ~350ms (the slowest sub-call dominates) — roughly 4× faster than calling the 6 atomic methods serially.

Source

pub async fn community_rank( &self, region_id: u32, ) -> Result<CommunityRanks, TailFinError>

Two community rankings for a region — by price metric and by sale-activity metric. Hits bff.591.com.tw/v1/community/community-rank. Each slot holds up to ~10 communities; both share the same time snapshot.

Pure-HTTP rental listing search via bff-house.591.com.tw/v3/web/rent/list.

No browser, no CSRF, no cookies required — verified 2026-04-30 against a clean curl from a fresh process. See docs/superpowers/research/2026-04-30-591-har-discovery.md.

Pagination is by params.first_row (page size 30). The server echoes firstRow: 0 even on later pages; trust your input offset, not the response field.

Returns (total_matching, listings) where total_matching is the global count across all pages (NOT this page’s length).

Source

pub async fn rent_crawl<F>( &self, params: &SearchParams, opts: &CrawlOptions, on_page: F, ) -> Result<usize, TailFinError>
where F: FnMut(usize, usize, &[SearchListing]),

Crawl all rental listings matching params, paginating automatically via /v3/web/rent/list.

Calls on_page(page_num, first_row, listings) after each page. Stops when a page returns fewer than RENT_PAGE_SIZE items (last page reached) or after opts.max_pages pages if non-zero.

Returns the total number of listings the callback received.

Curated premium-listing search.

POSTs JSON to bff-house.591.com.tw/v1/high-value/search. 591 returns a small (~6 items) hand-curated pool of premium sale listings; the request-side kind/type/section_id/ shape/room/price/area filters are “preferred” rather than strict (591’s curation logic decides the final set).

Two kind values populate distinct curated pools: 9 (the default, mostly residential) and 10 (a separate bucket with different streets / post_ids). Verified live 2026-04-30 — see HighValueParams for the full quirk catalog. Use HighValueParams::for_region for the typical defaults (kind=9, type=2).

Source

pub async fn coordinate_area( &self, latitude: f64, longitude: f64, region_id: u32, ) -> Result<Option<CoordArea>, TailFinError>

Reverse-geocode GPS coordinates to a 591 region+section.

Hits bff.591.com.tw/v1/coordinate/area. Returns Some(area) when the coordinates land inside Taiwan, None otherwise (591 responds with status: 0 and the message "坐标不在台湾范围内").

region_id is sent as a hint but doesn’t constrain the response — the server resolves the actual region/section from the lat/lng. Pass any valid region (1 = Taipei is a safe default).

Status-handling differs from sibling endpoints. Most Client591 methods collapse non-1 status into either an error or Ok(None); this method instead matches 1Ok(Some(_)), 0Ok(None) (documented miss), anything else → Err. The intent: a future server-side wire-state change becomes loud-fail rather than silently swallowed.

Source

pub async fn rent_detail( &self, post_id: u64, ) -> Result<RentDetail, TailFinError>

Single rent listing detail.

Hits bff-house.591.com.tw/v2/web/rent/detail. Returns the curated RentDetail subset of the wire data block — full address with lat/lng, deposit + cost breakdown, structured houseInfo / preference / service / surround sections, plus the listing’s primary contact (RentLinkInfo).

post_id is the rent listing ID (SearchListing.post_id from rent_search). Pure HTTP — no browser, no auth.

Source

pub async fn rent_photos( &self, post_id: u64, ) -> Result<Vec<RentPhotoGroup>, TailFinError>

Categorized photo gallery for a rent listing.

Hits bff-house.591.com.tw/v1/ware/photos. Returns a Vec of RentPhotoGroup (photo buckets like "picture", "floor", "environment"); empty Vec if the listing has no photos. type=1 is hardcoded on the wire — it’s the rent-side photo query mode (verified live 2026-04-30).

Source

pub async fn sale_detail( &self, post_id: u64, ) -> Result<SaleDetail, TailFinError>

Single sale listing detail.

Hits bff-house.591.com.tw/v1/touch/sale/detail. Returns the curated SaleDetail subset of the wire data block — title, price (raw + numeric), area / layout / floor / shape, lat / lng, age, fitment, agent contact (linkman, mobile, telephone, email, identity, company_name).

post_id is the bare numeric sale listing ID. The wire expects an S-prefixed form ("S19599759") — the adapter adds the prefix for you. Uses device_id=tail-fin-rust-client and device=touch query params; verified live 2026-05-01 that the endpoint returns status: 0 without a non-empty device_id.

Source

pub async fn sale_similar_wares( &self, post_id: u64, ) -> Result<Vec<SaleSimilarWare>, TailFinError>

Similar sale listings — curated “you might also like” set.

Hits bff-house.591.com.tw/v2/web/sale/similar-wares. Returns up to ~5 SaleSimilarWare entries (compact title / price / area / room / cover-photo URL) for cross-listing browse. Anonymous, no special headers needed.

Source

pub async fn sales( &self, id: u64, limit: usize, ) -> Result<(u32, Vec<SaleListing>), TailFinError>

Fetch active sale listings near a community.

Listings are flattened across all room types, sorted by post time (API order).

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more