@lobu/cli 6.0.0 → 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 +12 -12
- 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 +6 -6
- 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 +4 -4
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/install-targets.d.ts.map +1 -1
- package/dist/commands/memory/_lib/install-targets.js +1 -5
- package/dist/commands/memory/_lib/install-targets.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 +2 -2
- package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
- package/dist/commands/memory/_lib/schema.js +3 -3
- package/dist/commands/memory/_lib/schema.js.map +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 +7 -6
- package/dist/internal/oauth.d.ts.map +1 -1
- package/dist/internal/oauth.js +3 -3
- 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 +3173 -4404
- 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,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G2 Connector (V1 runtime)
|
|
3
|
+
*
|
|
4
|
+
* Scrapes B2B software reviews from G2.com using browser rendering with stealth mode.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
type ActionContext,
|
|
9
|
+
type ActionResult,
|
|
10
|
+
type ConnectorDefinition,
|
|
11
|
+
ConnectorRuntime,
|
|
12
|
+
calculateEngagementScore,
|
|
13
|
+
type EventEnvelope,
|
|
14
|
+
type SyncContext,
|
|
15
|
+
type SyncResult,
|
|
16
|
+
} from '@lobu/connector-sdk';
|
|
17
|
+
import {
|
|
18
|
+
handleCookieConsent,
|
|
19
|
+
openStealthBrowser,
|
|
20
|
+
validateUrlDomain,
|
|
21
|
+
withBrowserErrorCapture,
|
|
22
|
+
} from './browser-scraper-utils.ts';
|
|
23
|
+
|
|
24
|
+
interface G2Review {
|
|
25
|
+
rating: number;
|
|
26
|
+
title: string;
|
|
27
|
+
text: string;
|
|
28
|
+
author: string;
|
|
29
|
+
jobTitle: string;
|
|
30
|
+
industry: string;
|
|
31
|
+
companySize: string;
|
|
32
|
+
date: string;
|
|
33
|
+
badges: string[];
|
|
34
|
+
reviewUrl: string;
|
|
35
|
+
helpfulCount: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface G2Checkpoint {
|
|
39
|
+
last_sync_at?: string;
|
|
40
|
+
pages_crawled?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const configSchema = {
|
|
44
|
+
type: 'object',
|
|
45
|
+
required: ['product_url'],
|
|
46
|
+
properties: {
|
|
47
|
+
product_url: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Full G2 product review URL e.g. https://www.g2.com/products/confluence/reviews',
|
|
50
|
+
},
|
|
51
|
+
lookback_days: {
|
|
52
|
+
type: 'integer',
|
|
53
|
+
minimum: 1,
|
|
54
|
+
maximum: 730,
|
|
55
|
+
default: 365,
|
|
56
|
+
description: 'Number of days to look back for reviews (default 365)',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default class G2Connector extends ConnectorRuntime {
|
|
62
|
+
readonly definition: ConnectorDefinition = {
|
|
63
|
+
key: 'g2',
|
|
64
|
+
name: 'G2',
|
|
65
|
+
description: 'Scrapes B2B software reviews from G2.com.',
|
|
66
|
+
version: '1.0.0',
|
|
67
|
+
faviconDomain: 'g2.com',
|
|
68
|
+
authSchema: {
|
|
69
|
+
methods: [{ type: 'none' }],
|
|
70
|
+
},
|
|
71
|
+
feeds: {
|
|
72
|
+
reviews: {
|
|
73
|
+
key: 'reviews',
|
|
74
|
+
name: 'Product Reviews',
|
|
75
|
+
description: 'Scrape reviews for a G2 product listing.',
|
|
76
|
+
configSchema,
|
|
77
|
+
eventKinds: {
|
|
78
|
+
review: {
|
|
79
|
+
description: 'A G2 B2B software review',
|
|
80
|
+
metadataSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
rating: { type: 'number', description: 'Star rating (0-5)' },
|
|
84
|
+
helpful_count: { type: 'number' },
|
|
85
|
+
job_title: { type: 'string', description: 'Reviewer job title' },
|
|
86
|
+
industry: { type: 'string', description: 'Reviewer industry' },
|
|
87
|
+
company_size: { type: 'string', description: 'Reviewer company size' },
|
|
88
|
+
badges: { type: 'array', items: { type: 'string' }, description: 'Review badges' },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
optionsSchema: configSchema,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
async sync(ctx: SyncContext): Promise<SyncResult> {
|
|
99
|
+
const productUrl = ctx.config.product_url as string;
|
|
100
|
+
|
|
101
|
+
if (!productUrl?.match(/^https:\/\/www\.g2\.com\/products\/[^/]+\/reviews/)) {
|
|
102
|
+
return {
|
|
103
|
+
events: [],
|
|
104
|
+
checkpoint: ctx.checkpoint,
|
|
105
|
+
metadata: { items_found: 0, error: 'Invalid product_url' },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
validateUrlDomain(productUrl, 'g2.com');
|
|
109
|
+
|
|
110
|
+
// Extract product key from URL for origin_id generation
|
|
111
|
+
const productMatch = productUrl.match(/\/products\/([^/]+)/);
|
|
112
|
+
const productKey = productMatch ? productMatch[1] : 'unknown';
|
|
113
|
+
|
|
114
|
+
const baseUrl = productUrl;
|
|
115
|
+
const allEvents: EventEnvelope[] = [];
|
|
116
|
+
|
|
117
|
+
const session = await openStealthBrowser({ cdpUrl: 'auto' });
|
|
118
|
+
|
|
119
|
+
return withBrowserErrorCapture(session, 'g2-sync', async (page) => {
|
|
120
|
+
const maxPages = 5;
|
|
121
|
+
|
|
122
|
+
for (let pageNum = 1; pageNum <= maxPages; pageNum++) {
|
|
123
|
+
const pageUrl = pageNum === 1 ? baseUrl : `${baseUrl}?page=${pageNum}`;
|
|
124
|
+
|
|
125
|
+
await page.goto(pageUrl, { waitUntil: 'domcontentloaded' });
|
|
126
|
+
|
|
127
|
+
if (pageNum === 1) {
|
|
128
|
+
await handleCookieConsent(page, '#onetrust-accept-btn-handler');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Wait for review cards to load
|
|
132
|
+
try {
|
|
133
|
+
await page.waitForSelector('[itemprop="review"]', { timeout: 10000 });
|
|
134
|
+
} catch {
|
|
135
|
+
// No reviews found on this page — stop paginating
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Extract reviews from the page
|
|
140
|
+
const reviews: G2Review[] = await page.evaluate(() => {
|
|
141
|
+
const results: G2Review[] = [];
|
|
142
|
+
const reviewCards = document.querySelectorAll('[itemprop="review"]');
|
|
143
|
+
|
|
144
|
+
reviewCards.forEach((card) => {
|
|
145
|
+
try {
|
|
146
|
+
// Extract author name from meta tag
|
|
147
|
+
const authorMeta = card.querySelector('[itemprop="author"] meta[itemprop="name"]');
|
|
148
|
+
const author = authorMeta?.getAttribute('content') || 'Anonymous';
|
|
149
|
+
|
|
150
|
+
// Extract author details from sibling divs with elv-text-subtle class
|
|
151
|
+
const authorContainer = card.querySelector('[itemprop="author"]');
|
|
152
|
+
const parentDiv = authorContainer?.closest('.elv-gap-2')?.parentElement;
|
|
153
|
+
const detailDivs = parentDiv
|
|
154
|
+
? Array.from(parentDiv.querySelectorAll('.elv-text-subtle'))
|
|
155
|
+
: [];
|
|
156
|
+
|
|
157
|
+
// Parse author details (job title, industry, company size)
|
|
158
|
+
let jobTitle = '';
|
|
159
|
+
let industry = '';
|
|
160
|
+
let companySize = '';
|
|
161
|
+
|
|
162
|
+
if (detailDivs.length >= 3) {
|
|
163
|
+
jobTitle = detailDivs[0]?.textContent?.trim() || '';
|
|
164
|
+
industry = detailDivs[1]?.textContent?.trim() || '';
|
|
165
|
+
companySize = detailDivs[2]?.textContent?.trim() || '';
|
|
166
|
+
} else if (detailDivs.length === 2) {
|
|
167
|
+
jobTitle = detailDivs[0]?.textContent?.trim() || '';
|
|
168
|
+
companySize = detailDivs[1]?.textContent?.trim() || '';
|
|
169
|
+
} else if (detailDivs.length === 1) {
|
|
170
|
+
companySize = detailDivs[0]?.textContent?.trim() || '';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Extract date from meta tag
|
|
174
|
+
const dateMeta = card.querySelector('meta[itemprop="datePublished"]');
|
|
175
|
+
const dateStr = dateMeta?.getAttribute('content') || '';
|
|
176
|
+
|
|
177
|
+
// Extract rating
|
|
178
|
+
const ratingMeta = card.querySelector('[itemprop="ratingValue"]');
|
|
179
|
+
const rating = ratingMeta ? parseFloat(ratingMeta.getAttribute('content') || '0') : 0;
|
|
180
|
+
|
|
181
|
+
// Extract review title
|
|
182
|
+
const titleDiv = card.querySelector('[itemprop="name"] .elv-font-bold');
|
|
183
|
+
const title = titleDiv?.textContent?.trim().replace(/^"|"$/g, '') || '';
|
|
184
|
+
|
|
185
|
+
// Extract review body - use innerText to preserve visual spacing/newlines
|
|
186
|
+
const reviewBodyEl = card.querySelector('[itemprop="reviewBody"]');
|
|
187
|
+
const reviewBody = (reviewBodyEl as HTMLElement)?.innerText?.trim() || '';
|
|
188
|
+
|
|
189
|
+
// Extract badges
|
|
190
|
+
const badgeEls = card.querySelectorAll(
|
|
191
|
+
'[class*="badge"], [class*="tag"], .elv-rounded-sm.elv-border'
|
|
192
|
+
);
|
|
193
|
+
const badges = Array.from(badgeEls)
|
|
194
|
+
.map((el) => el.textContent?.trim())
|
|
195
|
+
.filter((text): text is string => !!text && text.length < 50 && text.length > 3);
|
|
196
|
+
|
|
197
|
+
// Extract review URL
|
|
198
|
+
const linkEl = card.querySelector('a[href*="survey_responses"]');
|
|
199
|
+
const href = linkEl?.getAttribute('href') || '';
|
|
200
|
+
const reviewUrl = href
|
|
201
|
+
? href.startsWith('http')
|
|
202
|
+
? href
|
|
203
|
+
: `https://www.g2.com${href}`
|
|
204
|
+
: '';
|
|
205
|
+
|
|
206
|
+
// Skip reviews with minimal content
|
|
207
|
+
if ((reviewBody || '').length < 50) return;
|
|
208
|
+
|
|
209
|
+
results.push({
|
|
210
|
+
rating,
|
|
211
|
+
title,
|
|
212
|
+
text: reviewBody,
|
|
213
|
+
author,
|
|
214
|
+
jobTitle,
|
|
215
|
+
industry,
|
|
216
|
+
companySize,
|
|
217
|
+
date: dateStr,
|
|
218
|
+
badges: badges.slice(0, 10),
|
|
219
|
+
reviewUrl,
|
|
220
|
+
helpfulCount: 0,
|
|
221
|
+
});
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.error('[G2Connector] Error parsing review card:', e);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return results;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Transform reviews to EventEnvelope format
|
|
231
|
+
for (const review of reviews) {
|
|
232
|
+
const event: EventEnvelope = {
|
|
233
|
+
origin_id: `g2-${productKey}-${review.date || 'nodate'}-${review.author.replace(/\s+/g, '-')}`,
|
|
234
|
+
title: review.title,
|
|
235
|
+
payload_text: review.text,
|
|
236
|
+
author_name: review.author,
|
|
237
|
+
occurred_at: review.date ? new Date(review.date) : new Date(),
|
|
238
|
+
origin_type: 'review',
|
|
239
|
+
score: calculateEngagementScore('g2', {
|
|
240
|
+
rating: review.rating,
|
|
241
|
+
helpful_count: 0,
|
|
242
|
+
}),
|
|
243
|
+
source_url: review.reviewUrl || baseUrl,
|
|
244
|
+
metadata: {
|
|
245
|
+
rating: review.rating,
|
|
246
|
+
helpful_count: review.helpfulCount,
|
|
247
|
+
job_title: review.jobTitle,
|
|
248
|
+
industry: review.industry,
|
|
249
|
+
company_size: review.companySize,
|
|
250
|
+
badges: review.badges,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
allEvents.push(event);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// If this page had no reviews, stop paginating
|
|
257
|
+
if (reviews.length === 0) break;
|
|
258
|
+
|
|
259
|
+
// Delay between pages
|
|
260
|
+
if (pageNum < maxPages) {
|
|
261
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Rate limit
|
|
265
|
+
await new Promise((resolve) => setTimeout(resolve, 6000));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const newCheckpoint: G2Checkpoint = {
|
|
269
|
+
last_sync_at: new Date().toISOString(),
|
|
270
|
+
pages_crawled: Math.min(5, allEvents.length > 0 ? Math.ceil(allEvents.length / 10) : 0),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
events: allEvents,
|
|
275
|
+
checkpoint: newCheckpoint as Record<string, unknown>,
|
|
276
|
+
metadata: {
|
|
277
|
+
items_found: allEvents.length,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async execute(_ctx: ActionContext): Promise<ActionResult> {
|
|
284
|
+
return { success: false, error: 'Actions not supported' };
|
|
285
|
+
}
|
|
286
|
+
}
|