diff --git a/docs/wiki b/docs/wiki index d0bb98f0..519e381e 160000 --- a/docs/wiki +++ b/docs/wiki @@ -1 +1 @@ -Subproject commit d0bb98f004fb292519dd56c42238ecb2f034eac9 +Subproject commit 519e381e8a1add62e76a39181ee61bad02816035 diff --git a/src/module/ab_decorator/__init__.py b/src/module/ab_decorator/__init__.py index f469894c..f25a083b 100644 --- a/src/module/ab_decorator/__init__.py +++ b/src/module/ab_decorator/__init__.py @@ -1,7 +1,9 @@ import logging import time +import threading logger = logging.getLogger(__name__) +lock = threading.Lock() def qb_connect_failed_wait(func): @@ -30,3 +32,10 @@ def api_failed(func): logger.debug(e) return wrapper + + +def locked(func): + def wrapper(*args, **kwargs): + with lock: + return func(*args, **kwargs) + return wrapper \ No newline at end of file diff --git a/src/module/checker/checker.py b/src/module/checker/checker.py index fc2c46d8..474637f6 100644 --- a/src/module/checker/checker.py +++ b/src/module/checker/checker.py @@ -2,7 +2,8 @@ import os.path from module.downloader import DownloadClient from module.network import RequestContent -from module.conf import settings, DATA_PATH +from module.conf import settings +from module.models import Config class Checker: @@ -46,8 +47,7 @@ class Checker: @staticmethod def check_first_run() -> bool: - token_exist = False if settings.rss_parser.token in ["", "token"] else True - if token_exist: - return False - else: + if settings.dict() == Config().dict(): return True + else: + return False diff --git a/src/module/core/sub_thread.py b/src/module/core/sub_thread.py index 33f9d1c5..122bf744 100644 --- a/src/module/core/sub_thread.py +++ b/src/module/core/sub_thread.py @@ -19,6 +19,8 @@ class RSSThread(ProgramStatus): ) def rss_loop(self): + with DownloadClient() as client: + client.init_downloader() while not self.stop_event.is_set(): # Analyse RSS with BangumiDatabase() as db: diff --git a/src/module/database/bangumi.py b/src/module/database/bangumi.py index 74e7fec5..f2129826 100644 --- a/src/module/database/bangumi.py +++ b/src/module/database/bangumi.py @@ -2,6 +2,7 @@ import logging from module.database.connector import DataConnector from module.models import BangumiData +from module.ab_decorator import locked logger = logging.getLogger(__name__) @@ -68,6 +69,7 @@ class BangumiDatabase(DataConnector): data_list = [self.__data_to_db(x) for x in data] self._update_list(data_list=data_list, table_name=self.__table_name) + @locked def update_rss(self, title_raw, rss_set: str): # Update rss and added self._cursor.execute( @@ -108,51 +110,59 @@ class BangumiDatabase(DataConnector): self._delete_all(self.__table_name) def search_all(self) -> list[BangumiData]: - self._cursor.execute( - """ - SELECT * FROM bangumi - """ - ) - return self.__fetch_data() + dict_data = self._search_datas(self.__table_name) + return [self.__db_to_data(x) for x in dict_data] def search_id(self, _id: int) -> BangumiData | None: - self._cursor.execute( - """ - SELECT * FROM bangumi WHERE id = :id - """, - {"id": _id}, - ) - values = self._cursor.fetchone() - if values is None: + condition = {"id": _id} + value = self._search_data(table_name=self.__table_name, condition=condition) + # self._cursor.execute( + # """ + # SELECT * FROM bangumi WHERE id = :id + # """, + # {"id": _id}, + # ) + # values = self._cursor.fetchone() + if value is None: return None keys = [x[0] for x in self._cursor.description] - dict_data = dict(zip(keys, values)) + dict_data = dict(zip(keys, value)) return self.__db_to_data(dict_data) def search_official_title(self, official_title: str) -> BangumiData | None: - self._cursor.execute( - """ - SELECT * FROM bangumi WHERE official_title = :official_title - """, - {"official_title": official_title}, + value = self._search_data( + table_name=self.__table_name, condition={"official_title": official_title} ) - values = self._cursor.fetchone() - if values is None: + # self._cursor.execute( + # """ + # SELECT * FROM bangumi WHERE official_title = :official_title + # """, + # {"official_title": official_title}, + # ) + # values = self._cursor.fetchone() + if value is None: return None keys = [x[0] for x in self._cursor.description] - dict_data = dict(zip(keys, values)) + dict_data = dict(zip(keys, value)) return self.__db_to_data(dict_data) def match_poster(self, bangumi_name: str) -> str: - self._cursor.execute( - """ - SELECT official_title, poster_link - FROM bangumi - WHERE INSTR(:bangumi_name, official_title) > 0 - """, - {"bangumi_name": bangumi_name}, + condition = f"INSTR({bangumi_name}, official_title) > 0" + keys = ["official_title", "poster_link"] + data = self._search_data( + table_name=self.__table_name, + keys=keys, + condition=condition, ) - data = self._cursor.fetchone() + # self._cursor.execute( + # """ + # SELECT official_title, poster_link + # FROM bangumi + # WHERE INSTR(:bangumi_name, official_title) > 0 + # """, + # {"bangumi_name": bangumi_name}, + # ) + # data = self._cursor.fetchone() if not data: return "" official_title, poster_link = data @@ -160,14 +170,20 @@ class BangumiDatabase(DataConnector): return "" return poster_link + @locked def match_list(self, torrent_list: list, rss_link: str) -> list: # Match title_raw in database - self._cursor.execute( - """ - SELECT title_raw, rss_link, poster_link FROM bangumi - """ + keys = ["title_raw", "rss_link", "poster_link"] + data = self._search_datas( + table_name=self.__table_name, + keys=keys, ) - data = self._cursor.fetchall() + # self._cursor.execute( + # """ + # SELECT title_raw, rss_link, poster_link FROM bangumi + # """ + # ) + # data = self._cursor.fetchall() if not data: return torrent_list # Match title @@ -189,6 +205,12 @@ class BangumiDatabase(DataConnector): def not_complete(self) -> list[BangumiData]: # Find eps_complete = False + condition = "eps_complete = 0" + data = self._search_datas( + table_name=self.__table_name, + condition=condition, + ) + self._cursor.execute( """ SELECT * FROM bangumi WHERE eps_collect = 0 diff --git a/src/module/database/connector.py b/src/module/database/connector.py index d74bb159..dbd87c04 100644 --- a/src/module/database/connector.py +++ b/src/module/database/connector.py @@ -2,6 +2,7 @@ import os import sqlite3 import logging + from module.conf import DATA_PATH logger = logging.getLogger(__name__) @@ -93,10 +94,35 @@ class DataConnector: ) self._conn.commit() + def _delete_all(self, table_name: str): self._cursor.execute(f"DELETE FROM {table_name}") self._conn.commit() + + def _search_data(self, table_name: str, keys: list[str] | None, condition: str) -> dict: + if keys is None: + self._cursor.execute(f"SELECT * FROM {table_name} WHERE {condition}") + else: + self._cursor.execute( + f"SELECT {', '.join(keys)} FROM {table_name} WHERE {condition}" + ) + return dict(zip(keys, self._cursor.fetchone())) + + + def _search_datas(self, table_name: str, keys: list[str] | None, condition: str = None) -> list[dict]: + if keys is None: + select_sql = "*" + else: + select_sql = ", ".join(keys) + if condition is None: + self._cursor.execute(f"SELECT {select_sql} FROM {table_name}") + else: + self._cursor.execute( + f"SELECT {select_sql} FROM {table_name} WHERE {condition}" + ) + return [dict(zip(keys, row)) for row in self._cursor.fetchall()] + def _table_exists(self, table_name: str) -> bool: self._cursor.execute( f"SELECT name FROM sqlite_master WHERE type='table' AND name=?;", diff --git a/src/module/downloader/client/qb_downloader.py b/src/module/downloader/client/qb_downloader.py index 8b22b813..fc5b622e 100644 --- a/src/module/downloader/client/qb_downloader.py +++ b/src/module/downloader/client/qb_downloader.py @@ -132,7 +132,12 @@ class QbDownloader: return self._client.torrents_info(hashes=_hash)[0].save_path def set_category(self, _hash, category): - self._client.torrents_set_category(category, hashes=_hash) + try: + self._client.torrents_set_category(category, hashes=_hash) + except Conflict409Error: + logger.warning(f"[Downloader] Category {category} does not exist") + self.add_category(category) + self._client.torrents_set_category(category, hashes=_hash) def check_connection(self): return self._client.app_version() diff --git a/src/module/manager/collector.py b/src/module/manager/collector.py index 7350108a..cff45d3f 100644 --- a/src/module/manager/collector.py +++ b/src/module/manager/collector.py @@ -59,8 +59,3 @@ def eps_complete(): bd.update_list(datas) -if __name__ == "__main__": - from module.conf import setup_logger - - setup_logger() - eps_complete() diff --git a/src/module/manager/renamer.py b/src/module/manager/renamer.py index c6b25b5e..6cfc9b8b 100644 --- a/src/module/manager/renamer.py +++ b/src/module/manager/renamer.py @@ -106,7 +106,7 @@ class Renamer(DownloadClient): _hash=_hash, old_path=media_path, new_path=new_path ) if not renamed: - logger.warning(f"[Renamrr] {media_path} rename failed") + logger.warning(f"[Renamer] {media_path} rename failed") # Delete bad torrent. if settings.bangumi_manage.remove_bad_torrent: self.delete_torrent(_hash) diff --git a/src/module/models/bangumi.py b/src/module/models/bangumi.py index f1c03369..2dd73340 100644 --- a/src/module/models/bangumi.py +++ b/src/module/models/bangumi.py @@ -28,6 +28,7 @@ class Notification(BaseModel): official_title: str = Field(..., alias="official_title", title="番剧名") season: int = Field(..., alias="season", title="番剧季度") episode: int = Field(..., alias="episode", title="番剧集数") + poster_path: str | None = Field(None, alias="poster_path", title="番剧海报路径") @dataclass diff --git a/src/module/notification/notification.py b/src/module/notification/notification.py index 22bde17a..1e13ff67 100644 --- a/src/module/notification/notification.py +++ b/src/module/notification/notification.py @@ -32,24 +32,25 @@ class PostNotification: ) @staticmethod - def _gen_message(notify: Notification) -> str: + def _get_poster(notify: Notification): with BangumiDatabase() as db: poster_path = db.match_poster(notify.official_title) if poster_path: poster_link = "https://mikanani.me" + poster_path - text = f""" - 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n{poster_link}\n - """ + # text = f""" + # 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n{poster_link}\n + # """ else: - text = f""" - 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n - """ - return text + poster_link = "https://mikanani.me" + # text = f""" + # 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n + # """ + notify.poster_path = poster_link def send_msg(self, notify: Notification) -> bool: - text = self._gen_message(notify) + self._get_poster(notify) try: - self.notifier.post_msg(text) + self.notifier.post_msg(notify) logger.debug(f"Send notification: {notify.official_title}") except Exception as e: logger.warning(f"Failed to send notification: {e}") diff --git a/src/module/notification/plugin/bark.py b/src/module/notification/plugin/bark.py index f405ab4b..e825a2c9 100644 --- a/src/module/notification/plugin/bark.py +++ b/src/module/notification/plugin/bark.py @@ -1,5 +1,7 @@ import logging + from module.network import RequestContent +from module.models import Notification logger = logging.getLogger(__name__) @@ -11,8 +13,16 @@ class BarkNotification(RequestContent): self.token = token self.notification_url = "https://api.day.app/push" - def post_msg(self, text) -> bool: - data = {"title": "AutoBangumi 番剧更新", "body": text, "device_key": self.token} + @staticmethod + def gen_message(notify: Notification) -> str: + text = f""" + 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n{notify.poster_path}\n + """ + return text + + def post_msg(self, notify: Notification) -> bool: + text = self.gen_message(notify) + data = {"title": notify.official_title, "body": text, "device_key": self.token} resp = self.post_data(self.notification_url, data) logger.debug(f"Bark notification: {resp.status_code}") return resp.status_code == 200 diff --git a/src/module/notification/plugin/server_chan.py b/src/module/notification/plugin/server_chan.py index 5b4b32d0..9903ed3e 100644 --- a/src/module/notification/plugin/server_chan.py +++ b/src/module/notification/plugin/server_chan.py @@ -1,6 +1,7 @@ import logging from module.network import RequestContent +from module.models import Notification logger = logging.getLogger(__name__) @@ -12,9 +13,17 @@ class ServerChanNotification(RequestContent): super().__init__() self.notification_url = f"https://sctapi.ftqq.com/{token}.send" - def post_msg(self, text: str) -> bool: + @staticmethod + def gen_message(notify: Notification) -> str: + text = f""" + 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n{notify.poster_path}\n + """ + return text + + def post_msg(self, notify: Notification) -> bool: + text = self.gen_message(notify) data = { - "title": "AutoBangumi 番剧更新", + "title": notify.official_title, "desp": text, } resp = self.post_data(self.notification_url, data) diff --git a/src/module/notification/plugin/slack.py b/src/module/notification/plugin/slack.py index 1773e85d..af4a0d83 100644 --- a/src/module/notification/plugin/slack.py +++ b/src/module/notification/plugin/slack.py @@ -1,6 +1,7 @@ import logging from module.network import RequestContent +from module.models import Notification logger = logging.getLogger(__name__) @@ -11,8 +12,16 @@ class SlackNotification(RequestContent): self.token = token self.notification_url = "https://api.day.app/push" - def post_msg(self, text) -> bool: - data = {"title": "AutoBangumi 番剧更新", "body": text, "device_key": self.token} + @staticmethod + def gen_message(notify: Notification) -> str: + text = f""" + 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n{notify.poster_path}\n + """ + return text + + def post_msg(self, notify: Notification) -> bool: + text = self.gen_message(notify) + data = {"title": notify.official_title, "body": text, "device_key": self.token} resp = self.post_data(self.notification_url, data) logger.debug(f"Bark notification: {resp.status_code}") return resp.status_code == 200 \ No newline at end of file diff --git a/src/module/notification/plugin/telegram.py b/src/module/notification/plugin/telegram.py index 71e90c16..b269aea3 100644 --- a/src/module/notification/plugin/telegram.py +++ b/src/module/notification/plugin/telegram.py @@ -1,6 +1,7 @@ import logging -from module.network.request_contents import RequestContent +from module.network import RequestContent +from module.models import Notification logger = logging.getLogger(__name__) @@ -11,7 +12,15 @@ class TelegramNotification(RequestContent): self.notification_url = f"https://api.telegram.org/bot{token}/sendMessage" self.chat_id = chat_id - def post_msg(self, text: str) -> bool: + @staticmethod + def gen_message(notify: Notification) -> str: + text = f""" + 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n{notify.poster_path}\n + """ + return text + + def post_msg(self, notify: Notification) -> bool: + text = self.gen_message(notify) data = { "chat_id": self.chat_id, "text": text, diff --git a/src/module/notification/plugin/wecom.py b/src/module/notification/plugin/wecom.py index c56e97e2..11a63a75 100644 --- a/src/module/notification/plugin/wecom.py +++ b/src/module/notification/plugin/wecom.py @@ -1,5 +1,7 @@ import logging + from module.network import RequestContent +from module.models import Notification logger = logging.getLogger(__name__) @@ -13,21 +15,26 @@ class WecomNotification(RequestContent): self.notification_url = f"{chat_id}" self.token = token - def post_msg(self, text: str) -> bool: + @staticmethod + def gen_message(notify: Notification) -> str: + text = f""" + 番剧名称:{notify.official_title}\n季度: 第{notify.season}季\n更新集数: 第{notify.episode}集\n{notify.poster_path}\n + """ + return text + + def post_msg(self, notify: Notification) -> bool: ##Change message format to match Wecom push better - info = text.split(":") - print(info) - title = "【番剧更新】" + info[1].split("\n")[0].strip() - msg = info[2].split("\n")[0].strip()+" "+info[3].split("\n")[0].strip() - picurl = info[3].split("\n")[1].strip() + title = "【番剧更新】" + notify.official_title + msg = self.gen_message(notify) + picurl = notify.poster_path #Default pic to avoid blank in message. Resolution:1068*455 - if picurl == "": + if picurl == "https://mikanani.me": picurl = "https://article.biliimg.com/bfs/article/d8bcd0408bf32594fd82f27de7d2c685829d1b2e.png" data = { - "key":self.token, - "type": "news", - "title": title, - "msg": msg, + "key":self.token, + "type": "news", + "title": title, + "msg": msg, "picurl":picurl } resp = self.post_data(self.notification_url, data)