lacspace-leads 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.d.cts CHANGED
@@ -1,9 +1,17 @@
1
1
  /** The fields a {@link Lead} can carry — request any subset via `fields`. */
2
- type LeadField = "name" | "category" | "rating" | "reviews" | "priceLevel" | "address" | "phone" | "website" | "email" | "facebook" | "instagram" | "whatsapp" | "plusCode" | "latitude" | "longitude" | "hours" | "mapsUrl";
2
+ type LeadField = "name" | "category" | "rating" | "reviews" | "priceLevel" | "address" | "phone" | "website" | "email" | "facebook" | "instagram" | "whatsapp" | "linkedin" | "twitter" | "youtube" | "tiktok" | "telegram" | "plusCode" | "latitude" | "longitude" | "hours" | "mapsUrl";
3
3
  /** Every field, in a sensible column order for exports. */
4
4
  declare const ALL_FIELDS: LeadField[];
5
5
  /** Fields that require visiting the business website (via `enrich`). */
6
6
  declare const ENRICHED_FIELDS: LeadField[];
7
+ /** Named field bundles for common jobs — pass via `preset` / `--preset`. */
8
+ declare const FIELD_PRESETS: Record<string, LeadField[]>;
9
+ /**
10
+ * Fields collected by default — everything Google Maps shows directly, i.e.
11
+ * {@link ALL_FIELDS} minus the {@link ENRICHED_FIELDS} that need a website visit.
12
+ * Ask for the enriched ones explicitly (or via `enrich`) to opt into that work.
13
+ */
14
+ declare const DEFAULT_FIELDS: LeadField[];
7
15
  /** A single collected business lead. Every field is optional — Maps listings vary. */
