flowtask 5.8.4__cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
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.
- flowtask/__init__.py +93 -0
- flowtask/__main__.py +38 -0
- flowtask/bots/__init__.py +6 -0
- flowtask/bots/check.py +93 -0
- flowtask/bots/codebot.py +51 -0
- flowtask/components/ASPX.py +148 -0
- flowtask/components/AddDataset.py +352 -0
- flowtask/components/Amazon.py +523 -0
- flowtask/components/AutoTask.py +314 -0
- flowtask/components/Azure.py +80 -0
- flowtask/components/AzureUsers.py +106 -0
- flowtask/components/BaseAction.py +91 -0
- flowtask/components/BaseLoop.py +198 -0
- flowtask/components/BestBuy.py +800 -0
- flowtask/components/CSVToGCS.py +120 -0
- flowtask/components/CompanyScraper/__init__.py +1 -0
- flowtask/components/CompanyScraper/parsers/__init__.py +6 -0
- flowtask/components/CompanyScraper/parsers/base.py +102 -0
- flowtask/components/CompanyScraper/parsers/explorium.py +192 -0
- flowtask/components/CompanyScraper/parsers/leadiq.py +206 -0
- flowtask/components/CompanyScraper/parsers/rocket.py +133 -0
- flowtask/components/CompanyScraper/parsers/siccode.py +109 -0
- flowtask/components/CompanyScraper/parsers/visualvisitor.py +130 -0
- flowtask/components/CompanyScraper/parsers/zoominfo.py +118 -0
- flowtask/components/CompanyScraper/scrapper.py +1054 -0
- flowtask/components/CopyTo.py +177 -0
- flowtask/components/CopyToBigQuery.py +243 -0
- flowtask/components/CopyToMongoDB.py +291 -0
- flowtask/components/CopyToPg.py +609 -0
- flowtask/components/CopyToRethink.py +207 -0
- flowtask/components/CreateGCSBucket.py +102 -0
- flowtask/components/CreateReport/CreateReport.py +228 -0
- flowtask/components/CreateReport/__init__.py +9 -0
- flowtask/components/CreateReport/charts/__init__.py +15 -0
- flowtask/components/CreateReport/charts/bar.py +51 -0
- flowtask/components/CreateReport/charts/base.py +66 -0
- flowtask/components/CreateReport/charts/pie.py +64 -0
- flowtask/components/CreateReport/utils.py +9 -0
- flowtask/components/CustomerSatisfaction.py +196 -0
- flowtask/components/DataInput.py +200 -0
- flowtask/components/DateList.py +255 -0
- flowtask/components/DbClient.py +163 -0
- flowtask/components/DialPad.py +146 -0
- flowtask/components/DocumentDBQuery.py +200 -0
- flowtask/components/DownloadFrom.py +371 -0
- flowtask/components/DownloadFromD2L.py +113 -0
- flowtask/components/DownloadFromFTP.py +181 -0
- flowtask/components/DownloadFromIMAP.py +315 -0
- flowtask/components/DownloadFromS3.py +198 -0
- flowtask/components/DownloadFromSFTP.py +265 -0
- flowtask/components/DownloadFromSharepoint.py +110 -0
- flowtask/components/DownloadFromSmartSheet.py +114 -0
- flowtask/components/DownloadS3File.py +229 -0
- flowtask/components/Dummy.py +59 -0
- flowtask/components/DuplicatePhoto.py +411 -0
- flowtask/components/EmployeeEvaluation.py +237 -0
- flowtask/components/ExecuteSQL.py +323 -0
- flowtask/components/ExtractHTML.py +178 -0
- flowtask/components/FileBase.py +178 -0
- flowtask/components/FileCopy.py +181 -0
- flowtask/components/FileDelete.py +82 -0
- flowtask/components/FileExists.py +146 -0
- flowtask/components/FileIteratorDelete.py +112 -0
- flowtask/components/FileList.py +194 -0
- flowtask/components/FileOpen.py +75 -0
- flowtask/components/FileRead.py +120 -0
- flowtask/components/FileRename.py +106 -0
- flowtask/components/FilterIf.py +284 -0
- flowtask/components/FilterRows/FilterRows.py +200 -0
- flowtask/components/FilterRows/__init__.py +10 -0
- flowtask/components/FilterRows/functions.py +4 -0
- flowtask/components/GCSToBigQuery.py +103 -0
- flowtask/components/GoogleA4.py +150 -0
- flowtask/components/GoogleGeoCoding.py +344 -0
- flowtask/components/GooglePlaces.py +315 -0
- flowtask/components/GoogleSearch.py +539 -0
- flowtask/components/HTTPClient.py +268 -0
- flowtask/components/ICIMS.py +146 -0
- flowtask/components/IF.py +179 -0
- flowtask/components/IcimsFolderCopy.py +173 -0
- flowtask/components/ImageFeatures/__init__.py +5 -0
- flowtask/components/ImageFeatures/process.py +233 -0
- flowtask/components/IteratorBase.py +251 -0
- flowtask/components/LangchainLoader/__init__.py +5 -0
- flowtask/components/LangchainLoader/loader.py +194 -0
- flowtask/components/LangchainLoader/loaders/__init__.py +22 -0
- flowtask/components/LangchainLoader/loaders/abstract.py +362 -0
- flowtask/components/LangchainLoader/loaders/basepdf.py +50 -0
- flowtask/components/LangchainLoader/loaders/docx.py +91 -0
- flowtask/components/LangchainLoader/loaders/html.py +119 -0
- flowtask/components/LangchainLoader/loaders/pdfblocks.py +146 -0
- flowtask/components/LangchainLoader/loaders/pdfmark.py +79 -0
- flowtask/components/LangchainLoader/loaders/pdftables.py +135 -0
- flowtask/components/LangchainLoader/loaders/qa.py +67 -0
- flowtask/components/LangchainLoader/loaders/txt.py +55 -0
- flowtask/components/LeadIQ.py +650 -0
- flowtask/components/Loop.py +253 -0
- flowtask/components/Lowes.py +334 -0
- flowtask/components/MS365Usage.py +156 -0
- flowtask/components/MSTeamsMessages.py +320 -0
- flowtask/components/MarketClustering.py +1051 -0
- flowtask/components/MergeFiles.py +362 -0
- flowtask/components/MilvusOutput.py +87 -0
- flowtask/components/NearByStores.py +175 -0
- flowtask/components/NetworkNinja/__init__.py +6 -0
- flowtask/components/NetworkNinja/models/__init__.py +52 -0
- flowtask/components/NetworkNinja/models/abstract.py +177 -0
- flowtask/components/NetworkNinja/models/account.py +39 -0
- flowtask/components/NetworkNinja/models/client.py +19 -0
- flowtask/components/NetworkNinja/models/district.py +14 -0
- flowtask/components/NetworkNinja/models/events.py +101 -0
- flowtask/components/NetworkNinja/models/forms.py +499 -0
- flowtask/components/NetworkNinja/models/market.py +16 -0
- flowtask/components/NetworkNinja/models/organization.py +34 -0
- flowtask/components/NetworkNinja/models/photos.py +125 -0
- flowtask/components/NetworkNinja/models/project.py +44 -0
- flowtask/components/NetworkNinja/models/region.py +28 -0
- flowtask/components/NetworkNinja/models/store.py +203 -0
- flowtask/components/NetworkNinja/models/user.py +151 -0
- flowtask/components/NetworkNinja/router.py +854 -0
- flowtask/components/Odoo.py +175 -0
- flowtask/components/OdooInjector.py +192 -0
- flowtask/components/OpenFromXML.py +126 -0
- flowtask/components/OpenWeather.py +41 -0
- flowtask/components/OpenWithBase.py +616 -0
- flowtask/components/OpenWithPandas.py +715 -0
- flowtask/components/PGPDecrypt.py +199 -0
- flowtask/components/PandasIterator.py +187 -0
- flowtask/components/PandasToFile.py +189 -0
- flowtask/components/Paradox.py +339 -0
- flowtask/components/ParamIterator.py +117 -0
- flowtask/components/ParseHTML.py +84 -0
- flowtask/components/PlacerStores.py +249 -0
- flowtask/components/Pokemon.py +507 -0
- flowtask/components/PositiveBot.py +62 -0
- flowtask/components/PowerPointSlide.py +400 -0
- flowtask/components/PrintMessage.py +127 -0
- flowtask/components/ProductCompetitors/__init__.py +5 -0
- flowtask/components/ProductCompetitors/parsers/__init__.py +7 -0
- flowtask/components/ProductCompetitors/parsers/base.py +72 -0
- flowtask/components/ProductCompetitors/parsers/bestbuy.py +86 -0
- flowtask/components/ProductCompetitors/parsers/lowes.py +103 -0
- flowtask/components/ProductCompetitors/scrapper.py +155 -0
- flowtask/components/ProductCompliant.py +169 -0
- flowtask/components/ProductInfo/__init__.py +1 -0
- flowtask/components/ProductInfo/parsers/__init__.py +5 -0
- flowtask/components/ProductInfo/parsers/base.py +83 -0
- flowtask/components/ProductInfo/parsers/brother.py +97 -0
- flowtask/components/ProductInfo/parsers/canon.py +167 -0
- flowtask/components/ProductInfo/parsers/epson.py +118 -0
- flowtask/components/ProductInfo/parsers/hp.py +131 -0
- flowtask/components/ProductInfo/parsers/samsung.py +97 -0
- flowtask/components/ProductInfo/scraper.py +319 -0
- flowtask/components/ProductPricing.py +118 -0
- flowtask/components/QS.py +261 -0
- flowtask/components/QSBase.py +201 -0
- flowtask/components/QueryIterator.py +273 -0
- flowtask/components/QueryToInsert.py +327 -0
- flowtask/components/QueryToPandas.py +432 -0
- flowtask/components/RESTClient.py +195 -0
- flowtask/components/RethinkDBQuery.py +189 -0
- flowtask/components/Rsync.py +74 -0
- flowtask/components/RunSSH.py +59 -0
- flowtask/components/RunShell.py +71 -0
- flowtask/components/SalesForce.py +20 -0
- flowtask/components/SaveImageBank/__init__.py +257 -0
- flowtask/components/SchedulingVisits.py +592 -0
- flowtask/components/ScrapPage.py +216 -0
- flowtask/components/ScrapSearch.py +79 -0
- flowtask/components/SendNotify.py +257 -0
- flowtask/components/SentimentAnalysis.py +694 -0
- flowtask/components/ServiceScrapper/__init__.py +5 -0
- flowtask/components/ServiceScrapper/parsers/__init__.py +1 -0
- flowtask/components/ServiceScrapper/parsers/base.py +94 -0
- flowtask/components/ServiceScrapper/parsers/costco.py +93 -0
- flowtask/components/ServiceScrapper/scrapper.py +199 -0
- flowtask/components/SetVariables.py +156 -0
- flowtask/components/SubTask.py +182 -0
- flowtask/components/SuiteCRM.py +48 -0
- flowtask/components/Switch.py +175 -0
- flowtask/components/TableBase.py +148 -0
- flowtask/components/TableDelete.py +312 -0
- flowtask/components/TableInput.py +143 -0
- flowtask/components/TableOutput/TableOutput.py +384 -0
- flowtask/components/TableOutput/__init__.py +3 -0
- flowtask/components/TableSchema.py +534 -0
- flowtask/components/Target.py +223 -0
- flowtask/components/ThumbnailGenerator.py +156 -0
- flowtask/components/ToPandas.py +67 -0
- flowtask/components/TransformRows/TransformRows.py +507 -0
- flowtask/components/TransformRows/__init__.py +9 -0
- flowtask/components/TransformRows/functions.py +559 -0
- flowtask/components/TransposeRows.py +176 -0
- flowtask/components/UPCDatabase.py +86 -0
- flowtask/components/UnGzip.py +171 -0
- flowtask/components/Uncompress.py +172 -0
- flowtask/components/UniqueRows.py +126 -0
- flowtask/components/Unzip.py +107 -0
- flowtask/components/UpdateOperationalVars.py +147 -0
- flowtask/components/UploadTo.py +299 -0
- flowtask/components/UploadToS3.py +136 -0
- flowtask/components/UploadToSFTP.py +160 -0
- flowtask/components/UploadToSharepoint.py +205 -0
- flowtask/components/UserFunc.py +122 -0
- flowtask/components/VivaTracker.py +140 -0
- flowtask/components/WSDLClient.py +123 -0
- flowtask/components/Wait.py +18 -0
- flowtask/components/Walmart.py +199 -0
- flowtask/components/Workplace.py +134 -0
- flowtask/components/XMLToPandas.py +267 -0
- flowtask/components/Zammad/__init__.py +41 -0
- flowtask/components/Zammad/models.py +0 -0
- flowtask/components/ZoomInfoScraper.py +409 -0
- flowtask/components/__init__.py +104 -0
- flowtask/components/abstract.py +18 -0
- flowtask/components/flow.py +530 -0
- flowtask/components/google.py +335 -0
- flowtask/components/group.py +221 -0
- flowtask/components/py.typed +0 -0
- flowtask/components/reviewscrap.py +132 -0
- flowtask/components/tAutoincrement.py +117 -0
- flowtask/components/tConcat.py +109 -0
- flowtask/components/tExplode.py +119 -0
- flowtask/components/tFilter.py +184 -0
- flowtask/components/tGroup.py +236 -0
- flowtask/components/tJoin.py +270 -0
- flowtask/components/tMap/__init__.py +9 -0
- flowtask/components/tMap/functions.py +54 -0
- flowtask/components/tMap/tMap.py +450 -0
- flowtask/components/tMelt.py +112 -0
- flowtask/components/tMerge.py +114 -0
- flowtask/components/tOrder.py +93 -0
- flowtask/components/tPandas.py +94 -0
- flowtask/components/tPivot.py +71 -0
- flowtask/components/tPluckCols.py +76 -0
- flowtask/components/tUnnest.py +82 -0
- flowtask/components/user.py +401 -0
- flowtask/conf.py +457 -0
- flowtask/download.py +102 -0
- flowtask/events/__init__.py +11 -0
- flowtask/events/events/__init__.py +20 -0
- flowtask/events/events/abstract.py +95 -0
- flowtask/events/events/alerts/__init__.py +362 -0
- flowtask/events/events/alerts/colfunctions.py +131 -0
- flowtask/events/events/alerts/functions.py +158 -0
- flowtask/events/events/dummy.py +12 -0
- flowtask/events/events/exec.py +124 -0
- flowtask/events/events/file/__init__.py +7 -0
- flowtask/events/events/file/base.py +51 -0
- flowtask/events/events/file/copy.py +23 -0
- flowtask/events/events/file/delete.py +16 -0
- flowtask/events/events/interfaces/__init__.py +9 -0
- flowtask/events/events/interfaces/client.py +67 -0
- flowtask/events/events/interfaces/credentials.py +28 -0
- flowtask/events/events/interfaces/notifications.py +58 -0
- flowtask/events/events/jira.py +122 -0
- flowtask/events/events/log.py +26 -0
- flowtask/events/events/logerr.py +52 -0
- flowtask/events/events/notify.py +59 -0
- flowtask/events/events/notify_event.py +160 -0
- flowtask/events/events/publish.py +54 -0
- flowtask/events/events/sendfile.py +104 -0
- flowtask/events/events/task.py +97 -0
- flowtask/events/events/teams.py +98 -0
- flowtask/events/events/webhook.py +58 -0
- flowtask/events/manager.py +287 -0
- flowtask/exceptions.c +39393 -0
- flowtask/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/extensions/__init__.py +3 -0
- flowtask/extensions/abstract.py +82 -0
- flowtask/extensions/logging/__init__.py +65 -0
- flowtask/hooks/__init__.py +9 -0
- flowtask/hooks/actions/__init__.py +22 -0
- flowtask/hooks/actions/abstract.py +66 -0
- flowtask/hooks/actions/dummy.py +23 -0
- flowtask/hooks/actions/jira.py +74 -0
- flowtask/hooks/actions/rest.py +320 -0
- flowtask/hooks/actions/sampledata.py +37 -0
- flowtask/hooks/actions/sensor.py +23 -0
- flowtask/hooks/actions/task.py +9 -0
- flowtask/hooks/actions/ticket.py +37 -0
- flowtask/hooks/actions/zammad.py +55 -0
- flowtask/hooks/hook.py +62 -0
- flowtask/hooks/models.py +17 -0
- flowtask/hooks/service.py +187 -0
- flowtask/hooks/step.py +91 -0
- flowtask/hooks/types/__init__.py +23 -0
- flowtask/hooks/types/base.py +129 -0
- flowtask/hooks/types/brokers/__init__.py +11 -0
- flowtask/hooks/types/brokers/base.py +54 -0
- flowtask/hooks/types/brokers/mqtt.py +35 -0
- flowtask/hooks/types/brokers/rabbitmq.py +82 -0
- flowtask/hooks/types/brokers/redis.py +83 -0
- flowtask/hooks/types/brokers/sqs.py +44 -0
- flowtask/hooks/types/fs.py +232 -0
- flowtask/hooks/types/http.py +49 -0
- flowtask/hooks/types/imap.py +200 -0
- flowtask/hooks/types/jira.py +279 -0
- flowtask/hooks/types/mail.py +205 -0
- flowtask/hooks/types/postgres.py +98 -0
- flowtask/hooks/types/responses/__init__.py +8 -0
- flowtask/hooks/types/responses/base.py +5 -0
- flowtask/hooks/types/sharepoint.py +288 -0
- flowtask/hooks/types/ssh.py +141 -0
- flowtask/hooks/types/tagged.py +59 -0
- flowtask/hooks/types/upload.py +85 -0
- flowtask/hooks/types/watch.py +71 -0
- flowtask/hooks/types/web.py +36 -0
- flowtask/interfaces/AzureClient.py +137 -0
- flowtask/interfaces/AzureGraph.py +839 -0
- flowtask/interfaces/Boto3Client.py +326 -0
- flowtask/interfaces/DropboxClient.py +173 -0
- flowtask/interfaces/ExcelHandler.py +94 -0
- flowtask/interfaces/FTPClient.py +131 -0
- flowtask/interfaces/GoogleCalendar.py +201 -0
- flowtask/interfaces/GoogleClient.py +133 -0
- flowtask/interfaces/GoogleDrive.py +127 -0
- flowtask/interfaces/GoogleGCS.py +89 -0
- flowtask/interfaces/GoogleGeocoding.py +93 -0
- flowtask/interfaces/GoogleLang.py +114 -0
- flowtask/interfaces/GooglePub.py +61 -0
- flowtask/interfaces/GoogleSheet.py +68 -0
- flowtask/interfaces/IMAPClient.py +137 -0
- flowtask/interfaces/O365Calendar.py +113 -0
- flowtask/interfaces/O365Client.py +220 -0
- flowtask/interfaces/OneDrive.py +284 -0
- flowtask/interfaces/Outlook.py +155 -0
- flowtask/interfaces/ParrotBot.py +130 -0
- flowtask/interfaces/SSHClient.py +378 -0
- flowtask/interfaces/Sharepoint.py +496 -0
- flowtask/interfaces/__init__.py +36 -0
- flowtask/interfaces/azureauth.py +119 -0
- flowtask/interfaces/cache.py +201 -0
- flowtask/interfaces/client.py +82 -0
- flowtask/interfaces/compress.py +525 -0
- flowtask/interfaces/credentials.py +124 -0
- flowtask/interfaces/d2l.py +239 -0
- flowtask/interfaces/databases/__init__.py +5 -0
- flowtask/interfaces/databases/db.py +223 -0
- flowtask/interfaces/databases/documentdb.py +55 -0
- flowtask/interfaces/databases/rethink.py +39 -0
- flowtask/interfaces/dataframes/__init__.py +11 -0
- flowtask/interfaces/dataframes/abstract.py +21 -0
- flowtask/interfaces/dataframes/arrow.py +71 -0
- flowtask/interfaces/dataframes/dt.py +69 -0
- flowtask/interfaces/dataframes/pandas.py +167 -0
- flowtask/interfaces/dataframes/polars.py +60 -0
- flowtask/interfaces/db.py +263 -0
- flowtask/interfaces/env.py +46 -0
- flowtask/interfaces/func.py +137 -0
- flowtask/interfaces/http.py +1780 -0
- flowtask/interfaces/locale.py +40 -0
- flowtask/interfaces/log.py +75 -0
- flowtask/interfaces/mask.py +143 -0
- flowtask/interfaces/notification.py +154 -0
- flowtask/interfaces/playwright.py +339 -0
- flowtask/interfaces/powerpoint.py +368 -0
- flowtask/interfaces/py.typed +0 -0
- flowtask/interfaces/qs.py +376 -0
- flowtask/interfaces/result.py +87 -0
- flowtask/interfaces/selenium_service.py +779 -0
- flowtask/interfaces/smartsheet.py +154 -0
- flowtask/interfaces/stat.py +39 -0
- flowtask/interfaces/task.py +96 -0
- flowtask/interfaces/template.py +118 -0
- flowtask/interfaces/vectorstores/__init__.py +1 -0
- flowtask/interfaces/vectorstores/abstract.py +133 -0
- flowtask/interfaces/vectorstores/milvus.py +669 -0
- flowtask/interfaces/zammad.py +107 -0
- flowtask/models.py +193 -0
- flowtask/parsers/__init__.py +15 -0
- flowtask/parsers/_yaml.c +11978 -0
- flowtask/parsers/_yaml.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/plugins/__init__.py +16 -0
- flowtask/plugins/components/__init__.py +0 -0
- flowtask/plugins/handler/__init__.py +45 -0
- flowtask/plugins/importer.py +31 -0
- flowtask/plugins/sources/__init__.py +0 -0
- flowtask/runner.py +283 -0
- flowtask/scheduler/__init__.py +9 -0
- flowtask/scheduler/functions.py +493 -0
- flowtask/scheduler/handlers/__init__.py +8 -0
- flowtask/scheduler/handlers/manager.py +504 -0
- flowtask/scheduler/handlers/models.py +58 -0
- flowtask/scheduler/handlers/service.py +72 -0
- flowtask/scheduler/notifications.py +65 -0
- flowtask/scheduler/scheduler.py +993 -0
- flowtask/services/__init__.py +0 -0
- flowtask/services/bots/__init__.py +0 -0
- flowtask/services/bots/telegram.py +264 -0
- flowtask/services/files/__init__.py +11 -0
- flowtask/services/files/manager.py +522 -0
- flowtask/services/files/model.py +37 -0
- flowtask/services/files/service.py +767 -0
- flowtask/services/jira/__init__.py +3 -0
- flowtask/services/jira/jira_actions.py +191 -0
- flowtask/services/tasks/__init__.py +13 -0
- flowtask/services/tasks/launcher.py +213 -0
- flowtask/services/tasks/manager.py +323 -0
- flowtask/services/tasks/service.py +275 -0
- flowtask/services/tasks/task_manager.py +376 -0
- flowtask/services/tasks/tasks.py +155 -0
- flowtask/storages/__init__.py +16 -0
- flowtask/storages/exceptions.py +12 -0
- flowtask/storages/files/__init__.py +8 -0
- flowtask/storages/files/abstract.py +29 -0
- flowtask/storages/files/filesystem.py +66 -0
- flowtask/storages/tasks/__init__.py +19 -0
- flowtask/storages/tasks/abstract.py +26 -0
- flowtask/storages/tasks/database.py +33 -0
- flowtask/storages/tasks/filesystem.py +108 -0
- flowtask/storages/tasks/github.py +119 -0
- flowtask/storages/tasks/memory.py +45 -0
- flowtask/storages/tasks/row.py +25 -0
- flowtask/tasks/__init__.py +0 -0
- flowtask/tasks/abstract.py +526 -0
- flowtask/tasks/command.py +118 -0
- flowtask/tasks/pile.py +486 -0
- flowtask/tasks/py.typed +0 -0
- flowtask/tasks/task.py +778 -0
- flowtask/template/__init__.py +161 -0
- flowtask/tests.py +257 -0
- flowtask/types/__init__.py +8 -0
- flowtask/types/typedefs.c +11347 -0
- flowtask/types/typedefs.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/__init__.py +24 -0
- flowtask/utils/constants.py +117 -0
- flowtask/utils/encoders.py +21 -0
- flowtask/utils/executor.py +112 -0
- flowtask/utils/functions.cpp +14280 -0
- flowtask/utils/functions.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/stats.py +308 -0
- flowtask/utils/transformations.py +74 -0
- flowtask/utils/uv.py +12 -0
- flowtask/utils/validators.py +97 -0
- flowtask/version.py +11 -0
- flowtask-5.8.4.dist-info/LICENSE +201 -0
- flowtask-5.8.4.dist-info/METADATA +209 -0
- flowtask-5.8.4.dist-info/RECORD +470 -0
- flowtask-5.8.4.dist-info/WHEEL +6 -0
- flowtask-5.8.4.dist-info/entry_points.txt +3 -0
- flowtask-5.8.4.dist-info/top_level.txt +2 -0
- plugins/components/CreateQR.py +39 -0
- plugins/components/TestComponent.py +28 -0
- plugins/components/Use1.py +13 -0
- plugins/components/Workplace.py +117 -0
- plugins/components/__init__.py +3 -0
- plugins/sources/__init__.py +0 -0
- plugins/sources/get_populartimes.py +78 -0
- plugins/sources/google.py +150 -0
- plugins/sources/hubspot.py +679 -0
- plugins/sources/icims.py +679 -0
- plugins/sources/mobileinsight.py +501 -0
- plugins/sources/newrelic.py +262 -0
- plugins/sources/uap.py +268 -0
- plugins/sources/venu.py +244 -0
- plugins/sources/vocinity.py +314 -0
@@ -0,0 +1,200 @@
|
|
1
|
+
from typing import Union, Optional
|
2
|
+
import imaplib
|
3
|
+
import time
|
4
|
+
from functools import partial
|
5
|
+
import ssl
|
6
|
+
from ...exceptions import ComponentError
|
7
|
+
from ...interfaces.azureauth import AzureAuth
|
8
|
+
from .watch import BaseWatchdog, BaseWatcher
|
9
|
+
from ...interfaces import CacheSupport
|
10
|
+
|
11
|
+
|
12
|
+
class ImapWatcher(BaseWatcher):
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
host: str,
|
16
|
+
port: int,
|
17
|
+
user: str,
|
18
|
+
password: str,
|
19
|
+
mailbox="INBOX",
|
20
|
+
use_ssl: bool = True,
|
21
|
+
interval: int = 320,
|
22
|
+
authmech: str = None,
|
23
|
+
search: Optional[Union[str, dict, list]] = None,
|
24
|
+
**kwargs,
|
25
|
+
):
|
26
|
+
super(ImapWatcher, self).__init__(**kwargs)
|
27
|
+
self.host = host
|
28
|
+
self.port = port
|
29
|
+
self.user = user
|
30
|
+
self.password = password
|
31
|
+
self.mailbox = mailbox
|
32
|
+
self.interval = interval
|
33
|
+
self.authmech = authmech
|
34
|
+
self.use_ssl = use_ssl
|
35
|
+
self.search = search
|
36
|
+
self.args = kwargs
|
37
|
+
self._expiration = kwargs.pop("expiration", None)
|
38
|
+
|
39
|
+
def close_watcher(self):
|
40
|
+
try:
|
41
|
+
self.imap_server.logout()
|
42
|
+
except OSError as exc:
|
43
|
+
self._logger.error(f"Error closing IMAP Connection: {exc}")
|
44
|
+
|
45
|
+
def connect(self, max_retries: int = 3, delay: int = 10):
|
46
|
+
if self.use_ssl is True:
|
47
|
+
sslcontext = ssl.create_default_context()
|
48
|
+
server = partial(
|
49
|
+
imaplib.IMAP4_SSL,
|
50
|
+
self.host,
|
51
|
+
self.port,
|
52
|
+
timeout=10,
|
53
|
+
ssl_context=sslcontext,
|
54
|
+
)
|
55
|
+
else:
|
56
|
+
server = partial(imaplib.IMAP4, self.host, self.port, timeout=10)
|
57
|
+
# azure auth:
|
58
|
+
if isinstance(server, str):
|
59
|
+
raise ComponentError(
|
60
|
+
f"Failed to configure to IMAP Server: {server}"
|
61
|
+
)
|
62
|
+
azure = AzureAuth() # default values
|
63
|
+
for attempt in range(max_retries):
|
64
|
+
try:
|
65
|
+
self.imap_server = server()
|
66
|
+
if self.authmech == "XOAUTH2":
|
67
|
+
self._logger.debug("IMAP Auth XOAUTH2")
|
68
|
+
try:
|
69
|
+
result, msg = self.imap_server.authenticate(
|
70
|
+
self.authmech,
|
71
|
+
lambda x: azure.binary_token(self.user, self.password),
|
72
|
+
)
|
73
|
+
if result != "OK":
|
74
|
+
raise ComponentError(
|
75
|
+
f"IMAP: Wrong response: {result} message={msg}"
|
76
|
+
)
|
77
|
+
except AttributeError as err:
|
78
|
+
raise ComponentError(
|
79
|
+
f"Login Forbidden, wrong username or password: {err}"
|
80
|
+
) from err
|
81
|
+
else:
|
82
|
+
self._logger.debug("IMAP Basic Login")
|
83
|
+
## making the server login:
|
84
|
+
r = self.imap_server.login(self.user, self.password)
|
85
|
+
if r.result == "NO":
|
86
|
+
raise ComponentError(
|
87
|
+
f"Login Forbidden, Server Disconnected: {r}"
|
88
|
+
)
|
89
|
+
### Select Mailbox
|
90
|
+
self.imap_server.select(self.mailbox, readonly=False)
|
91
|
+
# If connection is successful, break out of the loop
|
92
|
+
break
|
93
|
+
except Exception as exc:
|
94
|
+
server = f"{self.host}:{self.port}"
|
95
|
+
if attempt == max_retries - 1:
|
96
|
+
raise ComponentError(
|
97
|
+
f"Could not connect to IMAP Server {self.host}:{self.port}: {exc}"
|
98
|
+
) from exc
|
99
|
+
# Otherwise, log the exception and wait before retrying
|
100
|
+
self._logger.warning(
|
101
|
+
f"Attempt {attempt + 1} to connect to IMAP {server}: {exc}. Retrying in {delay} sec."
|
102
|
+
)
|
103
|
+
time.sleep(delay)
|
104
|
+
|
105
|
+
def build_search_criteria(self, search_criteria):
|
106
|
+
criteria = []
|
107
|
+
for key, value in search_criteria.items():
|
108
|
+
if value is None:
|
109
|
+
criteria.append(key)
|
110
|
+
else:
|
111
|
+
criteria.append(f'({key} "{value}")')
|
112
|
+
return " ".join(criteria)
|
113
|
+
|
114
|
+
def process_email(self, email_id, **kwargs):
|
115
|
+
self._logger.debug(f"Email {email_id} has processed.")
|
116
|
+
self.parent.call_actions(**kwargs)
|
117
|
+
|
118
|
+
def run(self, **kwargs):
|
119
|
+
try:
|
120
|
+
self.connect()
|
121
|
+
except ComponentError as exc:
|
122
|
+
self._logger.error(
|
123
|
+
f"Error connecting to IMAP server: {exc}"
|
124
|
+
)
|
125
|
+
return
|
126
|
+
while not self.stop_event.is_set():
|
127
|
+
try:
|
128
|
+
search_criteria = self.build_search_criteria(self.search)
|
129
|
+
self._logger.debug(f"IMAP: Running Search: {search_criteria}")
|
130
|
+
result, data = self.imap_server.search(None, search_criteria)
|
131
|
+
if result == "OK":
|
132
|
+
# TODO:
|
133
|
+
emails = data[0].split()
|
134
|
+
unseen_emails = len(emails)
|
135
|
+
self._logger.notice(
|
136
|
+
f"Found {unseen_emails} emails in {self.mailbox}"
|
137
|
+
)
|
138
|
+
for email_id in emails:
|
139
|
+
# Fetch the email's structure
|
140
|
+
status, mail_data = self.imap_server.fetch(
|
141
|
+
email_id, "(BODYSTRUCTURE)"
|
142
|
+
)
|
143
|
+
if status == "OK":
|
144
|
+
with CacheSupport(every=self._expiration) as cache:
|
145
|
+
if cache.exists(email_id):
|
146
|
+
continue
|
147
|
+
# check if email has attachments:
|
148
|
+
if (
|
149
|
+
"has_attachments" in self.args and self.args["has_attachments"] is True
|
150
|
+
):
|
151
|
+
structure = mail_data[0]
|
152
|
+
if isinstance(structure, bytes):
|
153
|
+
structure = structure.decode("utf-8")
|
154
|
+
# Check if the structure contains a filename parameter
|
155
|
+
if '("attachment"' in structure:
|
156
|
+
cache.setexp(email_id, value=structure)
|
157
|
+
self.process_email(email_id, **kwargs)
|
158
|
+
else:
|
159
|
+
structure = mail_data[0]
|
160
|
+
cache.setexp(email_id, value=structure)
|
161
|
+
self.process_email(email_id, **kwargs)
|
162
|
+
except Exception as e:
|
163
|
+
print(f"An error occurred while checking the mailbox: {e}")
|
164
|
+
# Reconnect if an error occurs
|
165
|
+
self.connect()
|
166
|
+
# Wait for the interval, but allow the sleep to be interrupted by the signal
|
167
|
+
try:
|
168
|
+
for _ in range(self.interval):
|
169
|
+
if self.stop_event.is_set():
|
170
|
+
break
|
171
|
+
time.sleep(1)
|
172
|
+
except KeyboardInterrupt:
|
173
|
+
break
|
174
|
+
|
175
|
+
|
176
|
+
class IMAPWatchdog(BaseWatchdog):
|
177
|
+
def processing_search(self, search_terms: dict):
|
178
|
+
search = {}
|
179
|
+
for key, value in search_terms.items():
|
180
|
+
val = self.mask_replacement(value)
|
181
|
+
search[key] = val
|
182
|
+
return search
|
183
|
+
|
184
|
+
def create_watcher(self, *args, **kwargs) -> BaseWatcher:
|
185
|
+
credentials = kwargs.pop("credentials", {})
|
186
|
+
self.mailbox = kwargs.pop("mailbox", "INBOX")
|
187
|
+
interval = kwargs.pop("interval", 60)
|
188
|
+
authmech = credentials.pop("authmech", None)
|
189
|
+
self.mask_start(**kwargs)
|
190
|
+
search = self.processing_search(kwargs.pop("search", {}))
|
191
|
+
# TODO: processing with masks the Search criteria:
|
192
|
+
self.credentials = self.set_credentials(credentials)
|
193
|
+
return ImapWatcher(
|
194
|
+
**self.credentials,
|
195
|
+
mailbox=self.mailbox,
|
196
|
+
interval=interval,
|
197
|
+
authmech=authmech,
|
198
|
+
search=search,
|
199
|
+
**kwargs,
|
200
|
+
)
|
@@ -0,0 +1,279 @@
|
|
1
|
+
from typing import Optional, Any, List, Dict
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from aiohttp import web
|
4
|
+
import hmac
|
5
|
+
import hashlib
|
6
|
+
from datamodel import BaseModel
|
7
|
+
from datamodel.libs.mapping import ClassDict
|
8
|
+
from datamodel.parsers.json import json_encoder, json_decoder
|
9
|
+
from ...conf import (
|
10
|
+
JIRA_SECRET_TOKEN,
|
11
|
+
JIRA_API_TOKEN,
|
12
|
+
JIRA_USERNAME,
|
13
|
+
JIRA_INSTANCE,
|
14
|
+
JIRA_PROJECT,
|
15
|
+
)
|
16
|
+
from .http import HTTPHook
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class AvatarUrls:
|
21
|
+
_48x48: str = field(metadata={'alias': '48x48'})
|
22
|
+
_24x24: str = field(metadata={'alias': '24x24'})
|
23
|
+
_16x16: str = field(metadata={'alias': '16x16'})
|
24
|
+
_32x32: str = field(metadata={'alias': '32x32'})
|
25
|
+
|
26
|
+
@dataclass
|
27
|
+
class User:
|
28
|
+
self: str
|
29
|
+
accountId: str
|
30
|
+
avatarUrls: AvatarUrls
|
31
|
+
displayName: str
|
32
|
+
active: bool
|
33
|
+
timeZone: str
|
34
|
+
accountType: str
|
35
|
+
|
36
|
+
@dataclass
|
37
|
+
class StatusCategory:
|
38
|
+
self: str
|
39
|
+
id: int
|
40
|
+
key: str
|
41
|
+
colorName: str
|
42
|
+
name: str
|
43
|
+
|
44
|
+
@dataclass
|
45
|
+
class Status:
|
46
|
+
self: str
|
47
|
+
description: str
|
48
|
+
iconUrl: str
|
49
|
+
name: str
|
50
|
+
id: str
|
51
|
+
statusCategory: StatusCategory
|
52
|
+
|
53
|
+
@dataclass
|
54
|
+
class Priority:
|
55
|
+
self: str
|
56
|
+
iconUrl: str
|
57
|
+
name: str
|
58
|
+
id: str
|
59
|
+
|
60
|
+
@dataclass
|
61
|
+
class Progress:
|
62
|
+
progress: int
|
63
|
+
total: int
|
64
|
+
|
65
|
+
@dataclass
|
66
|
+
class AggregateProgress:
|
67
|
+
progress: int
|
68
|
+
total: int
|
69
|
+
|
70
|
+
@dataclass
|
71
|
+
class Votes:
|
72
|
+
self: str
|
73
|
+
votes: int
|
74
|
+
hasVoted: bool
|
75
|
+
|
76
|
+
@dataclass
|
77
|
+
class Watches:
|
78
|
+
self: str
|
79
|
+
watchCount: int
|
80
|
+
isWatching: bool
|
81
|
+
|
82
|
+
@dataclass
|
83
|
+
class ProjectCategory:
|
84
|
+
self: str
|
85
|
+
id: str
|
86
|
+
description: str
|
87
|
+
name: str
|
88
|
+
|
89
|
+
@dataclass
|
90
|
+
class Project:
|
91
|
+
self: str
|
92
|
+
id: str
|
93
|
+
key: str
|
94
|
+
name: str
|
95
|
+
projectTypeKey: str
|
96
|
+
simplified: bool
|
97
|
+
avatarUrls: AvatarUrls
|
98
|
+
projectCategory: ProjectCategory
|
99
|
+
|
100
|
+
@dataclass
|
101
|
+
class IssueType:
|
102
|
+
self: str
|
103
|
+
id: str
|
104
|
+
description: str
|
105
|
+
iconUrl: str
|
106
|
+
name: str
|
107
|
+
subtask: bool
|
108
|
+
avatarId: int
|
109
|
+
hierarchyLevel: int
|
110
|
+
|
111
|
+
@dataclass
|
112
|
+
class TimeTracking:
|
113
|
+
originalEstimate: Optional[str] = None
|
114
|
+
remainingEstimate: Optional[str] = None
|
115
|
+
timeSpent: Optional[str] = None
|
116
|
+
|
117
|
+
class JiraIssue(BaseModel):
|
118
|
+
"""JiraIssue.
|
119
|
+
|
120
|
+
A BaseModel to represent a Jira Issue.
|
121
|
+
"""
|
122
|
+
self: str
|
123
|
+
id: str
|
124
|
+
key: str
|
125
|
+
event_type: str
|
126
|
+
timestamp: int
|
127
|
+
webhook_event: str
|
128
|
+
issue_event_type_name: str
|
129
|
+
issue_status: Optional[str]
|
130
|
+
description: Optional[str]
|
131
|
+
summary: str
|
132
|
+
changelog: Optional[Dict]
|
133
|
+
user: Optional[User]
|
134
|
+
issue: dict
|
135
|
+
fields: Optional[Dict]
|
136
|
+
priority: Optional[Priority]
|
137
|
+
status: Optional[Status]
|
138
|
+
creator: Optional[User]
|
139
|
+
reporter: Optional[User]
|
140
|
+
aggregateprogress: AggregateProgress
|
141
|
+
progress: Progress
|
142
|
+
votes: Votes
|
143
|
+
issuetype: IssueType
|
144
|
+
project: Project
|
145
|
+
watches: Watches
|
146
|
+
timetracking: TimeTracking
|
147
|
+
|
148
|
+
class Meta:
|
149
|
+
strict: bool = False
|
150
|
+
frozen: bool = False
|
151
|
+
|
152
|
+
class JiraTrigger(HTTPHook):
|
153
|
+
"""JiraTrigger.
|
154
|
+
|
155
|
+
A Trigger that handles Jira webhooks for issue events.
|
156
|
+
"""
|
157
|
+
|
158
|
+
def __init__(
|
159
|
+
self,
|
160
|
+
*args,
|
161
|
+
secret_token: str = None,
|
162
|
+
**kwargs
|
163
|
+
):
|
164
|
+
super().__init__(*args, **kwargs)
|
165
|
+
self.secret_token = secret_token or JIRA_SECRET_TOKEN
|
166
|
+
self.username = kwargs.get('username', JIRA_USERNAME)
|
167
|
+
self.instance = kwargs.get('instance', JIRA_INSTANCE)
|
168
|
+
self.project = kwargs.get('project', JIRA_PROJECT)
|
169
|
+
self.url = kwargs.get('url', '/api/v1/webhook/jira/')
|
170
|
+
self.methods = ['POST']
|
171
|
+
|
172
|
+
async def post(self, request: web.Request):
|
173
|
+
try:
|
174
|
+
# Verify the request
|
175
|
+
if self.secret_token:
|
176
|
+
signature = request.headers.get('X-Hub-Signature')
|
177
|
+
if not signature:
|
178
|
+
return web.Response(status=401, text='Unauthorized')
|
179
|
+
body = await request.read()
|
180
|
+
computed_signature = 'sha256=' + hmac.new(
|
181
|
+
self.secret_token.encode('utf-8'),
|
182
|
+
body,
|
183
|
+
hashlib.sha256
|
184
|
+
).hexdigest()
|
185
|
+
if not hmac.compare_digest(signature, computed_signature):
|
186
|
+
return web.Response(status=401, text='Unauthorized')
|
187
|
+
payload = json_decoder(body)
|
188
|
+
else:
|
189
|
+
payload = await request.json()
|
190
|
+
# Extract event details
|
191
|
+
webhook_event = payload.get('webhookEvent')
|
192
|
+
timestamp = payload.get('timestamp')
|
193
|
+
user = payload.get('user', {})
|
194
|
+
issue_type = payload.get('issue_event_type_name', 'issue_created')
|
195
|
+
changelog = payload.get('changelog', {})
|
196
|
+
issue = payload.get('issue', {})
|
197
|
+
issue_key = issue.get('key')
|
198
|
+
issue_fields = issue.get('fields', {})
|
199
|
+
issue_status = issue_fields.get('status', {}).get('name')
|
200
|
+
|
201
|
+
# Determine the event type
|
202
|
+
event_type = None
|
203
|
+
if webhook_event == 'jira:issue_created':
|
204
|
+
event_type = 'created'
|
205
|
+
elif webhook_event == 'jira:issue_updated':
|
206
|
+
# Check if the issue was closed
|
207
|
+
changelog = payload.get('changelog', {})
|
208
|
+
items = changelog.get('items', [])
|
209
|
+
for item in items:
|
210
|
+
if item.get('field') == 'status':
|
211
|
+
from_status = item.get('fromString')
|
212
|
+
to_status = item.get('toString')
|
213
|
+
if to_status.lower() == 'closed':
|
214
|
+
event_type = 'closed'
|
215
|
+
else:
|
216
|
+
event_type = 'updated'
|
217
|
+
break
|
218
|
+
else:
|
219
|
+
event_type = 'updated'
|
220
|
+
elif webhook_event == 'jira:issue_deleted':
|
221
|
+
event_type = 'deleted'
|
222
|
+
if event_type:
|
223
|
+
# extracting information from issue:
|
224
|
+
aggregateprogress = issue_fields.get('aggregateprogress', {})
|
225
|
+
issuetype = issue_fields.get('issuetype', {})
|
226
|
+
creator = issue_fields.get('creator', {})
|
227
|
+
reporter = issue_fields.get('reporter', {})
|
228
|
+
project = issue_fields.get('project', {})
|
229
|
+
watches = issue_fields.get('watches', {})
|
230
|
+
timetracking = issue_fields.get('timetracking', {})
|
231
|
+
priority = issue_fields.get('priority', {})
|
232
|
+
# Create the JiraIssue object
|
233
|
+
jira_issue = JiraIssue(
|
234
|
+
event_type=event_type,
|
235
|
+
issue_status=issue_status,
|
236
|
+
timestamp=timestamp,
|
237
|
+
webhook_event=webhook_event,
|
238
|
+
issue_event_type_name=issue_type,
|
239
|
+
aggregateprogress=aggregateprogress,
|
240
|
+
issuetype=issuetype,
|
241
|
+
creator=creator,
|
242
|
+
reporter=reporter,
|
243
|
+
description=issue_fields.get('description'),
|
244
|
+
summary=issue_fields.get('summary'),
|
245
|
+
changelog=changelog,
|
246
|
+
user=user,
|
247
|
+
priority=priority,
|
248
|
+
project=project,
|
249
|
+
watches=watches,
|
250
|
+
timetracking=timetracking,
|
251
|
+
**issue
|
252
|
+
)
|
253
|
+
# Prepare data to pass to actions
|
254
|
+
data = {
|
255
|
+
'webhook_event': webhook_event,
|
256
|
+
'event_type': event_type,
|
257
|
+
'issue_key': issue_key,
|
258
|
+
'issue_fields': issue_fields,
|
259
|
+
'issue': jira_issue
|
260
|
+
}
|
261
|
+
# Run actions
|
262
|
+
result = await self.run_actions(**data)
|
263
|
+
return self.response(
|
264
|
+
response=result,
|
265
|
+
status=self.default_status
|
266
|
+
)
|
267
|
+
else:
|
268
|
+
return web.Response(
|
269
|
+
status=200,
|
270
|
+
text='Event ignored'
|
271
|
+
)
|
272
|
+
except Exception as e:
|
273
|
+
self._logger.error(
|
274
|
+
f"Error processing Jira webhook: {e}"
|
275
|
+
)
|
276
|
+
return web.Response(
|
277
|
+
status=500,
|
278
|
+
text='Jira: Internal Server Error'
|
279
|
+
)
|
@@ -0,0 +1,205 @@
|
|
1
|
+
import asyncio
|
2
|
+
import datetime
|
3
|
+
from typing import Any, Optional
|
4
|
+
from aiohttp import web
|
5
|
+
from azure.identity.aio import ClientSecretCredential
|
6
|
+
from msgraph import GraphRequestAdapter, GraphServiceClient
|
7
|
+
from msgraph.generated.models.subscription import Subscription
|
8
|
+
from msgraph.generated.models.message import Message
|
9
|
+
from kiota_authentication_azure.azure_identity_authentication_provider import AzureIdentityAuthenticationProvider
|
10
|
+
from navconfig.logging import logging
|
11
|
+
from .http import HTTPHook
|
12
|
+
from ...conf import (
|
13
|
+
SHAREPOINT_APP_ID,
|
14
|
+
SHAREPOINT_APP_SECRET,
|
15
|
+
SHAREPOINT_TENANT_ID,
|
16
|
+
SHAREPOINT_TENANT_NAME
|
17
|
+
)
|
18
|
+
|
19
|
+
logging.getLogger(name='azure.identity.aio').setLevel(logging.WARNING)
|
20
|
+
logging.getLogger(name='azure.core').setLevel(logging.WARNING)
|
21
|
+
|
22
|
+
DEFAULT_SCOPES = ["https://graph.microsoft.com/.default"]
|
23
|
+
|
24
|
+
|
25
|
+
class EmailTrigger(HTTPHook):
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
*args,
|
30
|
+
webhook_url: str,
|
31
|
+
tenant_id: Optional[str] = None,
|
32
|
+
client_id: Optional[str] = None,
|
33
|
+
client_secret: Optional[str] = None,
|
34
|
+
resource: Optional[str] = None,
|
35
|
+
**kwargs
|
36
|
+
):
|
37
|
+
super().__init__(*args, **kwargs)
|
38
|
+
self.tenant_id = tenant_id or SHAREPOINT_TENANT_ID
|
39
|
+
self._tenant_name = kwargs.get('tenant', SHAREPOINT_TENANT_NAME)
|
40
|
+
self.client_id = client_id or SHAREPOINT_APP_ID
|
41
|
+
self.client_secret = client_secret or SHAREPOINT_APP_SECRET
|
42
|
+
self.webhook_url = webhook_url
|
43
|
+
self.client_state: str = kwargs.get('client_state', 'flowtask_state')
|
44
|
+
self.changetype: str = kwargs.get('changetype', 'created')
|
45
|
+
self.subscription_id = None
|
46
|
+
self._graph_client: GraphServiceClient = None
|
47
|
+
self._request_adapter: GraphRequestAdapter = None
|
48
|
+
self.renewal_task = None
|
49
|
+
self.renewal_interval = 3600 * 23 # Renew every 23 hours
|
50
|
+
|
51
|
+
def get_client(self):
|
52
|
+
return ClientSecretCredential(
|
53
|
+
tenant_id=self.tenant_id,
|
54
|
+
client_id=self.client_id,
|
55
|
+
client_secret=self.client_secret
|
56
|
+
)
|
57
|
+
|
58
|
+
def get_graph_client(
|
59
|
+
self,
|
60
|
+
client: Any,
|
61
|
+
scopes: Optional[list] = None
|
62
|
+
):
|
63
|
+
if not scopes:
|
64
|
+
scopes = self.scopes
|
65
|
+
return GraphServiceClient(credentials=client, scopes=scopes)
|
66
|
+
|
67
|
+
async def authenticate(self):
|
68
|
+
"""
|
69
|
+
Authenticates the client with Azure AD and initializes the Graph client.
|
70
|
+
|
71
|
+
This method creates a ClientSecretCredential using the tenant ID, client ID,
|
72
|
+
and client secret. It then sets up an AzureIdentityAuthenticationProvider,
|
73
|
+
initializes a GraphRequestAdapter, and finally creates a GraphServiceClient.
|
74
|
+
|
75
|
+
The method doesn't take any parameters as it uses the instance variables
|
76
|
+
for authentication details.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
None
|
80
|
+
"""
|
81
|
+
self._client = self.get_client()
|
82
|
+
auth_provider = AzureIdentityAuthenticationProvider(self._client)
|
83
|
+
self._request_adapter = GraphRequestAdapter(auth_provider)
|
84
|
+
self._graph_client = GraphServiceClient(
|
85
|
+
credentials=self._client,
|
86
|
+
scopes=self.scopes,
|
87
|
+
request_adapter=self._request_adapter
|
88
|
+
)
|
89
|
+
|
90
|
+
async def create_subscription(self):
|
91
|
+
if not self._graph_client:
|
92
|
+
await self.authenticate()
|
93
|
+
|
94
|
+
expiration_datetime = (datetime.datetime.utcnow() + datetime.timedelta(hours=23)).isoformat() + 'Z'
|
95
|
+
subscription = Subscription(
|
96
|
+
change_type=self.changetype,
|
97
|
+
notification_url=self.webhook_url,
|
98
|
+
resource="me/mailFolders('Inbox')/messages",
|
99
|
+
expiration_date_time=expiration_datetime,
|
100
|
+
client_state=self.client_state,
|
101
|
+
latest_supported_tls_version="v1_2",
|
102
|
+
)
|
103
|
+
|
104
|
+
result = await self._graph_client.subscriptions.post(body=subscription)
|
105
|
+
if result:
|
106
|
+
self.subscription_id = result.id
|
107
|
+
self._logger.info(f"Subscription created with ID: {self.subscription_id}")
|
108
|
+
|
109
|
+
else:
|
110
|
+
self._logger.error("Failed to create subscription")
|
111
|
+
raise Exception("Failed to create email subscription")
|
112
|
+
|
113
|
+
async def delete_subscription(self):
|
114
|
+
if not self.subscription_id:
|
115
|
+
return
|
116
|
+
if not self._graph_client:
|
117
|
+
await self.authenticate()
|
118
|
+
|
119
|
+
await self._graph_client.subscriptions.by_subscription_id(self.subscription_id).delete()
|
120
|
+
self._logger.info("Subscription deleted")
|
121
|
+
|
122
|
+
async def renew_subscription(self):
|
123
|
+
while True:
|
124
|
+
await asyncio.sleep(self.renewal_interval)
|
125
|
+
if not self.subscription_id:
|
126
|
+
continue
|
127
|
+
if not self._graph_client:
|
128
|
+
await self.authenticate()
|
129
|
+
|
130
|
+
expiration_datetime = (datetime.datetime.utcnow() + datetime.timedelta(hours=23)).isoformat() + 'Z'
|
131
|
+
subscription = Subscription(
|
132
|
+
expiration_date_time=expiration_datetime
|
133
|
+
)
|
134
|
+
await self._graph_client.subscriptions.by_subscription_id(self.subscription_id).patch(body=subscription)
|
135
|
+
self._logger.info("Subscription renewed")
|
136
|
+
|
137
|
+
async def post(self, request: web.Request):
|
138
|
+
self._logger.info("Received POST request")
|
139
|
+
# Handle validation token
|
140
|
+
validation_token = request.query.get('validationToken')
|
141
|
+
if validation_token:
|
142
|
+
self._logger.info(f"Received validation token: {validation_token}")
|
143
|
+
return web.Response(text=validation_token, status=200)
|
144
|
+
|
145
|
+
# Handle notifications
|
146
|
+
try:
|
147
|
+
data = await request.json()
|
148
|
+
self._logger.info(f"Received notification data: {data}")
|
149
|
+
except Exception as e:
|
150
|
+
self._logger.error(f"Failed to parse request JSON: {e}")
|
151
|
+
return web.Response(status=400)
|
152
|
+
|
153
|
+
# Verify clientState
|
154
|
+
if self.client_state:
|
155
|
+
client_state = data.get('value', [{}])[0].get('clientState')
|
156
|
+
if client_state != self.client_state:
|
157
|
+
self._logger.warning("Invalid clientState in notification")
|
158
|
+
return web.Response(status=202)
|
159
|
+
|
160
|
+
# Process notifications
|
161
|
+
await self.process_notifications(data)
|
162
|
+
return web.Response(status=202)
|
163
|
+
|
164
|
+
async def process_notifications(self, data):
|
165
|
+
notifications = data.get('value', [])
|
166
|
+
for notification in notifications:
|
167
|
+
resource = notification.get('resource')
|
168
|
+
self._logger.info(f"Received notification for resource: {resource}")
|
169
|
+
message_details = await self.get_message_details(resource)
|
170
|
+
# Run actions with the message details
|
171
|
+
await self.run_actions(message_details=message_details)
|
172
|
+
|
173
|
+
async def get_message_details(self, resource):
|
174
|
+
if not self._graph_client:
|
175
|
+
await self.authenticate()
|
176
|
+
|
177
|
+
# The resource should be something like "me/messages/{message-id}"
|
178
|
+
# Extract the message ID
|
179
|
+
parts = resource.split('/')
|
180
|
+
if 'messages' in parts:
|
181
|
+
message_index = parts.index('messages')
|
182
|
+
message_id = parts[message_index + 1]
|
183
|
+
|
184
|
+
message = await self._graph_client.me.messages.by_message_id(message_id).get()
|
185
|
+
return message.serialize()
|
186
|
+
else:
|
187
|
+
self._logger.error(
|
188
|
+
f"Cannot extract message ID from resource: {resource}"
|
189
|
+
)
|
190
|
+
return {}
|
191
|
+
|
192
|
+
async def on_startup(self, app):
|
193
|
+
self._logger.info("Starting EmailTrigger")
|
194
|
+
asyncio.create_task(self._subscription_creation())
|
195
|
+
|
196
|
+
async def _subscription_creation(self):
|
197
|
+
await asyncio.sleep(5)
|
198
|
+
await self.create_subscription()
|
199
|
+
self.renewal_task = asyncio.create_task(self.renew_subscription())
|
200
|
+
|
201
|
+
async def on_shutdown(self, app):
|
202
|
+
self._logger.info("Shutting down EmailTrigger")
|
203
|
+
if self.renewal_task:
|
204
|
+
self.renewal_task.cancel()
|
205
|
+
await self.delete_subscription()
|