flowtask 5.8.4__cp39-cp39-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.
Files changed (470) hide show
  1. flowtask/__init__.py +93 -0
  2. flowtask/__main__.py +38 -0
  3. flowtask/bots/__init__.py +6 -0
  4. flowtask/bots/check.py +93 -0
  5. flowtask/bots/codebot.py +51 -0
  6. flowtask/components/ASPX.py +148 -0
  7. flowtask/components/AddDataset.py +352 -0
  8. flowtask/components/Amazon.py +523 -0
  9. flowtask/components/AutoTask.py +314 -0
  10. flowtask/components/Azure.py +80 -0
  11. flowtask/components/AzureUsers.py +106 -0
  12. flowtask/components/BaseAction.py +91 -0
  13. flowtask/components/BaseLoop.py +198 -0
  14. flowtask/components/BestBuy.py +800 -0
  15. flowtask/components/CSVToGCS.py +120 -0
  16. flowtask/components/CompanyScraper/__init__.py +1 -0
  17. flowtask/components/CompanyScraper/parsers/__init__.py +6 -0
  18. flowtask/components/CompanyScraper/parsers/base.py +102 -0
  19. flowtask/components/CompanyScraper/parsers/explorium.py +192 -0
  20. flowtask/components/CompanyScraper/parsers/leadiq.py +206 -0
  21. flowtask/components/CompanyScraper/parsers/rocket.py +133 -0
  22. flowtask/components/CompanyScraper/parsers/siccode.py +109 -0
  23. flowtask/components/CompanyScraper/parsers/visualvisitor.py +130 -0
  24. flowtask/components/CompanyScraper/parsers/zoominfo.py +118 -0
  25. flowtask/components/CompanyScraper/scrapper.py +1054 -0
  26. flowtask/components/CopyTo.py +177 -0
  27. flowtask/components/CopyToBigQuery.py +243 -0
  28. flowtask/components/CopyToMongoDB.py +291 -0
  29. flowtask/components/CopyToPg.py +609 -0
  30. flowtask/components/CopyToRethink.py +207 -0
  31. flowtask/components/CreateGCSBucket.py +102 -0
  32. flowtask/components/CreateReport/CreateReport.py +228 -0
  33. flowtask/components/CreateReport/__init__.py +9 -0
  34. flowtask/components/CreateReport/charts/__init__.py +15 -0
  35. flowtask/components/CreateReport/charts/bar.py +51 -0
  36. flowtask/components/CreateReport/charts/base.py +66 -0
  37. flowtask/components/CreateReport/charts/pie.py +64 -0
  38. flowtask/components/CreateReport/utils.py +9 -0
  39. flowtask/components/CustomerSatisfaction.py +196 -0
  40. flowtask/components/DataInput.py +200 -0
  41. flowtask/components/DateList.py +255 -0
  42. flowtask/components/DbClient.py +163 -0
  43. flowtask/components/DialPad.py +146 -0
  44. flowtask/components/DocumentDBQuery.py +200 -0
  45. flowtask/components/DownloadFrom.py +371 -0
  46. flowtask/components/DownloadFromD2L.py +113 -0
  47. flowtask/components/DownloadFromFTP.py +181 -0
  48. flowtask/components/DownloadFromIMAP.py +315 -0
  49. flowtask/components/DownloadFromS3.py +198 -0
  50. flowtask/components/DownloadFromSFTP.py +265 -0
  51. flowtask/components/DownloadFromSharepoint.py +110 -0
  52. flowtask/components/DownloadFromSmartSheet.py +114 -0
  53. flowtask/components/DownloadS3File.py +229 -0
  54. flowtask/components/Dummy.py +59 -0
  55. flowtask/components/DuplicatePhoto.py +411 -0
  56. flowtask/components/EmployeeEvaluation.py +237 -0
  57. flowtask/components/ExecuteSQL.py +323 -0
  58. flowtask/components/ExtractHTML.py +178 -0
  59. flowtask/components/FileBase.py +178 -0
  60. flowtask/components/FileCopy.py +181 -0
  61. flowtask/components/FileDelete.py +82 -0
  62. flowtask/components/FileExists.py +146 -0
  63. flowtask/components/FileIteratorDelete.py +112 -0
  64. flowtask/components/FileList.py +194 -0
  65. flowtask/components/FileOpen.py +75 -0
  66. flowtask/components/FileRead.py +120 -0
  67. flowtask/components/FileRename.py +106 -0
  68. flowtask/components/FilterIf.py +284 -0
  69. flowtask/components/FilterRows/FilterRows.py +200 -0
  70. flowtask/components/FilterRows/__init__.py +10 -0
  71. flowtask/components/FilterRows/functions.py +4 -0
  72. flowtask/components/GCSToBigQuery.py +103 -0
  73. flowtask/components/GoogleA4.py +150 -0
  74. flowtask/components/GoogleGeoCoding.py +344 -0
  75. flowtask/components/GooglePlaces.py +315 -0
  76. flowtask/components/GoogleSearch.py +539 -0
  77. flowtask/components/HTTPClient.py +268 -0
  78. flowtask/components/ICIMS.py +146 -0
  79. flowtask/components/IF.py +179 -0
  80. flowtask/components/IcimsFolderCopy.py +173 -0
  81. flowtask/components/ImageFeatures/__init__.py +5 -0
  82. flowtask/components/ImageFeatures/process.py +233 -0
  83. flowtask/components/IteratorBase.py +251 -0
  84. flowtask/components/LangchainLoader/__init__.py +5 -0
  85. flowtask/components/LangchainLoader/loader.py +194 -0
  86. flowtask/components/LangchainLoader/loaders/__init__.py +22 -0
  87. flowtask/components/LangchainLoader/loaders/abstract.py +362 -0
  88. flowtask/components/LangchainLoader/loaders/basepdf.py +50 -0
  89. flowtask/components/LangchainLoader/loaders/docx.py +91 -0
  90. flowtask/components/LangchainLoader/loaders/html.py +119 -0
  91. flowtask/components/LangchainLoader/loaders/pdfblocks.py +146 -0
  92. flowtask/components/LangchainLoader/loaders/pdfmark.py +79 -0
  93. flowtask/components/LangchainLoader/loaders/pdftables.py +135 -0
  94. flowtask/components/LangchainLoader/loaders/qa.py +67 -0
  95. flowtask/components/LangchainLoader/loaders/txt.py +55 -0
  96. flowtask/components/LeadIQ.py +650 -0
  97. flowtask/components/Loop.py +253 -0
  98. flowtask/components/Lowes.py +334 -0
  99. flowtask/components/MS365Usage.py +156 -0
  100. flowtask/components/MSTeamsMessages.py +320 -0
  101. flowtask/components/MarketClustering.py +1051 -0
  102. flowtask/components/MergeFiles.py +362 -0
  103. flowtask/components/MilvusOutput.py +87 -0
  104. flowtask/components/NearByStores.py +175 -0
  105. flowtask/components/NetworkNinja/__init__.py +6 -0
  106. flowtask/components/NetworkNinja/models/__init__.py +52 -0
  107. flowtask/components/NetworkNinja/models/abstract.py +177 -0
  108. flowtask/components/NetworkNinja/models/account.py +39 -0
  109. flowtask/components/NetworkNinja/models/client.py +19 -0
  110. flowtask/components/NetworkNinja/models/district.py +14 -0
  111. flowtask/components/NetworkNinja/models/events.py +101 -0
  112. flowtask/components/NetworkNinja/models/forms.py +499 -0
  113. flowtask/components/NetworkNinja/models/market.py +16 -0
  114. flowtask/components/NetworkNinja/models/organization.py +34 -0
  115. flowtask/components/NetworkNinja/models/photos.py +125 -0
  116. flowtask/components/NetworkNinja/models/project.py +44 -0
  117. flowtask/components/NetworkNinja/models/region.py +28 -0
  118. flowtask/components/NetworkNinja/models/store.py +203 -0
  119. flowtask/components/NetworkNinja/models/user.py +151 -0
  120. flowtask/components/NetworkNinja/router.py +854 -0
  121. flowtask/components/Odoo.py +175 -0
  122. flowtask/components/OdooInjector.py +192 -0
  123. flowtask/components/OpenFromXML.py +126 -0
  124. flowtask/components/OpenWeather.py +41 -0
  125. flowtask/components/OpenWithBase.py +616 -0
  126. flowtask/components/OpenWithPandas.py +715 -0
  127. flowtask/components/PGPDecrypt.py +199 -0
  128. flowtask/components/PandasIterator.py +187 -0
  129. flowtask/components/PandasToFile.py +189 -0
  130. flowtask/components/Paradox.py +339 -0
  131. flowtask/components/ParamIterator.py +117 -0
  132. flowtask/components/ParseHTML.py +84 -0
  133. flowtask/components/PlacerStores.py +249 -0
  134. flowtask/components/Pokemon.py +507 -0
  135. flowtask/components/PositiveBot.py +62 -0
  136. flowtask/components/PowerPointSlide.py +400 -0
  137. flowtask/components/PrintMessage.py +127 -0
  138. flowtask/components/ProductCompetitors/__init__.py +5 -0
  139. flowtask/components/ProductCompetitors/parsers/__init__.py +7 -0
  140. flowtask/components/ProductCompetitors/parsers/base.py +72 -0
  141. flowtask/components/ProductCompetitors/parsers/bestbuy.py +86 -0
  142. flowtask/components/ProductCompetitors/parsers/lowes.py +103 -0
  143. flowtask/components/ProductCompetitors/scrapper.py +155 -0
  144. flowtask/components/ProductCompliant.py +169 -0
  145. flowtask/components/ProductInfo/__init__.py +1 -0
  146. flowtask/components/ProductInfo/parsers/__init__.py +5 -0
  147. flowtask/components/ProductInfo/parsers/base.py +83 -0
  148. flowtask/components/ProductInfo/parsers/brother.py +97 -0
  149. flowtask/components/ProductInfo/parsers/canon.py +167 -0
  150. flowtask/components/ProductInfo/parsers/epson.py +118 -0
  151. flowtask/components/ProductInfo/parsers/hp.py +131 -0
  152. flowtask/components/ProductInfo/parsers/samsung.py +97 -0
  153. flowtask/components/ProductInfo/scraper.py +319 -0
  154. flowtask/components/ProductPricing.py +118 -0
  155. flowtask/components/QS.py +261 -0
  156. flowtask/components/QSBase.py +201 -0
  157. flowtask/components/QueryIterator.py +273 -0
  158. flowtask/components/QueryToInsert.py +327 -0
  159. flowtask/components/QueryToPandas.py +432 -0
  160. flowtask/components/RESTClient.py +195 -0
  161. flowtask/components/RethinkDBQuery.py +189 -0
  162. flowtask/components/Rsync.py +74 -0
  163. flowtask/components/RunSSH.py +59 -0
  164. flowtask/components/RunShell.py +71 -0
  165. flowtask/components/SalesForce.py +20 -0
  166. flowtask/components/SaveImageBank/__init__.py +257 -0
  167. flowtask/components/SchedulingVisits.py +592 -0
  168. flowtask/components/ScrapPage.py +216 -0
  169. flowtask/components/ScrapSearch.py +79 -0
  170. flowtask/components/SendNotify.py +257 -0
  171. flowtask/components/SentimentAnalysis.py +694 -0
  172. flowtask/components/ServiceScrapper/__init__.py +5 -0
  173. flowtask/components/ServiceScrapper/parsers/__init__.py +1 -0
  174. flowtask/components/ServiceScrapper/parsers/base.py +94 -0
  175. flowtask/components/ServiceScrapper/parsers/costco.py +93 -0
  176. flowtask/components/ServiceScrapper/scrapper.py +199 -0
  177. flowtask/components/SetVariables.py +156 -0
  178. flowtask/components/SubTask.py +182 -0
  179. flowtask/components/SuiteCRM.py +48 -0
  180. flowtask/components/Switch.py +175 -0
  181. flowtask/components/TableBase.py +148 -0
  182. flowtask/components/TableDelete.py +312 -0
  183. flowtask/components/TableInput.py +143 -0
  184. flowtask/components/TableOutput/TableOutput.py +384 -0
  185. flowtask/components/TableOutput/__init__.py +3 -0
  186. flowtask/components/TableSchema.py +534 -0
  187. flowtask/components/Target.py +223 -0
  188. flowtask/components/ThumbnailGenerator.py +156 -0
  189. flowtask/components/ToPandas.py +67 -0
  190. flowtask/components/TransformRows/TransformRows.py +507 -0
  191. flowtask/components/TransformRows/__init__.py +9 -0
  192. flowtask/components/TransformRows/functions.py +559 -0
  193. flowtask/components/TransposeRows.py +176 -0
  194. flowtask/components/UPCDatabase.py +86 -0
  195. flowtask/components/UnGzip.py +171 -0
  196. flowtask/components/Uncompress.py +172 -0
  197. flowtask/components/UniqueRows.py +126 -0
  198. flowtask/components/Unzip.py +107 -0
  199. flowtask/components/UpdateOperationalVars.py +147 -0
  200. flowtask/components/UploadTo.py +299 -0
  201. flowtask/components/UploadToS3.py +136 -0
  202. flowtask/components/UploadToSFTP.py +160 -0
  203. flowtask/components/UploadToSharepoint.py +205 -0
  204. flowtask/components/UserFunc.py +122 -0
  205. flowtask/components/VivaTracker.py +140 -0
  206. flowtask/components/WSDLClient.py +123 -0
  207. flowtask/components/Wait.py +18 -0
  208. flowtask/components/Walmart.py +199 -0
  209. flowtask/components/Workplace.py +134 -0
  210. flowtask/components/XMLToPandas.py +267 -0
  211. flowtask/components/Zammad/__init__.py +41 -0
  212. flowtask/components/Zammad/models.py +0 -0
  213. flowtask/components/ZoomInfoScraper.py +409 -0
  214. flowtask/components/__init__.py +104 -0
  215. flowtask/components/abstract.py +18 -0
  216. flowtask/components/flow.py +530 -0
  217. flowtask/components/google.py +335 -0
  218. flowtask/components/group.py +221 -0
  219. flowtask/components/py.typed +0 -0
  220. flowtask/components/reviewscrap.py +132 -0
  221. flowtask/components/tAutoincrement.py +117 -0
  222. flowtask/components/tConcat.py +109 -0
  223. flowtask/components/tExplode.py +119 -0
  224. flowtask/components/tFilter.py +184 -0
  225. flowtask/components/tGroup.py +236 -0
  226. flowtask/components/tJoin.py +270 -0
  227. flowtask/components/tMap/__init__.py +9 -0
  228. flowtask/components/tMap/functions.py +54 -0
  229. flowtask/components/tMap/tMap.py +450 -0
  230. flowtask/components/tMelt.py +112 -0
  231. flowtask/components/tMerge.py +114 -0
  232. flowtask/components/tOrder.py +93 -0
  233. flowtask/components/tPandas.py +94 -0
  234. flowtask/components/tPivot.py +71 -0
  235. flowtask/components/tPluckCols.py +76 -0
  236. flowtask/components/tUnnest.py +82 -0
  237. flowtask/components/user.py +401 -0
  238. flowtask/conf.py +457 -0
  239. flowtask/download.py +102 -0
  240. flowtask/events/__init__.py +11 -0
  241. flowtask/events/events/__init__.py +20 -0
  242. flowtask/events/events/abstract.py +95 -0
  243. flowtask/events/events/alerts/__init__.py +362 -0
  244. flowtask/events/events/alerts/colfunctions.py +131 -0
  245. flowtask/events/events/alerts/functions.py +158 -0
  246. flowtask/events/events/dummy.py +12 -0
  247. flowtask/events/events/exec.py +124 -0
  248. flowtask/events/events/file/__init__.py +7 -0
  249. flowtask/events/events/file/base.py +51 -0
  250. flowtask/events/events/file/copy.py +23 -0
  251. flowtask/events/events/file/delete.py +16 -0
  252. flowtask/events/events/interfaces/__init__.py +9 -0
  253. flowtask/events/events/interfaces/client.py +67 -0
  254. flowtask/events/events/interfaces/credentials.py +28 -0
  255. flowtask/events/events/interfaces/notifications.py +58 -0
  256. flowtask/events/events/jira.py +122 -0
  257. flowtask/events/events/log.py +26 -0
  258. flowtask/events/events/logerr.py +52 -0
  259. flowtask/events/events/notify.py +59 -0
  260. flowtask/events/events/notify_event.py +160 -0
  261. flowtask/events/events/publish.py +54 -0
  262. flowtask/events/events/sendfile.py +104 -0
  263. flowtask/events/events/task.py +97 -0
  264. flowtask/events/events/teams.py +98 -0
  265. flowtask/events/events/webhook.py +58 -0
  266. flowtask/events/manager.py +287 -0
  267. flowtask/exceptions.c +39393 -0
  268. flowtask/exceptions.cpython-39-x86_64-linux-gnu.so +0 -0
  269. flowtask/extensions/__init__.py +3 -0
  270. flowtask/extensions/abstract.py +82 -0
  271. flowtask/extensions/logging/__init__.py +65 -0
  272. flowtask/hooks/__init__.py +9 -0
  273. flowtask/hooks/actions/__init__.py +22 -0
  274. flowtask/hooks/actions/abstract.py +66 -0
  275. flowtask/hooks/actions/dummy.py +23 -0
  276. flowtask/hooks/actions/jira.py +74 -0
  277. flowtask/hooks/actions/rest.py +320 -0
  278. flowtask/hooks/actions/sampledata.py +37 -0
  279. flowtask/hooks/actions/sensor.py +23 -0
  280. flowtask/hooks/actions/task.py +9 -0
  281. flowtask/hooks/actions/ticket.py +37 -0
  282. flowtask/hooks/actions/zammad.py +55 -0
  283. flowtask/hooks/hook.py +62 -0
  284. flowtask/hooks/models.py +17 -0
  285. flowtask/hooks/service.py +187 -0
  286. flowtask/hooks/step.py +91 -0
  287. flowtask/hooks/types/__init__.py +23 -0
  288. flowtask/hooks/types/base.py +129 -0
  289. flowtask/hooks/types/brokers/__init__.py +11 -0
  290. flowtask/hooks/types/brokers/base.py +54 -0
  291. flowtask/hooks/types/brokers/mqtt.py +35 -0
  292. flowtask/hooks/types/brokers/rabbitmq.py +82 -0
  293. flowtask/hooks/types/brokers/redis.py +83 -0
  294. flowtask/hooks/types/brokers/sqs.py +44 -0
  295. flowtask/hooks/types/fs.py +232 -0
  296. flowtask/hooks/types/http.py +49 -0
  297. flowtask/hooks/types/imap.py +200 -0
  298. flowtask/hooks/types/jira.py +279 -0
  299. flowtask/hooks/types/mail.py +205 -0
  300. flowtask/hooks/types/postgres.py +98 -0
  301. flowtask/hooks/types/responses/__init__.py +8 -0
  302. flowtask/hooks/types/responses/base.py +5 -0
  303. flowtask/hooks/types/sharepoint.py +288 -0
  304. flowtask/hooks/types/ssh.py +141 -0
  305. flowtask/hooks/types/tagged.py +59 -0
  306. flowtask/hooks/types/upload.py +85 -0
  307. flowtask/hooks/types/watch.py +71 -0
  308. flowtask/hooks/types/web.py +36 -0
  309. flowtask/interfaces/AzureClient.py +137 -0
  310. flowtask/interfaces/AzureGraph.py +839 -0
  311. flowtask/interfaces/Boto3Client.py +326 -0
  312. flowtask/interfaces/DropboxClient.py +173 -0
  313. flowtask/interfaces/ExcelHandler.py +94 -0
  314. flowtask/interfaces/FTPClient.py +131 -0
  315. flowtask/interfaces/GoogleCalendar.py +201 -0
  316. flowtask/interfaces/GoogleClient.py +133 -0
  317. flowtask/interfaces/GoogleDrive.py +127 -0
  318. flowtask/interfaces/GoogleGCS.py +89 -0
  319. flowtask/interfaces/GoogleGeocoding.py +93 -0
  320. flowtask/interfaces/GoogleLang.py +114 -0
  321. flowtask/interfaces/GooglePub.py +61 -0
  322. flowtask/interfaces/GoogleSheet.py +68 -0
  323. flowtask/interfaces/IMAPClient.py +137 -0
  324. flowtask/interfaces/O365Calendar.py +113 -0
  325. flowtask/interfaces/O365Client.py +220 -0
  326. flowtask/interfaces/OneDrive.py +284 -0
  327. flowtask/interfaces/Outlook.py +155 -0
  328. flowtask/interfaces/ParrotBot.py +130 -0
  329. flowtask/interfaces/SSHClient.py +378 -0
  330. flowtask/interfaces/Sharepoint.py +496 -0
  331. flowtask/interfaces/__init__.py +36 -0
  332. flowtask/interfaces/azureauth.py +119 -0
  333. flowtask/interfaces/cache.py +201 -0
  334. flowtask/interfaces/client.py +82 -0
  335. flowtask/interfaces/compress.py +525 -0
  336. flowtask/interfaces/credentials.py +124 -0
  337. flowtask/interfaces/d2l.py +239 -0
  338. flowtask/interfaces/databases/__init__.py +5 -0
  339. flowtask/interfaces/databases/db.py +223 -0
  340. flowtask/interfaces/databases/documentdb.py +55 -0
  341. flowtask/interfaces/databases/rethink.py +39 -0
  342. flowtask/interfaces/dataframes/__init__.py +11 -0
  343. flowtask/interfaces/dataframes/abstract.py +21 -0
  344. flowtask/interfaces/dataframes/arrow.py +71 -0
  345. flowtask/interfaces/dataframes/dt.py +69 -0
  346. flowtask/interfaces/dataframes/pandas.py +167 -0
  347. flowtask/interfaces/dataframes/polars.py +60 -0
  348. flowtask/interfaces/db.py +263 -0
  349. flowtask/interfaces/env.py +46 -0
  350. flowtask/interfaces/func.py +137 -0
  351. flowtask/interfaces/http.py +1780 -0
  352. flowtask/interfaces/locale.py +40 -0
  353. flowtask/interfaces/log.py +75 -0
  354. flowtask/interfaces/mask.py +143 -0
  355. flowtask/interfaces/notification.py +154 -0
  356. flowtask/interfaces/playwright.py +339 -0
  357. flowtask/interfaces/powerpoint.py +368 -0
  358. flowtask/interfaces/py.typed +0 -0
  359. flowtask/interfaces/qs.py +376 -0
  360. flowtask/interfaces/result.py +87 -0
  361. flowtask/interfaces/selenium_service.py +779 -0
  362. flowtask/interfaces/smartsheet.py +154 -0
  363. flowtask/interfaces/stat.py +39 -0
  364. flowtask/interfaces/task.py +96 -0
  365. flowtask/interfaces/template.py +118 -0
  366. flowtask/interfaces/vectorstores/__init__.py +1 -0
  367. flowtask/interfaces/vectorstores/abstract.py +133 -0
  368. flowtask/interfaces/vectorstores/milvus.py +669 -0
  369. flowtask/interfaces/zammad.py +107 -0
  370. flowtask/models.py +193 -0
  371. flowtask/parsers/__init__.py +15 -0
  372. flowtask/parsers/_yaml.c +11978 -0
  373. flowtask/parsers/_yaml.cpython-39-x86_64-linux-gnu.so +0 -0
  374. flowtask/parsers/argparser.py +235 -0
  375. flowtask/parsers/base.c +15155 -0
  376. flowtask/parsers/base.cpython-39-x86_64-linux-gnu.so +0 -0
  377. flowtask/parsers/json.c +11968 -0
  378. flowtask/parsers/json.cpython-39-x86_64-linux-gnu.so +0 -0
  379. flowtask/parsers/maps.py +49 -0
  380. flowtask/parsers/toml.c +11968 -0
  381. flowtask/parsers/toml.cpython-39-x86_64-linux-gnu.so +0 -0
  382. flowtask/plugins/__init__.py +16 -0
  383. flowtask/plugins/components/__init__.py +0 -0
  384. flowtask/plugins/handler/__init__.py +45 -0
  385. flowtask/plugins/importer.py +31 -0
  386. flowtask/plugins/sources/__init__.py +0 -0
  387. flowtask/runner.py +283 -0
  388. flowtask/scheduler/__init__.py +9 -0
  389. flowtask/scheduler/functions.py +493 -0
  390. flowtask/scheduler/handlers/__init__.py +8 -0
  391. flowtask/scheduler/handlers/manager.py +504 -0
  392. flowtask/scheduler/handlers/models.py +58 -0
  393. flowtask/scheduler/handlers/service.py +72 -0
  394. flowtask/scheduler/notifications.py +65 -0
  395. flowtask/scheduler/scheduler.py +993 -0
  396. flowtask/services/__init__.py +0 -0
  397. flowtask/services/bots/__init__.py +0 -0
  398. flowtask/services/bots/telegram.py +264 -0
  399. flowtask/services/files/__init__.py +11 -0
  400. flowtask/services/files/manager.py +522 -0
  401. flowtask/services/files/model.py +37 -0
  402. flowtask/services/files/service.py +767 -0
  403. flowtask/services/jira/__init__.py +3 -0
  404. flowtask/services/jira/jira_actions.py +191 -0
  405. flowtask/services/tasks/__init__.py +13 -0
  406. flowtask/services/tasks/launcher.py +213 -0
  407. flowtask/services/tasks/manager.py +323 -0
  408. flowtask/services/tasks/service.py +275 -0
  409. flowtask/services/tasks/task_manager.py +376 -0
  410. flowtask/services/tasks/tasks.py +155 -0
  411. flowtask/storages/__init__.py +16 -0
  412. flowtask/storages/exceptions.py +12 -0
  413. flowtask/storages/files/__init__.py +8 -0
  414. flowtask/storages/files/abstract.py +29 -0
  415. flowtask/storages/files/filesystem.py +66 -0
  416. flowtask/storages/tasks/__init__.py +19 -0
  417. flowtask/storages/tasks/abstract.py +26 -0
  418. flowtask/storages/tasks/database.py +33 -0
  419. flowtask/storages/tasks/filesystem.py +108 -0
  420. flowtask/storages/tasks/github.py +119 -0
  421. flowtask/storages/tasks/memory.py +45 -0
  422. flowtask/storages/tasks/row.py +25 -0
  423. flowtask/tasks/__init__.py +0 -0
  424. flowtask/tasks/abstract.py +526 -0
  425. flowtask/tasks/command.py +118 -0
  426. flowtask/tasks/pile.py +486 -0
  427. flowtask/tasks/py.typed +0 -0
  428. flowtask/tasks/task.py +778 -0
  429. flowtask/template/__init__.py +161 -0
  430. flowtask/tests.py +257 -0
  431. flowtask/types/__init__.py +8 -0
  432. flowtask/types/typedefs.c +11347 -0
  433. flowtask/types/typedefs.cpython-39-x86_64-linux-gnu.so +0 -0
  434. flowtask/utils/__init__.py +24 -0
  435. flowtask/utils/constants.py +117 -0
  436. flowtask/utils/encoders.py +21 -0
  437. flowtask/utils/executor.py +112 -0
  438. flowtask/utils/functions.cpp +14280 -0
  439. flowtask/utils/functions.cpython-39-x86_64-linux-gnu.so +0 -0
  440. flowtask/utils/json.cpp +13349 -0
  441. flowtask/utils/json.cpython-39-x86_64-linux-gnu.so +0 -0
  442. flowtask/utils/mail.py +63 -0
  443. flowtask/utils/parseqs.c +13324 -0
  444. flowtask/utils/parserqs.cpython-39-x86_64-linux-gnu.so +0 -0
  445. flowtask/utils/stats.py +308 -0
  446. flowtask/utils/transformations.py +74 -0
  447. flowtask/utils/uv.py +12 -0
  448. flowtask/utils/validators.py +97 -0
  449. flowtask/version.py +11 -0
  450. flowtask-5.8.4.dist-info/LICENSE +201 -0
  451. flowtask-5.8.4.dist-info/METADATA +209 -0
  452. flowtask-5.8.4.dist-info/RECORD +470 -0
  453. flowtask-5.8.4.dist-info/WHEEL +6 -0
  454. flowtask-5.8.4.dist-info/entry_points.txt +3 -0
  455. flowtask-5.8.4.dist-info/top_level.txt +2 -0
  456. plugins/components/CreateQR.py +39 -0
  457. plugins/components/TestComponent.py +28 -0
  458. plugins/components/Use1.py +13 -0
  459. plugins/components/Workplace.py +117 -0
  460. plugins/components/__init__.py +3 -0
  461. plugins/sources/__init__.py +0 -0
  462. plugins/sources/get_populartimes.py +78 -0
  463. plugins/sources/google.py +150 -0
  464. plugins/sources/hubspot.py +679 -0
  465. plugins/sources/icims.py +679 -0
  466. plugins/sources/mobileinsight.py +501 -0
  467. plugins/sources/newrelic.py +262 -0
  468. plugins/sources/uap.py +268 -0
  469. plugins/sources/venu.py +244 -0
  470. 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()