8
16
  interface Lead {
9
17
  name?: string;
@@ -25,6 +33,16 @@ interface Lead {
25
33
  instagram?: string;
26
34
  /** WhatsApp number/link, from the website (needs enrichment). */
27
35
  whatsapp?: string;
36
+ /** LinkedIn company/profile URL, from the website (needs enrichment). */
37
+ linkedin?: string;
38
+ /** Twitter / X URL, from the website (needs enrichment). */
39
+ twitter?: string;
40
+ /** YouTube channel URL, from the website (needs enrichment). */
41
+ youtube?: string;
42
+ /** TikTok URL, from the website (needs enrichment). */
43
+ tiktok?: string;
44
+ /** Telegram link, from the website (needs enrichment). */
45
+ telegram?: string;
28
46
  /** Google Plus Code, when shown. */
29
47
  plusCode?: string;
30
48
  /** Latitude, parsed from the listing's Maps URL. */
@@ -50,7 +68,9 @@ interface LeadFilters {
50
68
  hasEmail?: boolean;
51
69
  }
52
70
  /** Output formats the tool can write. */
53
- type OutputFormat = "json" | "csv" | "xlsx";
71
+ type OutputFormat = "json" | "ndjson" | "csv" | "xlsx";
72
+ /** How to sort collected leads before export. */
73
+ type SortKey$1 = "rating" | "reviews" | "name" | "priceLevel";
54
74
  /** Options for a lead search. */
55
75
  interface SearchOptions {
56
76
  /** City, e.g. "Kathmandu". */
@@ -85,6 +105,27 @@ interface SearchOptions {
85
105
  filters?: LeadFilters;
86
106
  /** Drop duplicate leads by this key. Default "website" when present else "name". */
87
107
  dedupe?: "website" | "phone" | "name" | "none";
108
+ /** Sort the results by this key before returning. */
109
+ sort?: SortKey$1;
110
+ /** Sort direction. Defaults to "desc" for numbers, "asc" for name. */
111
+ sortDir?: "asc" | "desc";
112
+ /**
113
+ * Normalise phone numbers to E.164 using this default country — an ISO-2 code
114
+ * (`"NP"`, `"US"`) or a raw calling code (`"977"`). Best-effort; numbers that
115
+ * can't be parsed are left as-is.
116
+ */
117
+ country?: string;
118
+ /**
119
+ * Tidy website URLs — unwrap Google redirects and strip tracking params.
120
+ * Default true.
121
+ */
122
+ cleanUrls?: boolean;
123
+ /** How many websites to enrich in parallel. Default 3. */
124
+ concurrency?: number;
125
+ /** Browser/UI locale, e.g. "en-US", "ne-NP". Default "en-US". */
126
+ locale?: string;
127
+ /** Google region bias (ccTLD-style), e.g. "np", "us". Sets Maps `gl`. */
128
+ region?: string;
88
129
  /** Stop collecting after this many milliseconds (best-effort). */
89
130
  maxMs?: number;
90
131
  /** Called with a short progress message as the search runs. */
@@ -122,6 +163,32 @@ declare function parseLatLng(url: string): {
122
163
  */
123
164
  declare function scrapeLeads(opts: SearchOptions): Promise<Lead[]>;
124
165
 
166
+ /** One sub-search in a batch. */
167
+ interface BatchQuery {
168
+ type?: string;
169
+ city?: string;
170
+ area?: string;
171
+ query?: string;
172
+ }
173
+ /**
174
+ * Run each `{type, city, area}` search in turn and merge the results, applying
175
+ * a single cross-search dedupe and sort at the end. Per-search `limit` is the
176
+ * cap for *each* sub-search; `opts.total`, when set, caps the merged list.
177
+ *
178
+ * Filtering and sorting are applied once, globally, here — so pass them via
179
+ * `opts` rather than relying on the per-search pass.
180
+ */
181
+ declare function searchLeadsBatch(queries: BatchQuery[], opts?: Omit<SearchOptions, "type" | "city" | "area" | "query"> & {
182
+ total?: number;
183
+ }): Promise<Lead[]>;
184
+ /**
185
+ * Convenience wrapper: expand a single request with comma-separated
186
+ * `type`/`city`/`area` into its cross-product and run it as a batch.
187
+ */
188
+ declare function searchLeadsMulti(opts: SearchOptions & {
189
+ total?: number;
190
+ }): Promise<Lead[]>;
191
+
125
192
  /** Project leads onto exactly the requested fields, in order, as header-keyed rows. */
126
193
  declare function toRows(leads: Lead[], fields?: LeadField[]): Record<string, string | number>[];
127
194
  /** Serialize leads to a UTF-8 string or bytes in the chosen format. */
@@ -142,8 +209,32 @@ declare function composeQuery(opts: {
142
209
  area?: string;
143
210
  city?: string;
144
211
  }): string;
145
- /** The Google Maps search URL for a query. `hl=en` keeps labels predictable. */
146
- declare function mapsSearchUrl(query: string): string;
212
+ /**
213
+ * The Google Maps search URL for a query. `hl` (interface language) keeps the
214
+ * scraped aria-labels predictable; `gl` biases results to a region.
215
+ */
216
+ declare function mapsSearchUrl(query: string, opts?: {
217
+ hl?: string;
218
+ gl?: string;
219
+ }): string;
220
+ /**
221
+ * Expand a possibly-multi search request into individual `{type, city, area}`
222
+ * queries — the cross-product of comma-separated types × cities × areas. An
223
+ * explicit `query` short-circuits to a single verbatim search. Used for batch.
224
+ */
225
+ declare function expandQueries(opts: {
226
+ type?: string;
227
+ city?: string;
228
+ area?: string;
229
+ query?: string;
230
+ }): {
231
+ type?: string;
232
+ city?: string;
233
+ area?: string;
234
+ query?: string;
235
+ }[];
236
+ /** Resolve a preset name to its field list, case-insensitively. */
237
+ declare function resolvePreset(name?: string): LeadField[] | undefined;
147
238
  /**
148
239
  * Normalise a fields request (array or comma string) into known {@link LeadField}s
149
240
  * in canonical order, de-duplicated. Unknown names are ignored; an empty result
@@ -161,6 +252,41 @@ declare function filterLeads(leads: Lead[], filters?: LeadFilters): Lead[];
161
252
  */
162
253
  declare function dedupeLeads(leads: Lead[], by?: NonNullable<SearchOptions["dedupe"]>): Lead[];
163
254
 
255
+ /**
256
+ * Pure data-cleaning helpers — website tidy-up, best-effort E.164 phone
257
+ * normalisation, and lead sorting. All exported so they can be unit-tested and
258
+ * reused; none touch the network or the disk.
259
+ */
260
+
261
+ /**
262
+ * Tidy a website URL: unwrap a Google `/url?q=…` redirect, drop tracking query
263
+ * params (utm_*, fbclid, gclid…) and any fragment, and lower-case the host.
264
+ * Returns `undefined` for empty input, and the original string if it can't be
265
+ * parsed as a URL. Pure.
266
+ */
267
+ declare function cleanWebsite(url?: string): string | undefined;
268
+ /**
269
+ * ISO-3166 alpha-2 → E.164 country calling code, for a curated set of common
270
+ * markets. Extend as needed; unknown codes fall through to best-effort.
271
+ */
272
+ declare const CALLING_CODES: Record<string, string>;
273
+ /** Resolve a country argument (ISO-2 code or a raw calling code) to digits. */
274
+ declare function callingCode(country?: string): string | undefined;
275
+ /**
276
+ * Best-effort E.164 normalisation of a phone string given a default country
277
+ * (an ISO-2 code like `"NP"` or a calling code like `"977"`). Numbers that
278
+ * already start with `+` are kept as international. When the result isn't a
279
+ * plausible 8–15-digit number, the original string is returned unchanged. Pure.
280
+ */
281
+ declare function normalizePhone(raw?: string, country?: string): string | undefined;
282
+ /** Keys a lead list can be sorted by. */
283
+ type SortKey = "rating" | "reviews" | "name" | "priceLevel";
284
+ /**
285
+ * Sort leads by a key, missing values always last. Stable, pure, new array.
286
+ * `dir` defaults to descending for numeric keys and ascending for `name`.
287
+ */
288
+ declare function sortLeads(leads: Lead[], by?: SortKey, dir?: "asc" | "desc"): Lead[];
289
+
164
290
  /**
165
291
  * Website enrichment — visit a business's site and pull an email + social
166
292
  * links. Uses global `fetch` (Node 20+); every network call is guarded so a
@@ -172,10 +298,18 @@ interface Contacts {
172
298
  facebook?: string;
173
299
  instagram?: string;
174
300
  whatsapp?: string;
301
+ linkedin?: string;
302
+ twitter?: string;
303
+ youtube?: string;
304
+ tiktok?: string;
305
+ telegram?: string;
175
306
  }
176
307
  /** Extract the best contact email from HTML, or `undefined`. Pure. */
177
308
  declare function extractEmails(html: string): string | undefined;
178
- /** Extract Facebook / Instagram / WhatsApp links from HTML. Pure. */
309
+ /**
310
+ * Extract social links (Facebook, Instagram, WhatsApp, LinkedIn, Twitter/X,
311
+ * YouTube, TikTok, Telegram) from HTML. Skips share/intent/widget links. Pure.
312
+ */
179
313
  declare function extractSocials(html: string): Omit<Contacts, "email">;
180
314
  /**
181
315
  * Fetch a website (home page, then a /contact page if needed) and pull an email
@@ -212,4 +346,4 @@ declare function convertFile(input: string, opts?: {
212
346
  count: number;
213
347
  }>;
214
348
 
215
- export { ALL_FIELDS, type Contacts, type DataRow, ENRICHED_FIELDS, type Lead, type LeadField, type LeadFilters, LeadsError, type OutputFormat, type SearchOptions, columnsOf, composeQuery, convertFile, dedupeLeads, defaultFilename, detectFormat, enrichContacts, extractEmails, extractSocials, filterLeads, mapsSearchUrl, normalizeFields, parseLatLng, parseRating, parseReviewCount, readRows, scrapeLeads, scrapeLeads as searchLeads, serialize, serializeRows, toRows };
349
+ export { ALL_FIELDS, type BatchQuery, CALLING_CODES, type Contacts, DEFAULT_FIELDS, type DataRow, ENRICHED_FIELDS, FIELD_PRESETS, type Lead, type LeadField, type LeadFilters, LeadsError, type OutputFormat, type SearchOptions, type SortKey$1 as SortKey, callingCode, cleanWebsite, columnsOf, composeQuery, convertFile, dedupeLeads, defaultFilename, detectFormat, enrichContacts, expandQueries, extractEmails, extractSocials, filterLeads, mapsSearchUrl, normalizeFields, normalizePhone, parseLatLng, parseRating, parseReviewCount, readRows, resolvePreset, scrapeLeads, scrapeLeads as searchLeads, searchLeadsBatch, searchLeadsMulti, serialize, serializeRows, sortLeads, toRows };
package/dist/lib.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  /** The fields a {@link Lead} can carry — request any subset via `fields`. */
2
- type LeadField = "name" | "category" | "rating" | "reviews" | "priceLevel" | "address" | "phone" | "website" | "email" | "facebook" | "instagram" | "whatsapp" | "plusCode" | "latitude" | "longitude" | "hours" | "mapsUrl";
2
+ type LeadField = "name" | "category" | "rating" | "reviews" | "priceLevel" | "address" | "phone" | "website" | "email" | "facebook" | "instagram" | "whatsapp" | "linkedin" | "twitter" | "youtube" | "tiktok" | "telegram" | "plusCode" | "latitude" | "longitude" | "hours" | "mapsUrl";
3
3
  /** Every field, in a sensible column order for exports. */
4
4
  declare const ALL_FIELDS: LeadField[];
5
5
  /** Fields that require visiting the business website (via `enrich`). */
6
6
  declare const ENRICHED_FIELDS: LeadField[];
7
+ /** Named field bundles for common jobs — pass via `preset` / `--preset`. */
8
+ declare const FIELD_PRESETS: Record<string, LeadField[]>;
9
+ /**
10
+ * Fields collected by default — everything Google Maps shows directly, i.e.
11
+ * {@link ALL_FIELDS} minus the {@link ENRICHED_FIELDS} that need a website visit.
12
+ * Ask for the enriched ones explicitly (or via `enrich`) to opt into that work.
13
+ */
14
+ declare const DEFAULT_FIELDS: LeadField[];
7
15
  /** A single collected business lead. Every field is optional — Maps listings vary. */
8
16
  interface Lead {
9
17
  name?: string;
@@ -25,6 +33,16 @@ interface Lead {
25
33
  instagram?: string;
26
34
  /** WhatsApp number/link, from the website (needs enrichment). */
27
35
  whatsapp?: string;
36
+ /** LinkedIn company/profile URL, from the website (needs enrichment). */
37
+ linkedin?: string;
38
+ /** Twitter / X URL, from the website (needs enrichment). */
39
+ twitter?: string;
40
+ /** YouTube channel URL, from the website (needs enrichment). */
41
+ youtube?: string;
42
+ /** TikTok URL, from the website (needs enrichment). */
43
+ tiktok?: string;
44
+ /** Telegram link, from the website (needs enrichment). */
45
+ telegram?: string;
28
46
  /** Google Plus Code, when shown. */
29
47
  plusCode?: string;
30
48
  /** Latitude, parsed from the listing's Maps URL. */
@@ -50,7 +68,9 @@ interface LeadFilters {
50
68
  hasEmail?: boolean;
51
69
  }
52
70
  /** Output formats the tool can write. */
53
- type OutputFormat = "json" | "csv" | "xlsx";
71
+ type OutputFormat = "json" | "ndjson" | "csv" | "xlsx";
72
+ /** How to sort collected leads before export. */
73
+ type SortKey$1 = "rating" | "reviews" | "name" | "priceLevel";
54
74
  /** Options for a lead search. */
55
75
  interface SearchOptions {
56
76
  /** City, e.g. "Kathmandu". */
@@ -85,6 +105,27 @@ interface SearchOptions {
85
105
  filters?: LeadFilters;
86
106
  /** Drop duplicate leads by this key. Default "website" when present else "name". */
87
107
  dedupe?: "website" | "phone" | "name" | "none";
108
+ /** Sort the results by this key before returning. */
109
+ sort?: SortKey$1;
110
+ /** Sort direction. Defaults to "desc" for numbers, "asc" for name. */
111
+ sortDir?: "asc" | "desc";
112
+ /**
113
+ * Normalise phone numbers to E.164 using this default country — an ISO-2 code
114
+ * (`"NP"`, `"US"`) or a raw calling code (`"977"`). Best-effort; numbers that
115
+ * can't be parsed are left as-is.
116
+ */
117
+ country?: string;
118
+ /**
119
+ * Tidy website URLs — unwrap Google redirects and strip tracking params.
120
+ * Default true.
121
+ */
122
+ cleanUrls?: boolean;
123
+ /** How many websites to enrich in parallel. Default 3. */
124
+ concurrency?: number;
125
+ /** Browser/UI locale, e.g. "en-US", "ne-NP". Default "en-US". */
126
+ locale?: string;
127
+ /** Google region bias (ccTLD-style), e.g. "np", "us". Sets Maps `gl`. */
128
+ region?: string;
88
129
  /** Stop collecting after this many milliseconds (best-effort). */
89
130
  maxMs?: number;
90
131
  /** Called with a short progress message as the search runs. */
@@ -122,6 +163,32 @@ declare function parseLatLng(url: string): {
122
163
  */
123
164
  declare function scrapeLeads(opts: SearchOptions): Promise<Lead[]>;
124
165
 
166
+ /** One sub-search in a batch. */
167
+ interface BatchQuery {
168
+ type?: string;
169
+ city?: string;
170
+ area?: string;
171
+ query?: string;
172
+ }
173
+ /**
174
+ * Run each `{type, city, area}` search in turn and merge the results, applying
175
+ * a single cross-search dedupe and sort at the end. Per-search `limit` is the
176
+ * cap for *each* sub-search; `opts.total`, when set, caps the merged list.
177
+ *
178
+ * Filtering and sorting are applied once, globally, here — so pass them via
179
+ * `opts` rather than relying on the per-search pass.
180
+ */
181
+ declare function searchLeadsBatch(queries: BatchQuery[], opts?: Omit<SearchOptions, "type" | "city" | "area" | "query"> & {
182
+ total?: number;
183
+ }): Promise<Lead[]>;
184
+ /**
185
+ * Convenience wrapper: expand a single request with comma-separated
186
+ * `type`/`city`/`area` into its cross-product and run it as a batch.
187
+ */
188
+ declare function searchLeadsMulti(opts: SearchOptions & {
189
+ total?: number;
190
+ }): Promise<Lead[]>;
191
+
125
192
  /** Project leads onto exactly the requested fields, in order, as header-keyed rows. */
126
193
  declare function toRows(leads: Lead[], fields?: LeadField[]): Record<string, string | number>[];
127
194
  /** Serialize leads to a UTF-8 string or bytes in the chosen format. */
@@ -142,8 +209,32 @@ declare function composeQuery(opts: {
142
209
  area?: string;
143
210
  city?: string;
144
211
  }): string;
145
- /** The Google Maps search URL for a query. `hl=en` keeps labels predictable. */
146
- declare function mapsSearchUrl(query: string): string;
212
+ /**
213
+ * The Google Maps search URL for a query. `hl` (interface language) keeps the
214
+ * scraped aria-labels predictable; `gl` biases results to a region.
215
+ */
216
+ declare function mapsSearchUrl(query: string, opts?: {
217
+ hl?: string;
218
+ gl?: string;
219
+ }): string;
220
+ /**
221
+ * Expand a possibly-multi search request into individual `{type, city, area}`
222
+ * queries — the cross-product of comma-separated types × cities × areas. An
223
+ * explicit `query` short-circuits to a single verbatim search. Used for batch.
224
+ */
225
+ declare function expandQueries(opts: {
226
+ type?: string;
227
+ city?: string;
228
+ area?: string;
229
+ query?: string;
230
+ }): {
231
+ type?: string;
232
+ city?: string;
233
+ area?: string;
234
+ query?: string;
235
+ }[];
236
+ /** Resolve a preset name to its field list, case-insensitively. */
237
+ declare function resolvePreset(name?: string): LeadField[] | undefined;
147
238
  /**
148
239
  * Normalise a fields request (array or comma string) into known {@link LeadField}s
149
240
  * in canonical order, de-duplicated. Unknown names are ignored; an empty result
@@ -161,6 +252,41 @@ declare function filterLeads(leads: Lead[], filters?: LeadFilters): Lead[];
161
252
  */
162
253
  declare function dedupeLeads(leads: Lead[], by?: NonNullable<SearchOptions["dedupe"]>): Lead[];
163
254
 
255
+ /**
256
+ * Pure data-cleaning helpers — website tidy-up, best-effort E.164 phone
257
+ * normalisation, and lead sorting. All exported so they can be unit-tested and
258
+ * reused; none touch the network or the disk.
259
+ */
260
+
261
+ /**
262
+ * Tidy a website URL: unwrap a Google `/url?q=…` redirect, drop tracking query
263
+ * params (utm_*, fbclid, gclid…) and any fragment, and lower-case the host.
264
+ * Returns `undefined` for empty input, and the original string if it can't be
265
+ * parsed as a URL. Pure.
266
+ */
267
+ declare function cleanWebsite(url?: string): string | undefined;
268
+ /**
269
+ * ISO-3166 alpha-2 → E.164 country calling code, for a curated set of common
270
+ * markets. Extend as needed; unknown codes fall through to best-effort.
271
+ */
272
+ declare const CALLING_CODES: Record<string, string>;
273
+ /** Resolve a country argument (ISO-2 code or a raw calling code) to digits. */
274
+ declare function callingCode(country?: string): string | undefined;
275
+ /**
276
+ * Best-effort E.164 normalisation of a phone string given a default country
277
+ * (an ISO-2 code like `"NP"` or a calling code like `"977"`). Numbers that
278
+ * already start with `+` are kept as international. When the result isn't a
279
+ * plausible 8–15-digit number, the original string is returned unchanged. Pure.
280
+ */
281
+ declare function normalizePhone(raw?: string, country?: string): string | undefined;
282
+ /** Keys a lead list can be sorted by. */
283
+ type SortKey = "rating" | "reviews" | "name" | "priceLevel";
284
+ /**
285
+ * Sort leads by a key, missing values always last. Stable, pure, new array.
286
+ * `dir` defaults to descending for numeric keys and ascending for `name`.
287
+ */
288
+ declare function sortLeads(leads: Lead[], by?: SortKey, dir?: "asc" | "desc"): Lead[];
289
+
164
290
  /**
165
291
  * Website enrichment — visit a business's site and pull an email + social
166
292
  * links. Uses global `fetch` (Node 20+); every network call is guarded so a
@@ -172,10 +298,18 @@ interface Contacts {
172
298
  facebook?: string;
173
299
  instagram?: string;
174
300
  whatsapp?: string;
301
+ linkedin?: string;
302
+ twitter?: string;
303
+ youtube?: string;
304
+ tiktok?: string;
305
+ telegram?: string;
175
306
  }
176
307
  /** Extract the best contact email from HTML, or `undefined`. Pure. */
177
308
  declare function extractEmails(html: string): string | undefined;
178
- /** Extract Facebook / Instagram / WhatsApp links from HTML. Pure. */
309
+ /**
310
+ * Extract social links (Facebook, Instagram, WhatsApp, LinkedIn, Twitter/X,
311
+ * YouTube, TikTok, Telegram) from HTML. Skips share/intent/widget links. Pure.
312
+ */
179
313
  declare function extractSocials(html: string): Omit<Contacts, "email">;
180
314
  /**
181
315
  * Fetch a website (home page, then a /contact page if needed) and pull an email
@@ -212,4 +346,4 @@ declare function convertFile(input: string, opts?: {
212
346
  count: number;
213
347
  }>;
214
348
 
215
- export { ALL_FIELDS, type Contacts, type DataRow, ENRICHED_FIELDS, type Lead, type LeadField, type LeadFilters, LeadsError, type OutputFormat, type SearchOptions, columnsOf, composeQuery, convertFile, dedupeLeads, defaultFilename, detectFormat, enrichContacts, extractEmails, extractSocials, filterLeads, mapsSearchUrl, normalizeFields, parseLatLng, parseRating, parseReviewCount, readRows, scrapeLeads, scrapeLeads as searchLeads, serialize, serializeRows, toRows };
349
+ export { ALL_FIELDS, type BatchQuery, CALLING_CODES, type Contacts, DEFAULT_FIELDS, type DataRow, ENRICHED_FIELDS, FIELD_PRESETS, type Lead, type LeadField, type LeadFilters, LeadsError, type OutputFormat, type SearchOptions, type SortKey$1 as SortKey, callingCode, cleanWebsite, columnsOf, composeQuery, convertFile, dedupeLeads, defaultFilename, detectFormat, enrichContacts, expandQueries, extractEmails, extractSocials, filterLeads, mapsSearchUrl, normalizeFields, normalizePhone, parseLatLng, parseRating, parseReviewCount, readRows, resolvePreset, scrapeLeads, scrapeLeads as searchLeads, searchLeadsBatch, searchLeadsMulti, serialize, serializeRows, sortLeads, toRows };