flowtask 5.8.4__cp312-cp312-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-312-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-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-312-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-312-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-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-312-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,839 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Any, Optional, List, Dict
|
3
|
+
from datetime import datetime, timedelta
|
4
|
+
from azure.identity.aio import (
|
5
|
+
ClientSecretCredential,
|
6
|
+
OnBehalfOfCredential
|
7
|
+
)
|
8
|
+
import msal
|
9
|
+
from msgraph import GraphServiceClient
|
10
|
+
from msgraph.generated.models.o_data_errors.o_data_error import ODataError
|
11
|
+
from msgraph.generated.models.chat_message import ChatMessage
|
12
|
+
from msgraph.generated.models.chat_message_collection_response import (
|
13
|
+
ChatMessageCollectionResponse
|
14
|
+
)
|
15
|
+
from msgraph.generated.teams.item.channels.get_all_messages.get_all_messages_request_builder import (
|
16
|
+
GetAllMessagesRequestBuilder
|
17
|
+
)
|
18
|
+
from msgraph.generated.chats.item.messages.messages_request_builder import (
|
19
|
+
MessagesRequestBuilder
|
20
|
+
)
|
21
|
+
from msgraph.generated.users.users_request_builder import (
|
22
|
+
UsersRequestBuilder
|
23
|
+
)
|
24
|
+
from kiota_abstractions.base_request_configuration import RequestConfiguration
|
25
|
+
from navconfig.logging import logging
|
26
|
+
from .client import ClientInterface
|
27
|
+
from ..conf import (
|
28
|
+
MS_TEAMS_TENANT_ID,
|
29
|
+
MS_TEAMS_CLIENT_ID,
|
30
|
+
MS_TEAMS_CLIENT_SECRET,
|
31
|
+
DEFAULT_TEAMS_USER
|
32
|
+
)
|
33
|
+
from ..exceptions import ComponentError, ConfigError
|
34
|
+
|
35
|
+
|
36
|
+
logging.getLogger('msal').setLevel(logging.INFO)
|
37
|
+
logging.getLogger('azure').setLevel(logging.WARNING)
|
38
|
+
|
39
|
+
DEFAULT_SCOPES = ["https://graph.microsoft.com/.default"]
|
40
|
+
|
41
|
+
|
42
|
+
def generate_auth_string(user, token):
|
43
|
+
return f"user={user}\x01Auth=Bearer {token}\x01\x01"
|
44
|
+
|
45
|
+
|
46
|
+
class AzureGraph(ClientInterface):
|
47
|
+
"""
|
48
|
+
AzureGraph.
|
49
|
+
|
50
|
+
Overview
|
51
|
+
|
52
|
+
Authentication and authorization Using Azure Identity and Microsoft Graph.
|
53
|
+
"""
|
54
|
+
_credentials: dict = {
|
55
|
+
"tenant_id": str,
|
56
|
+
"client_id": str,
|
57
|
+
"client_secret": str,
|
58
|
+
"user": str,
|
59
|
+
"password": str
|
60
|
+
}
|
61
|
+
|
62
|
+
def __init__(
|
63
|
+
self,
|
64
|
+
tenant_id: str = None,
|
65
|
+
client_id: str = None,
|
66
|
+
client_secret: str = None,
|
67
|
+
scopes: list = None,
|
68
|
+
**kwargs,
|
69
|
+
) -> None:
|
70
|
+
self.tenant_id = tenant_id or MS_TEAMS_TENANT_ID
|
71
|
+
# credentials:
|
72
|
+
self.client_id = client_id or MS_TEAMS_CLIENT_ID
|
73
|
+
self.client_secret = client_secret or MS_TEAMS_CLIENT_SECRET
|
74
|
+
# User delegated credentials:
|
75
|
+
self.user = kwargs.pop('user', None)
|
76
|
+
self.password = kwargs.pop('password', None)
|
77
|
+
self.user_credentials = None
|
78
|
+
# scopes:
|
79
|
+
self.scopes = scopes if scopes is not None else DEFAULT_SCOPES
|
80
|
+
kwargs['no_host'] = True
|
81
|
+
kwargs['credentials'] = kwargs.get(
|
82
|
+
"credentials", {
|
83
|
+
"client_id": self.client_id,
|
84
|
+
"tenant_id": self.tenant_id,
|
85
|
+
"client_secret": self.client_secret,
|
86
|
+
"user": self.user,
|
87
|
+
"password": self.password
|
88
|
+
}
|
89
|
+
)
|
90
|
+
super(AzureGraph, self).__init__(
|
91
|
+
**kwargs
|
92
|
+
)
|
93
|
+
self._client = None
|
94
|
+
self._graph = None
|
95
|
+
self.token_uri = f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token"
|
96
|
+
self.graph_uri = "https://graph.microsoft.com/v1.0"
|
97
|
+
# Logging:
|
98
|
+
if not hasattr(self, '_logger'):
|
99
|
+
self._logger = logging.getLogger('AzureGraph')
|
100
|
+
|
101
|
+
@property
|
102
|
+
def graph(self):
|
103
|
+
return self._graph
|
104
|
+
|
105
|
+
@property
|
106
|
+
def client(self):
|
107
|
+
return self._client
|
108
|
+
|
109
|
+
## Override the Async-Context:
|
110
|
+
async def __aenter__(self) -> "AzureGraph":
|
111
|
+
return self
|
112
|
+
|
113
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
114
|
+
# clean up anything you need to clean up
|
115
|
+
return self.close()
|
116
|
+
|
117
|
+
def get_client(self, kind: str = 'client_credentials', token: str = None):
|
118
|
+
if not self.credentials:
|
119
|
+
raise ConfigError(
|
120
|
+
"Azure Graph: Credentials are required to create a client."
|
121
|
+
)
|
122
|
+
tenant_id = self.credentials.get('tenant_id', self.tenant_id)
|
123
|
+
client_id = self.credentials.get('client_id', self.client_id)
|
124
|
+
client_secret = self.credentials.get('client_secret', self.client_secret)
|
125
|
+
# fix the token URL
|
126
|
+
self.token_uri = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
|
127
|
+
client = None
|
128
|
+
# TODO: other type of clients
|
129
|
+
if kind == 'client_credentials':
|
130
|
+
client = ClientSecretCredential(
|
131
|
+
tenant_id=tenant_id,
|
132
|
+
client_id=client_id,
|
133
|
+
client_secret=client_secret
|
134
|
+
)
|
135
|
+
elif kind == 'on_behalf_of':
|
136
|
+
client = OnBehalfOfCredential(
|
137
|
+
client_id=client_id,
|
138
|
+
client_secret=client_secret,
|
139
|
+
tenant_id=tenant_id,
|
140
|
+
user_assertion=token
|
141
|
+
)
|
142
|
+
return client
|
143
|
+
|
144
|
+
def get_graph_client(self, client: Any, token: str = None, scopes: Optional[list] = None):
|
145
|
+
if not scopes:
|
146
|
+
scopes = self.scopes
|
147
|
+
return GraphServiceClient(credentials=client, scopes=scopes)
|
148
|
+
|
149
|
+
async def get_token(self):
|
150
|
+
"""
|
151
|
+
Retrieves an access token for Microsoft Graph API using ClientSecretCredential.
|
152
|
+
"""
|
153
|
+
if not self._client:
|
154
|
+
self._client = self.get_client()
|
155
|
+
tenant_id = self.credentials.get('tenant_id', self.tenant_id)
|
156
|
+
try:
|
157
|
+
# Use the credential to obtain an access token
|
158
|
+
token = await self._client.get_token(
|
159
|
+
self.scopes[0],
|
160
|
+
tenant_id=tenant_id
|
161
|
+
)
|
162
|
+
self._logger.info(
|
163
|
+
"Access token retrieved successfully."
|
164
|
+
)
|
165
|
+
return token.token, token
|
166
|
+
except Exception as e:
|
167
|
+
self._logger.error(
|
168
|
+
f"Failed to retrieve access token: {e}"
|
169
|
+
)
|
170
|
+
raise ComponentError(
|
171
|
+
f"Could not obtain access token: {e}"
|
172
|
+
)
|
173
|
+
|
174
|
+
async def get_user_info(self, user_principal_name: str) -> dict:
|
175
|
+
"""
|
176
|
+
Fetches user information from Microsoft Graph API based on userPrincipalName.
|
177
|
+
|
178
|
+
Args:
|
179
|
+
user_principal_name (str): The user principal name (UPN) of the user to fetch info for.
|
180
|
+
|
181
|
+
Returns:
|
182
|
+
dict: User information as a dictionary.
|
183
|
+
"""
|
184
|
+
try:
|
185
|
+
if not self._graph:
|
186
|
+
raise ComponentError(
|
187
|
+
"Graph client not initialized. Please call 'open' first."
|
188
|
+
)
|
189
|
+
|
190
|
+
# Fetch the user info using the Graph client
|
191
|
+
user_info = await self._graph.users[user_principal_name].get()
|
192
|
+
self._logger.info(
|
193
|
+
f"Retrieved information for user: {user_principal_name}"
|
194
|
+
)
|
195
|
+
return user_info
|
196
|
+
except Exception as e:
|
197
|
+
self._logger.error(
|
198
|
+
f"Failed to retrieve user info for {user_principal_name}: {e}"
|
199
|
+
)
|
200
|
+
raise ComponentError(f"Could not retrieve user info: {e}")
|
201
|
+
|
202
|
+
def user_auth(self, username: str, password: str, scopes: list = None) -> dict:
|
203
|
+
tenant_id = self.credentials.get('tenant_id', self.tenant_id)
|
204
|
+
authority_url = f'https://login.microsoftonline.com/{tenant_id}'
|
205
|
+
client_id = self.credentials.get("client_id", self.client_id)
|
206
|
+
|
207
|
+
if not scopes:
|
208
|
+
scopes = ["https://graph.microsoft.com/.default"]
|
209
|
+
app = msal.PublicClientApplication(
|
210
|
+
authority=authority_url,
|
211
|
+
client_id=client_id,
|
212
|
+
client_credential=None
|
213
|
+
)
|
214
|
+
result = app.acquire_token_by_username_password(
|
215
|
+
username,
|
216
|
+
password,
|
217
|
+
scopes=scopes
|
218
|
+
)
|
219
|
+
if "access_token" not in result:
|
220
|
+
error_message = result.get('error_description', 'Unknown error')
|
221
|
+
error_code = result.get('error', 'Unknown error code')
|
222
|
+
raise RuntimeError(
|
223
|
+
f"Failed to obtain access token: {error_code} - {error_message}"
|
224
|
+
)
|
225
|
+
return result
|
226
|
+
|
227
|
+
def close(self, timeout: int = 1):
|
228
|
+
self._client = None
|
229
|
+
self._graph = None
|
230
|
+
|
231
|
+
def open(self, **kwargs) -> "AzureGraph":
|
232
|
+
"""open.
|
233
|
+
Starts (open) a connection to Microsoft Graph Service.
|
234
|
+
"""
|
235
|
+
self._client = self.get_client()
|
236
|
+
self._graph = self.get_graph_client(self._client)
|
237
|
+
self.user = self.credentials.get('user', self.user)
|
238
|
+
self.password = self.credentials.get('password', self.password)
|
239
|
+
if self.user and self.password:
|
240
|
+
self.user_credentials = self.user_auth(
|
241
|
+
username=self.user,
|
242
|
+
password=self.password,
|
243
|
+
scopes=self.scopes
|
244
|
+
)
|
245
|
+
return self
|
246
|
+
|
247
|
+
async def get_msteams_channel_messages(
|
248
|
+
self,
|
249
|
+
team_id: str,
|
250
|
+
channel_id: str,
|
251
|
+
start_time: Optional[str] = None,
|
252
|
+
end_time: Optional[str] = None,
|
253
|
+
max_messages: Optional[int] = None
|
254
|
+
) -> List[Dict]:
|
255
|
+
"""
|
256
|
+
Fetches messages from a Teams channel.
|
257
|
+
|
258
|
+
Args:
|
259
|
+
team_id (str): The ID of the team.
|
260
|
+
channel_id (str): The ID of the channel.
|
261
|
+
start_time (str, optional): ISO 8601 formatted start time to filter messages.
|
262
|
+
end_time (str, optional): ISO 8601 formatted end time to filter messages.
|
263
|
+
max_messages (int, optional): Maximum number of messages to retrieve.
|
264
|
+
|
265
|
+
Returns:
|
266
|
+
List[Dict]: A list of message objects.
|
267
|
+
"""
|
268
|
+
if not self._graph:
|
269
|
+
raise ComponentError(
|
270
|
+
"Graph client not initialized. Please call 'open' first."
|
271
|
+
)
|
272
|
+
|
273
|
+
messages = []
|
274
|
+
print('Credentials <>', self.credentials)
|
275
|
+
_filter = f"lastModifiedDateTime gt {start_time!s} and lastModifiedDateTime lt {end_time!s}"
|
276
|
+
print('Filter > ', _filter)
|
277
|
+
try:
|
278
|
+
query_params = GetAllMessagesRequestBuilder.GetAllMessagesRequestBuilderGetQueryParameters(
|
279
|
+
filter=_filter
|
280
|
+
)
|
281
|
+
|
282
|
+
request_configuration = RequestConfiguration(
|
283
|
+
query_parameters=query_params,
|
284
|
+
)
|
285
|
+
|
286
|
+
messages = await self._graph.teams.by_team_id(team_id).channels.get_all_messages.get(
|
287
|
+
request_configuration=request_configuration
|
288
|
+
)
|
289
|
+
|
290
|
+
print('Messages ', messages)
|
291
|
+
return messages
|
292
|
+
except Exception as e:
|
293
|
+
self._logger.error(
|
294
|
+
f"Failed to retrieve channel messages: {e}"
|
295
|
+
)
|
296
|
+
raise ComponentError(
|
297
|
+
f"Could not retrieve channel messages: {e}"
|
298
|
+
)
|
299
|
+
|
300
|
+
def _is_within_time_range(
|
301
|
+
self,
|
302
|
+
message_time_str: str,
|
303
|
+
start_time: Optional[str],
|
304
|
+
end_time: Optional[str]
|
305
|
+
) -> bool:
|
306
|
+
"""
|
307
|
+
Checks if a message's time is within the specified time range.
|
308
|
+
|
309
|
+
Args:
|
310
|
+
message_time_str (str): The message's creation time as an ISO 8601 string.
|
311
|
+
start_time (str, optional): ISO 8601 formatted start time.
|
312
|
+
end_time (str, optional): ISO 8601 formatted end time.
|
313
|
+
|
314
|
+
Returns:
|
315
|
+
bool: True if within range, False otherwise.
|
316
|
+
"""
|
317
|
+
message_time = datetime.fromisoformat(message_time_str.rstrip('Z'))
|
318
|
+
|
319
|
+
if start_time:
|
320
|
+
start = datetime.fromisoformat(start_time.rstrip('Z'))
|
321
|
+
if message_time < start:
|
322
|
+
return False
|
323
|
+
|
324
|
+
if end_time:
|
325
|
+
end = datetime.fromisoformat(end_time.rstrip('Z'))
|
326
|
+
if message_time > end:
|
327
|
+
return False
|
328
|
+
|
329
|
+
return True
|
330
|
+
|
331
|
+
async def get_channel_details(self, team_id: str, channel_id: str) -> Dict:
|
332
|
+
"""
|
333
|
+
Fetches details of a Teams channel.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
team_id (str): The ID of the team.
|
337
|
+
channel_id (str): The ID of the channel.
|
338
|
+
|
339
|
+
Returns:
|
340
|
+
Dict: A dictionary containing channel details.
|
341
|
+
"""
|
342
|
+
if not self._graph:
|
343
|
+
raise ComponentError(
|
344
|
+
"Graph client not initialized. Please call 'open' first."
|
345
|
+
)
|
346
|
+
|
347
|
+
try:
|
348
|
+
channel_details = await self._graph.teams.by_team_id(team_id).channels.by_channel_id(channel_id).get()
|
349
|
+
|
350
|
+
print('CHANNEL DETAILS > ', channel_details)
|
351
|
+
self._logger.info(
|
352
|
+
f"Retrieved details for channel: {channel_details.get('displayName')}"
|
353
|
+
)
|
354
|
+
return channel_details
|
355
|
+
except Exception as e:
|
356
|
+
self._logger.error(
|
357
|
+
f"Failed to retrieve channel details: {e}"
|
358
|
+
)
|
359
|
+
raise ComponentError(
|
360
|
+
f"Could not retrieve channel details: {e}"
|
361
|
+
)
|
362
|
+
|
363
|
+
async def get_channel_members(self, team_id: str, channel_id: str) -> List[Dict]:
|
364
|
+
"""
|
365
|
+
Fetches the list of members in a Teams channel.
|
366
|
+
|
367
|
+
Args:
|
368
|
+
team_id (str): The ID of the team.
|
369
|
+
channel_id (str): The ID of the channel.
|
370
|
+
|
371
|
+
Returns:
|
372
|
+
List[Dict]: A list of member objects.
|
373
|
+
"""
|
374
|
+
if not self._graph:
|
375
|
+
raise ComponentError(
|
376
|
+
"Graph client not initialized. Please call 'open' first."
|
377
|
+
)
|
378
|
+
|
379
|
+
members = []
|
380
|
+
endpoint = self._graph.teams[team_id].channels[channel_id].members
|
381
|
+
query_params = {
|
382
|
+
'$top': 50 # Adjust as needed
|
383
|
+
}
|
384
|
+
|
385
|
+
# Initial request
|
386
|
+
request = endpoint.get(
|
387
|
+
query_parameters=query_params
|
388
|
+
)
|
389
|
+
|
390
|
+
try:
|
391
|
+
# Pagination loop
|
392
|
+
while request:
|
393
|
+
response = await self._graph.send_request(request)
|
394
|
+
response_data = await response.json()
|
395
|
+
|
396
|
+
batch_members = response_data.get('value', [])
|
397
|
+
members.extend(batch_members)
|
398
|
+
|
399
|
+
# Check for pagination
|
400
|
+
next_link = response_data.get('@odata.nextLink')
|
401
|
+
if next_link:
|
402
|
+
# Create a new request for the next page
|
403
|
+
request = self._graph.create_request("GET", next_link)
|
404
|
+
else:
|
405
|
+
break
|
406
|
+
|
407
|
+
self._logger.info(
|
408
|
+
f"Retrieved {len(members)} members from channel."
|
409
|
+
)
|
410
|
+
return members
|
411
|
+
except Exception as e:
|
412
|
+
self._logger.error(
|
413
|
+
f"Failed to retrieve channel members: {e}"
|
414
|
+
)
|
415
|
+
raise ComponentError(f"Could not retrieve channel members: {e}")
|
416
|
+
|
417
|
+
async def find_channel_by_name(self, channel_name: str):
|
418
|
+
if not self._graph:
|
419
|
+
|
420
|
+
raise ComponentError(
|
421
|
+
"Graph client not initialized. Please call 'open' first."
|
422
|
+
)
|
423
|
+
|
424
|
+
# List all teams
|
425
|
+
teams = await self._graph.teams.get()
|
426
|
+
print(f"Total Teams Found: {len(teams)}")
|
427
|
+
|
428
|
+
for team in teams:
|
429
|
+
team_id = team.get('id')
|
430
|
+
team_display_name = team.get(
|
431
|
+
'displayName',
|
432
|
+
'Unknown Team'
|
433
|
+
)
|
434
|
+
print(f"Checking Team: {team_display_name} (ID: {team_id})")
|
435
|
+
|
436
|
+
# List channels in the team
|
437
|
+
channels = await self._graph.list_channels_in_team(team_id)
|
438
|
+
print(
|
439
|
+
f"Total Channels in Team '{team_display_name}': {len(channels)}"
|
440
|
+
)
|
441
|
+
|
442
|
+
# Search for the channel by name
|
443
|
+
for channel in channels:
|
444
|
+
channel_display_name = channel.get('displayName', '')
|
445
|
+
if channel_display_name.lower() == channel_name.lower():
|
446
|
+
channel_id = channel.get('id')
|
447
|
+
print(
|
448
|
+
f"Channel Found: {channel_display_name}"
|
449
|
+
)
|
450
|
+
print(
|
451
|
+
f"Team ID: {team_id}"
|
452
|
+
)
|
453
|
+
print(
|
454
|
+
f"Channel ID: {channel_id}"
|
455
|
+
)
|
456
|
+
|
457
|
+
# return team_id and channel_id
|
458
|
+
return team_id, channel_id
|
459
|
+
|
460
|
+
async def list_chats(self, user: str) -> List[Dict]:
|
461
|
+
"""
|
462
|
+
Lists all chats accessible to the application or user.
|
463
|
+
|
464
|
+
Returns:
|
465
|
+
List[Dict]: A list of chat objects.
|
466
|
+
"""
|
467
|
+
if not self._graph:
|
468
|
+
raise ComponentError(
|
469
|
+
"Graph client not initialized. Please call 'open' first."
|
470
|
+
)
|
471
|
+
|
472
|
+
try:
|
473
|
+
chats = []
|
474
|
+
chats = await self._graph.users.by_user_id(
|
475
|
+
user
|
476
|
+
).chats.get()
|
477
|
+
|
478
|
+
# getting chats from ChatCollectionResponse:
|
479
|
+
return chats.value
|
480
|
+
|
481
|
+
except Exception as e:
|
482
|
+
self._logger.error(f"Failed to retrieve chats: {e}")
|
483
|
+
raise ComponentError(f"Could not retrieve chats: {e}")
|
484
|
+
|
485
|
+
async def list_user_chats(self, user: str) -> List[Dict]:
|
486
|
+
"""
|
487
|
+
Lists all chats accessible to the User.
|
488
|
+
|
489
|
+
Returns:
|
490
|
+
List[Dict]: A list of chat objects.
|
491
|
+
"""
|
492
|
+
if not self._graph:
|
493
|
+
raise ComponentError(
|
494
|
+
"Graph client not initialized. Please call 'open' first."
|
495
|
+
)
|
496
|
+
|
497
|
+
try:
|
498
|
+
chats = []
|
499
|
+
chats = await self._graph.users.by_user_id(
|
500
|
+
user
|
501
|
+
).chats.get()
|
502
|
+
|
503
|
+
# getting chats from ChatCollectionResponse:
|
504
|
+
return chats.value
|
505
|
+
|
506
|
+
except Exception as e:
|
507
|
+
self._logger.error(
|
508
|
+
f"Failed to retrieve chats: {e}"
|
509
|
+
)
|
510
|
+
raise ComponentError(
|
511
|
+
f"Could not retrieve chats: {e}"
|
512
|
+
)
|
513
|
+
|
514
|
+
async def find_chat_by_name(self, chat_name: str, user: str = None) -> Optional[str]:
|
515
|
+
"""
|
516
|
+
Finds a chat by its name (topic) and returns its chat_id.
|
517
|
+
|
518
|
+
Args:
|
519
|
+
chat_name (str): The name of the chat to find.
|
520
|
+
|
521
|
+
Returns:
|
522
|
+
Optional[str]: The chat_id if found, else None.
|
523
|
+
"""
|
524
|
+
chats = await self.list_chats(user or DEFAULT_TEAMS_USER)
|
525
|
+
for chat in chats:
|
526
|
+
if chat.chat_type.Group == 'group' and chat.topic == chat_name:
|
527
|
+
return chat
|
528
|
+
return None
|
529
|
+
|
530
|
+
async def get_chat_messages(
|
531
|
+
self,
|
532
|
+
chat_id: str,
|
533
|
+
start_time: Optional[str] = None,
|
534
|
+
end_time: Optional[str] = None,
|
535
|
+
messages_per_page: int = 50,
|
536
|
+
max_messages: Optional[int] = None
|
537
|
+
) -> Optional[List]:
|
538
|
+
"""
|
539
|
+
Get chat messages.
|
540
|
+
|
541
|
+
Args:
|
542
|
+
chat_id (str): Id of Chat
|
543
|
+
|
544
|
+
Returns:
|
545
|
+
Optional[List]: All Chat Messages based on criteria.
|
546
|
+
"""
|
547
|
+
args = {
|
548
|
+
"orderby": ["lastModifiedDateTime desc"]
|
549
|
+
}
|
550
|
+
args['top'] = min(messages_per_page, 50) # max 50 message per-page
|
551
|
+
if start_time and end_time:
|
552
|
+
args['filter'] = f"lastModifiedDateTime gt {start_time!s} and lastModifiedDateTime lt {end_time!s}"
|
553
|
+
|
554
|
+
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
|
555
|
+
**args
|
556
|
+
)
|
557
|
+
request_configuration = RequestConfiguration(
|
558
|
+
query_parameters=query_params,
|
559
|
+
)
|
560
|
+
|
561
|
+
messages = []
|
562
|
+
response = await self._graph.chats.by_chat_id(chat_id).messages.get(
|
563
|
+
request_configuration=request_configuration
|
564
|
+
)
|
565
|
+
|
566
|
+
if isinstance(response, ChatMessageCollectionResponse):
|
567
|
+
messages.extend(response.value)
|
568
|
+
else:
|
569
|
+
self._logger.warning(
|
570
|
+
f"Unable to find Chat messages over {chat_id}, {response}"
|
571
|
+
)
|
572
|
+
return []
|
573
|
+
|
574
|
+
try:
|
575
|
+
next_link = response.odata_next_link
|
576
|
+
|
577
|
+
while next_link:
|
578
|
+
response = await self._graph.chats.with_url(next_link).get()
|
579
|
+
if not response:
|
580
|
+
break
|
581
|
+
|
582
|
+
# for user in users.value:
|
583
|
+
messages.extend(response.value)
|
584
|
+
|
585
|
+
# Check if we have reached the max_users limit
|
586
|
+
if max_messages and len(messages) >= max_messages:
|
587
|
+
# Trim the list to the max_users limit
|
588
|
+
messages = messages[:max_messages]
|
589
|
+
break
|
590
|
+
|
591
|
+
next_link = response.odata_next_link
|
592
|
+
except Exception as exc:
|
593
|
+
raise ComponentError(
|
594
|
+
f"Could not retrieve chat messages: {exc}"
|
595
|
+
)
|
596
|
+
# returning the messages
|
597
|
+
return messages
|
598
|
+
|
599
|
+
async def get_all_items(self, client, initial_request):
|
600
|
+
items = []
|
601
|
+
|
602
|
+
# Perform the initial request
|
603
|
+
response = await initial_request()
|
604
|
+
|
605
|
+
# Add initial response items
|
606
|
+
if hasattr(response, 'value'):
|
607
|
+
items.extend(response.value)
|
608
|
+
|
609
|
+
# Check for next link and paginate
|
610
|
+
while hasattr(response, 'odata_next_link') and response.odata_next_link:
|
611
|
+
# Use the next link to get the next set of results
|
612
|
+
next_request = client.request_adapter.send_async(
|
613
|
+
response.get_next_page_request_information(),
|
614
|
+
response_type=type(response)
|
615
|
+
)
|
616
|
+
response = await next_request
|
617
|
+
|
618
|
+
if hasattr(response, 'value'):
|
619
|
+
items.extend(response.value)
|
620
|
+
|
621
|
+
return items
|
622
|
+
|
623
|
+
async def get_user_photo(self, user_id: str):
|
624
|
+
try:
|
625
|
+
photo = await self._graph.users.by_user_id(user_id).photo.content.get()
|
626
|
+
return photo # This returns the photo content as a binary stream
|
627
|
+
except Exception as e:
|
628
|
+
if "ImageNotFoundException" in str(e) or "404" in str(e):
|
629
|
+
# Return None or an alternative to indicate no photo found
|
630
|
+
return None
|
631
|
+
self._logger.error(
|
632
|
+
f"Failed to retrieve photo for user {user_id}: {e}"
|
633
|
+
)
|
634
|
+
return None
|
635
|
+
|
636
|
+
async def list_users(
|
637
|
+
self,
|
638
|
+
fields: list = [
|
639
|
+
"id",
|
640
|
+
"displayName",
|
641
|
+
"surname",
|
642
|
+
"givenName",
|
643
|
+
"mail",
|
644
|
+
"department",
|
645
|
+
"jobTitle",
|
646
|
+
"officeLocation",
|
647
|
+
"mobilePhone",
|
648
|
+
"userPrincipalName",
|
649
|
+
"createdDateTime"
|
650
|
+
],
|
651
|
+
users_per_page: int = 50,
|
652
|
+
max_users: int = None,
|
653
|
+
with_photo: bool = True,
|
654
|
+
order_by: str = "displayName",
|
655
|
+
sort_order: str = "asc"
|
656
|
+
):
|
657
|
+
args = {
|
658
|
+
"select": fields,
|
659
|
+
"top": min(users_per_page, 50), # Limit to 50 users per page
|
660
|
+
"orderby": f"{order_by} {sort_order}"
|
661
|
+
}
|
662
|
+
# Define the initial request configuration (select specific fields if needed)
|
663
|
+
query_params = UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
|
664
|
+
**args
|
665
|
+
)
|
666
|
+
request_config = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration(
|
667
|
+
query_parameters=query_params
|
668
|
+
)
|
669
|
+
|
670
|
+
users_list = []
|
671
|
+
|
672
|
+
users = await self._graph.users.get(request_configuration=request_config)
|
673
|
+
# for user in users.value:
|
674
|
+
users_list.extend(users.value)
|
675
|
+
|
676
|
+
next_link = users.odata_next_link
|
677
|
+
while next_link:
|
678
|
+
users = await self._graph.users.with_url(next_link).get()
|
679
|
+
if not users:
|
680
|
+
break
|
681
|
+
|
682
|
+
# for user in users.value:
|
683
|
+
users_list.extend(users.value)
|
684
|
+
|
685
|
+
# Check if we have reached the max_users limit
|
686
|
+
if max_users and len(users_list) >= max_users:
|
687
|
+
# Trim the list to the max_users limit
|
688
|
+
users_list = users_list[:max_users]
|
689
|
+
break
|
690
|
+
|
691
|
+
next_link = users.odata_next_link
|
692
|
+
|
693
|
+
if with_photo is True:
|
694
|
+
for user in users_list:
|
695
|
+
user_photo = await self.get_user_photo(user.id)
|
696
|
+
if user_photo:
|
697
|
+
user.photo = user_photo
|
698
|
+
else:
|
699
|
+
user.photo = "No photo available"
|
700
|
+
|
701
|
+
# Sort the users locally by createdDateTime in ascending order
|
702
|
+
if len(users_list) > 0 and getattr(users_list[0], 'created_date_time', None) is not None:
|
703
|
+
users_list.sort(key=lambda user: user.created_date_time)
|
704
|
+
return users_list
|
705
|
+
|
706
|
+
async def get_user(self, user_id: str) -> Dict:
|
707
|
+
"""
|
708
|
+
Fetches user information from Microsoft Graph API based on user ID.
|
709
|
+
|
710
|
+
Args:
|
711
|
+
user_id (str): The Azure AD object ID of the user.
|
712
|
+
|
713
|
+
Returns:
|
714
|
+
Dict: User information as a dictionary.
|
715
|
+
"""
|
716
|
+
if not self._graph:
|
717
|
+
raise ComponentError(
|
718
|
+
"Graph client not initialized. Please call 'open' first."
|
719
|
+
)
|
720
|
+
|
721
|
+
try:
|
722
|
+
# Use the email (userPrincipalName) to get the user
|
723
|
+
return await self._graph.users.by_user_id(user_id).get()
|
724
|
+
except Exception as e:
|
725
|
+
if "Insufficient privileges" in str(e):
|
726
|
+
self._logger.error(
|
727
|
+
"Please ensure your app has User.Read.All or Directory.Read.All permissions."
|
728
|
+
)
|
729
|
+
else:
|
730
|
+
self._logger.error(
|
731
|
+
f"Failed to retrieve user with email {user_id}: {e}"
|
732
|
+
)
|
733
|
+
raise ComponentError(
|
734
|
+
f"Could not retrieve user info: {e}"
|
735
|
+
)
|
736
|
+
|
737
|
+
def _filter_messages_by_user(self, messages: list, user: object):
|
738
|
+
filtered_messages = []
|
739
|
+
user_id = user.id
|
740
|
+
for message in messages:
|
741
|
+
if isinstance(message, ChatMessage):
|
742
|
+
if (message.from_ and message.from_.user and message.from_.user.id == user_id) or message.reply_to_id:
|
743
|
+
filtered_messages.append(message)
|
744
|
+
return filtered_messages
|
745
|
+
|
746
|
+
## MS Teams Chats and messages:
|
747
|
+
async def user_chat_messages(
|
748
|
+
self,
|
749
|
+
user: object,
|
750
|
+
chat_id: str,
|
751
|
+
start_time: Optional[str] = None,
|
752
|
+
end_time: Optional[str] = None,
|
753
|
+
messages_per_page: int = 50,
|
754
|
+
max_messages: Optional[int] = None,
|
755
|
+
max_retries: int = 3
|
756
|
+
) -> Optional[List]:
|
757
|
+
"""
|
758
|
+
Get User chat messages.
|
759
|
+
|
760
|
+
Args:
|
761
|
+
chat_id (str): Id of Chat
|
762
|
+
|
763
|
+
Returns:
|
764
|
+
Optional[List]: All Chat Messages based on criteria.
|
765
|
+
"""
|
766
|
+
args = {
|
767
|
+
"orderby": ["lastModifiedDateTime desc"]
|
768
|
+
}
|
769
|
+
args['top'] = min(messages_per_page, 50) # max 50 message per-page
|
770
|
+
if start_time and end_time:
|
771
|
+
if isinstance(start_time, datetime):
|
772
|
+
start_time = start_time.isoformat() + 'Z'
|
773
|
+
if isinstance(end_time, datetime):
|
774
|
+
end_time = end_time.isoformat() + 'Z'
|
775
|
+
args['filter'] = f"lastModifiedDateTime gt {start_time!s} and lastModifiedDateTime lt {end_time!s}"
|
776
|
+
else:
|
777
|
+
start_time = (datetime.utcnow() - timedelta(days=1)).isoformat() + 'Z'
|
778
|
+
end_time = datetime.utcnow().isoformat() + 'Z'
|
779
|
+
args['filter'] = f"lastModifiedDateTime gt {start_time} and lastModifiedDateTime lt {end_time}"
|
780
|
+
|
781
|
+
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
|
782
|
+
**args
|
783
|
+
)
|
784
|
+
request_configuration = RequestConfiguration(
|
785
|
+
query_parameters=query_params,
|
786
|
+
)
|
787
|
+
|
788
|
+
messages = []
|
789
|
+
response = await self._graph.chats.by_chat_id(chat_id).messages.get(
|
790
|
+
request_configuration=request_configuration
|
791
|
+
)
|
792
|
+
|
793
|
+
if isinstance(response, ChatMessageCollectionResponse):
|
794
|
+
messages.extend(response.value)
|
795
|
+
else:
|
796
|
+
self._logger.warning(
|
797
|
+
f"Unable to find Chat messages over {chat_id}, {response}"
|
798
|
+
)
|
799
|
+
return []
|
800
|
+
|
801
|
+
# Filter messages based on the user's `id`
|
802
|
+
messages = self._filter_messages_by_user(messages, user)
|
803
|
+
next_link = response.odata_next_link
|
804
|
+
|
805
|
+
for attempt in range(max_retries):
|
806
|
+
try:
|
807
|
+
# retry for don't loose API calls
|
808
|
+
while next_link:
|
809
|
+
response = await self._graph.chats.with_url(next_link).get()
|
810
|
+
if not response:
|
811
|
+
break
|
812
|
+
|
813
|
+
# Check if we have reached the max_users limit
|
814
|
+
if max_messages and len(messages) >= max_messages:
|
815
|
+
# Trim the list to the max_users limit
|
816
|
+
messages = messages[:max_messages]
|
817
|
+
break
|
818
|
+
|
819
|
+
# for user in users.value:
|
820
|
+
filtered = self._filter_messages_by_user(response.value, user)
|
821
|
+
messages.extend(filtered)
|
822
|
+
|
823
|
+
next_link = response.odata_next_link
|
824
|
+
except ODataError as exc:
|
825
|
+
if exc.error.code == "TooManyRequests":
|
826
|
+
retry_after = exc.error.inner_error.additional_data.get('Retry-After', None)
|
827
|
+
if retry_after:
|
828
|
+
wait_time = int(retry_after)
|
829
|
+
print(f"Rate limit hit. Retrying in {wait_time} seconds...")
|
830
|
+
await asyncio.sleep(wait_time)
|
831
|
+
else:
|
832
|
+
print("Rate limit hit. Retrying with exponential backoff...")
|
833
|
+
await asyncio.sleep(2 ** attempt)
|
834
|
+
except Exception as exc:
|
835
|
+
raise ComponentError(
|
836
|
+
f"Could not retrieve chat messages: {exc}"
|
837
|
+
)
|
838
|
+
# returning the messages
|
839
|
+
return messages
|