From d657276cfd84529e3d25d4cc598a68eec6b3f843 Mon Sep 17 00:00:00 2001 From: sowevo Date: Wed, 26 Apr 2023 13:52:24 +0800 Subject: [PATCH 01/34] =?UTF-8?q?plex=E6=8E=A5=E5=8F=A3=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mediaserver/client/plex.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/mediaserver/client/plex.py b/app/mediaserver/client/plex.py index da5d1cba..085862e0 100644 --- a/app/mediaserver/client/plex.py +++ b/app/mediaserver/client/plex.py @@ -105,7 +105,7 @@ def get_activity_log(self, num): return [] ret_array = [] try: - # type的含义: 1 电影 4 剧集单集 + # type的含义: 1 电影 4 剧集单集 详见 plexapi/utils.py中SEARCHTYPES的定义 # 根据最后播放时间倒序获取数据 historys = self._plex.library.search(sort='lastViewedAt:desc', limit=num, type='1,4') for his in historys: @@ -315,10 +315,10 @@ def get_libraries(self): match library.type: case "movie": library_type = MediaType.MOVIE.value - image_list_str = self.get_libraries_image(library.key) + image_list_str = self.get_libraries_image(library.key, 1) case "show": library_type = MediaType.TV.value - image_list_str = self.get_libraries_image(library.key) + image_list_str = self.get_libraries_image(library.key, 2) case _: continue libraries.append({ @@ -333,12 +333,18 @@ def get_libraries(self): return libraries @lru_cache(maxsize=10) - def get_libraries_image(self, library_key): + def get_libraries_image(self, library_key, type): + """ + 获取媒体服务器最近添加的媒体的图片列表 + param: library_key + param: type type的含义: 1 电影 2 剧集 详见 plexapi/utils.py中SEARCHTYPES的定义 + """ if not self._plex: return "" - library = self._plex.library.sectionByID(library_key) # 担心有些没图片,多获取几个 - items = library.recentlyAdded(maxresults=8) + items = self._plex.fetchItems(f"/hubs/home/recentlyAdded?type={type}§ionID={library_key}", + container_size=8, + container_start=0) poster_urls = [] for item in items: if item.posterUrl is not None: @@ -493,12 +499,7 @@ def get_resume(self, num=12): """ if not self._plex: return [] - items = self._plex.library.search(**{ - 'sort': 'lastViewedAt:desc', # 按最后观看时间排序 - 'type': '1,4', # 1 电影 4 剧集单集 - 'viewOffset!': '0', # 播放进度不等于0的 - 'limit': num # 限制结果数量 - }) + items = self._plex.fetchItems('/hubs/continueWatching/items', container_start=0, container_size=num) ret_resume = [] for item in items: item_type = MediaType.MOVIE.value if item.TYPE == "movie" else MediaType.TV.value @@ -526,9 +527,9 @@ def get_latest(self, num=20): """ if not self._plex: return [] - items = self._plex.library.recentlyAdded() + items = self._plex.fetchItems('/library/recentlyAdded', container_start=0, container_size=num) ret_resume = [] - for item in items[:num]: + for item in items: item_type = MediaType.MOVIE.value if item.TYPE == "movie" else MediaType.TV.value link = self.get_play_url(item.key) title = item.title if item_type == MediaType.MOVIE.value else \ From 17fd7841d378419ab42e19011cefabc793babc8b Mon Sep 17 00:00:00 2001 From: thsrite Date: Wed, 26 Apr 2023 14:28:39 +0800 Subject: [PATCH 02/34] =?UTF-8?q?fix=20=E8=87=AA=E5=8A=A8=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=E3=80=81=E8=87=AA=E5=8A=A8=E7=AD=BE=E5=88=B0=E5=81=9C?= =?UTF-8?q?=E6=AD=A2=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/plugins/modules/autobackup.py | 20 +++++++++++++++++++- app/plugins/modules/autosignin.py | 22 ++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/plugins/modules/autobackup.py b/app/plugins/modules/autobackup.py index a447572f..a7913e58 100644 --- a/app/plugins/modules/autobackup.py +++ b/app/plugins/modules/autobackup.py @@ -7,6 +7,7 @@ from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger +from threading import Event from app.plugins.modules._base import _IPluginModule from app.utils import SystemUtils from config import Config @@ -47,6 +48,8 @@ class AutoBackup(_IPluginModule): _bk_path = None _onlyonce = False _notify = False + # 退出事件 + _event = Event() @staticmethod def get_fields(): @@ -145,6 +148,9 @@ def init_config(self, config=None): self._notify = config.get("notify") self._onlyonce = config.get("onlyonce") + # 停止现有任务 + self.stop_service() + # 启动服务 if self._enabled or self._onlyonce: self._scheduler = BackgroundScheduler(timezone=Config().get_timezone()) @@ -228,7 +234,19 @@ def __backup(self): f"剩余备份数量 {bk_cnt - del_cnt}") def stop_service(self): - pass + """ + 退出插件 + """ + try: + if self._scheduler: + self._scheduler.remove_all_jobs() + if self._scheduler.running: + self._event.set() + self._scheduler.shutdown() + self._event.clear() + self._scheduler = None + except Exception as e: + print(str(e)) def get_state(self): return self._enabled and self._cron diff --git a/app/plugins/modules/autosignin.py b/app/plugins/modules/autosignin.py index 77b2db9c..9ef55799 100644 --- a/app/plugins/modules/autosignin.py +++ b/app/plugins/modules/autosignin.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta from multiprocessing.dummy import Pool as ThreadPool from multiprocessing.pool import ThreadPool +from threading import Event import pytz from apscheduler.schedulers.background import BackgroundScheduler @@ -62,6 +63,8 @@ class AutoSignIn(_IPluginModule): _onlyonce = False _notify = False _clean = False + # 退出事件 + _event = Event() @staticmethod def get_fields(): @@ -194,6 +197,9 @@ def init_config(self, config=None): self._onlyonce = config.get("onlyonce") self._clean = config.get("clean") + # 停止现有任务 + self.stop_service() + # 启动服务 if self._enabled or self._onlyonce: # 加载模块 @@ -334,7 +340,7 @@ def sign_in(self): self.send_message(title="【自动签到任务完成】", text=f"本次签到数量: {len(sign_sites)} \n" f"命中重试数量: {len(retry_sites) if self._retry_keyword else 0} \n" - f"下次签到数量: {len(retry_sites + self._special_sites)} \n" + f"下次签到数量: {len(set(retry_sites + self._special_sites))} \n" f"详见签到消息") else: self.error("站点签到任务失败!") @@ -471,7 +477,19 @@ def __signin_base(self, site_info): return f"{site} 签到出错:{str(e)}!" def stop_service(self): - pass + """ + 退出插件 + """ + try: + if self._scheduler: + self._scheduler.remove_all_jobs() + if self._scheduler.running: + self._event.set() + self._scheduler.shutdown() + self._event.clear() + self._scheduler = None + except Exception as e: + print(str(e)) def get_state(self): return self._enabled and self._cron From 8d3857716450f9040cd248684e1aff170775f782 Mon Sep 17 00:00:00 2001 From: thsrite Date: Wed, 26 Apr 2023 14:47:01 +0800 Subject: [PATCH 03/34] =?UTF-8?q?fix=20=E4=BB=BF=E7=9C=9F=E7=AD=BE?= =?UTF-8?q?=E5=88=B0=E5=A4=9A=E6=AC=A1=E8=BF=90=E8=A1=8C=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E7=AD=BE=E5=88=B0=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/plugins/modules/autosignin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/plugins/modules/autosignin.py b/app/plugins/modules/autosignin.py index 9ef55799..1c3b8a49 100644 --- a/app/plugins/modules/autosignin.py +++ b/app/plugins/modules/autosignin.py @@ -434,6 +434,10 @@ def __signin_base(self, site_info): if not cloudflare: self.info("%s 仿真签到失败,无法通过Cloudflare" % site) return f"【{site}】仿真签到失败,无法通过Cloudflare!" + + # 判断是否已签到 [签到已得125, 补签卡: 0] + if re.search(r'已签|签到已得', chrome.get_html(), re.IGNORECASE): + return f"【{site}】签到成功" self.info("%s 仿真签到成功" % site) return f"【{site}】仿真签到成功" except Exception as e: From 1738405e6c2916a4778761ba363c8ed5f2819e7b Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 17:17:53 +0800 Subject: [PATCH 04/34] =?UTF-8?q?fix=20=E4=BD=BF=E7=94=A8WebSocket?= =?UTF-8?q?=E5=88=B7=E6=96=B0WEB=E6=97=A5=E5=BF=97=E5=92=8C=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- log.py | 1 + requirements.txt | 1 + web/action.py | 54 ----- web/main.py | 53 +++++ web/static/js/functions.js | 264 +++++++++++------------- web/templates/download/downloading.html | 6 +- web/templates/index.html | 4 +- web/templates/navigation.html | 31 ++- web/templates/rss/user_rss.html | 4 +- web/templates/site/statistics.html | 12 +- 10 files changed, 217 insertions(+), 213 deletions(-) diff --git a/log.py b/log.py index b9ae918b..f5eeaffe 100644 --- a/log.py +++ b/log.py @@ -11,6 +11,7 @@ logging.getLogger('werkzeug').setLevel(logging.ERROR) lock = threading.Lock() + LOG_QUEUE = deque(maxlen=200) LOG_INDEX = 0 diff --git a/requirements.txt b/requirements.txt index 19543c3a..edf23ade 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,6 +29,7 @@ Flask-Login==0.6.2 fast-bencode==1.1.3 flask-compress==1.13 flask-restx==1.0.6 +flask-sock==0.6.0 func_timeout==4.3.5 greenlet==1.1.3.post0 h11==0.12.0 diff --git a/web/action.py b/web/action.py index c7abaa3e..bbb73fa7 100644 --- a/web/action.py +++ b/web/action.py @@ -66,7 +66,6 @@ def __init__(self): "rename": self.__rename, "rename_udf": self.__rename_udf, "delete_history": self.delete_history, - "logging": self.__logging, "version": self.__version, "update_site": self.__update_site, "get_site": self.__get_site, @@ -89,7 +88,6 @@ def __init__(self): "test_connection": self.__test_connection, "user_manager": self.__user_manager, "refresh_rss": self.__refresh_rss, - "refresh_message": self.__refresh_message, "delete_tmdb_cache": self.__delete_tmdb_cache, "movie_calendar_data": self.__movie_calendar_data, "tv_calendar_data": self.__tv_calendar_data, @@ -1041,37 +1039,6 @@ def delete_media_file(filedir, filename): ExceptionUtils.exception_traceback(e) return True, f"{file} 删除失败" - @staticmethod - def __logging(data): - """ - 查询实时日志 - """ - log_list = [] - refresh_new = data.get('refresh_new') - source = data.get('source') - - if not source: - if not refresh_new: - log_list = list(log.LOG_QUEUE) - elif log.LOG_INDEX: - if log.LOG_INDEX > len(log.LOG_QUEUE): - log_list = list(log.LOG_QUEUE) - else: - log_list = list(log.LOG_QUEUE[-log.LOG_INDEX:]) - log.LOG_INDEX = 0 - else: - for message in log.LOG_QUEUE: - if str(message.get("source")) == source: - log_list.append(message) - else: - continue - if refresh_new: - if int(refresh_new) < len(log_list): - log_list = log_list[int(refresh_new):] - elif int(refresh_new) >= len(log_list): - log_list = [] - return {"loglist": log_list} - @staticmethod def __version(data): """ @@ -1751,27 +1718,6 @@ def get_system_message(lst_time): "lst_time": lst_time } - def __refresh_message(self, data): - """ - 刷新首页消息中心 - """ - lst_time = data.get("lst_time") - system_msg = self.get_system_message(lst_time=lst_time) - messages = system_msg.get("message") - lst_time = system_msg.get("lst_time") - ret_messages = [] - for message in list(reversed(messages)): - content = re.sub(r"#+", "
", - re.sub(r"<[^>]+>", "", - re.sub(r"
", "####", message.get("content"), flags=re.IGNORECASE))) - ret_messages.append({ - "level": "bg-red" if message.get("level") == "ERROR" else "", - "title": message.get("title"), - "content": content, - "time": message.get("time") - }) - return {"code": 0, "message": ret_messages, "lst_time": lst_time} - @staticmethod def __delete_tmdb_cache(data): """ diff --git a/web/main.py b/web/main.py index 4fe35bb0..cb5c993e 100644 --- a/web/main.py +++ b/web/main.py @@ -18,6 +18,7 @@ redirect, Response from flask_compress import Compress from flask_login import LoginManager, login_user, login_required, current_user +from flask_sock import Sock from icalendar import Calendar, Event, Alarm from werkzeug.middleware.proxy_fix import ProxyFix @@ -59,6 +60,9 @@ App.secret_key = os.urandom(24) App.permanent_session_lifetime = datetime.timedelta(days=30) +# Flask Socket +Sock = Sock(App) + # 启用压缩 Compress(App) @@ -1644,6 +1648,55 @@ def Img(): return response +@Sock.route('/logging') +def logging_handler(ws): + """ + 实时日志WebSocket + """ + while True: + try: + message = ws.receive() + _source = json.loads(message).get("source") + if log.LOG_INDEX > 0: + logs = list(log.LOG_QUEUE)[-log.LOG_INDEX:] + log.LOG_INDEX = 0 + if _source: + logs = [l for l in logs if l.get("source") == _source] + ws.send((json.dumps(logs))) + else: + ws.send(json.dumps([])) + except Exception as err: + print(str(err)) + break + + +@Sock.route('/message') +def message_handler(ws): + while True: + try: + data = ws.receive() + system_msg = WebAction().get_system_message(lst_time=json.loads(data).get("lst_time")) + messages = system_msg.get("message") + lst_time = system_msg.get("lst_time") + ret_messages = [] + for message in list(reversed(messages)): + content = re.sub(r"#+", "
", + re.sub(r"<[^>]+>", "", + re.sub(r"
", "####", message.get("content"), flags=re.IGNORECASE))) + ret_messages.append({ + "level": "bg-red" if message.get("level") == "ERROR" else "", + "title": message.get("title"), + "content": content, + "time": message.get("time") + }) + ws.send((json.dumps({ + "lst_time": lst_time, + "message": ret_messages + }))) + except Exception as err: + print(str(err)) + break + # base64模板过滤器 @App.template_filter('b64encode') def b64encode(s): diff --git a/web/static/js/functions.js b/web/static/js/functions.js index c0761b25..01b71cd3 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -1,30 +1,36 @@ /** * 公共变量区 */ -//刷新LOG标志 -let refresh_logging_flag = false; -// 日志来源筛选时关掉之前的刷新日志计时器 -let refresh_logging_timer; -let logger_source = ""; // 刷新进度 -let refresh_process_flag = false; -let refresh_fail_count = 0; +let RefreshProcessFlag = false; +let RefreshFailCount = 0; // 刷新订阅站点列表 -let RSS_SITES_LENGTH = 0; +let RssSitesLength = 0; // 刷新搜索站点列表 -let SEARCH_SITES_LENGTH = 0; +let SearchSitesLength = 0; // 种子上传控件 -let torrent_dropzone; +let TorrentDropZone; // 默认转移模式 -let default_transfer_mode; +let DefaultTransferMode; // 默认路径 -let default_path; +let DefaultPath; // 页面正在加载中的标志 let NavPageLoading = false; // 加载中页面的字柄 let NavPageXhr; // 是否允许打断弹窗 let GlobalModalAbort = true; +//实时日志刷新标志 +let RefreshLoggingFlag = false; +// 日志来源筛选时关掉之前的刷新日志计时器 +let LoggingSource = ""; +// 日志WebSocket +let LoggingWS; +// 是否存量消息刷新 +let OldMessageFlag = true; +// 消息WebSocket +let MessageWS; + /** * 公共函数区 @@ -119,108 +125,91 @@ function hide_wait_modal() { } -//开始刷新日志 +//发送刷新日志请求 function start_logging() { - refresh_logging_flag = true; - refresh_logging(); + RefreshLoggingFlag = true; + LoggingWS.send(JSON.stringify({"source": LoggingSource})); } //停止刷新日志 function stop_logging() { - refresh_logging_flag = false; + RefreshLoggingFlag = false; } //刷新日志 -function refresh_logging(flag) { - let refresh_new = $("#logging_content").children().length; - ajax_post("logging", {"refresh_new": refresh_new, "source": logger_source}, function (ret) { - if (ret.loglist) { - let log_list = ret.loglist; - let tdstyle = "padding-top: 0.5rem; padding-bottom: 0.5rem"; - let tbody = ""; - for (let log of log_list) { - let text = log.text; - const source = log.source; - const time = log.time; - const level = log.level; - let tcolor = ''; - let bgcolor = ''; - let tstyle = '-webkit-line-clamp:4; display: -webkit-box; -webkit-box-orient:vertical; overflow:hidden; text-overflow: ellipsis;'; - if (level === "WARN") { - tcolor = "text-warning"; - bgcolor = "bg-warning"; - } else if (level === "ERROR") { - tcolor = "text-danger"; - bgcolor = "bg-danger"; - } else if (source === "System") { - tcolor = "text-info"; - bgcolor = "bg-info"; - } else { - tcolor = "text"; - } - if (["Rmt", "Plugin"].includes(source) && text.includes(" 到 ")) { - tstyle = `${tstyle} white-space: pre;` - text = text.replace(/\s到\s/, "\n=> ") - } - if (text.includes("http") || text.includes("magnet")) { - tstyle = `${tstyle} word-break: break-all;` - text = text.replace(/:((?:http|magnet).+?)(?:\s|$)/g, ":$1") - } - tbody = `${tbody} - - ${time} - ${source} - ${text} - `; +function render_logging(log_list) { + if (log_list) { + let tdstyle = "padding-top: 0.5rem; padding-bottom: 0.5rem"; + let tbody = ""; + for (let log of log_list) { + let text = log.text; + const source = log.source; + const time = log.time; + const level = log.level; + let tcolor = ''; + let bgcolor = ''; + let tstyle = '-webkit-line-clamp:4; display: -webkit-box; -webkit-box-orient:vertical; overflow:hidden; text-overflow: ellipsis;'; + if (level === "WARN") { + tcolor = "text-warning"; + bgcolor = "bg-warning"; + } else if (level === "ERROR") { + tcolor = "text-danger"; + bgcolor = "bg-danger"; + } else if (source === "System") { + tcolor = "text-info"; + bgcolor = "bg-info"; + } else { + tcolor = "text"; } - let logging_table_obj = $("#logging_table"); - let bool_ToScrolTop = (logging_table_obj.scrollTop() + logging_table_obj.prop("offsetHeight")) >= logging_table_obj.prop("scrollHeight"); - $("#logging_content").append(tbody); - if (bool_ToScrolTop) { - setTimeout(function () { - logging_table_obj.scrollTop(logging_table_obj.prop("scrollHeight")); - }, 500); + if (["Rmt", "Plugin"].includes(source) && text.includes(" 到 ")) { + tstyle = `${tstyle} white-space: pre;` + text = text.replace(/\s到\s/, "\n=> ") } + if (text.includes("http") || text.includes("magnet")) { + tstyle = `${tstyle} word-break: break-all;` + text = text.replace(/:((?:http|magnet).+?)(?:\s|$)/g, ":$1") + } + tbody = `${tbody} + + ${time} + ${source} + ${text} + `; } - if ($("#modal-logging").is(":hidden") && flag) { - stop_logging(); - } - if (refresh_logging_flag === true) { - refresh_logging_timer = window.setTimeout("refresh_logging(true)", 2000); + let logging_table_obj = $("#logging_table"); + let bool_ToScrolTop = (logging_table_obj.scrollTop() + logging_table_obj.prop("offsetHeight")) >= logging_table_obj.prop("scrollHeight"); + $("#logging_content").append(tbody); + if (bool_ToScrolTop) { + setTimeout(function () { + logging_table_obj.scrollTop(logging_table_obj.prop("scrollHeight")); + }, 500); } - }, true, false); + } + if ($("#modal-logging").is(":hidden")) { + RefreshLoggingFlag = false; + } + if (RefreshLoggingFlag) { + window.setTimeout("start_logging()", 1000); + } } //暂停实时日志 function pause_logging() { - if (refresh_logging_flag === true) { + let btn = $("#logging_stop_btn") + if (btn.text() === "暂停") { + btn.text("开始") stop_logging(); - $("#logging_stop_btn").text("开始") } else { + btn.text("暂停") start_logging(); - $("#logging_stop_btn").text("暂停") } } -//实时日志关闭 -$("#logging_close_btn").unbind("click").click(function () { - stop_logging(); - // 清空日志 - $("#logging_content").html("") - logger_source = "" -}); - -$("#logging_close_head").unbind("click").click(function () { - stop_logging(); - // 清空日志 - $("#logging_content").html("") - logger_source = "" -}); - // 显示实时日志 function show_logging_modal() { - start_logging(); + $("#logging_stop_btn").text("暂停"); $('#modal-logging').modal('show'); + start_logging(); } // 渲染日志来源下拉列表 @@ -234,46 +223,43 @@ function get_logging_source() { // 日志来源筛选 function logger_select(source) { - logger_source = source - if (logger_source === "All") logger_source = ""; - - // 关闭之前的定时刷新日志 - clearTimeout(refresh_logging_timer) - - // 清空日志 + LoggingSource = source + if (LoggingSource === "All"){ + LoggingSource = ""; + } $("#logging_content").html("") - - // 重新拉取日志 - refresh_logging() } //刷新消息中心 -function refresh_message(time_str) { - ajax_post("refresh_message", {"lst_time": time_str}, function (ret) { - if (ret.code === 0) { - let lst_time = ret.lst_time; - const msgs = ret.message; - for (let msg of msgs) { - let html_text = `
-
-
- -
-
- ${msg.title} -
${msg.content}
-
${msg.time}
-
-
-
`; - $("#system-messages").prepend(html_text); - if (time_str) { - browserNotification(msg.title, msg.content); - } - } - setTimeout("refresh_message('" + lst_time + "')", 10000); +function render_message(ret) { + let lst_time = ret.lst_time; + const msgs = ret.message; + for (let msg of msgs) { + let html_text = `
+
+
+ +
+
+ ${msg.title} +
${msg.content}
+
${msg.time}
+
+
+
`; + $("#system-messages").prepend(html_text); + if (!OldMessageFlag) { + browserNotification(msg.title, msg.content); + } else { + OldMessageFlag = false; } - }, true, false); + } + setTimeout("get_message('" + lst_time + "')", 3000); +} + +//发送拉取消息的请求 +function get_message(lst_time) { + MessageWS.send(JSON.stringify({"lst_time": lst_time})); } //检查系统是否在线 @@ -388,7 +374,7 @@ function switch_cooperation_sites(obj) { // 刷新进度条 function refresh_process(type) { - if (!refresh_process_flag) { + if (!RefreshProcessFlag) { return; } ajax_post("refresh_process", {type: type}, function (ret) { @@ -396,9 +382,9 @@ function refresh_process(type) { $("#modal_process_bar").attr("style", "width: " + ret.value + "%").attr("aria-valuenow", ret.value); $("#modal_process_text").text(ret.text); } else { - refresh_fail_count = refresh_fail_count + 1; + RefreshFailCount = RefreshFailCount + 1; } - if (refresh_fail_count < 5) { + if (RefreshFailCount < 5) { setTimeout("refresh_process('" + type + "')", 200); } }, true, false); @@ -415,14 +401,14 @@ function show_refresh_process(title, type) { $("#modal_process_text").text("请稍候..."); $("#modal-process").modal("show"); //刷新进度 - refresh_process_flag = true; - refresh_fail_count = 0; + RefreshProcessFlag = true; + RefreshFailCount = 0; refresh_process(type); } // 关闭全局进度框 function hide_refresh_process() { - refresh_process_flag = false; + RefreshProcessFlag = false; $("#modal-process").modal("hide"); } @@ -744,14 +730,14 @@ function add_rss_manual(flag) { } //订阅站点 let rss_sites = select_GetSelectedVAL("rss_sites"); - if (rss_sites.length === RSS_SITES_LENGTH) { + if (rss_sites.length === RssSitesLength) { rss_sites = []; } //搜索站点 let search_sites = []; if (!fuzzy_match) { search_sites = select_GetSelectedVAL("search_sites"); - if (search_sites.length === SEARCH_SITES_LENGTH) { + if (search_sites.length === SearchSitesLength) { search_sites = []; } } @@ -960,8 +946,8 @@ function save_default_rss_setting() { const rss_sites = select_GetSelectedVAL("default_rss_sites"); const search_sites = select_GetSelectedVAL("default_search_sites"); const sites = { - rss_sites: (rss_sites.length === RSS_SITES_LENGTH) ? [] : rss_sites, - search_sites: (search_sites.length === SEARCH_SITES_LENGTH) ? [] : search_sites + rss_sites: (rss_sites.length === RssSitesLength) ? [] : rss_sites, + search_sites: (search_sites.length === SearchSitesLength) ? [] : search_sites }; const common = input_select_GetVal("modal-default-rss-setting", "default_rss_setting_"); const key = common.mtype === "MOV" ? "DefaultRssSettingMOV" : "DefaultRssSettingTV"; @@ -1094,7 +1080,7 @@ function refresh_rsssites_select(obj_id, item_name, aync = true) { if (ret.code === 0) { let rsssites_select = $(`#${obj_id}`); let rsssites_select_content = ""; - RSS_SITES_LENGTH = ret.sites.length; + RssSitesLength = ret.sites.length; if (ret.sites.length > 0) { rsssites_select.parent().parent().show(); } else { @@ -1118,7 +1104,7 @@ function refresh_searchsites_select(obj_id, item_name, aync = true) { if (ret.code === 0) { let searchsites_select = $(`#${obj_id}`); let searchsites_select_content = ""; - SEARCH_SITES_LENGTH = ret.indexers.length; + SearchSitesLength = ret.indexers.length; if (ret.indexers.length > 0) { searchsites_select.parent().parent().show(); } else { @@ -1275,7 +1261,7 @@ function show_download_modal(id, name, site = undefined, func = undefined, show_ $("#search_download_btn").unbind("click").click(download_link); } // 清空 - torrent_dropzone.removeAllFiles(); + TorrentDropZone.removeAllFiles(); $("#modal-search-download").modal('show'); } @@ -1468,7 +1454,7 @@ function media_name_test_ui(data, result_div) { function show_manual_transfer_modal(manual_type, inpath, syncmod, media_type, unknown_id, transferlog_id) { // 初始化类型 if (!syncmod) { - syncmod = default_transfer_mode; + syncmod = DefaultTransferMode; } let source = CURRENT_PAGE_URI; $("#rename_source").val(source); @@ -1481,7 +1467,7 @@ function show_manual_transfer_modal(manual_type, inpath, syncmod, media_type, un if (inpath) { $("#rename_inpath").val(inpath); } else { - $("#rename_inpath").val(default_path); + $("#rename_inpath").val(DefaultPath); } $("#rename_outpath").val(''); $("#rename_syncmod_customize").val(syncmod); diff --git a/web/templates/download/downloading.html b/web/templates/download/downloading.html index e059086c..9fbe17d4 100644 --- a/web/templates/download/downloading.html +++ b/web/templates/download/downloading.html @@ -172,12 +172,12 @@

