From fd1809a1a71739faed5bf7f83fd4e48113d69759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D1=82=D0=B5=D0=BC=20=D0=93=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B0=D0=BA=D0=B1=D0=B5=D1=80=D0=BE=D0=B2?= Date: Wed, 17 Jul 2024 22:55:22 +0300 Subject: [PATCH 1/3] fix lostfilm --- monitorrent/plugins/trackers/lostfilm.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/monitorrent/plugins/trackers/lostfilm.py b/monitorrent/plugins/trackers/lostfilm.py index 036619aa..501b85f7 100644 --- a/monitorrent/plugins/trackers/lostfilm.py +++ b/monitorrent/plugins/trackers/lostfilm.py @@ -625,7 +625,19 @@ def parse_download(table): **self.tracker_settings.get_requests_kwargs()) soup = get_soup(download_page.text) - return list(map(parse_download, soup.find_all('div', class_='inner-box--item'))) + table = soup.find_all('div', class_='inner-box--item') + if len(table) == 0: + def a_href(tag): + return tag.name == 'a' and tag.has_attr('href') and tag.attrs['href'] != '/' + next_path = soup.find(a_href).attrs['href'] + url_parts = urlparse(download_page_url) + new_url_pattern = '{scheme}://{netloc}{path}' + download_page_url = new_url_pattern.format(scheme=url_parts.scheme, netloc=url_parts.netloc, path=next_path) + download_page = session.get(download_page_url, headers=self.headers, cookies=self.get_cookies(), + **self.tracker_settings.get_requests_kwargs()) + soup = get_soup(download_page.text) + table = soup.find_all('div', class_='inner-box--item') + return list(map(parse_download, table)) def replace_domain(self, url): url_parts = urlparse(url) From 543d772e237a367515026183e89c6513a9f5b76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D1=82=D0=B5=D0=BC=20=D0=93=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B0=D0=BA=D0=B1=D0=B5=D1=80=D0=BE=D0=B2?= Date: Mon, 24 Aug 2026 22:44:26 +0300 Subject: [PATCH 2/3] Revert "fix lostfilm" This reverts commit fd1809a1a71739faed5bf7f83fd4e48113d69759. --- monitorrent/plugins/trackers/lostfilm.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/monitorrent/plugins/trackers/lostfilm.py b/monitorrent/plugins/trackers/lostfilm.py index 501b85f7..036619aa 100644 --- a/monitorrent/plugins/trackers/lostfilm.py +++ b/monitorrent/plugins/trackers/lostfilm.py @@ -625,19 +625,7 @@ def parse_download(table): **self.tracker_settings.get_requests_kwargs()) soup = get_soup(download_page.text) - table = soup.find_all('div', class_='inner-box--item') - if len(table) == 0: - def a_href(tag): - return tag.name == 'a' and tag.has_attr('href') and tag.attrs['href'] != '/' - next_path = soup.find(a_href).attrs['href'] - url_parts = urlparse(download_page_url) - new_url_pattern = '{scheme}://{netloc}{path}' - download_page_url = new_url_pattern.format(scheme=url_parts.scheme, netloc=url_parts.netloc, path=next_path) - download_page = session.get(download_page_url, headers=self.headers, cookies=self.get_cookies(), - **self.tracker_settings.get_requests_kwargs()) - soup = get_soup(download_page.text) - table = soup.find_all('div', class_='inner-box--item') - return list(map(parse_download, table)) + return list(map(parse_download, soup.find_all('div', class_='inner-box--item'))) def replace_domain(self, url): url_parts = urlparse(url) From 673c022ef3b15d2d01505bd4775ef377d944b946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D1=82=D0=B5=D0=BC=20=D0=93=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B0=D0=BA=D0=B1=D0=B5=D1=80=D0=BE=D0=B2?= Date: Mon, 24 Aug 2026 22:57:45 +0300 Subject: [PATCH 3/3] fix: lostfilm.tv cat > 999, quoted onclick args and mirror redirects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed and hand-verified subset of #424 (zalessky, "Поддержка зеркал + адаптация к новой разметке сайта"). The original PR rewrote the whole file (+264/-862): dropped DB migrations, inlined LostFilmTVTracker into the plugin, removed timeout/proxy kwargs and the Cloudflare cookie refresh flow. This keeps that architecture and carries over only the actual site-adaptation fixes: - _play_episode_re allowed cat only up to 3 digits and required a fixed single-quoted, unspaced onclick body; shows with cat >= 1000 failed to match and crashed with AttributeError on .group('episode'). Now accepts single/double quotes, extra whitespace and 4-digit cat. - _follow_show_re: same quoting relaxation for FollowSerial(...). - _parse_series: episodes used to be found via a fixed table.movie-parts-list > tr > td.zeta > div chain that no longer matches current markup; now matches any onclick=PlayEpisode node inside a season block. Also guards missing series-block/h2, skips the season-as-a-whole pseudo-episode (999) and duplicate episodes. - get_download_info: meta-refresh parsing picked the first tag (usually charset, no content attr -> KeyError) and assumed an absolute redirect URL. Mirrors return a relative path, which made the follow-up request fail with requests.exceptions.MissingSchema. Now finds the refresh meta specifically and resolves the URL with urljoin against the tracker domain. - _prepare_request: (topic.season, topic.episode) > latest_episode raised TypeError on Python 3 when topic.episode was None (season set without an episode) or when a season number was a tuple (fractional season). None is now treated as 0 and tuple season numbers are skipped instead of crashing. Not carried over from #424: removal of DB migrations, tracker/plugin merge, requests.Session() replacing tracker_settings timeout/proxy kwargs, dropped Cloudflare cookie refresh, and behavior changes to login()/verify()/check_download() error handling. --- monitorrent/plugins/trackers/lostfilm.py | 74 +++++++++++++----------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/monitorrent/plugins/trackers/lostfilm.py b/monitorrent/plugins/trackers/lostfilm.py index 036619aa..8ed730e2 100644 --- a/monitorrent/plugins/trackers/lostfilm.py +++ b/monitorrent/plugins/trackers/lostfilm.py @@ -2,7 +2,7 @@ import json import sys import re -from urllib.parse import urlparse +from urllib.parse import urlparse, urljoin import requests import cloudscraper @@ -467,9 +467,9 @@ def __init__(self, quality, download_url): class LostFilmTVTracker(object): tracker_settings: TrackerSettings = None _season_title_info = re.compile(u'^(?P\d+)(\.(?P\d+))?\s+сезон' + - u'(\s+((\d+)-)?(?P\d+)\s+серия)?$') - _follow_show_re = re.compile(r'^FollowSerial\((?P\d+)(\s*,\s*(true|false))?\)$', re.UNICODE) - _play_episode_re = re.compile(r"^PlayEpisode\('(?P\d{1,3})\s*(?P\d{3})\s*(?P\d{3})'\)$", + u'(\s+((\d+)-)?(?P\d+)\s+серия)?') + _follow_show_re = re.compile(r'^FollowSerial\(\s*[\'"]?(?P\d+)', re.UNICODE) + _play_episode_re = re.compile(r"^PlayEpisode\(\s*['\"](?P\d{1,4})\s*(?P\d{3})\s*(?P\d{3})['\"]", re.UNICODE) playwright_timeout = 30000 @@ -557,34 +557,33 @@ def parse_url(self, url, parse_series=False): def _parse_series(self, soup): """ - :rtype : dict + :rtype : collections.Iterable[LostFilmSeason] """ series_block = soup.find('div', class_='series-block') - serie_blocks = series_block.find_all('div', class_='serie-block') - result = dict() - for season_node in serie_blocks: - season_title = season_node.find('h2').text - series_table = season_node.find('table', class_='movie-parts-list') - series = series_table.find_all('tr', class_=None) - - # when next season is planned it already exist on seasons page - # but without any episodes yet and without download button - if not any(series): + if series_block is None: + return + for season_node in series_block.find_all('div', class_='serie-block'): + season_title_node = season_node.find('h2') + if season_title_node is None: continue - season_number = self._parse_season_info(season_title) + season_number = self._parse_season_info(season_title_node.text.strip()) season = LostFilmSeason(season_number) - for serie in series: - zeta = serie.find('td', class_='zeta') - play_episode = zeta.find('div').attrs['onclick'] - - play_episode_match = self._play_episode_re.match(play_episode) + for play_node in season_node.find_all('div', onclick=self._play_episode_re): + play_episode_match = self._play_episode_re.match(play_node.attrs['onclick']) + if play_episode_match is None: + continue episode_number = int(play_episode_match.group('episode')) + if episode_number == 999 or episode_number in season.episodes_dict: + continue - episode = LostFilmEpisode(season_number, episode_number) - season.add_episode(episode) - yield season + season.add_episode(LostFilmEpisode(season_number, episode_number)) + + # when next season is planned it already exist on seasons page + # but without any episodes yet and without download button + if len(season) > 0: + yield season def _parse_season_info(self, info): if info == u'Дополнительные материалы': @@ -618,8 +617,14 @@ def parse_download(table): **self.tracker_settings.get_requests_kwargs()) soup = get_soup(download_redirect.text) - meta_content = soup.find('meta').attrs['content'] - download_page_url = meta_content.split(';')[1].strip()[4:] + meta = soup.find('meta', attrs={'http-equiv': re.compile('refresh', re.IGNORECASE)}) + if meta is None or ';' not in meta.attrs.get('content', ''): + return None + redirect_url = meta.attrs['content'].split(';', 1)[1].strip() + if redirect_url.lower().startswith('url='): + redirect_url = redirect_url[4:] + download_page_url = urljoin('https://{domain}/'.format(domain=self.domain), + redirect_url.strip('\'"')) download_page = session.get(download_page_url, headers=self.headers, cookies=self.get_cookies(), **self.tracker_settings.get_requests_kwargs()) @@ -906,20 +911,23 @@ def get_topic_info(self, topic): def _prepare_request(self, topic): show = self.tracker.parse_url(topic.url, True) - if isinstance(show, Response): + if isinstance(show, Response) or show is None: return show - latest_episode = (topic.season, topic.episode) - if latest_episode == (None, None): - episodes = [show.last_season.last_episode] + if topic.season is None: + last_season = show.last_season + last_episode = last_season.last_episode if last_season is not None else None + episodes = [last_episode] if last_episode is not None else [] else: + latest_episode = (topic.season, topic.episode or 0) episodes = [episode for season in show for episode in season - if not SpecialSeasons.is_special(episode.season) and - (episode.season, episode.number) > latest_episode] + if isinstance(season.number, int) and + (season.number, episode.number) > latest_episode] resut = [] for episode in episodes: - download_infos = self.tracker.get_download_info(topic.url, topic.cat, episode.season, episode.number) + download_infos = self.tracker.get_download_info(topic.url, show.cat or topic.cat, + episode.season, episode.number) topic_quality = LostFilmQuality.parse(topic.quality) download_info = None