plexflow 0.0.64__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 (256) hide show
  1. plexflow/__init__.py +0 -0
  2. plexflow/__main__.py +15 -0
  3. plexflow/core/.DS_Store +0 -0
  4. plexflow/core/__init__.py +0 -0
  5. plexflow/core/context/__init__.py +0 -0
  6. plexflow/core/context/metadata/__init__.py +0 -0
  7. plexflow/core/context/metadata/context.py +32 -0
  8. plexflow/core/context/metadata/tmdb/__init__.py +0 -0
  9. plexflow/core/context/metadata/tmdb/context.py +45 -0
  10. plexflow/core/context/partial_context.py +46 -0
  11. plexflow/core/context/partials/__init__.py +8 -0
  12. plexflow/core/context/partials/cache.py +16 -0
  13. plexflow/core/context/partials/context.py +12 -0
  14. plexflow/core/context/partials/ids.py +37 -0
  15. plexflow/core/context/partials/movie.py +115 -0
  16. plexflow/core/context/partials/tgx_batch.py +33 -0
  17. plexflow/core/context/partials/tgx_context.py +34 -0
  18. plexflow/core/context/partials/torrents.py +23 -0
  19. plexflow/core/context/partials/watchlist.py +35 -0
  20. plexflow/core/context/plexflow_context.py +29 -0
  21. plexflow/core/context/plexflow_property.py +36 -0
  22. plexflow/core/context/root/__init__.py +0 -0
  23. plexflow/core/context/root/context.py +25 -0
  24. plexflow/core/context/select/__init__.py +0 -0
  25. plexflow/core/context/select/context.py +45 -0
  26. plexflow/core/context/torrent/__init__.py +0 -0
  27. plexflow/core/context/torrent/context.py +43 -0
  28. plexflow/core/context/torrent/tpb/__init__.py +0 -0
  29. plexflow/core/context/torrent/tpb/context.py +45 -0
  30. plexflow/core/context/torrent/yts/__init__.py +0 -0
  31. plexflow/core/context/torrent/yts/context.py +45 -0
  32. plexflow/core/context/watchlist/__init__.py +0 -0
  33. plexflow/core/context/watchlist/context.py +46 -0
  34. plexflow/core/downloads/__init__.py +0 -0
  35. plexflow/core/downloads/candidates/__init__.py +0 -0
  36. plexflow/core/downloads/candidates/download_candidate.py +210 -0
  37. plexflow/core/downloads/candidates/filtered.py +51 -0
  38. plexflow/core/downloads/candidates/utils.py +39 -0
  39. plexflow/core/env/__init__.py +0 -0
  40. plexflow/core/env/env.py +31 -0
  41. plexflow/core/genai/__init__.py +0 -0
  42. plexflow/core/genai/bot.py +9 -0
  43. plexflow/core/genai/plexa.py +54 -0
  44. plexflow/core/genai/torrent/imdb_verify.py +65 -0
  45. plexflow/core/genai/torrent/movie.py +25 -0
  46. plexflow/core/genai/utils/__init__.py +0 -0
  47. plexflow/core/genai/utils/loader.py +5 -0
  48. plexflow/core/metadata/__init__.py +0 -0
  49. plexflow/core/metadata/auto/__init__.py +0 -0
  50. plexflow/core/metadata/auto/auto_meta.py +40 -0
  51. plexflow/core/metadata/auto/auto_providers/__init__.py +0 -0
  52. plexflow/core/metadata/auto/auto_providers/auto/__init__.py +0 -0
  53. plexflow/core/metadata/auto/auto_providers/auto/episode.py +49 -0
  54. plexflow/core/metadata/auto/auto_providers/auto/item.py +55 -0
  55. plexflow/core/metadata/auto/auto_providers/auto/movie.py +13 -0
  56. plexflow/core/metadata/auto/auto_providers/auto/season.py +43 -0
  57. plexflow/core/metadata/auto/auto_providers/auto/show.py +26 -0
  58. plexflow/core/metadata/auto/auto_providers/imdb/__init__.py +0 -0
  59. plexflow/core/metadata/auto/auto_providers/imdb/movie.py +36 -0
  60. plexflow/core/metadata/auto/auto_providers/imdb/show.py +45 -0
  61. plexflow/core/metadata/auto/auto_providers/moviemeter/__init__.py +0 -0
  62. plexflow/core/metadata/auto/auto_providers/moviemeter/movie.py +40 -0
  63. plexflow/core/metadata/auto/auto_providers/plex/__init__.py +0 -0
  64. plexflow/core/metadata/auto/auto_providers/plex/movie.py +39 -0
  65. plexflow/core/metadata/auto/auto_providers/tmdb/__init__.py +0 -0
  66. plexflow/core/metadata/auto/auto_providers/tmdb/episode.py +30 -0
  67. plexflow/core/metadata/auto/auto_providers/tmdb/movie.py +36 -0
  68. plexflow/core/metadata/auto/auto_providers/tmdb/season.py +23 -0
  69. plexflow/core/metadata/auto/auto_providers/tmdb/show.py +41 -0
  70. plexflow/core/metadata/auto/auto_providers/tmdb.py +92 -0
  71. plexflow/core/metadata/auto/auto_providers/tvdb/__init__.py +0 -0
  72. plexflow/core/metadata/auto/auto_providers/tvdb/episode.py +28 -0
  73. plexflow/core/metadata/auto/auto_providers/tvdb/movie.py +36 -0
  74. plexflow/core/metadata/auto/auto_providers/tvdb/season.py +25 -0
  75. plexflow/core/metadata/auto/auto_providers/tvdb/show.py +41 -0
  76. plexflow/core/metadata/providers/__init__.py +0 -0
  77. plexflow/core/metadata/providers/imdb/__init__.py +0 -0
  78. plexflow/core/metadata/providers/imdb/datatypes.py +53 -0
  79. plexflow/core/metadata/providers/imdb/imdb.py +112 -0
  80. plexflow/core/metadata/providers/moviemeter/__init__.py +0 -0
  81. plexflow/core/metadata/providers/moviemeter/datatypes.py +111 -0
  82. plexflow/core/metadata/providers/moviemeter/moviemeter.py +42 -0
  83. plexflow/core/metadata/providers/plex/__init__.py +0 -0
  84. plexflow/core/metadata/providers/plex/datatypes.py +693 -0
  85. plexflow/core/metadata/providers/plex/plex.py +167 -0
  86. plexflow/core/metadata/providers/tmdb/__init__.py +0 -0
  87. plexflow/core/metadata/providers/tmdb/datatypes.py +460 -0
  88. plexflow/core/metadata/providers/tmdb/tmdb.py +85 -0
  89. plexflow/core/metadata/providers/tvdb/__init__.py +0 -0
  90. plexflow/core/metadata/providers/tvdb/datatypes.py +257 -0
  91. plexflow/core/metadata/providers/tvdb/tv_datatypes.py +554 -0
  92. plexflow/core/metadata/providers/tvdb/tvdb.py +65 -0
  93. plexflow/core/metadata/providers/universal/__init__.py +0 -0
  94. plexflow/core/metadata/providers/universal/movie.py +130 -0
  95. plexflow/core/metadata/providers/universal/old.py +192 -0
  96. plexflow/core/metadata/providers/universal/show.py +107 -0
  97. plexflow/core/plex/__init__.py +0 -0
  98. plexflow/core/plex/api/context/authorized.py +15 -0
  99. plexflow/core/plex/api/context/discover.py +14 -0
  100. plexflow/core/plex/api/context/library.py +14 -0
  101. plexflow/core/plex/discover/__init__.py +0 -0
  102. plexflow/core/plex/discover/activity.py +448 -0
  103. plexflow/core/plex/discover/comment.py +89 -0
  104. plexflow/core/plex/discover/feed.py +11 -0
  105. plexflow/core/plex/hooks/__init__.py +0 -0
  106. plexflow/core/plex/hooks/plex_authorized.py +60 -0
  107. plexflow/core/plex/hooks/plexflow_database.py +6 -0
  108. plexflow/core/plex/library/__init__.py +0 -0
  109. plexflow/core/plex/library/library.py +103 -0
  110. plexflow/core/plex/token/__init__.py +0 -0
  111. plexflow/core/plex/token/auto_token.py +91 -0
  112. plexflow/core/plex/utils/__init__.py +0 -0
  113. plexflow/core/plex/utils/paginated.py +39 -0
  114. plexflow/core/plex/watchlist/__init__.py +0 -0
  115. plexflow/core/plex/watchlist/datatypes.py +124 -0
  116. plexflow/core/plex/watchlist/watchlist.py +23 -0
  117. plexflow/core/storage/__init__.py +0 -0
  118. plexflow/core/storage/object/__init__.py +0 -0
  119. plexflow/core/storage/object/plexflow_storage.py +143 -0
  120. plexflow/core/storage/object/redis_storage.py +169 -0
  121. plexflow/core/subtitles/__init__.py +0 -0
  122. plexflow/core/subtitles/providers/__init__.py +0 -0
  123. plexflow/core/subtitles/providers/auto_subtitles.py +48 -0
  124. plexflow/core/subtitles/providers/oss/__init__.py +0 -0
  125. plexflow/core/subtitles/providers/oss/datatypes.py +104 -0
  126. plexflow/core/subtitles/providers/oss/download.py +48 -0
  127. plexflow/core/subtitles/providers/oss/old.py +144 -0
  128. plexflow/core/subtitles/providers/oss/oss.py +400 -0
  129. plexflow/core/subtitles/providers/oss/oss_subtitle.py +32 -0
  130. plexflow/core/subtitles/providers/oss/search.py +52 -0
  131. plexflow/core/subtitles/providers/oss/unlimited_oss.py +231 -0
  132. plexflow/core/subtitles/providers/oss/utils/__init__.py +0 -0
  133. plexflow/core/subtitles/providers/oss/utils/config.py +63 -0
  134. plexflow/core/subtitles/providers/oss/utils/download_client.py +22 -0
  135. plexflow/core/subtitles/providers/oss/utils/exceptions.py +35 -0
  136. plexflow/core/subtitles/providers/oss/utils/file_utils.py +83 -0
  137. plexflow/core/subtitles/providers/oss/utils/languages.py +78 -0
  138. plexflow/core/subtitles/providers/oss/utils/response_base.py +221 -0
  139. plexflow/core/subtitles/providers/oss/utils/responses.py +176 -0
  140. plexflow/core/subtitles/providers/oss/utils/srt.py +561 -0
  141. plexflow/core/subtitles/results/__init__.py +0 -0
  142. plexflow/core/subtitles/results/subtitle.py +170 -0
  143. plexflow/core/torrents/__init__.py +0 -0
  144. plexflow/core/torrents/analyzers/analyzed_torrent.py +143 -0
  145. plexflow/core/torrents/analyzers/analyzer.py +45 -0
  146. plexflow/core/torrents/analyzers/torrentquest/analyzer.py +47 -0
  147. plexflow/core/torrents/auto/auto_providers/auto/__init__.py +0 -0
  148. plexflow/core/torrents/auto/auto_providers/auto/torrent.py +64 -0
  149. plexflow/core/torrents/auto/auto_providers/tpb/torrent.py +62 -0
  150. plexflow/core/torrents/auto/auto_torrents.py +29 -0
  151. plexflow/core/torrents/providers/__init__.py +0 -0
  152. plexflow/core/torrents/providers/ext/__init__.py +0 -0
  153. plexflow/core/torrents/providers/ext/ext.py +18 -0
  154. plexflow/core/torrents/providers/ext/utils.py +64 -0
  155. plexflow/core/torrents/providers/extratorrent/__init__.py +0 -0
  156. plexflow/core/torrents/providers/extratorrent/extratorrent.py +21 -0
  157. plexflow/core/torrents/providers/extratorrent/utils.py +66 -0
  158. plexflow/core/torrents/providers/eztv/__init__.py +0 -0
  159. plexflow/core/torrents/providers/eztv/eztv.py +47 -0
  160. plexflow/core/torrents/providers/eztv/utils.py +83 -0
  161. plexflow/core/torrents/providers/rarbg2/__init__.py +0 -0
  162. plexflow/core/torrents/providers/rarbg2/rarbg2.py +19 -0
  163. plexflow/core/torrents/providers/rarbg2/utils.py +76 -0
  164. plexflow/core/torrents/providers/snowfl/__init__.py +0 -0
  165. plexflow/core/torrents/providers/snowfl/snowfl.py +36 -0
  166. plexflow/core/torrents/providers/snowfl/utils.py +59 -0
  167. plexflow/core/torrents/providers/tgx/__init__.py +0 -0
  168. plexflow/core/torrents/providers/tgx/context.py +50 -0
  169. plexflow/core/torrents/providers/tgx/dump.py +40 -0
  170. plexflow/core/torrents/providers/tgx/tgx.py +22 -0
  171. plexflow/core/torrents/providers/tgx/utils.py +61 -0
  172. plexflow/core/torrents/providers/therarbg/__init__.py +0 -0
  173. plexflow/core/torrents/providers/therarbg/therarbg.py +17 -0
  174. plexflow/core/torrents/providers/therarbg/utils.py +61 -0
  175. plexflow/core/torrents/providers/torrentquest/__init__.py +0 -0
  176. plexflow/core/torrents/providers/torrentquest/torrentquest.py +20 -0
  177. plexflow/core/torrents/providers/torrentquest/utils.py +70 -0
  178. plexflow/core/torrents/providers/tpb/__init__.py +0 -0
  179. plexflow/core/torrents/providers/tpb/tpb.py +17 -0
  180. plexflow/core/torrents/providers/tpb/utils.py +139 -0
  181. plexflow/core/torrents/providers/yts/__init__.py +0 -0
  182. plexflow/core/torrents/providers/yts/utils.py +57 -0
  183. plexflow/core/torrents/providers/yts/yts.py +31 -0
  184. plexflow/core/torrents/results/__init__.py +0 -0
  185. plexflow/core/torrents/results/torrent.py +165 -0
  186. plexflow/core/torrents/results/universal.py +220 -0
  187. plexflow/core/torrents/results/utils.py +15 -0
  188. plexflow/events/__init__.py +0 -0
  189. plexflow/events/download/__init__.py +0 -0
  190. plexflow/events/download/torrent_events.py +96 -0
  191. plexflow/events/publish/__init__.py +0 -0
  192. plexflow/events/publish/publish.py +34 -0
  193. plexflow/logging/__init__.py +0 -0
  194. plexflow/logging/log_setup.py +8 -0
  195. plexflow/spiders/quiet_logger.py +9 -0
  196. plexflow/spiders/tgx/pipelines/dump_json_pipeline.py +30 -0
  197. plexflow/spiders/tgx/pipelines/meta_pipeline.py +13 -0
  198. plexflow/spiders/tgx/pipelines/publish_pipeline.py +14 -0
  199. plexflow/spiders/tgx/pipelines/torrent_info_pipeline.py +12 -0
  200. plexflow/spiders/tgx/pipelines/validation_pipeline.py +17 -0
  201. plexflow/spiders/tgx/settings.py +36 -0
  202. plexflow/spiders/tgx/spider.py +72 -0
  203. plexflow/utils/__init__.py +0 -0
  204. plexflow/utils/antibot/human_like_requests.py +122 -0
  205. plexflow/utils/api/__init__.py +0 -0
  206. plexflow/utils/api/context/http.py +62 -0
  207. plexflow/utils/api/rest/__init__.py +0 -0
  208. plexflow/utils/api/rest/antibot_restful.py +68 -0
  209. plexflow/utils/api/rest/restful.py +49 -0
  210. plexflow/utils/captcha/__init__.py +0 -0
  211. plexflow/utils/captcha/bypass/__init__.py +0 -0
  212. plexflow/utils/captcha/bypass/decode_audio.py +34 -0
  213. plexflow/utils/download/__init__.py +0 -0
  214. plexflow/utils/download/gz.py +26 -0
  215. plexflow/utils/filesystem/__init__.py +0 -0
  216. plexflow/utils/filesystem/search.py +129 -0
  217. plexflow/utils/gmail/__init__.py +0 -0
  218. plexflow/utils/gmail/mails.py +116 -0
  219. plexflow/utils/hooks/__init__.py +0 -0
  220. plexflow/utils/hooks/http.py +84 -0
  221. plexflow/utils/hooks/postgresql.py +93 -0
  222. plexflow/utils/hooks/redis.py +112 -0
  223. plexflow/utils/image/storage.py +36 -0
  224. plexflow/utils/imdb/__init__.py +0 -0
  225. plexflow/utils/imdb/imdb_codes.py +107 -0
  226. plexflow/utils/pubsub/consume.py +82 -0
  227. plexflow/utils/pubsub/produce.py +25 -0
  228. plexflow/utils/retry/__init__.py +0 -0
  229. plexflow/utils/retry/utils.py +38 -0
  230. plexflow/utils/strings/__init__.py +0 -0
  231. plexflow/utils/strings/filesize.py +55 -0
  232. plexflow/utils/strings/language.py +14 -0
  233. plexflow/utils/subtitle/search.py +76 -0
  234. plexflow/utils/tasks/decorators.py +78 -0
  235. plexflow/utils/tasks/k8s/task.py +70 -0
  236. plexflow/utils/thread_safe/safe_list.py +54 -0
  237. plexflow/utils/thread_safe/safe_set.py +69 -0
  238. plexflow/utils/torrent/__init__.py +0 -0
  239. plexflow/utils/torrent/analyze.py +118 -0
  240. plexflow/utils/torrent/extract/common.py +37 -0
  241. plexflow/utils/torrent/extract/ext.py +2391 -0
  242. plexflow/utils/torrent/extract/extratorrent.py +56 -0
  243. plexflow/utils/torrent/extract/kat.py +1581 -0
  244. plexflow/utils/torrent/extract/tgx.py +96 -0
  245. plexflow/utils/torrent/extract/therarbg.py +170 -0
  246. plexflow/utils/torrent/extract/torrentquest.py +171 -0
  247. plexflow/utils/torrent/files.py +36 -0
  248. plexflow/utils/torrent/hash.py +90 -0
  249. plexflow/utils/transcribe/__init__.py +0 -0
  250. plexflow/utils/transcribe/speech2text.py +40 -0
  251. plexflow/utils/video/__init__.py +0 -0
  252. plexflow/utils/video/subtitle.py +73 -0
  253. plexflow-0.0.64.dist-info/METADATA +71 -0
  254. plexflow-0.0.64.dist-info/RECORD +256 -0
  255. plexflow-0.0.64.dist-info/WHEEL +4 -0
  256. plexflow-0.0.64.dist-info/entry_points.txt +24 -0
