@lobu/cli 6.0.1 → 6.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/README.md +20 -27
- package/dist/bundled-skills/lobu/SKILL.md +11 -11
- package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +26 -0
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +1 -1
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +4 -4
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/agent.d.ts +7 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +65 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts +12 -9
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +117 -56
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/dev.d.ts +15 -7
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +79 -44
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +136 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eval.d.ts +8 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +56 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts +20 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +332 -183
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +11 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +28 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +14 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/mcp.d.ts +2 -2
- package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
- package/dist/commands/memory/_lib/mcp.js +24 -12
- package/dist/commands/memory/_lib/mcp.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
- package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
- package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/schema.d.ts +1 -1
- package/dist/commands/memory/_lib/schema.js +1 -1
- package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.js +5 -6
- package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
- package/dist/commands/memory/run.d.ts.map +1 -1
- package/dist/commands/memory/run.js +2 -2
- package/dist/commands/memory/run.js.map +1 -1
- package/dist/commands/platforms/platform-prompts.d.ts +0 -1
- package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
- package/dist/commands/platforms/platform-prompts.js +54 -8
- package/dist/commands/platforms/platform-prompts.js.map +1 -1
- package/dist/commands/telemetry.d.ts +10 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +68 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connectors/README.md +534 -0
- package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
- package/dist/connectors/browser-scraper-utils.ts +214 -0
- package/dist/connectors/capterra.ts +273 -0
- package/dist/connectors/g2.ts +286 -0
- package/dist/connectors/github.ts +1553 -0
- package/dist/connectors/glassdoor.ts +291 -0
- package/dist/connectors/gmaps.ts +197 -0
- package/dist/connectors/google_calendar.ts +631 -0
- package/dist/connectors/google_gmail.ts +751 -0
- package/dist/connectors/google_photos.ts +776 -0
- package/dist/connectors/google_play.ts +342 -0
- package/dist/connectors/hackernews.ts +471 -0
- package/dist/connectors/index.ts +23 -0
- package/dist/connectors/ios_appstore.ts +226 -0
- package/dist/connectors/linkedin.ts +471 -0
- package/dist/connectors/microsoft_outlook.ts +410 -0
- package/dist/connectors/producthunt.ts +471 -0
- package/dist/connectors/reddit.ts +600 -0
- package/dist/connectors/rss.ts +448 -0
- package/dist/connectors/spotify.ts +590 -0
- package/dist/connectors/trustpilot.ts +199 -0
- package/dist/connectors/website.ts +629 -0
- package/dist/connectors/whatsapp.ts +1073 -0
- package/dist/connectors/x.ts +526 -0
- package/dist/connectors/youtube.ts +666 -0
- package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
- package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
- package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
- package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
- package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
- package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
- package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
- package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
- package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
- package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
- package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
- package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
- package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
- package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
- package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
- package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
- package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
- package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
- package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
- package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
- package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
- package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
- package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
- package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
- package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
- package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
- package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
- package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
- package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
- package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
- package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
- package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
- package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
- package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
- package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
- package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
- package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
- package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
- package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
- package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
- package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
- package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
- package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
- package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
- package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
- package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -23
- package/dist/index.js.map +1 -1
- package/dist/internal/api-client.d.ts +4 -8
- package/dist/internal/api-client.d.ts.map +1 -1
- package/dist/internal/api-client.js +1 -1
- package/dist/internal/api-client.js.map +1 -1
- package/dist/internal/context.js +2 -2
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/credentials.d.ts.map +1 -1
- package/dist/internal/credentials.js +6 -1
- package/dist/internal/credentials.js.map +1 -1
- package/dist/internal/index.d.ts +2 -3
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +2 -2
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/oauth.d.ts +6 -5
- package/dist/internal/oauth.d.ts.map +1 -1
- package/dist/internal/oauth.js +2 -2
- package/dist/internal/project-link.d.ts +10 -0
- package/dist/internal/project-link.d.ts.map +1 -0
- package/dist/internal/project-link.js +48 -0
- package/dist/internal/project-link.js.map +1 -0
- package/dist/providers.json +2 -2
- package/dist/server.bundle.mjs +3090 -4321
- package/dist/start-local.bundle.mjs +71481 -0
- package/dist/templates/README.md.tmpl +10 -11
- package/package.json +14 -12
- package/dist/__tests__/chat.integration.test.d.ts +0 -2
- package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
- package/dist/__tests__/chat.integration.test.js +0 -337
- package/dist/__tests__/chat.integration.test.js.map +0 -1
- package/dist/__tests__/dev.test.d.ts +0 -2
- package/dist/__tests__/dev.test.d.ts.map +0 -1
- package/dist/__tests__/dev.test.js +0 -25
- package/dist/__tests__/dev.test.js.map +0 -1
- package/dist/__tests__/init-memory.test.d.ts +0 -2
- package/dist/__tests__/init-memory.test.d.ts.map +0 -1
- package/dist/__tests__/init-memory.test.js +0 -45
- package/dist/__tests__/init-memory.test.js.map +0 -1
- package/dist/__tests__/token.test.d.ts +0 -2
- package/dist/__tests__/token.test.d.ts.map +0 -1
- package/dist/__tests__/token.test.js +0 -52
- package/dist/__tests__/token.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
- package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
- package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
- package/dist/commands/apply.d.ts +0 -3
- package/dist/commands/apply.d.ts.map +0 -1
- package/dist/commands/apply.js +0 -5
- package/dist/commands/apply.js.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
- package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
- package/dist/internal/__tests__/api-client.test.d.ts +0 -2
- package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
- package/dist/internal/__tests__/api-client.test.js +0 -95
- package/dist/internal/__tests__/api-client.test.js.map +0 -1
- package/dist/internal/__tests__/context.test.d.ts +0 -2
- package/dist/internal/__tests__/context.test.d.ts.map +0 -1
- package/dist/internal/__tests__/context.test.js +0 -77
- package/dist/internal/__tests__/context.test.js.map +0 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Play Store Connector (V1 runtime)
|
|
3
|
+
*
|
|
4
|
+
* Syncs app reviews from the Google Play Store.
|
|
5
|
+
* Directly calls the Play Store batchexecute API instead of using an npm package.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type ActionContext,
|
|
10
|
+
type ActionResult,
|
|
11
|
+
type ConnectorDefinition,
|
|
12
|
+
ConnectorRuntime,
|
|
13
|
+
calculateEngagementScore,
|
|
14
|
+
type EventEnvelope,
|
|
15
|
+
type SyncContext,
|
|
16
|
+
type SyncResult,
|
|
17
|
+
} from '@lobu/connector-sdk';
|
|
18
|
+
|
|
19
|
+
// ── Google Play batchexecute API helpers ────────────────────────────
|
|
20
|
+
|
|
21
|
+
const BASE_URL = 'https://play.google.com';
|
|
22
|
+
|
|
23
|
+
const SORT = {
|
|
24
|
+
HELPFULNESS: 1,
|
|
25
|
+
NEWEST: 2,
|
|
26
|
+
RATING: 3,
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build the URL-encoded `f.req` form body for the batchexecute reviews RPC.
|
|
31
|
+
*
|
|
32
|
+
* The body encodes a nested JSON structure:
|
|
33
|
+
* [["UsvDTd","[null,null,[2,<sort>,[<num>,null,<token>],null,[]],[\"<appId>\",7]]",null,"generic"]]
|
|
34
|
+
*
|
|
35
|
+
* For the initial request the token slot is `null`; for paginated requests it
|
|
36
|
+
* is the string token returned by the previous response.
|
|
37
|
+
*/
|
|
38
|
+
function buildRequestBody(
|
|
39
|
+
appId: string,
|
|
40
|
+
sort: number,
|
|
41
|
+
numReviews: number,
|
|
42
|
+
token: string | null
|
|
43
|
+
): string {
|
|
44
|
+
const tokenPart =
|
|
45
|
+
token === null
|
|
46
|
+
? `%5B${numReviews}%2Cnull%2Cnull%5D`
|
|
47
|
+
: `%5B${numReviews}%2Cnull%2C%5C%22${token}%5C%22%5D`;
|
|
48
|
+
|
|
49
|
+
return `f.req=%5B%5B%5B%22UsvDTd%22%2C%22%5Bnull%2Cnull%2C%5B2%2C${sort}%2C${tokenPart}%2Cnull%2C%5B%5D%5D%2C%5B%5C%22${appId}%5C%22%2C7%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildBatchUrl(lang: string, country: string): string {
|
|
53
|
+
return `${BASE_URL}/_/PlayStoreUi/data/batchexecute?rpcids=qnKhOb&bl=boq_playuiserver_20190903.08_p0&hl=${lang}&gl=${country}&authuser&soc-app=121&soc-platform=1&soc-device=1&_reqid=1065213`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface RawReview {
|
|
57
|
+
id: string;
|
|
58
|
+
userName: string;
|
|
59
|
+
userImage: string | null;
|
|
60
|
+
date: string | null;
|
|
61
|
+
score: number;
|
|
62
|
+
text: string;
|
|
63
|
+
replyDate: string | null;
|
|
64
|
+
replyText: string | null;
|
|
65
|
+
version: string | null;
|
|
66
|
+
thumbsUp: number;
|
|
67
|
+
url: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert Google's epoch-seconds + partial-millis array into an ISO date string.
|
|
72
|
+
* The date field comes as `[seconds, partialMillis]`.
|
|
73
|
+
*/
|
|
74
|
+
function parseDate(dateArray: unknown): string | null {
|
|
75
|
+
if (!Array.isArray(dateArray)) return null;
|
|
76
|
+
const milliStr = String(dateArray[1] ?? '000');
|
|
77
|
+
const totalMs = `${dateArray[0]}${milliStr.substring(0, 3)}`;
|
|
78
|
+
return new Date(Number(totalMs)).toJSON();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extract structured review objects from the raw nested-array response data.
|
|
83
|
+
*/
|
|
84
|
+
function extractReviews(data: any[], appId: string): RawReview[] {
|
|
85
|
+
const reviewsList: any[] | undefined = data?.[0];
|
|
86
|
+
if (!Array.isArray(reviewsList)) return [];
|
|
87
|
+
|
|
88
|
+
return reviewsList.map((r: any) => ({
|
|
89
|
+
id: r[0] ?? '',
|
|
90
|
+
userName: r[1]?.[0] ?? '',
|
|
91
|
+
userImage: r[1]?.[1]?.[3]?.[2] ?? null,
|
|
92
|
+
date: parseDate(r[5]),
|
|
93
|
+
score: r[2] ?? 0,
|
|
94
|
+
text: r[4] ?? '',
|
|
95
|
+
replyDate: parseDate(r[7]?.[2]),
|
|
96
|
+
replyText: r[7]?.[1] ?? null,
|
|
97
|
+
version: r[10] ?? null,
|
|
98
|
+
thumbsUp: r[6] ?? 0,
|
|
99
|
+
url: `${BASE_URL}/store/apps/details?id=${appId}&reviewId=${r[0] ?? ''}`,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function extractPaginationToken(data: any[]): string | null {
|
|
104
|
+
return data?.[1]?.[1] ?? null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fetch a single page of reviews from the Play Store batchexecute endpoint.
|
|
109
|
+
* Returns the parsed review objects and the next pagination token (if any).
|
|
110
|
+
*/
|
|
111
|
+
async function fetchReviewsPage(
|
|
112
|
+
appId: string,
|
|
113
|
+
sort: number,
|
|
114
|
+
lang: string,
|
|
115
|
+
country: string,
|
|
116
|
+
token: string | null,
|
|
117
|
+
numPerPage = 150
|
|
118
|
+
): Promise<{ reviews: RawReview[]; nextToken: string | null }> {
|
|
119
|
+
const url = buildBatchUrl(lang, country);
|
|
120
|
+
const body = buildRequestBody(appId, sort, numPerPage, token);
|
|
121
|
+
|
|
122
|
+
const res = await fetch(url, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: {
|
|
125
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
126
|
+
},
|
|
127
|
+
body,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!res.ok) {
|
|
131
|
+
if (res.status === 404) throw new Error('App not found (404)');
|
|
132
|
+
throw new Error(`Google Play request failed: ${res.status} ${res.statusText}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const text = await res.text();
|
|
136
|
+
|
|
137
|
+
// Response starts with ")]}'" (security prefix), then a newline, then JSON.
|
|
138
|
+
// The library skips the first 5 characters.
|
|
139
|
+
const outer = JSON.parse(text.substring(5));
|
|
140
|
+
const innerJson: string | null = outer?.[0]?.[2];
|
|
141
|
+
|
|
142
|
+
if (innerJson === null || innerJson === undefined) {
|
|
143
|
+
return { reviews: [], nextToken: null };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const data = JSON.parse(innerJson);
|
|
147
|
+
return {
|
|
148
|
+
reviews: extractReviews(data, appId),
|
|
149
|
+
nextToken: extractPaginationToken(data),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Connector implementation ────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
interface GooglePlayCheckpoint {
|
|
156
|
+
last_timestamp?: string;
|
|
157
|
+
pagination_token?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export default class GooglePlayConnector extends ConnectorRuntime {
|
|
161
|
+
readonly definition: ConnectorDefinition = {
|
|
162
|
+
key: 'google_play',
|
|
163
|
+
name: 'Google Play Store',
|
|
164
|
+
description: 'Fetches app reviews from the Google Play Store.',
|
|
165
|
+
version: '1.0.0',
|
|
166
|
+
faviconDomain: 'play.google.com',
|
|
167
|
+
authSchema: {
|
|
168
|
+
methods: [{ type: 'none' }],
|
|
169
|
+
},
|
|
170
|
+
feeds: {
|
|
171
|
+
reviews: {
|
|
172
|
+
key: 'reviews',
|
|
173
|
+
name: 'App Reviews',
|
|
174
|
+
description: 'Fetch reviews for an Android app.',
|
|
175
|
+
configSchema: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
required: ['app_id'],
|
|
178
|
+
properties: {
|
|
179
|
+
app_id: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
minLength: 1,
|
|
182
|
+
description: 'Google Play package name (e.g., "com.spotify.music")',
|
|
183
|
+
},
|
|
184
|
+
country: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
minLength: 2,
|
|
187
|
+
maxLength: 2,
|
|
188
|
+
default: 'us',
|
|
189
|
+
description: 'ISO country code',
|
|
190
|
+
},
|
|
191
|
+
lang: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
minLength: 2,
|
|
194
|
+
maxLength: 5,
|
|
195
|
+
default: 'en',
|
|
196
|
+
description: 'Language code',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
eventKinds: {
|
|
201
|
+
review: {
|
|
202
|
+
description: 'A Google Play Store app review',
|
|
203
|
+
metadataSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
rating: { type: 'number', description: 'Star rating (1-5)' },
|
|
207
|
+
thumbs_up: { type: 'number', description: 'Thumbs up count' },
|
|
208
|
+
version: { type: 'string', description: 'App version reviewed' },
|
|
209
|
+
reply: { type: 'string', description: 'Developer reply text' },
|
|
210
|
+
reply_date: { type: 'string', description: 'Developer reply date' },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
optionsSchema: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
required: ['app_id'],
|
|
220
|
+
properties: {
|
|
221
|
+
app_id: {
|
|
222
|
+
type: 'string',
|
|
223
|
+
minLength: 1,
|
|
224
|
+
description: 'Google Play package name (e.g., "com.spotify.music")',
|
|
225
|
+
},
|
|
226
|
+
country: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
minLength: 2,
|
|
229
|
+
maxLength: 2,
|
|
230
|
+
default: 'us',
|
|
231
|
+
description: 'ISO country code',
|
|
232
|
+
},
|
|
233
|
+
lang: {
|
|
234
|
+
type: 'string',
|
|
235
|
+
minLength: 2,
|
|
236
|
+
maxLength: 5,
|
|
237
|
+
default: 'en',
|
|
238
|
+
description: 'Language code',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
async sync(ctx: SyncContext): Promise<SyncResult> {
|
|
245
|
+
const app_id = ctx.config.app_id as string;
|
|
246
|
+
const country = (ctx.config.country as string) || 'us';
|
|
247
|
+
const lang = (ctx.config.lang as string) || 'en';
|
|
248
|
+
const checkpoint = (ctx.checkpoint ?? {}) as GooglePlayCheckpoint;
|
|
249
|
+
|
|
250
|
+
const MAX_REVIEWS = 500;
|
|
251
|
+
const allReviews: RawReview[] = [];
|
|
252
|
+
let nextToken: string | null = checkpoint.pagination_token ?? null;
|
|
253
|
+
const lastTimestamp = checkpoint.last_timestamp
|
|
254
|
+
? new Date(checkpoint.last_timestamp).getTime()
|
|
255
|
+
: null;
|
|
256
|
+
let hitCheckpoint = false;
|
|
257
|
+
|
|
258
|
+
while (allReviews.length < MAX_REVIEWS) {
|
|
259
|
+
const page = await fetchReviewsPage(app_id, SORT.HELPFULNESS, lang, country, nextToken);
|
|
260
|
+
|
|
261
|
+
if (page.reviews.length === 0) break;
|
|
262
|
+
|
|
263
|
+
if (lastTimestamp) {
|
|
264
|
+
for (const review of page.reviews) {
|
|
265
|
+
const reviewTime = review.date ? new Date(review.date).getTime() : 0;
|
|
266
|
+
if (reviewTime > lastTimestamp) {
|
|
267
|
+
allReviews.push(review);
|
|
268
|
+
} else {
|
|
269
|
+
hitCheckpoint = true;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (hitCheckpoint) break;
|
|
274
|
+
} else {
|
|
275
|
+
allReviews.push(...page.reviews);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
nextToken = page.nextToken;
|
|
279
|
+
if (!nextToken) break;
|
|
280
|
+
|
|
281
|
+
// Rate-limit delay between pagination requests
|
|
282
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Transform to EventEnvelope format — skip reviews without text
|
|
286
|
+
const events: EventEnvelope[] = allReviews
|
|
287
|
+
.filter((review) => review.text)
|
|
288
|
+
.map((review) => {
|
|
289
|
+
const rating = review.score || 0;
|
|
290
|
+
const thumbsUp = review.thumbsUp || 0;
|
|
291
|
+
const replyCount = review.replyDate ? 1 : 0;
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
origin_id: review.id,
|
|
295
|
+
payload_text: review.text,
|
|
296
|
+
author_name: review.userName || undefined,
|
|
297
|
+
occurred_at: review.date ? new Date(review.date) : new Date(),
|
|
298
|
+
origin_type: 'review',
|
|
299
|
+
score: calculateEngagementScore('google_play', {
|
|
300
|
+
rating,
|
|
301
|
+
helpful_count: thumbsUp,
|
|
302
|
+
reply_count: replyCount,
|
|
303
|
+
}),
|
|
304
|
+
source_url: review.url,
|
|
305
|
+
metadata: {
|
|
306
|
+
rating,
|
|
307
|
+
thumbs_up: thumbsUp,
|
|
308
|
+
version: review.version,
|
|
309
|
+
reply: review.replyText,
|
|
310
|
+
reply_date: review.replyDate,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Sort by published date descending
|
|
316
|
+
events.sort((a, b) => b.occurred_at.getTime() - a.occurred_at.getTime());
|
|
317
|
+
|
|
318
|
+
const newCheckpoint: GooglePlayCheckpoint =
|
|
319
|
+
events.length > 0
|
|
320
|
+
? {
|
|
321
|
+
last_timestamp: events[0].occurred_at.toISOString(),
|
|
322
|
+
pagination_token: nextToken ?? undefined,
|
|
323
|
+
}
|
|
324
|
+
: {
|
|
325
|
+
last_timestamp: checkpoint.last_timestamp,
|
|
326
|
+
pagination_token: checkpoint.pagination_token,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
events,
|
|
331
|
+
checkpoint: newCheckpoint as Record<string, unknown>,
|
|
332
|
+
metadata: {
|
|
333
|
+
items_found: allReviews.length,
|
|
334
|
+
items_skipped: 0,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async execute(_ctx: ActionContext): Promise<ActionResult> {
|
|
340
|
+
return { success: false, error: 'Actions not supported' };
|
|
341
|
+
}
|
|
342
|
+
}
|