moriarty-project 0.1.6__py3-none-any.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 (416) hide show
  1. moriarty/__init__.py +5 -0
  2. moriarty/adapters/__init__.py +0 -0
  3. moriarty/agent/__init__.py +0 -0
  4. moriarty/assets/modules/.gitkeep +0 -0
  5. moriarty/assets/modules/asia/douban.yaml +19 -0
  6. moriarty/assets/modules/asia/kakao.yaml +19 -0
  7. moriarty/assets/modules/asia/line.yaml +19 -0
  8. moriarty/assets/modules/asia/mixi.yaml +19 -0
  9. moriarty/assets/modules/asia/naver.yaml +19 -0
  10. moriarty/assets/modules/asia/qq.yaml +19 -0
  11. moriarty/assets/modules/asia/vk.yaml +19 -0
  12. moriarty/assets/modules/asia/wechat.yaml +19 -0
  13. moriarty/assets/modules/asia/weibo.yaml +19 -0
  14. moriarty/assets/modules/asia/xiaohongshu.yaml +19 -0
  15. moriarty/assets/modules/behance.yaml +47 -0
  16. moriarty/assets/modules/business/crunchbase.yaml +27 -0
  17. moriarty/assets/modules/business/fiverr.yaml +32 -0
  18. moriarty/assets/modules/business/freelancer.yaml +27 -0
  19. moriarty/assets/modules/business/glassdoor.yaml +27 -0
  20. moriarty/assets/modules/business/guru.yaml +26 -0
  21. moriarty/assets/modules/business/indeed.yaml +25 -0
  22. moriarty/assets/modules/business/monster.yaml +25 -0
  23. moriarty/assets/modules/business/peopleperhour.yaml +26 -0
  24. moriarty/assets/modules/business/toptal.yaml +28 -0
  25. moriarty/assets/modules/business/upwork.yaml +27 -0
  26. moriarty/assets/modules/business/ziprecruiter.yaml +25 -0
  27. moriarty/assets/modules/content/buymeacoffee.yaml +27 -0
  28. moriarty/assets/modules/content/gumroad.yaml +27 -0
  29. moriarty/assets/modules/content/ko-fi.yaml +32 -0
  30. moriarty/assets/modules/content/onlyfans.yaml +27 -0
  31. moriarty/assets/modules/content/patreon.yaml +33 -0
  32. moriarty/assets/modules/content/substack.yaml +32 -0
  33. moriarty/assets/modules/creative/500px.yaml +31 -0
  34. moriarty/assets/modules/creative/artstation.yaml +33 -0
  35. moriarty/assets/modules/creative/deviantart.yaml +32 -0
  36. moriarty/assets/modules/creative/flickr.yaml +31 -0
  37. moriarty/assets/modules/creative/pexels.yaml +26 -0
  38. moriarty/assets/modules/creative/unsplash.yaml +26 -0
  39. moriarty/assets/modules/creative/vimeo.yaml +31 -0
  40. moriarty/assets/modules/crypto/binance.yaml +27 -0
  41. moriarty/assets/modules/crypto/bitcointalk.yaml +33 -0
  42. moriarty/assets/modules/crypto/coinbase.yaml +26 -0
  43. moriarty/assets/modules/crypto/etherscan.yaml +32 -0
  44. moriarty/assets/modules/crypto/foundation.yaml +28 -0
  45. moriarty/assets/modules/crypto/kraken.yaml +27 -0
  46. moriarty/assets/modules/crypto/mirror.yaml +27 -0
  47. moriarty/assets/modules/crypto/niftygateway.yaml +26 -0
  48. moriarty/assets/modules/crypto/opensea.yaml +32 -0
  49. moriarty/assets/modules/crypto/rarible.yaml +27 -0
  50. moriarty/assets/modules/crypto/superrare.yaml +29 -0
  51. moriarty/assets/modules/dating/bumble.yaml +25 -0
  52. moriarty/assets/modules/dating/grindr.yaml +27 -0
  53. moriarty/assets/modules/dating/happn.yaml +25 -0
  54. moriarty/assets/modules/dating/her.yaml +27 -0
  55. moriarty/assets/modules/dating/hinge.yaml +25 -0
  56. moriarty/assets/modules/dating/match.yaml +25 -0
  57. moriarty/assets/modules/dating/meetme.yaml +27 -0
  58. moriarty/assets/modules/dating/okcupid.yaml +25 -0
  59. moriarty/assets/modules/dating/pof.yaml +25 -0
  60. moriarty/assets/modules/dating/tinder.yaml +25 -0
  61. moriarty/assets/modules/dating-nsfw/adultfriendfinder.yaml +28 -0
  62. moriarty/assets/modules/dating-nsfw/ashley-madison.yaml +26 -0
  63. moriarty/assets/modules/design/adobe-portfolio.yaml +27 -0
  64. moriarty/assets/modules/design/carbonmade.yaml +27 -0
  65. moriarty/assets/modules/design/cgsociety.yaml +27 -0
  66. moriarty/assets/modules/design/coroflot.yaml +27 -0
  67. moriarty/assets/modules/design/figma.yaml +27 -0
  68. moriarty/assets/modules/design/sketch.yaml +26 -0
  69. moriarty/assets/modules/dev/bitbucket.yaml +35 -0
  70. moriarty/assets/modules/dev/codeforces.yaml +32 -0
  71. moriarty/assets/modules/dev/codepen.yaml +34 -0
  72. moriarty/assets/modules/dev/hackerone.yaml +32 -0
  73. moriarty/assets/modules/dev/hackthebox.yaml +27 -0
  74. moriarty/assets/modules/dev/huggingface.yaml +27 -0
  75. moriarty/assets/modules/dev/kaggle.yaml +32 -0
  76. moriarty/assets/modules/dev/leetcode.yaml +32 -0
  77. moriarty/assets/modules/dev/replit.yaml +31 -0
  78. moriarty/assets/modules/dribbble.yaml +53 -0
  79. moriarty/assets/modules/ecommerce/etsy.yaml +32 -0
  80. moriarty/assets/modules/education/duolingo.yaml +32 -0
  81. moriarty/assets/modules/education/edx.yaml +26 -0
  82. moriarty/assets/modules/education/khanacademy.yaml +26 -0
  83. moriarty/assets/modules/education/lynda.yaml +27 -0
  84. moriarty/assets/modules/education/memrise.yaml +27 -0
  85. moriarty/assets/modules/education/pluralsight.yaml +27 -0
  86. moriarty/assets/modules/education/skillshare.yaml +27 -0
  87. moriarty/assets/modules/education/udacity.yaml +27 -0
  88. moriarty/assets/modules/email/github_email.yaml +40 -0
  89. moriarty/assets/modules/email/gravatar.yaml +23 -0
  90. moriarty/assets/modules/europe/badoo.yaml +19 -0
  91. moriarty/assets/modules/europe/lovoo.yaml +19 -0
  92. moriarty/assets/modules/europe/myspace.yaml +19 -0
  93. moriarty/assets/modules/europe/netlog.yaml +19 -0
  94. moriarty/assets/modules/europe/ok.yaml +19 -0
  95. moriarty/assets/modules/europe/skyrock.yaml +19 -0
  96. moriarty/assets/modules/europe/studivz.yaml +19 -0
  97. moriarty/assets/modules/europe/tuenti.yaml +19 -0
  98. moriarty/assets/modules/europe/viadeo.yaml +19 -0
  99. moriarty/assets/modules/europe/xing.yaml +19 -0
  100. moriarty/assets/modules/fitness/fitbit.yaml +27 -0
  101. moriarty/assets/modules/fitness/garmin.yaml +27 -0
  102. moriarty/assets/modules/fitness/myfitnesspal.yaml +27 -0
  103. moriarty/assets/modules/fitness/strava.yaml +33 -0
  104. moriarty/assets/modules/fitness/zwift.yaml +28 -0
  105. moriarty/assets/modules/food/allrecipes.yaml +27 -0
  106. moriarty/assets/modules/food/tasty.yaml +27 -0
  107. moriarty/assets/modules/food/yelp.yaml +32 -0
  108. moriarty/assets/modules/food/zomato.yaml +28 -0
  109. moriarty/assets/modules/forums/4chan.yaml +26 -0
  110. moriarty/assets/modules/forums/8kun.yaml +26 -0
  111. moriarty/assets/modules/forums/9gag.yaml +26 -0
  112. moriarty/assets/modules/forums/discourse.yaml +26 -0
  113. moriarty/assets/modules/forums/disqus.yaml +31 -0
  114. moriarty/assets/modules/forums/hackernews.yaml +32 -0
  115. moriarty/assets/modules/forums/launchpad.yaml +27 -0
  116. moriarty/assets/modules/forums/phpbb.yaml +25 -0
  117. moriarty/assets/modules/forums/quora.yaml +32 -0
  118. moriarty/assets/modules/forums/serverfault.yaml +27 -0
  119. moriarty/assets/modules/forums/slashdot.yaml +28 -0
  120. moriarty/assets/modules/forums/stackexchange.yaml +32 -0
  121. moriarty/assets/modules/forums/superuser.yaml +27 -0
  122. moriarty/assets/modules/forums/vbulletin.yaml +25 -0
  123. moriarty/assets/modules/forums/xenforo.yaml +25 -0
  124. moriarty/assets/modules/forums-nsfw/kiwifarms.yaml +25 -0
  125. moriarty/assets/modules/forums-nsfw/lolcow.yaml +26 -0
  126. moriarty/assets/modules/gaming/apextracker.yaml +27 -0
  127. moriarty/assets/modules/gaming/battlenet.yaml +26 -0
  128. moriarty/assets/modules/gaming/chess.yaml +30 -0
  129. moriarty/assets/modules/gaming/discord-public.yaml +27 -0
  130. moriarty/assets/modules/gaming/dotabuff.yaml +32 -0
  131. moriarty/assets/modules/gaming/epicgames.yaml +25 -0
  132. moriarty/assets/modules/gaming/faceit.yaml +33 -0
  133. moriarty/assets/modules/gaming/fortnitetracker.yaml +32 -0
  134. moriarty/assets/modules/gaming/gog.yaml +26 -0
  135. moriarty/assets/modules/gaming/itch.yaml +32 -0
  136. moriarty/assets/modules/gaming/kongregate.yaml +25 -0
  137. moriarty/assets/modules/gaming/minecraft.yaml +31 -0
  138. moriarty/assets/modules/gaming/opgg.yaml +32 -0
  139. moriarty/assets/modules/gaming/origin.yaml +26 -0
  140. moriarty/assets/modules/gaming/playstation.yaml +30 -0
  141. moriarty/assets/modules/gaming/roblox.yaml +31 -0
  142. moriarty/assets/modules/gaming/xbox.yaml +25 -0
  143. moriarty/assets/modules/github.yaml +68 -0
  144. moriarty/assets/modules/gitlab.yaml +60 -0
  145. moriarty/assets/modules/instagram.yaml +48 -0
  146. moriarty/assets/modules/latam/fotolog.yaml +27 -0
  147. moriarty/assets/modules/latam/orkut.yaml +26 -0
  148. moriarty/assets/modules/latam/taringa.yaml +27 -0
  149. moriarty/assets/modules/learning/coursera.yaml +26 -0
  150. moriarty/assets/modules/learning/udemy.yaml +26 -0
  151. moriarty/assets/modules/linkedin.yaml +40 -0
  152. moriarty/assets/modules/marketplaces/depop.yaml +28 -0
  153. moriarty/assets/modules/marketplaces/ebay.yaml +32 -0
  154. moriarty/assets/modules/marketplaces/grailed.yaml +27 -0
  155. moriarty/assets/modules/marketplaces/mercari.yaml +26 -0
  156. moriarty/assets/modules/marketplaces/poshmark.yaml +27 -0
  157. moriarty/assets/modules/marketplaces/reverb.yaml +27 -0
  158. moriarty/assets/modules/marketplaces/vinted.yaml +28 -0
  159. moriarty/assets/modules/medium.yaml +44 -0
  160. moriarty/assets/modules/music/audiomack.yaml +26 -0
  161. moriarty/assets/modules/music/bandcamp.yaml +30 -0
  162. moriarty/assets/modules/music/beatport.yaml +28 -0
  163. moriarty/assets/modules/music/deezer.yaml +26 -0
  164. moriarty/assets/modules/music/discogs.yaml +32 -0
  165. moriarty/assets/modules/music/genius.yaml +26 -0
  166. moriarty/assets/modules/music/lastfm.yaml +30 -0
  167. moriarty/assets/modules/music/mixcloud.yaml +26 -0
  168. moriarty/assets/modules/music/reverbnation.yaml +31 -0
  169. moriarty/assets/modules/music/soundcloud.yaml +31 -0
  170. moriarty/assets/modules/music/spotify.yaml +26 -0
  171. moriarty/assets/modules/music/tidal.yaml +26 -0
  172. moriarty/assets/modules/nsfw/adultwork.yaml +27 -0
  173. moriarty/assets/modules/nsfw/bongacams.yaml +28 -0
  174. moriarty/assets/modules/nsfw/cam4.yaml +28 -0
  175. moriarty/assets/modules/nsfw/chaturbate.yaml +28 -0
  176. moriarty/assets/modules/nsfw/clips4sale.yaml +27 -0
  177. moriarty/assets/modules/nsfw/extralunchmoney.yaml +27 -0
  178. moriarty/assets/modules/nsfw/fansly.yaml +28 -0
  179. moriarty/assets/modules/nsfw/fetlife.yaml +28 -0
  180. moriarty/assets/modules/nsfw/iwantclips.yaml +27 -0
  181. moriarty/assets/modules/nsfw/justforfans.yaml +28 -0
  182. moriarty/assets/modules/nsfw/loyalfans.yaml +28 -0
  183. moriarty/assets/modules/nsfw/manyvids.yaml +27 -0
  184. moriarty/assets/modules/nsfw/myfreecams.yaml +28 -0
  185. moriarty/assets/modules/nsfw/niteflirt.yaml +26 -0
  186. moriarty/assets/modules/nsfw/pornhub.yaml +32 -0
  187. moriarty/assets/modules/nsfw/redtube.yaml +27 -0
  188. moriarty/assets/modules/nsfw/stripchat.yaml +28 -0
  189. moriarty/assets/modules/nsfw/xhamster.yaml +27 -0
  190. moriarty/assets/modules/nsfw/xvideos.yaml +27 -0
  191. moriarty/assets/modules/nsfw/youporn.yaml +27 -0
  192. moriarty/assets/modules/photography/eyeem.yaml +25 -0
  193. moriarty/assets/modules/photography/fotki.yaml +25 -0
  194. moriarty/assets/modules/photography/photobucket.yaml +26 -0
  195. moriarty/assets/modules/photography/smugmug.yaml +25 -0
  196. moriarty/assets/modules/photography/vsco.yaml +27 -0
  197. moriarty/assets/modules/pinterest.yaml +40 -0
  198. moriarty/assets/modules/podcasts/anchor.yaml +26 -0
  199. moriarty/assets/modules/podcasts/castbox.yaml +26 -0
  200. moriarty/assets/modules/podcasts/podbean.yaml +26 -0
  201. moriarty/assets/modules/professional/about.yaml +31 -0
  202. moriarty/assets/modules/professional/academia.yaml +27 -0
  203. moriarty/assets/modules/professional/angellist.yaml +27 -0
  204. moriarty/assets/modules/professional/calendly.yaml +26 -0
  205. moriarty/assets/modules/professional/issuu.yaml +27 -0
  206. moriarty/assets/modules/professional/mendeley.yaml +27 -0
  207. moriarty/assets/modules/professional/notion.yaml +27 -0
  208. moriarty/assets/modules/professional/orcid.yaml +27 -0
  209. moriarty/assets/modules/professional/producthunt.yaml +31 -0
  210. moriarty/assets/modules/professional/researchgate.yaml +32 -0
  211. moriarty/assets/modules/professional/scribd.yaml +27 -0
  212. moriarty/assets/modules/professional/slideshare.yaml +31 -0
  213. moriarty/assets/modules/professional/trello.yaml +26 -0
  214. moriarty/assets/modules/professional/typeform.yaml +27 -0
  215. moriarty/assets/modules/reddit.yaml +46 -0
  216. moriarty/assets/modules/regional/amino.yaml +27 -0
  217. moriarty/assets/modules/regional/ask-fm.yaml +32 -0
  218. moriarty/assets/modules/regional/babycenter.yaml +26 -0
  219. moriarty/assets/modules/regional/cafemom.yaml +27 -0
  220. moriarty/assets/modules/regional/care2.yaml +27 -0
  221. moriarty/assets/modules/regional/diaspora.yaml +26 -0
  222. moriarty/assets/modules/regional/ello.yaml +27 -0
  223. moriarty/assets/modules/regional/gaia.yaml +27 -0
  224. moriarty/assets/modules/regional/habbo.yaml +27 -0
  225. moriarty/assets/modules/regional/imvu.yaml +27 -0
  226. moriarty/assets/modules/regional/lemmy.yaml +27 -0
  227. moriarty/assets/modules/regional/peertube.yaml +26 -0
  228. moriarty/assets/modules/regional/pixelfed.yaml +27 -0
  229. moriarty/assets/modules/regional/plurk.yaml +26 -0
  230. moriarty/assets/modules/regional/recroom.yaml +27 -0
  231. moriarty/assets/modules/regional/secondlife.yaml +26 -0
  232. moriarty/assets/modules/regional/vine-archive.yaml +27 -0
  233. moriarty/assets/modules/regional/vrchat.yaml +27 -0
  234. moriarty/assets/modules/regional/weheartit.yaml +27 -0
  235. moriarty/assets/modules/social/anilist.yaml +27 -0
  236. moriarty/assets/modules/social/beacons.yaml +26 -0
  237. moriarty/assets/modules/social/blogger.yaml +27 -0
  238. moriarty/assets/modules/social/crunchyroll.yaml +27 -0
  239. moriarty/assets/modules/social/discord.yaml +27 -0
  240. moriarty/assets/modules/social/dreamwidth.yaml +26 -0
  241. moriarty/assets/modules/social/facebook.yaml +34 -0
  242. moriarty/assets/modules/social/goodreads.yaml +32 -0
  243. moriarty/assets/modules/social/imdb.yaml +27 -0
  244. moriarty/assets/modules/social/kitsu.yaml +27 -0
  245. moriarty/assets/modules/social/letterboxd.yaml +32 -0
  246. moriarty/assets/modules/social/linktree.yaml +26 -0
  247. moriarty/assets/modules/social/livejournal.yaml +27 -0
  248. moriarty/assets/modules/social/mastodon.yaml +30 -0
  249. moriarty/assets/modules/social/minds.yaml +25 -0
  250. moriarty/assets/modules/social/myanimelist.yaml +32 -0
  251. moriarty/assets/modules/social/ravelry.yaml +27 -0
  252. moriarty/assets/modules/social/snapchat.yaml +25 -0
  253. moriarty/assets/modules/social/telegram.yaml +35 -0
  254. moriarty/assets/modules/social/tiktok.yaml +35 -0
  255. moriarty/assets/modules/social/trakt.yaml +28 -0
  256. moriarty/assets/modules/social/wattpad.yaml +32 -0
  257. moriarty/assets/modules/social/wordpress-com.yaml +26 -0
  258. moriarty/assets/modules/sports/espn.yaml +26 -0
  259. moriarty/assets/modules/sports/untappd.yaml +32 -0
  260. moriarty/assets/modules/stackoverflow.yaml +47 -0
  261. moriarty/assets/modules/steam.yaml +47 -0
  262. moriarty/assets/modules/streaming/caffeine.yaml +25 -0
  263. moriarty/assets/modules/streaming/dlive.yaml +27 -0
  264. moriarty/assets/modules/streaming/trovo.yaml +25 -0
  265. moriarty/assets/modules/travel/airbnb.yaml +26 -0
  266. moriarty/assets/modules/travel/booking.yaml +26 -0
  267. moriarty/assets/modules/travel/couchsurfing.yaml +27 -0
  268. moriarty/assets/modules/travel/tripadvisor.yaml +32 -0
  269. moriarty/assets/modules/tumblr.yaml +40 -0
  270. moriarty/assets/modules/twitch.yaml +48 -0
  271. moriarty/assets/modules/twitter.yaml +39 -0
  272. moriarty/assets/modules/youtube.yaml +42 -0
  273. moriarty/assets/templates/cves/CVE-2017-5638.yaml +27 -0
  274. moriarty/assets/templates/cves/CVE-2018-7600.yaml +30 -0
  275. moriarty/assets/templates/cves/CVE-2019-11510.yaml +27 -0
  276. moriarty/assets/templates/cves/CVE-2019-19781.yaml +28 -0
  277. moriarty/assets/templates/cves/CVE-2020-14882.yaml +28 -0
  278. moriarty/assets/templates/cves/CVE-2020-14883.yaml +29 -0
  279. moriarty/assets/templates/cves/CVE-2020-3452.yaml +28 -0
  280. moriarty/assets/templates/cves/CVE-2020-5902.yaml +28 -0
  281. moriarty/assets/templates/cves/CVE-2021-21972.yaml +31 -0
  282. moriarty/assets/templates/cves/CVE-2021-21985.yaml +28 -0
  283. moriarty/assets/templates/cves/CVE-2021-26084.yaml +30 -0
  284. moriarty/assets/templates/cves/CVE-2021-41773.yaml +25 -0
  285. moriarty/assets/templates/cves/CVE-2021-42013.yaml +28 -0
  286. moriarty/assets/templates/cves/CVE-2021-44228.yaml +27 -0
  287. moriarty/assets/templates/cves/CVE-2022-0185.yaml +21 -0
  288. moriarty/assets/templates/cves/CVE-2022-1388.yaml +36 -0
  289. moriarty/assets/templates/cves/CVE-2022-22954.yaml +28 -0
  290. moriarty/assets/templates/cves/CVE-2022-22965.yaml +31 -0
  291. moriarty/assets/templates/cves/CVE-2022-26134.yaml +27 -0
  292. moriarty/assets/templates/cves/CVE-2023-22515.yaml +27 -0
  293. moriarty/assets/templates/cves/CVE-2023-22527.yaml +29 -0
  294. moriarty/assets/templates/cves/CVE-2023-23752.yaml +33 -0
  295. moriarty/assets/templates/cves/CVE-2023-27350.yaml +27 -0
  296. moriarty/assets/templates/cves/CVE-2023-2868.yaml +27 -0
  297. moriarty/assets/templates/cves/CVE-2023-34362.yaml +27 -0
  298. moriarty/assets/templates/cves/CVE-2023-3519.yaml +28 -0
  299. moriarty/assets/templates/cves/CVE-2023-4966.yaml +27 -0
  300. moriarty/assets/templates/default-logins/admin-weak.yaml +40 -0
  301. moriarty/assets/templates/default-logins/wordpress-default.yaml +38 -0
  302. moriarty/assets/templates/exposures/aws-credentials.yaml +35 -0
  303. moriarty/assets/templates/exposures/backup-files.yaml +36 -0
  304. moriarty/assets/templates/exposures/database-files.yaml +34 -0
  305. moriarty/assets/templates/exposures/docker-exposed.yaml +31 -0
  306. moriarty/assets/templates/exposures/env-exposed.yaml +41 -0
  307. moriarty/assets/templates/exposures/git-exposed.yaml +41 -0
  308. moriarty/assets/templates/exposures/phpinfo.yaml +36 -0
  309. moriarty/assets/templates/exposures/svn-exposed.yaml +28 -0
  310. moriarty/assets/templates/fuzzing/api-endpoints.yaml +39 -0
  311. moriarty/assets/templates/fuzzing/common-files.yaml +37 -0
  312. moriarty/assets/templates/fuzzing/open-redirect-fuzz.yaml +35 -0
  313. moriarty/assets/templates/fuzzing/xss-search-fuzz.yaml +29 -0
  314. moriarty/assets/templates/git-config.yaml +18 -0
  315. moriarty/assets/templates/misconfigurations/cors-misconfiguration.yaml +30 -0
  316. moriarty/assets/templates/misconfigurations/debug-enabled.yaml +29 -0
  317. moriarty/assets/templates/misconfigurations/directory-listing.yaml +33 -0
  318. moriarty/assets/templates/misconfigurations/jwt-none-algo.yaml +30 -0
  319. moriarty/assets/templates/misconfigurations/ssl-tls-weak.yaml +23 -0
  320. moriarty/assets/templates/vulnerabilities/lfi-basic.yaml +31 -0
  321. moriarty/assets/templates/vulnerabilities/open-redirect.yaml +31 -0
  322. moriarty/assets/templates/vulnerabilities/rce-basic.yaml +34 -0
  323. moriarty/assets/templates/vulnerabilities/sqli-error.yaml +39 -0
  324. moriarty/assets/templates/vulnerabilities/ssrf-basic.yaml +31 -0
  325. moriarty/assets/templates/vulnerabilities/xss-reflected.yaml +38 -0
  326. moriarty/assets/templates/vulnerabilities/xxe-basic.yaml +30 -0
  327. moriarty/assets/wordlists/subdomains-1000.txt +1063 -0
  328. moriarty/cli/__init__.py +3 -0
  329. moriarty/cli/app.py +120 -0
  330. moriarty/cli/async_utils.py +19 -0
  331. moriarty/cli/dns.py +83 -0
  332. moriarty/cli/domain_cmd.py +572 -0
  333. moriarty/cli/email.py +383 -0
  334. moriarty/cli/email_investigate.py +224 -0
  335. moriarty/cli/intelligence.py +329 -0
  336. moriarty/cli/output.py +62 -0
  337. moriarty/cli/rdap.py +94 -0
  338. moriarty/cli/state.py +38 -0
  339. moriarty/cli/tls.py +91 -0
  340. moriarty/cli/user.py +227 -0
  341. moriarty/core/cache_backend.py +223 -0
  342. moriarty/core/config_manager.py +303 -0
  343. moriarty/correlator/__init__.py +0 -0
  344. moriarty/data/__init__.py +81 -0
  345. moriarty/data/ioc/__init__.py +142 -0
  346. moriarty/data/ioc/matcher.py +254 -0
  347. moriarty/data/ioc/types.py +267 -0
  348. moriarty/data/local_intelligence.py +507 -0
  349. moriarty/data/signature_loaders/__init__.py +103 -0
  350. moriarty/data/signature_loaders/base.py +54 -0
  351. moriarty/data/signature_loaders/ioc_feed.py +356 -0
  352. moriarty/data/signature_loaders/wappalyzer.py +112 -0
  353. moriarty/dsl/__init__.py +0 -0
  354. moriarty/dsl/loader.py +99 -0
  355. moriarty/dsl/schema.py +47 -0
  356. moriarty/export/__init__.py +0 -0
  357. moriarty/intelligence/__init__.py +27 -0
  358. moriarty/intelligence/__main__.py +150 -0
  359. moriarty/intelligence/config.py +395 -0
  360. moriarty/intelligence/ioc.py +267 -0
  361. moriarty/intelligence/signatures.py +550 -0
  362. moriarty/intelligence/storage.py +501 -0
  363. moriarty/interop/__init__.py +0 -0
  364. moriarty/logging/__init__.py +0 -0
  365. moriarty/logging/config.py +47 -0
  366. moriarty/models/__init__.py +16 -0
  367. moriarty/models/assertion.py +24 -0
  368. moriarty/models/entity.py +22 -0
  369. moriarty/models/evidence.py +37 -0
  370. moriarty/models/relation.py +24 -0
  371. moriarty/models/types.py +28 -0
  372. moriarty/modules/__init__.py +0 -0
  373. moriarty/modules/avatar_hash.py +184 -0
  374. moriarty/modules/directory_fuzzer.py +322 -0
  375. moriarty/modules/dns_scan.py +40 -0
  376. moriarty/modules/domain_scanner.py +620 -0
  377. moriarty/modules/email_check.py +98 -0
  378. moriarty/modules/email_investigate.py +267 -0
  379. moriarty/modules/email_security.py +274 -0
  380. moriarty/modules/googlemaps_lookup.py +106 -0
  381. moriarty/modules/headless_executor.py +201 -0
  382. moriarty/modules/orchestrator.py +60 -0
  383. moriarty/modules/passive_recon.py +444 -0
  384. moriarty/modules/phone_extractor.py +151 -0
  385. moriarty/modules/pipeline_orchestrator.py +726 -0
  386. moriarty/modules/port_scanner.py +129 -0
  387. moriarty/modules/rdap.py +61 -0
  388. moriarty/modules/rdap_extended.py +188 -0
  389. moriarty/modules/stealth_mode.py +610 -0
  390. moriarty/modules/subdomain_discovery.py +595 -0
  391. moriarty/modules/technology_profiler.py +361 -0
  392. moriarty/modules/template_executor.py +239 -0
  393. moriarty/modules/template_scanner.py +1048 -0
  394. moriarty/modules/tls_scan.py +46 -0
  395. moriarty/modules/tls_validator.py +188 -0
  396. moriarty/modules/vuln_scanner.py +483 -0
  397. moriarty/modules/waf_detector.py +585 -0
  398. moriarty/modules/wayback_discovery.py +234 -0
  399. moriarty/modules/web_crawler.py +163 -0
  400. moriarty/net/__init__.py +0 -0
  401. moriarty/net/dns_cache.py +175 -0
  402. moriarty/net/dns_client.py +188 -0
  403. moriarty/net/rdap_client.py +52 -0
  404. moriarty/net/smtp_client.py +114 -0
  405. moriarty/net/tls_client.py +111 -0
  406. moriarty/parsers/__init__.py +0 -0
  407. moriarty/parsers/html_parser.py +136 -0
  408. moriarty/tests/__init__.py +0 -0
  409. moriarty/tests/test_email_service.py +17 -0
  410. moriarty/tests/test_models.py +46 -0
  411. moriarty/tests/test_orchestrator.py +30 -0
  412. moriarty/tests/test_tls_client.py +18 -0
  413. moriarty_project-0.1.6.dist-info/METADATA +388 -0
  414. moriarty_project-0.1.6.dist-info/RECORD +418 -0
  415. moriarty_project-0.1.6.dist-info/WHEEL +4 -0
  416. moriarty_project-0.1.6.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,444 @@
