flow-frame-core 0.1.0
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 +64 -0
- package/dist/Dockerfile +86 -0
- package/dist/GPU_DEPLOYMENT_README.md +324 -0
- package/dist/OPS_AGENT_README.md +174 -0
- package/dist/README-H100-VM.md +192 -0
- package/dist/README-worker-pools.md +231 -0
- package/dist/README.md +8 -0
- package/dist/WEB-ELEMENT-REQUESTS-README.md +302 -0
- package/dist/append.d.ts +3 -0
- package/dist/append.d.ts.map +1 -0
- package/dist/append.js +42 -0
- package/dist/append.js.map +1 -0
- package/dist/audioRoutes.d.ts +2 -0
- package/dist/audioRoutes.d.ts.map +1 -0
- package/dist/audioRoutes.js +97 -0
- package/dist/audioRoutes.js.map +1 -0
- package/dist/augment-parallel.d.ts +6 -0
- package/dist/augment-parallel.d.ts.map +1 -0
- package/dist/augment-parallel.js +128 -0
- package/dist/augment-parallel.js.map +1 -0
- package/dist/augment-worker.d.ts +2 -0
- package/dist/augment-worker.d.ts.map +1 -0
- package/dist/augment-worker.js +100 -0
- package/dist/augment-worker.js.map +1 -0
- package/dist/browerRoutes.d.ts +2 -0
- package/dist/browerRoutes.d.ts.map +1 -0
- package/dist/browerRoutes.js +323 -0
- package/dist/browerRoutes.js.map +1 -0
- package/dist/browser-utils/utils.d.ts +6 -0
- package/dist/browser-utils/utils.d.ts.map +1 -0
- package/dist/browser-utils/utils.js +133 -0
- package/dist/browser-utils/utils.js.map +1 -0
- package/dist/capture_training_data_endpoints.d.ts +158 -0
- package/dist/capture_training_data_endpoints.d.ts.map +1 -0
- package/dist/capture_training_data_endpoints.js +1812 -0
- package/dist/capture_training_data_endpoints.js.map +1 -0
- package/dist/config.json +28 -0
- package/dist/configEndpoints.d.ts +2 -0
- package/dist/configEndpoints.d.ts.map +1 -0
- package/dist/configEndpoints.js +459 -0
- package/dist/configEndpoints.js.map +1 -0
- package/dist/constants.d.ts +109 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +110 -0
- package/dist/constants.js.map +1 -0
- package/dist/docs/workflow_nodes.md +257 -0
- package/dist/download.d.ts +11 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +31 -0
- package/dist/download.js.map +1 -0
- package/dist/download.py +61 -0
- package/dist/ecosystem.config.json +63 -0
- package/dist/email-body-extractor.d.ts +20 -0
- package/dist/email-body-extractor.d.ts.map +1 -0
- package/dist/email-body-extractor.js +103 -0
- package/dist/email-body-extractor.js.map +1 -0
- package/dist/express_util.d.ts +2 -0
- package/dist/express_util.d.ts.map +1 -0
- package/dist/express_util.js +30 -0
- package/dist/express_util.js.map +1 -0
- package/dist/extension/background.d.ts +2 -0
- package/dist/extension/background.d.ts.map +1 -0
- package/dist/extension/background.js +268 -0
- package/dist/extension/background.js.map +1 -0
- package/dist/extension/manifest.json +19 -0
- package/dist/extensionUtils.d.ts +2 -0
- package/dist/extensionUtils.d.ts.map +1 -0
- package/dist/extensionUtils.js +48 -0
- package/dist/extensionUtils.js.map +1 -0
- package/dist/filter-gmail-poller/README.md +320 -0
- package/dist/filter-gmail-poller/demo.d.ts +2 -0
- package/dist/filter-gmail-poller/demo.d.ts.map +1 -0
- package/dist/filter-gmail-poller/demo.js +79 -0
- package/dist/filter-gmail-poller/demo.js.map +1 -0
- package/dist/filter-gmail-poller/example-existing-app.d.ts +2 -0
- package/dist/filter-gmail-poller/example-existing-app.d.ts.map +1 -0
- package/dist/filter-gmail-poller/example-existing-app.js +72 -0
- package/dist/filter-gmail-poller/example-existing-app.js.map +1 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.d.ts +160 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.d.ts.map +1 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.js +1048 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.js.map +1 -0
- package/dist/filter-gmail-poller/index.d.ts +3 -0
- package/dist/filter-gmail-poller/index.d.ts.map +1 -0
- package/dist/filter-gmail-poller/index.js +18 -0
- package/dist/filter-gmail-poller/index.js.map +1 -0
- package/dist/filter-gmail-poller/manual-test.d.ts +2 -0
- package/dist/filter-gmail-poller/manual-test.d.ts.map +1 -0
- package/dist/filter-gmail-poller/manual-test.js +70 -0
- package/dist/filter-gmail-poller/manual-test.js.map +1 -0
- package/dist/filter-gmail-poller/poller-prompts.d.ts +12 -0
- package/dist/filter-gmail-poller/poller-prompts.d.ts.map +1 -0
- package/dist/filter-gmail-poller/poller-prompts.js +330 -0
- package/dist/filter-gmail-poller/poller-prompts.js.map +1 -0
- package/dist/filter-gmail-poller/test.js +69 -0
- package/dist/flowframe-auto-firebase-adminsdk.json +13 -0
- package/dist/gmail-poller/README-microsoft-email-poller.md +203 -0
- package/dist/gmail-poller/README.md +129 -0
- package/dist/gmail-poller/example.d.ts +5 -0
- package/dist/gmail-poller/example.d.ts.map +1 -0
- package/dist/gmail-poller/example.js +83 -0
- package/dist/gmail-poller/example.js.map +1 -0
- package/dist/gmail-poller/gmail-poller.d.ts +82 -0
- package/dist/gmail-poller/gmail-poller.d.ts.map +1 -0
- package/dist/gmail-poller/gmail-poller.js +455 -0
- package/dist/gmail-poller/gmail-poller.js.map +1 -0
- package/dist/gmail-poller/manual-test.d.ts +2 -0
- package/dist/gmail-poller/manual-test.d.ts.map +1 -0
- package/dist/gmail-poller/manual-test.js +37 -0
- package/dist/gmail-poller/manual-test.js.map +1 -0
- package/dist/gmail-poller/microsoft-email-example.d.ts +8 -0
- package/dist/gmail-poller/microsoft-email-example.d.ts.map +1 -0
- package/dist/gmail-poller/microsoft-email-example.js +58 -0
- package/dist/gmail-poller/microsoft-email-example.js.map +1 -0
- package/dist/gmail-poller/microsoft-email-poller.d.ts +73 -0
- package/dist/gmail-poller/microsoft-email-poller.d.ts.map +1 -0
- package/dist/gmail-poller/microsoft-email-poller.js +346 -0
- package/dist/gmail-poller/microsoft-email-poller.js.map +1 -0
- package/dist/gmail-poller/setup-auth.d.ts +3 -0
- package/dist/gmail-poller/setup-auth.d.ts.map +1 -0
- package/dist/gmail-poller/setup-auth.js +36 -0
- package/dist/gmail-poller/setup-auth.js.map +1 -0
- package/dist/gmail-poller/test.js +36 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/inference/augment_levels.d.ts +2 -0
- package/dist/inference/augment_levels.d.ts.map +1 -0
- package/dist/inference/augment_levels.js +1 -0
- package/dist/inference/augment_levels.js.map +1 -0
- package/dist/inference/capture-overlay.d.ts +13 -0
- package/dist/inference/capture-overlay.d.ts.map +1 -0
- package/dist/inference/capture-overlay.js +355 -0
- package/dist/inference/capture-overlay.js.map +1 -0
- package/dist/inference/capturescreenshot.d.ts +12 -0
- package/dist/inference/capturescreenshot.d.ts.map +1 -0
- package/dist/inference/capturescreenshot.js +157 -0
- package/dist/inference/capturescreenshot.js.map +1 -0
- package/dist/jsonHandler.d.ts +37 -0
- package/dist/jsonHandler.d.ts.map +1 -0
- package/dist/jsonHandler.js +191 -0
- package/dist/jsonHandler.js.map +1 -0
- package/dist/localStorage.json +11 -0
- package/dist/media_data_endpoints.d.ts +2 -0
- package/dist/media_data_endpoints.d.ts.map +1 -0
- package/dist/media_data_endpoints.js +102 -0
- package/dist/media_data_endpoints.js.map +1 -0
- package/dist/operations/blender-ops.d.ts +4 -0
- package/dist/operations/blender-ops.d.ts.map +1 -0
- package/dist/operations/blender-ops.js +55 -0
- package/dist/operations/blender-ops.js.map +1 -0
- package/dist/operations.d.ts +34 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +1514 -0
- package/dist/operations.js.map +1 -0
- package/dist/pdfRoutes.d.ts +2 -0
- package/dist/pdfRoutes.d.ts.map +1 -0
- package/dist/pdfRoutes.js +56 -0
- package/dist/pdfRoutes.js.map +1 -0
- package/dist/peers.d.ts +9 -0
- package/dist/peers.d.ts.map +1 -0
- package/dist/peers.js +70 -0
- package/dist/peers.js.map +1 -0
- package/dist/playparser.d.ts +2 -0
- package/dist/playparser.d.ts.map +1 -0
- package/dist/playparser.js +281 -0
- package/dist/playparser.js.map +1 -0
- package/dist/process.d.ts +4 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +375 -0
- package/dist/process.js.map +1 -0
- package/dist/promptRoutes.d.ts +7 -0
- package/dist/promptRoutes.d.ts.map +1 -0
- package/dist/promptRoutes.js +68 -0
- package/dist/promptRoutes.js.map +1 -0
- package/dist/queueManager.d.ts +23 -0
- package/dist/queueManager.d.ts.map +1 -0
- package/dist/queueManager.js +96 -0
- package/dist/queueManager.js.map +1 -0
- package/dist/run-flow.d.ts +8 -0
- package/dist/run-flow.d.ts.map +1 -0
- package/dist/run-flow.js +220 -0
- package/dist/run-flow.js.map +1 -0
- package/dist/scraper.d.ts +2 -0
- package/dist/scraper.d.ts.map +1 -0
- package/dist/scraper.js +75 -0
- package/dist/scraper.js.map +1 -0
- package/dist/scraper_endpoints.d.ts +2 -0
- package/dist/scraper_endpoints.d.ts.map +1 -0
- package/dist/scraper_endpoints.js +40 -0
- package/dist/scraper_endpoints.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +528 -0
- package/dist/server.js.map +1 -0
- package/dist/services/ModelContext.d.ts +7 -0
- package/dist/services/ModelContext.d.ts.map +1 -0
- package/dist/services/ModelContext.js +7 -0
- package/dist/services/ModelContext.js.map +1 -0
- package/dist/services/agenticUiPlanner.d.ts +27 -0
- package/dist/services/agenticUiPlanner.d.ts.map +1 -0
- package/dist/services/agenticUiPlanner.js +161 -0
- package/dist/services/agenticUiPlanner.js.map +1 -0
- package/dist/services/apiKeyService.d.ts +3 -0
- package/dist/services/apiKeyService.d.ts.map +1 -0
- package/dist/services/apiKeyService.js +7 -0
- package/dist/services/apiKeyService.js.map +1 -0
- package/dist/services/audioService.d.ts +10 -0
- package/dist/services/audioService.d.ts.map +1 -0
- package/dist/services/audioService.js +140 -0
- package/dist/services/audioService.js.map +1 -0
- package/dist/services/autoPromptOptimizer.d.ts +44 -0
- package/dist/services/autoPromptOptimizer.d.ts.map +1 -0
- package/dist/services/autoPromptOptimizer.js +344 -0
- package/dist/services/autoPromptOptimizer.js.map +1 -0
- package/dist/services/autoPromptOptimizer.manual-test.d.ts +2 -0
- package/dist/services/autoPromptOptimizer.manual-test.d.ts.map +1 -0
- package/dist/services/autoPromptOptimizer.manual-test.js +27 -0
- package/dist/services/autoPromptOptimizer.manual-test.js.map +1 -0
- package/dist/services/chainExecutor.d.ts +26 -0
- package/dist/services/chainExecutor.d.ts.map +1 -0
- package/dist/services/chainExecutor.js +399 -0
- package/dist/services/chainExecutor.js.map +1 -0
- package/dist/services/classifyImageQuestion.d.ts +55 -0
- package/dist/services/classifyImageQuestion.d.ts.map +1 -0
- package/dist/services/classifyImageQuestion.js +428 -0
- package/dist/services/classifyImageQuestion.js.map +1 -0
- package/dist/services/configuration/executor.d.ts +3 -0
- package/dist/services/configuration/executor.d.ts.map +1 -0
- package/dist/services/configuration/executor.js +795 -0
- package/dist/services/configuration/executor.js.map +1 -0
- package/dist/services/error.d.ts +13 -0
- package/dist/services/error.d.ts.map +1 -0
- package/dist/services/error.js +34 -0
- package/dist/services/error.js.map +1 -0
- package/dist/services/executor.d.ts +11 -0
- package/dist/services/executor.d.ts.map +1 -0
- package/dist/services/executor.js +1587 -0
- package/dist/services/executor.js.map +1 -0
- package/dist/services/extractPdf.d.ts +26 -0
- package/dist/services/extractPdf.d.ts.map +1 -0
- package/dist/services/extractPdf.js +256 -0
- package/dist/services/extractPdf.js.map +1 -0
- package/dist/services/generateJsTransformFromPrompt.d.ts +11 -0
- package/dist/services/generateJsTransformFromPrompt.d.ts.map +1 -0
- package/dist/services/generateJsTransformFromPrompt.js +328 -0
- package/dist/services/generateJsTransformFromPrompt.js.map +1 -0
- package/dist/services/localizeFirebaseMedia.d.ts +20 -0
- package/dist/services/localizeFirebaseMedia.d.ts.map +1 -0
- package/dist/services/localizeFirebaseMedia.js +135 -0
- package/dist/services/localizeFirebaseMedia.js.map +1 -0
- package/dist/services/polyfill_canvas.d.ts +2 -0
- package/dist/services/polyfill_canvas.d.ts.map +1 -0
- package/dist/services/polyfill_canvas.js +19 -0
- package/dist/services/polyfill_canvas.js.map +1 -0
- package/dist/services/promptRoutes.d.ts +7 -0
- package/dist/services/promptRoutes.d.ts.map +1 -0
- package/dist/services/promptRoutes.js +70 -0
- package/dist/services/promptRoutes.js.map +1 -0
- package/dist/services/runPrompt.d.ts +29 -0
- package/dist/services/runPrompt.d.ts.map +1 -0
- package/dist/services/runPrompt.js +232 -0
- package/dist/services/runPrompt.js.map +1 -0
- package/dist/services/schemaInference.d.ts +2 -0
- package/dist/services/schemaInference.d.ts.map +1 -0
- package/dist/services/schemaInference.js +17 -0
- package/dist/services/schemaInference.js.map +1 -0
- package/dist/services/self-learning/api.d.ts +2 -0
- package/dist/services/self-learning/api.d.ts.map +1 -0
- package/dist/services/self-learning/api.js +84 -0
- package/dist/services/self-learning/api.js.map +1 -0
- package/dist/services/self-learning/autolearn.d.ts +23 -0
- package/dist/services/self-learning/autolearn.d.ts.map +1 -0
- package/dist/services/self-learning/autolearn.js +308 -0
- package/dist/services/self-learning/autolearn.js.map +1 -0
- package/dist/services/self-learning/discover.d.ts +11 -0
- package/dist/services/self-learning/discover.d.ts.map +1 -0
- package/dist/services/self-learning/discover.js +446 -0
- package/dist/services/self-learning/discover.js.map +1 -0
- package/dist/services/self-learning/image.d.ts +10 -0
- package/dist/services/self-learning/image.d.ts.map +1 -0
- package/dist/services/self-learning/image.js +38 -0
- package/dist/services/self-learning/image.js.map +1 -0
- package/dist/services/self-learning/injest.d.ts +25 -0
- package/dist/services/self-learning/injest.d.ts.map +1 -0
- package/dist/services/self-learning/injest.js +110 -0
- package/dist/services/self-learning/injest.js.map +1 -0
- package/dist/services/self-learning/learn.d.ts +2 -0
- package/dist/services/self-learning/learn.d.ts.map +1 -0
- package/dist/services/self-learning/learn.js +145 -0
- package/dist/services/self-learning/learn.js.map +1 -0
- package/dist/services/self-learning/matcher.d.ts +2 -0
- package/dist/services/self-learning/matcher.d.ts.map +1 -0
- package/dist/services/self-learning/matcher.js +38 -0
- package/dist/services/self-learning/matcher.js.map +1 -0
- package/dist/services/self-learning/openai.d.ts +8 -0
- package/dist/services/self-learning/openai.d.ts.map +1 -0
- package/dist/services/self-learning/openai.js +97 -0
- package/dist/services/self-learning/openai.js.map +1 -0
- package/dist/services/self-learning/phash.d.ts +5 -0
- package/dist/services/self-learning/phash.d.ts.map +1 -0
- package/dist/services/self-learning/phash.js +68 -0
- package/dist/services/self-learning/phash.js.map +1 -0
- package/dist/services/self-learning/recognize.d.ts +17 -0
- package/dist/services/self-learning/recognize.d.ts.map +1 -0
- package/dist/services/self-learning/recognize.js +116 -0
- package/dist/services/self-learning/recognize.js.map +1 -0
- package/dist/services/self-learning/record_transition.d.ts +8 -0
- package/dist/services/self-learning/record_transition.d.ts.map +1 -0
- package/dist/services/self-learning/record_transition.js +20 -0
- package/dist/services/self-learning/record_transition.js.map +1 -0
- package/dist/services/self-learning/registry.d.ts +4 -0
- package/dist/services/self-learning/registry.d.ts.map +1 -0
- package/dist/services/self-learning/registry.js +19 -0
- package/dist/services/self-learning/registry.js.map +1 -0
- package/dist/services/self-learning/schema.d.ts +114 -0
- package/dist/services/self-learning/schema.d.ts.map +1 -0
- package/dist/services/self-learning/schema.js +70 -0
- package/dist/services/self-learning/schema.js.map +1 -0
- package/dist/services/self-learning/schemaStrictify.d.ts +2 -0
- package/dist/services/self-learning/schemaStrictify.d.ts.map +1 -0
- package/dist/services/self-learning/schemaStrictify.js +34 -0
- package/dist/services/self-learning/schemaStrictify.js.map +1 -0
- package/dist/services/self-learning/transition_graph.d.ts +6 -0
- package/dist/services/self-learning/transition_graph.d.ts.map +1 -0
- package/dist/services/self-learning/transition_graph.js +83 -0
- package/dist/services/self-learning/transition_graph.js.map +1 -0
- package/dist/services/self-learning/transition_log.d.ts +3 -0
- package/dist/services/self-learning/transition_log.d.ts.map +1 -0
- package/dist/services/self-learning/transition_log.js +42 -0
- package/dist/services/self-learning/transition_log.js.map +1 -0
- package/dist/services/self-learning/util.d.ts +3 -0
- package/dist/services/self-learning/util.d.ts.map +1 -0
- package/dist/services/self-learning/util.js +11 -0
- package/dist/services/self-learning/util.js.map +1 -0
- package/dist/services/stepByStepAiPlanner.d.ts +39 -0
- package/dist/services/stepByStepAiPlanner.d.ts.map +1 -0
- package/dist/services/stepByStepAiPlanner.js +379 -0
- package/dist/services/stepByStepAiPlanner.js.map +1 -0
- package/dist/services/test-genjs.js +39 -0
- package/dist/services/test-genjs.manual-test.d.ts +2 -0
- package/dist/services/test-genjs.manual-test.d.ts.map +1 -0
- package/dist/services/test-genjs.manual-test.js +40 -0
- package/dist/services/test-genjs.manual-test.js.map +1 -0
- package/dist/services/uiMapPathFinder.d.ts +13 -0
- package/dist/services/uiMapPathFinder.d.ts.map +1 -0
- package/dist/services/uiMapPathFinder.js +79 -0
- package/dist/services/uiMapPathFinder.js.map +1 -0
- package/dist/services/uiMapService.d.ts +26 -0
- package/dist/services/uiMapService.d.ts.map +1 -0
- package/dist/services/uiMapService.js +275 -0
- package/dist/services/uiMapService.js.map +1 -0
- package/dist/services/uiPlanner.d.ts +54 -0
- package/dist/services/uiPlanner.d.ts.map +1 -0
- package/dist/services/uiPlanner.js +558 -0
- package/dist/services/uiPlanner.js.map +1 -0
- package/dist/services/utilityFunctions.d.ts +80 -0
- package/dist/services/utilityFunctions.d.ts.map +1 -0
- package/dist/services/utilityFunctions.js +352 -0
- package/dist/services/utilityFunctions.js.map +1 -0
- package/dist/services/variableGenerator.d.ts +39 -0
- package/dist/services/variableGenerator.d.ts.map +1 -0
- package/dist/services/variableGenerator.js +157 -0
- package/dist/services/variableGenerator.js.map +1 -0
- package/dist/services/workflow/build-workflow.d.ts +49 -0
- package/dist/services/workflow/build-workflow.d.ts.map +1 -0
- package/dist/services/workflow/build-workflow.js +119 -0
- package/dist/services/workflow/build-workflow.js.map +1 -0
- package/dist/standardRoutes.d.ts +2 -0
- package/dist/standardRoutes.d.ts.map +1 -0
- package/dist/standardRoutes.js +1495 -0
- package/dist/standardRoutes.js.map +1 -0
- package/dist/stepWorkflowRoutes.d.ts +2 -0
- package/dist/stepWorkflowRoutes.d.ts.map +1 -0
- package/dist/stepWorkflowRoutes.js +1007 -0
- package/dist/stepWorkflowRoutes.js.map +1 -0
- package/dist/storage.d.ts +19 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.docker.json +61 -0
- package/dist/storage.js +131 -0
- package/dist/storage.js.map +1 -0
- package/dist/storage.json +78 -0
- package/dist/storage_cache/boxes.json +48 -0
- package/dist/storage_cache/suno_state.json +3 -0
- package/dist/suno_download.d.ts +11 -0
- package/dist/suno_download.d.ts.map +1 -0
- package/dist/suno_download.js +33 -0
- package/dist/suno_download.js.map +1 -0
- package/dist/suno_download.py +119 -0
- package/dist/test-web-element-requests.d.ts +6 -0
- package/dist/test-web-element-requests.d.ts.map +1 -0
- package/dist/test-web-element-requests.js +114 -0
- package/dist/test-web-element-requests.js.map +1 -0
- package/dist/test_pdf_render.d.ts +2 -0
- package/dist/test_pdf_render.d.ts.map +1 -0
- package/dist/test_pdf_render.js +50 -0
- package/dist/test_pdf_render.js.map +1 -0
- package/dist/training_data_viewer_endpoints.d.ts +2 -0
- package/dist/training_data_viewer_endpoints.d.ts.map +1 -0
- package/dist/training_data_viewer_endpoints.js +141 -0
- package/dist/training_data_viewer_endpoints.js.map +1 -0
- package/dist/utils.d.ts +353 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +1517 -0
- package/dist/utils.js.map +1 -0
- package/dist/vm-h100.env.template +55 -0
- package/dist/web-element-requests.d.ts +102 -0
- package/dist/web-element-requests.d.ts.map +1 -0
- package/dist/web-element-requests.js +278 -0
- package/dist/web-element-requests.js.map +1 -0
- package/dist/workflowRoutes.d.ts +2 -0
- package/dist/workflowRoutes.d.ts.map +1 -0
- package/dist/workflowRoutes.js +441 -0
- package/dist/workflowRoutes.js.map +1 -0
- package/package.json +109 -0
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { authenticate } from '@google-cloud/local-auth';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import cors from 'cors';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import { extractPdf } from '../services/extractPdf.js';
|
|
10
|
+
import { ensureDir, get } from '../utils.js';
|
|
11
|
+
import { KEYS } from '../constants.js';
|
|
12
|
+
// ES module equivalent of __dirname
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
// Gmail API scopes
|
|
16
|
+
const DEFAULT_SCOPES = [
|
|
17
|
+
'https://www.googleapis.com/auth/gmail.readonly',
|
|
18
|
+
'https://www.googleapis.com/auth/gmail.modify',
|
|
19
|
+
'https://www.googleapis.com/auth/gmail.labels'
|
|
20
|
+
];
|
|
21
|
+
const TEXT_MIME_PREFIXES = ['text/', 'application/json', 'application/xml'];
|
|
22
|
+
const TEXT_EXTENSIONS = ['.txt', '.md', '.csv', '.json', '.xml', '.log'];
|
|
23
|
+
class FilterGmailPoller {
|
|
24
|
+
static instance = null;
|
|
25
|
+
constructor(configPath = './config.json') {
|
|
26
|
+
this.configPath = configPath;
|
|
27
|
+
this.config = null;
|
|
28
|
+
this.gmail = null;
|
|
29
|
+
this.isAuthenticated = false;
|
|
30
|
+
this.pollingInterval = null;
|
|
31
|
+
this.emails = []; // In-memory storage for filtered emails
|
|
32
|
+
this.app = null;
|
|
33
|
+
this.server = null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Load configuration from JSON file
|
|
37
|
+
*/
|
|
38
|
+
async loadConfig() {
|
|
39
|
+
try {
|
|
40
|
+
const configData = await fs.readFile(this.configPath, 'utf8');
|
|
41
|
+
this.config = JSON.parse(configData);
|
|
42
|
+
console.log('โ
Configuration loaded successfully');
|
|
43
|
+
return this.config;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('โ Error loading configuration:', error);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Authenticate with Gmail API
|
|
52
|
+
*/
|
|
53
|
+
async authenticate() {
|
|
54
|
+
try {
|
|
55
|
+
const credentialsPath = path.resolve(__dirname, this.config.gmail.credentialsPath);
|
|
56
|
+
const auth = await authenticate({
|
|
57
|
+
keyfilePath: credentialsPath,
|
|
58
|
+
scopes: this.config.gmail.scopes || DEFAULT_SCOPES,
|
|
59
|
+
});
|
|
60
|
+
this.gmail = google.gmail({ version: 'v1', auth });
|
|
61
|
+
this.isAuthenticated = true;
|
|
62
|
+
console.log('โ
Gmail authentication successful');
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error('โ Gmail authentication failed:', error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if email subject starts with the configured prefix
|
|
71
|
+
*/
|
|
72
|
+
matchesFilter(email) {
|
|
73
|
+
const subject = this.getEmailSubject(email);
|
|
74
|
+
const prefix = this.config.filter.subjectPrefix;
|
|
75
|
+
return subject && subject.startsWith(prefix);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Extract and decode email body from Gmail message
|
|
79
|
+
* @param {Object} email - Gmail message object
|
|
80
|
+
* @param {string} [preferredType='text/plain'] - Preferred MIME type ('text/plain' or 'text/html')
|
|
81
|
+
* @returns {string} - Decoded email body as string
|
|
82
|
+
*/
|
|
83
|
+
extractEmailBody(email, preferredType = 'text/plain') {
|
|
84
|
+
if (!email || !email.payload) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
// Function to recursively find body in message parts
|
|
89
|
+
const findBodyInParts = (parts, preferredType) => {
|
|
90
|
+
for (const part of parts) {
|
|
91
|
+
// Check if this part has the body we want
|
|
92
|
+
if (part.mimeType === preferredType && part.body && part.body.data) {
|
|
93
|
+
return part.body.data;
|
|
94
|
+
}
|
|
95
|
+
// If this part has sub-parts, search recursively
|
|
96
|
+
if (part.parts && part.parts.length > 0) {
|
|
97
|
+
const found = findBodyInParts(part.parts, preferredType);
|
|
98
|
+
if (found)
|
|
99
|
+
return found;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
};
|
|
104
|
+
let bodyData = null;
|
|
105
|
+
// First try to find the preferred type
|
|
106
|
+
if (email.payload.parts && email.payload.parts.length > 0) {
|
|
107
|
+
bodyData = findBodyInParts(email.payload.parts, preferredType);
|
|
108
|
+
}
|
|
109
|
+
// If preferred type not found and we want plain text, try HTML as fallback
|
|
110
|
+
if (!bodyData && preferredType === 'text/plain') {
|
|
111
|
+
if (email.payload.parts && email.payload.parts.length > 0) {
|
|
112
|
+
bodyData = findBodyInParts(email.payload.parts, 'text/html');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// If still not found, check the root payload body
|
|
116
|
+
if (!bodyData && email.payload.body && email.payload.body.data) {
|
|
117
|
+
bodyData = email.payload.body.data;
|
|
118
|
+
}
|
|
119
|
+
// If we found body data, decode it
|
|
120
|
+
if (bodyData) {
|
|
121
|
+
// Gmail uses URL-safe base64, convert to regular base64 first
|
|
122
|
+
const regularBase64 = bodyData.replace(/-/g, '+').replace(/_/g, '/');
|
|
123
|
+
// Decode from base64 to string
|
|
124
|
+
const decodedBody = Buffer.from(regularBase64, 'base64').toString('utf-8');
|
|
125
|
+
return decodedBody;
|
|
126
|
+
}
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.error('โ Error extracting email body:', error);
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Extract subject from email headers
|
|
136
|
+
*/
|
|
137
|
+
getEmailSubject(email) {
|
|
138
|
+
if (!email.payload || !email.payload.headers)
|
|
139
|
+
return '';
|
|
140
|
+
const subjectHeader = email.payload.headers.find(header => header.name.toLowerCase() === 'subject');
|
|
141
|
+
return subjectHeader ? subjectHeader.value : '';
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Resolve a Gmail label ID by its display name
|
|
145
|
+
*/
|
|
146
|
+
async getLabelIdByName(labelName) {
|
|
147
|
+
if (!labelName) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const labels = await this.getLabels();
|
|
151
|
+
const lower = labelName.toLowerCase();
|
|
152
|
+
const match = labels.find(label => label.name.toLowerCase() === lower);
|
|
153
|
+
if (!match) {
|
|
154
|
+
console.warn(`โ ๏ธ Label not found: ${labelName}`);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return match.id;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Determine which label IDs to use when querying Gmail
|
|
161
|
+
*/
|
|
162
|
+
async getFilterLabelIds() {
|
|
163
|
+
if (!this.config || !this.config.filter) {
|
|
164
|
+
return ['INBOX'];
|
|
165
|
+
}
|
|
166
|
+
const { labelIds, labelNames, labelName } = this.config.filter;
|
|
167
|
+
if (Array.isArray(labelIds) && labelIds.length > 0) {
|
|
168
|
+
return labelIds;
|
|
169
|
+
}
|
|
170
|
+
const names = [];
|
|
171
|
+
if (labelName) {
|
|
172
|
+
names.push(labelName);
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(labelNames)) {
|
|
175
|
+
names.push(...labelNames);
|
|
176
|
+
}
|
|
177
|
+
if (names.length === 0) {
|
|
178
|
+
return ['INBOX'];
|
|
179
|
+
}
|
|
180
|
+
const availableLabels = await this.getLabels();
|
|
181
|
+
const lookup = new Map(availableLabels.map(label => [label.name.toLowerCase(), label.id]));
|
|
182
|
+
const resolved = names
|
|
183
|
+
.map(name => {
|
|
184
|
+
const id = lookup.get(name.toLowerCase());
|
|
185
|
+
if (!id) {
|
|
186
|
+
console.warn(`โ ๏ธ Configured label "${name}" not found in Gmail`);
|
|
187
|
+
}
|
|
188
|
+
return id;
|
|
189
|
+
})
|
|
190
|
+
.filter(Boolean);
|
|
191
|
+
return resolved.length > 0 ? resolved : ['INBOX'];
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get all labels
|
|
195
|
+
*/
|
|
196
|
+
async getLabels() {
|
|
197
|
+
if (!this.isAuthenticated) {
|
|
198
|
+
throw new Error('Not authenticated with Gmail');
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const response = await this.gmail.users.labels.list({
|
|
202
|
+
userId: 'me'
|
|
203
|
+
});
|
|
204
|
+
return response.data.labels || [];
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.error('โ Error getting labels:', error);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Create new label/folder
|
|
213
|
+
*/
|
|
214
|
+
async createLabel(labelName) {
|
|
215
|
+
if (!this.isAuthenticated) {
|
|
216
|
+
throw new Error('Not authenticated with Gmail');
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
// Add human delay
|
|
220
|
+
await this.delay(this.getHumanDelay());
|
|
221
|
+
const response = await this.gmail.users.labels.create({
|
|
222
|
+
userId: 'me',
|
|
223
|
+
requestBody: {
|
|
224
|
+
name: labelName,
|
|
225
|
+
labelListVisibility: 'labelShow',
|
|
226
|
+
messageListVisibility: 'show'
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
console.log(`๐ Created label: ${labelName}`);
|
|
230
|
+
return response.data;
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
console.error('โ Error creating label:', error);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Find or create label
|
|
239
|
+
*/
|
|
240
|
+
async findOrCreateLabel(labelName) {
|
|
241
|
+
const labels = await this.getLabels();
|
|
242
|
+
const existingLabel = labels.find(label => label.name === labelName);
|
|
243
|
+
if (existingLabel) {
|
|
244
|
+
return existingLabel;
|
|
245
|
+
}
|
|
246
|
+
return await this.createLabel(labelName);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get human-like delay
|
|
250
|
+
*/
|
|
251
|
+
getHumanDelay() {
|
|
252
|
+
const min = 1000; // 1 second minimum delay
|
|
253
|
+
const max = 3000; // 3 seconds maximum delay
|
|
254
|
+
return Math.random() * (max - min) + min;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get full email details
|
|
258
|
+
*/
|
|
259
|
+
async getEmailDetails(messageId) {
|
|
260
|
+
if (!this.isAuthenticated) {
|
|
261
|
+
throw new Error('Not authenticated with Gmail');
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
// Add human delay
|
|
265
|
+
await this.delay(this.getHumanDelay());
|
|
266
|
+
const response = await this.gmail.users.messages.get({
|
|
267
|
+
userId: 'me',
|
|
268
|
+
id: messageId,
|
|
269
|
+
format: 'full'
|
|
270
|
+
});
|
|
271
|
+
const enriched = await this.includeAttachmentsInBody(response.data);
|
|
272
|
+
return enriched;
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
console.error('โ Error getting email details:', error);
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Move email to different label/folder
|
|
281
|
+
*/
|
|
282
|
+
async moveEmail(messageId, targetLabelId, removeLabelIds = []) {
|
|
283
|
+
if (!this.isAuthenticated) {
|
|
284
|
+
throw new Error('Not authenticated with Gmail');
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
// Add human delay
|
|
288
|
+
await this.delay(this.getHumanDelay());
|
|
289
|
+
const response = await this.gmail.users.messages.modify({
|
|
290
|
+
userId: 'me',
|
|
291
|
+
id: messageId,
|
|
292
|
+
requestBody: {
|
|
293
|
+
addLabelIds: [targetLabelId],
|
|
294
|
+
removeLabelIds: removeLabelIds
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
console.log(`๐ฆ Moved email ${messageId} to label ${targetLabelId}`);
|
|
298
|
+
return response.data;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
console.error('โ Error moving email:', error);
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Utility delay function
|
|
307
|
+
*/
|
|
308
|
+
delay(ms) {
|
|
309
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Ensure a folder/label exists by name
|
|
313
|
+
* Returns the label object (existing or newly created)
|
|
314
|
+
*/
|
|
315
|
+
async ensureFolderExists(folderName) {
|
|
316
|
+
if (!this.isAuthenticated) {
|
|
317
|
+
console.log('๐ Not authenticated, authenticating now...');
|
|
318
|
+
await this.authenticate();
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const label = await this.findOrCreateLabel(folderName);
|
|
322
|
+
console.log(`๐ Folder "${folderName}" is ready (ID: ${label.id})`);
|
|
323
|
+
return label;
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
console.error(`โ Error ensuring folder "${folderName}" exists:`, error);
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Move an email to a specific folder by name
|
|
332
|
+
* Automatically ensures the folder exists before moving
|
|
333
|
+
*/
|
|
334
|
+
async moveEmailToFolder(messageId, folderName, removeFromInbox = true) {
|
|
335
|
+
if (!this.isAuthenticated) {
|
|
336
|
+
console.log('๐ Not authenticated, authenticating now...');
|
|
337
|
+
await this.authenticate();
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
// Ensure the folder exists
|
|
341
|
+
const targetLabel = await this.ensureFolderExists(folderName);
|
|
342
|
+
// Prepare label IDs to remove (typically INBOX if requested)
|
|
343
|
+
const removeLabelIds = removeFromInbox ? ['INBOX'] : [];
|
|
344
|
+
// Move the email
|
|
345
|
+
const result = await this.moveEmail(messageId, targetLabel.id, removeLabelIds);
|
|
346
|
+
console.log(`๐ฆ Moved email ${messageId} to folder "${folderName}"`);
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error(`โ Error moving email ${messageId} to folder "${folderName}":`, error);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Step through emails manually with pagination
|
|
356
|
+
* Ensures authentication and allows controlled processing of emails
|
|
357
|
+
* Honors the configured filter (subject prefix)
|
|
358
|
+
*/
|
|
359
|
+
async stepThroughEmails(options = {}) {
|
|
360
|
+
// Ensure authentication
|
|
361
|
+
if (!this.isAuthenticated) {
|
|
362
|
+
console.log('๐ Not authenticated, authenticating now...');
|
|
363
|
+
await this.authenticate();
|
|
364
|
+
}
|
|
365
|
+
const { query = '', labelIds = null, folderName = null, maxResults = 10, pageToken = null, includeDetails = true } = options;
|
|
366
|
+
let effectiveLabelIds = labelIds;
|
|
367
|
+
if (!effectiveLabelIds || effectiveLabelIds.length === 0) {
|
|
368
|
+
if (folderName) {
|
|
369
|
+
const labelId = await this.getLabelIdByName(folderName);
|
|
370
|
+
if (!labelId) {
|
|
371
|
+
throw new Error(`Label "${folderName}" not found`);
|
|
372
|
+
}
|
|
373
|
+
effectiveLabelIds = [labelId];
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
effectiveLabelIds = await this.getFilterLabelIds();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
// Get message list with pagination
|
|
381
|
+
const listOptions = {
|
|
382
|
+
userId: 'me',
|
|
383
|
+
q: query,
|
|
384
|
+
maxResults: maxResults,
|
|
385
|
+
pageToken: pageToken
|
|
386
|
+
};
|
|
387
|
+
if (effectiveLabelIds && effectiveLabelIds.length > 0) {
|
|
388
|
+
listOptions.labelIds = effectiveLabelIds;
|
|
389
|
+
}
|
|
390
|
+
const listResponse = await this.gmail.users.messages.list(listOptions);
|
|
391
|
+
const messages = listResponse.data.messages || [];
|
|
392
|
+
console.log(`๐ง Found ${messages.length} messages (page token: ${listResponse.data.nextPageToken || 'none'})`);
|
|
393
|
+
let emailDetails = [];
|
|
394
|
+
let filteredCount = 0;
|
|
395
|
+
if (includeDetails && messages.length > 0) {
|
|
396
|
+
// Get full details for each message and apply filter
|
|
397
|
+
for (const message of messages) {
|
|
398
|
+
const details = await this.getEmailDetails(message.id);
|
|
399
|
+
// Check if email matches our filter
|
|
400
|
+
if (this.matchesFilter(details)) {
|
|
401
|
+
emailDetails.push(details);
|
|
402
|
+
filteredCount++;
|
|
403
|
+
if (this.config.logging.enableConsole) {
|
|
404
|
+
console.log(`โ
Filtered email matches: ${this.getEmailSubject(details)}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
if (this.config.logging.enableConsole) {
|
|
409
|
+
console.log(`โ Filtered email doesn't match: ${this.getEmailSubject(details)}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
console.log(`๐ฏ After filtering: ${filteredCount} emails match the prefix "${this.config.filter.subjectPrefix}"`);
|
|
415
|
+
return {
|
|
416
|
+
messages: includeDetails ? emailDetails : messages, // Note: if includeDetails=false, we can't filter
|
|
417
|
+
nextPageToken: listResponse.data.nextPageToken,
|
|
418
|
+
hasMore: !!listResponse.data.nextPageToken,
|
|
419
|
+
totalMessages: messages.length,
|
|
420
|
+
filteredMessages: filteredCount,
|
|
421
|
+
subjectPrefix: this.config.filter.subjectPrefix
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.error('โ Error stepping through emails:', error);
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Add email to memory (keeping only the most recent N emails)
|
|
431
|
+
*/
|
|
432
|
+
addEmailToMemory(email) {
|
|
433
|
+
// Add timestamp for sorting
|
|
434
|
+
const emailWithTimestamp = {
|
|
435
|
+
...email,
|
|
436
|
+
filteredAt: new Date().toISOString(),
|
|
437
|
+
subject: this.getEmailSubject(email)
|
|
438
|
+
};
|
|
439
|
+
// Add to beginning of array (most recent first)
|
|
440
|
+
this.emails.unshift(emailWithTimestamp);
|
|
441
|
+
// Keep only the configured maximum number of emails
|
|
442
|
+
if (this.emails.length > this.config.filter.maxEmailsInMemory) {
|
|
443
|
+
this.emails = this.emails.slice(0, this.config.filter.maxEmailsInMemory);
|
|
444
|
+
}
|
|
445
|
+
if (this.config.logging.enableConsole) {
|
|
446
|
+
console.log(`๐ง Added filtered email: ${emailWithTimestamp.subject}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Poll for new emails and filter them
|
|
451
|
+
*/
|
|
452
|
+
async pollAndFilter() {
|
|
453
|
+
if (!this.isAuthenticated) {
|
|
454
|
+
throw new Error('Not authenticated with Gmail');
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const labelIds = await this.getFilterLabelIds();
|
|
458
|
+
// Poll for recent emails (last 24 hours to avoid too much data)
|
|
459
|
+
const query = `newer_than:1d`;
|
|
460
|
+
const listOptions = {
|
|
461
|
+
userId: 'me',
|
|
462
|
+
q: query,
|
|
463
|
+
maxResults: 50
|
|
464
|
+
};
|
|
465
|
+
if (labelIds && labelIds.length > 0) {
|
|
466
|
+
listOptions.labelIds = labelIds;
|
|
467
|
+
}
|
|
468
|
+
const response = await this.gmail.users.messages.list(listOptions);
|
|
469
|
+
const messages = response.data.messages || [];
|
|
470
|
+
if (this.config.logging.enableConsole) {
|
|
471
|
+
console.log(`๐ง Found ${messages.length} recent messages`);
|
|
472
|
+
}
|
|
473
|
+
// Process each message
|
|
474
|
+
for (const message of messages) {
|
|
475
|
+
try {
|
|
476
|
+
const emailDetails = await this.gmail.users.messages.get({
|
|
477
|
+
userId: 'me',
|
|
478
|
+
id: message.id,
|
|
479
|
+
format: 'full'
|
|
480
|
+
});
|
|
481
|
+
// Check if email matches our filter
|
|
482
|
+
if (this.matchesFilter(emailDetails.data)) {
|
|
483
|
+
this.addEmailToMemory(emailDetails.data);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
console.error(`โ Error processing message ${message.id}:`, error);
|
|
488
|
+
}
|
|
489
|
+
// Small delay between processing emails
|
|
490
|
+
await this.delay(100);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
console.error('โ Error polling emails:', error);
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Start the polling process
|
|
500
|
+
*/
|
|
501
|
+
async startPolling() {
|
|
502
|
+
// Authenticate on first polling start if not already authenticated
|
|
503
|
+
if (!this.isAuthenticated) {
|
|
504
|
+
console.log('๐ Authenticating with Gmail...');
|
|
505
|
+
await this.authenticateOnce();
|
|
506
|
+
}
|
|
507
|
+
console.log('๐ Starting Filter Gmail Poller...');
|
|
508
|
+
console.log(`๐ง Filtering emails with prefix: "${this.config.filter.subjectPrefix}"`);
|
|
509
|
+
console.log(`โฐ Polling interval: ${this.config.filter.pollingIntervalMs}ms`);
|
|
510
|
+
console.log(`๐พ Max emails in memory: ${this.config.filter.maxEmailsInMemory}`);
|
|
511
|
+
const poll = async () => {
|
|
512
|
+
try {
|
|
513
|
+
await this.pollAndFilter();
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
console.error('โ Polling error:', error);
|
|
517
|
+
}
|
|
518
|
+
// Schedule next poll
|
|
519
|
+
this.pollingInterval = setTimeout(poll, this.config.filter.pollingIntervalMs);
|
|
520
|
+
};
|
|
521
|
+
// Start polling immediately
|
|
522
|
+
await poll();
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Stop the polling process
|
|
526
|
+
*/
|
|
527
|
+
stopPolling() {
|
|
528
|
+
if (this.pollingInterval) {
|
|
529
|
+
clearTimeout(this.pollingInterval);
|
|
530
|
+
this.pollingInterval = null;
|
|
531
|
+
console.log('โน๏ธ Polling stopped');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Start the API server or integrate with existing Express app
|
|
536
|
+
* @param {Object} [existingApp] - Optional existing Express app to integrate with
|
|
537
|
+
*/
|
|
538
|
+
async startAPIServer(existingApp = null) {
|
|
539
|
+
// Use existing app or create new one
|
|
540
|
+
this.app = existingApp || express();
|
|
541
|
+
// Only add middleware if we created the app (to avoid conflicts)
|
|
542
|
+
if (!existingApp) {
|
|
543
|
+
this.app.use(cors());
|
|
544
|
+
this.app.use(express.json());
|
|
545
|
+
}
|
|
546
|
+
console.log('๐ง Setting up API routes...');
|
|
547
|
+
// API Routes
|
|
548
|
+
this.app.get('/api/emails', (req, res) => {
|
|
549
|
+
try {
|
|
550
|
+
const limit = parseInt(req.query.limit) || this.config.filter.maxEmailsInMemory;
|
|
551
|
+
const emails = this.emails.slice(0, limit);
|
|
552
|
+
res.json({
|
|
553
|
+
success: true,
|
|
554
|
+
count: emails.length,
|
|
555
|
+
emails: emails,
|
|
556
|
+
config: {
|
|
557
|
+
subjectPrefix: this.config.filter.subjectPrefix,
|
|
558
|
+
maxEmailsInMemory: this.config.filter.maxEmailsInMemory
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
res.status(500).json({
|
|
564
|
+
success: false,
|
|
565
|
+
error: error.message
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
this.app.get('/api/emails/:id', (req, res) => {
|
|
570
|
+
try {
|
|
571
|
+
const email = this.emails.find(e => e.id === req.params.id);
|
|
572
|
+
if (!email) {
|
|
573
|
+
return res.status(404).json({
|
|
574
|
+
success: false,
|
|
575
|
+
error: 'Email not found'
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
res.json({
|
|
579
|
+
success: true,
|
|
580
|
+
email: email
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
res.status(500).json({
|
|
585
|
+
success: false,
|
|
586
|
+
error: error.message
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
this.app.get('/api/stats', (req, res) => {
|
|
591
|
+
res.json({
|
|
592
|
+
success: true,
|
|
593
|
+
stats: {
|
|
594
|
+
totalEmails: this.emails.length,
|
|
595
|
+
maxEmails: this.config.filter.maxEmailsInMemory,
|
|
596
|
+
subjectPrefix: this.config.filter.subjectPrefix,
|
|
597
|
+
pollingInterval: this.config.filter.pollingIntervalMs,
|
|
598
|
+
isPolling: this.pollingInterval !== null,
|
|
599
|
+
lastPoll: new Date().toISOString()
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
this.app.get('/api/health', (req, res) => {
|
|
604
|
+
res.json({
|
|
605
|
+
success: true,
|
|
606
|
+
status: 'healthy',
|
|
607
|
+
timestamp: new Date().toISOString()
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
// GET endpoint to read current configuration
|
|
611
|
+
this.app.get('/api/config', (req, res) => {
|
|
612
|
+
res.json({
|
|
613
|
+
success: true,
|
|
614
|
+
config: this.config,
|
|
615
|
+
timestamp: new Date().toISOString()
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
// POST endpoint to start polling
|
|
619
|
+
this.app.post('/api/polling/start', async (req, res) => {
|
|
620
|
+
try {
|
|
621
|
+
await this.startPolling();
|
|
622
|
+
res.json({
|
|
623
|
+
success: true,
|
|
624
|
+
message: 'Polling started successfully',
|
|
625
|
+
isPolling: true,
|
|
626
|
+
timestamp: new Date().toISOString()
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
res.status(500).json({
|
|
631
|
+
success: false,
|
|
632
|
+
error: error.message
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
// POST endpoint to stop polling
|
|
637
|
+
this.app.post('/api/polling/stop', (req, res) => {
|
|
638
|
+
try {
|
|
639
|
+
this.stopPolling();
|
|
640
|
+
res.json({
|
|
641
|
+
success: true,
|
|
642
|
+
message: 'Polling stopped successfully',
|
|
643
|
+
isPolling: false,
|
|
644
|
+
timestamp: new Date().toISOString()
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
res.status(500).json({
|
|
649
|
+
success: false,
|
|
650
|
+
error: error.message
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
// GET endpoint to check polling status
|
|
655
|
+
this.app.get('/api/polling/status', (req, res) => {
|
|
656
|
+
res.json({
|
|
657
|
+
success: true,
|
|
658
|
+
isPolling: this.pollingInterval !== null,
|
|
659
|
+
isAuthenticated: this.isAuthenticated,
|
|
660
|
+
timestamp: new Date().toISOString()
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
// GET endpoint to check if polling is active (simple boolean response)
|
|
664
|
+
this.app.get('/api/polling', (req, res) => {
|
|
665
|
+
res.json({
|
|
666
|
+
isPolling: this.pollingInterval !== null,
|
|
667
|
+
// return number of emails in memory for convenience
|
|
668
|
+
numEmailsInMemory: this.emails.length
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
// PUT endpoint to update filter configuration
|
|
672
|
+
this.app.put('/api/config/filter', async (req, res) => {
|
|
673
|
+
try {
|
|
674
|
+
const updates = req.body;
|
|
675
|
+
// Validate input
|
|
676
|
+
const allowedFields = ['subjectPrefix', 'maxEmailsInMemory', 'pollingIntervalMs', 'labelIds', 'labelNames', 'labelName'];
|
|
677
|
+
const invalidFields = Object.keys(updates).filter(key => !allowedFields.includes(key));
|
|
678
|
+
if (invalidFields.length > 0) {
|
|
679
|
+
return res.status(400).json({
|
|
680
|
+
success: false,
|
|
681
|
+
error: `Invalid fields: ${invalidFields.join(', ')}. Allowed: ${allowedFields.join(', ')}`
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
// Update configuration
|
|
685
|
+
const result = await this.updateFilterConfig(updates);
|
|
686
|
+
if (result.success) {
|
|
687
|
+
res.json({
|
|
688
|
+
success: true,
|
|
689
|
+
message: 'Filter configuration updated successfully',
|
|
690
|
+
updated: result.updated,
|
|
691
|
+
pollingRestarted: result.pollingRestarted,
|
|
692
|
+
newConfig: result.newConfig
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
res.status(500).json(result);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
res.status(500).json({
|
|
701
|
+
success: false,
|
|
702
|
+
error: error.message
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
// Only start server if we created the app (not using existing app)
|
|
707
|
+
if (!existingApp) {
|
|
708
|
+
return new Promise((resolve, reject) => {
|
|
709
|
+
try {
|
|
710
|
+
this.server = this.app.listen(this.config.api.port, this.config.api.host, () => {
|
|
711
|
+
console.log(`๐ API server started on http://${this.config.api.host}:${this.config.api.port}`);
|
|
712
|
+
console.log(`๐ Available endpoints:`);
|
|
713
|
+
console.log(` GET /api/emails - Get filtered emails`);
|
|
714
|
+
console.log(` GET /api/emails/:id - Get specific email`);
|
|
715
|
+
console.log(` GET /api/stats - Get poller statistics`);
|
|
716
|
+
console.log(` GET /api/health - Health check`);
|
|
717
|
+
console.log(` PUT /api/config/filter - Update filter configuration`);
|
|
718
|
+
resolve();
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
reject(error);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
console.log(`๐ Filter Gmail Poller routes added to existing Express app`);
|
|
728
|
+
console.log(`๐ Available endpoints:`);
|
|
729
|
+
console.log(` GET /api/emails - Get filtered emails`);
|
|
730
|
+
console.log(` GET /api/emails/:id - Get specific email`);
|
|
731
|
+
console.log(` GET /api/stats - Get poller statistics`);
|
|
732
|
+
console.log(` GET /api/health - Health check`);
|
|
733
|
+
console.log(` PUT /api/config/filter - Update filter configuration`);
|
|
734
|
+
return Promise.resolve();
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Stop the API server (only if we created it)
|
|
739
|
+
*/
|
|
740
|
+
stopAPIServer() {
|
|
741
|
+
if (this.server) {
|
|
742
|
+
this.server.close();
|
|
743
|
+
this.server = null;
|
|
744
|
+
console.log('๐ API server stopped');
|
|
745
|
+
}
|
|
746
|
+
else if (this.app) {
|
|
747
|
+
console.log('๐ Filter Gmail Poller routes removed from Express app');
|
|
748
|
+
// Note: We don't remove routes from existing apps as Express doesn't provide
|
|
749
|
+
// a built-in way to remove routes. The routes will remain active.
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Authenticate with Gmail API (only once)
|
|
754
|
+
*/
|
|
755
|
+
async authenticateOnce() {
|
|
756
|
+
if (this.isAuthenticated) {
|
|
757
|
+
console.log('โ
Already authenticated');
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
await this.authenticate();
|
|
762
|
+
}
|
|
763
|
+
catch (error) {
|
|
764
|
+
console.error('โ Authentication failed:', error);
|
|
765
|
+
throw error;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Initialize and start everything (without auto-starting polling)
|
|
770
|
+
* @param {Object} [existingApp] - Optional existing Express app to integrate with
|
|
771
|
+
*/
|
|
772
|
+
async initialize(existingApp = null) {
|
|
773
|
+
try {
|
|
774
|
+
console.log('๐ฏ Initializing Filter Gmail Poller...');
|
|
775
|
+
// Load configuration
|
|
776
|
+
await this.loadConfig();
|
|
777
|
+
// Start API server (pass existing app if provided)
|
|
778
|
+
await this.startAPIServer(existingApp);
|
|
779
|
+
console.log('โ
Filter Gmail Poller initialized successfully!');
|
|
780
|
+
console.log('๐ก Use startPolling() to begin polling (authentication will occur then)');
|
|
781
|
+
// Handle graceful shutdown (only if we created our own server)
|
|
782
|
+
if (!existingApp) {
|
|
783
|
+
process.on('SIGINT', () => {
|
|
784
|
+
console.log('\n๐ Shutting down...');
|
|
785
|
+
this.stopPolling();
|
|
786
|
+
this.stopAPIServer();
|
|
787
|
+
process.exit(0);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
console.error('โ Failed to initialize Filter Gmail Poller:', error);
|
|
793
|
+
throw error;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Initialize and start everything (legacy method - now calls initialize)
|
|
798
|
+
* @param {Object} [existingApp] - Optional existing Express app to integrate with
|
|
799
|
+
*/
|
|
800
|
+
async start(existingApp = null) {
|
|
801
|
+
await this.initialize(existingApp);
|
|
802
|
+
// Optionally start polling immediately for backward compatibility
|
|
803
|
+
await this.startPolling();
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Utility delay function
|
|
807
|
+
*/
|
|
808
|
+
delay(ms) {
|
|
809
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get current emails (for testing/debugging)
|
|
813
|
+
*/
|
|
814
|
+
getEmails() {
|
|
815
|
+
return this.emails;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Update filter configuration parameters
|
|
819
|
+
* @param {Object} updates - Configuration updates
|
|
820
|
+
* @param {string} [updates.subjectPrefix] - New subject prefix
|
|
821
|
+
* @param {number} [updates.maxEmailsInMemory] - New maximum emails in memory
|
|
822
|
+
* @param {number} [updates.pollingIntervalMs] - New polling interval in milliseconds
|
|
823
|
+
*/
|
|
824
|
+
async updateFilterConfig(updates) {
|
|
825
|
+
try {
|
|
826
|
+
const oldConfig = { ...this.config.filter };
|
|
827
|
+
let configChanged = false;
|
|
828
|
+
let pollingRestartNeeded = false;
|
|
829
|
+
// Update subject prefix
|
|
830
|
+
if (updates.subjectPrefix !== undefined) {
|
|
831
|
+
this.config.filter.subjectPrefix = updates.subjectPrefix;
|
|
832
|
+
configChanged = true;
|
|
833
|
+
// Clear existing emails since they may no longer match the new filter
|
|
834
|
+
this.clearEmails();
|
|
835
|
+
console.log(`๐ง Updated subject prefix to: "${updates.subjectPrefix}" (cleared existing emails)`);
|
|
836
|
+
}
|
|
837
|
+
// Update max emails in memory
|
|
838
|
+
if (updates.maxEmailsInMemory !== undefined) {
|
|
839
|
+
const newMax = Math.max(1, updates.maxEmailsInMemory); // Ensure at least 1
|
|
840
|
+
this.config.filter.maxEmailsInMemory = newMax;
|
|
841
|
+
configChanged = true;
|
|
842
|
+
// Trim existing emails if necessary
|
|
843
|
+
if (this.emails.length > newMax) {
|
|
844
|
+
this.emails = this.emails.slice(0, newMax);
|
|
845
|
+
console.log(`๐งน Trimmed emails to ${newMax} (removed ${this.emails.length - newMax} old emails)`);
|
|
846
|
+
}
|
|
847
|
+
console.log(`๐พ Updated max emails in memory to: ${newMax}`);
|
|
848
|
+
}
|
|
849
|
+
// Update label IDs explicitly
|
|
850
|
+
if (updates.labelIds !== undefined) {
|
|
851
|
+
const newLabelIds = Array.isArray(updates.labelIds) ? updates.labelIds : [updates.labelIds];
|
|
852
|
+
this.config.filter.labelIds = newLabelIds.filter(Boolean);
|
|
853
|
+
configChanged = true;
|
|
854
|
+
console.log(`๐ท๏ธ Updated label IDs to: ${this.config.filter.labelIds.join(', ') || 'none'}`);
|
|
855
|
+
}
|
|
856
|
+
// Update label names
|
|
857
|
+
if (updates.labelNames !== undefined) {
|
|
858
|
+
const newLabelNames = Array.isArray(updates.labelNames) ? updates.labelNames : [updates.labelNames];
|
|
859
|
+
this.config.filter.labelNames = newLabelNames.filter(Boolean);
|
|
860
|
+
configChanged = true;
|
|
861
|
+
console.log(`๐ท๏ธ Updated label names to: ${this.config.filter.labelNames.join(', ') || 'none'}`);
|
|
862
|
+
}
|
|
863
|
+
if (updates.labelName !== undefined) {
|
|
864
|
+
this.config.filter.labelName = updates.labelName || '';
|
|
865
|
+
configChanged = true;
|
|
866
|
+
console.log(`๐ท๏ธ Updated label name to: ${this.config.filter.labelName || 'none'}`);
|
|
867
|
+
}
|
|
868
|
+
// Update polling interval
|
|
869
|
+
if (updates.pollingIntervalMs !== undefined) {
|
|
870
|
+
const newInterval = Math.max(5000, updates.pollingIntervalMs); // Minimum 5 seconds
|
|
871
|
+
this.config.filter.pollingIntervalMs = newInterval;
|
|
872
|
+
configChanged = true;
|
|
873
|
+
pollingRestartNeeded = true;
|
|
874
|
+
console.log(`โฐ Updated polling interval to: ${newInterval}ms`);
|
|
875
|
+
}
|
|
876
|
+
// Save updated config to file
|
|
877
|
+
if (configChanged) {
|
|
878
|
+
await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2));
|
|
879
|
+
console.log('๐พ Configuration saved to file');
|
|
880
|
+
// Restart polling if interval changed
|
|
881
|
+
if (pollingRestartNeeded && this.pollingInterval) {
|
|
882
|
+
console.log('๐ Restarting polling with new interval...');
|
|
883
|
+
this.stopPolling();
|
|
884
|
+
await this.startPolling();
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
success: true,
|
|
889
|
+
updated: configChanged,
|
|
890
|
+
pollingRestarted: pollingRestartNeeded,
|
|
891
|
+
newConfig: this.config.filter
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
catch (error) {
|
|
895
|
+
console.error('โ Error updating filter config:', error);
|
|
896
|
+
return {
|
|
897
|
+
success: false,
|
|
898
|
+
error: error.message
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Clear all emails from memory
|
|
904
|
+
*/
|
|
905
|
+
clearEmails() {
|
|
906
|
+
this.emails = [];
|
|
907
|
+
console.log('๐งน Cleared all emails from memory');
|
|
908
|
+
}
|
|
909
|
+
isTextMimeType(mimeType = '') {
|
|
910
|
+
return TEXT_MIME_PREFIXES.some(prefix => mimeType.startsWith(prefix));
|
|
911
|
+
}
|
|
912
|
+
isTextExtension(filename = '') {
|
|
913
|
+
const lower = filename.toLowerCase();
|
|
914
|
+
return TEXT_EXTENSIONS.some(ext => lower.endsWith(ext));
|
|
915
|
+
}
|
|
916
|
+
decodeToBuffer(data = '') {
|
|
917
|
+
const normalized = data.replace(/-/g, '+').replace(/_/g, '/');
|
|
918
|
+
return Buffer.from(normalized, 'base64');
|
|
919
|
+
}
|
|
920
|
+
encodeToBase64Url(str = '') {
|
|
921
|
+
return Buffer.from(str, 'utf8')
|
|
922
|
+
.toString('base64')
|
|
923
|
+
.replace(/\+/g, '-')
|
|
924
|
+
.replace(/\//g, '_')
|
|
925
|
+
.replace(/=+$/g, '');
|
|
926
|
+
}
|
|
927
|
+
collectAttachmentParts(part, attachments = []) {
|
|
928
|
+
if (!part) {
|
|
929
|
+
return attachments;
|
|
930
|
+
}
|
|
931
|
+
if (part.filename && part.body && (part.body.attachmentId || part.body.data)) {
|
|
932
|
+
attachments.push(part);
|
|
933
|
+
}
|
|
934
|
+
if (Array.isArray(part.parts)) {
|
|
935
|
+
part.parts.forEach(child => this.collectAttachmentParts(child, attachments));
|
|
936
|
+
}
|
|
937
|
+
return attachments;
|
|
938
|
+
}
|
|
939
|
+
async fetchAttachmentBuffer(messageId, attachmentId) {
|
|
940
|
+
if (!attachmentId) {
|
|
941
|
+
return Buffer.alloc(0);
|
|
942
|
+
}
|
|
943
|
+
const attachment = await this.gmail.users.messages.attachments.get({
|
|
944
|
+
userId: 'me',
|
|
945
|
+
messageId,
|
|
946
|
+
id: attachmentId
|
|
947
|
+
});
|
|
948
|
+
return this.decodeToBuffer(attachment.data?.data || '');
|
|
949
|
+
}
|
|
950
|
+
async parsePdfAttachment(buffer, messageId, filename = 'attachment.pdf') {
|
|
951
|
+
try {
|
|
952
|
+
const pdfFolder = get(KEYS.pdf_folder_path);
|
|
953
|
+
// Use configured folder or a temp folder for extraction
|
|
954
|
+
const outDir = pdfFolder
|
|
955
|
+
? path.join(pdfFolder, messageId)
|
|
956
|
+
: path.join(os.tmpdir(), 'story-gen-pdf', messageId);
|
|
957
|
+
// Ensure directory exists
|
|
958
|
+
await ensureDir(outDir);
|
|
959
|
+
// Save the original PDF if we have a configured folder
|
|
960
|
+
if (pdfFolder) {
|
|
961
|
+
const savePath = path.join(outDir, filename);
|
|
962
|
+
try {
|
|
963
|
+
await fs.access(savePath);
|
|
964
|
+
console.log(`โ ๏ธ PDF file already exists at: ${savePath}, skipping save.`);
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
await fs.writeFile(savePath, buffer);
|
|
968
|
+
console.log(`๐พ Saved PDF attachment to: ${savePath}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
console.log(`๐ Extracting PDF content to: ${outDir}`);
|
|
972
|
+
const result = await extractPdf({
|
|
973
|
+
pdfBuffer: buffer,
|
|
974
|
+
outDir: outDir,
|
|
975
|
+
extractText: true,
|
|
976
|
+
extractImages: false,
|
|
977
|
+
renderPages: false
|
|
978
|
+
});
|
|
979
|
+
const fullText = Object.values(result.textByPage || {}).join('\n');
|
|
980
|
+
console.log(`๐ Extracted ${result.numPages} pages and ${result.images?.length || 0} images`);
|
|
981
|
+
return fullText.trim() || null;
|
|
982
|
+
}
|
|
983
|
+
catch (error) {
|
|
984
|
+
console.warn(`Failed to parse PDF attachment ${filename}:`, error);
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
async extractAttachmentText(messageId, part) {
|
|
989
|
+
const mimeType = part.mimeType || '';
|
|
990
|
+
const filename = part.filename || '';
|
|
991
|
+
console.log(`๐ Extracting attachment: ${filename} (${mimeType}) from message ${messageId}`);
|
|
992
|
+
let buffer;
|
|
993
|
+
if (part.body?.data) {
|
|
994
|
+
buffer = this.decodeToBuffer(part.body.data);
|
|
995
|
+
}
|
|
996
|
+
else if (part.body?.attachmentId) {
|
|
997
|
+
buffer = await this.fetchAttachmentBuffer(messageId, part.body.attachmentId);
|
|
998
|
+
}
|
|
999
|
+
if (!buffer || !buffer.length) {
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
if (this.isTextMimeType(mimeType) || this.isTextExtension(filename)) {
|
|
1003
|
+
return buffer.toString('utf8').trim();
|
|
1004
|
+
}
|
|
1005
|
+
if (mimeType === 'application/pdf' || filename.toLowerCase().endsWith('.pdf')) {
|
|
1006
|
+
console.log(`๐ Parsing PDF attachment: ${filename}`);
|
|
1007
|
+
return await this.parsePdfAttachment(buffer, messageId, filename || 'attachment.pdf');
|
|
1008
|
+
}
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
async includeAttachmentsInBody(message) {
|
|
1012
|
+
if (!message || !message.payload) {
|
|
1013
|
+
return message;
|
|
1014
|
+
}
|
|
1015
|
+
const attachments = this.collectAttachmentParts(message.payload, []);
|
|
1016
|
+
if (!attachments.length) {
|
|
1017
|
+
return message;
|
|
1018
|
+
}
|
|
1019
|
+
const attachmentSections = [];
|
|
1020
|
+
for (const attachment of attachments) {
|
|
1021
|
+
const text = await this.extractAttachmentText(message.id, attachment);
|
|
1022
|
+
if (text) {
|
|
1023
|
+
attachmentSections.push(`Attachment: ${attachment.filename || attachment.mimeType}\n${text}`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (!attachmentSections.length) {
|
|
1027
|
+
return message;
|
|
1028
|
+
}
|
|
1029
|
+
const combined = attachmentSections.join('\n\n');
|
|
1030
|
+
message.attachmentText = combined;
|
|
1031
|
+
const textPart = {
|
|
1032
|
+
mimeType: 'text/plain',
|
|
1033
|
+
filename: 'attachments.txt',
|
|
1034
|
+
body: {
|
|
1035
|
+
data: this.encodeToBase64Url(`\n\n--- Attachments ---\n${combined}`)
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
if (Array.isArray(message.payload.parts)) {
|
|
1039
|
+
message.payload.parts.push(textPart);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
message.payload.parts = [textPart];
|
|
1043
|
+
}
|
|
1044
|
+
return message;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
export default FilterGmailPoller;
|
|
1048
|
+
//# sourceMappingURL=filter-gmail-poller.js.map
|