@@ -0,0 +1,170 @@
1
+ from abc import ABC, abstractmethod
2
+ from datetime import datetime
3
+ from typing import List, Union
4
+ import PTN
5
+
6
+ class Subtitle(ABC):
7
+ """
8
+ Abstract base class for subtitle objects.
9
+ """
10
+
11
+ @property
12
+ @abstractmethod
13
+ def release_name(self) -> str:
14
+ """
15
+ Gets the release name of the subtitle.
16
+
17
+ Returns:
18
+ str: The release name of the subtitle.
19
+ """
20
+ pass
21
+
22
+ @property
23
+ def uploader(self) -> str:
24
+ """
25
+ Gets the uploader of the subtitle.
26
+
27
+ Returns:
28
+ str: The uploader of the subtitle.
29
+ """
30
+ return self.source
31
+
32
+ @property
33
+ @abstractmethod
34
+ def date(self) -> datetime:
35
+ """
36
+ Gets the date of the subtitle.
37
+
38
+ Returns:
39
+ datetime: The date of the subtitle.
40
+ """
41
+ pass
42
+
43
+ @property
44
+ @abstractmethod
45
+ def imdb_code(self) -> str:
46
+ """
47
+ Gets the IMDb code of the subtitle.
48
+
49
+ Returns:
50
+ str: The IMDb code of the subtitle.
51
+ """
52
+ pass
53
+
54
+ @property
55
+ def source(self) -> str:
56
+ """
57
+ Gets the source of the subtitle.
58
+
59
+ Returns:
60
+ str: The source of the subtitle.
61
+ """
62
+ return self.src
63
+
64
+ @property
65
+ @abstractmethod
66
+ def language(self):
67
+ """
68
+ Gets the language of the subtitle.
69
+
70
+ Returns:
71
+ The language of the subtitle.
72
+ """
73
+ pass
74
+
75
+ @property
76
+ def subtitle_id(self) -> str:
77
+ """
78
+ Gets the subtitle ID.
79
+
80
+ Returns:
81
+ str: The subtitle ID.
82
+ """
83
+ return f"{self.source}_{self.hash}"
84
+
85
+ @property
86
+ def parsed_release_name(self) -> dict:
87
+ """
88
+ Parses the release name of the subtitle.
89
+
90
+ Returns:
91
+ dict: The parsed release name of the subtitle.
92
+ """
93
+ return PTN.parse(self.release_name)
94
+
95
+ @property
96
+ def encoder_name(self) -> str:
97
+ """
98
+ Gets the encoder name of the subtitle.
99
+
100
+ Returns:
101
+ str: The encoder name of the subtitle.
102
+ """
103
+ parts = self.parsed_release_name
104
+ return parts.get("encoder")
105
+
106
+ @property
107
+ def season(self) -> Union[int, List[int]]:
108
+ """
109
+ Gets the season number of the subtitle.
110
+
111
+ Returns:
112
+ Union[int, List[int]]: The season number of the subtitle.
113
+ """
114
+ parts = self.parsed_release_name
115
+ return parts.get("season")
116
+
117
+ @property
118
+ def episode(self) -> Union[int, List[int]]:
119
+ """
120
+ Gets the episode number of the subtitle.
121
+
122
+ Returns:
123
+ Union[int, List[int]]: The episode number of the subtitle.
124
+ """
125
+ parts = self.parsed_release_name
126
+ return parts.get("episode")
127
+
128
+ @property
129
+ def has_multiple_episodes(self):
130
+ """
131
+ Checks if the subtitle has multiple episodes.
132
+
133
+ Returns:
134
+ bool: True if the subtitle has multiple episodes, False otherwise.
135
+ """
136
+ tmp = self.episode
137
+ return isinstance(tmp, list) and len(tmp) > 1
138
+
139
+ @property
140
+ def has_multiple_seasons(self):
141
+ """
142
+ Checks if the subtitle has multiple seasons.
143
+
144
+ Returns:
145
+ bool: True if the subtitle has multiple seasons, False otherwise.
146
+ """
147
+ tmp = self.season
148
+ return isinstance(tmp, list) and len(tmp) > 1
149
+
150
+ @property
151
+ def title(self) -> str:
152
+ """
153
+ Gets the title of the subtitle.
154
+
155
+ Returns:
156
+ str: The title of the subtitle.
157
+ """
158
+ parts = self.parsed_release_name
159
+ return parts.get("title")
160
+
161
+ @property
162
+ def year(self) -> int:
163
+ """
164
+ Gets the year of the subtitle.
165
+
166
+ Returns:
167
+ int: The year of the subtitle.
168
+ """
169
+ parts = self.parsed_release_name
170
+ return parts.get("year")
File without changes
@@ -0,0 +1,143 @@
1
+ from plexflow.core.torrents.results.torrent import Torrent
2
+ from datetime import datetime
3
+ from plexflow.utils.imdb.imdb_codes import IMDbCode
4
+ from typing import List, Union
5
+ from plexflow.utils.torrent.files import TorrentFile
6
+
7
+ class AnalyzedTorrent:
8
+ def __init__(self, torrent: Torrent, **kwargs):
9
+ self._torrent = torrent
10
+ self._details = kwargs
11
+ self._ai_details = {}
12
+
13
+ def update_from_ai(self, **kwargs):
14
+ self._ai_details.update(kwargs)
15
+
16
+ @property
17
+ def release_name(self):
18
+ return self._torrent.release_name or self._details.get('release_name') or self._ai_details.get('release_name')
19
+
20
+ @property
21
+ def seeds(self) -> int:
22
+ return self._torrent.seeds or self._details.get('seeds') or self._ai_details.get('seeds')
23
+
24
+ @property
25
+ def peers(self) -> int:
26
+ return self._torrent.peers or self._details.get('peers') or self._ai_details.get('peers')
27
+
28
+ @property
29
+ def size_bytes(self) -> int:
30
+ return self._torrent.size_bytes or self._details.get('size_bytes') or self._ai_details.get('size_bytes')
31
+
32
+ @property
33
+ def magnet(self) -> str:
34
+ return self._torrent.magnet or self._details.get('magnet')
35
+
36
+ @property
37
+ def hash(self) -> str:
38
+ return self._torrent.hash or self._details.get('hash')
39
+
40
+ @property
41
+ def uploader(self) -> str:
42
+ return self._torrent.uploader or self._details.get('uploader') or self._ai_details.get('uploader')
43
+
44
+ @property
45
+ def date(self) -> datetime:
46
+ return self._torrent.date or self._details.get('date') or self._ai_details.get('date')
47
+
48
+ @property
49
+ def imdb_code(self) -> IMDbCode:
50
+ return self._torrent.imdb_code or IMDbCode(self._details.get('imdb_code')) if self._details.get('imdb_code') else None
51
+
52
+ @property
53
+ def source(self) -> str:
54
+ return self._torrent.source
55
+
56
+ @property
57
+ def torrent_id(self) -> str:
58
+ return self._torrent.torrent_id or self._details.get('torrent_id')
59
+
60
+ @property
61
+ def parsed_release_name(self) -> dict:
62
+ return self._torrent.parsed_release_name
63
+
64
+ @property
65
+ def encoder_name(self) -> str:
66
+ return self._torrent.encoder_name
67
+
68
+ @property
69
+ def season(self) -> Union[int, List[int]]:
70
+ return self._torrent.season
71
+
72
+ @property
73
+ def episode(self) -> Union[int, List[int]]:
74
+ return self._torrent.episode
75
+
76
+ @property
77
+ def has_multiple_episodes(self):
78
+ return self._torrent.has_multiple_episodes
79
+
80
+ @property
81
+ def has_multiple_seasons(self):
82
+ return self._torrent.has_multiple_seasons
83
+
84
+ @property
85
+ def title(self) -> str:
86
+ return self._torrent.title
87
+
88
+ @property
89
+ def year(self) -> int:
90
+ return self._torrent.year
91
+
92
+ @property
93
+ def quality(self) -> str:
94
+ return self._torrent.quality
95
+
96
+ @property
97
+ def is_bad_quality(self):
98
+ return self._torrent.is_bad_quality
99
+
100
+ @property
101
+ def url(self) -> str:
102
+ return self._torrent.url
103
+
104
+ @property
105
+ def html_content(self) -> str:
106
+ return self._details.get('html_content')
107
+
108
+ @property
109
+ def resolved_urls(self) -> dict:
110
+ return self._details.get('resolved_urls')
111
+
112
+ @property
113
+ def files(self) -> List[TorrentFile]:
114
+ return self._details.get('file_list')
115
+
116
+ @property
117
+ def has_native_subtitles(self):
118
+ subs = self._details.get('subtitles')
119
+ if subs and len(subs) > 0:
120
+ return True
121
+ return False
122
+
123
+ @property
124
+ def has_native_dutch_subtitles(self):
125
+ subs = self._details.get('subtitles')
126
+ if subs and len(subs) > 0:
127
+ for sub in subs:
128
+ if 'dutch' in sub.lower():
129
+ return True
130
+ return False
131
+
132
+ @property
133
+ def has_native_english_subtitles(self):
134
+ subs = self._details.get('subtitles')
135
+ if subs and len(subs) > 0:
136
+ for sub in subs:
137
+ if 'english' in sub.lower():
138
+ return True
139
+ return False
140
+
141
+ @property
142
+ def native_subtitles(self):
143
+ return self._details.get('subtitles')
@@ -0,0 +1,45 @@
1
+ from plexflow.core.torrents.results.torrent import Torrent
2
+ import requests
3
+ from plexflow.core.torrents.analyzers.analyzed_torrent import AnalyzedTorrent
4
+ from abc import ABC, abstractmethod
5
+
6
+ class TorrentAnalyzer(ABC):
7
+ def __init__(self, torrent: Torrent):
8
+ self._torrent = torrent
9
+
10
+ self._resolved_urls = self.resolve_urls()
11
+ self._full_urls_content = '\n\n'.join(self._resolved_urls.values())
12
+
13
+ def resolve_urls(self):
14
+ resolved_urls = {}
15
+
16
+ if self._torrent.url:
17
+ print(f"Resolving URL: {self._torrent.url}")
18
+ r = requests.get(
19
+ url=self._torrent.url,
20
+ headers=self._headers,
21
+ )
22
+
23
+ r.raise_for_status()
24
+
25
+ html = r.text
26
+
27
+ resolved_urls[self._torrent.url] = html
28
+ else:
29
+ print(f"Torrent {self._torrent} does not have a URL.")
30
+
31
+ return resolved_urls
32
+
33
+ @abstractmethod
34
+ def do_analysis(self):
35
+ pass
36
+
37
+ def analyze(self):
38
+ details = self.do_analysis()
39
+ if isinstance(details, dict):
40
+ details = {
41
+ **details,
42
+ 'resolved_urls': self._resolved_urls,
43
+ 'html_content': self._full_urls_content,
44
+ }
45
+ return AnalyzedTorrent(self._torrent, **details)
@@ -0,0 +1,47 @@
1
+ from plexflow.core.torrents.results.torrent import Torrent
2
+ from plexflow.utils.torrent.extract.torrentquest import extract_torrent_details
3
+ import requests
4
+ import re
5
+ from plexflow.core.torrents.analyzers.analyzed_torrent import AnalyzedTorrent
6
+ from plexflow.core.torrents.analyzers.analyzer import TorrentAnalyzer
7
+
8
+ class TorrentQuestAnalyzer(TorrentAnalyzer):
9
+ def __init__(self, torrent: Torrent):
10
+ self._nfo_link_pattern = re.compile(r"information\('file_([^']+)'\)", re.IGNORECASE)
11
+
12
+ self._headers= {
13
+ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
14
+ 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
15
+ }
16
+
17
+ super().__init__(torrent)
18
+
19
+ def resolve_urls(self):
20
+ resolved_urls = super().resolve_urls()
21
+
22
+ if len(resolved_urls) > 0:
23
+ html = next(iter(resolved_urls.values()))
24
+ nfo_match = next(re.finditer(self._nfo_link_pattern, html), None)
25
+
26
+ if nfo_match:
27
+ nfo_id = nfo_match.group(1)
28
+ nfo_url = f"https://torrentquest.com/info/{nfo_id}"
29
+
30
+ print(f"Resolving NFO URL: {nfo_url}")
31
+ r_nfo = requests.get(
32
+ url=nfo_url,
33
+ headers=self._headers,
34
+ )
35
+
36
+ r_nfo.raise_for_status()
37
+
38
+ nfo_html = r_nfo.text
39
+
40
+ resolved_urls[nfo_url] = nfo_html
41
+
42
+ return resolved_urls
43
+
44
+ def do_analysis(self):
45
+ # resolve torrent URL and extract the torrent information from the page
46
+ details = extract_torrent_details(self._full_urls_content)
47
+ return details
@@ -0,0 +1,64 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+ from plexflow.utils.torrent.hash import extract_torrent_hash
4
+ import PTN
5
+
6
+ class AutoTorrent(ABC):
7
+ def __init__(self, imdb_id: str, release_name: str, magnet_uri: str, source: str) -> None:
8
+ self._imdb_id = imdb_id
9
+ self._release_name = release_name
10
+ self._magnet_uri = magnet_uri
11
+ self._source = source
12
+
13
+ @property
14
+ @abstractmethod
15
+ def id(self) -> Any:
16
+ pass
17
+
18
+ @property
19
+ def hash(self) -> str:
20
+ return extract_torrent_hash(self.magnet_uri)
21
+
22
+ @property
23
+ def release_name(self) -> str:
24
+ return self._release_name
25
+
26
+ @property
27
+ def magnet_uri(self) -> str:
28
+ return self._magnet_uri
29
+
30
+ @property
31
+ def parsed_release_name(self) -> dict:
32
+ return PTN.parse(self.release_name)
33
+
34
+ @property
35
+ def title(self) -> str:
36
+ return self.parsed_release_name.get("title")
37
+
38
+ @property
39
+ def year(self) -> int:
40
+ return self.parsed_release_name.get("year")
41
+
42
+ @property
43
+ def encoder(self) -> str:
44
+ return self.parsed_release_name.get("encoder")
45
+
46
+ @property
47
+ def resolution(self) -> str:
48
+ return self.parsed_release_name.get("resolution")
49
+
50
+ @property
51
+ def quality(self) -> str:
52
+ return self.parsed_release_name.get("quality")
53
+
54
+ @property
55
+ def is_cam(self) -> bool:
56
+ return self.parsed_release_name.get("quality") == "CAM"
57
+
58
+ @property
59
+ def imdb_id(self) -> str:
60
+ return self._imdb_id
61
+
62
+ @property
63
+ def source(self) -> str:
64
+ return self._source
@@ -0,0 +1,62 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+ from plexflow.utils.torrent.hash import extract_torrent_hash
4
+ import PTN
5
+ from plexflow.core.torrents.providers.tpb.utils import TPBSearchResult
6
+
7
+ class AutoTPBTorrent(ABC):
8
+ def __init__(self, torrent: TPBSearchResult) -> None:
9
+ super().__init__(imdb_id=torrent.imdb_id, release_name=torrent.title, magnet_uri=torrent.magnet_uri, source="TPB")
10
+
11
+ @property
12
+ @abstractmethod
13
+ def id(self) -> Any:
14
+ pass
15
+
16
+ @property
17
+ def hash(self) -> str:
18
+ return extract_torrent_hash(self.magnet_uri)
19
+
20
+ @property
21
+ def release_name(self) -> str:
22
+ return self._release_name
23
+
24
+ @property
25
+ def magnet_uri(self) -> str:
26
+ return self._magnet_uri
27
+
28
+ @property
29
+ def parsed_release_name(self) -> dict:
30
+ return PTN.parse(self.release_name)
31
+
32
+ @property
33
+ def title(self) -> str:
34
+ return self.parsed_release_name.get("title")
35
+
36
+ @property
37
+ def year(self) -> int:
38
+ return self.parsed_release_name.get("year")
39
+
40
+ @property
41
+ def encoder(self) -> str:
42
+ return self.parsed_release_name.get("encoder")
43
+
44
+ @property
45
+ def resolution(self) -> str:
46
+ return self.parsed_release_name.get("resolution")
47
+
48
+ @property
49
+ def quality(self) -> str:
50
+ return self.parsed_release_name.get("quality")
51
+
52
+ @property
53
+ def is_cam(self) -> bool:
54
+ return self.parsed_release_name.get("quality") == "CAM"
55
+
56
+ @property
57
+ def imdb_id(self) -> str:
58
+ return self._imdb_id
59
+
60
+ @property
61
+ def source(self) -> str:
62
+ return self._source
@@ -0,0 +1,29 @@
1
+ from plexflow.core.torrents.providers.tpb.tpb import TPB
2
+ from plexflow.core.torrents.providers.yts.yts import YTS
3
+ from plexflow.core.torrents.providers.torrentquest.torrentquest import TorrentQuest
4
+ from plexflow.core.torrents.providers.extratorrent.extratorrent import ExtraTorrent
5
+ from plexflow.core.torrents.providers.ext.ext import Ext
6
+ from plexflow.core.torrents.providers.snowfl.snowfl import Snowfl
7
+ from plexflow.core.torrents.providers.therarbg.therarbg import TheRarbg
8
+ from typing import List
9
+ from plexflow.core.torrents.results.torrent import Torrent
10
+
11
+ class AutoTorrents:
12
+ @staticmethod
13
+ def movie(imdb_id: str = None, query: str = None, source: str = 'yts', **kwargs) -> List[Torrent]:
14
+ if source == 'tpb':
15
+ return TPB(**kwargs).search(query=imdb_id)
16
+ elif source == 'yts':
17
+ return YTS(**kwargs).search(query=imdb_id)
18
+ elif source == 'torrentquest':
19
+ return TorrentQuest(**kwargs).search(query=query)
20
+ elif source == 'extratorrent':
21
+ return ExtraTorrent(**kwargs).search(query=query)
22
+ elif source == 'therarbg':
23
+ return TheRarbg(**kwargs).search(query=imdb_id)
24
+ elif source == 'ext':
25
+ return Ext(**kwargs).search(query=query)
26
+ elif source == 'snowfl':
27
+ return Snowfl(**kwargs).search(query=query)
28
+ else:
29
+ raise ValueError(f"Invalid source: {source}")
File without changes
File without changes
@@ -0,0 +1,18 @@
1
+ from plexflow.utils.api.rest.antibot_restful import AntibotRestful
2
+ from plexflow.core.torrents.providers.ext.utils import ExtSearchResult
3
+ from plexflow.utils.torrent.extract.ext import extract_torrent_results
4
+ from typing import List
5
+ import os
6
+
7
+ class Ext(AntibotRestful):
8
+ def __init__(self, base_url: str = 'https://ext.to', use_xvfb: bool = os.getenv('USE_XVFB', 'false').lower() == 'true'):
9
+ super().__init__(base_url=base_url, use_xvfb=use_xvfb)
10
+
11
+ def search(self, query: str) -> List[ExtSearchResult]:
12
+ capture = self.get('/search', query_params={
13
+ 'c': 'movies',
14
+ 'q': query
15
+ })
16
+
17
+ data = extract_torrent_results(html=capture.html)
18
+ return list(map(lambda t: ExtSearchResult(**t), data))
@@ -0,0 +1,64 @@
1
+ from datetime import datetime
2
+ from plexflow.core.torrents.results.torrent import Torrent
3
+ from plexflow.utils.imdb.imdb_codes import IMDbCode
4
+ from plexflow.utils.torrent.hash import extract_torrent_hash
5
+
6
+ class ExtSearchResult(Torrent):
7
+ def __init__(self, **kwargs):
8
+ super().__init__()
9
+ self._name = kwargs.get('name')
10
+ self._date = kwargs.get('date')
11
+ self._type = kwargs.get('type')
12
+ self._size = kwargs.get('size_bytes')
13
+ self._seeds = kwargs.get('seeds')
14
+ self._peers = kwargs.get('peers')
15
+ self._link = kwargs.get('link')
16
+ self.src = 'ext'
17
+
18
+ @property
19
+ def source(self) -> str:
20
+ return self.src
21
+
22
+ @property
23
+ def magnet(self) -> str:
24
+ return None
25
+
26
+ @property
27
+ def date(self) -> datetime:
28
+ return self._date
29
+
30
+ @property
31
+ def seeds(self) -> int:
32
+ return self._seeds
33
+
34
+ @property
35
+ def peers(self) -> int:
36
+ return self._peers
37
+
38
+ @property
39
+ def size_bytes(self) -> int:
40
+ return self._size
41
+
42
+ @property
43
+ def imdb_code(self) -> IMDbCode:
44
+ return None
45
+
46
+ @property
47
+ def release_name(self) -> str:
48
+ return self._name
49
+
50
+ @property
51
+ def hash(self) -> str:
52
+ return None
53
+
54
+ @property
55
+ def uploader(self) -> str:
56
+ return None
57
+
58
+ @property
59
+ def url(self) -> str:
60
+ return self._link
61
+
62
+ @property
63
+ def category(self) -> str:
64
+ return self._type
@@ -0,0 +1,21 @@
1
+ from plexflow.utils.api.rest.antibot_restful import AntibotRestful
2
+ from plexflow.core.torrents.providers.extratorrent.utils import ExtraTorrentSearchResult
3
+ from plexflow.utils.torrent.extract.extratorrent import extract_torrent_results
4
+ from typing import List
5
+ import os
6
+
7
+ class ExtraTorrent(AntibotRestful):
8
+ def __init__(self, base_url: str = 'https://extratorrent.st', use_xvfb: bool = os.getenv('USE_XVFB', 'false').lower() == 'true'):
9
+ super().__init__(base_url=base_url, use_xvfb=use_xvfb)
10
+
11
+ def search(self, query: str) -> List[ExtraTorrentSearchResult]:
12
+ capture = self.get('/search', query_params={
13
+ 'search': query,
14
+ 'new': 1,
15
+ 'x': 0,
16
+ 'y': 0,
17
+ 's_cat': 1,
18
+ })
19
+
20
+ data = extract_torrent_results(html=capture.html)
21
+ return list(map(lambda t: ExtraTorrentSearchResult(**t), data))