function show_torrent_download_modal(type) { show_download_modal('', '', '', function(){ let urls = $("#torrent_urls").val(); - if ((!torrent_dropzone || !torrent_dropzone.files || !torrent_dropzone.files[0]) && !urls) { + if ((!TorrentDropZone || !TorrentDropZone.files || !TorrentDropZone.files[0]) && !urls) { return; } $("#search_download_btn").text("处理中...").attr("disabled", true); const params = { - "files": torrent_dropzone.files, + "files": TorrentDropZone.files, "urls": urls.split("\n"), "dl_dir": get_savepath("search_download_dir", "search_download_dir_manual"), "dl_setting": $("#search_download_setting").val() @@ -186,7 +186,7 @@

$("#search_download_btn").attr("disabled", false).text("下载"); $("#modal-search-download").modal('hide'); if (ret.code == 0) { - torrent_dropzone.removeAllFiles(); + TorrentDropZone.removeAllFiles(); $("#torrent_urls").val(""); window_history_refresh(); } else { diff --git a/web/templates/index.html b/web/templates/index.html index 14f3df46..94b82342 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -505,7 +505,9 @@

$(document).ready(function () { // 响应大小调整 window.onresize = function () { - chart_statistics.resize(); + if(chart_statistics) { + chart_statistics.resize(); + } }; }); diff --git a/web/templates/navigation.html b/web/templates/navigation.html index ef5312e1..d1dbafe9 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -360,7 +360,7 @@ - +
@@ -376,10 +376,8 @@
@@ -1168,14 +1166,25 @@ }); // 全局变量默认值 - torrent_dropzone = new Dropzone("#torrent_files"); - torrent_dropzone.options.acceptedFiles = ".torrent"; - default_transfer_mode = '{{ SyncMod }}'; - default_path = "{{ DefaultPath }}"; + TorrentDropZone = new Dropzone("#torrent_files"); + TorrentDropZone.options.acceptedFiles = ".torrent"; + DefaultTransferMode = '{{ SyncMod }}'; + DefaultPath = "{{ DefaultPath }}"; {% if "系统设置" in CurrentUser.pris %} - // 刷新消息 - refresh_message(""); + // WebSocket刷新日志 + LoggingWS = new WebSocket('ws://' + window.location.host + '/logging'); + LoggingWS.onmessage = function (event) { + render_logging(JSON.parse(event.data)) + } + // WebSocket刷新消息 + MessageWS = new WebSocket('ws://' + window.location.host + '/message'); + MessageWS.onmessage = function (event) { + render_message(JSON.parse(event.data)) + } + MessageWS.onopen = function (event) { + get_message(''); + } {% endif %} {% if not TMDBFlag %} diff --git a/web/templates/rss/user_rss.html b/web/templates/rss/user_rss.html index b23dac35..f5a6ae52 100644 --- a/web/templates/rss/user_rss.html +++ b/web/templates/rss/user_rss.html @@ -735,8 +735,8 @@ let rss_sites = select_GetSelectedVAL('userrss_rss_rss_sites'); let search_sites = select_GetSelectedVAL('userrss_rss_search_sites'); userrss_detail.sites = { - rss_sites: (rss_sites.length === RSS_SITES_LENGTH) ? [] : rss_sites, - search_sites: (search_sites.length === SEARCH_SITES_LENGTH) ? [] : search_sites + rss_sites: (rss_sites.length === RssSitesLength) ? [] : rss_sites, + search_sites: (search_sites.length === SearchSitesLength) ? [] : search_sites } } else { return; diff --git a/web/templates/site/statistics.html b/web/templates/site/statistics.html index aa1aec58..f65b76c0 100644 --- a/web/templates/site/statistics.html +++ b/web/templates/site/statistics.html @@ -702,9 +702,15 @@ // 响应大小调整 window.onresize = function () { - pie_ul.resize(); - pie_dl.resize(); - chart_history.resize(); + if (pie_ul) { + pie_ul.resize(); + } + if (pie_dl) { + pie_dl.resize(); + } + if (chart_history) { + chart_history.resize(); + } }; function switch_date(date_offset){ From 84c4dad16bb8c37ab80feeabaf81b03cc7acd017 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 17:28:03 +0800 Subject: [PATCH 05/34] fix --- web/templates/navigation.html | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/web/templates/navigation.html b/web/templates/navigation.html index d1dbafe9..8e2917b3 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -1176,17 +1176,28 @@ LoggingWS = new WebSocket('ws://' + window.location.host + '/logging'); LoggingWS.onmessage = function (event) { render_logging(JSON.parse(event.data)) - } - // WebSocket刷新消息 - MessageWS = new WebSocket('ws://' + window.location.host + '/message'); - MessageWS.onmessage = function (event) { - render_message(JSON.parse(event.data)) - } - MessageWS.onopen = function (event) { - get_message(''); - } + }; + LoggingWS.onclose = function() { + console.log('Logging Connection closed'); + LoggingWS = new WebSocket('ws://' + window.location.host + '/logging'); + console.log('Logging Connection Reopen'); + }; {% endif %} + // WebSocket刷新消息 + MessageWS = new WebSocket('ws://' + window.location.host + '/message'); + MessageWS.onmessage = function (event) { + render_message(JSON.parse(event.data)) + }; + MessageWS.onclose = function() { + console.log('Message Connection closed'); + MessageWS = new WebSocket('ws://' + window.location.host + '/message'); + console.log('Message Connection Reopen'); + }; + MessageWS.onopen = function (event) { + get_message(''); + }; + {% if not TMDBFlag %} // 检查tmdb配置 show_init_alert_modal(); From 65fd7a03c3c780c9a4fc67786b1326a29c600a1f Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 17:41:45 +0800 Subject: [PATCH 06/34] fix --- web/templates/navigation.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web/templates/navigation.html b/web/templates/navigation.html index 8e2917b3..44e09014 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -1171,27 +1171,33 @@ DefaultTransferMode = '{{ SyncMod }}'; DefaultPath = "{{ DefaultPath }}"; + // 当前协议 + let protocol = "ws://"; + if (window.location.protocol === "https") { + protocol = "wss://" + } + {% if "系统设置" in CurrentUser.pris %} // WebSocket刷新日志 - LoggingWS = new WebSocket('ws://' + window.location.host + '/logging'); + LoggingWS = new WebSocket(protocol + window.location.host + '/logging'); LoggingWS.onmessage = function (event) { render_logging(JSON.parse(event.data)) }; LoggingWS.onclose = function() { console.log('Logging Connection closed'); - LoggingWS = new WebSocket('ws://' + window.location.host + '/logging'); + LoggingWS = new WebSocket(protocol + window.location.host + '/logging'); console.log('Logging Connection Reopen'); }; {% endif %} // WebSocket刷新消息 - MessageWS = new WebSocket('ws://' + window.location.host + '/message'); + MessageWS = new WebSocket(protocol + window.location.host + '/message'); MessageWS.onmessage = function (event) { render_message(JSON.parse(event.data)) }; MessageWS.onclose = function() { console.log('Message Connection closed'); - MessageWS = new WebSocket('ws://' + window.location.host + '/message'); + MessageWS = new WebSocket(protocol + window.location.host + '/message'); console.log('Message Connection Reopen'); }; MessageWS.onopen = function (event) { From 4d6081097474f1f19dbdab9c3479bb1fa518e39b Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 17:43:48 +0800 Subject: [PATCH 07/34] fix --- web/templates/navigation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/templates/navigation.html b/web/templates/navigation.html index 44e09014..0331f18f 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -1173,7 +1173,7 @@ // 当前协议 let protocol = "ws://"; - if (window.location.protocol === "https") { + if (window.location.protocol === "https:") { protocol = "wss://" } From c6ef38ebce1be180f5ab2e2b81bf3aec3b205f0b Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 17:49:22 +0800 Subject: [PATCH 08/34] fix --- web/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/main.py b/web/main.py index cb5c993e..13e72940 100644 --- a/web/main.py +++ b/web/main.py @@ -1672,6 +1672,9 @@ def logging_handler(ws): @Sock.route('/message') def message_handler(ws): + """ + 消息中心WebSocket + """ while True: try: data = ws.receive() From 35b2c2d2d9a8307155963d2d03dd2f5f1c9fa63e Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 18:02:28 +0800 Subject: [PATCH 09/34] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20functions.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/static/js/functions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/static/js/functions.js b/web/static/js/functions.js index 01b71cd3..cd098d01 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -209,7 +209,7 @@ function pause_logging() { function show_logging_modal() { $("#logging_stop_btn").text("暂停"); $('#modal-logging').modal('show'); - start_logging(); + setTimeout("start_logging()", 1000); } // 渲染日志来源下拉列表 From 9a8e26e702b8515e8c07c7976b3e3eacf43a6379 Mon Sep 17 00:00:00 2001 From: sowevo Date: Wed, 26 Apr 2023 18:11:14 +0800 Subject: [PATCH 10/34] =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mediaserver/client/plex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mediaserver/client/plex.py b/app/mediaserver/client/plex.py index f2017819..ced0346a 100644 --- a/app/mediaserver/client/plex.py +++ b/app/mediaserver/client/plex.py @@ -533,7 +533,7 @@ def get_latest(self, num=20): link = self.get_play_url(item.key) title = item.title if item_type == MediaType.MOVIE.value else \ "%s 第%s季" % (item.parentTitle, item.index) - image = self.get_nt_image_url(item.artUrl) + image = self.get_nt_image_url(item.posterUrl) ret_resume.append({ "id": item.key, "name": title, From d3f999b4554096dddc063bdb46b922d19b6b9719 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 19:40:47 +0800 Subject: [PATCH 11/34] fix --- web/static/js/functions.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/web/static/js/functions.js b/web/static/js/functions.js index cd098d01..7cdd6f18 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -124,18 +124,11 @@ function hide_wait_modal() { $("#modal-wait").modal("hide"); } - //发送刷新日志请求 -function start_logging() { - RefreshLoggingFlag = true; +function get_logging() { LoggingWS.send(JSON.stringify({"source": LoggingSource})); } -//停止刷新日志 -function stop_logging() { - RefreshLoggingFlag = false; -} - //刷新日志 function render_logging(log_list) { if (log_list) { @@ -189,7 +182,7 @@ function render_logging(log_list) { RefreshLoggingFlag = false; } if (RefreshLoggingFlag) { - window.setTimeout("start_logging()", 1000); + window.setTimeout("get_logging()", 1000); } } @@ -198,10 +191,11 @@ function pause_logging() { let btn = $("#logging_stop_btn") if (btn.text() === "暂停") { btn.text("开始") - stop_logging(); + RefreshLoggingFlag = false; } else { - btn.text("暂停") - start_logging(); + btn.text("暂停"); + RefreshLoggingFlag = true; + get_logging(); } } @@ -209,7 +203,8 @@ function pause_logging() { function show_logging_modal() { $("#logging_stop_btn").text("暂停"); $('#modal-logging').modal('show'); - setTimeout("start_logging()", 1000); + RefreshLoggingFlag = true; + setTimeout("get_logging()", 1000); } // 渲染日志来源下拉列表 From 72a0c48b8f98bc29d2c48116f5a0c4b60bd3746b Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 20:51:33 +0800 Subject: [PATCH 12/34] =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BA=A4=E4=BA=92=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/message/message.py | 2 ++ app/plugins/modules/autosignin.py | 2 +- web/main.py | 47 +++++++++++++++++++------------ web/static/js/functions.js | 31 ++++++++++++++++---- web/static/js/util.js | 22 +++++++++++++++ web/templates/navigation.html | 13 +++++---- 6 files changed, 88 insertions(+), 29 deletions(-) diff --git a/app/message/message.py b/app/message/message.py index 8f7a9503..63fda9ec 100644 --- a/app/message/message.py +++ b/app/message/message.py @@ -159,6 +159,8 @@ def send_channel_msg(self, channel, title, text="", image="", url="", user_id="" """ # 插入消息中心 self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + if channel == SearchType.WEB: + return True # 发送消息 client = self._active_interactive_clients.get(channel) if client: diff --git a/app/plugins/modules/autosignin.py b/app/plugins/modules/autosignin.py index 1c3b8a49..b8a8787d 100644 --- a/app/plugins/modules/autosignin.py +++ b/app/plugins/modules/autosignin.py @@ -253,7 +253,7 @@ def get_command(): } @EventHandler.register(EventType.SiteSignin) - def sign_in(self): + def sign_in(self, event=None): """ 自动签到 """ diff --git a/web/main.py b/web/main.py index 13e72940..6876da2a 100644 --- a/web/main.py +++ b/web/main.py @@ -1678,28 +1678,39 @@ def message_handler(ws): while True: try: data = ws.receive() - system_msg = WebAction().get_system_message(lst_time=json.loads(data).get("lst_time")) - messages = system_msg.get("message") - lst_time = system_msg.get("lst_time") - ret_messages = [] - for message in list(reversed(messages)): - content = re.sub(r"#+", "
", - re.sub(r"<[^>]+>", "", - re.sub(r"
", "####", message.get("content"), flags=re.IGNORECASE))) - ret_messages.append({ - "level": "bg-red" if message.get("level") == "ERROR" else "", - "title": message.get("title"), - "content": content, - "time": message.get("time") - }) - ws.send((json.dumps({ - "lst_time": lst_time, - "message": ret_messages - }))) + msgbody = json.loads(data) + if msgbody.get("text"): + # 发送的消息 + WebAction().handle_message_job(msg=msgbody.get("text"), + in_from=SearchType.WEB, + user_id=current_user.username, + user_name=current_user.username) + ws.send((json.dumps({}))) + else: + # 拉取消息 + system_msg = WebAction().get_system_message(lst_time=msgbody.get("lst_time")) + messages = system_msg.get("message") + lst_time = system_msg.get("lst_time") + ret_messages = [] + for message in list(reversed(messages)): + content = re.sub(r"#+", "
", + re.sub(r"<[^>]+>", "", + re.sub(r"
", "####", message.get("content"), flags=re.IGNORECASE))) + ret_messages.append({ + "level": "bg-red" if message.get("level") == "ERROR" else "", + "title": message.get("title"), + "content": content, + "time": message.get("time") + }) + ws.send((json.dumps({ + "lst_time": lst_time, + "message": ret_messages + }))) except Exception as err: print(str(err)) break + # base64模板过滤器 @App.template_filter('b64encode') def b64encode(s): diff --git a/web/static/js/functions.js b/web/static/js/functions.js index 7cdd6f18..5b4a2c97 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -219,7 +219,7 @@ function get_logging_source() { // 日志来源筛选 function logger_select(source) { LoggingSource = source - if (LoggingSource === "All"){ + if (LoggingSource === "All") { LoggingSource = ""; } $("#logging_content").html("") @@ -233,11 +233,11 @@ function render_message(ret) { let html_text = `
- + N
${msg.title} -
${msg.content}
+
${msg.content}
${msg.time}
@@ -1160,7 +1160,7 @@ function refresh_savepath_select(obj_id, aync = true, sid = "", is_default = fal } // 切换手动输入 -function check_manual_input_path(select_id, input_id, manual_path=null) { +function check_manual_input_path(select_id, input_id, manual_path = null) { let savepath_select = $(`#${select_id}`); let savepath_input_manual = $(`#${input_id}`); if (manual_path !== null) { @@ -1438,7 +1438,7 @@ function media_name_test_ui(data, result_div) { $(`#${result_div}`).empty(); const sort_array = ["org_string", "ignored_words", "replaced_words", "offset_words", "type", "category", "name", "title", "tmdbid", "year", "season_episode", "part", - "restype", "effect", "pix", "video_codec", "audio_codec", "team","customization"] + "restype", "effect", "pix", "video_codec", "audio_codec", "team", "customization"] // 调用组件实例的自定义方法.. 一次性添加chips document.querySelector(`#${result_div}`).add_chips_all(sort_array, data); } @@ -1707,3 +1707,24 @@ function search_tmdbid_by_name(keyid, resultid) { } }); } + +//WEB页面发送消息 +function send_web_message(obj) { + let text = $(obj).val(); + if (!text) { + return + } + $(obj).val(""); + MessageWS.send(JSON.stringify({"text": text})); + $("#system-messages").prepend(`
+
+
+ ${text} +
${new Date().format("yyyy-MM-dd hh:mm:ss")}
+
+
+ Y +
+
+
`); +} diff --git a/web/static/js/util.js b/web/static/js/util.js index 4a9db5aa..ae1c93b8 100644 --- a/web/static/js/util.js +++ b/web/static/js/util.js @@ -6,6 +6,27 @@ String.prototype.replaceAll = function (s1, s2) { return this.replace(new RegExp(s1, "gm"), s2) } +// 日期时间 +Date.prototype.format = function (format) { + var o = { + "M+": this.getMonth() + 1, //month + "d+": this.getDate(), //day + "h+": this.getHours(), //hour + "m+": this.getMinutes(), //minute + "s+": this.getSeconds(), //second + "q+": Math.floor((this.getMonth() + 3) / 3), //quarter + "S": this.getMilliseconds() //millisecond + } + if (/(y+)/.test(format)) format = format.replace(RegExp.$1, + (this.getFullYear() + "").substr(4 - RegExp.$1.length)); + for (const k in o) + if (new RegExp("(" + k + ")").test(format)) + format = format.replace(RegExp.$1, + RegExp.$1.length == 1 ? o[k] : + ("00" + o[k]).substr(("" + o[k]).length)); + return format; +} + // Ajax主方法 function ajax_post(cmd, params, handler, aync = true, show_progress = true) { if (show_progress) { @@ -384,6 +405,7 @@ function window_history_refresh() { //当前页面地址 let CURRENT_PAGE_URI = ""; + // 保存页面历史 function window_history(newflag = false, extra = undefined) { const state = { diff --git a/web/templates/navigation.html b/web/templates/navigation.html index 0331f18f..93ed5327 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -108,13 +108,15 @@

消息中心

-
+
-
- +
+
+ + +
@@ -372,6 +374,7 @@ + 刷新中...
From 40bf25b2d83fa569093c9505fb11c2c62a352efb Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 21:08:59 +0800 Subject: [PATCH 13/34] =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BA=A4=E4=BA=92=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/templates/navigation.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/templates/navigation.html b/web/templates/navigation.html index 93ed5327..4fc73d57 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -108,10 +108,10 @@

消息中心

-
+
-
+
From c08f6b502a67b1fac677c67236ee4bee8e17f657 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 21:32:17 +0800 Subject: [PATCH 14/34] fix --- web/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/main.py b/web/main.py index 6876da2a..702aaa7a 100644 --- a/web/main.py +++ b/web/main.py @@ -1649,6 +1649,7 @@ def Img(): @Sock.route('/logging') +@login_required def logging_handler(ws): """ 实时日志WebSocket @@ -1671,6 +1672,7 @@ def logging_handler(ws): @Sock.route('/message') +@login_required def message_handler(ws): """ 消息中心WebSocket From 0a56b0d0806c286749e532de2bab64c2f2961bb2 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 26 Apr 2023 21:41:41 +0800 Subject: [PATCH 15/34] fix --- app/message/message.py | 34 +++++++++++++++++----------------- app/message/message_center.py | 17 ++++------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/app/message/message.py b/app/message/message.py index 63fda9ec..d4e9a2d1 100644 --- a/app/message/message.py +++ b/app/message/message.py @@ -158,7 +158,7 @@ def send_channel_msg(self, channel, title, text="", image="", url="", user_id="" :return: 发送状态、错误信息 """ # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) if channel == SearchType.WEB: return True # 发送消息 @@ -251,7 +251,7 @@ def send_download_message(self, in_from: SearchType, can_item, download_setting_ can_item.description = re.sub(r'<[^>]+>', '', description) msg_text = f"{msg_text}\n描述:{can_item.description}" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=msg_title, content=msg_text) + self.messagecenter.insert_system_message(title=msg_title, content=msg_text) # 发送消息 for client in self._active_clients: if "download_start" in client.get("switchs"): @@ -286,7 +286,7 @@ def send_transfer_movie_message(self, in_from: Enum, media_info, exist_filenum, if exist_filenum != 0: msg_str = f"{msg_str},{exist_filenum}个文件已存在" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=msg_title, content=msg_str) + self.messagecenter.insert_system_message(title=msg_title, content=msg_str) # 发送消息 for client in self._active_clients: if "transfer_finished" in client.get("switchs"): @@ -318,7 +318,7 @@ def send_transfer_tv_message(self, message_medias: dict, in_from: Enum): else: msg_str = f"{msg_str},总大小:{StringUtils.str_filesize(item_info.size)},来自:{in_from.value}" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=msg_title, content=msg_str) + self.messagecenter.insert_system_message(title=msg_title, content=msg_str) # 发送消息 for client in self._active_clients: if "transfer_finished" in client.get("switchs"): @@ -336,7 +336,7 @@ def send_download_fail_message(self, item, error_msg): title = "添加下载任务失败:%s %s" % (item.get_title_string(), item.get_season_episode_string()) text = f"站点:{item.site}\n种子名称:{item.org_string}\n种子链接:{item.enclosure}\n错误信息:{error_msg}" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "download_fail" in client.get("switchs"): @@ -362,7 +362,7 @@ def send_rss_success_message(self, in_from: Enum, media_info): if media_info.user_name: msg_str = f"{msg_str},用户:{media_info.user_name}" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=msg_title, content=msg_str) + self.messagecenter.insert_system_message(title=msg_title, content=msg_str) # 发送消息 for client in self._active_clients: if "rss_added" in client.get("switchs"): @@ -389,7 +389,7 @@ def send_rss_finished_message(self, media_info): if media_info.vote_average: msg_str = f"{msg_str},{media_info.get_vote_string()}" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=msg_title, content=msg_str) + self.messagecenter.insert_system_message(title=msg_title, content=msg_str) # 发送消息 for client in self._active_clients: if "rss_finished" in client.get("switchs"): @@ -410,7 +410,7 @@ def send_site_signin_message(self, msgs: list): title = "站点签到" text = "\n".join(msgs) # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "site_signin" in client.get("switchs"): @@ -429,7 +429,7 @@ def send_site_message(self, title=None, text=None): if not text: text = "" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "site_message" in client.get("switchs"): @@ -448,7 +448,7 @@ def send_transfer_fail_message(self, path, count, text): title = f"【{count} 个文件入库失败】" text = f"源路径:{path}\n原因:{text}" # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "transfer_fail" in client.get("switchs"): @@ -466,7 +466,7 @@ def send_auto_remove_torrents_message(self, title, text): if not title or not text: return # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "auto_remove_torrents" in client.get("switchs"): @@ -484,7 +484,7 @@ def send_brushtask_remove_message(self, title, text): if not title or not text: return # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "brushtask_remove" in client.get("switchs"): @@ -502,7 +502,7 @@ def send_brushtask_added_message(self, title, text): if not title or not text: return # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "brushtask_added" in client.get("switchs"): @@ -575,7 +575,7 @@ def send_mediaserver_message(self, event_info: dict, channel, image_url): # 插入消息中心 message_content = "\n".join(message_texts) - self.messagecenter.insert_system_message(level="INFO", title=message_title, content=message_content) + self.messagecenter.insert_system_message(title=message_title, content=message_content) # 发送消息 for client in self._active_clients: @@ -594,7 +594,7 @@ def send_plugin_message(self, title, text="", image=""): if not title: return # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "custom_message" in client.get("switchs"): @@ -614,7 +614,7 @@ def send_custom_message(self, clients, title, text="", image=""): if not clients: return # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if str(client.get("id")) in clients: @@ -660,7 +660,7 @@ def send_user_statistics_message(self, msgs: list): title = "站点数据统计" text = "\n".join(msgs) # 插入消息中心 - self.messagecenter.insert_system_message(level="INFO", title=title, content=text) + self.messagecenter.insert_system_message(title=title, content=text) # 发送消息 for client in self._active_clients: if "ptrefresh_date_message" in client.get("switchs"): diff --git a/app/message/message_center.py b/app/message/message_center.py index 80b477d8..e21a082b 100644 --- a/app/message/message_center.py +++ b/app/message/message_center.py @@ -13,30 +13,21 @@ class MessageCenter: def __init__(self): pass - def insert_system_message(self, level, title, content=None): + def insert_system_message(self, title, content=None): """ 新增系统消息 - :param level: 级别 :param title: 标题 :param content: 内容 """ - if not level or not title: - return - if not content and title.find(":") != -1: - strings = title.split(":") - if strings and len(strings) > 1: - title = strings[0] - content = strings[1] title = title.replace("\n", "
").strip() if title else "" content = content.replace("\n", "
").strip() if content else "" - self.__append_message_queue(level, title, content) + self.__append_message_queue(title, content) - def __append_message_queue(self, level, title, content): + def __append_message_queue(self, title, content): """ 将消息增加到队列 """ - self._message_queue.appendleft({"level": level, - "title": title, + self._message_queue.appendleft({"title": title, "content": content, "time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}) From 26ca6598115445d592ac45ddc45c79ba6ab377fd Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 26 Apr 2023 22:18:54 +0800 Subject: [PATCH 16/34] feature: add doulist douban api --- app/media/doubanapi/apiv2.py | 123 +++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/app/media/doubanapi/apiv2.py b/app/media/doubanapi/apiv2.py index 6a924135..4effe703 100644 --- a/app/media/doubanapi/apiv2.py +++ b/app/media/doubanapi/apiv2.py @@ -132,6 +132,10 @@ class DoubanApi(object): "music_interests": "/music/%s/interests", "music_reviews": "/music/%s/reviews", "music_recommendations": "/music/%s/recommendations", + + # doulist + "doulist": "/doulist/", + "doulist_items": "/doulist/%s/items", } _user_agents = [ @@ -239,3 +243,122 @@ def tv_chinese_best_weekly(self, start=0, count=20, ts=datetime.strftime(datetim def tv_global_best_weekly(self, start=0, count=20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): return self.__invoke(self._urls["tv_global_best_weekly"], start=start, count=count, _ts=ts) + + def doulist_detail(self, subject_id): + """ + 豆列详情 + :param subject_id: 豆列id + :return: + { + "is_follow": false, + "screenshot_title": "分享海报", + "playable_count": 1226, + "screenshot_url": "douban:\/\/partial.douban.com\/screenshot\/doulist\/13712178\/_content", + "create_time": "2014-10-05 10:41:22", + "owner": { + "kind": "user", + "name": "依然饭特稀", + "url": "https:\/\/www.douban.com\/people\/56698183\/", + "uri": "douban:\/\/douban.com\/user\/56698183", + "avatar": "https://img2.doubanio.com\/icon\/up56698183-12.jpg", + "is_club": false, + "type": "user", + "id": "56698183", + "uid": "yrftx" + }, + "screenshot_type": "rexxar", + "id": "13712178", + "category": "movie", + "is_merged_cover": false, + "title": "评价人数超过十万的电影", + "is_subject_selection": false, + "followers_count": 53081, + "is_private": false, + "item_abstracts": [], + "type": "doulist", + "update_time": "2023-04-22 22:19:48", + "list_type": "ugc_doulist", + "tags": [], + "syncing_note": null, + "cover_url": "https://img9.doubanio.com\/view\/elanor_image\/raw\/public\/91314905.jpg", + "header_bg_image": "", + "doulist_type": "", + "done_count": 0, + "desc": "谢谢大家的关注和点赞,不过我更希望大家能在留言板上补充遗漏。\r\n看腻了豆瓣的评分排序,不如试试评价人数排序。评价人数并不代表作品的优劣,但是它起码说明了作品的存在感。这不一定是选电影最好的方法,却一定是选电影风险最小的方法。\r\n欢迎关注我关于读书的两个豆列: \r\n豆瓣评价人数超过一万的外文书籍 \r\nhttp:\/\/www.douban.com\/doulist\/37912871\/ \r\n豆瓣评价人数超过一万的中文书籍\r\nhttp:\/\/www.douban.com\/doulist\/36708212\/", + "items_count": 1453, + "wechat_timeline_share": "url", + "url": "https:\/\/www.douban.com\/doulist\/13712178\/", + "is_sys_private": false, + "uri": "douban:\/\/douban.com\/doulist\/13712178", + "sharing_url": "https:\/\/www.douban.com\/doulist\/13712178\/" + } + """ + return self.__invoke(self._urls["doulist"] + subject_id) + + def doulist_items(self, subject_id, start=0, count=20, ts=datetime.strftime(datetime.now(), '%Y%m%d')): + """ + 豆列列表 + :param subject_id: 豆列id + :param start: 开始 + :param count: 数量 + :param ts: 时间戳 + :return: + { + "count": 3, + "start": 0, + "total": 1453, + "items": [{ + "comment": "", + "rating": { + "count": 2834097, + "max": 10, + "star_count": 5.0, + "value": 9.7 + }, + "subtitle": "1994 \/ 美国 \/ 剧情 犯罪 \/ 弗兰克·德拉邦特 \/ 蒂姆·罗宾斯 摩根·弗里曼", + "title": "肖申克的救赎", + "url": "https:\/\/movie.douban.com\/subject\/1292052\/", + "target_id": "1292052", + "uri": "douban:\/\/douban.com\/movie\/1292052", + "cover_url": "https:\/\/qnmob3.doubanio.com\/view\/photo\/m_ratio_poster\/public\/p480747492.jpg?imageView2\/2\/q\/80\/w\/300\/h\/300\/format\/jpg", + "create_time": "2014-10-05 10:41:51", + "type": "movie", + "id": "19877287" + }, { + "comment": "", + "rating": { + "count": 2255839, + "max": 10, + "star_count": 4.5, + "value": 9.4 + }, + "subtitle": "1994 \/ 法国 美国 \/ 剧情 动作 犯罪 \/ 吕克·贝松 \/ 让·雷诺 娜塔莉·波特曼", + "title": "这个杀手不太冷", + "url": "https:\/\/movie.douban.com\/subject\/1295644\/", + "target_id": "1295644", + "uri": "douban:\/\/douban.com\/movie\/1295644", + "cover_url": "https:\/\/qnmob3.doubanio.com\/view\/photo\/m_ratio_poster\/public\/p511118051.jpg?imageView2\/2\/q\/80\/w\/300\/h\/300\/format\/jpg", + "create_time": "2014-10-05 10:42:34", + "type": "movie", + "id": "19877286" + }, { + "comment": "", + "rating": { + "count": 2198702, + "max": 10, + "star_count": 4.5, + "value": 9.4 + }, + "subtitle": "2001 \/ 日本 \/ 剧情 动画 奇幻 \/ 宫崎骏 \/ 柊瑠美 入野自由", + "title": "千与千寻", + "url": "https:\/\/movie.douban.com\/subject\/1291561\/", + "target_id": "1291561", + "uri": "douban:\/\/douban.com\/movie\/1291561", + "cover_url": "https:\/\/qnmob3.doubanio.com\/view\/photo\/m_ratio_poster\/public\/p2557573348.jpg?imageView2\/2\/q\/80\/w\/300\/h\/300\/format\/jpg", + "create_time": "2014-10-05 10:47:12", + "type": "movie", + "id": "19877280" + }] + } + """ + return self.__invoke(self._urls["doulist_items"] % subject_id, start=start, count=count, _ts=ts) From 49d1db14019157c3b167faea8d5ac573f0146be6 Mon Sep 17 00:00:00 2001 From: sowevo Date: Thu, 27 Apr 2023 03:34:16 +0800 Subject: [PATCH 17/34] =?UTF-8?q?plex=E6=8C=89=E8=B7=AF=E5=BE=84=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E5=AA=92=E4=BD=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mediaserver/client/plex.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/app/mediaserver/client/plex.py b/app/mediaserver/client/plex.py index ced0346a..32a01317 100644 --- a/app/mediaserver/client/plex.py +++ b/app/mediaserver/client/plex.py @@ -1,3 +1,4 @@ +import os from functools import lru_cache import log @@ -291,11 +292,36 @@ def refresh_root_library(self): def refresh_library_by_items(self, items): """ - 按类型、名称、年份来刷新媒体库,未找到对应的API,直接刷整个库 + 按路径刷新媒体库 """ if not self._plex: return False - return self._plex.library.update() + # _libraries可能未初始化,初始化一下 + if not self._libraries: + try: + self._libraries = self._plex.library.sections() + except Exception as err: + ExceptionUtils.exception_traceback(err) + for item in items: + target_path = item.get("target_path") + librarie = self.__find_librarie(target_path, self._libraries) + if librarie: + log.info(f"【{self.client_name}】刷新媒体库:{librarie.key} : {target_path}") + librarie.update(path=target_path) + + @staticmethod + def __find_librarie(path, libraries): + """ + 判断这个path属于哪个媒体库 + """ + try: + for librarie in libraries: + librarie_path = librarie.locations[0] + if os.path.commonprefix([path, librarie_path]) == librarie_path: + return librarie + except Exception as err: + ExceptionUtils.exception_traceback(err) + return None def get_libraries(self): """ From 0c903dcc8e6373859aec1df966aa7c5a55f2bc45 Mon Sep 17 00:00:00 2001 From: sowevo Date: Thu, 27 Apr 2023 03:58:20 +0800 Subject: [PATCH 18/34] ? --- app/plugins/modules/libraryrefresh.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/plugins/modules/libraryrefresh.py b/app/plugins/modules/libraryrefresh.py index fa1d7460..fac29dc4 100644 --- a/app/plugins/modules/libraryrefresh.py +++ b/app/plugins/modules/libraryrefresh.py @@ -127,6 +127,7 @@ def __refresh_library(self, event_data): "year": year, "type": media_info.get("type"), "category": media_info.get("category"), + # 这里不应该是 event_data.get("target_path")么 "target_path": event_data.get("dest") }]) else: From fd87f1fbc28192550186c9c8f76cb0d10c6ea9f2 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 07:03:06 +0800 Subject: [PATCH 19/34] fix --- app/message/message.py | 10 +++++++++- web/static/js/functions.js | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/message/message.py b/app/message/message.py index d4e9a2d1..44b11b39 100644 --- a/app/message/message.py +++ b/app/message/message.py @@ -158,8 +158,8 @@ def send_channel_msg(self, channel, title, text="", image="", url="", user_id="" :return: 发送状态、错误信息 """ # 插入消息中心 - self.messagecenter.insert_system_message(title=title, content=text) if channel == SearchType.WEB: + self.messagecenter.insert_system_message(title=title, content=text) return True # 发送消息 client = self._active_interactive_clients.get(channel) @@ -198,6 +198,14 @@ def send_channel_list_msg(self, channel, title, medias: list, user_id=""): :param user_id: 用户ID,如有则只发给这个用户 :return: 发送状态、错误信息 """ + if channel == SearchType.WEB: + texts = [] + index = 1 + for media in medias: + texts.append(f"{index}. {media.get_title_string()},{media.get_vote_string()}") + index += 1 + self.messagecenter.insert_system_message(title=title, content="\n".join(texts)) + return True client = self._active_interactive_clients.get(channel) if client: state = self.__send_list_msg(client=client, diff --git a/web/static/js/functions.js b/web/static/js/functions.js index 5b4a2c97..ed757333 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -243,6 +243,7 @@ function render_message(ret) {
`; $("#system-messages").prepend(html_text); + $(".offcanvas-body").animate({scrollTop:0}, 300); if (!OldMessageFlag) { browserNotification(msg.title, msg.content); } else { @@ -1727,4 +1728,5 @@ function send_web_message(obj) {
`); + $(".offcanvas-body").animate({scrollTop:0}, 300); } From 8affa36df5471b994099cf554a0b540995063883 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 07:08:02 +0800 Subject: [PATCH 20/34] fix --- web/static/js/functions.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/static/js/functions.js b/web/static/js/functions.js index ed757333..fd89ca49 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -233,7 +233,7 @@ function render_message(ret) { let html_text = `
- N + NT
${msg.title} @@ -1724,7 +1724,13 @@ function send_web_message(obj) {
${new Date().format("yyyy-MM-dd hh:mm:ss")}
- Y + + + + + + +
`); From 03223ce9d831632968f90fb6a47265d0f4742bbb Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 07:12:50 +0800 Subject: [PATCH 21/34] fix --- web/static/js/functions.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/web/static/js/functions.js b/web/static/js/functions.js index fd89ca49..d65261a0 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -230,6 +230,7 @@ function render_message(ret) { let lst_time = ret.lst_time; const msgs = ret.message; for (let msg of msgs) { + // 消息UI let html_text = `
@@ -243,13 +244,16 @@ function render_message(ret) {
`; $("#system-messages").prepend(html_text); + // 滚动到顶部 $(".offcanvas-body").animate({scrollTop:0}, 300); - if (!OldMessageFlag) { + // 浏览器消息提醒 + if (!OldMessageFlag && !$("#offcanvasEnd").is(":hidden")) { browserNotification(msg.title, msg.content); - } else { - OldMessageFlag = false; } } + // 非旧消息 + OldMessageFlag = false; + // 下一次处理 setTimeout("get_message('" + lst_time + "')", 3000); } @@ -1715,8 +1719,11 @@ function send_web_message(obj) { if (!text) { return } + // 清空输入框 $(obj).val(""); + // 消息交互 MessageWS.send(JSON.stringify({"text": text})); + // 显示自己发送的消息 $("#system-messages").prepend(`
@@ -1734,5 +1741,6 @@ function send_web_message(obj) {
`); + // 滚动到顶部 $(".offcanvas-body").animate({scrollTop:0}, 300); } From 863e10a5166ed428d117a468c6246c238ff20349 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 08:20:33 +0800 Subject: [PATCH 22/34] fix --- web/static/js/functions.js | 77 ++++++++++++++++++++++++++++++----- web/static/js/util.js | 10 ++--- web/templates/navigation.html | 49 +++++----------------- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/web/static/js/functions.js b/web/static/js/functions.js index d65261a0..00b12e40 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -30,6 +30,11 @@ let LoggingWS; let OldMessageFlag = true; // 消息WebSocket let MessageWS; +// 当前协议 +let WSProtocol = "ws://"; +if (window.location.protocol === "https:") { + WSProtocol = "wss://" +} /** @@ -83,11 +88,11 @@ function navmenu(page, newflag = false) { // 刷新filetree控件 init_filetree_element(); } - if (page !== CURRENT_PAGE_URI) { + if (page !== CurrentPageUri) { // 切换页面时滚动到顶部 $(window).scrollTop(0); // 记录当前页面ID - CURRENT_PAGE_URI = page; + CurrentPageUri = page; } // 并记录当前历史记录 window_history(!newflag); @@ -124,12 +129,30 @@ function hide_wait_modal() { $("#modal-wait").modal("hide"); } -//发送刷新日志请求 +// 连接日志服务 +function connect_logging() { + LoggingWS = new WebSocket(WSProtocol + window.location.host + '/logging'); + LoggingWS.onmessage = function (event) { + render_logging(JSON.parse(event.data)) + }; + LoggingWS.onclose = function(event) { + setTimeout(function() { + connect_logging(); + }, 2000); + }; + LoggingWS.onerror = function(event) { + setTimeout(function() { + connect_logging(); + }, 2000); + } +} + +// 发送刷新日志请求 function get_logging() { LoggingWS.send(JSON.stringify({"source": LoggingSource})); } -//刷新日志 +// 刷新日志 function render_logging(log_list) { if (log_list) { let tdstyle = "padding-top: 0.5rem; padding-bottom: 0.5rem"; @@ -186,7 +209,7 @@ function render_logging(log_list) { } } -//暂停实时日志 +// 暂停实时日志 function pause_logging() { let btn = $("#logging_stop_btn") if (btn.text() === "暂停") { @@ -201,8 +224,12 @@ function pause_logging() { // 显示实时日志 function show_logging_modal() { + // 显示窗口 $("#logging_stop_btn").text("暂停"); $('#modal-logging').modal('show'); + // 连接日志服务 + connect_logging(); + // 开始获取日志 RefreshLoggingFlag = true; setTimeout("get_logging()", 1000); } @@ -225,7 +252,29 @@ function logger_select(source) { $("#logging_content").html("") } -//刷新消息中心 +// 连接消息服务 +function connect_message() { + MessageWS = new WebSocket(WSProtocol + window.location.host + '/message'); + MessageWS.onmessage = function (event) { + render_message(JSON.parse(event.data)) + }; + MessageWS.onclose = function(event) { + setTimeout(function() { + connect_message(); + }, 2000); + }; + MessageWS.onerror = function(event) { + setTimeout(function() { + connect_message(); + }, 2000); + } + MessageWS.onopen = function (event) { + get_message(''); + }; + +} + +// 刷新消息中心 function render_message(ret) { let lst_time = ret.lst_time; const msgs = ret.message; @@ -778,7 +827,7 @@ function add_rss_manual(flag) { $("#modal-manual-rss").modal("hide"); ajax_post("add_rss_media", data, function (ret) { if (ret.code === 0) { - if (CURRENT_PAGE_URI.startsWith("tv_rss") || CURRENT_PAGE_URI.startsWith("movie_rss")) { + if (CurrentPageUri.startsWith("tv_rss") || CurrentPageUri.startsWith("movie_rss")) { window_history_refresh(); } else { show_rss_success_modal(ret.rssid, type, name + " 添加订阅成功!"); @@ -787,7 +836,7 @@ function add_rss_manual(flag) { show_add_rss_media_modal(mtype); } } else { - if (CURRENT_PAGE_URI.startsWith("tv_rss") || CURRENT_PAGE_URI.startsWith("movie_rss")) { + if (CurrentPageUri.startsWith("tv_rss") || CurrentPageUri.startsWith("movie_rss")) { show_fail_modal(`${ret.name} 订阅失败:${ret.msg}!`, function () { $("#modal-manual-rss").modal("show"); }); @@ -820,9 +869,9 @@ function change_over_edition_check(obj) { function remove_rss_manual(type, name, year, rssid) { $("#modal-manual-rss").modal('hide'); let page; - if (CURRENT_PAGE_URI.startsWith("tv_rss")) { + if (CurrentPageUri.startsWith("tv_rss")) { page = "tv_rss"; - } else if (CURRENT_PAGE_URI.startsWith("movie_rss")) { + } else if (CurrentPageUri.startsWith("movie_rss")) { page = "movie_rss"; } else { page = undefined @@ -1456,7 +1505,7 @@ function show_manual_transfer_modal(manual_type, inpath, syncmod, media_type, un if (!syncmod) { syncmod = DefaultTransferMode; } - let source = CURRENT_PAGE_URI; + let source = CurrentPageUri; $("#rename_source").val(source); $("#rename_manual_type").val(manual_type); if (manual_type === 3) { @@ -1744,3 +1793,9 @@ function send_web_message(obj) { // 滚动到顶部 $(".offcanvas-body").animate({scrollTop:0}, 300); } + +// 初始化DropZone +function init_dropzone() { + TorrentDropZone = new Dropzone("#torrent_files"); + TorrentDropZone.options.acceptedFiles = ".torrent"; +} diff --git a/web/static/js/util.js b/web/static/js/util.js index ae1c93b8..c39c25e9 100644 --- a/web/static/js/util.js +++ b/web/static/js/util.js @@ -8,7 +8,7 @@ String.prototype.replaceAll = function (s1, s2) { // 日期时间 Date.prototype.format = function (format) { - var o = { + const o = { "M+": this.getMonth() + 1, //month "d+": this.getDate(), //day "h+": this.getHours(), //hour @@ -16,13 +16,13 @@ Date.prototype.format = function (format) { "s+": this.getSeconds(), //second "q+": Math.floor((this.getMonth() + 3) / 3), //quarter "S": this.getMilliseconds() //millisecond - } + }; if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (const k in o) if (new RegExp("(" + k + ")").test(format)) format = format.replace(RegExp.$1, - RegExp.$1.length == 1 ? o[k] : + RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)); return format; } @@ -404,7 +404,7 @@ function window_history_refresh() { } //当前页面地址 -let CURRENT_PAGE_URI = ""; +let CurrentPageUri = ""; // 保存页面历史 function window_history(newflag = false, extra = undefined) { @@ -413,7 +413,7 @@ function window_history(newflag = false, extra = undefined) { html: $("#page_content").html(), // 页面内容 scroll: $(".page").scrollTop(), // 页面滚动位置 CurrentPage: sessionStorage.CurrentPage, // 页面当前页码 - page: CURRENT_PAGE_URI, // 当前页面地址 + page: CurrentPageUri, // 当前页面地址 extra: extra, // 额外的保存数据 }; if (newflag) { diff --git a/web/templates/navigation.html b/web/templates/navigation.html index 4fc73d57..97d70ecd 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -1124,8 +1124,7 @@
- - + @@ -1142,6 +1141,7 @@ // 页面初始事件 $(document).ready(function () { + // 监听页面状态 window.addEventListener('popstate', function(e) { if (e.state) { @@ -1149,7 +1149,7 @@ if (e.state.CurrentPage) { sessionStorage.CurrentPage = e.state.CurrentPage; } - CURRENT_PAGE_URI = e.state.page; + CurrentPageUri = e.state.page; document.querySelector("#navbar-menu").update_active(); $(window).unbind('scroll'); document.title = e.state.title; @@ -1159,9 +1159,6 @@ } }); - // TomSelect下拉框 - init_tomselect(); - // tooltip点击事件处理,阻止冒泡到上级元素,避免比如`点击tooltip切换了switch`的问题 $('body').on('click', 'span[data-bs-toggle="tooltip"]', function (e) { e.preventDefault(); @@ -1169,52 +1166,28 @@ }); // 全局变量默认值 - TorrentDropZone = new Dropzone("#torrent_files"); - TorrentDropZone.options.acceptedFiles = ".torrent"; DefaultTransferMode = '{{ SyncMod }}'; DefaultPath = "{{ DefaultPath }}"; - // 当前协议 - let protocol = "ws://"; - if (window.location.protocol === "https:") { - protocol = "wss://" - } + // TomSelect下拉框 + init_tomselect(); - {% if "系统设置" in CurrentUser.pris %} - // WebSocket刷新日志 - LoggingWS = new WebSocket(protocol + window.location.host + '/logging'); - LoggingWS.onmessage = function (event) { - render_logging(JSON.parse(event.data)) - }; - LoggingWS.onclose = function() { - console.log('Logging Connection closed'); - LoggingWS = new WebSocket(protocol + window.location.host + '/logging'); - console.log('Logging Connection Reopen'); - }; - {% endif %} + // Dropzone配置 + init_dropzone(); - // WebSocket刷新消息 - MessageWS = new WebSocket(protocol + window.location.host + '/message'); - MessageWS.onmessage = function (event) { - render_message(JSON.parse(event.data)) - }; - MessageWS.onclose = function() { - console.log('Message Connection closed'); - MessageWS = new WebSocket(protocol + window.location.host + '/message'); - console.log('Message Connection Reopen'); - }; - MessageWS.onopen = function (event) { - get_message(''); - }; + // WebSocket刷新消息中心 + connect_message(); {% if not TMDBFlag %} // 检查tmdb配置 show_init_alert_modal(); {% elif current_user.level == 1 %} + // 用户认证 show_user_auth_modal(); {% endif %} }); + From 64aa1b937f1a95fa26bafea914234e3ddcc3d25a Mon Sep 17 00:00:00 2001 From: thsrite Date: Thu, 27 Apr 2023 10:39:59 +0800 Subject: [PATCH 23/34] =?UTF-8?q?fix=20=E5=A4=A9=E7=A9=BA=E7=AD=BE?= =?UTF-8?q?=E5=88=B0=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/plugins/modules/_autosignin/hdsky.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/plugins/modules/_autosignin/hdsky.py b/app/plugins/modules/_autosignin/hdsky.py index aaec6ead..4eebb843 100644 --- a/app/plugins/modules/_autosignin/hdsky.py +++ b/app/plugins/modules/_autosignin/hdsky.py @@ -120,7 +120,7 @@ def signin(self, site_info: dict): elif str(json.loads(res.text)["message"]) == "invalid_imagehash": # 验证码错误 self.warn(f"签到失败:验证码错误") - return False, f'【{site}】{site}签到失败:验证码错误' + return False, f'【{site}】签到失败:验证码错误' self.error(f'签到失败:未获取到验证码') - return False, f'{site}签到失败:未获取到验证码' + return False, f'【{site}】签到失败:未获取到验证码' From 1428f2ac9803c7439e73b595cb1763a51a7b4949 Mon Sep 17 00:00:00 2001 From: sowevo Date: Thu, 27 Apr 2023 11:12:44 +0800 Subject: [PATCH 24/34] =?UTF-8?q?plex=E6=8C=89=E8=B7=AF=E5=BE=84=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E5=AA=92=E4=BD=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mediaserver/client/plex.py | 30 +++++++++++++++++---------- app/plugins/modules/libraryrefresh.py | 3 ++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/mediaserver/client/plex.py b/app/mediaserver/client/plex.py index 32a01317..8423e235 100644 --- a/app/mediaserver/client/plex.py +++ b/app/mediaserver/client/plex.py @@ -1,6 +1,6 @@ import os from functools import lru_cache - +from urllib.parse import quote_plus import log from app.mediaserver.client._base import _IMediaClient from app.utils import ExceptionUtils @@ -302,26 +302,34 @@ def refresh_library_by_items(self, items): self._libraries = self._plex.library.sections() except Exception as err: ExceptionUtils.exception_traceback(err) + result_dict = {} for item in items: - target_path = item.get("target_path") - librarie = self.__find_librarie(target_path, self._libraries) - if librarie: - log.info(f"【{self.client_name}】刷新媒体库:{librarie.key} : {target_path}") - librarie.update(path=target_path) + file_path = item.get("file_path") + lib_key, path = self.__find_librarie(file_path, self._libraries) + result_dict[path] = lib_key + if "" in result_dict: + # 如果有匹配失败的,刷新整个库 + self._plex.library.update() + for path, lib_key in result_dict.items(): + log.info(f"【{self.client_name}】刷新媒体库:{lib_key} : {path}") + self._plex.query(f'/library/sections/{lib_key}/refresh?path={quote_plus(path)}') @staticmethod def __find_librarie(path, libraries): """ 判断这个path属于哪个媒体库 + 多个媒体库配置的目录不应有重复和嵌套, + 使用os.path.commonprefix([path, location]) == location应该没问题 """ try: - for librarie in libraries: - librarie_path = librarie.locations[0] - if os.path.commonprefix([path, librarie_path]) == librarie_path: - return librarie + for lib in libraries: + if hasattr(lib, "locations") and lib.locations: + for location in lib.locations: + if os.path.commonprefix([path, location]) == location: + return lib.key, path except Exception as err: ExceptionUtils.exception_traceback(err) - return None + return "", "" def get_libraries(self): """ diff --git a/app/plugins/modules/libraryrefresh.py b/app/plugins/modules/libraryrefresh.py index fac29dc4..35bb52b8 100644 --- a/app/plugins/modules/libraryrefresh.py +++ b/app/plugins/modules/libraryrefresh.py @@ -128,7 +128,8 @@ def __refresh_library(self, event_data): "type": media_info.get("type"), "category": media_info.get("category"), # 这里不应该是 event_data.get("target_path")么 - "target_path": event_data.get("dest") + "target_path": event_data.get("dest"), + "file_path": event_data.get("target_path") }]) else: self.info(f"媒体服务器 {mediaserver_type} 刷新整库 ...") From 4e6ba92b9ea852294c52683e9467214873699433 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 11:43:47 +0800 Subject: [PATCH 25/34] =?UTF-8?q?WEB=E6=B6=88=E6=81=AF=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/action.py | 35 ++++++++++++++++++++++------------- web/main.py | 4 +++- web/static/js/functions.js | 13 +++++++++---- web/templates/navigation.html | 14 ++++++++++++-- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/web/action.py b/web/action.py index bbb73fa7..64e81f3e 100644 --- a/web/action.py +++ b/web/action.py @@ -50,8 +50,10 @@ class WebAction: _actions = {} + _commands = {} def __init__(self): + # WEB请求响应 self._actions = { "sch": self.__sch, "search": self.__search, @@ -231,6 +233,19 @@ def __init__(self): "iyuu_bind_site": self.iyuu_bind_site, "run_plugin_method": self.run_plugin_method, } + # 远程命令响应 + self._commands = { + "/ptr": {"func": TorrentRemover().auto_remove_torrents, "desc": "自动删种"}, + "/ptt": {"func": Downloader().transfer, "desc": "下载文件转移"}, + "/rst": {"func": Sync().transfer_sync, "desc": "目录同步"}, + "/rss": {"func": Rss().rssdownload, "desc": "电影/电视剧订阅"}, + "/ssa": {"func": Subscribe().subscribe_search_all, "desc": "订阅搜索"}, + "/tbl": {"func": self.truncate_blacklist, "desc": "清理转移缓存"}, + "/trh": {"func": self.truncate_rsshistory, "desc": "清理RSS缓存"}, + "/utf": {"func": self.unidentification, "desc": "重新识别"}, + "/udt": {"func": self.update_system, "desc": "系统更新"}, + "/sta": {"func": self.user_statistics, "desc": "站点数据统计"} + } def action(self, cmd, data=None): func = self._actions.get(cmd) @@ -337,18 +352,6 @@ def handle_message_job(self, msg, in_from=SearchType.OT, user_id=None, user_name """ if not msg: return - commands = { - "/ptr": {"func": TorrentRemover().auto_remove_torrents, "desc": "自动删种"}, - "/ptt": {"func": Downloader().transfer, "desc": "下载文件转移"}, - "/rst": {"func": Sync().transfer_sync, "desc": "目录同步"}, - "/rss": {"func": Rss().rssdownload, "desc": "电影/电视剧订阅"}, - "/ssa": {"func": Subscribe().subscribe_search_all, "desc": "订阅搜索"}, - "/tbl": {"func": self.truncate_blacklist, "desc": "清理转移缓存"}, - "/trh": {"func": self.truncate_rsshistory, "desc": "清理RSS缓存"}, - "/utf": {"func": self.unidentification, "desc": "重新识别"}, - "/udt": {"func": self.update_system, "desc": "系统更新"}, - "/sta": {"func": self.user_statistics, "desc": "站点数据统计"} - } # 触发MessageIncoming事件 EventManager().send_event(EventType.MessageIncoming, { @@ -360,7 +363,7 @@ def handle_message_job(self, msg, in_from=SearchType.OT, user_id=None, user_name }) # 系统内置命令 - command = commands.get(msg) + command = self._commands.get(msg) if command: # 启动服务 ThreadHelper().start_thread(command.get("func"), ()) @@ -5137,3 +5140,9 @@ def run_plugin_method(data): data.pop("method") result = PluginManager().run_plugin_method(pid=plugin_id, method=method, **data) return {"code": 0, "result": result} + + def get_commands(self): + """ + 获取命令列表 + """ + return [{"id": cid, "name": cmd.get("desc")} for cid, cmd in self._commands.items()] diff --git a/web/main.py b/web/main.py index 702aaa7a..842fd705 100644 --- a/web/main.py +++ b/web/main.py @@ -211,6 +211,7 @@ def web(): CustomScriptCfg = SystemConfig().get(SystemConfigKey.CustomScript) CooperationSites = current_user.get_authsites() Menus = WebAction().get_user_menus().get("menus") or [] + Commands = WebAction().get_commands() return render_template('navigation.html', GoPage=GoPage, CurrentUser=current_user, @@ -227,7 +228,8 @@ def web(): CustomScriptCfg=CustomScriptCfg, CooperationSites=CooperationSites, DefaultPath=DefaultPath, - Menus=Menus) + Menus=Menus, + Commands=Commands) # 开始 diff --git a/web/static/js/functions.js b/web/static/js/functions.js index 00b12e40..3a82b81b 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -1764,12 +1764,17 @@ function search_tmdbid_by_name(keyid, resultid) { //WEB页面发送消息 function send_web_message(obj) { - let text = $(obj).val(); - if (!text) { + if (!obj) { return } - // 清空输入框 - $(obj).val(""); + let text; + // 如果是jQuery对像 + if (obj instanceof jQuery) { + text = obj.val(); + obj.val(""); + } else { + text = obj; + } // 消息交互 MessageWS.send(JSON.stringify({"text": text})); // 显示自己发送的消息 diff --git a/web/templates/navigation.html b/web/templates/navigation.html index 97d70ecd..e5b3339d 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -113,9 +113,19 @@

消息中心

+ + - + onkeydown="if(event.keyCode==13) send_web_message($(this))"> +
From 5fa878b030beec10af3fab0429b617052b6a4a10 Mon Sep 17 00:00:00 2001 From: sowevo Date: Thu, 27 Apr 2023 11:54:55 +0800 Subject: [PATCH 26/34] =?UTF-8?q?plex=E6=8C=89=E8=B7=AF=E5=BE=84=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E5=AA=92=E4=BD=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mediaserver/client/plex.py | 8 ++++++-- app/plugins/modules/libraryrefresh.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/mediaserver/client/plex.py b/app/mediaserver/client/plex.py index 8423e235..55819988 100644 --- a/app/mediaserver/client/plex.py +++ b/app/mediaserver/client/plex.py @@ -321,12 +321,16 @@ def __find_librarie(path, libraries): 多个媒体库配置的目录不应有重复和嵌套, 使用os.path.commonprefix([path, location]) == location应该没问题 """ + if path is None: + return "", "" + # 只要路径,不要文件名 + dir_path = os.path.dirname(path) try: for lib in libraries: if hasattr(lib, "locations") and lib.locations: for location in lib.locations: - if os.path.commonprefix([path, location]) == location: - return lib.key, path + if os.path.commonprefix([dir_path, location]) == location: + return lib.key, dir_path except Exception as err: ExceptionUtils.exception_traceback(err) return "", "" diff --git a/app/plugins/modules/libraryrefresh.py b/app/plugins/modules/libraryrefresh.py index 35bb52b8..5a35b932 100644 --- a/app/plugins/modules/libraryrefresh.py +++ b/app/plugins/modules/libraryrefresh.py @@ -129,6 +129,7 @@ def __refresh_library(self, event_data): "category": media_info.get("category"), # 这里不应该是 event_data.get("target_path")么 "target_path": event_data.get("dest"), + # 这个媒体的转移后的最终路径,包含文件名 "file_path": event_data.get("target_path") }]) else: From f571e4ed73364093d439947d389c1d89c9a44b7d Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 12:20:21 +0800 Subject: [PATCH 27/34] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=99=A8=E7=89=88=E6=9C=AC=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/templates/setting/downloader.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/templates/setting/downloader.html b/web/templates/setting/downloader.html index 3ab2d463..1ba0c64b 100644 --- a/web/templates/setting/downloader.html +++ b/web/templates/setting/downloader.html @@ -117,7 +117,8 @@
- + {% for Id, Attr in DownloaderConf.items() %}
From 5d94c26a339352b60b3d1973500721b86104b987 Mon Sep 17 00:00:00 2001 From: sowevo Date: Thu, 27 Apr 2023 13:01:05 +0800 Subject: [PATCH 28/34] =?UTF-8?q?plex=E6=8C=89=E8=B7=AF=E5=BE=84=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E5=AA=92=E4=BD=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mediaserver/client/plex.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/mediaserver/client/plex.py b/app/mediaserver/client/plex.py index 55819988..008a2916 100644 --- a/app/mediaserver/client/plex.py +++ b/app/mediaserver/client/plex.py @@ -306,6 +306,7 @@ def refresh_library_by_items(self, items): for item in items: file_path = item.get("file_path") lib_key, path = self.__find_librarie(file_path, self._libraries) + # 如果存在同一剧集的多集,key(path)相同会合并 result_dict[path] = lib_key if "" in result_dict: # 如果有匹配失败的,刷新整个库 From 7fd4825d873d8fb643a6bbcb8a19324f1ef9daa4 Mon Sep 17 00:00:00 2001 From: sowevo Date: Thu, 27 Apr 2023 13:09:21 +0800 Subject: [PATCH 29/34] =?UTF-8?q?plex=E6=8C=89=E8=B7=AF=E5=BE=84=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E5=AA=92=E4=BD=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/mediaserver/client/plex.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/mediaserver/client/plex.py b/app/mediaserver/client/plex.py index 008a2916..dcd24cde 100644 --- a/app/mediaserver/client/plex.py +++ b/app/mediaserver/client/plex.py @@ -311,9 +311,11 @@ def refresh_library_by_items(self, items): if "" in result_dict: # 如果有匹配失败的,刷新整个库 self._plex.library.update() - for path, lib_key in result_dict.items(): - log.info(f"【{self.client_name}】刷新媒体库:{lib_key} : {path}") - self._plex.query(f'/library/sections/{lib_key}/refresh?path={quote_plus(path)}') + else: + # 否则一个一个刷新 + for path, lib_key in result_dict.items(): + log.info(f"【{self.client_name}】刷新媒体库:{lib_key} : {path}") + self._plex.query(f'/library/sections/{lib_key}/refresh?path={quote_plus(path)}') @staticmethod def __find_librarie(path, libraries): From a8201891e3be6d3457524034a933954efaacfce5 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 16:22:13 +0800 Subject: [PATCH 30/34] fix #4565 --- app/plugins/modules/libraryrefresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/plugins/modules/libraryrefresh.py b/app/plugins/modules/libraryrefresh.py index 5a35b932..ca7bd232 100644 --- a/app/plugins/modules/libraryrefresh.py +++ b/app/plugins/modules/libraryrefresh.py @@ -127,7 +127,7 @@ def __refresh_library(self, event_data): "year": year, "type": media_info.get("type"), "category": media_info.get("category"), - # 这里不应该是 event_data.get("target_path")么 + # 目的媒体库目录 "target_path": event_data.get("dest"), # 这个媒体的转移后的最终路径,包含文件名 "file_path": event_data.get("target_path") From 6e301bd48830a73a0652a41afbe970f340bb13ae Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 16:49:14 +0800 Subject: [PATCH 31/34] fix --- web/static/js/functions.js | 17 +++++++---------- web/templates/navigation.html | 9 +++++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/web/static/js/functions.js b/web/static/js/functions.js index 3a82b81b..8e3e1a10 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -234,22 +234,19 @@ function show_logging_modal() { setTimeout("get_logging()", 1000); } -// 渲染日志来源下拉列表 -function get_logging_source() { - $("#dropdown-menu-logger").html('') - let menu = ['All', 'System', 'Rss', 'Rmt', 'Meta', 'Sync', 'Sites', 'Brush', 'Douban', 'Spider', 'Message', 'Indexer', 'Searcher', 'Subscribe', 'Downloader', 'TorrentRemover', 'Plugin'] - for (let i = 0; i < menu.length; i++) { - $("#dropdown-menu-logger").append(`${menu[i]}`); - } -} - // 日志来源筛选 function logger_select(source) { LoggingSource = source if (LoggingSource === "All") { LoggingSource = ""; } - $("#logging_content").html("") + let logtype = `刷新中...`; + if (LoggingSource) { + logtype = `【${LoggingSource}】刷新中...`; + } + $("#logging_content").html(`${logtype}`); + // 拉取新日志 + get_logging(); } // 连接消息服务 diff --git a/web/templates/navigation.html b/web/templates/navigation.html index e5b3339d..cddd3a24 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -365,10 +365,15 @@

From 508f63d447e0895ae8c422c058ed6cbe379c237d Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 17:06:59 +0800 Subject: [PATCH 32/34] reconnecting-websocket --- web/static/js/functions.js | 24 +- .../js/modules/reconnecting-websocket.js | 365 ++++++++++++++++++ web/templates/navigation.html | 1 + 3 files changed, 368 insertions(+), 22 deletions(-) create mode 100644 web/static/js/modules/reconnecting-websocket.js diff --git a/web/static/js/functions.js b/web/static/js/functions.js index 8e3e1a10..a63078e1 100644 --- a/web/static/js/functions.js +++ b/web/static/js/functions.js @@ -131,20 +131,10 @@ function hide_wait_modal() { // 连接日志服务 function connect_logging() { - LoggingWS = new WebSocket(WSProtocol + window.location.host + '/logging'); + LoggingWS = new ReconnectingWebSocket(WSProtocol + window.location.host + '/logging'); LoggingWS.onmessage = function (event) { render_logging(JSON.parse(event.data)) }; - LoggingWS.onclose = function(event) { - setTimeout(function() { - connect_logging(); - }, 2000); - }; - LoggingWS.onerror = function(event) { - setTimeout(function() { - connect_logging(); - }, 2000); - } } // 发送刷新日志请求 @@ -251,20 +241,10 @@ function logger_select(source) { // 连接消息服务 function connect_message() { - MessageWS = new WebSocket(WSProtocol + window.location.host + '/message'); + MessageWS = new ReconnectingWebSocket(WSProtocol + window.location.host + '/message'); MessageWS.onmessage = function (event) { render_message(JSON.parse(event.data)) }; - MessageWS.onclose = function(event) { - setTimeout(function() { - connect_message(); - }, 2000); - }; - MessageWS.onerror = function(event) { - setTimeout(function() { - connect_message(); - }, 2000); - } MessageWS.onopen = function (event) { get_message(''); }; diff --git a/web/static/js/modules/reconnecting-websocket.js b/web/static/js/modules/reconnecting-websocket.js new file mode 100644 index 00000000..0cd4332d --- /dev/null +++ b/web/static/js/modules/reconnecting-websocket.js @@ -0,0 +1,365 @@ +// MIT License: +// +// Copyright (c) 2010-2012, Joe Walnes +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +/** + * This behaves like a WebSocket in every way, except if it fails to connect, + * or it gets disconnected, it will repeatedly poll until it successfully connects + * again. + * + * It is API compatible, so when you have: + * ws = new WebSocket('ws://....'); + * you can replace with: + * ws = new ReconnectingWebSocket('ws://....'); + * + * The event stream will typically look like: + * onconnecting + * onopen + * onmessage + * onmessage + * onclose // lost connection + * onconnecting + * onopen // sometime later... + * onmessage + * onmessage + * etc... + * + * It is API compatible with the standard WebSocket API, apart from the following members: + * + * - `bufferedAmount` + * - `extensions` + * - `binaryType` + * + * Latest version: https://github.com/joewalnes/reconnecting-websocket/ + * - Joe Walnes + * + * Syntax + * ====== + * var socket = new ReconnectingWebSocket(url, protocols, options); + * + * Parameters + * ========== + * url - The url you are connecting to. + * protocols - Optional string or array of protocols. + * options - See below + * + * Options + * ======= + * Options can either be passed upon instantiation or set after instantiation: + * + * var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 }); + * + * or + * + * var socket = new ReconnectingWebSocket(url); + * socket.debug = true; + * socket.reconnectInterval = 4000; + * + * debug + * - Whether this instance should log debug messages. Accepts true or false. Default: false. + * + * automaticOpen + * - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close(). + * + * reconnectInterval + * - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000. + * + * maxReconnectInterval + * - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000. + * + * reconnectDecay + * - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5. + * + * timeoutInterval + * - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000. + * + */ +(function (global, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof module !== 'undefined' && module.exports){ + module.exports = factory(); + } else { + global.ReconnectingWebSocket = factory(); + } +})(this, function () { + + if (!('WebSocket' in window)) { + return; + } + + function ReconnectingWebSocket(url, protocols, options) { + + // Default settings + var settings = { + + /** Whether this instance should log debug messages. */ + debug: false, + + /** Whether or not the websocket should attempt to connect immediately upon instantiation. */ + automaticOpen: true, + + /** The number of milliseconds to delay before attempting to reconnect. */ + reconnectInterval: 1000, + /** The maximum number of milliseconds to delay a reconnection attempt. */ + maxReconnectInterval: 30000, + /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */ + reconnectDecay: 1.5, + + /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */ + timeoutInterval: 2000, + + /** The maximum number of reconnection attempts to make. Unlimited if null. */ + maxReconnectAttempts: null, + + /** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */ + binaryType: 'blob' + } + if (!options) { options = {}; } + + // Overwrite and define settings with options if they exist. + for (var key in settings) { + if (typeof options[key] !== 'undefined') { + this[key] = options[key]; + } else { + this[key] = settings[key]; + } + } + + // These should be treated as read-only properties + + /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */ + this.url = url; + + /** The number of attempted reconnects since starting, or the last successful connection. Read only. */ + this.reconnectAttempts = 0; + + /** + * The current state of the connection. + * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED + * Read only. + */ + this.readyState = WebSocket.CONNECTING; + + /** + * A string indicating the name of the sub-protocol the server selected; this will be one of + * the strings specified in the protocols parameter when creating the WebSocket object. + * Read only. + */ + this.protocol = null; + + // Private state variables + + var self = this; + var ws; + var forcedClose = false; + var timedOut = false; + var eventTarget = document.createElement('div'); + + // Wire up "on*" properties as event handlers + + eventTarget.addEventListener('open', function(event) { self.onopen(event); }); + eventTarget.addEventListener('close', function(event) { self.onclose(event); }); + eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); }); + eventTarget.addEventListener('message', function(event) { self.onmessage(event); }); + eventTarget.addEventListener('error', function(event) { self.onerror(event); }); + + // Expose the API required by EventTarget + + this.addEventListener = eventTarget.addEventListener.bind(eventTarget); + this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget); + this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget); + + /** + * This function generates an event that is compatible with standard + * compliant browsers and IE9 - IE11 + * + * This will prevent the error: + * Object doesn't support this action + * + * http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563 + * @param s String The name that the event should use + * @param args Object an optional object that the event will use + */ + function generateEvent(s, args) { + var evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(s, false, false, args); + return evt; + }; + + this.open = function (reconnectAttempt) { + ws = new WebSocket(self.url, protocols || []); + ws.binaryType = this.binaryType; + + if (reconnectAttempt) { + if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) { + return; + } + } else { + eventTarget.dispatchEvent(generateEvent('connecting')); + this.reconnectAttempts = 0; + } + + if (self.debug || ReconnectingWebSocket.debugAll) { + console.debug('ReconnectingWebSocket', 'attempt-connect', self.url); + } + + var localWs = ws; + var timeout = setTimeout(function() { + if (self.debug || ReconnectingWebSocket.debugAll) { + console.debug('ReconnectingWebSocket', 'connection-timeout', self.url); + } + timedOut = true; + localWs.close(); + timedOut = false; + }, self.timeoutInterval); + + ws.onopen = function(event) { + clearTimeout(timeout); + if (self.debug || ReconnectingWebSocket.debugAll) { + console.debug('ReconnectingWebSocket', 'onopen', self.url); + } + self.protocol = ws.protocol; + self.readyState = WebSocket.OPEN; + self.reconnectAttempts = 0; + var e = generateEvent('open'); + e.isReconnect = reconnectAttempt; + reconnectAttempt = false; + eventTarget.dispatchEvent(e); + }; + + ws.onclose = function(event) { + clearTimeout(timeout); + ws = null; + if (forcedClose) { + self.readyState = WebSocket.CLOSED; + eventTarget.dispatchEvent(generateEvent('close')); + } else { + self.readyState = WebSocket.CONNECTING; + var e = generateEvent('connecting'); + e.code = event.code; + e.reason = event.reason; + e.wasClean = event.wasClean; + eventTarget.dispatchEvent(e); + if (!reconnectAttempt && !timedOut) { + if (self.debug || ReconnectingWebSocket.debugAll) { + console.debug('ReconnectingWebSocket', 'onclose', self.url); + } + eventTarget.dispatchEvent(generateEvent('close')); + } + + var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts); + setTimeout(function() { + self.reconnectAttempts++; + self.open(true); + }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout); + } + }; + ws.onmessage = function(event) { + if (self.debug || ReconnectingWebSocket.debugAll) { + console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data); + } + var e = generateEvent('message'); + e.data = event.data; + eventTarget.dispatchEvent(e); + }; + ws.onerror = function(event) { + if (self.debug || ReconnectingWebSocket.debugAll) { + console.debug('ReconnectingWebSocket', 'onerror', self.url, event); + } + eventTarget.dispatchEvent(generateEvent('error')); + }; + } + + // Whether or not to create a websocket upon instantiation + if (this.automaticOpen == true) { + this.open(false); + } + + /** + * Transmits data to the server over the WebSocket connection. + * + * @param data a text string, ArrayBuffer or Blob to send to the server. + */ + this.send = function(data) { + if (ws) { + if (self.debug || ReconnectingWebSocket.debugAll) { + console.debug('ReconnectingWebSocket', 'send', self.url, data); + } + return ws.send(data); + } else { + throw 'INVALID_STATE_ERR : Pausing to reconnect websocket'; + } + }; + + /** + * Closes the WebSocket connection or connection attempt, if any. + * If the connection is already CLOSED, this method does nothing. + */ + this.close = function(code, reason) { + // Default CLOSE_NORMAL code + if (typeof code == 'undefined') { + code = 1000; + } + forcedClose = true; + if (ws) { + ws.close(code, reason); + } + }; + + /** + * Additional public API method to refresh the connection if still open (close, re-open). + * For example, if the app suspects bad data / missed heart beats, it can try to refresh. + */ + this.refresh = function() { + if (ws) { + ws.close(); + } + }; + } + + /** + * An event listener to be called when the WebSocket connection's readyState changes to OPEN; + * this indicates that the connection is ready to send and receive data. + */ + ReconnectingWebSocket.prototype.onopen = function(event) {}; + /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */ + ReconnectingWebSocket.prototype.onclose = function(event) {}; + /** An event listener to be called when a connection begins being attempted. */ + ReconnectingWebSocket.prototype.onconnecting = function(event) {}; + /** An event listener to be called when a message is received from the server. */ + ReconnectingWebSocket.prototype.onmessage = function(event) {}; + /** An event listener to be called when an error occurs. */ + ReconnectingWebSocket.prototype.onerror = function(event) {}; + + /** + * Whether all instances of ReconnectingWebSocket should log debug messages. + * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true. + */ + ReconnectingWebSocket.debugAll = false; + + ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING; + ReconnectingWebSocket.OPEN = WebSocket.OPEN; + ReconnectingWebSocket.CLOSING = WebSocket.CLOSING; + ReconnectingWebSocket.CLOSED = WebSocket.CLOSED; + + return ReconnectingWebSocket; +}); diff --git a/web/templates/navigation.html b/web/templates/navigation.html index cddd3a24..54f5bab4 100644 --- a/web/templates/navigation.html +++ b/web/templates/navigation.html @@ -78,6 +78,7 @@ + From 5593800181c4928a2b3f3d915939a051de8ec553 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 17:09:22 +0800 Subject: [PATCH 33/34] add SOCK_SERVER_OPTIONS --- web/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/web/main.py b/web/main.py index 842fd705..b34ef51f 100644 --- a/web/main.py +++ b/web/main.py @@ -57,6 +57,7 @@ App.wsgi_app = ProxyFix(App.wsgi_app) App.config['JSON_AS_ASCII'] = False App.config['JSON_SORT_KEYS'] = False +App.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 25} App.secret_key = os.urandom(24) App.permanent_session_lifetime = datetime.timedelta(days=30) From 8bada583cb8fbdf7160c0d8d9f2030db208a0042 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 27 Apr 2023 17:17:14 +0800 Subject: [PATCH 34/34] remove Sock Exception --- web/main.py | 86 ++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/web/main.py b/web/main.py index b34ef51f..8710c6ac 100644 --- a/web/main.py +++ b/web/main.py @@ -1658,20 +1658,16 @@ def logging_handler(ws): 实时日志WebSocket """ while True: - try: - message = ws.receive() - _source = json.loads(message).get("source") - if log.LOG_INDEX > 0: - logs = list(log.LOG_QUEUE)[-log.LOG_INDEX:] - log.LOG_INDEX = 0 - if _source: - logs = [l for l in logs if l.get("source") == _source] - ws.send((json.dumps(logs))) - else: - ws.send(json.dumps([])) - except Exception as err: - print(str(err)) - break + message = ws.receive() + _source = json.loads(message).get("source") + if log.LOG_INDEX > 0: + logs = list(log.LOG_QUEUE)[-log.LOG_INDEX:] + log.LOG_INDEX = 0 + if _source: + logs = [l for l in logs if l.get("source") == _source] + ws.send((json.dumps(logs))) + else: + ws.send(json.dumps([])) @Sock.route('/message') @@ -1681,39 +1677,35 @@ def message_handler(ws): 消息中心WebSocket """ while True: - try: - data = ws.receive() - msgbody = json.loads(data) - if msgbody.get("text"): - # 发送的消息 - WebAction().handle_message_job(msg=msgbody.get("text"), - in_from=SearchType.WEB, - user_id=current_user.username, - user_name=current_user.username) - ws.send((json.dumps({}))) - else: - # 拉取消息 - system_msg = WebAction().get_system_message(lst_time=msgbody.get("lst_time")) - messages = system_msg.get("message") - lst_time = system_msg.get("lst_time") - ret_messages = [] - for message in list(reversed(messages)): - content = re.sub(r"#+", "
", - re.sub(r"<[^>]+>", "", - re.sub(r"
", "####", message.get("content"), flags=re.IGNORECASE))) - ret_messages.append({ - "level": "bg-red" if message.get("level") == "ERROR" else "", - "title": message.get("title"), - "content": content, - "time": message.get("time") - }) - ws.send((json.dumps({ - "lst_time": lst_time, - "message": ret_messages - }))) - except Exception as err: - print(str(err)) - break + data = ws.receive() + msgbody = json.loads(data) + if msgbody.get("text"): + # 发送的消息 + WebAction().handle_message_job(msg=msgbody.get("text"), + in_from=SearchType.WEB, + user_id=current_user.username, + user_name=current_user.username) + ws.send((json.dumps({}))) + else: + # 拉取消息 + system_msg = WebAction().get_system_message(lst_time=msgbody.get("lst_time")) + messages = system_msg.get("message") + lst_time = system_msg.get("lst_time") + ret_messages = [] + for message in list(reversed(messages)): + content = re.sub(r"#+", "
", + re.sub(r"<[^>]+>", "", + re.sub(r"
", "####", message.get("content"), flags=re.IGNORECASE))) + ret_messages.append({ + "level": "bg-red" if message.get("level") == "ERROR" else "", + "title": message.get("title"), + "content": content, + "time": message.get("time") + }) + ws.send((json.dumps({ + "lst_time": lst_time, + "message": ret_messages + }))) # base64模板过滤器