import base64 import datetime import os.path import re import shutil import sqlite3 import time import traceback import urllib import xml.dom.minidom from functools import wraps from math import floor from pathlib import Path from threading import Lock from urllib import parse from flask import Flask, request, json, render_template, make_response, session, send_from_directory, send_file from flask_compress import Compress from flask_login import LoginManager, login_user, login_required, current_user import log from app.brushtask import BrushTask from app.conf import ModuleConf, SystemConfig from app.downloader import Downloader from app.filter import Filter from app.helper import SecurityHelper, MetaHelper, ChromeHelper, ThreadHelper from app.indexer import Indexer from app.media.meta import MetaInfo from app.mediaserver import MediaServer from app.message import Message from app.plugins import EventManager, PluginManager from app.rsschecker import RssChecker from app.sites import Sites, SiteUserInfo from app.subscribe import Subscribe from app.sync import Sync from app.torrentremover import TorrentRemover from app.utils import DomUtils, SystemUtils, ExceptionUtils, StringUtils from app.utils.types import * from config import PT_TRANSFER_INTERVAL, Config from web.action import WebAction from web.apiv1 import apiv1_bp from web.backend.WXBizMsgCrypt3 import WXBizMsgCrypt from web.backend.user import User from web.backend.wallpaper import get_login_wallpaper from web.backend.web_utils import WebUtils from web.security import require_auth # 配置文件锁 ConfigLock = Lock() # Flask App App = Flask(__name__) App.config['JSON_AS_ASCII'] = False App.secret_key = os.urandom(24) App.permanent_session_lifetime = datetime.timedelta(days=30) # 启用压缩 Compress(App) # 登录管理模块 LoginManager = LoginManager() LoginManager.login_view = "login" LoginManager.init_app(App) # API注册 App.register_blueprint(apiv1_bp, url_prefix="/api/v1") @App.after_request def add_header(r): """ 统一添加Http头,标用缓存,避免Flask多线程+Chrome内核会发生的静态资源加载出错的问题 r.headers["Cache-Control"] = "no-cache, no-store, max-age=0" r.headers["Pragma"] = "no-cache" r.headers["Expires"] = "0" """ return r # 定义获取登录用户的方法 @LoginManager.user_loader def load_user(user_id): return User().get(user_id) # 页面不存在 @App.errorhandler(404) def page_not_found(error): return render_template("404.html", error=error), 404 # 服务错误 @App.errorhandler(500) def page_server_error(error): return render_template("500.html", error=error), 500 def action_login_check(func): """ Action安全认证 """ @wraps(func) def login_check(*args, **kwargs): if not current_user.is_authenticated: return {"code": -1, "msg": "用户未登录"} return func(*args, **kwargs) return login_check # 主页面 @App.route('/', methods=['GET', 'POST']) def login(): def redirect_to_navigation(userinfo): """ 跳转到导航页面 """ # 判断当前的运营环境 SystemFlag = SystemUtils.get_system() SyncMod = Config().get_config('pt').get('rmt_mode') TMDBFlag = 1 if Config().get_config('app').get('rmt_tmdbkey') else 0 if not SyncMod: SyncMod = "link" RmtModeDict = WebAction().get_rmt_modes() RestypeDict = ModuleConf.TORRENT_SEARCH_PARAMS.get("restype") PixDict = ModuleConf.TORRENT_SEARCH_PARAMS.get("pix") SiteFavicons = Sites().get_site_favicon() Indexers = Indexer().get_indexers() SearchSource = "douban" if Config().get_config("laboratory").get("use_douban_titles") else "tmdb" CustomScriptCfg = SystemConfig().get_system_config("CustomScript") return render_template('navigation.html', GoPage=GoPage, UserName=userinfo.username, UserPris=str(userinfo.pris).split(","), SystemFlag=SystemFlag.value, TMDBFlag=TMDBFlag, AppVersion=WebUtils.get_current_version(), RestypeDict=RestypeDict, PixDict=PixDict, SyncMod=SyncMod, SiteFavicons=SiteFavicons, RmtModeDict=RmtModeDict, Indexers=Indexers, SearchSource=SearchSource, CustomScriptCfg=CustomScriptCfg) def redirect_to_login(errmsg=''): """ 跳转到登录页面 """ return render_template('login.html', GoPage=GoPage, LoginWallpaper=get_login_wallpaper(), err_msg=errmsg) # 登录认证 if request.method == 'GET': GoPage = request.args.get("next") or "" if GoPage.startswith('/'): GoPage = GoPage[1:] if current_user.is_authenticated: userid = current_user.id username = current_user.username if userid is None or username is None: return redirect_to_login() else: # 登录成功 return redirect_to_navigation(User().get_user(username)) else: return redirect_to_login() else: GoPage = request.form.get('next') or "" if GoPage.startswith('/'): GoPage = GoPage[1:] username = request.form.get('username') password = request.form.get('password') remember = request.form.get('remember') if not username: return redirect_to_login('请输入用户名') user_info = User().get_user(username) if not user_info: return redirect_to_login('用户名或密码错误') # 校验密码 if user_info.verify_password(password): # 创建用户 Session login_user(user_info) session.permanent = True if remember else False # 登录成功 return redirect_to_navigation(user_info) else: return redirect_to_login('用户名或密码错误') # 开始 @App.route('/index', methods=['POST', 'GET']) @login_required def index(): # 媒体服务器类型 MSType = Config().get_config('media').get('media_server') # 获取媒体数量 MediaCounts = WebAction().get_library_mediacount() if MediaCounts.get("code") == 0: ServerSucess = True else: ServerSucess = False # 获得活动日志 Activity = WebAction().get_library_playhistory().get("result") # 磁盘空间 LibrarySpaces = WebAction().get_library_spacesize() # 转移历史统计 TransferStatistics = WebAction().get_transfer_statistics() return render_template("index.html", ServerSucess=ServerSucess, MediaCount={'MovieCount': MediaCounts.get("Movie"), 'SeriesCount': MediaCounts.get("Series"), 'SongCount': MediaCounts.get("Music"), "EpisodeCount": MediaCounts.get("Episodes")}, Activitys=Activity, UserCount=MediaCounts.get("User"), FreeSpace=LibrarySpaces.get("FreeSpace"), TotalSpace=LibrarySpaces.get("TotalSpace"), UsedSapce=LibrarySpaces.get("UsedSapce"), UsedPercent=LibrarySpaces.get("UsedPercent"), MovieChartLabels=TransferStatistics.get("MovieChartLabels"), TvChartLabels=TransferStatistics.get("TvChartLabels"), MovieNums=TransferStatistics.get("MovieNums"), TvNums=TransferStatistics.get("TvNums"), AnimeNums=TransferStatistics.get("AnimeNums"), MediaServerType=MSType ) # 资源搜索页面 @App.route('/search', methods=['POST', 'GET']) @login_required def search(): # 权限 if current_user.is_authenticated: username = current_user.username pris = User().get_user(username).get("pris") else: pris = "" # 结果 res = WebAction().get_search_result() SearchResults = res.get("result") Count = res.get("total") return render_template("search.html", UserPris=str(pris).split(","), Count=Count, Results=SearchResults, SiteDict=Indexer().get_indexer_hash_dict(), UPCHAR=chr(8593)) # 电影订阅页面 @App.route('/movie_rss', methods=['POST', 'GET']) @login_required def movie_rss(): RssItems = WebAction().get_movie_rss_list().get("result") RuleGroups = {str(group["id"]): group["name"] for group in Filter().get_rule_groups()} DownloadSettings = Downloader().get_download_setting() return render_template("rss/movie_rss.html", Count=len(RssItems), RuleGroups=RuleGroups, DownloadSettings=DownloadSettings, Items=RssItems ) # 电视剧订阅页面 @App.route('/tv_rss', methods=['POST', 'GET']) @login_required def tv_rss(): RssItems = WebAction().get_tv_rss_list().get("result") RuleGroups = {str(group["id"]): group["name"] for group in Filter().get_rule_groups()} DownloadSettings = Downloader().get_download_setting() return render_template("rss/tv_rss.html", Count=len(RssItems), RuleGroups=RuleGroups, DownloadSettings=DownloadSettings, Items=RssItems ) # 订阅历史页面 @App.route('/rss_history', methods=['POST', 'GET']) @login_required def rss_history(): mtype = request.args.get("t") RssHistory = WebAction().get_rss_history({"type": mtype}).get("result") return render_template("rss/rss_history.html", Count=len(RssHistory), Items=RssHistory, Type=mtype ) # 订阅日历页面 @App.route('/rss_calendar', methods=['POST', 'GET']) @login_required def rss_calendar(): Today = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d') # 电影订阅 RssMovieItems = [ { "tmdbid": movie.get("tmdbid"), "rssid": movie.get("id") } for movie in Subscribe().get_subscribe_movies().values() if movie.get("tmdbid") ] # 电视剧订阅 RssTvItems = [ { "id": tv.get("tmdbid"), "rssid": tv.get("id"), "season": int(str(tv.get('season')).replace("S", "")), "name": tv.get("name"), } for tv in Subscribe().get_subscribe_tvs().values() if tv.get('season') and tv.get("tmdbid") ] # 自定义订阅 RssTvItems += RssChecker().get_userrss_mediainfos() # 电视剧订阅去重 Uniques = set() UniqueTvItems = [] for item in RssTvItems: unique = f"{item.get('id')}_{item.get('season')}" if unique not in Uniques: Uniques.add(unique) UniqueTvItems.append(item) return render_template("rss/rss_calendar.html", Today=Today, RssMovieItems=RssMovieItems, RssTvItems=UniqueTvItems) # 站点维护页面 @App.route('/site', methods=['POST', 'GET']) @login_required def sites(): CfgSites = Sites().get_sites() RuleGroups = {str(group["id"]): group["name"] for group in Filter().get_rule_groups()} DownloadSettings = {did: attr["name"] for did, attr in Downloader().get_download_setting().items()} ChromeOk = ChromeHelper().get_status() CookieCloudCfg = SystemConfig().get_system_config('CookieCloud') CookieUserInfoCfg = SystemConfig().get_system_config('CookieUserInfo') return render_template("site/site.html", Sites=CfgSites, RuleGroups=RuleGroups, DownloadSettings=DownloadSettings, ChromeOk=ChromeOk, CookieCloudCfg=CookieCloudCfg, CookieUserInfoCfg=CookieUserInfoCfg) # 站点列表页面 @App.route('/sitelist', methods=['POST', 'GET']) @login_required def sitelist(): IndexerSites = Indexer().get_builtin_indexers(check=False, public=False) return render_template("site/sitelist.html", Sites=IndexerSites, Count=len(IndexerSites)) # 站点资源页面 @App.route('/resources', methods=['POST', 'GET']) @login_required def resources(): site_id = request.args.get("site") site_name = request.args.get("title") page = request.args.get("page") or 0 keyword = request.args.get("keyword") Results = WebAction().action("list_site_resources", {"id": site_id, "page": page, "keyword": keyword}).get( "data") or [] return render_template("site/resources.html", Results=Results, SiteId=site_id, Title=site_name, KeyWord=keyword, TotalCount=len(Results), PageRange=range(0, 10), CurrentPage=int(page), TotalPage=10) # 推荐页面 @App.route('/recommend', methods=['POST', 'GET']) @login_required def recommend(): Type = request.args.get("type") or "" SubType = request.args.get("subtype") or "" Title = request.args.get("title") or "" SubTitle = request.args.get("subtitle") or "" CurrentPage = request.args.get("page") or 1 Week = request.args.get("week") or "" TmdbId = request.args.get("tmdbid") or "" PersonId = request.args.get("personid") or "" Keyword = request.args.get("keyword") or "" Source = request.args.get("source") or "" FilterKey = request.args.get("filter") or "" Params = json.loads(request.args.get("params")) if request.args.get("params") else {} return render_template("discovery/recommend.html", Type=Type, SubType=SubType, Title=Title, CurrentPage=CurrentPage, Week=Week, TmdbId=TmdbId, PersonId=PersonId, SubTitle=SubTitle, Keyword=Keyword, Source=Source, Filter=FilterKey, FilterConf=ModuleConf.DISCOVER_FILTER_CONF.get(FilterKey) if FilterKey else {}, Params=Params) # 推荐页面 @App.route('/ranking', methods=['POST', 'GET']) @login_required def ranking(): return render_template("discovery/ranking.html", DiscoveryType="RANKING") # 豆瓣电影 @App.route('/douban_movie', methods=['POST', 'GET']) @login_required def douban_movie(): return render_template("discovery/recommend.html", Type="DOUBANTAG", SubType="MOV", Title="豆瓣电影", Filter="douban_movie", FilterConf=ModuleConf.DISCOVER_FILTER_CONF.get('douban_movie')) # 豆瓣电视剧 @App.route('/douban_tv', methods=['POST', 'GET']) @login_required def douban_tv(): return render_template("discovery/recommend.html", Type="DOUBANTAG", SubType="TV", Title="豆瓣电视剧", Filter="douban_tv", FilterConf=ModuleConf.DISCOVER_FILTER_CONF.get('douban_tv')) @App.route('/tmdb_movie', methods=['POST', 'GET']) @login_required def tmdb_movie(): return render_template("discovery/recommend.html", Type="DISCOVER", SubType="MOV", Title="TMDB电影", Filter="tmdb_movie", FilterConf=ModuleConf.DISCOVER_FILTER_CONF.get('tmdb_movie')) @App.route('/tmdb_tv', methods=['POST', 'GET']) @login_required def tmdb_tv(): return render_template("discovery/recommend.html", Type="DISCOVER", SubType="TV", Title="TMDB电视剧", Filter="tmdb_tv", FilterConf=ModuleConf.DISCOVER_FILTER_CONF.get('tmdb_tv')) # Bangumi每日放送 @App.route('/bangumi', methods=['POST', 'GET']) @login_required def discovery_bangumi(): return render_template("discovery/ranking.html", DiscoveryType="BANGUMI") # 媒体详情页面 @App.route('/media_detail', methods=['POST', 'GET']) @login_required def media_detail(): TmdbId = request.args.get("id") Type = request.args.get("type") return render_template("discovery/mediainfo.html", TmdbId=TmdbId, Type=Type) # 演职人员页面 @App.route('/discovery_person', methods=['POST', 'GET']) @login_required def discovery_person(): TmdbId = request.args.get("tmdbid") Title = request.args.get("title") SubTitle = request.args.get("subtitle") Type = request.args.get("type") return render_template("discovery/person.html", TmdbId=TmdbId, Title=Title, SubTitle=SubTitle, Type=Type) # 正在下载页面 @App.route('/downloading', methods=['POST', 'GET']) @login_required def downloading(): DispTorrents = WebAction().get_downloading().get("result") return render_template("download/downloading.html", DownloadCount=len(DispTorrents), Torrents=DispTorrents, Client=Config().get_config("pt").get("pt_client")) # 近期下载页面 @App.route('/downloaded', methods=['POST', 'GET']) @login_required def downloaded(): CurrentPage = request.args.get("page") or 1 return render_template("discovery/recommend.html", Type='DOWNLOADED', Title='近期下载', CurrentPage=CurrentPage) @App.route('/torrent_remove', methods=['POST', 'GET']) @login_required def torrent_remove(): TorrentRemoveTasks = TorrentRemover().get_torrent_remove_tasks() return render_template("download/torrent_remove.html", DownloaderConfig=ModuleConf.TORRENTREMOVER_DICT, Count=len(TorrentRemoveTasks), TorrentRemoveTasks=TorrentRemoveTasks) # 数据统计页面 @App.route('/statistics', methods=['POST', 'GET']) @login_required def statistics(): # 刷新单个site refresh_site = request.args.getlist("refresh_site") # 强制刷新所有 refresh_force = True if request.args.get("refresh_force") else False # 总上传下载 TotalUpload = 0 TotalDownload = 0 TotalSeedingSize = 0 TotalSeeding = 0 # 站点标签及上传下载 SiteNames = [] SiteUploads = [] SiteDownloads = [] SiteRatios = [] SiteErrs = {} # 站点上传下载 SiteData = SiteUserInfo().get_pt_date(specify_sites=refresh_site, force=refresh_force) if isinstance(SiteData, dict): for name, data in SiteData.items(): if not data: continue up = data.get("upload", 0) dl = data.get("download", 0) ratio = data.get("ratio", 0) seeding = data.get("seeding", 0) seeding_size = data.get("seeding_size", 0) err_msg = data.get("err_msg", "") SiteErrs.update({name: err_msg}) if not up and not dl and not ratio: continue if not str(up).isdigit() or not str(dl).isdigit(): continue if name not in SiteNames: SiteNames.append(name) TotalUpload += int(up) TotalDownload += int(dl) TotalSeeding += int(seeding) TotalSeedingSize += int(seeding_size) SiteUploads.append(int(up)) SiteDownloads.append(int(dl)) SiteRatios.append(round(float(ratio), 1)) # 近期上传下载各站点汇总 CurrentUpload, CurrentDownload, _, _, _ = SiteUserInfo().get_pt_site_statistics_history( days=2) # 站点用户数据 SiteUserStatistics = WebAction().get_site_user_statistics({"encoding": "DICT"}).get("data") return render_template("site/statistics.html", CurrentDownload=CurrentDownload, CurrentUpload=CurrentUpload, TotalDownload=TotalDownload, TotalUpload=TotalUpload, TotalSeedingSize=TotalSeedingSize, TotalSeeding=TotalSeeding, SiteDownloads=SiteDownloads, SiteUploads=SiteUploads, SiteRatios=SiteRatios, SiteNames=SiteNames, SiteErr=SiteErrs, SiteUserStatistics=SiteUserStatistics) # 刷流任务页面 @App.route('/brushtask', methods=['POST', 'GET']) @login_required def brushtask(): # 站点列表 CfgSites = Sites().get_sites(brush=True) # 下载器列表 Downloaders = BrushTask().get_downloader_info() # 任务列表 Tasks = BrushTask().get_brushtask_info() return render_template("site/brushtask.html", Count=len(Tasks), Sites=CfgSites, Tasks=Tasks, Downloaders=Downloaders) # 自定义下载器页面 @App.route('/userdownloader', methods=['POST', 'GET']) @login_required def userdownloader(): downloaders = BrushTask().get_downloader_info() return render_template("download/userdownloader.html", Count=len(downloaders), Downloaders=downloaders) # 服务页面 @App.route('/service', methods=['POST', 'GET']) @login_required def service(): scheduler_cfg_list = [] RuleGroups = Filter().get_rule_groups() pt = Config().get_config('pt') if pt: # RSS订阅 pt_check_interval = pt.get('pt_check_interval') if str(pt_check_interval).isdigit(): tim_rssdownload = str(round(int(pt_check_interval) / 60)) + " 分钟" rss_state = 'ON' else: tim_rssdownload = "" rss_state = 'OFF' svg = ''' ''' scheduler_cfg_list.append( {'name': 'RSS订阅', 'time': tim_rssdownload, 'state': rss_state, 'id': 'rssdownload', 'svg': svg, 'color': "blue"}) search_rss_interval = pt.get('search_rss_interval') if str(search_rss_interval).isdigit(): if int(search_rss_interval) < 6: search_rss_interval = 6 tim_rsssearch = str(int(search_rss_interval)) + " 小时" rss_search_state = 'ON' else: tim_rsssearch = "" rss_search_state = 'OFF' svg = ''' ''' scheduler_cfg_list.append( {'name': '订阅搜索', 'time': tim_rsssearch, 'state': rss_search_state, 'id': 'subscribe_search_all', 'svg': svg, 'color': "blue"}) # 下载文件转移 pt_monitor = pt.get('pt_monitor') if pt_monitor: tim_pttransfer = str(round(PT_TRANSFER_INTERVAL / 60)) + " 分钟" sta_pttransfer = 'ON' else: tim_pttransfer = "" sta_pttransfer = 'OFF' svg = ''' ''' scheduler_cfg_list.append( {'name': '下载文件转移', 'time': tim_pttransfer, 'state': sta_pttransfer, 'id': 'pttransfer', 'svg': svg, 'color': "green"}) # 删种 torrent_remove_tasks = TorrentRemover().get_torrent_remove_tasks() if torrent_remove_tasks: sta_autoremovetorrents = 'ON' svg = ''' ''' scheduler_cfg_list.append( {'name': '自动删种', 'state': sta_autoremovetorrents, 'id': 'autoremovetorrents', 'svg': svg, 'color': "twitter"}) # 自动签到 tim_ptsignin = pt.get('ptsignin_cron') if tim_ptsignin: if str(tim_ptsignin).find(':') == -1: tim_ptsignin = "%s 小时" % tim_ptsignin sta_ptsignin = 'ON' svg = ''' ''' scheduler_cfg_list.append( {'name': '站点签到', 'time': tim_ptsignin, 'state': sta_ptsignin, 'id': 'ptsignin', 'svg': svg, 'color': "facebook"}) # 目录同步 sync_paths = Sync().get_sync_dirs() if sync_paths: sta_sync = 'ON' svg = ''' ''' scheduler_cfg_list.append( {'name': '目录同步', 'time': '实时监控', 'state': sta_sync, 'id': 'sync', 'svg': svg, 'color': "orange"}) # 豆瓣同步 douban_cfg = Config().get_config('douban') if douban_cfg: interval = douban_cfg.get('interval') if interval: interval = "%s 小时" % interval sta_douban = "ON" svg = ''' ''' scheduler_cfg_list.append( {'name': '豆瓣想看', 'time': interval, 'state': sta_douban, 'id': 'douban', 'svg': svg, 'color': "pink"}) # 清理文件整理缓存 svg = ''' ''' scheduler_cfg_list.append( {'name': '清理转移缓存', 'time': '手动', 'state': 'OFF', 'id': 'blacklist', 'svg': svg, 'color': 'red'}) # 清理RSS缓存 svg = ''' ''' scheduler_cfg_list.append( {'name': '清理RSS缓存', 'time': '手动', 'state': 'OFF', 'id': 'rsshistory', 'svg': svg, 'color': 'purple'}) # 名称识别测试 svg = ''' ''' scheduler_cfg_list.append( {'name': '名称识别测试', 'time': '', 'state': 'OFF', 'id': 'nametest', 'svg': svg, 'color': 'lime'}) # 过滤规则测试 svg = ''' ''' scheduler_cfg_list.append( {'name': '过滤规则测试', 'time': '', 'state': 'OFF', 'id': 'ruletest', 'svg': svg, 'color': 'yellow'}) # 网络连通性测试 svg = ''' ''' targets = ModuleConf.NETTEST_TARGETS scheduler_cfg_list.append( {'name': '网络连通性测试', 'time': '', 'state': 'OFF', 'id': 'nettest', 'svg': svg, 'color': 'cyan', "targets": targets}) # 备份 svg = ''' ''' scheduler_cfg_list.append( {'name': '备份&恢复', 'time': '', 'state': 'OFF', 'id': 'backup', 'svg': svg, 'color': 'green'}) return render_template("service.html", Count=len(scheduler_cfg_list), RuleGroups=RuleGroups, SchedulerTasks=scheduler_cfg_list) # 历史记录页面 @App.route('/history', methods=['POST', 'GET']) @login_required def history(): pagenum = request.args.get("pagenum") keyword = request.args.get("s") or "" current_page = request.args.get("page") Result = WebAction().get_transfer_history({"keyword": keyword, "page": current_page, "pagenum": pagenum}) PageRange = WebUtils.get_page_range(current_page=Result.get("currentPage"), total_page=Result.get("totalPage")) return render_template("rename/history.html", TotalCount=Result.get("total"), Count=len(Result.get("result")), Historys=Result.get("result"), Search=keyword, CurrentPage=Result.get("currentPage"), TotalPage=Result.get("totalPage"), PageRange=PageRange, PageNum=Result.get("currentPage")) # TMDB缓存页面 @App.route('/tmdbcache', methods=['POST', 'GET']) @login_required def tmdbcache(): page_num = request.args.get("pagenum") if not page_num: page_num = 30 search_str = request.args.get("s") if not search_str: search_str = "" current_page = request.args.get("page") if not current_page: current_page = 1 else: current_page = int(current_page) total_count, tmdb_caches = MetaHelper().dump_meta_data(search_str, current_page, page_num) total_page = floor(total_count / page_num) + 1 page_range = WebUtils.get_page_range(current_page=current_page, total_page=total_page) return render_template("rename/tmdbcache.html", TotalCount=total_count, Count=len(tmdb_caches), TmdbCaches=tmdb_caches, Search=search_str, CurrentPage=current_page, TotalPage=total_page, PageRange=page_range, PageNum=page_num) # 手工识别页面 @App.route('/unidentification', methods=['POST', 'GET']) @login_required def unidentification(): pagenum = request.args.get("pagenum") keyword = request.args.get("s") or "" current_page = request.args.get("page") Result = WebAction().get_unknown_list_by_page({"keyword": keyword, "page": current_page, "pagenum": pagenum}) PageRange = WebUtils.get_page_range(current_page=Result.get("currentPage"), total_page=Result.get("totalPage")) return render_template("rename/unidentification.html", TotalCount=Result.get("total"), Count=len(Result.get("items")), Items=Result.get("items"), Search=keyword, CurrentPage=Result.get("currentPage"), TotalPage=Result.get("totalPage"), PageRange=PageRange, PageNum=Result.get("currentPage")) # 文件管理页面 @App.route('/mediafile', methods=['POST', 'GET']) @login_required def mediafile(): download_dirs = Downloader().get_download_visit_dirs() if download_dirs: try: DirD = os.path.commonpath(download_dirs).replace("\\", "/") except Exception as err: print(str(err)) DirD = "/" else: DirD = "/" DirR = request.args.get("dir") return render_template("rename/mediafile.html", Dir=DirR or DirD) # 基础设置页面 @App.route('/basic', methods=['POST', 'GET']) @login_required def basic(): proxy = Config().get_config('app').get("proxies", {}).get("http") if proxy: proxy = proxy.replace("http://", "") RmtModeDict = WebAction().get_rmt_modes() CustomScriptCfg = SystemConfig().get_system_config("CustomScript") return render_template("setting/basic.html", Config=Config().get_config(), Proxy=proxy, RmtModeDict=RmtModeDict, CustomScriptCfg=CustomScriptCfg) # 自定义识别词设置页面 @App.route('/customwords', methods=['POST', 'GET']) @login_required def customwords(): groups = WebAction().get_customwords().get("result") return render_template("setting/customwords.html", Groups=groups, GroupsCount=len(groups)) # 目录同步页面 @App.route('/directorysync', methods=['POST', 'GET']) @login_required def directorysync(): RmtModeDict = WebAction().get_rmt_modes() SyncPaths = WebAction().get_directorysync().get("result") return render_template("setting/directorysync.html", SyncPaths=SyncPaths, SyncCount=len(SyncPaths), RmtModeDict=RmtModeDict) # 豆瓣页面 @App.route('/douban', methods=['POST', 'GET']) @login_required def douban(): DoubanHistory = WebAction().get_douban_history().get("result") return render_template("setting/douban.html", Config=Config().get_config(), HistoryCount=len(DoubanHistory), DoubanHistory=DoubanHistory) # 下载器页面 @App.route('/downloader', methods=['POST', 'GET']) @login_required def downloader(): return render_template("setting/downloader.html", Config=Config().get_config(), SpeedLimitConf=SystemConfig().get_system_config("SpeedLimit") or {}, DownloaderConf=ModuleConf.DOWNLOADER_CONF) # 下载设置页面 @App.route('/download_setting', methods=['POST', 'GET']) @login_required def download_setting(): DownloadSetting = Downloader().get_download_setting() DefaultDownloadSetting = Downloader().get_default_download_setting() Count = len(DownloadSetting) return render_template("setting/download_setting.html", DownloadSetting=DownloadSetting, DefaultDownloadSetting=DefaultDownloadSetting, DownloaderTypes=DownloaderType, Count=Count) # 索引器页面 @App.route('/indexer', methods=['POST', 'GET']) @login_required def indexer(): indexers = Indexer().get_builtin_indexers(check=False) private_count = len([item.id for item in indexers if not item.public]) public_count = len([item.id for item in indexers if item.public]) return render_template("setting/indexer.html", Config=Config().get_config(), PrivateCount=private_count, PublicCount=public_count, Indexers=indexers, IndexerConf=ModuleConf.INDEXER_CONF) # 媒体库页面 @App.route('/library', methods=['POST', 'GET']) @login_required def library(): return render_template("setting/library.html", Config=Config().get_config()) # 媒体服务器页面 @App.route('/mediaserver', methods=['POST', 'GET']) @login_required def mediaserver(): return render_template("setting/mediaserver.html", Config=Config().get_config(), MediaServerConf=ModuleConf.MEDIASERVER_CONF) # 通知消息页面 @App.route('/notification', methods=['POST', 'GET']) @login_required def notification(): MessageClients = Message().get_message_client_info() Channels = ModuleConf.MESSAGE_CONF.get("client") Switchs = ModuleConf.MESSAGE_CONF.get("switch") return render_template("setting/notification.html", Channels=Channels, Switchs=Switchs, ClientCount=len(MessageClients), MessageClients=MessageClients) # 用户管理页面 @App.route('/users', methods=['POST', 'GET']) @login_required def users(): Users = WebAction().get_users().get("result") return render_template("setting/users.html", Users=Users, UserCount=len(Users)) # 过滤规则设置页面 @App.route('/filterrule', methods=['POST', 'GET']) @login_required def filterrule(): result = WebAction().get_filterrules() return render_template("setting/filterrule.html", Count=len(result.get("ruleGroups")), RuleGroups=result.get("ruleGroups"), Init_RuleGroups=result.get("initRules")) # 自定义订阅页面 @App.route('/user_rss', methods=['POST', 'GET']) @login_required def user_rss(): Tasks = RssChecker().get_rsstask_info() RssParsers = RssChecker().get_userrss_parser() RuleGroups = {str(group["id"]): group["name"] for group in Filter().get_rule_groups()} DownloadSettings = {did: attr["name"] for did, attr in Downloader().get_download_setting().items()} RestypeDict = ModuleConf.TORRENT_SEARCH_PARAMS.get("restype") PixDict = ModuleConf.TORRENT_SEARCH_PARAMS.get("pix") return render_template("rss/user_rss.html", Tasks=Tasks, Count=len(Tasks), RssParsers=RssParsers, RuleGroups=RuleGroups, RestypeDict=RestypeDict, PixDict=PixDict, DownloadSettings=DownloadSettings) # RSS解析器页面 @App.route('/rss_parser', methods=['POST', 'GET']) @login_required def rss_parser(): RssParsers = RssChecker().get_userrss_parser() return render_template("rss/rss_parser.html", RssParsers=RssParsers, Count=len(RssParsers)) # 插件页面 @App.route('/plugin', methods=['POST', 'GET']) @login_required def plugin(): Plugins = PluginManager().get_plugins_conf() return render_template("setting/plugin.html", Plugins=Plugins) # 事件响应 @App.route('/do', methods=['POST']) @action_login_check def do(): try: cmd = request.form.get("cmd") data = request.form.get("data") except Exception as e: ExceptionUtils.exception_traceback(e) return {"code": -1, "msg": str(e)} if data: data = json.loads(data) return WebAction().action(cmd, data) # 目录事件响应 @App.route('/dirlist', methods=['POST']) @login_required def dirlist(): r = ['
') return make_response(''.join(r), 200) # 禁止搜索引擎 @App.route('/robots.txt', methods=['GET', 'POST']) def robots(): return send_from_directory("", "robots.txt") # 响应企业微信消息 @App.route('/wechat', methods=['GET', 'POST']) def wechat(): # 当前在用的交互渠道 interactive_client = Message().get_interactive_client(SearchType.WX) if not interactive_client: return make_response("NAStool没有启用微信交互", 200) conf = interactive_client.get("config") sToken = conf.get('token') sEncodingAESKey = conf.get('encodingAESKey') sCorpID = conf.get('corpid') if not sToken or not sEncodingAESKey or not sCorpID: return wxcpt = WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID) sVerifyMsgSig = request.args.get("msg_signature") sVerifyTimeStamp = request.args.get("timestamp") sVerifyNonce = request.args.get("nonce") if request.method == 'GET': if not sVerifyMsgSig and not sVerifyTimeStamp and not sVerifyNonce: return "NAStool微信交互服务正常!