diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c799401b..45dc1eef 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: build-docker-image +name: Build(Docker) on: release: diff --git a/.gitignore b/.gitignore index 3b911810..6c06300d 100644 --- a/.gitignore +++ b/.gitignore @@ -163,13 +163,13 @@ cython_debug/ # Custom /auto_bangumi/conf/const_dev.py -/config/bangumi.json +/config /auto_bangumi/tester.py /resource/names.txt -/auto_bangumi/parser/analyser/tmdb.py +/auto_bangumi/parser/analyser/tmdb_parser.py /auto_bangumi/run_debug.sh /auto_bangumi/debug_run.sh diff --git a/auto_bangumi/conf/const.py b/auto_bangumi/conf/const.py index 6f97d08b..3cdecd20 100644 --- a/auto_bangumi/conf/const.py +++ b/auto_bangumi/conf/const.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- DEFAULT_SETTINGS = { - "version": "2.5.10", + "version": "2.5.11", "data_version": 4.0, "host_ip": "localhost:8080", "sleep_time": 7200, @@ -12,7 +12,7 @@ DEFAULT_SETTINGS = { "method": "pn", "enable_group_tag": False, "info_path": "/config/bangumi.json", - "not_contain": "720", + "not_contain": r"720|\d+-\d+", "rule_name_re": r"\:|\/|\.", "connect_retry_interval": 5, "debug_mode": False, diff --git a/auto_bangumi/core/download_client.py b/auto_bangumi/core/download_client.py index e64d94d9..b1ae900b 100644 --- a/auto_bangumi/core/download_client.py +++ b/auto_bangumi/core/download_client.py @@ -1,6 +1,7 @@ import re import logging import os +from attrdict import AttrDict from downloader import getClient from downloader.exceptions import ConflictError @@ -54,17 +55,25 @@ class DownloadClient: logger.info(f"Add {official_name} Season {season}") def rss_feed(self): - try: - self.client.rss_remove_item(item_path="Mikan_RSS") - except ConflictError: - logger.info("No feed exists, start adding feed.") - try: - self.client.rss_add_feed(url=settings.rss_link, item_path="Mikan_RSS") - logger.info("Add RSS Feed successfully.") - except ConnectionError: - logger.warning("Error with adding RSS Feed.") - except ConflictError: - logger.info("RSS Already exists.") + if not settings.refresh_rss: + if self.client.get_rss_info() == settings.rss_link: + logger.info("RSS Already exists.") + else: + logger.info("No feed exists, start adding feed.") + self.client.rss_add_feed(url=settings.rss_link, item_path="Mikan_RSS") + logger.info("Add RSS Feed successfully.") + else: + try: + self.client.rss_remove_item(item_path="Mikan_RSS") + except ConflictError: + logger.info("No feed exists, start adding feed.") + try: + self.client.rss_add_feed(url=settings.rss_link, item_path="Mikan_RSS") + logger.info("Add RSS Feed successfully.") + except ConnectionError: + logger.warning("Error with adding RSS Feed.") + except ConflictError: + logger.info("RSS Already exists.") def add_collection_feed(self, rss_link, item_path): self.client.rss_add_feed(url=rss_link, item_path=item_path) @@ -79,7 +88,7 @@ class DownloadClient: # logger.info("to rule.") logger.debug("Finished.") - def get_torrent_info(self): + def get_torrent_info(self) -> [AttrDict]: return self.client.torrents_info( status_filter="completed", category="Bangumi" ) @@ -113,6 +122,7 @@ class DownloadClient: self.client.rss_add_feed(url=rss_link, item_path=item_path) logger.info("Add RSS Feed successfully.") + if __name__ == "__main__": put = DownloadClient() diff --git a/auto_bangumi/core/eps_complete.py b/auto_bangumi/core/eps_complete.py index 4e8ddadf..57ed119b 100644 --- a/auto_bangumi/core/eps_complete.py +++ b/auto_bangumi/core/eps_complete.py @@ -31,7 +31,7 @@ class FullSeasonGet: downloads = [] for torrent in torrents: download_info = { - "url": torrent, + "url": torrent.torrent_link, "save_path": os.path.join( settings.download_path, data["official_title"], @@ -77,4 +77,4 @@ if __name__ == "__main__": "added": True, "eps_collect": True } - print(a.init_eps_complete_search_str(data)) \ No newline at end of file + print(a.init_eps_complete_search_str(data)) diff --git a/auto_bangumi/core/renamer.py b/auto_bangumi/core/renamer.py index a6a67748..873d75b4 100644 --- a/auto_bangumi/core/renamer.py +++ b/auto_bangumi/core/renamer.py @@ -17,7 +17,8 @@ class Renamer: self.client = download_client self._renamer = TitleParser() - def print_result(self, torrent_count, rename_count): + @staticmethod + def print_result(torrent_count, rename_count): if rename_count != 0: logger.info(f"Finished checking {torrent_count} files' name, renamed {rename_count} files.") logger.debug(f"Checked {torrent_count} files") @@ -27,7 +28,8 @@ class Renamer: torrent_count = len(recent_info) return recent_info, torrent_count - def split_path(self, path: str): + @staticmethod + def split_path(path: str): suffix = os.path.splitext(path)[-1] path = path.replace(settings.download_path, "") path_parts = PurePath(path).parts \ diff --git a/auto_bangumi/core/rss_analyser.py b/auto_bangumi/core/rss_analyser.py index aa9096fa..2720564a 100644 --- a/auto_bangumi/core/rss_analyser.py +++ b/auto_bangumi/core/rss_analyser.py @@ -16,10 +16,11 @@ class RSSAnalyser: self._title_analyser = TitleParser() self._request = RequestContent() - def rss_to_datas(self, bangumi_info: list): - rss_titles = self._request.get_titles(settings.rss_link) + def rss_to_datas(self, bangumi_info: list) -> list: + rss_torrents = self._request.get_torrents(settings.rss_link) self._request.close_session() - for raw_title in rss_titles: + for torrent in rss_torrents: + raw_title = torrent.name extra_add = True if bangumi_info is not []: for d in bangumi_info: @@ -27,18 +28,16 @@ class RSSAnalyser: logger.debug(f"Had added {d['title_raw']} before") extra_add = False break - if re.search(settings.not_contain, raw_title) is not None: - extra_add = False if extra_add: data = self._title_analyser.return_dict(raw_title) if data is not None and data["official_title"] not in bangumi_info: bangumi_info.append(data) return bangumi_info - def rss_to_data(self, url): - rss_title = self._request.get_title(url) + def rss_to_data(self, url) -> dict: + rss_torrents = self._request.get_torrents(url) self._request.close_session() - data = self._title_analyser.return_dict(rss_title) + data = self._title_analyser.return_dict(rss_torrents[0].name) return data def run(self, bangumi_info: list, download_client: DownloadClient): diff --git a/auto_bangumi/downloader/qb_downloader.py b/auto_bangumi/downloader/qb_downloader.py index 5e875dc3..a20a51dd 100644 --- a/auto_bangumi/downloader/qb_downloader.py +++ b/auto_bangumi/downloader/qb_downloader.py @@ -59,6 +59,13 @@ class QbDownloader: def torrents_rename_file(self, torrent_hash, old_path, new_path): self._client.torrents_rename_file(torrent_hash=torrent_hash, old_path=old_path, new_path=new_path) + def get_rss_info(self): + item = self._client.rss_items().get("Mikan_RSS") + if item is not None: + return item.url + else: + return None + def rss_add_feed(self, url, item_path): try: self._client.rss_add_feed(url, item_path) diff --git a/auto_bangumi/network/__init__.py b/auto_bangumi/network/__init__.py index 17ba953a..90f6a684 100644 --- a/auto_bangumi/network/__init__.py +++ b/auto_bangumi/network/__init__.py @@ -1,4 +1,14 @@ +import re +from dataclasses import dataclass + from network.request import RequestURL +from conf import settings + + +@dataclass +class TorrentInfo: + name: str + torrent_link: str class RequestContent: @@ -6,23 +16,24 @@ class RequestContent: self._req = RequestURL() # Mikanani RSS - def get_titles(self, url): + def get_torrents(self, url: str) -> [TorrentInfo]: soup = self._req.get_content(url) - items = soup.find_all("item") - return [item.title.string for item in items] + torrent_titles = [item.title.string for item in soup.find_all("item")] + keep_index = [] + for idx, title in enumerate(torrent_titles): + if re.search(settings.not_contain, title) is None: + keep_index.append(idx) + torrent_urls = [item.get("url") for item in soup.find_all("enclosure")] + return [TorrentInfo(torrent_titles[i], torrent_urls[i]) for i in keep_index] - def get_title(self, url): + def get_torrent(self, url) -> TorrentInfo: soup = self._req.get_content(url) item = soup.find("item") - return item.title.string - - def get_torrents(self, url): - soup = self._req.get_content(url) - enclosure = soup.find_all("enclosure") - return [t["url"] for t in enclosure] + enclosure = item.find("enclosure") + return TorrentInfo(item.title.string, enclosure["url"]) # API JSON - def get_json(self, url): + def get_json(self, url) -> dict: return self._req.get_content(url, content="json") def close_session(self): @@ -31,6 +42,8 @@ class RequestContent: if __name__ == "__main__": r = RequestContent() - url = "https://mikanani.me/RSS/Bangumi?bangumiId=2685&subgroupid=552" - title = r.get_title(url) - print(title) \ No newline at end of file + rss_url = "https://mikanani.me/RSS/Bangumi?bangumiId=2739&subgroupid=203" + titles = r.get_torrents(rss_url) + print(settings.not_contain) + for title in titles: + print(title.name, title.torrent_link) diff --git a/auto_bangumi/parser/analyser/__init__.py b/auto_bangumi/parser/analyser/__init__.py index 9d752a48..d187fa27 100644 --- a/auto_bangumi/parser/analyser/__init__.py +++ b/auto_bangumi/parser/analyser/__init__.py @@ -1,3 +1,4 @@ from .raw_parser import RawParser from .rename_parser import DownloadParser -from .tmdb import TMDBMatcher +from .tmdb_parser import TMDBMatcher + diff --git a/auto_bangumi/parser/analyser/bangumi_api.py b/auto_bangumi/parser/analyser/bgm_parser.py similarity index 93% rename from auto_bangumi/parser/analyser/bangumi_api.py rename to auto_bangumi/parser/analyser/bgm_parser.py index 604b0ce2..6d876760 100644 --- a/auto_bangumi/parser/analyser/bangumi_api.py +++ b/auto_bangumi/parser/analyser/bgm_parser.py @@ -5,7 +5,7 @@ from network import RequestContent from conf import settings -class BangumiAPI: +class BgmAPI: def __init__(self): self.search_url = lambda e: \ f"https://api.bgm.tv/search/subject/{e}?type=2" @@ -21,7 +21,6 @@ class BangumiAPI: return contents[0]["name"], contents[0]["name_cn"] - if __name__ == '__main__': - BGM = BangumiAPI() + BGM = BgmAPI() print(BGM.search("辉夜大小姐")) \ No newline at end of file diff --git a/auto_bangumi/parser/analyser/raw_parser.py b/auto_bangumi/parser/analyser/raw_parser.py index 21043db1..9716923a 100644 --- a/auto_bangumi/parser/analyser/raw_parser.py +++ b/auto_bangumi/parser/analyser/raw_parser.py @@ -128,7 +128,7 @@ class RawParser: sub, dpi, source = self.find_tags(other) # 剩余信息处理 return name, season, season_raw, episode, sub, dpi, source, name_group, group - def analyse(self, raw): + def analyse(self, raw: str) -> Episode or None: try: ret = self.process(raw) if ret is None: @@ -150,5 +150,6 @@ class RawParser: if __name__ == "__main__": test = RawParser() - ep = test.analyse("[ANi] Classroom of the Elite S2 - 欢迎来到实力至上主义的教室 第二季 - 01 [1080P][Baha][WEB-DL][AAC AVC][CHT][MP4]") - print(ep.title, ep.ep_info.number) + test_txt = "[SWSUB][7月新番][继母的拖油瓶是我的前女友/継母の连れ子が元カノだった][001][GB_JP][AVC][1080P][网盘][无修正] [331.6MB] [复制磁连]" + ep = test.analyse(test_txt) + print(ep.title) diff --git a/auto_bangumi/parser/analyser/rename_parser.py b/auto_bangumi/parser/analyser/rename_parser.py index ed8bc503..9d461814 100644 --- a/auto_bangumi/parser/analyser/rename_parser.py +++ b/auto_bangumi/parser/analyser/rename_parser.py @@ -1,12 +1,23 @@ +import dataclasses import re import logging from os import path +from dataclasses import dataclass # from .raw_parser import RawParser logger = logging.getLogger(__name__) +@dataclass +class DownloadInfo: + name: str + season: int + suffix: str + file_name: str + folder_name: str + + class DownloadParser: def __init__(self): self.rules = [ @@ -18,82 +29,65 @@ class DownloadParser: r"(.*)第(\d*\.*\d*)話(?:END)?(.*)", ] - def rename_normal(self, info_dict): - name = info_dict["name"] - season = info_dict["season"] + @staticmethod + def rename_init(name, folder_name, season, suffix) -> DownloadInfo: + n = re.split(r"[\[\]()【】()]", name) + suffix = suffix if suffix is not None else n[-1] + file_name = name.replace(f"[{n[1]}]", "") + if season < 10: + season = f"0{season}" + return DownloadInfo(name, season, suffix, file_name, folder_name) + + def rename_normal(self, info: DownloadInfo): for rule in self.rules: match_obj = re.match(rule, name, re.I) if match_obj is not None: title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip() - new_name = f"{title} S{season}E{match_obj.group(2)}{match_obj.group(3)}" + new_name = f"{title} S{info.season}E{match_obj.group(2)}{match_obj.group(3)}" return new_name - def rename_pn(self, info_dict): - name = info_dict["name"] - season = info_dict["season"] - suffix = info_dict["suffix"] - n = re.split(r"[\[\]()【】()]", name) - file_name = name.replace(f"[{n[1]}]", "") - if season < 10: - season = f"0{season}" + def rename_pn(self, info: DownloadInfo): for rule in self.rules: - match_obj = re.match(rule, file_name, re.I) + match_obj = re.match(rule, info.file_name, re.I) if match_obj is not None: title = re.sub(r"([Ss]|Season )\d{1,3}", "", match_obj.group(1)).strip() - title = title if title != "" else info_dict.get("folder_name") + title = title if title != "" else info.folder_name new_name = re.sub( r"[\[\]]", "", - f"{title} S{season}E{match_obj.group(2)}{suffix}", + f"{title} S{info.season}E{match_obj.group(2)}{info.suffix}", ) return new_name - def rename_advance(self, info_dict): - name = info_dict["name"] - folder_name = info_dict["folder_name"] - suffix = info_dict["suffix"] - season = info_dict["season"] - n = re.split(r"[\[\]()【】()]", name) - file_name = name.replace(f"[{n[1]}]", "") - if season < 10: - season = f"0{season}" + def rename_advance(self, info: DownloadInfo): for rule in self.rules: - match_obj = re.match(rule, file_name, re.I) + match_obj = re.match(rule, info.file_name, re.I) if match_obj is not None: new_name = re.sub( r"[\[\]]", "", - f"{folder_name} S{season}E{match_obj.group(2)}{suffix}", + f"{info.folder_name} S{info.season}E{match_obj.group(2)}{info.suffix}", ) return new_name - def rename_no_season_pn(self, info_dict): - name = info_dict["name"] - suffix = info_dict["suffix"] - n = re.split(r"[\[\]()【】()]", name) - file_name = name.replace(f"[{n[1]}]", "") + def rename_no_season_pn(self, info: DownloadInfo): for rule in self.rules: - match_obj = re.match(rule, file_name, re.I) + match_obj = re.match(rule, info.file_name, re.I) if match_obj is not None: title = match_obj.group(1).strip() new_name = re.sub( r"[\[\]]", "", - f"{title} E{match_obj.group(2)}{suffix}", + f"{title} E{match_obj.group(2)}{info.suffix}", ) return new_name @staticmethod - def rename_none(info_dict): - return info_dict["name"] + def rename_none(info: DownloadInfo): + return info.name - def download_rename(self, name, folder_name, season,suffix, method): - info_dict = { - "name": name, - "folder_name": folder_name, - "season": season, - "suffix": suffix - } + def download_rename(self, name, folder_name, season, suffix, method): + rename_info = self.rename_init(name, folder_name, season, suffix) method_dict = { "normal": self.rename_normal, "pn": self.rename_pn, @@ -102,11 +96,11 @@ class DownloadParser: "none": self.rename_none } logger.debug(f"Name: {folder_name}, File type: {path.splitext(name)[-1]}, Season {season}") - return method_dict[method.lower()](info_dict) + return method_dict[method.lower()](rename_info) if __name__ == "__main__": - name = "[Isekai Meikyuu de Harem wo][01][BIG5][1080P][AT-X].mp4" + name = "[sub][Isekai Meikyuu de Harem wo][01][BIG5][1080P][AT-X].mp4" rename = DownloadParser() - new_name = rename.download_rename(name, "Made abyess", 1, ".mp4", "Advance") - print(new_name) \ No newline at end of file + new_name = rename.download_rename(name, "Made abyess", 1, ".mp4", "pn") + print(new_name) diff --git a/auto_bangumi/parser/analyser/tmdb.py b/auto_bangumi/parser/analyser/tmdb_parser.py similarity index 58% rename from auto_bangumi/parser/analyser/tmdb.py rename to auto_bangumi/parser/analyser/tmdb_parser.py index 3837f88f..9443b142 100644 --- a/auto_bangumi/parser/analyser/tmdb.py +++ b/auto_bangumi/parser/analyser/tmdb_parser.py @@ -1,16 +1,19 @@ import re import time +from dataclasses import dataclass from network import RequestContent from conf import settings +@dataclass class TMDBInfo: id: int title_jp: str title_zh: str season: dict last_season: int + year_number: int class TMDBMatcher: @@ -21,11 +24,11 @@ class TMDBMatcher: f"https://api.themoviedb.org/3/tv/{e}?api_key={settings.tmdb_api}&language=zh-CN" self._request = RequestContent() - def is_animation(self, id): - url_info = self.info_url(id) + def is_animation(self, tv_id) -> bool: + url_info = self.info_url(tv_id) type_id = self._request.get_json(url_info)["genres"] for type in type_id: - if type["id"] == 16: + if type.get("id") == 16: return True return False @@ -37,39 +40,40 @@ class TMDBMatcher: # return title["title"] # return None - def get_season(self, seasons: list): + @staticmethod + def get_season(seasons: list) -> int: for season in seasons: - if re.search(r"第 \d 季", season["season"]) is not None: - date = season["air_date"].split("-") + if re.search(r"第 \d 季", season.get("season")) is not None: + date = season.get("air_date").split("-") [year, _ , _] = date now_year = time.localtime().tm_year if int(year) == now_year: - return int(re.findall(r"\d", season["season"])[0]) + return int(re.findall(r"\d", season.get("season"))[0]) - def tmdb_search(self, title): - tmdb_info = TMDBInfo() + def tmdb_search(self, title) -> TMDBInfo: url = self.search_url(title) - contents = self._request.get_json(url)["results"] + contents = self._request.get_json(url).get("results") if contents.__len__() == 0: url = self.search_url(title.replace(" ", "")) - contents = self._request.get_json(url)["results"] + contents = self._request.get_json(url).get("results") # 判断动画 for content in contents: id = content["id"] if self.is_animation(id): - tmdb_info.id = id break - url_info = self.info_url(tmdb_info.id) + url_info = self.info_url(id) info_content = self._request.get_json(url_info) # 关闭链接 self._request.close_session() - tmdb_info.season = [{"season": s["name"], "air_date": s["air_date"]} for s in info_content["seasons"]] - tmdb_info.last_season = self.get_season(tmdb_info.season) - tmdb_info.title_jp = info_content["original_name"] - tmdb_info.title_zh = info_content["name"] - return tmdb_info + season = [{"season": s.get("name"), "air_date": s.get("air_date")} for s in info_content.get("seasons")] + last_season = self.get_season(season) + title_jp = info_content.get("original_name") + title_zh = info_content.get("name") + year_number = info_content.get("first_air_date").split("-")[0] + return TMDBInfo(id, title_jp, title_zh, season, last_season, year_number) if __name__ == "__main__": - test = " Love Live!虹咲学园 学园偶像同好会" - print(TMDBMatcher().tmdb_search(test).title_zh) + test = "辉夜大小姐" + info = TMDBMatcher().tmdb_search(test) + print(f"{info.title_zh}({info.year_number})") diff --git a/auto_bangumi/parser/title_parser.py b/auto_bangumi/parser/title_parser.py index 51472f43..bf851ab2 100644 --- a/auto_bangumi/parser/title_parser.py +++ b/auto_bangumi/parser/title_parser.py @@ -10,27 +10,33 @@ class TitleParser: def __init__(self): self._raw_parser = RawParser() self._download_parser = DownloadParser() + self._tmdb_parser = TMDBMatcher() - def raw_parser(self, raw): + def raw_parser(self, raw: str): return self._raw_parser.analyse(raw) def download_parser(self, download_raw, folder_name, season, suffix, method=settings.method): return self._download_parser.download_rename(download_raw, folder_name, season, suffix, method) - def return_dict(self, raw): - tmdb = TMDBMatcher() + def tmdb_parser(self, title: str, season:int): + try: + tmdb_info = self._tmdb_parser.tmdb_search(title) + logger.debug(f"TMDB Matched, title is {tmdb_info.title_zh}") + except Exception as e: + logger.warning("Not Matched with TMDB") + return title, season + if settings.title_language == "zh": + official_title = f"{tmdb_info.title_zh}({tmdb_info.year_number})" + elif settings.title_language == "jp": + official_title = f"{tmdb_info.title_jp}({tmdb_info.year_number})" + season = tmdb_info.last_season + return official_title, season + + def return_dict(self, raw: str): try: episode = self.raw_parser(raw) if settings.enable_tmdb: - try: - tmdb_info = tmdb.tmdb_search(episode.title) - official_title = tmdb_info.title_zh if settings.title_language == "zh" else tmdb_info.title_jp - season = tmdb_info.last_season - except Exception as e: - logger.debug(e) - logger.info("Not Match in TMDB") - official_title = episode.title - season = episode.season_info.number + official_title, season = self.tmdb_parser(episode.title, episode.season_info.number) else: official_title = episode.title season = episode.season_info.number @@ -54,9 +60,10 @@ class TitleParser: if __name__ == '__main__': import re + from conf.const_dev import DEV_SETTINGS + settings.init(DEV_SETTINGS) T = TitleParser() - raw = "[Lilith-Raws] 神渣☆偶像 / Kami Kuzu☆Idol - 01 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4]" + raw = "[SWSUB][7月新番][继母的拖油瓶是我的前女友/継母の连れ子が元カノだった][001][GB_JP][AVC][1080P][网盘][无修正] [331.6MB] [复制磁连]" season = int(re.search(r"\d{1,2}", "S02").group()) - title = T.raw_parser(raw) - print(season, title.title, title.ep_info.number) - print(T.return_dict(raw)) + dict = T.return_dict(raw) + print(dict) diff --git a/requirements.txt b/requirements.txt index 3ff9f17b..2ccd49b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ soupsieve typing_extensions==4.3.0 urllib3==1.26.9 uvicorn +attrdict