1
+ """Coleta passiva de inteligência sobre domínios (Passive Recon)."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import base64
6
+ import json
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ import httpx
11
+ import structlog
12
+
13
+ from .technology_profiler import profile_domain
14
+ from ..net.rdap_client import RDAPClient
15
+
16
+ logger = structlog.get_logger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class PassiveReconResult:
21
+ """Estrutura consolidada dos artefatos coletados."""
22
+
23
+ domain: str
24
+ subdomains: Dict[str, List[str]] = field(default_factory=dict)
25
+ credentials: Dict[str, Any] = field(default_factory=dict)
26
+ leaks: Dict[str, Any] = field(default_factory=dict)
27
+ technologies: Dict[str, Any] = field(default_factory=dict)
28
+ reputation: Dict[str, Any] = field(default_factory=dict)
29
+ rdap: Optional[Dict[str, Any]] = None
30
+ whois: Optional[str] = None
31
+
32
+ def to_dict(self) -> Dict[str, Any]:
33
+ return {
34
+ "domain": self.domain,
35
+ "subdomains": self.subdomains,
36
+ "credentials": self.credentials,
37
+ "leaks": self.leaks,
38
+ "technologies": self.technologies,
39
+ "reputation": self.reputation,
40
+ "whois": self.whois,
41
+ "rdap": self.rdap,
42
+ }
43
+
44
+
45
+ class PassiveRecon:
46
+ """Orquestra integrações passivas usando as credenciais do ConfigManager."""
47
+
48
+ def __init__(self, domain: str, timeout: float = 15.0):
49
+ self.domain = domain.lower().strip()
50
+ self.timeout = timeout
51
+
52
+ try:
53
+ from moriarty.core.config_manager import config_manager
54
+
55
+ self.config = config_manager
56
+ except Exception: # pragma: no cover - fallback ao rodar fora do cli
57
+ self.config = None
58
+
59
+ self.session = httpx.AsyncClient(timeout=self.timeout, follow_redirects=True)
60
+
61
+ async def close(self) -> None:
62
+ await self.session.aclose()
63
+
64
+ async def collect(self) -> PassiveReconResult:
65
+ """Executa todas as coletas em paralelo."""
66
+ result = PassiveReconResult(domain=self.domain)
67
+
68
+ tasks = [
69
+ self._gather_subdomains(result),
70
+ self._gather_credentials(result),
71
+ self._fingerprint_http(result),
72
+ self._gather_reputation(result),
73
+ self._collect_whois(result),
74
+ self._collect_rdap(result),
75
+ ]
76
+
77
+ await asyncio.gather(*tasks)
78
+ return result
79
+
80
+ # ------------------------------------------------------------------
81
+ # Subdomains / Passive Sources
82
+ # ------------------------------------------------------------------
83
+ async def _gather_subdomains(self, result: PassiveReconResult) -> None:
84
+ collectors = [
85
+ self._from_passivetotal,
86
+ self._from_certspotter,
87
+ self._from_censys,
88
+ self._from_securitytrails,
89
+ self._from_spyse,
90
+ self._from_leakix,
91
+ ]
92
+
93
+ subdomains: Dict[str, List[str]] = {}
94
+
95
+ async def run_collector(handler):
96
+ name = handler.__name__.replace("_from_", "")
97
+ try:
98
+ data = await handler()
99
+ if data:
100
+ subdomains[name] = sorted(set(data))
101
+ except Exception as exc: # pragma: no cover - tolera falhas pontuais
102
+ logger.debug("passiverecon.source.error", source=name, error=str(exc))
103
+
104
+ await asyncio.gather(*(run_collector(c) for c in collectors))
105
+ result.subdomains = subdomains
106
+
107
+ async def _from_passivetotal(self) -> List[str]:
108
+ creds = self._get_credentials("passivetotal")
109
+ if not creds or not creds.get("username") or not creds.get("key"):
110
+ return []
111
+
112
+ auth = base64.b64encode(f"{creds['username']}:{creds['key']}".encode()).decode()
113
+ url = "https://api.passivetotal.org/v2/enrichment/subdomains"
114
+ params = {"query": self.domain}
115
+ headers = {"Authorization": f"Basic {auth}"}
116
+
117
+ response = await self.session.get(url, params=params, headers=headers)
118
+ if response.status_code != 200:
119
+ logger.debug("passiverecon.passivetotal.http", status=response.status_code)
120
+ return []
121
+
122
+ payload = response.json()
123
+ return [f"{sub}.{self.domain}" for sub in payload.get("subdomains", [])]
124
+
125
+ async def _from_certspotter(self) -> List[str]:
126
+ url = "https://api.certspotter.com/v1/issuances"
127
+ params = {
128
+ "domain": self.domain,
129
+ "include_subdomains": "true",
130
+ "expand": "dns_names",
131
+ }
132
+
133
+ response = await self.session.get(url, params=params)
134
+ if response.status_code != 200:
135
+ logger.debug("passiverecon.certspotter.http", status=response.status_code)
136
+ return []
137
+
138
+ hosts: List[str] = []
139
+ for entry in response.json():
140
+ hosts.extend(entry.get("dns_names", []))
141
+ return [h for h in hosts if h.endswith(self.domain)]
142
+
143
+ async def _from_censys(self) -> List[str]:
144
+ creds = self._get_credentials("censys")
145
+ if not creds or not creds.get("id") or not creds.get("secret"):
146
+ return []
147
+
148
+ query = f"services.tls.certificates.leaf_data.subject_dn: {self.domain}"
149
+ url = "https://search.censys.io/api/v2/hosts/search"
150
+ auth = (creds["id"], creds["secret"])
151
+ payload = {"q": query, "per_page": 50}
152
+
153
+ response = await self.session.post(url, auth=auth, json=payload)
154
+ if response.status_code != 200:
155
+ logger.debug("passiverecon.censys.http", status=response.status_code)
156
+ return []
157
+
158
+ hosts = []
159
+ for entry in response.json().get("result", {}).get("hits", []):
160
+ ip = entry.get("ip")
161
+ if ip:
162
+ hosts.append(ip)
163
+ for service in entry.get("services", []):
164
+ hostname = service.get("observed_dns_names") or []
165
+ hosts.extend([h for h in hostname if h.endswith(self.domain)])
166
+ return hosts
167
+
168
+ async def _from_securitytrails(self) -> List[str]:
169
+ creds = self._get_credentials("securitytrails")
170
+ if not creds or not creds.get("key"):
171
+ return []
172
+
173
+ headers = {"APIKEY": creds["key"]}
174
+ url = f"https://api.securitytrails.com/v1/history/{self.domain}/dns/a"
175
+ response = await self.session.get(url, headers=headers)
176
+ if response.status_code != 200:
177
+ logger.debug("passiverecon.securitytrails.http", status=response.status_code)
178
+ return []
179
+
180
+ hosts = []
181
+ data = response.json()
182
+ for record in data.get("records", []):
183
+ hosts.extend(record.get("values", []))
184
+ return hosts
185
+
186
+ async def _from_spyse(self) -> List[str]:
187
+ creds = self._get_credentials("spyse")
188
+ if not creds or not creds.get("key"):
189
+ return []
190
+
191
+ headers = {"Authorization": f"Bearer {creds['key']}"}
192
+ payload = {"search_params": {"domain": {"equals": self.domain}}}
193
+ url = "https://api.spyse.com/v4/data/domain/search"
194
+
195
+ response = await self.session.post(url, headers=headers, json=payload)
196
+ if response.status_code != 200:
197
+ logger.debug("passiverecon.spyse.http", status=response.status_code)
198
+ return []
199
+
200
+ hosts = []
201
+ for entry in response.json().get("data", {}).get("items", []):
202
+ hosts.extend(entry.get("related_domains", []))
203
+ return [h for h in hosts if h.endswith(self.domain)]
204
+
205
+ async def _from_leakix(self) -> List[str]:
206
+ creds = self._get_credentials("leakix")
207
+ if not creds or not creds.get("key"):
208
+ return []
209
+
210
+ headers = {"Authorization": f"Bearer {creds['key']}"}
211
+ url = f"https://leakix.net/api/v1/lookup/{self.domain}"
212
+ response = await self.session.get(url, headers=headers)
213
+ if response.status_code != 200:
214
+ logger.debug("passiverecon.leakix.http", status=response.status_code)
215
+ return []
216
+
217
+ results = response.json().get("results", [])
218
+ hosts = []
219
+ for entry in results:
220
+ host = entry.get("host")
221
+ if host:
222
+ hosts.append(host)
223
+ return hosts
224
+
225
+ # ------------------------------------------------------------------
226
+ # Credenciais e vazamentos
227
+ # ------------------------------------------------------------------
228
+ async def _gather_credentials(self, result: PassiveReconResult) -> None:
229
+ leaks: Dict[str, Any] = {}
230
+
231
+ hibp = await self._from_hibp()
232
+ if hibp:
233
+ leaks["haveibeenpwned"] = hibp
234
+
235
+ leakpeek = await self._from_leakpeek()
236
+ if leakpeek:
237
+ leaks["leakpeek"] = leakpeek
238
+
239
+ result.leaks = leaks
240
+
241
+ async def _from_hibp(self) -> Optional[Dict[str, Any]]:
242
+ creds = self._get_credentials("hibp")
243
+ if not creds or not creds.get("key"):
244
+ return None
245
+
246
+ headers = {
247
+ "hibp-api-key": creds["key"],
248
+ "user-agent": "moriarty-osint",
249
+ }
250
+ url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{self.domain}"
251
+
252
+ response = await self.session.get(url, headers=headers)
253
+ if response.status_code == 404:
254
+ return {"breaches": []}
255
+ if response.status_code != 200:
256
+ logger.debug("passiverecon.hibp.http", status=response.status_code)
257
+ return None
258
+ return {"breaches": response.json()}
259
+
260
+ async def _from_leakpeek(self) -> Optional[List[Dict[str, Any]]]:
261
+ creds = self._get_credentials("leakpeek")
262
+ if not creds or not creds.get("key"):
263
+ return None
264
+
265
+ headers = {"Authorization": f"Bearer {creds['key']}"}
266
+ url = f"https://api.leakpeek.com/v1/leaks/{self.domain}"
267
+ response = await self.session.get(url, headers=headers)
268
+ if response.status_code != 200:
269
+ logger.debug("passiverecon.leakpeek.http", status=response.status_code)
270
+ return None
271
+ return response.json().get("results", [])
272
+
273
+ # ------------------------------------------------------------------
274
+ # Fingerprinting HTTP / tecnologia
275
+ # ------------------------------------------------------------------
276
+ async def _fingerprint_http(self, result: PassiveReconResult) -> None:
277
+ try:
278
+ profile = await profile_domain(self.domain, session=self.session)
279
+ except Exception as exc: # pragma: no cover - tolerate fingerprint failures
280
+ logger.debug("passiverecon.fingerprint.error", domain=self.domain, error=str(exc))
281
+ profile = {}
282
+ result.technologies = profile
283
+
284
+ # ------------------------------------------------------------------
285
+ # Reputação / ameaças
286
+ # ------------------------------------------------------------------
287
+ async def _gather_reputation(self, result: PassiveReconResult) -> None:
288
+ rep: Dict[str, Any] = {}
289
+
290
+ otx = await self._from_alienvault()
291
+ if otx:
292
+ rep["alienvault"] = otx
293
+
294
+ threatfox = await self._from_threatfox()
295
+ if threatfox:
296
+ rep["threatfox"] = threatfox
297
+
298
+ urlhaus = await self._from_urlhaus()
299
+ if urlhaus:
300
+ rep["urlhaus"] = urlhaus
301
+
302
+ result.reputation = rep
303
+
304
+ async def _from_alienvault(self) -> Optional[Dict[str, Any]]:
305
+ url = f"https://otx.alienvault.com/api/v1/indicators/domain/{self.domain}/general"
306
+ response = await self.session.get(url)
307
+ if response.status_code != 200:
308
+ logger.debug("passiverecon.alienvault.http", status=response.status_code)
309
+ return None
310
+ data = response.json()
311
+ pulses = [pulse.get("name") for pulse in data.get("pulse_info", {}).get("pulses", [])]
312
+ return {
313
+ "pulses": pulses,
314
+ "alexa": data.get("alexa"),
315
+ "asn": data.get("asn"),
316
+ }
317
+
318
+ async def _from_threatfox(self) -> Optional[Dict[str, Any]]:
319
+ """Consulta threat intel da Abuse.ch (ThreatFox)."""
320
+ url = "https://threatfox-api.abuse.ch/api/v1/"
321
+ payload = {"query": "search_ioc", "search_term": self.domain}
322
+ try:
323
+ response = await self.session.post(url, json=payload)
324
+ except Exception as exc: # pragma: no cover
325
+ logger.debug("passiverecon.threatfox.error", error=str(exc))
326
+ return None
327
+
328
+ if response.status_code != 200:
329
+ logger.debug("passiverecon.threatfox.http", status=response.status_code)
330
+ return None
331
+
332
+ data = response.json()
333
+ if data.get("query_status") != "ok":
334
+ return None
335
+
336
+ records = data.get("data", [])
337
+ families = sorted({entry.get("malware", "") for entry in records if entry.get("malware")})
338
+ return {
339
+ "count": len(records),
340
+ "malware_families": families,
341
+ }
342
+
343
+ async def _from_urlhaus(self) -> Optional[Dict[str, Any]]:
344
+ """Consulta URLs maliciosas hospedadas no domínio (URLHaus)."""
345
+ url = "https://urlhaus-api.abuse.ch/v1/host/"
346
+ data = {"host": self.domain}
347
+ try:
348
+ response = await self.session.post(url, data=data)
349
+ except Exception as exc: # pragma: no cover
350
+ logger.debug("passiverecon.urlhaus.error", error=str(exc))
351
+ return None
352
+
353
+ if response.status_code != 200:
354
+ logger.debug("passiverecon.urlhaus.http", status=response.status_code)
355
+ return None
356
+
357
+ payload = response.json()
358
+ if payload.get("query_status") != "ok":
359
+ return None
360
+
361
+ entries = payload.get("urls", [])
362
+ latest = None
363
+ if entries:
364
+ latest_record = entries[0]
365
+ latest = {
366
+ "url": latest_record.get("url"),
367
+ "status": latest_record.get("url_status"),
368
+ "threat": latest_record.get("threat"),
369
+ "reporter": latest_record.get("reporter"),
370
+ }
371
+
372
+ return {
373
+ "count": len(entries),
374
+ "latest": latest,
375
+ }
376
+
377
+ # ------------------------------------------------------------------
378
+ # WHOIS
379
+ # ------------------------------------------------------------------
380
+ async def _collect_whois(self, result: PassiveReconResult) -> None:
381
+ cmd = await asyncio.create_subprocess_exec(
382
+ "whois",
383
+ self.domain,
384
+ stdout=asyncio.subprocess.PIPE,
385
+ stderr=asyncio.subprocess.PIPE,
386
+ )
387
+ stdout, _ = await cmd.communicate()
388
+ result.whois = stdout.decode(errors="ignore") if stdout else None
389
+
390
+ async def _collect_rdap(self, result: PassiveReconResult) -> None:
391
+ """Consulta RDAP oficial para complementar WHOIS."""
392
+ try:
393
+ client = RDAPClient(timeout=self.timeout, http2=True)
394
+ rdap = await client.fetch(f"domain/{self.domain}")
395
+ except Exception as exc: # pragma: no cover - RDAP falhas toleradas
396
+ logger.debug("passiverecon.rdap.error", domain=self.domain, error=str(exc))
397
+ return
398
+
399
+ payload = rdap.payload.copy()
400
+ result.rdap = {
401
+ "url": rdap.url,
402
+ "status": rdap.status,
403
+ "latency_ms": round(rdap.latency_ms, 2),
404
+ "handle": payload.get("handle"),
405
+ "registrar": payload.get("registrar", {}).get("name")
406
+ if isinstance(payload.get("registrar"), dict)
407
+ else payload.get("registrar"),
408
+ "events": payload.get("events", []),
409
+ "raw": payload,
410
+ }
411
+
412
+ # ------------------------------------------------------------------
413
+ # Utilidades
414
+ # ------------------------------------------------------------------
415
+ def _get_credentials(self, service: str) -> Dict[str, Any]:
416
+ if not self.config:
417
+ return {}
418
+
419
+ api_keys = getattr(self.config, "api_keys", None)
420
+ if not api_keys:
421
+ return {}
422
+
423
+ data = getattr(api_keys, service, None)
424
+ if isinstance(data, dict):
425
+ return data
426
+ if isinstance(data, str):
427
+ return {"key": data}
428
+
429
+ # Suporte a nomes específicos (ex.: censys_id/secret)
430
+ if service == "censys":
431
+ return {
432
+ "id": getattr(api_keys, "censys_id", None),
433
+ "secret": getattr(api_keys, "censys_secret", None),
434
+ }
435
+ if service == "passivetotal":
436
+ return {
437
+ "username": getattr(api_keys, "passivetotal_username", None),
438
+ "key": getattr(api_keys, "passivetotal_key", None),
439
+ }
440
+
441
+ return {}
442
+
443
+
444
+ __all__ = ["PassiveRecon", "PassiveReconResult"]
@@ -0,0 +1,151 @@
1
+ """Extração de telefones de perfis sociais."""
2
+ import re
3
+ from dataclasses import dataclass
4
+ from typing import List, Optional
5
+
6
+ import phonenumbers
7
+ import structlog
8
+
9
+ logger = structlog.get_logger(__name__)
10
+
11
+
12
+ @dataclass
13
+ class PhoneNumber:
14
+ """Número de telefone normalizado."""
15
+ raw: str
16
+ normalized: str # E.164 format
17
+ country_code: str
18
+ national_number: str
19
+ is_valid: bool
20
+ is_mobile: bool
21
+ carrier: Optional[str] = None
22
+ location: Optional[str] = None
23
+
24
+
25
+ class PhoneExtractor:
26
+ """Extrai e valida números de telefone."""
27
+
28
+ # Padrões comuns de telefone
29
+ PHONE_PATTERNS = [
30
+ r'\+\d{1,3}[\s\-]?\(?\d{1,4}\)?[\s\-]?\d{1,4}[\s\-]?\d{1,9}', # Internacional
31
+ r'\(?\d{3}\)?[\s\-]?\d{3}[\s\-]?\d{4}', # US/BR format
32
+ r'\d{2}[\s\-]?\d{4,5}[\s\-]?\d{4}', # BR format
33
+ r'\d{10,15}', # Números longos
34
+ ]
35
+
36
+ def extract_from_text(self, text: str, default_region: str = "US") -> List[PhoneNumber]:
37
+ """
38
+ Extrai números de telefone de um texto.
39
+
40
+ Args:
41
+ text: Texto para buscar
42
+ default_region: Código do país padrão (ISO 3166-1 alpha-2)
43
+ """
44
+ if not text:
45
+ return []
46
+
47
+ logger.debug("phone.extract.start", text_length=len(text))
48
+
49
+ phones = []
50
+ seen = set()
51
+
52
+ for pattern in self.PHONE_PATTERNS:
53
+ matches = re.finditer(pattern, text)
54
+
55
+ for match in matches:
56
+ raw = match.group(0)
57
+
58
+ # Remove duplicatas
59
+ if raw in seen:
60
+ continue
61
+ seen.add(raw)
62
+
63
+ # Tenta parsear com phonenumbers
64
+ try:
65
+ parsed = phonenumbers.parse(raw, default_region)
66
+
67
+ if phonenumbers.is_valid_number(parsed):
68
+ normalized = phonenumbers.format_number(
69
+ parsed,
70
+ phonenumbers.PhoneNumberFormat.E164
71
+ )
72
+
73
+ national = phonenumbers.format_number(
74
+ parsed,
75
+ phonenumbers.PhoneNumberFormat.NATIONAL
76
+ )
77
+
78
+ is_mobile = phonenumbers.number_type(parsed) in [
79
+ phonenumbers.PhoneNumberType.MOBILE,
80
+ phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE,
81
+ ]
82
+
83
+ # Tenta obter carrier (operadora)
84
+ carrier = None
85
+ try:
86
+ from phonenumbers import carrier as carrier_module
87
+ carrier = carrier_module.name_for_number(parsed, "en")
88
+ except:
89
+ pass
90
+
91
+ # Tenta obter localização
92
+ location = None
93
+ try:
94
+ from phonenumbers import geocoder
95
+ location = geocoder.description_for_number(parsed, "en")
96
+ except:
97
+ pass
98
+
99
+ phone = PhoneNumber(
100
+ raw=raw,
101
+ normalized=normalized,
102
+ country_code=f"+{parsed.country_code}",
103
+ national_number=national,
104
+ is_valid=True,
105
+ is_mobile=is_mobile,
106
+ carrier=carrier if carrier else None,
107
+ location=location if location else None,
108
+ )
109
+
110
+ phones.append(phone)
111
+ logger.debug("phone.extracted", phone=normalized, location=location)
112
+
113
+ except phonenumbers.NumberParseException:
114
+ # Número inválido, ignora
115
+ continue
116
+
117
+ logger.info("phone.extract.complete", count=len(phones))
118
+ return phones
119
+
120
+ def extract_from_profile(self, profile_data: dict, default_region: str = "US") -> List[PhoneNumber]:
121
+ """
122
+ Extrai telefones de dados de perfil.
123
+
124
+ Busca em campos comuns: bio, description, contact, etc.
125
+ """
126
+ phones = []
127
+
128
+ # Campos comuns onde telefones aparecem
129
+ fields_to_check = [
130
+ "bio", "description", "about", "contact", "phone",
131
+ "contact_info", "business_phone", "mobile", "tel",
132
+ ]
133
+
134
+ for field in fields_to_check:
135
+ if field in profile_data and profile_data[field]:
136
+ value = str(profile_data[field])
137
+ extracted = self.extract_from_text(value, default_region)
138
+ phones.extend(extracted)
139
+
140
+ # Remove duplicatas
141
+ unique_phones = {}
142
+ for phone in phones:
143
+ unique_phones[phone.normalized] = phone
144
+
145
+ return list(unique_phones.values())
146
+
147
+
148
+ __all__ = [
149
+ "PhoneExtractor",
150
+ "PhoneNumber",
151
+ ]