@mradex77/google-play-scraper 0.1.1

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/index.js ADDED
@@ -0,0 +1,3253 @@
1
+ import { z } from "zod";
2
+ import * as cheerio from "cheerio";
3
+ import { LRUCache } from "lru-cache";
4
+ //#region src/constants.ts
5
+ const BASE_URL = "https://play.google.com";
6
+ const clusters = {
7
+ new: "new",
8
+ top: "top"
9
+ };
10
+ const category = {
11
+ APPLICATION: "APPLICATION",
12
+ ANDROID_WEAR: "ANDROID_WEAR",
13
+ ART_AND_DESIGN: "ART_AND_DESIGN",
14
+ AUTO_AND_VEHICLES: "AUTO_AND_VEHICLES",
15
+ BEAUTY: "BEAUTY",
16
+ BOOKS_AND_REFERENCE: "BOOKS_AND_REFERENCE",
17
+ BUSINESS: "BUSINESS",
18
+ COMICS: "COMICS",
19
+ COMMUNICATION: "COMMUNICATION",
20
+ DATING: "DATING",
21
+ EDUCATION: "EDUCATION",
22
+ ENTERTAINMENT: "ENTERTAINMENT",
23
+ EVENTS: "EVENTS",
24
+ FINANCE: "FINANCE",
25
+ FOOD_AND_DRINK: "FOOD_AND_DRINK",
26
+ HEALTH_AND_FITNESS: "HEALTH_AND_FITNESS",
27
+ HOUSE_AND_HOME: "HOUSE_AND_HOME",
28
+ LIBRARIES_AND_DEMO: "LIBRARIES_AND_DEMO",
29
+ LIFESTYLE: "LIFESTYLE",
30
+ MAPS_AND_NAVIGATION: "MAPS_AND_NAVIGATION",
31
+ MEDICAL: "MEDICAL",
32
+ MUSIC_AND_AUDIO: "MUSIC_AND_AUDIO",
33
+ NEWS_AND_MAGAZINES: "NEWS_AND_MAGAZINES",
34
+ PARENTING: "PARENTING",
35
+ PERSONALIZATION: "PERSONALIZATION",
36
+ PHOTOGRAPHY: "PHOTOGRAPHY",
37
+ PRODUCTIVITY: "PRODUCTIVITY",
38
+ SHOPPING: "SHOPPING",
39
+ SOCIAL: "SOCIAL",
40
+ SPORTS: "SPORTS",
41
+ TOOLS: "TOOLS",
42
+ TRAVEL_AND_LOCAL: "TRAVEL_AND_LOCAL",
43
+ VIDEO_PLAYERS: "VIDEO_PLAYERS",
44
+ WATCH_FACE: "WATCH_FACE",
45
+ WEATHER: "WEATHER",
46
+ GAME: "GAME",
47
+ GAME_ACTION: "GAME_ACTION",
48
+ GAME_ADVENTURE: "GAME_ADVENTURE",
49
+ GAME_ARCADE: "GAME_ARCADE",
50
+ GAME_BOARD: "GAME_BOARD",
51
+ GAME_CARD: "GAME_CARD",
52
+ GAME_CASINO: "GAME_CASINO",
53
+ GAME_CASUAL: "GAME_CASUAL",
54
+ GAME_EDUCATIONAL: "GAME_EDUCATIONAL",
55
+ GAME_MUSIC: "GAME_MUSIC",
56
+ GAME_PUZZLE: "GAME_PUZZLE",
57
+ GAME_RACING: "GAME_RACING",
58
+ GAME_ROLE_PLAYING: "GAME_ROLE_PLAYING",
59
+ GAME_SIMULATION: "GAME_SIMULATION",
60
+ GAME_SPORTS: "GAME_SPORTS",
61
+ GAME_STRATEGY: "GAME_STRATEGY",
62
+ GAME_TRIVIA: "GAME_TRIVIA",
63
+ GAME_WORD: "GAME_WORD",
64
+ FAMILY: "FAMILY",
65
+ FAMILY_ACTION: "FAMILY_ACTION",
66
+ FAMILY_BRAINGAMES: "FAMILY_BRAINGAMES",
67
+ FAMILY_CREATE: "FAMILY_CREATE",
68
+ FAMILY_EDUCATION: "FAMILY_EDUCATION",
69
+ FAMILY_MUSICVIDEO: "FAMILY_MUSICVIDEO",
70
+ FAMILY_PRETEND: "FAMILY_PRETEND"
71
+ };
72
+ const collection = {
73
+ TOP_FREE: "TOP_FREE",
74
+ TOP_PAID: "TOP_PAID",
75
+ GROSSING: "GROSSING"
76
+ };
77
+ const sort = {
78
+ NEWEST: 2,
79
+ RATING: 3,
80
+ HELPFULNESS: 1
81
+ };
82
+ const age = {
83
+ FIVE_UNDER: "AGE_RANGE1",
84
+ SIX_EIGHT: "AGE_RANGE2",
85
+ NINE_UP: "AGE_RANGE3"
86
+ };
87
+ const permission = {
88
+ COMMON: 0,
89
+ OTHER: 1
90
+ };
91
+ Object.freeze(clusters);
92
+ Object.freeze(category);
93
+ Object.freeze(collection);
94
+ Object.freeze(sort);
95
+ Object.freeze(age);
96
+ Object.freeze(permission);
97
+ //#endregion
98
+ //#region src/core/errors.ts
99
+ var GooglePlayError = class extends Error {
100
+ constructor(message) {
101
+ super(message);
102
+ this.name = "GooglePlayError";
103
+ }
104
+ };
105
+ var ValidationError = class ValidationError extends GooglePlayError {
106
+ constructor(message) {
107
+ super(message);
108
+ this.name = "ValidationError";
109
+ }
110
+ static fromZod(error, context) {
111
+ const details = error.issues.map((issue) => {
112
+ const path = issue.path.join(".");
113
+ return path ? `${path}: ${issue.message}` : issue.message;
114
+ }).join("; ");
115
+ return new ValidationError(`${context}: ${details}`);
116
+ }
117
+ };
118
+ var HttpError = class extends GooglePlayError {
119
+ status;
120
+ url;
121
+ constructor(message, status, url) {
122
+ super(message);
123
+ this.name = "HttpError";
124
+ this.status = status;
125
+ this.url = url;
126
+ }
127
+ };
128
+ var NotFoundError = class extends HttpError {
129
+ constructor(message, status, url) {
130
+ super(message, status, url);
131
+ this.name = "NotFoundError";
132
+ }
133
+ };
134
+ var RateLimitError = class extends HttpError {
135
+ constructor(message, status, url) {
136
+ super(message, status, url);
137
+ this.name = "RateLimitError";
138
+ }
139
+ };
140
+ var BlockedError = class extends GooglePlayError {
141
+ constructor(message) {
142
+ super(message);
143
+ this.name = "BlockedError";
144
+ }
145
+ };
146
+ var ParseError = class extends GooglePlayError {
147
+ constructor(message) {
148
+ super(message);
149
+ this.name = "ParseError";
150
+ }
151
+ };
152
+ var SpecError = class SpecError extends ParseError {
153
+ context;
154
+ failures;
155
+ constructor(context, failures) {
156
+ super(SpecError.buildMessage(context, failures));
157
+ this.name = "SpecError";
158
+ this.context = context;
159
+ this.failures = failures;
160
+ }
161
+ static buildMessage(context, failures) {
162
+ const lines = failures.map((failure) => {
163
+ const paths = failure.paths.map((path) => `[${path.join(", ")}]`).join(" | ");
164
+ return ` ${failure.field} (${paths}): ${failure.message}`;
165
+ });
166
+ return [`${context} failed to parse ${failures.length.toString()} field(s):`, ...lines].join("\n");
167
+ }
168
+ };
169
+ //#endregion
170
+ //#region src/core/http.ts
171
+ const DEFAULT_RETRIES = 2;
172
+ const DEFAULT_TIMEOUT_MS = 3e4;
173
+ const BASE_BACKOFF_MS = 500;
174
+ const THROTTLE_WINDOW_MS = 1e3;
175
+ const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
176
+ const ANY_MIME = "*";
177
+ const DEFAULT_HEADERS = {
178
+ "User-Agent": USER_AGENT,
179
+ Accept: `text/html,${ANY_MIME}/${ANY_MIME}`,
180
+ "Accept-Language": "en-US,en;q=0.9"
181
+ };
182
+ const FORM_CONTENT_TYPE = "application/x-www-form-urlencoded;charset=UTF-8";
183
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
184
+ function createRateLimiter(rate) {
185
+ let timestamps = [];
186
+ let tail = Promise.resolve();
187
+ const reserve = async () => {
188
+ const now = Date.now();
189
+ const windowStart = now - THROTTLE_WINDOW_MS;
190
+ timestamps = timestamps.filter((timestamp) => timestamp > windowStart);
191
+ if (timestamps.length >= rate) {
192
+ const oldest = timestamps[0] ?? now;
193
+ await sleep(oldest + THROTTLE_WINDOW_MS - now);
194
+ return reserve();
195
+ }
196
+ timestamps.push(Date.now());
197
+ };
198
+ return () => {
199
+ const result = tail.then(reserve);
200
+ tail = result.catch(() => void 0);
201
+ return result;
202
+ };
203
+ }
204
+ function buildHeaders(method, configHeaders, requestHeaders) {
205
+ const headers = { ...DEFAULT_HEADERS };
206
+ if (method === "POST") headers["Content-Type"] = FORM_CONTENT_TYPE;
207
+ Object.assign(headers, configHeaders ?? {}, requestHeaders ?? {});
208
+ return headers;
209
+ }
210
+ function isRetryableStatus(status) {
211
+ return status === 429 || status >= 500;
212
+ }
213
+ function parseRetryAfter(response) {
214
+ const header = response.headers.get("retry-after");
215
+ if (header === null) return;
216
+ const seconds = Number(header);
217
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
218
+ }
219
+ function computeBackoff(attempt, retryAfterSeconds) {
220
+ if (retryAfterSeconds !== void 0) return retryAfterSeconds * 1e3;
221
+ const ceiling = BASE_BACKOFF_MS * 2 ** attempt;
222
+ return Math.random() * ceiling;
223
+ }
224
+ function mapStatusToError(status, url) {
225
+ if (status === 404) return new NotFoundError("App not found (404)", status, url);
226
+ if (status === 429) return new RateLimitError("Rate limited by Google Play (429)", status, url);
227
+ return new HttpError(`Request to ${url} failed with status ${status.toString()}`, status, url);
228
+ }
229
+ function hostIsConsent(finalUrl) {
230
+ if (!finalUrl) return false;
231
+ try {
232
+ return new URL(finalUrl).host === "consent.google.com";
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+ function assertNotBlocked(response, body) {
238
+ if (hostIsConsent(response.url) || body.includes("www.google.com/recaptcha") || body.includes("unusual traffic")) throw new BlockedError("Blocked by Google Play (consent wall or captcha)");
239
+ }
240
+ function createHttpClient(config = {}) {
241
+ const fetchImpl = config.fetchImpl ?? fetch;
242
+ const retries = config.retries ?? DEFAULT_RETRIES;
243
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
244
+ const limiter = config.throttle !== void 0 ? createRateLimiter(config.throttle) : void 0;
245
+ const request = async (req) => {
246
+ const method = req.method ?? "GET";
247
+ const headers = buildHeaders(method, config.headers, req.headers);
248
+ for (let attempt = 0;; attempt += 1) {
249
+ if (limiter) await limiter();
250
+ try {
251
+ const response = await fetchImpl(req.url, {
252
+ method,
253
+ headers,
254
+ body: req.body,
255
+ signal: AbortSignal.timeout(timeoutMs)
256
+ });
257
+ if (response.ok) {
258
+ const body = await response.text();
259
+ assertNotBlocked(response, body);
260
+ return body;
261
+ }
262
+ if (isRetryableStatus(response.status) && attempt < retries) {
263
+ await sleep(computeBackoff(attempt, parseRetryAfter(response)));
264
+ continue;
265
+ }
266
+ throw mapStatusToError(response.status, req.url);
267
+ } catch (error) {
268
+ if (error instanceof GooglePlayError) throw error;
269
+ if (attempt < retries) {
270
+ await sleep(computeBackoff(attempt, void 0));
271
+ continue;
272
+ }
273
+ const httpError = new HttpError(`Network request to ${req.url} failed`, 0, req.url);
274
+ httpError.cause = error;
275
+ throw httpError;
276
+ }
277
+ }
278
+ };
279
+ return { request };
280
+ }
281
+ function clientFromOptions(opts) {
282
+ return createHttpClient({
283
+ throttle: opts.throttle,
284
+ fetchImpl: opts.requestOptions?.fetchImpl,
285
+ retries: opts.requestOptions?.retries,
286
+ timeoutMs: opts.requestOptions?.timeoutMs,
287
+ headers: opts.requestOptions?.headers
288
+ });
289
+ }
290
+ //#endregion
291
+ //#region src/core/options.ts
292
+ const requestOptionsSchema = z.object({
293
+ headers: z.record(z.string(), z.string()).optional(),
294
+ fetchImpl: z.custom((value) => typeof value === "function").optional(),
295
+ timeoutMs: z.number().int().positive().max(12e4).optional(),
296
+ retries: z.number().int().min(0).max(5).optional()
297
+ });
298
+ const baseOptionsSchema = z.object({
299
+ lang: z.string().min(2).max(7).default("en"),
300
+ country: z.string().length(2).default("us"),
301
+ throttle: z.number().positive().max(50).optional(),
302
+ requestOptions: requestOptionsSchema.optional()
303
+ });
304
+ function parseOptions(schema, input, context) {
305
+ const result = schema.safeParse(input);
306
+ if (!result.success) throw ValidationError.fromZod(result.error, context);
307
+ return result.data;
308
+ }
309
+ //#endregion
310
+ //#region src/core/scriptData.ts
311
+ const SCRIPT_BLOCK_REGEX = />AF_initDataCallback[\s\S]*?<\/script/g;
312
+ const BLOCK_KEY_REGEX = /(ds:.*?)'/;
313
+ const BLOCK_PAYLOAD_REGEX = /data:([\s\S]*?), sideChannel: {}}\);<\//;
314
+ const SERVICE_TABLE_REGEX = /; var AF_dataServiceRequests[\s\S]*?; var AF_initDataChunkQueue/;
315
+ const SERVICE_PAIR_REGEX = /'(ds:\d+)'\s*:\s*\{\s*id:\s*'([^']+)'/g;
316
+ function parseBlocks(html) {
317
+ const blocks = {};
318
+ const matches = html.match(SCRIPT_BLOCK_REGEX);
319
+ if (matches === null) return blocks;
320
+ for (const block of matches) {
321
+ const keyMatch = BLOCK_KEY_REGEX.exec(block);
322
+ const payloadMatch = BLOCK_PAYLOAD_REGEX.exec(block);
323
+ const key = keyMatch?.[1];
324
+ const payload = payloadMatch?.[1];
325
+ if (key === void 0 || payload === void 0) continue;
326
+ try {
327
+ blocks[key] = JSON.parse(payload);
328
+ } catch {
329
+ continue;
330
+ }
331
+ }
332
+ return blocks;
333
+ }
334
+ function parseServiceRequests(html) {
335
+ const requests = {};
336
+ const table = SERVICE_TABLE_REGEX.exec(html)?.[0];
337
+ if (table === void 0) return requests;
338
+ for (const pair of table.matchAll(SERVICE_PAIR_REGEX)) {
339
+ const dsKey = pair[1];
340
+ const rpcId = pair[2];
341
+ if (dsKey !== void 0 && rpcId !== void 0) requests[dsKey] = rpcId;
342
+ }
343
+ return requests;
344
+ }
345
+ function parseScriptData(html) {
346
+ return {
347
+ blocks: parseBlocks(html),
348
+ serviceRequests: parseServiceRequests(html)
349
+ };
350
+ }
351
+ function resolveDsKey(data, rpcId) {
352
+ for (const [dsKey, id] of Object.entries(data.serviceRequests)) if (id === rpcId) return dsKey;
353
+ }
354
+ //#endregion
355
+ //#region src/core/path.ts
356
+ function isIndexable(value) {
357
+ return Array.isArray(value);
358
+ }
359
+ function isRecord(value) {
360
+ return typeof value === "object" && value !== null && !Array.isArray(value);
361
+ }
362
+ function step(value, segment) {
363
+ if (typeof segment === "number") {
364
+ if (!isIndexable(value)) return;
365
+ return value[segment < 0 ? value.length + segment : segment];
366
+ }
367
+ if (isRecord(value)) return value[segment];
368
+ }
369
+ function getPath(value, path) {
370
+ let current = value;
371
+ for (const segment of path) {
372
+ if (current === void 0 || current === null) return;
373
+ current = step(current, segment);
374
+ }
375
+ return current;
376
+ }
377
+ //#endregion
378
+ //#region src/core/spec.ts
379
+ function isScriptData(source) {
380
+ return typeof source === "object" && source !== null && "blocks" in source && "serviceRequests" in source;
381
+ }
382
+ function candidatePaths(spec, source) {
383
+ if (spec.serviceRequestId !== void 0 && isScriptData(source)) {
384
+ const dsKey = resolveDsKey(source, spec.serviceRequestId);
385
+ if (dsKey !== void 0) return spec.paths.map((path) => [dsKey, ...path]);
386
+ }
387
+ return spec.paths;
388
+ }
389
+ function resolveValue(root, paths) {
390
+ for (const path of paths) {
391
+ const value = getPath(root, path);
392
+ if (value !== void 0 && value !== null) return value;
393
+ }
394
+ }
395
+ function failureMessage(error) {
396
+ if (error instanceof z.ZodError) return error.issues.map((issue) => {
397
+ const path = issue.path.join(".");
398
+ return path ? `${path}: ${issue.message}` : issue.message;
399
+ }).join("; ");
400
+ if (error instanceof Error) return error.message;
401
+ if (typeof error === "string") return error;
402
+ return "non-error thrown during extraction";
403
+ }
404
+ function extract(source, specs, context) {
405
+ const root = isScriptData(source) ? source.blocks : source;
406
+ const result = {};
407
+ const failures = [];
408
+ for (const [field, spec] of Object.entries(specs)) {
409
+ const paths = candidatePaths(spec, source);
410
+ const raw = resolveValue(root, paths);
411
+ try {
412
+ const input = spec.transform ? spec.transform(raw, source) : raw;
413
+ result[field] = spec.schema.parse(input);
414
+ } catch (error) {
415
+ failures.push({
416
+ field,
417
+ paths,
418
+ message: failureMessage(error)
419
+ });
420
+ }
421
+ }
422
+ if (failures.length > 0) throw new SpecError(context, failures);
423
+ return result;
424
+ }
425
+ //#endregion
426
+ //#region src/features/app/schema.ts
427
+ const appCategorySchema = z.object({
428
+ name: z.string(),
429
+ id: z.string().nullable()
430
+ });
431
+ const histogramSchema = z.object({
432
+ "1": z.number(),
433
+ "2": z.number(),
434
+ "3": z.number(),
435
+ "4": z.number(),
436
+ "5": z.number()
437
+ });
438
+ const appSchema = z.object({
439
+ title: z.string(),
440
+ description: z.string(),
441
+ descriptionHTML: z.string(),
442
+ summary: z.string().optional(),
443
+ installs: z.string().optional(),
444
+ minInstalls: z.number().optional(),
445
+ maxInstalls: z.number().optional(),
446
+ score: z.number().min(0).max(5).optional(),
447
+ scoreText: z.string().optional(),
448
+ ratings: z.number().optional(),
449
+ reviews: z.number().optional(),
450
+ histogram: histogramSchema,
451
+ price: z.number(),
452
+ originalPrice: z.number().optional(),
453
+ discountEndDate: z.number().optional(),
454
+ free: z.boolean(),
455
+ currency: z.string().optional(),
456
+ priceText: z.string(),
457
+ available: z.boolean(),
458
+ offersIAP: z.boolean(),
459
+ IAPRange: z.string().optional(),
460
+ androidVersion: z.string(),
461
+ androidVersionText: z.string(),
462
+ androidMaxVersion: z.string(),
463
+ developer: z.string(),
464
+ developerId: z.string(),
465
+ developerEmail: z.string().optional(),
466
+ developerWebsite: z.string().optional(),
467
+ developerAddress: z.string().optional(),
468
+ developerLegalName: z.string().optional(),
469
+ developerLegalEmail: z.string().optional(),
470
+ developerLegalAddress: z.string().optional(),
471
+ developerLegalPhoneNumber: z.string().optional(),
472
+ privacyPolicy: z.string().optional(),
473
+ developerInternalID: z.string(),
474
+ genre: z.string(),
475
+ genreId: z.string(),
476
+ categories: z.array(appCategorySchema),
477
+ icon: z.string(),
478
+ headerImage: z.string().optional(),
479
+ screenshots: z.array(z.string()),
480
+ video: z.string().optional(),
481
+ videoImage: z.string().optional(),
482
+ previewVideo: z.string().optional(),
483
+ contentRating: z.string().optional(),
484
+ contentRatingDescription: z.string().optional(),
485
+ adSupported: z.boolean(),
486
+ released: z.string().optional(),
487
+ updated: z.number(),
488
+ version: z.string(),
489
+ recentChanges: z.string().optional(),
490
+ comments: z.array(z.string()),
491
+ preregister: z.boolean(),
492
+ earlyAccessEnabled: z.boolean(),
493
+ isAvailableInPlayPass: z.boolean(),
494
+ appId: z.string(),
495
+ url: z.string()
496
+ });
497
+ //#endregion
498
+ //#region src/features/app/transforms.ts
499
+ const COMMENT_ROOTS = ["ds:8", "ds:9"];
500
+ const MAX_COMMENTS = 5;
501
+ const MICROS_PER_UNIT$4 = 1e6;
502
+ function descriptionHtmlLocalized(value) {
503
+ const translated = getPath(value, [
504
+ 12,
505
+ 0,
506
+ 0,
507
+ 1
508
+ ]);
509
+ const original = getPath(value, [
510
+ 72,
511
+ 0,
512
+ 1
513
+ ]);
514
+ const resolved = typeof translated === "string" && translated.length > 0 ? translated : original;
515
+ return typeof resolved === "string" ? resolved : void 0;
516
+ }
517
+ function descriptionText(html) {
518
+ if (typeof html !== "string") return;
519
+ return cheerio.load(`<div>${html.replace(/<br>/g, "\r\n")}</div>`)("div").text();
520
+ }
521
+ function priceText(value) {
522
+ return typeof value === "string" && value.length > 0 ? value : "Free";
523
+ }
524
+ function normalizeAndroidVersion(value) {
525
+ if (typeof value !== "string") return "VARY";
526
+ const token = value.split(" ")[0];
527
+ if (token !== void 0 && parseFloat(token)) return token;
528
+ return "VARY";
529
+ }
530
+ function buildHistogram(container) {
531
+ return {
532
+ 1: histogramCount(container, 1),
533
+ 2: histogramCount(container, 2),
534
+ 3: histogramCount(container, 3),
535
+ 4: histogramCount(container, 4),
536
+ 5: histogramCount(container, 5)
537
+ };
538
+ }
539
+ function histogramCount(container, star) {
540
+ const bucket = getPath(container, [star, 1]);
541
+ return typeof bucket === "number" ? bucket : 0;
542
+ }
543
+ function microsToUnits$4(value) {
544
+ if (typeof value !== "number") return 0;
545
+ return value / MICROS_PER_UNIT$4 || 0;
546
+ }
547
+ function developerIdFromUrl(value) {
548
+ if (typeof value !== "string") return;
549
+ return value.split("id=")[1];
550
+ }
551
+ function extractComments(source) {
552
+ for (const root of COMMENT_ROOTS) {
553
+ const author = getPath(source, [
554
+ root,
555
+ 0,
556
+ 0,
557
+ 1,
558
+ 0
559
+ ]);
560
+ const version = getPath(source, [
561
+ root,
562
+ 0,
563
+ 0,
564
+ 10
565
+ ]);
566
+ const date = getPath(source, [
567
+ root,
568
+ 0,
569
+ 0,
570
+ 5,
571
+ 0
572
+ ]);
573
+ if (author && version && date) {
574
+ const comments = getPath(source, [root, 0]);
575
+ if (Array.isArray(comments)) return comments.map((comment) => getPath(comment, [4])).filter((text) => typeof text === "string").slice(0, MAX_COMMENTS);
576
+ }
577
+ }
578
+ return [];
579
+ }
580
+ function extractScreenshots(value) {
581
+ if (!Array.isArray(value)) return [];
582
+ return value.map((shot) => getPath(shot, [3, 2])).filter((url) => typeof url === "string");
583
+ }
584
+ function extractCategories(value, categories = []) {
585
+ if (!Array.isArray(value) || value.length === 0) return categories;
586
+ if (value.length >= 4 && typeof value[0] === "string") {
587
+ categories.push({
588
+ name: value[0],
589
+ id: nullableString(value[2])
590
+ });
591
+ return categories;
592
+ }
593
+ for (const sub of value) extractCategories(sub, categories);
594
+ return categories;
595
+ }
596
+ function categoriesFromDetail(value) {
597
+ const categories = extractCategories(getPath(value, [118]));
598
+ if (categories.length > 0) return categories;
599
+ const name = getPath(value, [
600
+ 79,
601
+ 0,
602
+ 0,
603
+ 0
604
+ ]);
605
+ if (typeof name === "string") return [{
606
+ name,
607
+ id: nullableString(getPath(value, [
608
+ 79,
609
+ 0,
610
+ 0,
611
+ 2
612
+ ]))
613
+ }];
614
+ return categories;
615
+ }
616
+ function nullableString(value) {
617
+ return typeof value === "string" ? value : null;
618
+ }
619
+ //#endregion
620
+ //#region src/features/app/specs.ts
621
+ const shape$6 = appSchema.shape;
622
+ const appSpecs = {
623
+ title: {
624
+ paths: [[
625
+ "ds:5",
626
+ 1,
627
+ 2,
628
+ 0,
629
+ 0
630
+ ]],
631
+ schema: shape$6.title
632
+ },
633
+ description: {
634
+ paths: [[
635
+ "ds:5",
636
+ 1,
637
+ 2
638
+ ]],
639
+ schema: shape$6.description,
640
+ transform: (value) => descriptionText(descriptionHtmlLocalized(value))
641
+ },
642
+ descriptionHTML: {
643
+ paths: [[
644
+ "ds:5",
645
+ 1,
646
+ 2
647
+ ]],
648
+ schema: shape$6.descriptionHTML,
649
+ transform: descriptionHtmlLocalized
650
+ },
651
+ summary: {
652
+ paths: [[
653
+ "ds:5",
654
+ 1,
655
+ 2,
656
+ 73,
657
+ 0,
658
+ 1
659
+ ]],
660
+ schema: shape$6.summary
661
+ },
662
+ installs: {
663
+ paths: [[
664
+ "ds:5",
665
+ 1,
666
+ 2,
667
+ 13,
668
+ 0
669
+ ]],
670
+ schema: shape$6.installs
671
+ },
672
+ minInstalls: {
673
+ paths: [[
674
+ "ds:5",
675
+ 1,
676
+ 2,
677
+ 13,
678
+ 1
679
+ ]],
680
+ schema: shape$6.minInstalls
681
+ },
682
+ maxInstalls: {
683
+ paths: [[
684
+ "ds:5",
685
+ 1,
686
+ 2,
687
+ 13,
688
+ 2
689
+ ]],
690
+ schema: shape$6.maxInstalls
691
+ },
692
+ score: {
693
+ paths: [[
694
+ "ds:5",
695
+ 1,
696
+ 2,
697
+ 51,
698
+ 0,
699
+ 1
700
+ ]],
701
+ schema: shape$6.score
702
+ },
703
+ scoreText: {
704
+ paths: [[
705
+ "ds:5",
706
+ 1,
707
+ 2,
708
+ 51,
709
+ 0,
710
+ 0
711
+ ]],
712
+ schema: shape$6.scoreText
713
+ },
714
+ ratings: {
715
+ paths: [[
716
+ "ds:5",
717
+ 1,
718
+ 2,
719
+ 51,
720
+ 2,
721
+ 1
722
+ ]],
723
+ schema: shape$6.ratings
724
+ },
725
+ reviews: {
726
+ paths: [[
727
+ "ds:5",
728
+ 1,
729
+ 2,
730
+ 51,
731
+ 3,
732
+ 1
733
+ ]],
734
+ schema: shape$6.reviews
735
+ },
736
+ histogram: {
737
+ paths: [[
738
+ "ds:5",
739
+ 1,
740
+ 2,
741
+ 51,
742
+ 1
743
+ ]],
744
+ schema: shape$6.histogram,
745
+ transform: buildHistogram
746
+ },
747
+ price: {
748
+ paths: [[
749
+ "ds:5",
750
+ 1,
751
+ 2,
752
+ 57,
753
+ 0,
754
+ 0,
755
+ 0,
756
+ 0,
757
+ 1,
758
+ 0,
759
+ 0
760
+ ]],
761
+ schema: shape$6.price,
762
+ transform: microsToUnits$4
763
+ },
764
+ originalPrice: {
765
+ paths: [[
766
+ "ds:5",
767
+ 1,
768
+ 2,
769
+ 57,
770
+ 0,
771
+ 0,
772
+ 0,
773
+ 0,
774
+ 1,
775
+ 1,
776
+ 0
777
+ ]],
778
+ schema: shape$6.originalPrice,
779
+ transform: (value) => typeof value === "number" && value !== 0 ? microsToUnits$4(value) : void 0
780
+ },
781
+ discountEndDate: {
782
+ paths: [[
783
+ "ds:5",
784
+ 1,
785
+ 2,
786
+ 57,
787
+ 0,
788
+ 0,
789
+ 0,
790
+ 0,
791
+ 14,
792
+ 1
793
+ ]],
794
+ schema: shape$6.discountEndDate
795
+ },
796
+ free: {
797
+ paths: [[
798
+ "ds:5",
799
+ 1,
800
+ 2,
801
+ 57,
802
+ 0,
803
+ 0,
804
+ 0,
805
+ 0,
806
+ 1,
807
+ 0,
808
+ 0
809
+ ]],
810
+ schema: shape$6.free,
811
+ transform: (value) => value === 0
812
+ },
813
+ currency: {
814
+ paths: [[
815
+ "ds:5",
816
+ 1,
817
+ 2,
818
+ 57,
819
+ 0,
820
+ 0,
821
+ 0,
822
+ 0,
823
+ 1,
824
+ 0,
825
+ 1
826
+ ]],
827
+ schema: shape$6.currency
828
+ },
829
+ priceText: {
830
+ paths: [[
831
+ "ds:5",
832
+ 1,
833
+ 2,
834
+ 57,
835
+ 0,
836
+ 0,
837
+ 0,
838
+ 0,
839
+ 1,
840
+ 0,
841
+ 2
842
+ ]],
843
+ schema: shape$6.priceText,
844
+ transform: priceText
845
+ },
846
+ available: {
847
+ paths: [[
848
+ "ds:5",
849
+ 1,
850
+ 2,
851
+ 18,
852
+ 0
853
+ ]],
854
+ schema: shape$6.available,
855
+ transform: (value) => Boolean(value)
856
+ },
857
+ offersIAP: {
858
+ paths: [[
859
+ "ds:5",
860
+ 1,
861
+ 2,
862
+ 19,
863
+ 0
864
+ ]],
865
+ schema: shape$6.offersIAP,
866
+ transform: (value) => Boolean(value)
867
+ },
868
+ IAPRange: {
869
+ paths: [[
870
+ "ds:5",
871
+ 1,
872
+ 2,
873
+ 19,
874
+ 0
875
+ ]],
876
+ schema: shape$6.IAPRange
877
+ },
878
+ androidVersion: {
879
+ paths: [[
880
+ "ds:5",
881
+ 1,
882
+ 2,
883
+ 140,
884
+ 1,
885
+ 1,
886
+ 0,
887
+ 0,
888
+ 1
889
+ ], [
890
+ "ds:5",
891
+ 1,
892
+ 2,
893
+ -1,
894
+ "141",
895
+ 1,
896
+ 1,
897
+ 0,
898
+ 0,
899
+ 1
900
+ ]],
901
+ schema: shape$6.androidVersion,
902
+ transform: normalizeAndroidVersion
903
+ },
904
+ androidVersionText: {
905
+ paths: [[
906
+ "ds:5",
907
+ 1,
908
+ 2,
909
+ 140,
910
+ 1,
911
+ 1,
912
+ 0,
913
+ 0,
914
+ 1
915
+ ], [
916
+ "ds:5",
917
+ 1,
918
+ 2,
919
+ -1,
920
+ "141",
921
+ 1,
922
+ 1,
923
+ 0,
924
+ 0,
925
+ 1
926
+ ]],
927
+ schema: shape$6.androidVersionText,
928
+ transform: (value) => typeof value === "string" && value.length > 0 ? value : "Varies with device"
929
+ },
930
+ androidMaxVersion: {
931
+ paths: [[
932
+ "ds:5",
933
+ 1,
934
+ 2,
935
+ 140,
936
+ 1,
937
+ 1,
938
+ 0,
939
+ 1,
940
+ 1
941
+ ], [
942
+ "ds:5",
943
+ 1,
944
+ 2,
945
+ -1,
946
+ "141",
947
+ 1,
948
+ 1,
949
+ 0,
950
+ 1,
951
+ 1
952
+ ]],
953
+ schema: shape$6.androidMaxVersion,
954
+ transform: normalizeAndroidVersion
955
+ },
956
+ developer: {
957
+ paths: [[
958
+ "ds:5",
959
+ 1,
960
+ 2,
961
+ 68,
962
+ 0
963
+ ]],
964
+ schema: shape$6.developer
965
+ },
966
+ developerId: {
967
+ paths: [[
968
+ "ds:5",
969
+ 1,
970
+ 2,
971
+ 68,
972
+ 1,
973
+ 4,
974
+ 2
975
+ ]],
976
+ schema: shape$6.developerId,
977
+ transform: developerIdFromUrl
978
+ },
979
+ developerEmail: {
980
+ paths: [[
981
+ "ds:5",
982
+ 1,
983
+ 2,
984
+ 69,
985
+ 1,
986
+ 0
987
+ ]],
988
+ schema: shape$6.developerEmail
989
+ },
990
+ developerWebsite: {
991
+ paths: [[
992
+ "ds:5",
993
+ 1,
994
+ 2,
995
+ 69,
996
+ 0,
997
+ 5,
998
+ 2
999
+ ]],
1000
+ schema: shape$6.developerWebsite
1001
+ },
1002
+ developerAddress: {
1003
+ paths: [[
1004
+ "ds:5",
1005
+ 1,
1006
+ 2,
1007
+ 69,
1008
+ 2,
1009
+ 0
1010
+ ]],
1011
+ schema: shape$6.developerAddress
1012
+ },
1013
+ developerLegalName: {
1014
+ paths: [[
1015
+ "ds:5",
1016
+ 1,
1017
+ 2,
1018
+ 69,
1019
+ 4,
1020
+ 0
1021
+ ]],
1022
+ schema: shape$6.developerLegalName
1023
+ },
1024
+ developerLegalEmail: {
1025
+ paths: [[
1026
+ "ds:5",
1027
+ 1,
1028
+ 2,
1029
+ 69,
1030
+ 4,
1031
+ 1,
1032
+ 0
1033
+ ]],
1034
+ schema: shape$6.developerLegalEmail
1035
+ },
1036
+ developerLegalAddress: {
1037
+ paths: [[
1038
+ "ds:5",
1039
+ 1,
1040
+ 2,
1041
+ 69
1042
+ ]],
1043
+ schema: shape$6.developerLegalAddress,
1044
+ transform: (value) => {
1045
+ const address = getPath(value, [
1046
+ 4,
1047
+ 2,
1048
+ 0
1049
+ ]);
1050
+ return typeof address === "string" ? address.replace(/\n/g, ", ") : void 0;
1051
+ }
1052
+ },
1053
+ developerLegalPhoneNumber: {
1054
+ paths: [[
1055
+ "ds:5",
1056
+ 1,
1057
+ 2,
1058
+ 69,
1059
+ 4,
1060
+ 3
1061
+ ]],
1062
+ schema: shape$6.developerLegalPhoneNumber
1063
+ },
1064
+ privacyPolicy: {
1065
+ paths: [[
1066
+ "ds:5",
1067
+ 1,
1068
+ 2,
1069
+ 99,
1070
+ 0,
1071
+ 5,
1072
+ 2
1073
+ ]],
1074
+ schema: shape$6.privacyPolicy
1075
+ },
1076
+ developerInternalID: {
1077
+ paths: [[
1078
+ "ds:5",
1079
+ 1,
1080
+ 2,
1081
+ 68,
1082
+ 1,
1083
+ 4,
1084
+ 2
1085
+ ]],
1086
+ schema: shape$6.developerInternalID,
1087
+ transform: developerIdFromUrl
1088
+ },
1089
+ genre: {
1090
+ paths: [[
1091
+ "ds:5",
1092
+ 1,
1093
+ 2,
1094
+ 79,
1095
+ 0,
1096
+ 0,
1097
+ 0
1098
+ ]],
1099
+ schema: shape$6.genre
1100
+ },
1101
+ genreId: {
1102
+ paths: [[
1103
+ "ds:5",
1104
+ 1,
1105
+ 2,
1106
+ 79,
1107
+ 0,
1108
+ 0,
1109
+ 2
1110
+ ]],
1111
+ schema: shape$6.genreId
1112
+ },
1113
+ categories: {
1114
+ paths: [[
1115
+ "ds:5",
1116
+ 1,
1117
+ 2
1118
+ ]],
1119
+ schema: shape$6.categories,
1120
+ transform: categoriesFromDetail
1121
+ },
1122
+ icon: {
1123
+ paths: [[
1124
+ "ds:5",
1125
+ 1,
1126
+ 2,
1127
+ 95,
1128
+ 0,
1129
+ 3,
1130
+ 2
1131
+ ]],
1132
+ schema: shape$6.icon
1133
+ },
1134
+ headerImage: {
1135
+ paths: [[
1136
+ "ds:5",
1137
+ 1,
1138
+ 2,
1139
+ 96,
1140
+ 0,
1141
+ 3,
1142
+ 2
1143
+ ]],
1144
+ schema: shape$6.headerImage
1145
+ },
1146
+ screenshots: {
1147
+ paths: [[
1148
+ "ds:5",
1149
+ 1,
1150
+ 2,
1151
+ 78,
1152
+ 0
1153
+ ]],
1154
+ schema: shape$6.screenshots,
1155
+ transform: extractScreenshots
1156
+ },
1157
+ video: {
1158
+ paths: [[
1159
+ "ds:5",
1160
+ 1,
1161
+ 2,
1162
+ 100,
1163
+ 0,
1164
+ 0,
1165
+ 3,
1166
+ 2
1167
+ ]],
1168
+ schema: shape$6.video
1169
+ },
1170
+ videoImage: {
1171
+ paths: [[
1172
+ "ds:5",
1173
+ 1,
1174
+ 2,
1175
+ 100,
1176
+ 1,
1177
+ 0,
1178
+ 3,
1179
+ 2
1180
+ ]],
1181
+ schema: shape$6.videoImage
1182
+ },
1183
+ previewVideo: {
1184
+ paths: [[
1185
+ "ds:5",
1186
+ 1,
1187
+ 2,
1188
+ 100,
1189
+ 1,
1190
+ 2,
1191
+ 0,
1192
+ 2
1193
+ ]],
1194
+ schema: shape$6.previewVideo
1195
+ },
1196
+ contentRating: {
1197
+ paths: [[
1198
+ "ds:5",
1199
+ 1,
1200
+ 2,
1201
+ 9,
1202
+ 0
1203
+ ]],
1204
+ schema: shape$6.contentRating
1205
+ },
1206
+ contentRatingDescription: {
1207
+ paths: [[
1208
+ "ds:5",
1209
+ 1,
1210
+ 2,
1211
+ 9,
1212
+ 2,
1213
+ 1
1214
+ ]],
1215
+ schema: shape$6.contentRatingDescription
1216
+ },
1217
+ adSupported: {
1218
+ paths: [[
1219
+ "ds:5",
1220
+ 1,
1221
+ 2,
1222
+ 48
1223
+ ]],
1224
+ schema: shape$6.adSupported,
1225
+ transform: (value) => Boolean(value)
1226
+ },
1227
+ released: {
1228
+ paths: [[
1229
+ "ds:5",
1230
+ 1,
1231
+ 2,
1232
+ 10,
1233
+ 0
1234
+ ]],
1235
+ schema: shape$6.released
1236
+ },
1237
+ updated: {
1238
+ paths: [[
1239
+ "ds:5",
1240
+ 1,
1241
+ 2,
1242
+ 145,
1243
+ 0,
1244
+ 1,
1245
+ 0
1246
+ ], [
1247
+ "ds:5",
1248
+ 1,
1249
+ 2,
1250
+ -1,
1251
+ "146",
1252
+ 0,
1253
+ 1,
1254
+ 0
1255
+ ]],
1256
+ schema: shape$6.updated,
1257
+ transform: (value) => typeof value === "number" ? value * 1e3 : value
1258
+ },
1259
+ version: {
1260
+ paths: [[
1261
+ "ds:5",
1262
+ 1,
1263
+ 2,
1264
+ 140,
1265
+ 0,
1266
+ 0,
1267
+ 0
1268
+ ], [
1269
+ "ds:5",
1270
+ 1,
1271
+ 2,
1272
+ -1,
1273
+ "141",
1274
+ 0,
1275
+ 0,
1276
+ 0
1277
+ ]],
1278
+ schema: shape$6.version,
1279
+ transform: (value) => typeof value === "string" && value.length > 0 ? value : "VARY"
1280
+ },
1281
+ recentChanges: {
1282
+ paths: [[
1283
+ "ds:5",
1284
+ 1,
1285
+ 2,
1286
+ 144,
1287
+ 1,
1288
+ 1
1289
+ ], [
1290
+ "ds:5",
1291
+ 1,
1292
+ 2,
1293
+ -1,
1294
+ "145",
1295
+ 1,
1296
+ 1
1297
+ ]],
1298
+ schema: shape$6.recentChanges
1299
+ },
1300
+ comments: {
1301
+ paths: [[]],
1302
+ schema: shape$6.comments,
1303
+ transform: extractComments
1304
+ },
1305
+ preregister: {
1306
+ paths: [[
1307
+ "ds:5",
1308
+ 1,
1309
+ 2,
1310
+ 18,
1311
+ 0
1312
+ ]],
1313
+ schema: shape$6.preregister,
1314
+ transform: (value) => value === 1
1315
+ },
1316
+ earlyAccessEnabled: {
1317
+ paths: [[
1318
+ "ds:5",
1319
+ 1,
1320
+ 2,
1321
+ 18,
1322
+ 2
1323
+ ]],
1324
+ schema: shape$6.earlyAccessEnabled,
1325
+ transform: (value) => typeof value === "string"
1326
+ },
1327
+ isAvailableInPlayPass: {
1328
+ paths: [[
1329
+ "ds:5",
1330
+ 1,
1331
+ 2,
1332
+ 62
1333
+ ]],
1334
+ schema: shape$6.isAvailableInPlayPass,
1335
+ transform: (value) => Boolean(value)
1336
+ }
1337
+ };
1338
+ //#endregion
1339
+ //#region src/features/app/app.ts
1340
+ const appOptionsSchema = baseOptionsSchema.extend({ appId: z.string().min(1) });
1341
+ const DETAILS_URL = `${BASE_URL}/store/apps/details`;
1342
+ async function app(options) {
1343
+ const parsed = parseOptions(appOptionsSchema, options, "app");
1344
+ const params = new URLSearchParams({
1345
+ id: parsed.appId,
1346
+ hl: parsed.lang,
1347
+ gl: parsed.country
1348
+ });
1349
+ const url = `${DETAILS_URL}?${params.toString()}`;
1350
+ const extracted = extract(parseScriptData(await clientFromOptions(parsed).request({ url })), appSpecs, "app");
1351
+ return appSchema.parse({
1352
+ ...extracted,
1353
+ appId: parsed.appId,
1354
+ url
1355
+ });
1356
+ }
1357
+ //#endregion
1358
+ //#region src/core/batchexecute.ts
1359
+ const BATCH_URL = `${BASE_URL}/_/PlayStoreUi/data/batchexecute`;
1360
+ const DEFAULT_ENVELOPE_TAIL = [null, "generic"];
1361
+ const WRB_FRAME_MARKER = "wrb.fr";
1362
+ const SNIPPET_LENGTH = 200;
1363
+ function isArray(value) {
1364
+ return Array.isArray(value);
1365
+ }
1366
+ function snippet(text) {
1367
+ return text.slice(0, SNIPPET_LENGTH);
1368
+ }
1369
+ function buildBatchBody(rpcId, payload, envelopeTail = DEFAULT_ENVELOPE_TAIL) {
1370
+ const envelope = [[[
1371
+ rpcId,
1372
+ JSON.stringify(payload),
1373
+ ...envelopeTail
1374
+ ]]];
1375
+ return new URLSearchParams({ "f.req": JSON.stringify(envelope) }).toString();
1376
+ }
1377
+ function matchEnvelope(frames, rpcId) {
1378
+ for (const frame of frames) {
1379
+ if (!isArray(frame)) continue;
1380
+ if (frame[0] === WRB_FRAME_MARKER && frame[1] === rpcId) {
1381
+ const raw = frame[2];
1382
+ if (typeof raw === "string") return {
1383
+ found: true,
1384
+ value: JSON.parse(raw)
1385
+ };
1386
+ return {
1387
+ found: true,
1388
+ value: null
1389
+ };
1390
+ }
1391
+ }
1392
+ return {
1393
+ found: false,
1394
+ value: void 0
1395
+ };
1396
+ }
1397
+ function tryParseArray(text) {
1398
+ try {
1399
+ const parsed = JSON.parse(text);
1400
+ return isArray(parsed) ? parsed : void 0;
1401
+ } catch {
1402
+ return;
1403
+ }
1404
+ }
1405
+ function parseBatchResponse(text, rpcId) {
1406
+ const start = text.indexOf("[");
1407
+ if (start === -1) throw new ParseError(`batchexecute response missing array start: ${snippet(text)}`);
1408
+ const body = text.slice(start);
1409
+ for (const line of body.split("\n")) {
1410
+ const trimmed = line.trim();
1411
+ if (!trimmed.startsWith("[")) continue;
1412
+ const frames = tryParseArray(trimmed);
1413
+ if (frames === void 0) continue;
1414
+ const match = matchEnvelope(frames, rpcId);
1415
+ if (match.found) return match.value;
1416
+ }
1417
+ const whole = tryParseArray(body);
1418
+ if (whole !== void 0) {
1419
+ const match = matchEnvelope(whole, rpcId);
1420
+ if (match.found) return match.value;
1421
+ }
1422
+ throw new ParseError(`batchexecute response has no envelope for rpc ${rpcId}: ${snippet(body)}`);
1423
+ }
1424
+ //#endregion
1425
+ //#region src/core/pagination.ts
1426
+ const CLUSTER_RPC_ID = "qnKhOb";
1427
+ const CLUSTER_STATIC_QUERY = "rpcids=qnKhOb&f.sid=-697906427155521722&bl=boq_playuiserver_20190903.08_p0";
1428
+ const CLUSTER_TRAILING_QUERY = "authuser&soc-app=121&soc-platform=1&soc-device=1&_reqid=1065213";
1429
+ function clusterUrl(lang, country) {
1430
+ return `${BATCH_URL}?${CLUSTER_STATIC_QUERY}&hl=${lang}&gl=${country}&${CLUSTER_TRAILING_QUERY}`;
1431
+ }
1432
+ function buildClusterBody(numberOfApps, withToken) {
1433
+ return `f.req=%5B%5B%5B%22qnKhOb%22%2C%22%5B%5Bnull%2C%5B%5B10%2C%5B10%2C${numberOfApps.toString()}%5D%5D%2Ctrue%2Cnull%2C%5B96%2C27%2C4%2C8%2C57%2C30%2C110%2C79%2C11%2C16%2C49%2C1%2C3%2C9%2C12%2C104%2C55%2C56%2C51%2C10%2C34%2C77%5D%5D%2Cnull%2C%5C%22${withToken}%5C%22%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D`;
1434
+ }
1435
+ function asToken(value) {
1436
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1437
+ }
1438
+ async function fetchClusterApps(params) {
1439
+ const { client, lang, country, num, itemSpecs, appsPath, tokenPath, context } = params;
1440
+ const collected = [...params.initialApps];
1441
+ let token = asToken(params.initialToken);
1442
+ while (collected.length < num && token !== void 0) {
1443
+ const body = buildClusterBody(100, token);
1444
+ const payload = parseBatchResponse(await client.request({
1445
+ url: clusterUrl(lang, country),
1446
+ method: "POST",
1447
+ body
1448
+ }), CLUSTER_RPC_ID);
1449
+ const apps = getPath(payload, appsPath);
1450
+ if (!Array.isArray(apps) || apps.length === 0) break;
1451
+ for (const item of apps) collected.push(extract(item, itemSpecs, context));
1452
+ token = asToken(getPath(payload, tokenPath));
1453
+ }
1454
+ return collected.slice(0, num);
1455
+ }
1456
+ //#endregion
1457
+ //#region src/core/fullDetail.ts
1458
+ const DEFAULT_CONCURRENCY = 3;
1459
+ async function resolveFullDetail(items, options, getApp, concurrency = DEFAULT_CONCURRENCY) {
1460
+ const results = new Array(items.length);
1461
+ let cursor = 0;
1462
+ const worker = async () => {
1463
+ while (cursor < items.length) {
1464
+ const index = cursor;
1465
+ cursor += 1;
1466
+ const item = items[index];
1467
+ if (item === void 0) continue;
1468
+ results[index] = await getApp({
1469
+ appId: item.appId,
1470
+ lang: options.lang,
1471
+ country: options.country,
1472
+ throttle: options.throttle,
1473
+ requestOptions: options.requestOptions
1474
+ });
1475
+ }
1476
+ };
1477
+ const workerCount = Math.min(concurrency, items.length);
1478
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
1479
+ return results;
1480
+ }
1481
+ //#endregion
1482
+ //#region src/core/appItem.ts
1483
+ const appItemSchema = z.object({
1484
+ title: z.string(),
1485
+ appId: z.string(),
1486
+ url: z.string(),
1487
+ icon: z.string(),
1488
+ developer: z.string(),
1489
+ developerId: z.string().optional(),
1490
+ currency: z.string().optional(),
1491
+ price: z.number(),
1492
+ free: z.boolean(),
1493
+ summary: z.string().optional(),
1494
+ scoreText: z.string().optional(),
1495
+ score: z.number().min(0).max(5).optional()
1496
+ });
1497
+ //#endregion
1498
+ //#region src/features/search/schema.ts
1499
+ const searchResultSchema = appItemSchema;
1500
+ //#endregion
1501
+ //#region src/features/search/specs.ts
1502
+ const MICROS_PER_UNIT$3 = 1e6;
1503
+ const shape$5 = searchResultSchema.shape;
1504
+ function resolveUrl$4(value) {
1505
+ return typeof value === "string" ? new URL(value, BASE_URL).toString() : void 0;
1506
+ }
1507
+ function microsToUnits$3(value) {
1508
+ return typeof value === "number" ? value / MICROS_PER_UNIT$3 || 0 : 0;
1509
+ }
1510
+ function isFree$3(value) {
1511
+ return value === 0;
1512
+ }
1513
+ function developerIdFromLink(value) {
1514
+ return typeof value === "string" ? value.split("?id=")[1] : void 0;
1515
+ }
1516
+ const INITIAL_MAPPINGS = {
1517
+ app: [
1518
+ "ds:4",
1519
+ 0,
1520
+ 1,
1521
+ 0,
1522
+ 23
1523
+ ],
1524
+ sections: [
1525
+ "ds:4",
1526
+ 0,
1527
+ 1
1528
+ ]
1529
+ };
1530
+ const SECTIONS_MAPPING = {
1531
+ apps: [22, 0],
1532
+ token: [
1533
+ 22,
1534
+ 1,
1535
+ 3,
1536
+ 1
1537
+ ]
1538
+ };
1539
+ const CLUSTER_MAPPINGS$1 = {
1540
+ apps: [
1541
+ 0,
1542
+ 0,
1543
+ 0
1544
+ ],
1545
+ token: [
1546
+ 0,
1547
+ 0,
1548
+ 7,
1549
+ 1
1550
+ ]
1551
+ };
1552
+ const searchItemSpecs = {
1553
+ title: {
1554
+ paths: [[0, 3]],
1555
+ schema: shape$5.title
1556
+ },
1557
+ appId: {
1558
+ paths: [[
1559
+ 0,
1560
+ 0,
1561
+ 0
1562
+ ]],
1563
+ schema: shape$5.appId
1564
+ },
1565
+ url: {
1566
+ paths: [[
1567
+ 0,
1568
+ 10,
1569
+ 4,
1570
+ 2
1571
+ ]],
1572
+ schema: shape$5.url,
1573
+ transform: resolveUrl$4
1574
+ },
1575
+ icon: {
1576
+ paths: [[
1577
+ 0,
1578
+ 1,
1579
+ 3,
1580
+ 2
1581
+ ]],
1582
+ schema: shape$5.icon
1583
+ },
1584
+ developer: {
1585
+ paths: [[0, 14]],
1586
+ schema: shape$5.developer
1587
+ },
1588
+ currency: {
1589
+ paths: [[
1590
+ 0,
1591
+ 8,
1592
+ 1,
1593
+ 0,
1594
+ 1
1595
+ ]],
1596
+ schema: shape$5.currency
1597
+ },
1598
+ price: {
1599
+ paths: [[
1600
+ 0,
1601
+ 8,
1602
+ 1,
1603
+ 0,
1604
+ 0
1605
+ ]],
1606
+ schema: shape$5.price,
1607
+ transform: microsToUnits$3
1608
+ },
1609
+ free: {
1610
+ paths: [[
1611
+ 0,
1612
+ 8,
1613
+ 1,
1614
+ 0,
1615
+ 0
1616
+ ]],
1617
+ schema: shape$5.free,
1618
+ transform: isFree$3
1619
+ },
1620
+ summary: {
1621
+ paths: [[
1622
+ 0,
1623
+ 13,
1624
+ 1
1625
+ ]],
1626
+ schema: shape$5.summary
1627
+ },
1628
+ scoreText: {
1629
+ paths: [[
1630
+ 0,
1631
+ 4,
1632
+ 0
1633
+ ]],
1634
+ schema: shape$5.scoreText
1635
+ },
1636
+ score: {
1637
+ paths: [[
1638
+ 0,
1639
+ 4,
1640
+ 1
1641
+ ]],
1642
+ schema: shape$5.score
1643
+ }
1644
+ };
1645
+ const searchPageItemSpecs = {
1646
+ title: {
1647
+ paths: [[3]],
1648
+ schema: shape$5.title
1649
+ },
1650
+ appId: {
1651
+ paths: [[0, 0]],
1652
+ schema: shape$5.appId
1653
+ },
1654
+ url: {
1655
+ paths: [[
1656
+ 10,
1657
+ 4,
1658
+ 2
1659
+ ]],
1660
+ schema: shape$5.url,
1661
+ transform: resolveUrl$4
1662
+ },
1663
+ icon: {
1664
+ paths: [[
1665
+ 1,
1666
+ 3,
1667
+ 2
1668
+ ]],
1669
+ schema: shape$5.icon
1670
+ },
1671
+ developer: {
1672
+ paths: [[14]],
1673
+ schema: shape$5.developer
1674
+ },
1675
+ currency: {
1676
+ paths: [[
1677
+ 8,
1678
+ 1,
1679
+ 0,
1680
+ 1
1681
+ ]],
1682
+ schema: shape$5.currency
1683
+ },
1684
+ price: {
1685
+ paths: [[
1686
+ 8,
1687
+ 1,
1688
+ 0,
1689
+ 0
1690
+ ]],
1691
+ schema: shape$5.price,
1692
+ transform: microsToUnits$3
1693
+ },
1694
+ free: {
1695
+ paths: [[
1696
+ 8,
1697
+ 1,
1698
+ 0,
1699
+ 0
1700
+ ]],
1701
+ schema: shape$5.free,
1702
+ transform: isFree$3
1703
+ },
1704
+ summary: {
1705
+ paths: [[13, 1]],
1706
+ schema: shape$5.summary
1707
+ },
1708
+ scoreText: {
1709
+ paths: [[4, 0]],
1710
+ schema: shape$5.scoreText
1711
+ },
1712
+ score: {
1713
+ paths: [[4, 1]],
1714
+ schema: shape$5.score
1715
+ }
1716
+ };
1717
+ const exactMatchSpecs = {
1718
+ title: {
1719
+ paths: [[
1720
+ 16,
1721
+ 2,
1722
+ 0,
1723
+ 0
1724
+ ]],
1725
+ schema: shape$5.title
1726
+ },
1727
+ appId: {
1728
+ paths: [[
1729
+ 16,
1730
+ 3,
1731
+ "12",
1732
+ 0,
1733
+ 0
1734
+ ]],
1735
+ schema: shape$5.appId
1736
+ },
1737
+ url: {
1738
+ paths: [[
1739
+ 17,
1740
+ 0,
1741
+ 0,
1742
+ 4,
1743
+ 2
1744
+ ]],
1745
+ schema: shape$5.url,
1746
+ transform: resolveUrl$4
1747
+ },
1748
+ icon: {
1749
+ paths: [[
1750
+ 16,
1751
+ 2,
1752
+ 95,
1753
+ 0,
1754
+ 3,
1755
+ 2
1756
+ ]],
1757
+ schema: shape$5.icon
1758
+ },
1759
+ developer: {
1760
+ paths: [[
1761
+ 16,
1762
+ 2,
1763
+ 68,
1764
+ 0
1765
+ ]],
1766
+ schema: shape$5.developer
1767
+ },
1768
+ developerId: {
1769
+ paths: [[
1770
+ 16,
1771
+ 2,
1772
+ 68,
1773
+ 1,
1774
+ 4,
1775
+ 2
1776
+ ]],
1777
+ schema: shape$5.developerId,
1778
+ transform: developerIdFromLink
1779
+ },
1780
+ currency: {
1781
+ paths: [[
1782
+ 17,
1783
+ 0,
1784
+ 2,
1785
+ 0,
1786
+ 1,
1787
+ 0,
1788
+ 1
1789
+ ]],
1790
+ schema: shape$5.currency
1791
+ },
1792
+ price: {
1793
+ paths: [[
1794
+ 17,
1795
+ 0,
1796
+ 2,
1797
+ 0,
1798
+ 1,
1799
+ 0,
1800
+ 0
1801
+ ]],
1802
+ schema: shape$5.price,
1803
+ transform: microsToUnits$3
1804
+ },
1805
+ free: {
1806
+ paths: [[
1807
+ 17,
1808
+ 0,
1809
+ 2,
1810
+ 0,
1811
+ 1,
1812
+ 0,
1813
+ 0
1814
+ ]],
1815
+ schema: shape$5.free,
1816
+ transform: isFree$3
1817
+ },
1818
+ summary: {
1819
+ paths: [[
1820
+ 16,
1821
+ 2,
1822
+ 73,
1823
+ 0,
1824
+ 1
1825
+ ]],
1826
+ schema: shape$5.summary
1827
+ },
1828
+ scoreText: {
1829
+ paths: [[
1830
+ 16,
1831
+ 2,
1832
+ 51,
1833
+ 0,
1834
+ 0
1835
+ ]],
1836
+ schema: shape$5.scoreText
1837
+ },
1838
+ score: {
1839
+ paths: [[
1840
+ 16,
1841
+ 2,
1842
+ 51,
1843
+ 0,
1844
+ 1
1845
+ ]],
1846
+ schema: shape$5.score
1847
+ }
1848
+ };
1849
+ function priceGoogleValue(value) {
1850
+ switch (value) {
1851
+ case "free": return 1;
1852
+ case "paid": return 2;
1853
+ default: return 0;
1854
+ }
1855
+ }
1856
+ //#endregion
1857
+ //#region src/features/search/search.ts
1858
+ const searchOptionsSchema = baseOptionsSchema.extend({
1859
+ term: z.string().min(1),
1860
+ num: z.number().int().min(1).max(250).default(20),
1861
+ price: z.enum([
1862
+ "all",
1863
+ "free",
1864
+ "paid"
1865
+ ]).default("all"),
1866
+ fullDetail: z.boolean().default(false)
1867
+ });
1868
+ const SEARCH_URL = `${BASE_URL}/store/search`;
1869
+ const SEARCH_CONTEXT = "search";
1870
+ function prependExactMatch(data, apps) {
1871
+ const exactMatchData = getPath(data.blocks, INITIAL_MAPPINGS.app);
1872
+ if (exactMatchData === void 0 || exactMatchData === null) return apps;
1873
+ let exactMatch;
1874
+ try {
1875
+ exactMatch = extract(exactMatchData, exactMatchSpecs, SEARCH_CONTEXT);
1876
+ } catch {
1877
+ return apps;
1878
+ }
1879
+ if (apps.some((item) => item.appId === exactMatch.appId)) return apps;
1880
+ return [exactMatch, ...apps];
1881
+ }
1882
+ function firstPage(data) {
1883
+ const sections = getPath(data.blocks, INITIAL_MAPPINGS.sections);
1884
+ if (!Array.isArray(sections)) return {
1885
+ apps: [],
1886
+ token: void 0
1887
+ };
1888
+ for (const section of sections) {
1889
+ const apps = getPath(section, SECTIONS_MAPPING.apps);
1890
+ if (Array.isArray(apps) && apps.length > 0) {
1891
+ const extracted = apps.map((item) => extract(item, searchItemSpecs, SEARCH_CONTEXT));
1892
+ const token = getPath(section, SECTIONS_MAPPING.token);
1893
+ return {
1894
+ apps: prependExactMatch(data, extracted),
1895
+ token: typeof token === "string" ? token : void 0
1896
+ };
1897
+ }
1898
+ }
1899
+ return {
1900
+ apps: [],
1901
+ token: void 0
1902
+ };
1903
+ }
1904
+ function createSearch(getApp) {
1905
+ return async function search(options) {
1906
+ const parsed = parseOptions(searchOptionsSchema, options, SEARCH_CONTEXT);
1907
+ const params = new URLSearchParams({
1908
+ c: "apps",
1909
+ q: parsed.term,
1910
+ hl: parsed.lang,
1911
+ gl: parsed.country,
1912
+ price: priceGoogleValue(parsed.price).toString()
1913
+ });
1914
+ const client = clientFromOptions(parsed);
1915
+ const page = firstPage(parseScriptData(await client.request({ url: `${SEARCH_URL}?${params.toString()}` })));
1916
+ const sliced = (await fetchClusterApps({
1917
+ client,
1918
+ lang: parsed.lang,
1919
+ country: parsed.country,
1920
+ num: parsed.num,
1921
+ initialApps: page.apps,
1922
+ initialToken: page.token,
1923
+ itemSpecs: searchPageItemSpecs,
1924
+ appsPath: CLUSTER_MAPPINGS$1.apps,
1925
+ tokenPath: CLUSTER_MAPPINGS$1.token,
1926
+ context: SEARCH_CONTEXT
1927
+ })).slice(0, parsed.num);
1928
+ if (parsed.fullDetail) return resolveFullDetail(sliced, parsed, getApp);
1929
+ return z.array(searchResultSchema).parse(sliced);
1930
+ };
1931
+ }
1932
+ const search = createSearch(app);
1933
+ //#endregion
1934
+ //#region src/features/suggest/specs.ts
1935
+ const SUGGEST_RPC_ID = "IJ4APc";
1936
+ const STATIC_QUERY_PARAMS$1 = {
1937
+ rpcids: SUGGEST_RPC_ID,
1938
+ "f.sid": "-697906427155521722",
1939
+ bl: "boq_playuiserver_20190903.08_p0",
1940
+ "soc-app": "121",
1941
+ "soc-platform": "1",
1942
+ "soc-device": "1",
1943
+ _reqid: "1065213"
1944
+ };
1945
+ const SUGGEST_LIMIT = 10;
1946
+ const SUGGEST_DATASET = 2;
1947
+ const SUGGEST_MODE = 4;
1948
+ function suggestUrl(lang, country) {
1949
+ return `${BATCH_URL}?${new URLSearchParams({
1950
+ ...STATIC_QUERY_PARAMS$1,
1951
+ hl: lang,
1952
+ gl: country
1953
+ }).toString()}`;
1954
+ }
1955
+ function buildSuggestPayload(term) {
1956
+ return [[
1957
+ null,
1958
+ [term],
1959
+ [SUGGEST_LIMIT],
1960
+ [SUGGEST_DATASET],
1961
+ SUGGEST_MODE
1962
+ ]];
1963
+ }
1964
+ const SUGGESTIONS_PATH = [0, 0];
1965
+ const SUGGESTION_TEXT_PATH = [0];
1966
+ //#endregion
1967
+ //#region src/features/suggest/suggest.ts
1968
+ const suggestOptionsSchema = baseOptionsSchema.extend({ term: z.string().min(1) });
1969
+ const SUGGEST_CONTEXT = "suggest";
1970
+ const MAX_SUGGESTIONS = 5;
1971
+ async function suggest(options) {
1972
+ const parsed = parseOptions(suggestOptionsSchema, options, SUGGEST_CONTEXT);
1973
+ const client = clientFromOptions(parsed);
1974
+ const body = buildBatchBody(SUGGEST_RPC_ID, buildSuggestPayload(parsed.term), []);
1975
+ const payload = parseBatchResponse(await client.request({
1976
+ url: suggestUrl(parsed.lang, parsed.country),
1977
+ method: "POST",
1978
+ body
1979
+ }), SUGGEST_RPC_ID);
1980
+ if (payload === null) return [];
1981
+ const entries = getPath(payload, SUGGESTIONS_PATH);
1982
+ if (!Array.isArray(entries)) return [];
1983
+ return z.array(z.string()).parse(entries.map((entry) => getPath(entry, SUGGESTION_TEXT_PATH))).slice(0, MAX_SUGGESTIONS);
1984
+ }
1985
+ //#endregion
1986
+ //#region src/features/list/schema.ts
1987
+ const listItemSchema = appItemSchema;
1988
+ //#endregion
1989
+ //#region src/features/list/specs.ts
1990
+ const LIST_RPC_ID = "vyAe2";
1991
+ const MICROS_PER_UNIT$2 = 1e6;
1992
+ const shape$4 = appItemSchema.shape;
1993
+ const STATIC_QUERY_PARAMS = {
1994
+ rpcids: LIST_RPC_ID,
1995
+ "source-path": "/store/apps",
1996
+ "f.sid": "-4178618388443751758",
1997
+ bl: "boq_playuiserver_20220612.08_p0",
1998
+ authuser: "0",
1999
+ "soc-app": "121",
2000
+ "soc-platform": "1",
2001
+ "soc-device": "1",
2002
+ _reqid: "82003",
2003
+ rt: "c"
2004
+ };
2005
+ const CLUSTER_NAMES = {
2006
+ TOP_FREE: "topselling_free",
2007
+ TOP_PAID: "topselling_paid",
2008
+ GROSSING: "topgrossing"
2009
+ };
2010
+ function listUrl(lang, country, age) {
2011
+ const params = new URLSearchParams({
2012
+ ...STATIC_QUERY_PARAMS,
2013
+ hl: lang,
2014
+ gl: country
2015
+ });
2016
+ if (age !== void 0) params.set("age", age);
2017
+ return `${BATCH_URL}?${params.toString()}`;
2018
+ }
2019
+ function buildListBody({ num, collection, category }) {
2020
+ return `f.req=%5B%5B%5B%22vyAe2%22%2C%22%5B%5Bnull%2C%5B%5B8%2C%5B20%2C${num}%5D%5D%2Ctrue%2Cnull%2C%5B64%2C1%2C195%2C71%2C8%2C72%2C9%2C10%2C11%2C139%2C12%2C16%2C145%2C148%2C150%2C151%2C152%2C27%2C30%2C31%2C96%2C32%2C34%2C163%2C100%2C165%2C104%2C169%2C108%2C110%2C113%2C55%2C56%2C57%2C122%5D%2C%5Bnull%2Cnull%2C%5B%5B%5Btrue%5D%2Cnull%2C%5B%5Bnull%2C%5B%5D%5D%5D%2Cnull%2Cnull%2Cnull%2Cnull%2C%5Bnull%2C2%5D%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%5B1%5D%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%5B1%5D%5D%2C%5Bnull%2C%5B%5Bnull%2C%5B%5D%5D%5D%5D%2C%5Bnull%2C%5B%5Bnull%2C%5B%5D%5D%5D%2Cnull%2C%5Btrue%5D%5D%2C%5Bnull%2C%5B%5Bnull%2C%5B%5D%5D%5D%5D%2Cnull%2Cnull%2Cnull%2Cnull%2C%5B%5B%5Bnull%2C%5B%5D%5D%5D%5D%2C%5B%5B%5Bnull%2C%5B%5D%5D%5D%5D%5D%2C%5B%5B%5B%5B7%2C1%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C31%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C104%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C9%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C8%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C27%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C12%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C65%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C110%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C88%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C11%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C56%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C55%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C96%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C10%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C122%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C72%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C71%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C64%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C113%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C139%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C150%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C169%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C165%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C151%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C163%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C32%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C16%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C108%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B7%2C100%5D%2C%5B%5B1%2C73%2C96%2C103%2C97%2C58%2C50%2C92%2C52%2C112%2C69%2C19%2C31%2C101%2C123%2C74%2C49%2C80%2C38%2C20%2C10%2C14%2C79%2C43%2C42%2C139%5D%5D%5D%2C%5B%5B9%2C1%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C31%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C104%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C9%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C8%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C27%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C12%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C65%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C110%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C88%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C11%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C56%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C55%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C96%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C10%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C122%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C72%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C71%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C64%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C113%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C139%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C150%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C169%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C165%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C151%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C163%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C32%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C16%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C108%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B9%2C100%5D%2C%5B%5B1%2C7%2C9%2C24%2C12%2C31%2C5%2C15%2C27%2C8%2C13%2C10%5D%5D%5D%2C%5B%5B17%2C1%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C31%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C104%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C9%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C8%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C27%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C12%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C65%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C110%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C88%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C11%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C56%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C55%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C96%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C10%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C122%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C72%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C71%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C64%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C113%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C139%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C150%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C169%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C165%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C151%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C163%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C32%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C16%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C108%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B17%2C100%5D%2C%5B%5B1%2C7%2C9%2C25%2C13%2C31%2C5%2C41%2C27%2C8%2C14%2C10%5D%5D%5D%2C%5B%5B10%2C1%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C31%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C104%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C9%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C8%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C27%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C12%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C65%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C110%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C88%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C11%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C56%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C55%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C96%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C10%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C122%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C72%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C71%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C64%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C113%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C139%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C150%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C169%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C165%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C151%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C163%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C32%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C16%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C108%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B10%2C100%5D%2C%5B%5B1%2C7%2C6%2C9%5D%5D%5D%2C%5B%5B1%2C1%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C31%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C104%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C9%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C8%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C27%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C12%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C65%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C110%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C88%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C11%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C56%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C55%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C96%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C10%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C122%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C72%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C71%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C64%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C113%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C139%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C150%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C169%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C165%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C151%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C163%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C32%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C16%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C108%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B1%2C100%5D%2C%5B%5B1%2C5%2C14%2C38%2C19%2C29%2C34%2C4%2C12%2C11%2C6%2C30%2C43%2C40%2C42%2C16%2C10%2C7%5D%5D%5D%2C%5B%5B4%2C1%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C31%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C104%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C9%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C8%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C27%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C12%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C65%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C110%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C88%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C11%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C56%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C55%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C96%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C10%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C122%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C72%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C71%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C64%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C113%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C139%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C150%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C169%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C165%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C151%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C163%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C32%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C16%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C108%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B4%2C100%5D%2C%5B%5B1%2C3%2C5%2C4%2C7%2C6%2C11%2C19%2C21%2C17%2C15%2C12%2C16%2C20%5D%5D%5D%2C%5B%5B3%2C1%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C31%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C104%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C9%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C8%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C27%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C12%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C65%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C110%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C88%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C11%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C56%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C55%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C96%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C10%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C122%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C72%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C71%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C64%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C113%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C139%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C150%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C169%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C165%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C151%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C163%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C32%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C16%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C108%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B3%2C100%5D%2C%5B%5B1%2C5%2C14%2C4%2C10%2C17%5D%5D%5D%2C%5B%5B2%2C1%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C31%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C104%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C9%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C8%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C27%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C12%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C65%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C110%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C88%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C11%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C56%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C55%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C96%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C10%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C122%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C72%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C71%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C64%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C113%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C139%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C150%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C169%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C165%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C151%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C163%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C32%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C16%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C108%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%2C%5B%5B2%2C100%5D%2C%5B%5B1%2C5%2C7%2C4%2C13%2C16%2C12%2C18%5D%5D%5D%5D%5D%5D%2Cnull%2Cnull%2C%5B%5B%5B1%2C2%5D%2C%5B10%2C8%2C9%5D%2C%5B%5D%2C%5B%5D%5D%5D%5D%2C%5B2%2C%5C%22${collection}%5C%22%2C%5C%22${category}%5C%22%5D%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D&at=AFSRYlx8XZfN8-O-IKASbNBDkB6T%3A1655531200971&`;
2021
+ }
2022
+ function resolveUrl$3(value) {
2023
+ return typeof value === "string" ? new URL(value, BASE_URL).toString() : void 0;
2024
+ }
2025
+ function microsToUnits$2(value) {
2026
+ return typeof value === "number" ? value / MICROS_PER_UNIT$2 || 0 : 0;
2027
+ }
2028
+ function isFree$2(value) {
2029
+ return value === 0;
2030
+ }
2031
+ const APPS_PATH = [
2032
+ 0,
2033
+ 1,
2034
+ 0,
2035
+ 28,
2036
+ 0
2037
+ ];
2038
+ const listItemSpecs = {
2039
+ title: {
2040
+ paths: [[0, 3]],
2041
+ schema: shape$4.title
2042
+ },
2043
+ appId: {
2044
+ paths: [[
2045
+ 0,
2046
+ 0,
2047
+ 0
2048
+ ]],
2049
+ schema: shape$4.appId
2050
+ },
2051
+ url: {
2052
+ paths: [[
2053
+ 0,
2054
+ 10,
2055
+ 4,
2056
+ 2
2057
+ ]],
2058
+ schema: shape$4.url,
2059
+ transform: resolveUrl$3
2060
+ },
2061
+ icon: {
2062
+ paths: [[
2063
+ 0,
2064
+ 1,
2065
+ 3,
2066
+ 2
2067
+ ]],
2068
+ schema: shape$4.icon
2069
+ },
2070
+ developer: {
2071
+ paths: [[0, 14]],
2072
+ schema: shape$4.developer
2073
+ },
2074
+ currency: {
2075
+ paths: [[
2076
+ 0,
2077
+ 8,
2078
+ 1,
2079
+ 0,
2080
+ 1
2081
+ ]],
2082
+ schema: shape$4.currency
2083
+ },
2084
+ price: {
2085
+ paths: [[
2086
+ 0,
2087
+ 8,
2088
+ 1,
2089
+ 0,
2090
+ 0
2091
+ ]],
2092
+ schema: shape$4.price,
2093
+ transform: microsToUnits$2
2094
+ },
2095
+ free: {
2096
+ paths: [[
2097
+ 0,
2098
+ 8,
2099
+ 1,
2100
+ 0,
2101
+ 0
2102
+ ]],
2103
+ schema: shape$4.free,
2104
+ transform: isFree$2
2105
+ },
2106
+ summary: {
2107
+ paths: [[
2108
+ 0,
2109
+ 13,
2110
+ 1
2111
+ ]],
2112
+ schema: shape$4.summary
2113
+ },
2114
+ scoreText: {
2115
+ paths: [[
2116
+ 0,
2117
+ 4,
2118
+ 0
2119
+ ]],
2120
+ schema: shape$4.scoreText
2121
+ },
2122
+ score: {
2123
+ paths: [[
2124
+ 0,
2125
+ 4,
2126
+ 1
2127
+ ]],
2128
+ schema: shape$4.score
2129
+ }
2130
+ };
2131
+ //#endregion
2132
+ //#region src/features/list/list.ts
2133
+ const listOptionsSchema = baseOptionsSchema.extend({
2134
+ collection: z.enum(collection).default("TOP_FREE"),
2135
+ category: z.enum(category).default("APPLICATION"),
2136
+ age: z.enum(age).optional(),
2137
+ num: z.number().int().min(1).default(500),
2138
+ fullDetail: z.boolean().default(false)
2139
+ });
2140
+ const LIST_CONTEXT = "list";
2141
+ function createList(getApp) {
2142
+ return async function list(options) {
2143
+ const parsed = parseOptions(listOptionsSchema, options, LIST_CONTEXT);
2144
+ const client = clientFromOptions(parsed);
2145
+ const body = buildListBody({
2146
+ num: parsed.num.toString(),
2147
+ collection: CLUSTER_NAMES[parsed.collection],
2148
+ category: parsed.category
2149
+ });
2150
+ const appsData = getPath(parseBatchResponse(await client.request({
2151
+ url: listUrl(parsed.lang, parsed.country, parsed.age),
2152
+ method: "POST",
2153
+ body
2154
+ }), LIST_RPC_ID), APPS_PATH);
2155
+ const items = Array.isArray(appsData) ? appsData.map((item) => extract(item, listItemSpecs, LIST_CONTEXT)) : [];
2156
+ if (parsed.fullDetail) return resolveFullDetail(items, parsed, getApp);
2157
+ return z.array(listItemSchema).parse(items);
2158
+ };
2159
+ }
2160
+ const list = createList(app);
2161
+ //#endregion
2162
+ //#region src/features/categories/categories.ts
2163
+ const categoriesOptionsSchema = baseOptionsSchema.pick({
2164
+ throttle: true,
2165
+ requestOptions: true
2166
+ });
2167
+ const CATEGORIES_CONTEXT = "categories";
2168
+ const categoryListSchema = z.array(z.string()).min(1);
2169
+ const CATEGORY_IDS = Object.values(category);
2170
+ function categories(options) {
2171
+ return Promise.resolve().then(() => {
2172
+ parseOptions(categoriesOptionsSchema, options ?? {}, CATEGORIES_CONTEXT);
2173
+ return categoryListSchema.parse([...CATEGORY_IDS]);
2174
+ });
2175
+ }
2176
+ //#endregion
2177
+ //#region src/core/clusterItem.ts
2178
+ const shape$3 = appItemSchema.shape;
2179
+ const PRICE_NUMBER = /([0-9.,]+)/;
2180
+ function resolveUrl$2(value) {
2181
+ return typeof value === "string" ? new URL(value, BASE_URL).toString() : void 0;
2182
+ }
2183
+ function priceFromText(value) {
2184
+ if (typeof value !== "string") return 0;
2185
+ const match = PRICE_NUMBER.exec(value);
2186
+ return match === null ? 0 : Number.parseFloat(match[0]);
2187
+ }
2188
+ function isFreeText(value) {
2189
+ return value === void 0 || value === null;
2190
+ }
2191
+ const clusterItemSpecs = {
2192
+ title: {
2193
+ paths: [[2]],
2194
+ schema: shape$3.title
2195
+ },
2196
+ appId: {
2197
+ paths: [[12, 0]],
2198
+ schema: shape$3.appId
2199
+ },
2200
+ url: {
2201
+ paths: [[
2202
+ 9,
2203
+ 4,
2204
+ 2
2205
+ ]],
2206
+ schema: shape$3.url,
2207
+ transform: resolveUrl$2
2208
+ },
2209
+ icon: {
2210
+ paths: [[
2211
+ 1,
2212
+ 1,
2213
+ 0,
2214
+ 3,
2215
+ 2
2216
+ ]],
2217
+ schema: shape$3.icon
2218
+ },
2219
+ developer: {
2220
+ paths: [[
2221
+ 4,
2222
+ 0,
2223
+ 0,
2224
+ 0
2225
+ ]],
2226
+ schema: shape$3.developer
2227
+ },
2228
+ currency: {
2229
+ paths: [[
2230
+ 7,
2231
+ 0,
2232
+ 3,
2233
+ 2,
2234
+ 1,
2235
+ 0,
2236
+ 1
2237
+ ]],
2238
+ schema: shape$3.currency
2239
+ },
2240
+ price: {
2241
+ paths: [[
2242
+ 7,
2243
+ 0,
2244
+ 3,
2245
+ 2,
2246
+ 1,
2247
+ 0,
2248
+ 2
2249
+ ]],
2250
+ schema: shape$3.price,
2251
+ transform: priceFromText
2252
+ },
2253
+ free: {
2254
+ paths: [[
2255
+ 7,
2256
+ 0,
2257
+ 3,
2258
+ 2,
2259
+ 1,
2260
+ 0,
2261
+ 2
2262
+ ]],
2263
+ schema: shape$3.free,
2264
+ transform: isFreeText
2265
+ },
2266
+ summary: {
2267
+ paths: [[
2268
+ 4,
2269
+ 1,
2270
+ 1,
2271
+ 1,
2272
+ 1
2273
+ ]],
2274
+ schema: shape$3.summary
2275
+ },
2276
+ scoreText: {
2277
+ paths: [[
2278
+ 6,
2279
+ 0,
2280
+ 2,
2281
+ 1,
2282
+ 0
2283
+ ]],
2284
+ schema: shape$3.scoreText
2285
+ },
2286
+ score: {
2287
+ paths: [[
2288
+ 6,
2289
+ 0,
2290
+ 2,
2291
+ 1,
2292
+ 1
2293
+ ]],
2294
+ schema: shape$3.score
2295
+ }
2296
+ };
2297
+ //#endregion
2298
+ //#region src/features/developer/schema.ts
2299
+ const developerAppSchema = appItemSchema;
2300
+ //#endregion
2301
+ //#region src/features/developer/specs.ts
2302
+ const MICROS_PER_UNIT$1 = 1e6;
2303
+ const shape$2 = developerAppSchema.shape;
2304
+ const NUMERIC_ID = /^\d+$/;
2305
+ function isNumericDevId(devId) {
2306
+ return NUMERIC_ID.test(devId);
2307
+ }
2308
+ function developerUrl(devId, lang, country) {
2309
+ return `${BASE_URL}${isNumericDevId(devId) ? "/store/apps/dev" : "/store/apps/developer"}?${new URLSearchParams({
2310
+ id: devId,
2311
+ hl: lang,
2312
+ gl: country
2313
+ }).toString()}`;
2314
+ }
2315
+ const NUMERIC_INITIAL_MAPPINGS = {
2316
+ apps: [
2317
+ "ds:3",
2318
+ 0,
2319
+ 1,
2320
+ 0,
2321
+ 21,
2322
+ 0
2323
+ ],
2324
+ token: [
2325
+ "ds:3",
2326
+ 0,
2327
+ 1,
2328
+ 0,
2329
+ 21,
2330
+ 1,
2331
+ 3,
2332
+ 1
2333
+ ]
2334
+ };
2335
+ const NAME_INITIAL_MAPPINGS = {
2336
+ apps: [
2337
+ "ds:3",
2338
+ 0,
2339
+ 1,
2340
+ 0,
2341
+ 22,
2342
+ 0
2343
+ ],
2344
+ token: [
2345
+ "ds:3",
2346
+ 0,
2347
+ 1,
2348
+ 0,
2349
+ 22,
2350
+ 1,
2351
+ 3,
2352
+ 1
2353
+ ]
2354
+ };
2355
+ const CLUSTER_MAPPINGS = {
2356
+ apps: [
2357
+ 0,
2358
+ 6,
2359
+ 0
2360
+ ],
2361
+ token: [
2362
+ 0,
2363
+ 6,
2364
+ 7,
2365
+ 1
2366
+ ]
2367
+ };
2368
+ function resolveUrl$1(value) {
2369
+ return typeof value === "string" ? new URL(value, BASE_URL).toString() : void 0;
2370
+ }
2371
+ function microsToUnits$1(value) {
2372
+ return typeof value === "number" ? value / MICROS_PER_UNIT$1 || 0 : 0;
2373
+ }
2374
+ function isFree$1(value) {
2375
+ return value === 0;
2376
+ }
2377
+ const nameItemSpecs = {
2378
+ title: {
2379
+ paths: [[0, 3]],
2380
+ schema: shape$2.title
2381
+ },
2382
+ appId: {
2383
+ paths: [[
2384
+ 0,
2385
+ 0,
2386
+ 0
2387
+ ]],
2388
+ schema: shape$2.appId
2389
+ },
2390
+ url: {
2391
+ paths: [[
2392
+ 0,
2393
+ 10,
2394
+ 4,
2395
+ 2
2396
+ ]],
2397
+ schema: shape$2.url,
2398
+ transform: resolveUrl$1
2399
+ },
2400
+ icon: {
2401
+ paths: [[
2402
+ 0,
2403
+ 1,
2404
+ 3,
2405
+ 2
2406
+ ]],
2407
+ schema: shape$2.icon
2408
+ },
2409
+ developer: {
2410
+ paths: [[0, 14]],
2411
+ schema: shape$2.developer
2412
+ },
2413
+ currency: {
2414
+ paths: [[
2415
+ 0,
2416
+ 8,
2417
+ 1,
2418
+ 0,
2419
+ 1
2420
+ ]],
2421
+ schema: shape$2.currency
2422
+ },
2423
+ price: {
2424
+ paths: [[
2425
+ 0,
2426
+ 8,
2427
+ 1,
2428
+ 0,
2429
+ 0
2430
+ ]],
2431
+ schema: shape$2.price,
2432
+ transform: microsToUnits$1
2433
+ },
2434
+ free: {
2435
+ paths: [[
2436
+ 0,
2437
+ 8,
2438
+ 1,
2439
+ 0,
2440
+ 0
2441
+ ]],
2442
+ schema: shape$2.free,
2443
+ transform: isFree$1
2444
+ },
2445
+ summary: {
2446
+ paths: [[
2447
+ 0,
2448
+ 13,
2449
+ 1
2450
+ ]],
2451
+ schema: shape$2.summary
2452
+ },
2453
+ scoreText: {
2454
+ paths: [[
2455
+ 0,
2456
+ 4,
2457
+ 0
2458
+ ]],
2459
+ schema: shape$2.scoreText
2460
+ },
2461
+ score: {
2462
+ paths: [[
2463
+ 0,
2464
+ 4,
2465
+ 1
2466
+ ]],
2467
+ schema: shape$2.score
2468
+ }
2469
+ };
2470
+ const numericItemSpecs = {
2471
+ title: {
2472
+ paths: [[3]],
2473
+ schema: shape$2.title
2474
+ },
2475
+ appId: {
2476
+ paths: [[0, 0]],
2477
+ schema: shape$2.appId
2478
+ },
2479
+ url: {
2480
+ paths: [[
2481
+ 10,
2482
+ 4,
2483
+ 2
2484
+ ]],
2485
+ schema: shape$2.url,
2486
+ transform: resolveUrl$1
2487
+ },
2488
+ icon: {
2489
+ paths: [[
2490
+ 1,
2491
+ 3,
2492
+ 2
2493
+ ]],
2494
+ schema: shape$2.icon
2495
+ },
2496
+ developer: {
2497
+ paths: [[14]],
2498
+ schema: shape$2.developer
2499
+ },
2500
+ currency: {
2501
+ paths: [[
2502
+ 8,
2503
+ 1,
2504
+ 0,
2505
+ 1
2506
+ ]],
2507
+ schema: shape$2.currency
2508
+ },
2509
+ price: {
2510
+ paths: [[
2511
+ 8,
2512
+ 1,
2513
+ 0,
2514
+ 0
2515
+ ]],
2516
+ schema: shape$2.price,
2517
+ transform: microsToUnits$1
2518
+ },
2519
+ free: {
2520
+ paths: [[
2521
+ 8,
2522
+ 1,
2523
+ 0,
2524
+ 0
2525
+ ]],
2526
+ schema: shape$2.free,
2527
+ transform: isFree$1
2528
+ },
2529
+ summary: {
2530
+ paths: [[13, 1]],
2531
+ schema: shape$2.summary
2532
+ },
2533
+ scoreText: {
2534
+ paths: [[4, 0]],
2535
+ schema: shape$2.scoreText
2536
+ },
2537
+ score: {
2538
+ paths: [[4, 1]],
2539
+ schema: shape$2.score
2540
+ }
2541
+ };
2542
+ //#endregion
2543
+ //#region src/features/developer/developer.ts
2544
+ const developerOptionsSchema = baseOptionsSchema.extend({
2545
+ devId: z.string().min(1),
2546
+ num: z.number().int().min(1).default(60),
2547
+ fullDetail: z.boolean().default(false)
2548
+ });
2549
+ const DEVELOPER_CONTEXT = "developer";
2550
+ function extractInitial(blocks, numeric) {
2551
+ const mappings = numeric ? NUMERIC_INITIAL_MAPPINGS : NAME_INITIAL_MAPPINGS;
2552
+ const itemSpecs = numeric ? numericItemSpecs : nameItemSpecs;
2553
+ const appsData = getPath(blocks, mappings.apps);
2554
+ const apps = Array.isArray(appsData) ? appsData.map((item) => extract(item, itemSpecs, DEVELOPER_CONTEXT)) : [];
2555
+ const token = getPath(blocks, mappings.token);
2556
+ return {
2557
+ apps,
2558
+ token: typeof token === "string" ? token : void 0
2559
+ };
2560
+ }
2561
+ function createDeveloper(getApp) {
2562
+ return async function developer(options) {
2563
+ const parsed = parseOptions(developerOptionsSchema, options, DEVELOPER_CONTEXT);
2564
+ const numeric = isNumericDevId(parsed.devId);
2565
+ const client = clientFromOptions(parsed);
2566
+ const initial = extractInitial(parseScriptData(await client.request({ url: developerUrl(parsed.devId, parsed.lang, parsed.country) })).blocks, numeric);
2567
+ const sliced = (await fetchClusterApps({
2568
+ client,
2569
+ lang: parsed.lang,
2570
+ country: parsed.country,
2571
+ num: parsed.num,
2572
+ initialApps: initial.apps,
2573
+ initialToken: initial.token,
2574
+ itemSpecs: clusterItemSpecs,
2575
+ appsPath: CLUSTER_MAPPINGS.apps,
2576
+ tokenPath: CLUSTER_MAPPINGS.token,
2577
+ context: DEVELOPER_CONTEXT
2578
+ })).slice(0, parsed.num);
2579
+ if (parsed.fullDetail) return resolveFullDetail(sliced, parsed, getApp);
2580
+ return z.array(developerAppSchema).parse(sliced);
2581
+ };
2582
+ }
2583
+ const developer = createDeveloper(app);
2584
+ //#endregion
2585
+ //#region src/features/similar/schema.ts
2586
+ const similarAppSchema = appItemSchema;
2587
+ //#endregion
2588
+ //#region src/features/similar/specs.ts
2589
+ const MICROS_PER_UNIT = 1e6;
2590
+ const shape$1 = similarAppSchema.shape;
2591
+ const CLUSTERS_RPC_ID = "ag2B9c";
2592
+ const CLUSTERS_PATH = [1, 1];
2593
+ const CLUSTER_MAPPING = {
2594
+ title: [
2595
+ 21,
2596
+ 1,
2597
+ 0
2598
+ ],
2599
+ url: [
2600
+ 21,
2601
+ 1,
2602
+ 2,
2603
+ 4,
2604
+ 2
2605
+ ]
2606
+ };
2607
+ const SIMILAR_APPS = "Similar apps";
2608
+ const SIMILAR_GAMES = "Similar games";
2609
+ const CLUSTER_PAGE_MAPPINGS = {
2610
+ apps: [
2611
+ "ds:3",
2612
+ 0,
2613
+ 1,
2614
+ 0,
2615
+ 21,
2616
+ 0
2617
+ ],
2618
+ token: [
2619
+ "ds:3",
2620
+ 0,
2621
+ 1,
2622
+ 0,
2623
+ 21,
2624
+ 1,
2625
+ 3,
2626
+ 1
2627
+ ]
2628
+ };
2629
+ const PAGINATION_MAPPINGS = {
2630
+ apps: [
2631
+ 0,
2632
+ 0,
2633
+ 0
2634
+ ],
2635
+ token: [
2636
+ 0,
2637
+ 0,
2638
+ 7,
2639
+ 1
2640
+ ]
2641
+ };
2642
+ function similarDetailsUrl(appId, country) {
2643
+ return `${BASE_URL}/store/apps/details?${new URLSearchParams({
2644
+ id: appId,
2645
+ hl: "en",
2646
+ gl: country
2647
+ }).toString()}`;
2648
+ }
2649
+ function similarClusterUrl(clusterPath, lang, country) {
2650
+ return `${BASE_URL}${clusterPath}&gl=${country}&hl=${lang}`;
2651
+ }
2652
+ function findSimilarClusterPath(data) {
2653
+ const dsKey = resolveDsKey(data, CLUSTERS_RPC_ID);
2654
+ if (dsKey === void 0) return;
2655
+ const clusters = getPath(data.blocks, [dsKey, ...CLUSTERS_PATH]);
2656
+ if (!Array.isArray(clusters)) return;
2657
+ for (const cluster of clusters) {
2658
+ const title = getPath(cluster, CLUSTER_MAPPING.title);
2659
+ if (title === SIMILAR_APPS || title === SIMILAR_GAMES) {
2660
+ const clusterPath = getPath(cluster, CLUSTER_MAPPING.url);
2661
+ if (typeof clusterPath === "string") return clusterPath;
2662
+ }
2663
+ }
2664
+ }
2665
+ function resolveUrl(value) {
2666
+ return typeof value === "string" ? new URL(value, BASE_URL).toString() : void 0;
2667
+ }
2668
+ function microsToUnits(value) {
2669
+ return typeof value === "number" ? value / MICROS_PER_UNIT || 0 : 0;
2670
+ }
2671
+ function isFree(value) {
2672
+ return value === 0;
2673
+ }
2674
+ const similarItemSpecs = {
2675
+ title: {
2676
+ paths: [[3]],
2677
+ schema: shape$1.title
2678
+ },
2679
+ appId: {
2680
+ paths: [[0, 0]],
2681
+ schema: shape$1.appId
2682
+ },
2683
+ url: {
2684
+ paths: [[
2685
+ 10,
2686
+ 4,
2687
+ 2
2688
+ ]],
2689
+ schema: shape$1.url,
2690
+ transform: resolveUrl
2691
+ },
2692
+ icon: {
2693
+ paths: [[
2694
+ 1,
2695
+ 3,
2696
+ 2
2697
+ ]],
2698
+ schema: shape$1.icon
2699
+ },
2700
+ developer: {
2701
+ paths: [[14]],
2702
+ schema: shape$1.developer
2703
+ },
2704
+ currency: {
2705
+ paths: [[
2706
+ 8,
2707
+ 1,
2708
+ 0,
2709
+ 1
2710
+ ]],
2711
+ schema: shape$1.currency
2712
+ },
2713
+ price: {
2714
+ paths: [[
2715
+ 8,
2716
+ 1,
2717
+ 0,
2718
+ 0
2719
+ ]],
2720
+ schema: shape$1.price,
2721
+ transform: microsToUnits
2722
+ },
2723
+ free: {
2724
+ paths: [[
2725
+ 8,
2726
+ 1,
2727
+ 0,
2728
+ 0
2729
+ ]],
2730
+ schema: shape$1.free,
2731
+ transform: isFree
2732
+ },
2733
+ summary: {
2734
+ paths: [[13, 1]],
2735
+ schema: shape$1.summary
2736
+ },
2737
+ scoreText: {
2738
+ paths: [[4, 0]],
2739
+ schema: shape$1.scoreText
2740
+ },
2741
+ score: {
2742
+ paths: [[4, 1]],
2743
+ schema: shape$1.score
2744
+ }
2745
+ };
2746
+ //#endregion
2747
+ //#region src/features/similar/similar.ts
2748
+ const similarOptionsSchema = baseOptionsSchema.extend({
2749
+ appId: z.string().min(1),
2750
+ fullDetail: z.boolean().default(false)
2751
+ });
2752
+ const SIMILAR_CONTEXT = "similar";
2753
+ function extractClusterPage(blocks) {
2754
+ const appsData = getPath(blocks, CLUSTER_PAGE_MAPPINGS.apps);
2755
+ const apps = Array.isArray(appsData) ? appsData.map((item) => extract(item, similarItemSpecs, SIMILAR_CONTEXT)) : [];
2756
+ const token = getPath(blocks, CLUSTER_PAGE_MAPPINGS.token);
2757
+ return {
2758
+ apps,
2759
+ token: typeof token === "string" ? token : void 0
2760
+ };
2761
+ }
2762
+ function createSimilar(getApp) {
2763
+ return async function similar(options) {
2764
+ const parsed = parseOptions(similarOptionsSchema, options, SIMILAR_CONTEXT);
2765
+ const client = clientFromOptions(parsed);
2766
+ const clusterPath = findSimilarClusterPath(parseScriptData(await client.request({ url: similarDetailsUrl(parsed.appId, parsed.country) })));
2767
+ if (clusterPath === void 0) return z.array(similarAppSchema).parse([]);
2768
+ const page = extractClusterPage(parseScriptData(await client.request({ url: similarClusterUrl(clusterPath, parsed.lang, parsed.country) })).blocks);
2769
+ const items = await fetchClusterApps({
2770
+ client,
2771
+ lang: parsed.lang,
2772
+ country: parsed.country,
2773
+ num: 100,
2774
+ initialApps: page.apps,
2775
+ initialToken: page.token,
2776
+ itemSpecs: clusterItemSpecs,
2777
+ appsPath: PAGINATION_MAPPINGS.apps,
2778
+ tokenPath: PAGINATION_MAPPINGS.token,
2779
+ context: SIMILAR_CONTEXT
2780
+ });
2781
+ if (parsed.fullDetail) return resolveFullDetail(items, parsed, getApp);
2782
+ return z.array(similarAppSchema).parse(items);
2783
+ };
2784
+ }
2785
+ const similar = createSimilar(app);
2786
+ //#endregion
2787
+ //#region src/features/reviews/schema.ts
2788
+ const reviewCriteriaSchema = z.object({
2789
+ criteria: z.string(),
2790
+ rating: z.number().nullable()
2791
+ });
2792
+ const reviewSchema = z.object({
2793
+ id: z.string(),
2794
+ userName: z.string(),
2795
+ userImage: z.url().optional(),
2796
+ date: z.iso.datetime(),
2797
+ score: z.number().min(1).max(5),
2798
+ title: z.string().nullable().optional(),
2799
+ text: z.string().optional(),
2800
+ replyDate: z.iso.datetime().optional(),
2801
+ replyText: z.string().optional(),
2802
+ version: z.string().optional(),
2803
+ thumbsUp: z.number().optional(),
2804
+ criterias: z.array(reviewCriteriaSchema).default([])
2805
+ });
2806
+ const reviewsResultSchema = z.object({
2807
+ data: z.array(reviewSchema),
2808
+ nextPaginationToken: z.string().nullable()
2809
+ });
2810
+ //#endregion
2811
+ //#region src/features/reviews/specs.ts
2812
+ const REVIEWS_RPC_ID = "UsvDTd";
2813
+ const REVIEWS_STATIC_QUERY = "rpcids=qnKhOb&f.sid=-697906427155521722&bl=boq_playuiserver_20190903.08_p0";
2814
+ const REVIEWS_TRAILING_QUERY = "authuser&soc-app=121&soc-platform=1&soc-device=1&_reqid=1065213";
2815
+ function reviewsUrl(lang, country) {
2816
+ return `${BASE_URL}/_/PlayStoreUi/data/batchexecute?${REVIEWS_STATIC_QUERY}&hl=${lang}&gl=${country}&${REVIEWS_TRAILING_QUERY}`;
2817
+ }
2818
+ function buildInitialReviewsBody(sort, appId) {
2819
+ return `f.req=%5B%5B%5B%22UsvDTd%22%2C%22%5Bnull%2Cnull%2C%5B2%2C${sort.toString()}%2C%5B${150 .toString()}%2Cnull%2Cnull%5D%2Cnull%2C%5B%5D%5D%2C%5B%5C%22${appId}%5C%22%2C7%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D`;
2820
+ }
2821
+ function buildPaginatedReviewsBody(sort, appId, withToken) {
2822
+ return `f.req=%5B%5B%5B%22UsvDTd%22%2C%22%5Bnull%2Cnull%2C%5B2%2C${sort.toString()}%2C%5B${150 .toString()}%2Cnull%2C%5C%22${withToken}%5C%22%5D%2Cnull%2C%5B%5D%5D%2C%5B%5C%22${appId}%5C%22%2C7%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D`;
2823
+ }
2824
+ const REVIEWS_RESPONSE_PATHS = {
2825
+ reviews: [0],
2826
+ token: [1, 1]
2827
+ };
2828
+ const shape = reviewSchema.shape;
2829
+ function generateDate(value) {
2830
+ if (!Array.isArray(value)) return;
2831
+ const seconds = Number(value[0]);
2832
+ const nanos = Number(value[1]);
2833
+ const nanoText = (Number.isFinite(nanos) && nanos !== 0 ? nanos.toString() : "000").substring(0, 3);
2834
+ const milliseconds = Number(`${seconds.toString()}${nanoText}`);
2835
+ const date = new Date(milliseconds);
2836
+ return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
2837
+ }
2838
+ function alwaysNull() {
2839
+ return null;
2840
+ }
2841
+ function emptyToUndefined(value) {
2842
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2843
+ }
2844
+ function buildCriteria(entry) {
2845
+ if (!Array.isArray(entry)) return {
2846
+ criteria: void 0,
2847
+ rating: null
2848
+ };
2849
+ const fields = entry;
2850
+ const ratingHolder = fields[1];
2851
+ const ratingFields = Array.isArray(ratingHolder) ? ratingHolder : [];
2852
+ const rating = ratingFields.length > 0 ? ratingFields[0] : null;
2853
+ return {
2854
+ criteria: fields[0],
2855
+ rating
2856
+ };
2857
+ }
2858
+ function mapCriterias(value) {
2859
+ return Array.isArray(value) ? value.map(buildCriteria) : [];
2860
+ }
2861
+ const reviewItemSpecs = {
2862
+ id: {
2863
+ paths: [[0]],
2864
+ schema: shape.id
2865
+ },
2866
+ userName: {
2867
+ paths: [[1, 0]],
2868
+ schema: shape.userName
2869
+ },
2870
+ userImage: {
2871
+ paths: [[
2872
+ 1,
2873
+ 1,
2874
+ 3,
2875
+ 2
2876
+ ]],
2877
+ schema: shape.userImage
2878
+ },
2879
+ date: {
2880
+ paths: [[5]],
2881
+ schema: shape.date,
2882
+ transform: generateDate
2883
+ },
2884
+ score: {
2885
+ paths: [[2]],
2886
+ schema: shape.score
2887
+ },
2888
+ title: {
2889
+ paths: [[0]],
2890
+ schema: shape.title,
2891
+ transform: alwaysNull
2892
+ },
2893
+ text: {
2894
+ paths: [[4]],
2895
+ schema: shape.text
2896
+ },
2897
+ replyDate: {
2898
+ paths: [[7, 2]],
2899
+ schema: shape.replyDate,
2900
+ transform: generateDate
2901
+ },
2902
+ replyText: {
2903
+ paths: [[7, 1]],
2904
+ schema: shape.replyText,
2905
+ transform: emptyToUndefined
2906
+ },
2907
+ version: {
2908
+ paths: [[10]],
2909
+ schema: shape.version,
2910
+ transform: emptyToUndefined
2911
+ },
2912
+ thumbsUp: {
2913
+ paths: [[6]],
2914
+ schema: shape.thumbsUp
2915
+ },
2916
+ criterias: {
2917
+ paths: [[12, 0]],
2918
+ schema: shape.criterias,
2919
+ transform: mapCriterias
2920
+ }
2921
+ };
2922
+ //#endregion
2923
+ //#region src/features/reviews/reviews.ts
2924
+ const REVIEWS_CONTEXT = "reviews";
2925
+ const sortSchema = z.union([
2926
+ z.literal(sort.NEWEST),
2927
+ z.literal(sort.RATING),
2928
+ z.literal(sort.HELPFULNESS)
2929
+ ]).default(sort.NEWEST);
2930
+ const reviewsOptionsSchema = baseOptionsSchema.extend({
2931
+ appId: z.string().min(1),
2932
+ sort: sortSchema,
2933
+ num: z.number().int().min(1).default(150),
2934
+ paginate: z.boolean().default(false),
2935
+ nextPaginationToken: z.string().optional()
2936
+ });
2937
+ function reviewsBody(options, token) {
2938
+ return token === void 0 ? buildInitialReviewsBody(options.sort, options.appId) : buildPaginatedReviewsBody(options.sort, options.appId, token);
2939
+ }
2940
+ async function fetchReviewsPage(client, options, token) {
2941
+ const payload = parseBatchResponse(await client.request({
2942
+ url: reviewsUrl(options.lang, options.country),
2943
+ method: "POST",
2944
+ body: reviewsBody(options, token)
2945
+ }), REVIEWS_RPC_ID);
2946
+ const rawReviews = getPath(payload, REVIEWS_RESPONSE_PATHS.reviews);
2947
+ const reviews = Array.isArray(rawReviews) ? rawReviews.map((item) => extract(item, reviewItemSpecs, REVIEWS_CONTEXT)) : [];
2948
+ const rawToken = getPath(payload, REVIEWS_RESPONSE_PATHS.token);
2949
+ return {
2950
+ reviews,
2951
+ token: typeof rawToken === "string" && rawToken.length > 0 ? rawToken : void 0
2952
+ };
2953
+ }
2954
+ async function fetchSinglePage(client, options) {
2955
+ const page = await fetchReviewsPage(client, options, options.nextPaginationToken);
2956
+ return reviewsResultSchema.parse({
2957
+ data: page.reviews,
2958
+ nextPaginationToken: page.token ?? null
2959
+ });
2960
+ }
2961
+ async function accumulateReviews(client, options) {
2962
+ const collected = [];
2963
+ const seenTokens = /* @__PURE__ */ new Set();
2964
+ let token = options.nextPaginationToken;
2965
+ while (collected.length < options.num) {
2966
+ const page = await fetchReviewsPage(client, options, token);
2967
+ for (const review of page.reviews) collected.push(review);
2968
+ if (page.token === void 0 || seenTokens.has(page.token)) break;
2969
+ seenTokens.add(page.token);
2970
+ token = page.token;
2971
+ }
2972
+ return reviewsResultSchema.parse({
2973
+ data: collected.slice(0, options.num),
2974
+ nextPaginationToken: null
2975
+ });
2976
+ }
2977
+ async function reviews(options) {
2978
+ const parsed = parseOptions(reviewsOptionsSchema, options, REVIEWS_CONTEXT);
2979
+ const client = clientFromOptions(parsed);
2980
+ return parsed.paginate ? fetchSinglePage(client, parsed) : accumulateReviews(client, parsed);
2981
+ }
2982
+ //#endregion
2983
+ //#region src/features/permissions/schema.ts
2984
+ const permissionTypeSchema = z.union([z.literal(permission.COMMON), z.literal(permission.OTHER)]);
2985
+ const permissionSchema = z.object({
2986
+ permission: z.string(),
2987
+ type: permissionTypeSchema
2988
+ });
2989
+ //#endregion
2990
+ //#region src/features/permissions/specs.ts
2991
+ const PERMISSIONS_RPC_ID = "xdSrCf";
2992
+ const PERMISSIONS_STATIC_QUERY = "rpcids=qnKhOb&f.sid=-697906427155521722&bl=boq_playuiserver_20190903.08_p0";
2993
+ const PERMISSIONS_TRAILING_QUERY = "authuser&soc-app=121&soc-platform=1&soc-device=1&_reqid=1065213";
2994
+ function permissionsUrl(lang, country) {
2995
+ return `${BASE_URL}/_/PlayStoreUi/data/batchexecute?${PERMISSIONS_STATIC_QUERY}&hl=${lang}&gl=${country}&${PERMISSIONS_TRAILING_QUERY}`;
2996
+ }
2997
+ function buildPermissionsBody(appId) {
2998
+ return buildBatchBody(PERMISSIONS_RPC_ID, [[
2999
+ null,
3000
+ [appId, 7],
3001
+ []
3002
+ ]], [null, "1"]);
3003
+ }
3004
+ const PERMISSION_SECTIONS = [permission.COMMON, permission.OTHER];
3005
+ const GROUP_PERMISSIONS_PATH = [2];
3006
+ const PERMISSION_TEXT_PATH = [1];
3007
+ function sectionEntries(section, type) {
3008
+ if (!Array.isArray(section)) return [];
3009
+ const entries = [];
3010
+ for (const group of section) {
3011
+ const groupPermissions = getPath(group, GROUP_PERMISSIONS_PATH);
3012
+ if (!Array.isArray(groupPermissions)) continue;
3013
+ for (const groupPermission of groupPermissions) {
3014
+ const text = getPath(groupPermission, PERMISSION_TEXT_PATH);
3015
+ if (typeof text === "string" && text.length > 0) entries.push({
3016
+ permission: text,
3017
+ type
3018
+ });
3019
+ }
3020
+ }
3021
+ return entries;
3022
+ }
3023
+ function mapPermissions(payload) {
3024
+ if (!Array.isArray(payload)) return [];
3025
+ return PERMISSION_SECTIONS.flatMap((type) => sectionEntries(payload[type], type));
3026
+ }
3027
+ //#endregion
3028
+ //#region src/features/permissions/permissions.ts
3029
+ const PERMISSIONS_CONTEXT = "permissions";
3030
+ const permissionsOptionsSchema = baseOptionsSchema.extend({
3031
+ appId: z.string().min(1),
3032
+ short: z.boolean().default(false)
3033
+ });
3034
+ const permissionsResultSchema = z.array(permissionSchema);
3035
+ async function permissions(options) {
3036
+ const parsed = parseOptions(permissionsOptionsSchema, options, PERMISSIONS_CONTEXT);
3037
+ const payload = parseBatchResponse(await clientFromOptions(parsed).request({
3038
+ url: permissionsUrl(parsed.lang, parsed.country),
3039
+ method: "POST",
3040
+ body: buildPermissionsBody(parsed.appId)
3041
+ }), PERMISSIONS_RPC_ID);
3042
+ const entries = permissionsResultSchema.parse(mapPermissions(payload));
3043
+ if (!parsed.short) return entries;
3044
+ return entries.filter((entry) => entry.type === permission.COMMON).map((entry) => entry.permission);
3045
+ }
3046
+ //#endregion
3047
+ //#region src/features/datasafety/schema.ts
3048
+ const dataEntrySchema = z.object({
3049
+ data: z.string(),
3050
+ optional: z.boolean(),
3051
+ purpose: z.string().optional(),
3052
+ type: z.string()
3053
+ });
3054
+ const securityPracticeSchema = z.object({
3055
+ practice: z.string(),
3056
+ description: z.string().optional()
3057
+ });
3058
+ const dataSafetySchema = z.object({
3059
+ sharedData: z.array(dataEntrySchema).default([]),
3060
+ collectedData: z.array(dataEntrySchema).default([]),
3061
+ securityPractices: z.array(securityPracticeSchema).default([]),
3062
+ privacyPolicyUrl: z.url().optional()
3063
+ });
3064
+ //#endregion
3065
+ //#region src/features/datasafety/specs.ts
3066
+ const SHARED_DATA_PATH = [
3067
+ "ds:3",
3068
+ 1,
3069
+ 2,
3070
+ 1,
3071
+ "138",
3072
+ 4,
3073
+ 0,
3074
+ 0
3075
+ ];
3076
+ const COLLECTED_DATA_PATH = [
3077
+ "ds:3",
3078
+ 1,
3079
+ 2,
3080
+ 1,
3081
+ "138",
3082
+ 4,
3083
+ 1,
3084
+ 0
3085
+ ];
3086
+ const SECURITY_PRACTICES_PATH = [
3087
+ "ds:3",
3088
+ 1,
3089
+ 2,
3090
+ 1,
3091
+ "138",
3092
+ 9,
3093
+ 2
3094
+ ];
3095
+ const PRIVACY_POLICY_PATH = [
3096
+ "ds:3",
3097
+ 1,
3098
+ 2,
3099
+ 1,
3100
+ "100",
3101
+ 0,
3102
+ 5,
3103
+ 2
3104
+ ];
3105
+ const ENTRY_TYPE_PATH = [0, 1];
3106
+ const ENTRY_DETAILS_PATH = [4];
3107
+ const DETAIL_DATA_PATH = [0];
3108
+ const DETAIL_OPTIONAL_PATH = [1];
3109
+ const DETAIL_PURPOSE_PATH = [2];
3110
+ const PRACTICE_LABEL_PATH = [1];
3111
+ const PRACTICE_DESCRIPTION_PATH = [2, 1];
3112
+ function mapDataEntries(value) {
3113
+ if (!Array.isArray(value)) return [];
3114
+ return value.flatMap((entry) => {
3115
+ const type = getPath(entry, ENTRY_TYPE_PATH);
3116
+ const details = getPath(entry, ENTRY_DETAILS_PATH);
3117
+ if (!Array.isArray(details)) return [];
3118
+ return details.map((detail) => ({
3119
+ data: getPath(detail, DETAIL_DATA_PATH),
3120
+ optional: Boolean(getPath(detail, DETAIL_OPTIONAL_PATH)),
3121
+ purpose: getPath(detail, DETAIL_PURPOSE_PATH),
3122
+ type
3123
+ }));
3124
+ });
3125
+ }
3126
+ function mapSecurityPractices(value) {
3127
+ if (!Array.isArray(value)) return [];
3128
+ return value.map((practice) => ({
3129
+ practice: getPath(practice, PRACTICE_LABEL_PATH),
3130
+ description: getPath(practice, PRACTICE_DESCRIPTION_PATH)
3131
+ }));
3132
+ }
3133
+ const dataSafetySpecs = {
3134
+ sharedData: {
3135
+ paths: [SHARED_DATA_PATH],
3136
+ schema: z.array(dataEntrySchema).default([]),
3137
+ transform: mapDataEntries
3138
+ },
3139
+ collectedData: {
3140
+ paths: [COLLECTED_DATA_PATH],
3141
+ schema: z.array(dataEntrySchema).default([]),
3142
+ transform: mapDataEntries
3143
+ },
3144
+ securityPractices: {
3145
+ paths: [SECURITY_PRACTICES_PATH],
3146
+ schema: z.array(securityPracticeSchema).default([]),
3147
+ transform: mapSecurityPractices
3148
+ },
3149
+ privacyPolicyUrl: {
3150
+ paths: [PRIVACY_POLICY_PATH],
3151
+ schema: z.url().optional()
3152
+ }
3153
+ };
3154
+ //#endregion
3155
+ //#region src/features/datasafety/datasafety.ts
3156
+ const DATA_SAFETY_CONTEXT = "datasafety";
3157
+ const dataSafetyOptionsSchema = baseOptionsSchema.extend({ appId: z.string().min(1) });
3158
+ const DATA_SAFETY_URL = `${BASE_URL}/store/apps/datasafety`;
3159
+ async function datasafety(options) {
3160
+ const parsed = parseOptions(dataSafetyOptionsSchema, options, DATA_SAFETY_CONTEXT);
3161
+ const params = new URLSearchParams({
3162
+ id: parsed.appId,
3163
+ hl: parsed.lang
3164
+ });
3165
+ const url = `${DATA_SAFETY_URL}?${params.toString()}`;
3166
+ const extracted = extract(parseScriptData(await clientFromOptions(parsed).request({ url })), dataSafetySpecs, DATA_SAFETY_CONTEXT);
3167
+ return dataSafetySchema.parse(extracted);
3168
+ }
3169
+ //#endregion
3170
+ //#region src/features/memoized/memoized.ts
3171
+ const DEFAULT_MAX_AGE_MS = 1e3 * 60 * 5;
3172
+ const DEFAULT_MAX = 1e3;
3173
+ function createMemoizer(maxAgeMs, max) {
3174
+ const lifecycle = new LRUCache({
3175
+ max,
3176
+ ttl: maxAgeMs,
3177
+ ttlAutopurge: true,
3178
+ perf: { now: () => Date.now() },
3179
+ dispose: (drop) => {
3180
+ drop();
3181
+ }
3182
+ });
3183
+ return (name, fn) => {
3184
+ const store = /* @__PURE__ */ new Map();
3185
+ return (options) => {
3186
+ const key = `${name}:${JSON.stringify(options)}`;
3187
+ if (lifecycle.has(key)) {
3188
+ const cached = store.get(key);
3189
+ if (cached !== void 0) return cached;
3190
+ }
3191
+ const pending = fn(options);
3192
+ store.set(key, pending);
3193
+ lifecycle.set(key, () => {
3194
+ store.delete(key);
3195
+ });
3196
+ pending.catch(() => {
3197
+ store.delete(key);
3198
+ lifecycle.delete(key);
3199
+ });
3200
+ return pending;
3201
+ };
3202
+ };
3203
+ }
3204
+ function memoized(options) {
3205
+ const memoize = createMemoizer(options?.maxAgeMs ?? DEFAULT_MAX_AGE_MS, options?.max ?? DEFAULT_MAX);
3206
+ const memoApp = memoize("app", app);
3207
+ const memoCategories = memoize("categories", (input) => categories(input));
3208
+ return {
3209
+ BASE_URL,
3210
+ age,
3211
+ category,
3212
+ clusters,
3213
+ collection,
3214
+ permission,
3215
+ sort,
3216
+ app: memoApp,
3217
+ search: memoize("search", createSearch(memoApp)),
3218
+ suggest: memoize("suggest", suggest),
3219
+ list: memoize("list", createList(memoApp)),
3220
+ categories: (input) => memoCategories(input),
3221
+ developer: memoize("developer", createDeveloper(memoApp)),
3222
+ similar: memoize("similar", createSimilar(memoApp)),
3223
+ reviews: memoize("reviews", reviews),
3224
+ permissions: memoize("permissions", permissions),
3225
+ datasafety: memoize("datasafety", datasafety)
3226
+ };
3227
+ }
3228
+ //#endregion
3229
+ //#region src/index.ts
3230
+ const gplay = {
3231
+ BASE_URL,
3232
+ age,
3233
+ category,
3234
+ clusters,
3235
+ collection,
3236
+ permission,
3237
+ sort,
3238
+ app,
3239
+ search,
3240
+ suggest,
3241
+ list,
3242
+ categories,
3243
+ developer,
3244
+ similar,
3245
+ reviews,
3246
+ permissions,
3247
+ datasafety,
3248
+ memoized
3249
+ };
3250
+ //#endregion
3251
+ export { BASE_URL, BlockedError, GooglePlayError, HttpError, NotFoundError, ParseError, RateLimitError, SpecError, ValidationError, age, app, categories, category, clusters, collection, datasafety, gplay as default, developer, list, memoized, permission, permissions, reviews, search, similar, sort, suggest };
3252
+
3253
+ //# sourceMappingURL=index.js.map