@bubblelab/bubble-core 0.1.10 → 0.1.12
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/bubble-bundle.d.ts +803 -351
- package/dist/bubble-factory.d.ts.map +1 -1
- package/dist/bubble-factory.js +130 -35
- package/dist/bubble-factory.js.map +1 -1
- package/dist/bubble-flow/bubble-flow-class.d.ts +5 -0
- package/dist/bubble-flow/bubble-flow-class.d.ts.map +1 -1
- package/dist/bubble-flow/bubble-flow-class.js +20 -0
- package/dist/bubble-flow/bubble-flow-class.js.map +1 -1
- package/dist/bubble-flow/sample/simplified-data-analysis.flow.d.ts.map +1 -1
- package/dist/bubble-flow/sample/simplified-data-analysis.flow.js +6 -3
- package/dist/bubble-flow/sample/simplified-data-analysis.flow.js.map +1 -1
- package/dist/bubbles/service-bubble/agi-inc.d.ts +1121 -0
- package/dist/bubbles/service-bubble/agi-inc.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/agi-inc.js +730 -0
- package/dist/bubbles/service-bubble/agi-inc.js.map +1 -0
- package/dist/bubbles/service-bubble/ai-agent.d.ts +273 -61
- package/dist/bubbles/service-bubble/ai-agent.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/ai-agent.js +536 -405
- package/dist/bubbles/service-bubble/ai-agent.js.map +1 -1
- package/dist/bubbles/service-bubble/airtable.d.ts +1753 -0
- package/dist/bubbles/service-bubble/airtable.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/airtable.js +1173 -0
- package/dist/bubbles/service-bubble/airtable.js.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/google-maps-scraper.d.ts +240 -0
- package/dist/bubbles/service-bubble/apify/actors/google-maps-scraper.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/google-maps-scraper.js +119 -0
- package/dist/bubbles/service-bubble/apify/actors/google-maps-scraper.js.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/instagram-hashtag-scraper.d.ts +4 -4
- package/dist/bubbles/service-bubble/apify/actors/instagram-scraper.d.ts +14 -14
- package/dist/bubbles/service-bubble/apify/actors/linkedin-jobs-scraper.d.ts +137 -0
- package/dist/bubbles/service-bubble/apify/actors/linkedin-jobs-scraper.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/linkedin-jobs-scraper.js +81 -0
- package/dist/bubbles/service-bubble/apify/actors/linkedin-jobs-scraper.js.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/linkedin-posts-search.d.ts +6 -6
- package/dist/bubbles/service-bubble/apify/actors/linkedin-profile-posts.d.ts +32 -32
- package/dist/bubbles/service-bubble/apify/actors/tiktok-scraper.d.ts +488 -0
- package/dist/bubbles/service-bubble/apify/actors/tiktok-scraper.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/tiktok-scraper.js +463 -0
- package/dist/bubbles/service-bubble/apify/actors/tiktok-scraper.js.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/twitter-scraper.d.ts +262 -0
- package/dist/bubbles/service-bubble/apify/actors/twitter-scraper.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/twitter-scraper.js +291 -0
- package/dist/bubbles/service-bubble/apify/actors/twitter-scraper.js.map +1 -0
- package/dist/bubbles/service-bubble/apify/actors/youtube-scraper.d.ts +12 -12
- package/dist/bubbles/service-bubble/apify/apify-scraper.schema.d.ts +1301 -170
- package/dist/bubbles/service-bubble/apify/apify-scraper.schema.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/apify/apify-scraper.schema.js +32 -0
- package/dist/bubbles/service-bubble/apify/apify-scraper.schema.js.map +1 -1
- package/dist/bubbles/service-bubble/apify/apify.d.ts +158 -13
- package/dist/bubbles/service-bubble/apify/apify.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/apify/apify.js +222 -37
- package/dist/bubbles/service-bubble/apify/apify.js.map +1 -1
- package/dist/bubbles/service-bubble/browserbase/browserbase.d.ts +542 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.d.ts +37 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.js +203 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.integration.flow.js.map +1 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.js +593 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.js.map +1 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.schema.d.ts +518 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.schema.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.schema.js +311 -0
- package/dist/bubbles/service-bubble/browserbase/browserbase.schema.js.map +1 -0
- package/dist/bubbles/service-bubble/browserbase/index.d.ts +3 -0
- package/dist/bubbles/service-bubble/browserbase/index.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/browserbase/index.js +3 -0
- package/dist/bubbles/service-bubble/browserbase/index.js.map +1 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.d.ts +1358 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.js +219 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.js.map +1 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.schema.d.ts +1604 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.schema.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.schema.js +194 -0
- package/dist/bubbles/service-bubble/crustdata/crustdata.schema.js.map +1 -0
- package/dist/bubbles/service-bubble/crustdata/index.d.ts +3 -0
- package/dist/bubbles/service-bubble/crustdata/index.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/crustdata/index.js +3 -0
- package/dist/bubbles/service-bubble/crustdata/index.js.map +1 -0
- package/dist/bubbles/service-bubble/eleven-labs.d.ts +421 -0
- package/dist/bubbles/service-bubble/eleven-labs.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/eleven-labs.js +479 -0
- package/dist/bubbles/service-bubble/eleven-labs.js.map +1 -0
- package/dist/bubbles/service-bubble/firecrawl.d.ts +37748 -0
- package/dist/bubbles/service-bubble/firecrawl.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/firecrawl.js +1489 -0
- package/dist/bubbles/service-bubble/firecrawl.js.map +1 -0
- package/dist/bubbles/service-bubble/followupboss.d.ts +6822 -0
- package/dist/bubbles/service-bubble/followupboss.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/followupboss.js +1394 -0
- package/dist/bubbles/service-bubble/followupboss.js.map +1 -0
- package/dist/bubbles/service-bubble/github.d.ts +2393 -0
- package/dist/bubbles/service-bubble/github.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/github.js +1046 -0
- package/dist/bubbles/service-bubble/github.js.map +1 -0
- package/dist/bubbles/service-bubble/gmail.d.ts +180 -180
- package/dist/bubbles/service-bubble/google-calendar.d.ts +60 -60
- package/dist/bubbles/service-bubble/google-drive.d.ts +69 -68
- package/dist/bubbles/service-bubble/google-drive.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/google-drive.js +35 -3
- package/dist/bubbles/service-bubble/google-drive.js.map +1 -1
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.d.ts +943 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.integration.flow.d.ts +31 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.integration.flow.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.integration.flow.js +184 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.integration.flow.js.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.js +401 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.js.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.schema.d.ts +1024 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.schema.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/{google-sheets.js → google-sheets/google-sheets.schema.js} +45 -409
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.schema.js.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.utils.d.ts +38 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.utils.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.utils.js +183 -0
- package/dist/bubbles/service-bubble/google-sheets/google-sheets.utils.js.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/index.d.ts +4 -0
- package/dist/bubbles/service-bubble/google-sheets/index.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/google-sheets/index.js +4 -0
- package/dist/bubbles/service-bubble/google-sheets/index.js.map +1 -0
- package/dist/bubbles/service-bubble/hello-world.d.ts +4 -4
- package/dist/bubbles/service-bubble/http.d.ts +17 -5
- package/dist/bubbles/service-bubble/http.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/http.integration.flow.d.ts +49 -0
- package/dist/bubbles/service-bubble/http.integration.flow.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/http.integration.flow.js +425 -0
- package/dist/bubbles/service-bubble/http.integration.flow.js.map +1 -0
- package/dist/bubbles/service-bubble/http.js +47 -5
- package/dist/bubbles/service-bubble/http.js.map +1 -1
- package/dist/bubbles/service-bubble/insforge-db.d.ts +140 -0
- package/dist/bubbles/service-bubble/insforge-db.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/insforge-db.js +260 -0
- package/dist/bubbles/service-bubble/insforge-db.js.map +1 -0
- package/dist/bubbles/service-bubble/notion/index.d.ts +3 -0
- package/dist/bubbles/service-bubble/notion/index.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/notion/index.js +3 -0
- package/dist/bubbles/service-bubble/notion/index.js.map +1 -0
- package/dist/bubbles/service-bubble/notion/notion.d.ts +35405 -0
- package/dist/bubbles/service-bubble/notion/notion.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/notion/notion.js +1492 -0
- package/dist/bubbles/service-bubble/notion/notion.js.map +1 -0
- package/dist/bubbles/service-bubble/notion/property-schemas.d.ts +1148 -0
- package/dist/bubbles/service-bubble/notion/property-schemas.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/notion/property-schemas.js +341 -0
- package/dist/bubbles/service-bubble/notion/property-schemas.js.map +1 -0
- package/dist/bubbles/service-bubble/postgresql.d.ts +12 -12
- package/dist/bubbles/service-bubble/resend.d.ts +30 -9
- package/dist/bubbles/service-bubble/resend.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/resend.js +133 -2
- package/dist/bubbles/service-bubble/resend.js.map +1 -1
- package/dist/bubbles/service-bubble/slack.d.ts +1741 -798
- package/dist/bubbles/service-bubble/slack.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/slack.js +322 -28
- package/dist/bubbles/service-bubble/slack.js.map +1 -1
- package/dist/bubbles/service-bubble/storage.d.ts +26 -22
- package/dist/bubbles/service-bubble/storage.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/storage.js +45 -4
- package/dist/bubbles/service-bubble/storage.js.map +1 -1
- package/dist/bubbles/service-bubble/telegram.d.ts +7742 -0
- package/dist/bubbles/service-bubble/telegram.d.ts.map +1 -0
- package/dist/bubbles/service-bubble/telegram.js +1132 -0
- package/dist/bubbles/service-bubble/telegram.js.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.d.ts +494 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.d.ts +31 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.js +100 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.integration.flow.js.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.js +1301 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.js.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.d.ts +473 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.js +230 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/amazon-shopping-tool.schema.js.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.d.ts +3 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.js +3 -0
- package/dist/bubbles/tool-bubble/amazon-shopping-tool/index.js.map +1 -0
- package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.d.ts +76 -20
- package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.js +12 -0
- package/dist/bubbles/tool-bubble/bubbleflow-validation-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/chart-js-tool.d.ts +14 -14
- package/dist/bubbles/tool-bubble/code-edit-tool.d.ts +188 -0
- package/dist/bubbles/tool-bubble/code-edit-tool.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/code-edit-tool.js +321 -0
- package/dist/bubbles/tool-bubble/code-edit-tool.js.map +1 -0
- package/dist/bubbles/tool-bubble/company-enrichment-tool.d.ts +740 -0
- package/dist/bubbles/tool-bubble/company-enrichment-tool.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/company-enrichment-tool.js +350 -0
- package/dist/bubbles/tool-bubble/company-enrichment-tool.js.map +1 -0
- package/dist/bubbles/tool-bubble/get-bubble-details-tool.d.ts +8 -4
- package/dist/bubbles/tool-bubble/get-bubble-details-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/get-bubble-details-tool.js +115 -10
- package/dist/bubbles/tool-bubble/get-bubble-details-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/google-maps-tool.d.ts +455 -0
- package/dist/bubbles/tool-bubble/google-maps-tool.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/google-maps-tool.js +206 -0
- package/dist/bubbles/tool-bubble/google-maps-tool.js.map +1 -0
- package/dist/bubbles/tool-bubble/instagram-tool.d.ts +36 -36
- package/dist/bubbles/tool-bubble/instagram-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/instagram-tool.js +4 -2
- package/dist/bubbles/tool-bubble/instagram-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/linkedin-tool.d.ts +830 -453
- package/dist/bubbles/tool-bubble/linkedin-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/linkedin-tool.js +236 -14
- package/dist/bubbles/tool-bubble/linkedin-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/list-bubbles-tool.d.ts +4 -4
- package/dist/bubbles/tool-bubble/reddit-scrape-tool.d.ts +66 -66
- package/dist/bubbles/tool-bubble/research-agent-tool.d.ts +17 -16
- package/dist/bubbles/tool-bubble/research-agent-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/research-agent-tool.js +32 -17
- package/dist/bubbles/tool-bubble/research-agent-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/sql-query-tool.d.ts +8 -8
- package/dist/bubbles/tool-bubble/tiktok-tool.d.ts +485 -0
- package/dist/bubbles/tool-bubble/tiktok-tool.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/tiktok-tool.js +226 -0
- package/dist/bubbles/tool-bubble/tiktok-tool.js.map +1 -0
- package/dist/bubbles/tool-bubble/tool-template.d.ts +8 -8
- package/dist/bubbles/tool-bubble/twitter-tool.d.ts +947 -0
- package/dist/bubbles/tool-bubble/twitter-tool.d.ts.map +1 -0
- package/dist/bubbles/tool-bubble/twitter-tool.js +497 -0
- package/dist/bubbles/tool-bubble/twitter-tool.js.map +1 -0
- package/dist/bubbles/tool-bubble/web-crawl-tool.d.ts +22 -16
- package/dist/bubbles/tool-bubble/web-crawl-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/web-crawl-tool.js +58 -59
- package/dist/bubbles/tool-bubble/web-crawl-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/web-extract-tool.d.ts +8 -8
- package/dist/bubbles/tool-bubble/web-extract-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/web-extract-tool.js +17 -17
- package/dist/bubbles/tool-bubble/web-extract-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts +21 -113
- package/dist/bubbles/tool-bubble/web-scrape-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/web-scrape-tool.js +55 -73
- package/dist/bubbles/tool-bubble/web-scrape-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/web-search-tool.d.ts +20 -9
- package/dist/bubbles/tool-bubble/web-search-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/web-search-tool.js +45 -35
- package/dist/bubbles/tool-bubble/web-search-tool.js.map +1 -1
- package/dist/bubbles/tool-bubble/youtube-tool.d.ts +25 -25
- package/dist/bubbles/tool-bubble/youtube-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/youtube-tool.js +8 -5
- package/dist/bubbles/tool-bubble/youtube-tool.js.map +1 -1
- package/dist/bubbles/workflow-bubble/database-analyzer.workflow.d.ts +4 -4
- package/dist/bubbles/workflow-bubble/generate-document.workflow.d.ts +30 -30
- package/dist/bubbles/workflow-bubble/generate-document.workflow.js +1 -1
- package/dist/bubbles/workflow-bubble/parse-document.workflow.d.ts +22 -22
- package/dist/bubbles/workflow-bubble/parse-document.workflow.js +1 -1
- package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts +70 -70
- package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.d.ts.map +1 -1
- package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.js +4 -4
- package/dist/bubbles/workflow-bubble/pdf-form-operations.workflow.js.map +1 -1
- package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.d.ts +36 -36
- package/dist/bubbles/workflow-bubble/pdf-ocr.workflow.js +1 -1
- package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.d.ts +14 -14
- package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.d.ts.map +1 -1
- package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.js +6 -6
- package/dist/bubbles/workflow-bubble/slack-data-assistant.workflow.js.map +1 -1
- package/dist/bubbles/workflow-bubble/slack-formatter-agent.d.ts +18 -18
- package/dist/bubbles/workflow-bubble/slack-formatter-agent.d.ts.map +1 -1
- package/dist/bubbles/workflow-bubble/slack-formatter-agent.js +24 -4
- package/dist/bubbles/workflow-bubble/slack-formatter-agent.js.map +1 -1
- package/dist/bubbles/workflow-bubble/slack-notifier.workflow.d.ts +19 -19
- package/dist/bubbles/workflow-bubble/slack-notifier.workflow.d.ts.map +1 -1
- package/dist/bubbles/workflow-bubble/slack-notifier.workflow.js +8 -8
- package/dist/bubbles/workflow-bubble/slack-notifier.workflow.js.map +1 -1
- package/dist/bubbles.json +260 -77
- package/dist/index.d.ts +25 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/logging/BubbleLogger.d.ts +55 -16
- package/dist/logging/BubbleLogger.d.ts.map +1 -1
- package/dist/logging/BubbleLogger.js +192 -77
- package/dist/logging/BubbleLogger.js.map +1 -1
- package/dist/logging/StreamingBubbleLogger.d.ts +21 -1
- package/dist/logging/StreamingBubbleLogger.d.ts.map +1 -1
- package/dist/logging/StreamingBubbleLogger.js +80 -8
- package/dist/logging/StreamingBubbleLogger.js.map +1 -1
- package/dist/logging/WebhookStreamLogger.d.ts +66 -0
- package/dist/logging/WebhookStreamLogger.d.ts.map +1 -0
- package/dist/logging/WebhookStreamLogger.js +291 -0
- package/dist/logging/WebhookStreamLogger.js.map +1 -0
- package/dist/types/available-tools.d.ts +1 -1
- package/dist/types/available-tools.d.ts.map +1 -1
- package/dist/types/available-tools.js +7 -0
- package/dist/types/available-tools.js.map +1 -1
- package/dist/types/base-bubble-class.d.ts +6 -4
- package/dist/types/base-bubble-class.d.ts.map +1 -1
- package/dist/types/base-bubble-class.js +30 -23
- package/dist/types/base-bubble-class.js.map +1 -1
- package/dist/types/bubble.d.ts +2 -0
- package/dist/types/bubble.d.ts.map +1 -1
- package/dist/types/service-bubble-class.d.ts +1 -1
- package/dist/types/service-bubble-class.d.ts.map +1 -1
- package/dist/types/service-bubble-class.js +2 -2
- package/dist/types/service-bubble-class.js.map +1 -1
- package/dist/types/tool-bubble-class.d.ts +1 -1
- package/dist/types/tool-bubble-class.d.ts.map +1 -1
- package/dist/types/tool-bubble-class.js +60 -10
- package/dist/types/tool-bubble-class.js.map +1 -1
- package/dist/types/workflow-bubble-class.d.ts +1 -1
- package/dist/types/workflow-bubble-class.d.ts.map +1 -1
- package/dist/types/workflow-bubble-class.js +2 -2
- package/dist/types/workflow-bubble-class.js.map +1 -1
- package/dist/utils/agent-formatter.d.ts +14 -2
- package/dist/utils/agent-formatter.d.ts.map +1 -1
- package/dist/utils/agent-formatter.js +174 -26
- package/dist/utils/agent-formatter.js.map +1 -1
- package/dist/utils/bubbleflow-validation.d.ts +7 -0
- package/dist/utils/bubbleflow-validation.d.ts.map +1 -1
- package/dist/utils/bubbleflow-validation.js +171 -6
- package/dist/utils/bubbleflow-validation.js.map +1 -1
- package/dist/utils/json-parsing.d.ts.map +1 -1
- package/dist/utils/json-parsing.js +146 -0
- package/dist/utils/json-parsing.js.map +1 -1
- package/dist/utils/safe-gemini-chat.d.ts +31 -0
- package/dist/utils/safe-gemini-chat.d.ts.map +1 -0
- package/dist/utils/safe-gemini-chat.js +93 -0
- package/dist/utils/safe-gemini-chat.js.map +1 -0
- package/dist/utils/schema-comparison.d.ts +92 -0
- package/dist/utils/schema-comparison.d.ts.map +1 -0
- package/dist/utils/schema-comparison.js +716 -0
- package/dist/utils/schema-comparison.js.map +1 -0
- package/dist/utils/zod-schema.d.ts +24 -0
- package/dist/utils/zod-schema.d.ts.map +1 -0
- package/dist/utils/zod-schema.js +56 -0
- package/dist/utils/zod-schema.js.map +1 -0
- package/package.json +6 -4
- package/dist/bubbles/service-bubble/google-sheets.d.ts +0 -1811
- package/dist/bubbles/service-bubble/google-sheets.d.ts.map +0 -1
- package/dist/bubbles/service-bubble/google-sheets.js.map +0 -1
- package/dist/bubbles/service-bubble/x-twitter.d.ts +0 -814
- package/dist/bubbles/service-bubble/x-twitter.d.ts.map +0 -1
- package/dist/bubbles/service-bubble/x-twitter.js +0 -445
- package/dist/bubbles/service-bubble/x-twitter.js.map +0 -1
- package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts +0 -125
- package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.d.ts.map +0 -1
- package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js +0 -808
- package/dist/bubbles/workflow-bubble/bubbleflow-generator.workflow.js.map +0 -1
- package/dist/test-gm.d.ts +0 -10
- package/dist/test-gm.d.ts.map +0 -1
- package/dist/test-gm.js +0 -95
- package/dist/test-gm.js.map +0 -1
- package/dist/utils/param-helper.d.ts +0 -2
- package/dist/utils/param-helper.d.ts.map +0 -1
- package/dist/utils/param-helper.js +0 -5
- package/dist/utils/param-helper.js.map +0 -1
|
@@ -0,0 +1,1301 @@
|
|
|
1
|
+
import { ToolBubble } from '../../../types/tool-bubble-class.js';
|
|
2
|
+
import { CredentialType } from '@bubblelab/shared-schemas';
|
|
3
|
+
import { BrowserBaseBubble, BrowserSessionDataSchema, } from '../../service-bubble/browserbase/index.js';
|
|
4
|
+
import { StorageBubble } from '../../service-bubble/storage.js';
|
|
5
|
+
import { AmazonShoppingToolParamsSchema, AmazonShoppingToolResultSchema, } from './amazon-shopping-tool.schema.js';
|
|
6
|
+
// Debug logging helper - only logs when ENABLE_DEBUG_LOGS env var is set
|
|
7
|
+
const DEBUG = process.env.ENABLE_DEBUG_LOGS;
|
|
8
|
+
function debugLog(...args) {
|
|
9
|
+
if (DEBUG) {
|
|
10
|
+
console.log(...args);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Amazon Shopping Tool
|
|
15
|
+
*
|
|
16
|
+
* A tool bubble for automating Amazon shopping operations including
|
|
17
|
+
* adding items to cart, viewing cart, and completing checkout.
|
|
18
|
+
*
|
|
19
|
+
* This tool uses the BrowserBase service bubble internally to
|
|
20
|
+
* manage browser sessions with authenticated Amazon credentials.
|
|
21
|
+
*
|
|
22
|
+
* Features:
|
|
23
|
+
* - Add products to cart by URL or ASIN
|
|
24
|
+
* - View current cart contents and totals
|
|
25
|
+
* - Complete checkout with saved payment methods
|
|
26
|
+
* - Search for products
|
|
27
|
+
* - Get detailed product information
|
|
28
|
+
*
|
|
29
|
+
* Required Credentials:
|
|
30
|
+
* - AMAZON_CRED: Browser session credential with Amazon cookies
|
|
31
|
+
*
|
|
32
|
+
* Security:
|
|
33
|
+
* - Uses BrowserBase cloud browsers (isolated)
|
|
34
|
+
* - Credentials are encrypted at rest
|
|
35
|
+
* - Session data is not persisted beyond operation
|
|
36
|
+
*/
|
|
37
|
+
export class AmazonShoppingTool extends ToolBubble {
|
|
38
|
+
static bubbleName = 'amazon-shopping-tool';
|
|
39
|
+
static schema = AmazonShoppingToolParamsSchema;
|
|
40
|
+
static resultSchema = AmazonShoppingToolResultSchema;
|
|
41
|
+
static shortDescription = 'Amazon shopping automation - add to cart, view cart, checkout, search products';
|
|
42
|
+
static longDescription = `
|
|
43
|
+
Amazon Shopping Tool for automating shopping operations.
|
|
44
|
+
|
|
45
|
+
Features:
|
|
46
|
+
- Add products to cart by URL or ASIN
|
|
47
|
+
- View current cart contents and totals
|
|
48
|
+
- Complete checkout with saved payment methods
|
|
49
|
+
- Search for products on Amazon
|
|
50
|
+
- Get detailed product information
|
|
51
|
+
|
|
52
|
+
Required Credentials:
|
|
53
|
+
- AMAZON_CRED: Browser session credential (authenticate via browser session)
|
|
54
|
+
|
|
55
|
+
Note: Checkout requires saved payment and shipping information in Amazon account.
|
|
56
|
+
The tool operates using authenticated browser sessions to ensure security.
|
|
57
|
+
`;
|
|
58
|
+
static alias = 'amazon';
|
|
59
|
+
static type = 'tool';
|
|
60
|
+
sessionId = null;
|
|
61
|
+
contextId = null;
|
|
62
|
+
cookies = null;
|
|
63
|
+
constructor(params = { operation: 'get_cart' }, context) {
|
|
64
|
+
super(params, context);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Choose the credential to use for Amazon operations
|
|
68
|
+
* Returns AMAZON_CRED which contains contextId and cookies
|
|
69
|
+
*/
|
|
70
|
+
chooseCredential() {
|
|
71
|
+
const { credentials } = this.params;
|
|
72
|
+
if (!credentials || typeof credentials !== 'object') {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return credentials[CredentialType.AMAZON_CRED];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse the AMAZON_CRED to extract contextId and cookies
|
|
79
|
+
* Credential is base64-encoded JSON to avoid escaping issues
|
|
80
|
+
*/
|
|
81
|
+
parseBrowserSessionData() {
|
|
82
|
+
const credential = this.chooseCredential();
|
|
83
|
+
if (!credential) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
// Credential is base64-encoded JSON
|
|
88
|
+
const jsonString = Buffer.from(credential, 'base64').toString('utf-8');
|
|
89
|
+
const parsed = JSON.parse(jsonString);
|
|
90
|
+
const validated = BrowserSessionDataSchema.safeParse(parsed);
|
|
91
|
+
if (validated.success) {
|
|
92
|
+
return validated.data;
|
|
93
|
+
}
|
|
94
|
+
console.error('[AmazonShoppingTool] Invalid credential format:', validated.error);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error('[AmazonShoppingTool] Failed to parse credential:', error);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Extract ASIN from Amazon URL or return as-is if already an ASIN
|
|
104
|
+
*/
|
|
105
|
+
extractAsin(productUrlOrAsin) {
|
|
106
|
+
// If it's already an ASIN (10 characters, alphanumeric)
|
|
107
|
+
if (/^[A-Z0-9]{10}$/i.test(productUrlOrAsin)) {
|
|
108
|
+
return productUrlOrAsin.toUpperCase();
|
|
109
|
+
}
|
|
110
|
+
// Try to extract ASIN from URL patterns
|
|
111
|
+
// Pattern 1: /dp/ASIN
|
|
112
|
+
const dpMatch = productUrlOrAsin.match(/\/dp\/([A-Z0-9]{10})/i);
|
|
113
|
+
if (dpMatch)
|
|
114
|
+
return dpMatch[1].toUpperCase();
|
|
115
|
+
// Pattern 2: /gp/product/ASIN
|
|
116
|
+
const gpMatch = productUrlOrAsin.match(/\/gp\/product\/([A-Z0-9]{10})/i);
|
|
117
|
+
if (gpMatch)
|
|
118
|
+
return gpMatch[1].toUpperCase();
|
|
119
|
+
// Pattern 3: ASIN in query string
|
|
120
|
+
const asinMatch = productUrlOrAsin.match(/[?&]ASIN=([A-Z0-9]{10})/i);
|
|
121
|
+
if (asinMatch)
|
|
122
|
+
return asinMatch[1].toUpperCase();
|
|
123
|
+
// If we can't extract, return the original (might fail later)
|
|
124
|
+
return productUrlOrAsin;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build Amazon product URL from ASIN
|
|
128
|
+
*/
|
|
129
|
+
buildProductUrl(asin) {
|
|
130
|
+
return `https://www.amazon.com/dp/${asin}`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Start a browser session using BrowserBase
|
|
134
|
+
* Extracts contextId and cookies from AMAZON_CRED and passes them explicitly
|
|
135
|
+
*/
|
|
136
|
+
async startBrowserSession() {
|
|
137
|
+
debugLog('[AmazonShoppingTool] Starting browser session');
|
|
138
|
+
debugLog('[AmazonShoppingTool] Session ID:', this.sessionId);
|
|
139
|
+
if (this.sessionId) {
|
|
140
|
+
return this.sessionId;
|
|
141
|
+
}
|
|
142
|
+
// Parse credential to get contextId and cookies
|
|
143
|
+
const sessionData = this.parseBrowserSessionData();
|
|
144
|
+
if (sessionData) {
|
|
145
|
+
this.contextId = sessionData.contextId;
|
|
146
|
+
this.cookies = sessionData.cookies;
|
|
147
|
+
debugLog(`[AmazonShoppingTool] Loaded session data: contextId=${this.contextId}, cookies=${this.cookies.length}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
debugLog('[AmazonShoppingTool] No AMAZON_CRED found, creating new context');
|
|
151
|
+
}
|
|
152
|
+
// Create BrowserBaseBubble with explicit context_id and cookies
|
|
153
|
+
const startsession_browserbase = new BrowserBaseBubble({
|
|
154
|
+
operation: 'start_session',
|
|
155
|
+
context_id: this.contextId || undefined,
|
|
156
|
+
cookies: this.cookies || undefined,
|
|
157
|
+
credentials: this.params.credentials,
|
|
158
|
+
}, this.context, 'startsession_browserbase');
|
|
159
|
+
const result = await startsession_browserbase.action();
|
|
160
|
+
if (!result.data.success || !result.data.session_id) {
|
|
161
|
+
throw new Error(result.data.error || 'Failed to start browser session');
|
|
162
|
+
}
|
|
163
|
+
this.sessionId = result.data.session_id;
|
|
164
|
+
// Store the contextId from the result in case a new one was created
|
|
165
|
+
if (result.data.context_id) {
|
|
166
|
+
this.contextId = result.data.context_id;
|
|
167
|
+
}
|
|
168
|
+
debugLog(`[AmazonShoppingTool] Browser session started: ${this.sessionId}, context: ${this.contextId}`);
|
|
169
|
+
// Emit browser session start event with Amazon Shopping Tool's variableId
|
|
170
|
+
// so the live session shows on this tool's step in the UI
|
|
171
|
+
if (this.context?.logger && result.data.debug_url) {
|
|
172
|
+
this.context.logger.logBrowserSessionStart(this.sessionId, result.data.debug_url, this.context.variableId);
|
|
173
|
+
}
|
|
174
|
+
return this.sessionId;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* End the browser session
|
|
178
|
+
*/
|
|
179
|
+
async endBrowserSession() {
|
|
180
|
+
if (!this.sessionId)
|
|
181
|
+
return;
|
|
182
|
+
const sessionIdToEnd = this.sessionId;
|
|
183
|
+
try {
|
|
184
|
+
const endsession_browserbase = new BrowserBaseBubble({
|
|
185
|
+
operation: 'end_session',
|
|
186
|
+
session_id: sessionIdToEnd,
|
|
187
|
+
}, this.context, 'endsession_browserbase');
|
|
188
|
+
await endsession_browserbase.action();
|
|
189
|
+
debugLog(`[AmazonShoppingTool] Browser session ended: ${sessionIdToEnd}`);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
console.error('[AmazonShoppingTool] Error ending session:', error);
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
// Emit browser session end event to stop showing live view in UI
|
|
196
|
+
if (this.context?.logger) {
|
|
197
|
+
this.context.logger.logBrowserSessionEnd(sessionIdToEnd, this.context.variableId);
|
|
198
|
+
}
|
|
199
|
+
this.sessionId = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Navigate to a URL
|
|
204
|
+
*/
|
|
205
|
+
async navigateTo(url) {
|
|
206
|
+
if (!this.sessionId) {
|
|
207
|
+
throw new Error('No active browser session');
|
|
208
|
+
}
|
|
209
|
+
const navigate_browserbase = new BrowserBaseBubble({
|
|
210
|
+
operation: 'navigate',
|
|
211
|
+
session_id: this.sessionId,
|
|
212
|
+
url,
|
|
213
|
+
wait_until: 'domcontentloaded',
|
|
214
|
+
timeout: 30000,
|
|
215
|
+
}, this.context, 'navigate_browserbase');
|
|
216
|
+
const result = await navigate_browserbase.action();
|
|
217
|
+
if (!result.data.success) {
|
|
218
|
+
throw new Error(result.data.error || 'Navigation failed');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Click an element
|
|
223
|
+
*/
|
|
224
|
+
async clickElement(selector, waitForNav = false) {
|
|
225
|
+
if (!this.sessionId) {
|
|
226
|
+
throw new Error('No active browser session');
|
|
227
|
+
}
|
|
228
|
+
const click_browserbase = new BrowserBaseBubble({
|
|
229
|
+
operation: 'click',
|
|
230
|
+
session_id: this.sessionId,
|
|
231
|
+
selector,
|
|
232
|
+
wait_for_navigation: waitForNav,
|
|
233
|
+
timeout: 5000,
|
|
234
|
+
}, this.context, 'click_browserbase');
|
|
235
|
+
const result = await click_browserbase.action();
|
|
236
|
+
return result.data.success;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Evaluate JavaScript in page
|
|
240
|
+
*/
|
|
241
|
+
async evaluate(script) {
|
|
242
|
+
if (!this.sessionId) {
|
|
243
|
+
throw new Error('No active browser session');
|
|
244
|
+
}
|
|
245
|
+
const evaluate_browserbase = new BrowserBaseBubble({
|
|
246
|
+
operation: 'evaluate',
|
|
247
|
+
session_id: this.sessionId,
|
|
248
|
+
script,
|
|
249
|
+
}, this.context, 'evaluate_browserbase');
|
|
250
|
+
const result = await evaluate_browserbase.action();
|
|
251
|
+
if (!result.data.success) {
|
|
252
|
+
throw new Error(result.data.error || 'Script evaluation failed');
|
|
253
|
+
}
|
|
254
|
+
return result.data.result;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Wait for selector
|
|
258
|
+
*/
|
|
259
|
+
async waitForSelector(selector, timeout = 5000) {
|
|
260
|
+
if (!this.sessionId) {
|
|
261
|
+
throw new Error('No active browser session');
|
|
262
|
+
}
|
|
263
|
+
const waitselector_browserbase = new BrowserBaseBubble({
|
|
264
|
+
operation: 'wait',
|
|
265
|
+
session_id: this.sessionId,
|
|
266
|
+
wait_type: 'selector',
|
|
267
|
+
selector,
|
|
268
|
+
timeout,
|
|
269
|
+
}, this.context, 'waitselector_browserbase');
|
|
270
|
+
const result = await waitselector_browserbase.action();
|
|
271
|
+
return result.data.success;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Wait for navigation to complete
|
|
275
|
+
*/
|
|
276
|
+
async waitForNavigation(timeout = 30000) {
|
|
277
|
+
if (!this.sessionId) {
|
|
278
|
+
throw new Error('No active browser session');
|
|
279
|
+
}
|
|
280
|
+
const waitnavigation_browserbase = new BrowserBaseBubble({
|
|
281
|
+
operation: 'wait',
|
|
282
|
+
session_id: this.sessionId,
|
|
283
|
+
wait_type: 'navigation',
|
|
284
|
+
timeout,
|
|
285
|
+
}, this.context, 'waitnavigation_browserbase');
|
|
286
|
+
const result = await waitnavigation_browserbase.action();
|
|
287
|
+
return result.data.success;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Take a screenshot and upload to Cloudflare R2
|
|
291
|
+
* Returns the URL of the uploaded screenshot
|
|
292
|
+
*/
|
|
293
|
+
async takeScreenshotAndUpload(label, fullPage = false) {
|
|
294
|
+
if (!this.sessionId) {
|
|
295
|
+
console.error('[AmazonShoppingTool] No session for screenshot');
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
debugLog(`[AmazonShoppingTool] Taking screenshot: ${label}`);
|
|
300
|
+
// Take screenshot using BrowserBase
|
|
301
|
+
const screenshot_browserbase = new BrowserBaseBubble({
|
|
302
|
+
operation: 'screenshot',
|
|
303
|
+
session_id: this.sessionId,
|
|
304
|
+
full_page: fullPage,
|
|
305
|
+
format: 'png',
|
|
306
|
+
credentials: this.params.credentials,
|
|
307
|
+
}, this.context, 'screenshot_browserbase');
|
|
308
|
+
const screenshotResult = await screenshot_browserbase.action();
|
|
309
|
+
if (!screenshotResult.data.success || !screenshotResult.data.data) {
|
|
310
|
+
console.error('[AmazonShoppingTool] Screenshot failed:', screenshotResult.data.error);
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const base64Data = screenshotResult.data.data;
|
|
314
|
+
debugLog(`[AmazonShoppingTool] Screenshot captured, size: ${base64Data.length} chars`);
|
|
315
|
+
// Upload to Cloudflare R2 using StorageBubble
|
|
316
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
317
|
+
const fileName = `amazon-${label}-${timestamp}.png`;
|
|
318
|
+
const updatefile_storagebubble = new StorageBubble({
|
|
319
|
+
operation: 'updateFile',
|
|
320
|
+
bucketName: 'bubble-lab-bucket',
|
|
321
|
+
fileName,
|
|
322
|
+
fileContent: `data:image/png;base64,${base64Data}`,
|
|
323
|
+
contentType: 'image/png',
|
|
324
|
+
credentials: this.params.credentials,
|
|
325
|
+
}, this.context, 'updatefile_storagebubble');
|
|
326
|
+
const uploadResult = await updatefile_storagebubble.action();
|
|
327
|
+
if (!uploadResult.data.success || !uploadResult.data.fileName) {
|
|
328
|
+
console.error('[AmazonShoppingTool] Upload failed:', uploadResult.data.error);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
debugLog(`[AmazonShoppingTool] Screenshot uploaded: ${uploadResult.data.fileName}`);
|
|
332
|
+
// Get the download URL for the uploaded file
|
|
333
|
+
const getfile_storagebubble = new StorageBubble({
|
|
334
|
+
operation: 'getFile',
|
|
335
|
+
bucketName: 'bubble-lab-bucket',
|
|
336
|
+
fileName: uploadResult.data.fileName,
|
|
337
|
+
expirationMinutes: 60 * 24 * 7, // 7 days expiry
|
|
338
|
+
credentials: this.params.credentials,
|
|
339
|
+
}, this.context, 'getfile_storagebubble');
|
|
340
|
+
const fileResult = await getfile_storagebubble.action();
|
|
341
|
+
if (!fileResult.data.success || !fileResult.data.downloadUrl) {
|
|
342
|
+
console.error('[AmazonShoppingTool] Failed to get download URL:', fileResult.data.error);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
debugLog(`[AmazonShoppingTool] Screenshot URL generated: ${fileResult.data.downloadUrl.substring(0, 80)}...`);
|
|
346
|
+
return fileResult.data.downloadUrl;
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
console.error('[AmazonShoppingTool] Screenshot error:', error);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async performAction() {
|
|
354
|
+
const { operation } = this.params;
|
|
355
|
+
// Cast to output type since base class already parsed input through Zod
|
|
356
|
+
const parsedParams = this.params;
|
|
357
|
+
try {
|
|
358
|
+
// Start browser session
|
|
359
|
+
await this.startBrowserSession();
|
|
360
|
+
const result = await (async () => {
|
|
361
|
+
switch (operation) {
|
|
362
|
+
case 'add_to_cart':
|
|
363
|
+
return await this.addToCart(parsedParams);
|
|
364
|
+
case 'get_cart':
|
|
365
|
+
return (await this.getCart());
|
|
366
|
+
case 'checkout':
|
|
367
|
+
return (await this.checkout());
|
|
368
|
+
case 'search':
|
|
369
|
+
return await this.searchProducts(parsedParams);
|
|
370
|
+
case 'get_product':
|
|
371
|
+
return await this.getProduct(parsedParams);
|
|
372
|
+
case 'screenshot':
|
|
373
|
+
return await this.takeScreenshot(parsedParams);
|
|
374
|
+
default:
|
|
375
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
376
|
+
}
|
|
377
|
+
})();
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
console.error('[AmazonShoppingTool] Error:', error);
|
|
382
|
+
return {
|
|
383
|
+
operation,
|
|
384
|
+
success: false,
|
|
385
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
finally {
|
|
389
|
+
// Always clean up the session
|
|
390
|
+
await this.endBrowserSession();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Add a product to cart
|
|
395
|
+
*/
|
|
396
|
+
async addToCart(params) {
|
|
397
|
+
const asin = this.extractAsin(params.product_url);
|
|
398
|
+
const productUrl = this.buildProductUrl(asin);
|
|
399
|
+
debugLog(`[AmazonShoppingTool] Adding to cart: ${asin}`);
|
|
400
|
+
// Navigate to product page
|
|
401
|
+
await this.navigateTo(productUrl);
|
|
402
|
+
// Wait for and click Add to Cart button
|
|
403
|
+
const addToCartSelectors = [
|
|
404
|
+
'#submit\\.add-to-cart',
|
|
405
|
+
'#add-to-cart-button',
|
|
406
|
+
'input[name="submit.add-to-cart"]',
|
|
407
|
+
];
|
|
408
|
+
let clicked = false;
|
|
409
|
+
for (const selector of addToCartSelectors) {
|
|
410
|
+
const exists = await this.waitForSelector(selector, 2000);
|
|
411
|
+
if (exists) {
|
|
412
|
+
clicked = await this.clickElement(selector, true);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Wait a moment for any modal/popup to appear
|
|
417
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
418
|
+
// Check if we've already navigated away from product page (to cart confirmation)
|
|
419
|
+
// If so, skip the "No thanks" check entirely - we're done
|
|
420
|
+
let currentUrlAfterAdd;
|
|
421
|
+
try {
|
|
422
|
+
currentUrlAfterAdd = await this.getCurrentUrl();
|
|
423
|
+
debugLog(`[AmazonShoppingTool] URL after Add to Cart click: ${currentUrlAfterAdd}`);
|
|
424
|
+
// If we've navigated to cart or confirmation page, we're done - skip No Thanks check
|
|
425
|
+
if (currentUrlAfterAdd.includes('/cart') ||
|
|
426
|
+
currentUrlAfterAdd.includes('/gp/aw/d/') ||
|
|
427
|
+
currentUrlAfterAdd.includes('sw_pt=') ||
|
|
428
|
+
currentUrlAfterAdd.includes('/gp/product/handle-buy-box')) {
|
|
429
|
+
debugLog('[AmazonShoppingTool] Already navigated to cart/confirmation - skipping No Thanks check');
|
|
430
|
+
return {
|
|
431
|
+
operation: 'add_to_cart',
|
|
432
|
+
success: true,
|
|
433
|
+
message: `Added ${asin} to cart`,
|
|
434
|
+
cart_count: undefined,
|
|
435
|
+
error: '',
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
debugLog('[AmazonShoppingTool] Could not get URL (navigation may have occurred)');
|
|
441
|
+
}
|
|
442
|
+
// Handle protection plan modal - click "No thanks" if it appears
|
|
443
|
+
// This modal asks "Add to your order" with protection plan options
|
|
444
|
+
// Try multiple times as modal may take time to fully render
|
|
445
|
+
// NOTE: If "No thanks" is not found, we just skip and proceed - it's optional
|
|
446
|
+
debugLog('[AmazonShoppingTool] Checking for protection plan modal...');
|
|
447
|
+
// First, try to click using BrowserBase click operation with selector
|
|
448
|
+
// This handles Amazon's dynamically rendered modals better
|
|
449
|
+
// The attachSiNoCoverage button is in the side sheet (NOT in a-popover-preload)
|
|
450
|
+
const noThanksSelectors = [
|
|
451
|
+
'#attachSiNoCoverage',
|
|
452
|
+
'#attachSiNoCoverage input',
|
|
453
|
+
'#attachSiNoCoverage-ld',
|
|
454
|
+
'#attachSiNoCoverage-ld input',
|
|
455
|
+
'#attachSiNoCoverage-eu-enhanced',
|
|
456
|
+
'#attachSiNoCoverage-eu-enhanced input',
|
|
457
|
+
'.a-popover-wrapper .mbb__no button',
|
|
458
|
+
'.a-sheet-content .mbb__no button',
|
|
459
|
+
];
|
|
460
|
+
for (const selector of noThanksSelectors) {
|
|
461
|
+
try {
|
|
462
|
+
const selectorClicked = await this.clickElement(selector, false);
|
|
463
|
+
if (selectorClicked) {
|
|
464
|
+
debugLog(`[AmazonShoppingTool] Clicked No Thanks via selector: ${selector}`);
|
|
465
|
+
clicked = true;
|
|
466
|
+
// Wait for navigation after dismissing modal
|
|
467
|
+
debugLog('[AmazonShoppingTool] Waiting for navigation after No Thanks click...');
|
|
468
|
+
try {
|
|
469
|
+
await this.waitForNavigation(5000);
|
|
470
|
+
debugLog('[AmazonShoppingTool] Navigation complete after No Thanks');
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
debugLog('[AmazonShoppingTool] Navigation wait completed or timed out');
|
|
474
|
+
}
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch (err) {
|
|
479
|
+
// Selector not found or error during click, continue
|
|
480
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
481
|
+
if (errorMsg.includes('navigation') || errorMsg.includes('context')) {
|
|
482
|
+
// Navigation happened, which means click succeeded
|
|
483
|
+
debugLog('[AmazonShoppingTool] Click caused navigation (success)');
|
|
484
|
+
clicked = true;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// If selector-based click didn't work, try evaluate-based approach
|
|
490
|
+
if (!clicked) {
|
|
491
|
+
const noThanksResult = (await this.evaluate(`
|
|
492
|
+
(() => {
|
|
493
|
+
// Helper function to check if element is truly visible (not in preload/hidden containers)
|
|
494
|
+
function isElementVisible(el) {
|
|
495
|
+
// Check if inside a-popover-preload (hidden preload content)
|
|
496
|
+
if (el.closest('.a-popover-preload')) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
// Check computed style
|
|
500
|
+
const style = window.getComputedStyle(el);
|
|
501
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
// Check bounding rect
|
|
505
|
+
const rect = el.getBoundingClientRect();
|
|
506
|
+
return rect.width > 0 && rect.height > 0;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// First try: Look for attachSiNoCoverage buttons (the visible side sheet buttons)
|
|
510
|
+
const attachSiButtons = ['#attachSiNoCoverage', '#attachSiNoCoverage-ld', '#attachSiNoCoverage-eu-enhanced'];
|
|
511
|
+
for (const id of attachSiButtons) {
|
|
512
|
+
const btn = document.querySelector(id);
|
|
513
|
+
if (btn && isElementVisible(btn)) {
|
|
514
|
+
console.log('[Debug] Clicking attachSi button:', id);
|
|
515
|
+
btn.click();
|
|
516
|
+
return { clicked: true, text: 'No thanks', method: 'attachSi' };
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Second try: Look for visible mbb__no buttons (not in preload)
|
|
521
|
+
const mbbNoButtons = document.querySelectorAll('.mbb__no button, span.mbb__no');
|
|
522
|
+
console.log('[Debug] Found mbb__no elements:', mbbNoButtons.length);
|
|
523
|
+
for (const btn of mbbNoButtons) {
|
|
524
|
+
if (isElementVisible(btn)) {
|
|
525
|
+
console.log('[Debug] Clicking visible mbb__no button:', btn.textContent?.trim());
|
|
526
|
+
btn.click();
|
|
527
|
+
return { clicked: true, text: btn.textContent?.trim() || 'mbb__no', method: 'mbb__no' };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Third try: Look for visible "No thanks" text buttons (not in preload)
|
|
532
|
+
const allButtons = document.querySelectorAll('button, input[type="submit"], a, span[role="button"]');
|
|
533
|
+
for (const btn of allButtons) {
|
|
534
|
+
const text = (btn.value || btn.innerText || btn.textContent || '').trim().toLowerCase();
|
|
535
|
+
if (text === 'no thanks' || text === 'no, thanks') {
|
|
536
|
+
if (isElementVisible(btn)) {
|
|
537
|
+
console.log('[Debug] Clicking visible no thanks button:', text);
|
|
538
|
+
btn.click();
|
|
539
|
+
return { clicked: true, text: text, method: 'text-match' };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Log debug info about what we found
|
|
545
|
+
const preloadCount = document.querySelectorAll('.a-popover-preload .mbb__no').length;
|
|
546
|
+
const attachSiExists = document.querySelector('#attachSiNoCoverage') ? 'yes' : 'no';
|
|
547
|
+
console.log('[Debug] attachSiNoCoverage exists:', attachSiExists);
|
|
548
|
+
console.log('[Debug] mbb__no in preload (hidden):', preloadCount);
|
|
549
|
+
console.log('[Debug] No visible No Thanks button found');
|
|
550
|
+
return { clicked: false };
|
|
551
|
+
})()
|
|
552
|
+
`));
|
|
553
|
+
if (noThanksResult.clicked) {
|
|
554
|
+
clicked = true;
|
|
555
|
+
debugLog(`[AmazonShoppingTool] Dismissed protection plan modal by clicking "${noThanksResult.text}" (method: ${noThanksResult.method})`);
|
|
556
|
+
// Wait for navigation after dismissing modal
|
|
557
|
+
debugLog('[AmazonShoppingTool] Waiting for navigation...');
|
|
558
|
+
try {
|
|
559
|
+
await this.waitForNavigation(4000);
|
|
560
|
+
debugLog('[AmazonShoppingTool] Navigation complete after No thanks');
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
debugLog('[AmazonShoppingTool] Navigation wait timed out, continuing...');
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
debugLog('[AmazonShoppingTool] No protection plan modal detected (via evaluate)');
|
|
568
|
+
}
|
|
569
|
+
} // Close if (!clicked) block
|
|
570
|
+
// Debug: Save current page state for inspection (may fail after navigation)
|
|
571
|
+
try {
|
|
572
|
+
const currentUrl = await this.getCurrentUrl();
|
|
573
|
+
debugLog(`[AmazonShoppingTool] Current URL after add to cart: ${currentUrl}`);
|
|
574
|
+
await this.saveDebugState('add-to-cart-after');
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
debugLog('[AmazonShoppingTool] Could not save debug state (page may have navigated)');
|
|
578
|
+
}
|
|
579
|
+
// Try to get cart count (may fail after navigation)
|
|
580
|
+
let cartCount;
|
|
581
|
+
try {
|
|
582
|
+
const countResult = (await this.evaluate(`
|
|
583
|
+
(() => {
|
|
584
|
+
const cartEl = document.querySelector('#nav-cart-count');
|
|
585
|
+
if (cartEl) {
|
|
586
|
+
const count = parseInt(cartEl.textContent || '0', 10);
|
|
587
|
+
return isNaN(count) ? 0 : count;
|
|
588
|
+
}
|
|
589
|
+
return 0;
|
|
590
|
+
})()
|
|
591
|
+
`));
|
|
592
|
+
cartCount = countResult;
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
// Cart count is optional, may fail if page navigated
|
|
596
|
+
debugLog('[AmazonShoppingTool] Could not get cart count (page may have navigated)');
|
|
597
|
+
}
|
|
598
|
+
if (!clicked) {
|
|
599
|
+
return {
|
|
600
|
+
operation: 'add_to_cart',
|
|
601
|
+
success: false,
|
|
602
|
+
error: 'Could not find Add to Cart button. Product may be unavailable.',
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
operation: 'add_to_cart',
|
|
607
|
+
success: true,
|
|
608
|
+
message: `Added ${asin} to cart`,
|
|
609
|
+
cart_count: cartCount,
|
|
610
|
+
error: '',
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get cart contents
|
|
615
|
+
*/
|
|
616
|
+
async getCart() {
|
|
617
|
+
debugLog('[AmazonShoppingTool] Getting cart contents');
|
|
618
|
+
// Navigate to cart page
|
|
619
|
+
await this.navigateTo('https://www.amazon.com/gp/cart/view.html');
|
|
620
|
+
// Wait for cart to load
|
|
621
|
+
await this.waitForSelector('#sc-active-cart', 5000);
|
|
622
|
+
// Extract cart items using JavaScript
|
|
623
|
+
const cartData = (await this.evaluate(`
|
|
624
|
+
(() => {
|
|
625
|
+
const items = [];
|
|
626
|
+
const cartItems = document.querySelectorAll('[data-asin]');
|
|
627
|
+
|
|
628
|
+
cartItems.forEach(item => {
|
|
629
|
+
const asin = item.getAttribute('data-asin');
|
|
630
|
+
if (!asin) return;
|
|
631
|
+
|
|
632
|
+
const titleEl = item.querySelector('.sc-product-title, .a-truncate-cut');
|
|
633
|
+
const priceEl = item.querySelector('.sc-product-price, .sc-price');
|
|
634
|
+
const quantityEl = item.querySelector('select[name*="quantity"], .sc-quantity-textfield');
|
|
635
|
+
const imageEl = item.querySelector('img.sc-product-image, img[data-a-hires]');
|
|
636
|
+
|
|
637
|
+
items.push({
|
|
638
|
+
asin,
|
|
639
|
+
title: titleEl?.textContent?.trim() || 'Unknown Product',
|
|
640
|
+
price: priceEl?.textContent?.trim() || '',
|
|
641
|
+
quantity: quantityEl ? parseInt(quantityEl.value || '1', 10) : 1,
|
|
642
|
+
image: imageEl?.src || '',
|
|
643
|
+
url: 'https://www.amazon.com/dp/' + asin,
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Get subtotal
|
|
648
|
+
const subtotalEl = document.querySelector('#sc-subtotal-amount-activecart, .sc-subtotal-amount-activecart');
|
|
649
|
+
const subtotal = subtotalEl?.textContent?.trim() || '';
|
|
650
|
+
|
|
651
|
+
return { items, subtotal, totalItems: items.reduce((sum, i) => sum + i.quantity, 0) };
|
|
652
|
+
})()
|
|
653
|
+
`));
|
|
654
|
+
// Take confirmation screenshot of the cart
|
|
655
|
+
const screenshotUrl = await this.takeScreenshotAndUpload('cart', false);
|
|
656
|
+
return {
|
|
657
|
+
operation: 'get_cart',
|
|
658
|
+
success: true,
|
|
659
|
+
items: cartData.items,
|
|
660
|
+
subtotal: cartData.subtotal,
|
|
661
|
+
total_items: cartData.totalItems,
|
|
662
|
+
screenshot_url: screenshotUrl || undefined,
|
|
663
|
+
error: '',
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Get current page URL
|
|
668
|
+
*/
|
|
669
|
+
async getCurrentUrl() {
|
|
670
|
+
const result = (await this.evaluate(`window.location.href`));
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Complete checkout - uses same heuristics as amazon-cart-browserbase.ts
|
|
675
|
+
*/
|
|
676
|
+
async checkout() {
|
|
677
|
+
debugLog('[AmazonShoppingTool] Starting checkout');
|
|
678
|
+
// Step 1: Go to cart
|
|
679
|
+
debugLog('[AmazonShoppingTool] Step 1: Loading cart...');
|
|
680
|
+
await this.navigateTo('https://www.amazon.com/gp/cart/view.html');
|
|
681
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
682
|
+
// Check cart has items
|
|
683
|
+
const cartItemCount = (await this.evaluate(`
|
|
684
|
+
document.querySelectorAll('[data-asin]:not([data-asin=""])').length
|
|
685
|
+
`));
|
|
686
|
+
debugLog(`[AmazonShoppingTool] Found ${cartItemCount} items in cart`);
|
|
687
|
+
if (cartItemCount === 0) {
|
|
688
|
+
return {
|
|
689
|
+
operation: 'checkout',
|
|
690
|
+
success: false,
|
|
691
|
+
error: 'Cart is empty',
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
// Step 2: Click proceed to checkout
|
|
695
|
+
debugLog('[AmazonShoppingTool] Step 2: Looking for checkout button...');
|
|
696
|
+
const checkoutClickResult = (await this.evaluate(`
|
|
697
|
+
(() => {
|
|
698
|
+
// Prioritize input/button over span/a - input.value is the actual clickable form element
|
|
699
|
+
const priorityOrder = ['input', 'button', 'a', 'span'];
|
|
700
|
+
for (const tagName of priorityOrder) {
|
|
701
|
+
const elements = document.querySelectorAll(tagName);
|
|
702
|
+
for (const el of elements) {
|
|
703
|
+
const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
|
|
704
|
+
if (text === 'proceed to checkout' || text === 'proceed to retail checkout') {
|
|
705
|
+
el.click();
|
|
706
|
+
return { clicked: true, tag: el.tagName, className: el.className, text: text };
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return { clicked: false };
|
|
711
|
+
})()
|
|
712
|
+
`));
|
|
713
|
+
if (!checkoutClickResult.clicked) {
|
|
714
|
+
return {
|
|
715
|
+
operation: 'checkout',
|
|
716
|
+
success: false,
|
|
717
|
+
error: 'Could not find checkout button',
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
debugLog(`[AmazonShoppingTool] Clicked Proceed to checkout: <${checkoutClickResult.tag} class="${checkoutClickResult.className}">${checkoutClickResult.text}`);
|
|
721
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
722
|
+
let currentUrl = await this.getCurrentUrl();
|
|
723
|
+
debugLog(`[AmazonShoppingTool] Current URL: ${currentUrl}`);
|
|
724
|
+
// Check for sign-in redirect
|
|
725
|
+
if (currentUrl.includes('/ap/signin')) {
|
|
726
|
+
return {
|
|
727
|
+
operation: 'checkout',
|
|
728
|
+
success: false,
|
|
729
|
+
error: 'Password re-authentication required',
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
// Step 2.5: Handle "Continue to checkout" intermediate screen (BYG page)
|
|
733
|
+
debugLog('[AmazonShoppingTool] Step 2.5: Checking for Continue to checkout screen...');
|
|
734
|
+
const continueClickResult = (await this.evaluate(`
|
|
735
|
+
(() => {
|
|
736
|
+
const priorityOrder = ['input', 'button', 'a', 'span'];
|
|
737
|
+
for (const tagName of priorityOrder) {
|
|
738
|
+
const elements = document.querySelectorAll(tagName);
|
|
739
|
+
for (const el of elements) {
|
|
740
|
+
const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
|
|
741
|
+
if (text === 'continue to checkout') {
|
|
742
|
+
el.click();
|
|
743
|
+
return { clicked: true, tag: el.tagName, className: el.className, text: text };
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return { clicked: false };
|
|
748
|
+
})()
|
|
749
|
+
`));
|
|
750
|
+
if (continueClickResult.clicked) {
|
|
751
|
+
debugLog(`[AmazonShoppingTool] Clicked Continue to checkout: <${continueClickResult.tag} class="${continueClickResult.className}">${continueClickResult.text}`);
|
|
752
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
753
|
+
currentUrl = await this.getCurrentUrl();
|
|
754
|
+
debugLog(`[AmazonShoppingTool] After Continue to checkout URL: ${currentUrl}`);
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
debugLog('[AmazonShoppingTool] No Continue to checkout button found, continuing...');
|
|
758
|
+
}
|
|
759
|
+
// Check for sign-in redirect again
|
|
760
|
+
if (currentUrl.includes('/ap/signin')) {
|
|
761
|
+
return {
|
|
762
|
+
operation: 'checkout',
|
|
763
|
+
success: false,
|
|
764
|
+
error: 'Password re-authentication required',
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
// Step 3: Navigate through checkout steps (up to 5 pages: address, payment, review, etc.)
|
|
768
|
+
debugLog('[AmazonShoppingTool] Step 3: Navigating checkout steps...');
|
|
769
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
770
|
+
currentUrl = await this.getCurrentUrl();
|
|
771
|
+
debugLog(`[AmazonShoppingTool] Step 3.${attempt}: URL = ${currentUrl}`);
|
|
772
|
+
// Check for sign-in redirect
|
|
773
|
+
if (currentUrl.includes('/ap/signin')) {
|
|
774
|
+
return {
|
|
775
|
+
operation: 'checkout',
|
|
776
|
+
success: false,
|
|
777
|
+
error: 'Password re-authentication required',
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
// Try to click "Use this payment method" button if visible (before Place your order)
|
|
781
|
+
const usePaymentMethodResult = (await this.evaluate(`
|
|
782
|
+
(() => {
|
|
783
|
+
// First try by specific IDs (most reliable)
|
|
784
|
+
const primaryBtn = document.querySelector('#checkout-primary-continue-button-id');
|
|
785
|
+
const secondaryBtn = document.querySelector('#checkout-secondary-continue-button-id');
|
|
786
|
+
|
|
787
|
+
// Check if either button contains "use this payment method" text (case-insensitive)
|
|
788
|
+
for (const btn of [primaryBtn, secondaryBtn]) {
|
|
789
|
+
if (btn) {
|
|
790
|
+
const text = (btn.innerText || btn.textContent || '').trim().toLowerCase();
|
|
791
|
+
if (text.includes('use this payment method')) {
|
|
792
|
+
// Find the clickable input inside the button span
|
|
793
|
+
const input = btn.querySelector('input.a-button-input');
|
|
794
|
+
if (input) {
|
|
795
|
+
input.click();
|
|
796
|
+
return { clicked: true, tag: 'input', className: input.className, text: text, method: 'input-click' };
|
|
797
|
+
}
|
|
798
|
+
// Fallback to clicking the button span itself
|
|
799
|
+
btn.click();
|
|
800
|
+
return { clicked: true, tag: btn.tagName, className: btn.className, text: text, method: 'span-click' };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Fallback: search all elements for "use this payment method" text (case-insensitive)
|
|
806
|
+
const priorityOrder = ['input', 'button', 'a', 'span'];
|
|
807
|
+
for (const tagName of priorityOrder) {
|
|
808
|
+
const elements = document.querySelectorAll(tagName);
|
|
809
|
+
for (const el of elements) {
|
|
810
|
+
const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
|
|
811
|
+
if (text.includes('use this payment method')) {
|
|
812
|
+
el.click();
|
|
813
|
+
return { clicked: true, tag: el.tagName, className: el.className, text: text, method: 'fallback' };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return { clicked: false };
|
|
818
|
+
})()
|
|
819
|
+
`));
|
|
820
|
+
if (usePaymentMethodResult.clicked) {
|
|
821
|
+
debugLog(`[AmazonShoppingTool] Clicked "Use this payment method": <${usePaymentMethodResult.tag} class="${usePaymentMethodResult.className}"> via ${usePaymentMethodResult.method}`);
|
|
822
|
+
// Wait for the page to process the payment method selection
|
|
823
|
+
await new Promise((resolve) => setTimeout(resolve, 4000));
|
|
824
|
+
// Continue to next iteration to check for Place your order button
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
// Try to click "Place your order" button
|
|
828
|
+
const placeOrderResult = (await this.evaluate(`
|
|
829
|
+
(() => {
|
|
830
|
+
const priorityOrder = ['input', 'button', 'a', 'span'];
|
|
831
|
+
for (const tagName of priorityOrder) {
|
|
832
|
+
const elements = document.querySelectorAll(tagName);
|
|
833
|
+
for (const el of elements) {
|
|
834
|
+
const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
|
|
835
|
+
if (text === 'place your order' || text === 'place order') {
|
|
836
|
+
el.click();
|
|
837
|
+
return { clicked: true, tag: el.tagName, className: el.className, text: text };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return { clicked: false };
|
|
842
|
+
})()
|
|
843
|
+
`));
|
|
844
|
+
if (placeOrderResult.clicked) {
|
|
845
|
+
debugLog(`[AmazonShoppingTool] Clicked Place Order: <${placeOrderResult.tag} class="${placeOrderResult.className}">${placeOrderResult.text}`);
|
|
846
|
+
// Wait for navigation to confirmation page
|
|
847
|
+
debugLog('[AmazonShoppingTool] Waiting for navigation...');
|
|
848
|
+
try {
|
|
849
|
+
await this.waitForNavigation(15000);
|
|
850
|
+
debugLog('[AmazonShoppingTool] Navigation complete');
|
|
851
|
+
}
|
|
852
|
+
catch {
|
|
853
|
+
debugLog('[AmazonShoppingTool] Navigation wait timed out, continuing...');
|
|
854
|
+
}
|
|
855
|
+
break; // Order placed, exit loop
|
|
856
|
+
}
|
|
857
|
+
// Try to click "Continue" button to advance to next checkout page
|
|
858
|
+
const continueResult = (await this.evaluate(`
|
|
859
|
+
(() => {
|
|
860
|
+
const priorityOrder = ['input', 'button', 'a', 'span'];
|
|
861
|
+
for (const tagName of priorityOrder) {
|
|
862
|
+
const elements = document.querySelectorAll(tagName);
|
|
863
|
+
for (const el of elements) {
|
|
864
|
+
const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
|
|
865
|
+
if (text === 'continue' || text === 'continue to checkout') {
|
|
866
|
+
el.click();
|
|
867
|
+
return { clicked: true, tag: el.tagName, className: el.className, text: text };
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return { clicked: false };
|
|
872
|
+
})()
|
|
873
|
+
`));
|
|
874
|
+
if (continueResult.clicked) {
|
|
875
|
+
debugLog(`[AmazonShoppingTool] Clicked Continue: <${continueResult.tag} class="${continueResult.className}">${continueResult.text}`);
|
|
876
|
+
await new Promise((resolve) => setTimeout(resolve, 4000));
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
debugLog('[AmazonShoppingTool] No actionable button found, moving to confirmation check...');
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
// Step 4: Handle duplicate order warning
|
|
884
|
+
debugLog('[AmazonShoppingTool] Step 4: Checking page state...');
|
|
885
|
+
currentUrl = await this.getCurrentUrl();
|
|
886
|
+
debugLog(`[AmazonShoppingTool] Current URL: ${currentUrl}`);
|
|
887
|
+
if (currentUrl.includes('duplicateOrder')) {
|
|
888
|
+
debugLog('[AmazonShoppingTool] Detected duplicate order warning - clicking Place Order again...');
|
|
889
|
+
const duplicateResult = (await this.evaluate(`
|
|
890
|
+
(() => {
|
|
891
|
+
const priorityOrder = ['input', 'button', 'a', 'span'];
|
|
892
|
+
for (const tagName of priorityOrder) {
|
|
893
|
+
const elements = document.querySelectorAll(tagName);
|
|
894
|
+
for (const el of elements) {
|
|
895
|
+
const text = (el.value || el.innerText || el.textContent || '').trim().toLowerCase();
|
|
896
|
+
if (text === 'place your order' || text === 'place order') {
|
|
897
|
+
el.click();
|
|
898
|
+
return { clicked: true, tag: el.tagName, className: el.className, text: text };
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return { clicked: false };
|
|
903
|
+
})()
|
|
904
|
+
`));
|
|
905
|
+
if (duplicateResult.clicked) {
|
|
906
|
+
debugLog(`[AmazonShoppingTool] Clicked Place Order to confirm duplicate: <${duplicateResult.tag} class="${duplicateResult.className}">${duplicateResult.text}`);
|
|
907
|
+
debugLog('[AmazonShoppingTool] Waiting for navigation...');
|
|
908
|
+
try {
|
|
909
|
+
await this.waitForNavigation(15000);
|
|
910
|
+
debugLog('[AmazonShoppingTool] Navigation complete');
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
debugLog('[AmazonShoppingTool] Navigation wait timed out, continuing...');
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
// Step 5: Check for confirmation
|
|
918
|
+
debugLog('[AmazonShoppingTool] Step 5: Checking for order confirmation...');
|
|
919
|
+
let finalUrl = await this.getCurrentUrl();
|
|
920
|
+
debugLog(`[AmazonShoppingTool] Current URL: ${finalUrl}`);
|
|
921
|
+
// If on intermediate page (payment verification, etc.), wait for another navigation
|
|
922
|
+
if (finalUrl.includes('/cpe/') || finalUrl.includes('executions')) {
|
|
923
|
+
debugLog('[AmazonShoppingTool] On intermediate page, waiting for redirect...');
|
|
924
|
+
try {
|
|
925
|
+
await this.waitForNavigation(30000);
|
|
926
|
+
finalUrl = await this.getCurrentUrl();
|
|
927
|
+
debugLog(`[AmazonShoppingTool] After redirect URL: ${finalUrl}`);
|
|
928
|
+
}
|
|
929
|
+
catch {
|
|
930
|
+
debugLog('[AmazonShoppingTool] No further redirect, continuing...');
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
debugLog(`[AmazonShoppingTool] Final URL: ${finalUrl}`);
|
|
934
|
+
// Extract order number from URL if available (purchaseId parameter)
|
|
935
|
+
const urlOrderMatch = finalUrl.match(/purchaseId=(\d{3}-\d{7}-\d{7})/);
|
|
936
|
+
const orderNumberFromUrl = urlOrderMatch?.[1];
|
|
937
|
+
if (orderNumberFromUrl) {
|
|
938
|
+
debugLog(`[AmazonShoppingTool] Order number from URL: ${orderNumberFromUrl}`);
|
|
939
|
+
}
|
|
940
|
+
const isConfirmationUrl = finalUrl.includes('thankyou') ||
|
|
941
|
+
finalUrl.includes('confirmation') ||
|
|
942
|
+
finalUrl.includes('order-details') ||
|
|
943
|
+
finalUrl.includes('your-orders') ||
|
|
944
|
+
finalUrl.includes('gp/buy/thankyou');
|
|
945
|
+
debugLog(`[AmazonShoppingTool] URL indicates confirmation: ${isConfirmationUrl}`);
|
|
946
|
+
// Extract confirmation info
|
|
947
|
+
const confirmInfo = (await this.evaluate(`
|
|
948
|
+
((urlIsConfirmation) => {
|
|
949
|
+
const fullText = document.body.innerText;
|
|
950
|
+
|
|
951
|
+
// Skip navigation text - find where main content starts
|
|
952
|
+
const mainContentStart = fullText.indexOf('Order placed') !== -1 ? fullText.indexOf('Order placed') :
|
|
953
|
+
fullText.indexOf('Thank you') !== -1 ? fullText.indexOf('Thank you') :
|
|
954
|
+
fullText.indexOf('Arriving') !== -1 ? fullText.indexOf('Arriving') :
|
|
955
|
+
fullText.indexOf('Delivery') !== -1 ? fullText.indexOf('Delivery') :
|
|
956
|
+
0;
|
|
957
|
+
const text = fullText.substring(mainContentStart);
|
|
958
|
+
|
|
959
|
+
// Check for various success indicators
|
|
960
|
+
const hasOrderPlaced = text.toLowerCase().includes("order placed");
|
|
961
|
+
const hasThankYou = text.toLowerCase().includes("thank you");
|
|
962
|
+
const hasConfirmed = text.toLowerCase().includes("order confirmed");
|
|
963
|
+
const hasOnItsWay = text.toLowerCase().includes("on its way");
|
|
964
|
+
const hasOrderNumber = /\\d{3}-\\d{7}-\\d{7}/.test(text);
|
|
965
|
+
|
|
966
|
+
const hasSuccess = hasOrderPlaced || hasThankYou || hasConfirmed || hasOnItsWay || hasOrderNumber;
|
|
967
|
+
|
|
968
|
+
// Extract order details
|
|
969
|
+
const orderMatch = text.match(/(\\d{3}-\\d{7}-\\d{7})/);
|
|
970
|
+
|
|
971
|
+
// Delivery date - look for day of week + date pattern
|
|
972
|
+
const dayDateMatch = text.match(/((?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),?\\s*(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[.\\s]*\\d{1,2})/i);
|
|
973
|
+
const tomorrowMatch = text.match(/(Tomorrow,?\\s*(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?[.\\s]*\\d{0,2})/i);
|
|
974
|
+
const arrivingMatch = text.match(/Arriving\\s+([\\w\\s,]+\\d{1,2})/i);
|
|
975
|
+
const deliveryMatch = dayDateMatch || tomorrowMatch || arrivingMatch;
|
|
976
|
+
|
|
977
|
+
// Order total - look near beginning, not in recommendations
|
|
978
|
+
// Note: Amazon's thank you page often doesn't show detailed pricing - that's on the order details page
|
|
979
|
+
const firstPart = text.substring(0, 1500);
|
|
980
|
+
const totalMatch = firstPart.match(/Order total[:\\s]*\\$([\\d,.]+)/i) ||
|
|
981
|
+
firstPart.match(/Grand total[:\\s]*\\$([\\d,.]+)/i);
|
|
982
|
+
|
|
983
|
+
// Subtotal, shipping, tax - must be specifically labeled (avoid Prime savings messages)
|
|
984
|
+
const subtotalMatch = firstPart.match(/(?:^|\\n)\\s*Subtotal[:\\s]*\\$([\\d,.]+)/im) ||
|
|
985
|
+
firstPart.match(/Items?\\s*\\(\\d+\\)[:\\s]*\\$([\\d,.]+)/i);
|
|
986
|
+
// Shipping cost - must be "Shipping:" or "Shipping cost" not "shipping fees" (which is Prime savings)
|
|
987
|
+
const shippingMatch = firstPart.match(/(?:Shipping cost|Shipping & handling)[:\\s]*\\$([\\d,.]+)/i) ||
|
|
988
|
+
firstPart.match(/(?:^|\\n)\\s*Shipping[:\\s]*\\$([\\d,.]+)/im);
|
|
989
|
+
const taxMatch = firstPart.match(/(?:^|\\n)\\s*(?:Estimated )?[Tt]ax[:\\s]*\\$([\\d,.]+)/m);
|
|
990
|
+
|
|
991
|
+
// Shipping address - look for address after "Shipping to" but before any price or newline
|
|
992
|
+
const addressMatch = text.match(/(?:Shipping to|Delivering to|Ship to)[:\\s]*([A-Z][^\\n\\$]{10,150})/i);
|
|
993
|
+
|
|
994
|
+
// Payment method - look for card info
|
|
995
|
+
const paymentMatch = text.match(/(Visa|Mastercard|Amex|American Express|Discover)[^\\n]*ending in (\\d{4})/i) ||
|
|
996
|
+
text.match(/Payment method[:\\s]*([^\\n]{5,40})/i);
|
|
997
|
+
|
|
998
|
+
// Items - look for product titles (be more conservative to avoid promotional content)
|
|
999
|
+
const items = [];
|
|
1000
|
+
|
|
1001
|
+
return {
|
|
1002
|
+
isSuccess: hasSuccess || urlIsConfirmation,
|
|
1003
|
+
hasOrderPlaced,
|
|
1004
|
+
hasThankYou,
|
|
1005
|
+
hasConfirmed,
|
|
1006
|
+
hasOnItsWay,
|
|
1007
|
+
hasOrderNumber,
|
|
1008
|
+
orderNumber: orderMatch?.[1],
|
|
1009
|
+
deliveryDate: deliveryMatch?.[1]?.trim(),
|
|
1010
|
+
total: totalMatch ? '$' + totalMatch[1] : undefined,
|
|
1011
|
+
subtotal: subtotalMatch ? '$' + subtotalMatch[1] : undefined,
|
|
1012
|
+
shippingCost: shippingMatch ? '$' + shippingMatch[1] : undefined,
|
|
1013
|
+
tax: taxMatch ? '$' + taxMatch[1] : undefined,
|
|
1014
|
+
address: addressMatch?.[1]?.trim(),
|
|
1015
|
+
paymentMethod: paymentMatch?.[0]?.trim(),
|
|
1016
|
+
items: items.length > 0 ? items : undefined,
|
|
1017
|
+
};
|
|
1018
|
+
})(${isConfirmationUrl})
|
|
1019
|
+
`));
|
|
1020
|
+
debugLog(`[AmazonShoppingTool] Success indicators: orderPlaced=${confirmInfo.hasOrderPlaced}, thankYou=${confirmInfo.hasThankYou}, confirmed=${confirmInfo.hasConfirmed}, onItsWay=${confirmInfo.hasOnItsWay}, hasOrderNumber=${confirmInfo.hasOrderNumber}`);
|
|
1021
|
+
debugLog(`[AmazonShoppingTool] Order number: ${confirmInfo.orderNumber || 'not found'}`);
|
|
1022
|
+
debugLog(`[AmazonShoppingTool] Delivery: ${confirmInfo.deliveryDate || 'not found'}`);
|
|
1023
|
+
debugLog(`[AmazonShoppingTool] Total: ${confirmInfo.total || 'not found'}`);
|
|
1024
|
+
debugLog(`[AmazonShoppingTool] Subtotal: ${confirmInfo.subtotal || 'not found'}`);
|
|
1025
|
+
debugLog(`[AmazonShoppingTool] Shipping: ${confirmInfo.shippingCost || 'not found'}`);
|
|
1026
|
+
debugLog(`[AmazonShoppingTool] Tax: ${confirmInfo.tax || 'not found'}`);
|
|
1027
|
+
debugLog(`[AmazonShoppingTool] Address: ${confirmInfo.address || 'not found'}`);
|
|
1028
|
+
debugLog(`[AmazonShoppingTool] Payment: ${confirmInfo.paymentMethod || 'not found'}`);
|
|
1029
|
+
debugLog(`[AmazonShoppingTool] Items: ${confirmInfo.items?.length || 0} found`);
|
|
1030
|
+
// Save debug state before returning
|
|
1031
|
+
await this.saveDebugState('checkout-final');
|
|
1032
|
+
// If URL indicates success OR text indicates success, consider it successful
|
|
1033
|
+
if (!confirmInfo.isSuccess && !isConfirmationUrl) {
|
|
1034
|
+
debugLog('[AmazonShoppingTool] ERROR: No success indicators found');
|
|
1035
|
+
return {
|
|
1036
|
+
operation: 'checkout',
|
|
1037
|
+
success: false,
|
|
1038
|
+
error: 'Order may not have been placed',
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
debugLog('[AmazonShoppingTool] SUCCESS: Order confirmed!');
|
|
1042
|
+
// Take confirmation screenshot of the order
|
|
1043
|
+
const screenshotUrl = await this.takeScreenshotAndUpload('checkout-confirmation', false);
|
|
1044
|
+
return {
|
|
1045
|
+
operation: 'checkout',
|
|
1046
|
+
success: true,
|
|
1047
|
+
order_number: confirmInfo.orderNumber || orderNumberFromUrl,
|
|
1048
|
+
estimated_delivery: confirmInfo.deliveryDate,
|
|
1049
|
+
total: confirmInfo.total,
|
|
1050
|
+
subtotal: confirmInfo.subtotal,
|
|
1051
|
+
shipping_cost: confirmInfo.shippingCost,
|
|
1052
|
+
tax: confirmInfo.tax,
|
|
1053
|
+
shipping_address: confirmInfo.address,
|
|
1054
|
+
payment_method: confirmInfo.paymentMethod,
|
|
1055
|
+
items: confirmInfo.items,
|
|
1056
|
+
screenshot_url: screenshotUrl || undefined,
|
|
1057
|
+
error: '',
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Save current DOM state to file for debugging
|
|
1062
|
+
* Only saves when DEBUG env var is set
|
|
1063
|
+
*/
|
|
1064
|
+
async saveDebugState(label) {
|
|
1065
|
+
// Skip saving if DEBUG is not enabled
|
|
1066
|
+
if (!DEBUG) {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
const fs = await import('fs/promises');
|
|
1071
|
+
const htmlContent = (await this.evaluate(`document.documentElement.outerHTML`));
|
|
1072
|
+
const currentUrl = await this.getCurrentUrl();
|
|
1073
|
+
const timestamp = Date.now();
|
|
1074
|
+
const debugPath = `/tmp/amazon-debug-${label}-${timestamp}.html`;
|
|
1075
|
+
// Add URL as comment at top of file
|
|
1076
|
+
const contentWithUrl = `<!-- URL: ${currentUrl} -->\n${htmlContent}`;
|
|
1077
|
+
await fs.writeFile(debugPath, contentWithUrl);
|
|
1078
|
+
debugLog(`[AmazonShoppingTool] Saved debug DOM to: ${debugPath}`);
|
|
1079
|
+
return debugPath;
|
|
1080
|
+
}
|
|
1081
|
+
catch (e) {
|
|
1082
|
+
console.error('[AmazonShoppingTool] Failed to save debug state:', e);
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Search for products
|
|
1088
|
+
*/
|
|
1089
|
+
async searchProducts(params) {
|
|
1090
|
+
debugLog(`[AmazonShoppingTool] Searching for: ${params.query}`);
|
|
1091
|
+
const searchUrl = `https://www.amazon.com/s?k=${encodeURIComponent(params.query)}`;
|
|
1092
|
+
await this.navigateTo(searchUrl);
|
|
1093
|
+
// Wait for results
|
|
1094
|
+
await this.waitForSelector('[data-component-type="s-search-result"]', 7000);
|
|
1095
|
+
const maxResults = params.max_results || 5;
|
|
1096
|
+
// Debug: Save page state before extracting results
|
|
1097
|
+
debugLog('[AmazonShoppingTool] Saving debug state before search extraction...');
|
|
1098
|
+
await this.saveDebugState('search-before-extract');
|
|
1099
|
+
// Debug: Log current URL and page title
|
|
1100
|
+
const currentSearchUrl = await this.getCurrentUrl();
|
|
1101
|
+
debugLog(`[AmazonShoppingTool] Search URL: ${currentSearchUrl}`);
|
|
1102
|
+
// Extract search results with enhanced debugging
|
|
1103
|
+
const searchData = (await this.evaluate(`
|
|
1104
|
+
(() => {
|
|
1105
|
+
const results = [];
|
|
1106
|
+
const items = document.querySelectorAll('[data-component-type="s-search-result"]');
|
|
1107
|
+
const maxResults = ${maxResults};
|
|
1108
|
+
|
|
1109
|
+
console.log('[AmazonSearch Debug] Found ' + items.length + ' search result items');
|
|
1110
|
+
|
|
1111
|
+
for (let i = 0; i < Math.min(items.length, maxResults); i++) {
|
|
1112
|
+
const item = items[i];
|
|
1113
|
+
const asin = item.getAttribute('data-asin');
|
|
1114
|
+
if (!asin) {
|
|
1115
|
+
console.log('[AmazonSearch Debug] Item ' + i + ' has no ASIN, skipping');
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Extract title - Amazon has TWO h2 elements:
|
|
1120
|
+
// 1. First h2: Brand name only (e.g., "Amazon Basics")
|
|
1121
|
+
// 2. Second h2: Full title with aria-label (inside anchor element)
|
|
1122
|
+
// We need to get the SECOND h2 which has the full title
|
|
1123
|
+
let title = 'Unknown Product';
|
|
1124
|
+
let titleSelectorUsed = '';
|
|
1125
|
+
|
|
1126
|
+
// Approach 1: Look for h2 with aria-label attribute (contains full title)
|
|
1127
|
+
const h2WithAriaLabel = item.querySelector('h2[aria-label]');
|
|
1128
|
+
if (h2WithAriaLabel) {
|
|
1129
|
+
const ariaLabel = h2WithAriaLabel.getAttribute('aria-label');
|
|
1130
|
+
if (ariaLabel && ariaLabel.length > 10) {
|
|
1131
|
+
// Remove "Sponsored Ad - " prefix if present
|
|
1132
|
+
title = ariaLabel.replace(/^Sponsored Ad - /i, '').trim();
|
|
1133
|
+
titleSelectorUsed = 'h2[aria-label]';
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Approach 2: Look for anchor > h2 (full title h2 is inside anchor)
|
|
1138
|
+
if (titleSelectorUsed === '') {
|
|
1139
|
+
const anchorWithH2 = item.querySelector('a > h2, a h2.a-text-normal');
|
|
1140
|
+
if (anchorWithH2) {
|
|
1141
|
+
const h2Text = anchorWithH2.textContent?.trim();
|
|
1142
|
+
if (h2Text && h2Text.length > 10) {
|
|
1143
|
+
title = h2Text;
|
|
1144
|
+
titleSelectorUsed = 'a > h2';
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Approach 3: Look for [data-cy="title-recipe"] anchor text
|
|
1150
|
+
if (titleSelectorUsed === '') {
|
|
1151
|
+
const titleRecipe = item.querySelector('[data-cy="title-recipe"] a.a-text-normal, [data-cy="title-recipe"] a.s-link-style');
|
|
1152
|
+
if (titleRecipe) {
|
|
1153
|
+
const linkText = titleRecipe.textContent?.trim();
|
|
1154
|
+
if (linkText && linkText.length > 10) {
|
|
1155
|
+
title = linkText;
|
|
1156
|
+
titleSelectorUsed = '[data-cy="title-recipe"] a';
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// Approach 4: Fallback to image alt text (usually has full product name)
|
|
1162
|
+
if (titleSelectorUsed === '' || title.length < 20) {
|
|
1163
|
+
const imgEl = item.querySelector('.s-image');
|
|
1164
|
+
if (imgEl) {
|
|
1165
|
+
const altText = imgEl.getAttribute('alt');
|
|
1166
|
+
if (altText && altText.length > (title?.length || 0)) {
|
|
1167
|
+
// Remove "Sponsored Ad - " prefix if present
|
|
1168
|
+
title = altText.replace(/^Sponsored Ad - /i, '').trim();
|
|
1169
|
+
titleSelectorUsed = 'img[alt]';
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
console.log('[AmazonSearch Debug] Item ' + i + ' ASIN=' + asin + ' title="' + title.substring(0, 100) + '" selector=' + titleSelectorUsed);
|
|
1175
|
+
|
|
1176
|
+
const priceEl = item.querySelector('.a-price .a-offscreen');
|
|
1177
|
+
const ratingEl = item.querySelector('.a-icon-alt');
|
|
1178
|
+
const reviewsEl = item.querySelector('.a-size-small .a-link-normal');
|
|
1179
|
+
const imageEl = item.querySelector('.s-image');
|
|
1180
|
+
const primeEl = item.querySelector('.s-prime, .aok-relative');
|
|
1181
|
+
|
|
1182
|
+
results.push({
|
|
1183
|
+
asin,
|
|
1184
|
+
title,
|
|
1185
|
+
price: priceEl?.textContent?.trim() || '',
|
|
1186
|
+
rating: ratingEl?.textContent?.match(/[0-9.]+/)?.[0] || '',
|
|
1187
|
+
reviews_count: reviewsEl?.textContent?.match(/[0-9,]+/)?.[0] || '',
|
|
1188
|
+
url: 'https://www.amazon.com/dp/' + asin,
|
|
1189
|
+
image: imageEl?.src || '',
|
|
1190
|
+
prime: !!primeEl,
|
|
1191
|
+
_debug_titleSelector: titleSelectorUsed
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const totalEl = document.querySelector('.s-breadcrumb .a-color-state');
|
|
1196
|
+
const totalMatch = totalEl?.textContent?.match(/([0-9,]+)/);
|
|
1197
|
+
|
|
1198
|
+
return {
|
|
1199
|
+
results,
|
|
1200
|
+
totalResults: totalMatch ? parseInt(totalMatch[1].replace(/,/g, ''), 10) : results.length
|
|
1201
|
+
};
|
|
1202
|
+
})()
|
|
1203
|
+
`));
|
|
1204
|
+
// Log extracted results for debugging
|
|
1205
|
+
debugLog(`[AmazonShoppingTool] Extracted ${searchData.results.length} results`);
|
|
1206
|
+
searchData.results.forEach((r, i) => {
|
|
1207
|
+
debugLog(`[AmazonShoppingTool] Result ${i}: ASIN=${r.asin}, title="${r.title.substring(0, 50)}..."`);
|
|
1208
|
+
});
|
|
1209
|
+
return {
|
|
1210
|
+
operation: 'search',
|
|
1211
|
+
success: true,
|
|
1212
|
+
results: searchData.results,
|
|
1213
|
+
total_results: searchData.totalResults,
|
|
1214
|
+
error: '',
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Get product details
|
|
1219
|
+
*/
|
|
1220
|
+
async getProduct(params) {
|
|
1221
|
+
const asin = this.extractAsin(params.product_url);
|
|
1222
|
+
const productUrl = this.buildProductUrl(asin);
|
|
1223
|
+
debugLog(`[AmazonShoppingTool] Getting product details: ${asin}`);
|
|
1224
|
+
await this.navigateTo(productUrl);
|
|
1225
|
+
// Wait for product page
|
|
1226
|
+
await this.waitForSelector('#productTitle', 5000);
|
|
1227
|
+
// Extract product details
|
|
1228
|
+
const productData = (await this.evaluate(`
|
|
1229
|
+
(() => {
|
|
1230
|
+
const titleEl = document.querySelector('#productTitle');
|
|
1231
|
+
const priceEl = document.querySelector('.a-price .a-offscreen, #priceblock_ourprice, #priceblock_dealprice');
|
|
1232
|
+
const ratingEl = document.querySelector('#acrPopover .a-icon-alt');
|
|
1233
|
+
const reviewsEl = document.querySelector('#acrCustomerReviewText');
|
|
1234
|
+
const descEl = document.querySelector('#productDescription p, #feature-bullets');
|
|
1235
|
+
const availEl = document.querySelector('#availability span');
|
|
1236
|
+
|
|
1237
|
+
// Get feature bullets
|
|
1238
|
+
const features = [];
|
|
1239
|
+
document.querySelectorAll('#feature-bullets li span').forEach(el => {
|
|
1240
|
+
const text = el.textContent?.trim();
|
|
1241
|
+
if (text) features.push(text);
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
// Get images
|
|
1245
|
+
const images = [];
|
|
1246
|
+
document.querySelectorAll('#altImages img').forEach(img => {
|
|
1247
|
+
const src = img.src?.replace(/._[^.]+_./, '._AC_SL1500_.');
|
|
1248
|
+
if (src && !src.includes('play-button')) images.push(src);
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
return {
|
|
1252
|
+
asin: '${asin}',
|
|
1253
|
+
title: titleEl?.textContent?.trim() || 'Unknown Product',
|
|
1254
|
+
price: priceEl?.textContent?.trim() || '',
|
|
1255
|
+
rating: ratingEl?.textContent?.match(/[0-9.]+/)?.[0] || '',
|
|
1256
|
+
reviews_count: reviewsEl?.textContent?.match(/[0-9,]+/)?.[0] || '',
|
|
1257
|
+
description: descEl?.textContent?.trim() || '',
|
|
1258
|
+
features,
|
|
1259
|
+
availability: availEl?.textContent?.trim() || '',
|
|
1260
|
+
url: '${productUrl}',
|
|
1261
|
+
images,
|
|
1262
|
+
};
|
|
1263
|
+
})()
|
|
1264
|
+
`));
|
|
1265
|
+
return {
|
|
1266
|
+
operation: 'get_product',
|
|
1267
|
+
success: true,
|
|
1268
|
+
product: productData,
|
|
1269
|
+
error: '',
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Take a screenshot operation - navigate to URL if provided, then capture screenshot
|
|
1274
|
+
*/
|
|
1275
|
+
async takeScreenshot(params) {
|
|
1276
|
+
debugLog('[AmazonShoppingTool] Screenshot operation');
|
|
1277
|
+
// Navigate to URL if provided
|
|
1278
|
+
if (params.url) {
|
|
1279
|
+
debugLog(`[AmazonShoppingTool] Navigating to: ${params.url}`);
|
|
1280
|
+
await this.navigateTo(params.url);
|
|
1281
|
+
// Wait for page to load
|
|
1282
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1283
|
+
}
|
|
1284
|
+
// Take and upload screenshot
|
|
1285
|
+
const screenshotUrl = await this.takeScreenshotAndUpload('page', params.full_page);
|
|
1286
|
+
if (!screenshotUrl) {
|
|
1287
|
+
return {
|
|
1288
|
+
operation: 'screenshot',
|
|
1289
|
+
success: false,
|
|
1290
|
+
error: 'Failed to capture or upload screenshot',
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
operation: 'screenshot',
|
|
1295
|
+
success: true,
|
|
1296
|
+
screenshot_url: screenshotUrl,
|
|
1297
|
+
error: '',
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
//# sourceMappingURL=amazon-shopping-tool.js.map
|