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