23
Jan
Sickrage Sickbeard Tntvillage public provider
A simple torrent provider for SickRage edited from TntVillage provider at sickrage.github.io .
Gets torrent from TNTVillage public address “%releaselist.php”.
# coding=utf-8 # Author: m0m4x # URL: https://sickrage.github.io # # This file is part of SickRage. # # SickRage is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # SickRage is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with SickRage. If not, see <http://www.gnu.org/licenses/>. from __future__ import print_function, unicode_literals import re import traceback from pprint import pprint from sickbeard import db, logger, tvcache from sickbeard.bs4_parser import BS4Parser from sickbeard.common import Quality from sickbeard.name_parser.parser import InvalidNameException, InvalidShowException, NameParser from sickrage.helper.common import convert_size, try_int from sickrage.helper.exceptions import AuthException from sickrage.providers.torrent.TorrentProvider import TorrentProvider category_excluded = {'Sport': 22, 'Teatro': 23, 'Video Musicali': 21, 'Film': 4, 'Musica': 2, 'Students Releases': 13, 'E Books': 3, 'Linux': 6, 'Macintosh': 9, 'Windows Software': 10, 'Pc Game': 11, 'Playstation 2': 12, 'Wrestling': 24, 'Varie': 25, 'Xbox': 26, 'Immagini sfondi': 27, 'Altri Giochi': 28, 'Fumetteria': 30, 'Trash': 31, 'PlayStation 1': 32, 'PSP Portable': 33, 'A Book': 34, 'Podcast': 35, 'Edicola': 36, 'Mobile': 37} class TNTVillagePublicProvider(TorrentProvider): # pylint: disable=too-many-instance-attributes def __init__(self): TorrentProvider.__init__(self, "TNTVillagePublic") self._uid = None self._hash = None self.username = None self.password = None self.cat = None self.engrelease = None self.page = 10 self.subtitle = None self.minseed = None self.minleech = None self.hdtext = [' - Versione 720p', ' Versione 720p', ' V 720p', ' V 720', ' V HEVC', ' V HEVC', ' V 1080', ' Versione 1080p', ' 720p HEVC', ' Ver 720', ' 720p HEVC', ' 720p'] self.category_dict = {'Serie TV': 29, 'Cartoni': 8, 'Anime': 7, 'Programmi e Film TV': 1, 'Documentari': 14, 'All': 0} self.urls = {'base_url': 'http://forum.tntvillage.scambioetico.org', 'login': 'http://forum.tntvillage.scambioetico.org/index.php?act=Login&CODE=01', 'detail': 'http://forum.tntvillage.scambioetico.org/index.php?showtopic=%s', 'search': 'http://forum.tntvillage.scambioetico.org/?act=allreleases&%s', 'search_page': 'http://tntvillage.scambioetico.org/src/releaselist.php', 'download': 'http://forum.tntvillage.scambioetico.org/index.php?act=Attach&type=post&id=%s'} self.url = self.urls['base_url'] self.sub_string = ['sub', 'softsub'] self.proper_strings = ['PROPER', 'REPACK'] self.categories = "cat=29" self.cache = tvcache.TVCache(self, min_time=30) # only poll TNTVillage every 30 minutes max def _check_auth(self): if not self.username or not self.password: raise AuthException("Your authentication credentials for " + self.name + " are missing, check your config.") return True def login(self): if len(self.session.cookies) >= 3: if self.session.cookies.get('pass_hash', '') not in ('0', 0) and self.session.cookies.get('member_id') not in ('0', 0): return True login_params = {'UserName': self.username, 'PassWord': self.password, 'CookieDate': 1, 'submit': 'Connettiti al Forum'} response = self.get_url(self.urls['login'], post_data=login_params, returns='text') if not response: logger.log("Unable to connect to provider", logger.WARNING) return False if re.search('Sono stati riscontrati i seguenti errori', response) or re.search('<title>Connettiti</title>', response): logger.log("Invalid username or password. Check your settings", logger.WARNING) return False return True @staticmethod def _reverseQuality(quality): quality_string = '' if quality == Quality.SDTV: quality_string = ' HDTV x264' if quality == Quality.SDDVD: quality_string = ' DVDRIP' elif quality == Quality.HDTV: quality_string = ' 720p HDTV x264' elif quality == Quality.FULLHDTV: quality_string = ' 1080p HDTV x264' elif quality == Quality.RAWHDTV: quality_string = ' 1080i HDTV mpeg2' elif quality == Quality.HDWEBDL: quality_string = ' 720p WEB-DL h264' elif quality == Quality.FULLHDWEBDL: quality_string = ' 1080p WEB-DL h264' elif quality == Quality.HDBLURAY: quality_string = ' 720p Bluray x264' elif quality == Quality.FULLHDBLURAY: quality_string = ' 1080p Bluray x264' return quality_string @staticmethod def _episodeQuality(title): # pylint: disable=too-many-return-statements, too-many-branches """ Return The quality from the title """ def checkName(options, func): return func([re.search(option, title, re.I) for option in options]) dvdOptions = checkName(["dvd", "dvdrip", "dvdmux", "DVD9", "DVD5"], any) bluRayOptions = checkName(["BD", "BDmux", "BDrip", "BRrip", "Bluray"], any) sdOptions = checkName(["h264", "divx", "XviD", "tv", "TVrip", "SATRip", "DTTrip", "Mpeg2"], any) hdOptions = checkName(["720p"], any) fullHD = checkName(["1080p", "fullHD"], any) webdl = checkName(["webdl", "webmux", "webrip", "dl-webmux", "web-dlmux", "webdl-mux", "web-dl", "webdlmux", "dlmux", "mux"], any) if sdOptions and not dvdOptions and not fullHD and not hdOptions: return Quality.SDTV elif dvdOptions: return Quality.SDDVD elif hdOptions and not bluRayOptions and not fullHD and not webdl: return Quality.HDTV elif not hdOptions and not bluRayOptions and fullHD and not webdl: return Quality.FULLHDTV elif hdOptions and not bluRayOptions and not fullHD and webdl: return Quality.HDWEBDL elif not hdOptions and not bluRayOptions and fullHD and webdl: return Quality.FULLHDWEBDL elif bluRayOptions and hdOptions and not fullHD: return Quality.HDBLURAY elif bluRayOptions and fullHD and not hdOptions: return Quality.FULLHDBLURAY else: return Quality.UNKNOWN def _is_italian(self, title): name = title if not name or name == 'None': return False subFound = italian = False for sub in self.sub_string: if re.search(sub, name, re.I): subFound = True else: continue if re.search("[ -_.|]ita[ -_.|]", name.lower().split(sub)[0], re.I): logger.log("Found Italian release: " + name, logger.DEBUG) italian = True break if not subFound and re.search("ita", name, re.I): logger.log("Found Italian release: " + name, logger.DEBUG) italian = True return italian @staticmethod def _is_english(title): name = title if not name or name == 'None': return False english = False if re.search("eng", name, re.I): logger.log("Found English release: " + name, logger.DEBUG) english = True return english @staticmethod def _is_season_pack(name): try: parse_result = NameParser(tryIndexers=True).parse(name) except (InvalidNameException, InvalidShowException) as error: logger.log("{0}".format(error), logger.DEBUG) return False main_db_con = db.DBConnection() sql_selection = "select count(*) as count from tv_episodes where showid = ? and season = ?" episodes = main_db_con.select(sql_selection, [parse_result.show.indexerid, parse_result.season_number]) if int(episodes[0][b'count']) == len(parse_result.episode_numbers): return True def search(self, search_params, age=0, ep_obj=None): # pylint: disable=too-many-locals, too-many-branches, too-many-statements results = [] if not self.login(): return results self.categories = "cat=" + str(self.cat) for mode in search_params: items = [] logger.log("Search Mode: {0}".format(mode), logger.DEBUG) for search_string in search_params[mode]: if mode == 'RSS': self.page = 2 last_page = 0 y = int(self.page) if search_string == '': continue search_string = str(search_string).replace('.', ' ') for x in range(1, y): z = x * 20 if last_page: break req_params = { 'cat':self.categories, 'page':x, 'srcrel':search_string } data = self.get_url(self.urls['search_page'], post_data=req_params, returns='text') if not data: logger.log("No data returned from provider", logger.DEBUG) continue try: with BS4Parser(data, 'html5lib') as html: torrent_table = html.find('table') torrent_rows = torrent_table('tr') if torrent_table else [] # Continue only if one Release is found if len(torrent_rows) < 3: logger.log("Data returned from provider does not contain any torrents", logger.DEBUG) last_page = 1 continue if len(torrent_rows) < 42: last_page = 1 for result in torrent_table('tr')[:]: try: link = result('td')[6].find('a')['href'] title = result('td')[6].get_text() download_url = result('td')[0].find('a')['href'] leechers = int(result('td')[3].text) seeders = int(result('td')[4].text) size = -1 except (AttributeError, TypeError): continue #logger.log("Found " + title + "! " , logger.DEBUG) filename_qt = self._reverseQuality(self._episodeQuality(title)) for text in self.hdtext: title1 = title title = title.replace(text, filename_qt) if title != title1: break if Quality.nameQuality(title) == Quality.UNKNOWN: title += filename_qt if not self._is_italian(title) and not self.subtitle: logger.log("Torrent is subtitled, skipping: {0} ".format(title), logger.DEBUG) continue if self.engrelease and not self._is_english(title): logger.log("Torrent isnt english audio/subtitled , skipping: {0} ".format(title), logger.DEBUG) continue search_show = re.split(r'([Ss][\d{1,2}]+)', search_string)[0] show_title = search_show rindex = re.search(r'([Ss][\d{1,2}]+)', title) if rindex: show_title = title[:rindex.start()] ep_params = title[rindex.start():] if show_title.lower() != search_show.lower() and search_show.lower() in show_title.lower(): new_title = search_show + ep_params title = new_title if not all([title, download_url]): continue if self._is_season_pack(title): title = re.sub(r'([Ee][\d{1,2}\-?]+)', '', title) # Filter unseeded torrent if seeders < self.minseed or leechers < self.minleech: if mode != 'RSS': logger.log("Discarding torrent because it doesn't meet the minimum seeders or leechers: {0} (S:{1} L:{2})".format (title, seeders, leechers), logger.DEBUG) continue item = {'title': title, 'link': download_url, 'size': size, 'seeders': seeders, 'leechers': leechers, 'hash': ''} if mode != 'RSS': logger.log("Found result: {0} with {1} seeders and {2} leechers".format(title, seeders, leechers), logger.DEBUG) items.append(item) except Exception: logger.log("Failed parsing provider. Traceback: {0}".format(traceback.format_exc()), logger.ERROR) # For each search mode sort all the items by seeders if available if available items.sort(key=lambda d: try_int(d.get('seeders', 0)), reverse=True) results += items return results provider = TNTVillagePublicProvider()