1- # VERSION: 4.4
1+ # VERSION: 4.5
22# AUTHORS: Diego de las Heras ([email protected] ) 33# CONTRIBUTORS: ukharley
44# hannsen (github.com/hannsen)
1212from http .cookiejar import CookieJar
1313from multiprocessing .dummy import Pool
1414from threading import Lock
15+ from typing import Any , Dict , List , Union
1516from urllib .parse import unquote , urlencode
1617
1718import helpers
@@ -53,7 +54,7 @@ def enable_proxy(self, enable: bool) -> None:
5354# load configuration from file
5455CONFIG_FILE = 'jackett.json'
5556CONFIG_PATH = os .path .join (os .path .dirname (os .path .realpath (__file__ )), CONFIG_FILE )
56- CONFIG_DATA = {
57+ CONFIG_DATA : Dict [ str , Any ] = {
5758 'api_key' : 'YOUR_API_KEY_HERE' , # jackett api
5859 'url' : 'http://127.0.0.1:9117' , # jackett url
5960 'tracker_first' : False , # (False/True) add tracker name to beginning of search result
@@ -62,7 +63,7 @@ def enable_proxy(self, enable: bool) -> None:
6263PRINTER_THREAD_LOCK = Lock ()
6364
6465
65- def load_configuration ():
66+ def load_configuration () -> None :
6667 global CONFIG_DATA
6768 try :
6869 # try to load user data from file
@@ -85,7 +86,7 @@ def load_configuration():
8586 save_configuration ()
8687
8788
88- def save_configuration ():
89+ def save_configuration () -> None :
8990 with open (CONFIG_PATH , 'w' ) as f :
9091 f .write (json .dumps (CONFIG_DATA , indent = 4 , sort_keys = True ))
9192
@@ -94,7 +95,7 @@ def save_configuration():
9495###############################################################################
9596
9697
97- class jackett ( object ) :
98+ class jackett :
9899 name = 'Jackett'
99100 url = CONFIG_DATA ['url' ] if CONFIG_DATA ['url' ][- 1 ] != '/' else CONFIG_DATA ['url' ][:- 1 ]
100101 api_key = CONFIG_DATA ['api_key' ]
@@ -110,7 +111,7 @@ class jackett(object):
110111 'tv' : ['5000' ],
111112 }
112113
113- def download_torrent (self , download_url ) :
114+ def download_torrent (self , download_url : str ) -> None :
114115 # fix for some indexers with magnet link inside .torrent file
115116 if download_url .startswith ('magnet:?' ):
116117 print (download_url + " " + download_url )
@@ -122,7 +123,7 @@ def download_torrent(self, download_url):
122123 else :
123124 print (helpers .download_file (download_url ))
124125
125- def search (self , what , cat = 'all' ):
126+ def search (self , what : str , cat : str = 'all' ) -> None :
126127 what = unquote (what )
127128 category = self .supported_categories [cat .lower ()]
128129
@@ -147,52 +148,59 @@ def search(self, what, cat='all'):
147148 else :
148149 self .search_jackett_indexer (what , category , 'all' )
149150
150- def get_jackett_indexers (self , what ) :
151- params = [
151+ def get_jackett_indexers (self , what : str ) -> List [ str ] :
152+ params = urlencode ( [
152153 ('apikey' , self .api_key ),
153154 ('t' , 'indexers' ),
154155 ('configured' , 'true' )
155- ]
156- params = urlencode (params )
156+ ])
157157 jacket_url = self .url + "/api/v2.0/indexers/all/results/torznab/api?%s" % params
158158 response = self .get_response (jacket_url )
159159 if response is None :
160160 self .handle_error ("connection error getting indexer list" , what )
161- return
161+ return []
162162 # process results
163163 response_xml = xml .etree .ElementTree .fromstring (response )
164164 indexers = []
165165 for indexer in response_xml .findall ('indexer' ):
166166 indexers .append (indexer .attrib ['id' ])
167167 return indexers
168168
169- def search_jackett_indexer (self , what , category , indexer_id ):
169+ def search_jackett_indexer (self , what : str , category : Union [List [str ], None ], indexer_id : str ) -> None :
170+ def toStr (s : Union [str , None ]) -> str :
171+ return s if s is not None else ''
172+
173+ def getTextProp (e : Union [xml .etree .ElementTree .Element [str ], None ]) -> str :
174+ return toStr (e .text if e is not None else '' )
175+
170176 # prepare jackett url
171- params = [
177+ params_tmp = [
172178 ('apikey' , self .api_key ),
173179 ('q' , what )
174180 ]
175181 if category is not None :
176- params .append (('cat' , ',' .join (category )))
177- params = urlencode (params )
182+ params_tmp .append (('cat' , ',' .join (category )))
183+ params = urlencode (params_tmp )
178184 jacket_url = self .url + "/api/v2.0/indexers/" + indexer_id + "/results/torznab/api?%s" % params # noqa
179185 response = self .get_response (jacket_url )
180186 if response is None :
181187 self .handle_error ("connection error for indexer: " + indexer_id , what )
182188 return
183189 # process search results
184190 response_xml = xml .etree .ElementTree .fromstring (response )
185- for result in response_xml .find ('channel' ).findall ('item' ):
186- res = {}
191+ channel = response_xml .find ('channel' )
192+ if channel is None :
193+ return
194+ for result in channel .findall ('item' ):
195+ res : Dict [str , Any ] = {}
187196
188- title = result .find ('title' )
189- if title is not None :
190- title = title .text
197+ title_tmp = result .find ('title' )
198+ if title_tmp is not None :
199+ title = title_tmp .text
191200 else :
192201 continue
193202
194- tracker = result .find ('jackettindexer' )
195- tracker = '' if tracker is None else tracker .text
203+ tracker = getTextProp (result .find ('jackettindexer' ))
196204 if CONFIG_DATA ['tracker_first' ]:
197205 res ['name' ] = '[%s] %s' % (tracker , title )
198206 else :
@@ -209,7 +217,7 @@ def search_jackett_indexer(self, what, category, indexer_id):
209217 continue
210218
211219 res ['size' ] = result .find ('size' )
212- res ['size' ] = - 1 if res ['size' ] is None else (res ['size' ].text + ' B' )
220+ res ['size' ] = - 1 if res ['size' ] is None else (toStr ( res ['size' ].text ) + ' B' )
213221
214222 res ['seeds' ] = result .find (self .generate_xpath ('seeders' ))
215223 res ['seeds' ] = - 1 if res ['seeds' ] is None else int (res ['seeds' ].attrib ['value' ])
@@ -231,17 +239,17 @@ def search_jackett_indexer(self, what, category, indexer_id):
231239 res ['engine_url' ] = self .url
232240
233241 try :
234- date = datetime .strptime (result .find ('pubDate' ). text , '%a, %d %b %Y %H:%M:%S %z' )
242+ date = datetime .strptime (getTextProp ( result .find ('pubDate' )) , '%a, %d %b %Y %H:%M:%S %z' )
235243 res ['pub_date' ] = int (date .timestamp ())
236244 except Exception :
237245 res ['pub_date' ] = - 1
238246
239247 self .pretty_printer_thread_safe (res )
240248
241- def generate_xpath (self , tag ) :
249+ def generate_xpath (self , tag : str ) -> str :
242250 return './{http://torznab.com/schemas/2015/feed}attr[@name="%s"]' % tag
243251
244- def get_response (self , query ) :
252+ def get_response (self , query : str ) -> Union [ str , None ] :
245253 response = None
246254 try :
247255 # we can't use helpers.retrieve_url because of redirects
@@ -256,24 +264,26 @@ def get_response(self, query):
256264 pass
257265 return response
258266
259- def handle_error (self , error_msg , what ) :
267+ def handle_error (self , error_msg : str , what : str ) -> None :
260268 # we need to print the search text to be displayed in qBittorrent when
261269 # 'Torrent names only' is enabled
262270 self .pretty_printer_thread_safe ({
263- 'seeds' : - 1 ,
271+ 'link' : self .url ,
272+ 'name' : "Jackett: %s! Right-click this row and select 'Open description page' to open help. Configuration file: '%s' Search: '%s'" % (error_msg , CONFIG_PATH , what ), # noqa
264273 'size' : - 1 ,
274+ 'seeds' : - 1 ,
265275 'leech' : - 1 ,
266276 'engine_url' : self .url ,
267- 'link' : self .url ,
268277 'desc_link' : 'https://github.com/qbittorrent/search-plugins/wiki/How-to-configure-Jackett-plugin' , # noqa
269- 'name ' : "Jackett: %s! Right-click this row and select 'Open description page' to open help. Configuration file: '%s' Search: '%s'" % ( error_msg , CONFIG_PATH , what ) # noqa
278+ 'pub_date ' : - 1
270279 })
271280
272- def pretty_printer_thread_safe (self , dictionary ):
281+ def pretty_printer_thread_safe (self , dictionary : Dict [str , Any ]) -> None :
282+ escaped_dict = self .escape_pipe (dictionary )
273283 with PRINTER_THREAD_LOCK :
274- prettyPrinter (self . escape_pipe ( dictionary ))
284+ prettyPrinter (escaped_dict ) # type: ignore[arg-type] # refactor later
275285
276- def escape_pipe (self , dictionary ) :
286+ def escape_pipe (self , dictionary : Dict [ str , Any ]) -> Dict [ str , Any ] :
277287 # Safety measure until it's fixed in prettyPrinter
278288 for key in dictionary .keys ():
279289 if isinstance (dictionary [key ], str ):
0 commit comments