4666 lines
188 KiB
Python
4666 lines
188 KiB
Python
import base64
|
||
import datetime
|
||
import importlib
|
||
import json
|
||
import os.path
|
||
import re
|
||
import shutil
|
||
import signal
|
||
from math import floor
|
||
from urllib.parse import unquote
|
||
|
||
import cn2an
|
||
from flask_login import logout_user, current_user
|
||
from werkzeug.security import generate_password_hash
|
||
|
||
import log
|
||
from app.brushtask import BrushTask
|
||
from app.conf import SystemConfig, ModuleConf
|
||
from app.doubansync import DoubanSync
|
||
from app.downloader import Downloader
|
||
from app.downloader.client import Qbittorrent, Transmission
|
||
from app.filetransfer import FileTransfer
|
||
from app.filter import Filter
|
||
from app.helper import DbHelper, ProgressHelper, ThreadHelper, \
|
||
MetaHelper, DisplayHelper, WordsHelper, CookieCloudHelper
|
||
from app.indexer import Indexer
|
||
from app.media import Category, Media, Bangumi, DouBan
|
||
from app.media.meta import MetaInfo, MetaBase
|
||
from app.mediaserver import MediaServer
|
||
from app.message import Message, MessageCenter
|
||
from app.plugins import PluginManager, EventManager
|
||
from app.rss import Rss
|
||
from app.rsschecker import RssChecker
|
||
from app.scheduler import stop_scheduler
|
||
from app.sites import Sites, SiteUserInfo, SiteSignin, SiteCookie
|
||
from app.subscribe import Subscribe
|
||
from app.sync import Sync, stop_monitor
|
||
from app.torrentremover import TorrentRemover
|
||
from app.utils import StringUtils, EpisodeFormat, RequestUtils, PathUtils, \
|
||
SystemUtils, ExceptionUtils, Torrent
|
||
from app.utils.types import RmtMode, OsType, SearchType, DownloaderType, SyncType, MediaType, MovieTypes, TvTypes, \
|
||
EventType
|
||
from config import RMT_MEDIAEXT, TMDB_IMAGE_W500_URL, RMT_SUBEXT, Config
|
||
from web.backend.search_torrents import search_medias_for_web, search_media_by_message
|
||
from web.backend.web_utils import WebUtils
|
||
|
||
|
||
class WebAction:
|
||
dbhelper = None
|
||
_actions = {}
|
||
|
||
def __init__(self):
|
||
self.dbhelper = DbHelper()
|
||
self._actions = {
|
||
"sch": self.__sch,
|
||
"search": self.__search,
|
||
"download": self.__download,
|
||
"download_link": self.__download_link,
|
||
"download_torrent": self.__download_torrent,
|
||
"pt_start": self.__pt_start,
|
||
"pt_stop": self.__pt_stop,
|
||
"pt_remove": self.__pt_remove,
|
||
"pt_info": self.__pt_info,
|
||
"del_unknown_path": self.__del_unknown_path,
|
||
"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,
|
||
"del_site": self.__del_site,
|
||
"get_site_favicon": self.__get_site_favicon,
|
||
"restart": self.__restart,
|
||
"update_system": self.update_system,
|
||
"reset_db_version": self.__reset_db_version,
|
||
"logout": self.__logout,
|
||
"update_config": self.__update_config,
|
||
"update_directory": self.__update_directory,
|
||
"add_or_edit_sync_path": self.__add_or_edit_sync_path,
|
||
"get_sync_path": self.__get_sync_path,
|
||
"delete_sync_path": self.__delete_sync_path,
|
||
"check_sync_path": self.__check_sync_path,
|
||
"remove_rss_media": self.__remove_rss_media,
|
||
"add_rss_media": self.__add_rss_media,
|
||
"re_identification": self.re_identification,
|
||
"media_info": self.__media_info,
|
||
"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,
|
||
"modify_tmdb_cache": self.__modify_tmdb_cache,
|
||
"rss_detail": self.__rss_detail,
|
||
"truncate_blacklist": self.truncate_blacklist,
|
||
"truncate_rsshistory": self.truncate_rsshistory,
|
||
"add_brushtask": self.__add_brushtask,
|
||
"del_brushtask": self.__del_brushtask,
|
||
"brushtask_detail": self.__brushtask_detail,
|
||
"add_downloader": self.__add_downloader,
|
||
"delete_downloader": self.__delete_downloader,
|
||
"get_downloader": self.__get_downloader,
|
||
"name_test": self.__name_test,
|
||
"rule_test": self.__rule_test,
|
||
"net_test": self.__net_test,
|
||
"add_filtergroup": self.__add_filtergroup,
|
||
"restore_filtergroup": self.__restore_filtergroup,
|
||
"set_default_filtergroup": self.__set_default_filtergroup,
|
||
"del_filtergroup": self.__del_filtergroup,
|
||
"add_filterrule": self.__add_filterrule,
|
||
"del_filterrule": self.__del_filterrule,
|
||
"filterrule_detail": self.__filterrule_detail,
|
||
"get_site_activity": self.__get_site_activity,
|
||
"get_site_history": self.__get_site_history,
|
||
"get_recommend": self.get_recommend,
|
||
"get_downloaded": self.get_downloaded,
|
||
"get_site_seeding_info": self.__get_site_seeding_info,
|
||
"clear_tmdb_cache": self.__clear_tmdb_cache,
|
||
"check_site_attr": self.__check_site_attr,
|
||
"refresh_process": self.__refresh_process,
|
||
"restory_backup": self.__restory_backup,
|
||
"start_mediasync": self.__start_mediasync,
|
||
"mediasync_state": self.__mediasync_state,
|
||
"get_tvseason_list": self.__get_tvseason_list,
|
||
"get_userrss_task": self.__get_userrss_task,
|
||
"delete_userrss_task": self.__delete_userrss_task,
|
||
"update_userrss_task": self.__update_userrss_task,
|
||
"get_rssparser": self.__get_rssparser,
|
||
"delete_rssparser": self.__delete_rssparser,
|
||
"update_rssparser": self.__update_rssparser,
|
||
"run_userrss": self.__run_userrss,
|
||
"run_brushtask": self.__run_brushtask,
|
||
"list_site_resources": self.__list_site_resources,
|
||
"list_rss_articles": self.__list_rss_articles,
|
||
"rss_article_test": self.__rss_article_test,
|
||
"list_rss_history": self.__list_rss_history,
|
||
"rss_articles_check": self.__rss_articles_check,
|
||
"rss_articles_download": self.__rss_articles_download,
|
||
"add_custom_word_group": self.__add_custom_word_group,
|
||
"delete_custom_word_group": self.__delete_custom_word_group,
|
||
"add_or_edit_custom_word": self.__add_or_edit_custom_word,
|
||
"get_custom_word": self.__get_custom_word,
|
||
"delete_custom_word": self.__delete_custom_word,
|
||
"check_custom_words": self.__check_custom_words,
|
||
"export_custom_words": self.__export_custom_words,
|
||
"analyse_import_custom_words_code": self.__analyse_import_custom_words_code,
|
||
"import_custom_words": self.__import_custom_words,
|
||
"get_categories": self.__get_categories,
|
||
"re_rss_history": self.__re_rss_history,
|
||
"delete_rss_history": self.__delete_rss_history,
|
||
"share_filtergroup": self.__share_filtergroup,
|
||
"import_filtergroup": self.__import_filtergroup,
|
||
"get_transfer_statistics": self.get_transfer_statistics,
|
||
"get_library_spacesize": self.get_library_spacesize,
|
||
"get_library_mediacount": self.get_library_mediacount,
|
||
"get_library_playhistory": self.get_library_playhistory,
|
||
"get_search_result": self.get_search_result,
|
||
"search_media_infos": self.search_media_infos,
|
||
"get_movie_rss_list": self.get_movie_rss_list,
|
||
"get_tv_rss_list": self.get_tv_rss_list,
|
||
"get_rss_history": self.get_rss_history,
|
||
"get_transfer_history": self.get_transfer_history,
|
||
"get_unknown_list": self.get_unknown_list,
|
||
"get_unknown_list_by_page": self.get_unknown_list_by_page,
|
||
"get_customwords": self.get_customwords,
|
||
"get_directorysync": self.get_directorysync,
|
||
"get_users": self.get_users,
|
||
"get_filterrules": self.get_filterrules,
|
||
"get_downloading": self.get_downloading,
|
||
"test_site": self.__test_site,
|
||
"get_sub_path": self.__get_sub_path,
|
||
"rename_file": self.__rename_file,
|
||
"delete_files": self.__delete_files,
|
||
"download_subtitle": self.__download_subtitle,
|
||
"get_download_setting": self.__get_download_setting,
|
||
"update_download_setting": self.__update_download_setting,
|
||
"delete_download_setting": self.__delete_download_setting,
|
||
"update_message_client": self.__update_message_client,
|
||
"delete_message_client": self.__delete_message_client,
|
||
"check_message_client": self.__check_message_client,
|
||
"get_message_client": self.__get_message_client,
|
||
"test_message_client": self.__test_message_client,
|
||
"get_sites": self.__get_sites,
|
||
"get_indexers": self.__get_indexers,
|
||
"get_download_dirs": self.__get_download_dirs,
|
||
"find_hardlinks": self.__find_hardlinks,
|
||
"update_sites_cookie_ua": self.__update_sites_cookie_ua,
|
||
"update_site_cookie_ua": self.__update_site_cookie_ua,
|
||
"set_site_captcha_code": self.__set_site_captcha_code,
|
||
"update_torrent_remove_task": self.__update_torrent_remove_task,
|
||
"get_torrent_remove_task": self.__get_torrent_remove_task,
|
||
"delete_torrent_remove_task": self.__delete_torrent_remove_task,
|
||
"get_remove_torrents": self.__get_remove_torrents,
|
||
"auto_remove_torrents": self.__auto_remove_torrents,
|
||
"get_douban_history": self.get_douban_history,
|
||
"delete_douban_history": self.__delete_douban_history,
|
||
"list_brushtask_torrents": self.__list_brushtask_torrents,
|
||
"set_system_config": self.__set_system_config,
|
||
"get_site_user_statistics": self.get_site_user_statistics,
|
||
"send_custom_message": self.send_custom_message,
|
||
"cookiecloud_sync": self.__cookiecloud_sync,
|
||
"media_detail": self.media_detail,
|
||
"media_similar": self.__media_similar,
|
||
"media_recommendations": self.__media_recommendations,
|
||
"media_person": self.__media_person,
|
||
"person_medias": self.__person_medias,
|
||
"save_user_script": self.__save_user_script,
|
||
"run_directory_sync": self.__run_directory_sync,
|
||
"update_plugin_config": self.__update_plugin_config
|
||
}
|
||
|
||
def action(self, cmd, data=None):
|
||
func = self._actions.get(cmd)
|
||
if not func:
|
||
return {"code": -1, "msg": "非授权访问!"}
|
||
else:
|
||
return func(data)
|
||
|
||
def api_action(self, cmd, data=None):
|
||
result = self.action(cmd, data)
|
||
if not result:
|
||
return {
|
||
"code": -1,
|
||
"success": False,
|
||
"message": "服务异常,未获取到返回结果"
|
||
}
|
||
code = result.get("code", result.get("retcode", 0))
|
||
if not code or str(code) == "0":
|
||
success = True
|
||
else:
|
||
success = False
|
||
message = result.get("msg", result.get("retmsg", ""))
|
||
for key in ['code', 'retcode', 'msg', 'retmsg']:
|
||
if key in result:
|
||
result.pop(key)
|
||
return {
|
||
"code": code,
|
||
"success": success,
|
||
"message": message,
|
||
"data": result
|
||
}
|
||
|
||
@staticmethod
|
||
def restart_server():
|
||
"""
|
||
停止进程
|
||
"""
|
||
# 停止定时服务
|
||
stop_scheduler()
|
||
# 停止监控
|
||
stop_monitor()
|
||
# 关闭虚拟显示
|
||
DisplayHelper().stop_service()
|
||
# 关闭刷流
|
||
BrushTask().stop_service()
|
||
# 关闭自定义订阅
|
||
RssChecker().stop_service()
|
||
# 关闭插件
|
||
PluginManager().stop_service()
|
||
# 签退
|
||
logout_user()
|
||
# 重启进程
|
||
if os.name == "nt":
|
||
os.kill(os.getpid(), getattr(signal, "SIGKILL", signal.SIGTERM))
|
||
elif SystemUtils.is_synology():
|
||
os.system(
|
||
"ps -ef | grep -v grep | grep 'python run.py'|awk '{print $2}'|xargs kill -9")
|
||
else:
|
||
os.system("pm2 restart NAStool")
|
||
|
||
@staticmethod
|
||
def handle_message_job(msg, in_from=SearchType.OT, user_id=None, user_name=None):
|
||
"""
|
||
处理消息事件
|
||
"""
|
||
if not msg:
|
||
return
|
||
commands = {
|
||
"/ptr": {"func": TorrentRemover().auto_remove_torrents, "desp": "删种"},
|
||
"/ptt": {"func": Downloader().transfer, "desp": "下载文件转移"},
|
||
"/pts": {"func": SiteSignin().signin, "desp": "站点签到"},
|
||
"/rst": {"func": Sync().transfer_all_sync, "desp": "目录同步"},
|
||
"/rss": {"func": Rss().rssdownload, "desp": "RSS订阅"},
|
||
"/db": {"func": DoubanSync().sync, "desp": "豆瓣同步"},
|
||
"/ssa": {"func": Subscribe().subscribe_search_all, "desp": "订阅搜索"},
|
||
"/tbl": {"func": WebAction().truncate_blacklist, "desp": "清理转移缓存"},
|
||
"/trh": {"func": WebAction().truncate_rsshistory, "desp": "清理RSS缓存"},
|
||
"/utf": {"func": WebAction().unidentification, "desp": "重新识别"},
|
||
"/udt": {"func": WebAction().update_system, "desp": "系统更新"}
|
||
}
|
||
|
||
# 触发事件
|
||
EventManager().send_event(EventType.MessageIncoming, {
|
||
"channel": in_from.value,
|
||
"user_id": user_id,
|
||
"user_name": user_name,
|
||
"message": msg
|
||
|
||
})
|
||
|
||
command = commands.get(msg)
|
||
message = Message()
|
||
|
||
if command:
|
||
# 启动服务
|
||
ThreadHelper().start_thread(command.get("func"), ())
|
||
message.send_channel_msg(
|
||
channel=in_from, title="正在运行 %s ..." % command.get("desp"), user_id=user_id)
|
||
else:
|
||
# 站点检索或者添加订阅
|
||
ThreadHelper().start_thread(search_media_by_message,
|
||
(msg, in_from, user_id, user_name))
|
||
|
||
@staticmethod
|
||
def set_config_value(cfg, cfg_key, cfg_value):
|
||
"""
|
||
根据Key设置配置值
|
||
"""
|
||
# 密码
|
||
if cfg_key == "app.login_password":
|
||
if cfg_value and not cfg_value.startswith("[hash]"):
|
||
cfg['app']['login_password'] = "[hash]%s" % generate_password_hash(
|
||
cfg_value)
|
||
else:
|
||
cfg['app']['login_password'] = cfg_value or "password"
|
||
return cfg
|
||
# 代理
|
||
if cfg_key == "app.proxies":
|
||
if cfg_value:
|
||
if not cfg_value.startswith("http") and not cfg_value.startswith("sock"):
|
||
cfg['app']['proxies'] = {
|
||
"https": "http://%s" % cfg_value, "http": "http://%s" % cfg_value}
|
||
else:
|
||
cfg['app']['proxies'] = {"https": "%s" %
|
||
cfg_value, "http": "%s" % cfg_value}
|
||
else:
|
||
cfg['app']['proxies'] = {"https": None, "http": None}
|
||
return cfg
|
||
# 豆瓣用户列表
|
||
if cfg_key == "douban.users":
|
||
vals = cfg_value.split(",")
|
||
cfg['douban']['users'] = vals
|
||
return cfg
|
||
# 最大支持三层赋值
|
||
keys = cfg_key.split(".")
|
||
if keys:
|
||
if len(keys) == 1:
|
||
cfg[keys[0]] = cfg_value
|
||
elif len(keys) == 2:
|
||
if not cfg.get(keys[0]):
|
||
cfg[keys[0]] = {}
|
||
cfg[keys[0]][keys[1]] = cfg_value
|
||
elif len(keys) == 3:
|
||
if cfg.get(keys[0]):
|
||
if not cfg[keys[0]].get(keys[1]) or isinstance(cfg[keys[0]][keys[1]], str):
|
||
cfg[keys[0]][keys[1]] = {}
|
||
cfg[keys[0]][keys[1]][keys[2]] = cfg_value
|
||
else:
|
||
cfg[keys[0]] = {}
|
||
cfg[keys[0]][keys[1]] = {}
|
||
cfg[keys[0]][keys[1]][keys[2]] = cfg_value
|
||
|
||
return cfg
|
||
|
||
@staticmethod
|
||
def set_config_directory(cfg, oper, cfg_key, cfg_value, update_value=None):
|
||
"""
|
||
更新目录数据
|
||
"""
|
||
|
||
def remove_sync_path(obj, key):
|
||
if not isinstance(obj, list):
|
||
return []
|
||
ret_obj = []
|
||
for item in obj:
|
||
if item.split("@")[0].replace("\\", "/") != key.split("@")[0].replace("\\", "/"):
|
||
ret_obj.append(item)
|
||
return ret_obj
|
||
|
||
# 最大支持二层赋值
|
||
keys = cfg_key.split(".")
|
||
if keys:
|
||
if len(keys) == 1:
|
||
if cfg.get(keys[0]):
|
||
if not isinstance(cfg[keys[0]], list):
|
||
cfg[keys[0]] = [cfg[keys[0]]]
|
||
if oper == "add":
|
||
cfg[keys[0]].append(cfg_value)
|
||
elif oper == "sub":
|
||
cfg[keys[0]].remove(cfg_value)
|
||
if not cfg[keys[0]]:
|
||
cfg[keys[0]] = None
|
||
elif oper == "set":
|
||
cfg[keys[0]].remove(cfg_value)
|
||
if update_value:
|
||
cfg[keys[0]].append(update_value)
|
||
else:
|
||
cfg[keys[0]] = cfg_value
|
||
elif len(keys) == 2:
|
||
if cfg.get(keys[0]):
|
||
if not cfg[keys[0]].get(keys[1]):
|
||
cfg[keys[0]][keys[1]] = []
|
||
if not isinstance(cfg[keys[0]][keys[1]], list):
|
||
cfg[keys[0]][keys[1]] = [cfg[keys[0]][keys[1]]]
|
||
if oper == "add":
|
||
cfg[keys[0]][keys[1]].append(
|
||
cfg_value.replace("\\", "/"))
|
||
elif oper == "sub":
|
||
cfg[keys[0]][keys[1]] = remove_sync_path(
|
||
cfg[keys[0]][keys[1]], cfg_value)
|
||
if not cfg[keys[0]][keys[1]]:
|
||
cfg[keys[0]][keys[1]] = None
|
||
elif oper == "set":
|
||
cfg[keys[0]][keys[1]] = remove_sync_path(
|
||
cfg[keys[0]][keys[1]], cfg_value)
|
||
if update_value:
|
||
cfg[keys[0]][keys[1]].append(
|
||
update_value.replace("\\", "/"))
|
||
else:
|
||
cfg[keys[0]] = {}
|
||
cfg[keys[0]][keys[1]] = cfg_value.replace("\\", "/")
|
||
return cfg
|
||
|
||
@staticmethod
|
||
def __sch(data):
|
||
"""
|
||
启动定时服务
|
||
"""
|
||
commands = {
|
||
"autoremovetorrents": TorrentRemover().auto_remove_torrents,
|
||
"pttransfer": Downloader().transfer,
|
||
"ptsignin": SiteSignin().signin,
|
||
"sync": Sync().transfer_all_sync,
|
||
"rssdownload": Rss().rssdownload,
|
||
"douban": DoubanSync().sync,
|
||
"subscribe_search_all": Subscribe().subscribe_search_all,
|
||
}
|
||
sch_item = data.get("item")
|
||
if sch_item and commands.get(sch_item):
|
||
ThreadHelper().start_thread(commands.get(sch_item), ())
|
||
return {"retmsg": "服务已启动", "item": sch_item}
|
||
|
||
@staticmethod
|
||
def __search(data):
|
||
"""
|
||
WEB检索资源
|
||
"""
|
||
search_word = data.get("search_word")
|
||
ident_flag = False if data.get("unident") else True
|
||
filters = data.get("filters")
|
||
tmdbid = data.get("tmdbid")
|
||
media_type = data.get("media_type")
|
||
if media_type:
|
||
if media_type in MovieTypes:
|
||
media_type = MediaType.MOVIE
|
||
else:
|
||
media_type = MediaType.TV
|
||
if search_word:
|
||
ret, ret_msg = search_medias_for_web(content=search_word,
|
||
ident_flag=ident_flag,
|
||
filters=filters,
|
||
tmdbid=tmdbid,
|
||
media_type=media_type)
|
||
if ret != 0:
|
||
return {"code": ret, "msg": ret_msg}
|
||
return {"code": 0}
|
||
|
||
def __download(self, data):
|
||
"""
|
||
从WEB添加下载
|
||
"""
|
||
dl_id = data.get("id")
|
||
dl_dir = data.get("dir")
|
||
dl_setting = data.get("setting")
|
||
results = self.dbhelper.get_search_result_by_id(dl_id)
|
||
for res in results:
|
||
media = Media().get_media_info(title=res.TORRENT_NAME, subtitle=res.DESCRIPTION)
|
||
if not media:
|
||
continue
|
||
media.set_torrent_info(enclosure=res.ENCLOSURE,
|
||
size=res.SIZE,
|
||
site=res.SITE,
|
||
page_url=res.PAGEURL,
|
||
upload_volume_factor=float(
|
||
res.UPLOAD_VOLUME_FACTOR),
|
||
download_volume_factor=float(res.DOWNLOAD_VOLUME_FACTOR))
|
||
# 添加下载
|
||
ret, ret_msg = Downloader().download(media_info=media,
|
||
download_dir=dl_dir,
|
||
download_setting=dl_setting)
|
||
if ret:
|
||
# 发送消息
|
||
media.user_name = current_user.username
|
||
Message().send_download_message(in_from=SearchType.WEB,
|
||
can_item=media)
|
||
else:
|
||
return {"retcode": -1, "retmsg": ret_msg}
|
||
return {"retcode": 0, "retmsg": ""}
|
||
|
||
@staticmethod
|
||
def __download_link(data):
|
||
"""
|
||
从WEB添加下载链接
|
||
"""
|
||
site = data.get("site")
|
||
enclosure = data.get("enclosure")
|
||
title = data.get("title")
|
||
description = data.get("description")
|
||
page_url = data.get("page_url")
|
||
size = data.get("size")
|
||
seeders = data.get("seeders")
|
||
uploadvolumefactor = data.get("uploadvolumefactor")
|
||
downloadvolumefactor = data.get("downloadvolumefactor")
|
||
dl_dir = data.get("dl_dir")
|
||
dl_setting = data.get("dl_setting")
|
||
if not title or not enclosure:
|
||
return {"code": -1, "msg": "种子信息有误"}
|
||
media = Media().get_media_info(title=title, subtitle=description)
|
||
media.site = site
|
||
media.enclosure = enclosure
|
||
media.page_url = page_url
|
||
media.size = size
|
||
media.upload_volume_factor = float(uploadvolumefactor)
|
||
media.download_volume_factor = float(downloadvolumefactor)
|
||
media.seeders = seeders
|
||
# 添加下载
|
||
ret, ret_msg = Downloader().download(media_info=media,
|
||
download_dir=dl_dir,
|
||
download_setting=dl_setting)
|
||
if ret:
|
||
# 发送消息
|
||
media.user_name = current_user.username
|
||
Message().send_download_message(SearchType.WEB, media)
|
||
return {"code": 0, "msg": "下载成功"}
|
||
else:
|
||
return {"code": 1, "msg": ret_msg or "如连接正常,请检查下载任务是否存在"}
|
||
|
||
@staticmethod
|
||
def __download_torrent(data):
|
||
"""
|
||
从种子文件添加下载
|
||
"""
|
||
|
||
def __download(_media_info, _file_path):
|
||
_media_info.site = "WEB"
|
||
# 添加下载
|
||
ret, ret_msg = Downloader().download(media_info=_media_info,
|
||
download_dir=dl_dir,
|
||
download_setting=dl_setting,
|
||
torrent_file=_file_path)
|
||
# 发送消息
|
||
_media_info.user_name = current_user.username
|
||
if ret:
|
||
Message().send_download_message(SearchType.WEB, _media_info)
|
||
else:
|
||
Message().send_download_fail_message(_media_info, ret_msg)
|
||
|
||
dl_dir = data.get("dl_dir")
|
||
dl_setting = data.get("dl_setting")
|
||
files = data.get("files")
|
||
magnets = data.get("magnets")
|
||
if not files and not magnets:
|
||
return {"code": -1, "msg": "没有种子文件或磁链"}
|
||
for file_item in files:
|
||
if not file_item:
|
||
continue
|
||
file_name = file_item.get("upload", {}).get("filename")
|
||
file_path = os.path.join(Config().get_temp_path(), file_name)
|
||
media_info = Media().get_media_info(title=file_name)
|
||
__download(media_info, file_path)
|
||
for magnet in magnets:
|
||
if not magnet:
|
||
continue
|
||
file_path = None
|
||
title = Torrent().get_magnet_title(magnet)
|
||
if title:
|
||
media_info = Media().get_media_info(title=title)
|
||
else:
|
||
media_info = MetaInfo(title="磁力链接")
|
||
media_info.org_string = magnet
|
||
media_info.set_torrent_info(enclosure=magnet,
|
||
download_volume_factor=0,
|
||
upload_volume_factor=1)
|
||
__download(media_info, file_path)
|
||
return {"code": 0, "msg": "添加下载完成!"}
|
||
|
||
@staticmethod
|
||
def __pt_start(data):
|
||
"""
|
||
开始下载
|
||
"""
|
||
tid = data.get("id")
|
||
if id:
|
||
Downloader().start_torrents(ids=tid)
|
||
return {"retcode": 0, "id": tid}
|
||
|
||
@staticmethod
|
||
def __pt_stop(data):
|
||
"""
|
||
停止下载
|
||
"""
|
||
tid = data.get("id")
|
||
if id:
|
||
Downloader().stop_torrents(ids=tid)
|
||
return {"retcode": 0, "id": tid}
|
||
|
||
@staticmethod
|
||
def __pt_remove(data):
|
||
"""
|
||
删除下载
|
||
"""
|
||
tid = data.get("id")
|
||
if id:
|
||
Downloader().delete_torrents(ids=tid, delete_file=True)
|
||
return {"retcode": 0, "id": tid}
|
||
|
||
@staticmethod
|
||
def __pt_info(data):
|
||
"""
|
||
查询具体种子的信息
|
||
"""
|
||
ids = data.get("ids")
|
||
Client, Torrents = Downloader().get_torrents(torrent_ids=ids)
|
||
DispTorrents = []
|
||
for torrent in Torrents:
|
||
if not torrent:
|
||
continue
|
||
if Client == DownloaderType.QB:
|
||
if torrent.get('state') in ['pausedDL']:
|
||
state = "Stoped"
|
||
speed = "已暂停"
|
||
else:
|
||
state = "Downloading"
|
||
dlspeed = StringUtils.str_filesize(torrent.get('dlspeed'))
|
||
eta = StringUtils.str_timelong(torrent.get('eta'))
|
||
upspeed = StringUtils.str_filesize(torrent.get('upspeed'))
|
||
speed = "%s%sB/s %s%sB/s %s" % (chr(8595),
|
||
dlspeed, chr(8593), upspeed, eta)
|
||
# 进度
|
||
progress = round(torrent.get('progress') * 100)
|
||
# 主键
|
||
key = torrent.get('hash')
|
||
elif Client == DownloaderType.TR:
|
||
if torrent.status in ['stopped']:
|
||
state = "Stoped"
|
||
speed = "已暂停"
|
||
else:
|
||
state = "Downloading"
|
||
dlspeed = StringUtils.str_filesize(torrent.rateDownload)
|
||
upspeed = StringUtils.str_filesize(torrent.rateUpload)
|
||
speed = "%s%sB/s %s%sB/s" % (chr(8595),
|
||
dlspeed, chr(8593), upspeed)
|
||
# 进度
|
||
progress = round(torrent.progress, 1)
|
||
# 主键
|
||
key = torrent.id
|
||
else:
|
||
continue
|
||
torrent_info = {
|
||
'id': key,
|
||
'speed': speed,
|
||
'state': state,
|
||
'progress': progress
|
||
}
|
||
if torrent_info not in DispTorrents:
|
||
DispTorrents.append(torrent_info)
|
||
return {"retcode": 0, "torrents": DispTorrents}
|
||
|
||
def __del_unknown_path(self, data):
|
||
"""
|
||
删除路径
|
||
"""
|
||
tids = data.get("id")
|
||
if isinstance(tids, list):
|
||
for tid in tids:
|
||
if not tid:
|
||
continue
|
||
self.dbhelper.delete_transfer_unknown(tid)
|
||
return {"retcode": 0}
|
||
else:
|
||
retcode = self.dbhelper.delete_transfer_unknown(tids)
|
||
return {"retcode": retcode}
|
||
|
||
def __rename(self, data):
|
||
"""
|
||
手工转移
|
||
"""
|
||
path = dest_dir = None
|
||
syncmod = ModuleConf.RMT_MODES.get(data.get("syncmod"))
|
||
logid = data.get("logid")
|
||
if logid:
|
||
paths = self.dbhelper.get_transfer_path_by_id(logid)
|
||
if paths:
|
||
path = os.path.join(
|
||
paths[0].SOURCE_PATH, paths[0].SOURCE_FILENAME)
|
||
dest_dir = paths[0].DEST
|
||
else:
|
||
return {"retcode": -1, "retmsg": "未查询到转移日志记录"}
|
||
else:
|
||
unknown_id = data.get("unknown_id")
|
||
if unknown_id:
|
||
paths = self.dbhelper.get_unknown_path_by_id(unknown_id)
|
||
if paths:
|
||
path = paths[0].PATH
|
||
dest_dir = paths[0].DEST
|
||
else:
|
||
return {"retcode": -1, "retmsg": "未查询到未识别记录"}
|
||
if not dest_dir:
|
||
dest_dir = ""
|
||
if not path:
|
||
return {"retcode": -1, "retmsg": "输入路径有误"}
|
||
tmdbid = data.get("tmdb")
|
||
mtype = data.get("type")
|
||
season = data.get("season")
|
||
episode_format = data.get("episode_format")
|
||
episode_details = data.get("episode_details")
|
||
episode_offset = data.get("episode_offset")
|
||
min_filesize = data.get("min_filesize")
|
||
if mtype in MovieTypes:
|
||
media_type = MediaType.MOVIE
|
||
elif mtype in TvTypes:
|
||
media_type = MediaType.TV
|
||
else:
|
||
media_type = MediaType.ANIME
|
||
# 如果改次手动修复时一个单文件,自动修复改目录下同名文件,需要配合episode_format生效
|
||
need_fix_all = False
|
||
if os.path.splitext(path)[-1].lower() in RMT_MEDIAEXT and episode_format:
|
||
path = os.path.dirname(path)
|
||
need_fix_all = True
|
||
# 开始转移
|
||
succ_flag, ret_msg = self.__manual_transfer(inpath=path,
|
||
syncmod=syncmod,
|
||
outpath=dest_dir,
|
||
media_type=media_type,
|
||
episode_format=episode_format,
|
||
episode_details=episode_details,
|
||
episode_offset=episode_offset,
|
||
need_fix_all=need_fix_all,
|
||
min_filesize=min_filesize,
|
||
tmdbid=tmdbid,
|
||
season=season)
|
||
if succ_flag:
|
||
if not need_fix_all and not logid:
|
||
# 更新记录状态
|
||
self.dbhelper.update_transfer_unknown_state(path)
|
||
return {"retcode": 0, "retmsg": "转移成功"}
|
||
else:
|
||
return {"retcode": 2, "retmsg": ret_msg}
|
||
|
||
def __rename_udf(self, data):
|
||
"""
|
||
自定义识别
|
||
"""
|
||
inpath = data.get("inpath")
|
||
if not os.path.exists(inpath):
|
||
return {"retcode": -1, "retmsg": "输入路径不存在"}
|
||
outpath = data.get("outpath")
|
||
syncmod = ModuleConf.RMT_MODES.get(data.get("syncmod"))
|
||
tmdbid = data.get("tmdb")
|
||
mtype = data.get("type")
|
||
season = data.get("season")
|
||
episode_format = data.get("episode_format")
|
||
episode_details = data.get("episode_details")
|
||
episode_offset = data.get("episode_offset")
|
||
min_filesize = data.get("min_filesize")
|
||
if mtype in MovieTypes:
|
||
media_type = MediaType.MOVIE
|
||
elif mtype in TvTypes:
|
||
media_type = MediaType.TV
|
||
else:
|
||
media_type = MediaType.ANIME
|
||
# 开始转移
|
||
succ_flag, ret_msg = self.__manual_transfer(inpath=inpath,
|
||
syncmod=syncmod,
|
||
outpath=outpath,
|
||
media_type=media_type,
|
||
episode_format=episode_format,
|
||
episode_details=episode_details,
|
||
episode_offset=episode_offset,
|
||
min_filesize=min_filesize,
|
||
tmdbid=tmdbid,
|
||
season=season)
|
||
if succ_flag:
|
||
return {"retcode": 0, "retmsg": "转移成功"}
|
||
else:
|
||
return {"retcode": 2, "retmsg": ret_msg}
|
||
|
||
@staticmethod
|
||
def __manual_transfer(inpath,
|
||
syncmod,
|
||
outpath=None,
|
||
media_type=None,
|
||
episode_format=None,
|
||
episode_details=None,
|
||
episode_offset=None,
|
||
min_filesize=None,
|
||
tmdbid=None,
|
||
season=None,
|
||
need_fix_all=False
|
||
):
|
||
"""
|
||
开始手工转移文件
|
||
"""
|
||
inpath = os.path.normpath(inpath)
|
||
if outpath:
|
||
outpath = os.path.normpath(outpath)
|
||
if not os.path.exists(inpath):
|
||
return False, "输入路径不存在"
|
||
if tmdbid:
|
||
# 有输入TMDBID
|
||
tmdb_info = Media().get_tmdb_info(mtype=media_type, tmdbid=tmdbid)
|
||
if not tmdb_info:
|
||
return False, "识别失败,无法查询到TMDB信息"
|
||
# 按识别的信息转移
|
||
succ_flag, ret_msg = FileTransfer().transfer_media(in_from=SyncType.MAN,
|
||
in_path=inpath,
|
||
rmt_mode=syncmod,
|
||
target_dir=outpath,
|
||
tmdb_info=tmdb_info,
|
||
media_type=media_type,
|
||
season=season,
|
||
episode=(
|
||
EpisodeFormat(episode_format,
|
||
episode_details,
|
||
episode_offset),
|
||
need_fix_all),
|
||
min_filesize=min_filesize,
|
||
udf_flag=True)
|
||
else:
|
||
# 按识别的信息转移
|
||
succ_flag, ret_msg = FileTransfer().transfer_media(in_from=SyncType.MAN,
|
||
in_path=inpath,
|
||
rmt_mode=syncmod,
|
||
target_dir=outpath,
|
||
media_type=media_type,
|
||
episode=(
|
||
EpisodeFormat(episode_format,
|
||
episode_details,
|
||
episode_offset),
|
||
need_fix_all),
|
||
min_filesize=min_filesize,
|
||
udf_flag=True)
|
||
return succ_flag, ret_msg
|
||
|
||
def __delete_history(self, data):
|
||
"""
|
||
删除识别记录及文件
|
||
"""
|
||
logids = data.get('logids')
|
||
flag = data.get('flag')
|
||
for logid in logids:
|
||
# 读取历史记录
|
||
paths = self.dbhelper.get_transfer_path_by_id(logid)
|
||
if paths:
|
||
# 删除记录
|
||
self.dbhelper.delete_transfer_log_by_id(logid)
|
||
# 根据flag删除文件
|
||
source_path = paths[0].SOURCE_PATH
|
||
source_filename = paths[0].SOURCE_FILENAME
|
||
# 删除该识别记录对应的转移记录
|
||
self.dbhelper.delete_transfer_blacklist("%s/%s" % (source_path, source_filename))
|
||
dest = paths[0].DEST
|
||
dest_path = paths[0].DEST_PATH
|
||
dest_filename = paths[0].DEST_FILENAME
|
||
if flag in ["del_source", "del_all"]:
|
||
del_flag, del_msg = self.delete_media_file(
|
||
source_path, source_filename)
|
||
if not del_flag:
|
||
log.error(f"【History】{del_msg}")
|
||
else:
|
||
log.info(f"【History】{del_msg}")
|
||
if flag in ["del_dest", "del_all"]:
|
||
if dest_path and dest_filename:
|
||
del_flag, del_msg = self.delete_media_file(
|
||
dest_path, dest_filename)
|
||
if not del_flag:
|
||
log.error(f"【History】{del_msg}")
|
||
else:
|
||
log.info(f"【History】{del_msg}")
|
||
else:
|
||
meta_info = MetaInfo(title=source_filename)
|
||
meta_info.title = paths[0].TITLE
|
||
meta_info.category = paths[0].CATEGORY
|
||
meta_info.year = paths[0].YEAR
|
||
if paths[0].SEASON_EPISODE:
|
||
meta_info.begin_season = int(
|
||
str(paths[0].SEASON_EPISODE).replace("S", ""))
|
||
if paths[0].TYPE == MediaType.MOVIE.value:
|
||
meta_info.type = MediaType.MOVIE
|
||
else:
|
||
meta_info.type = MediaType.TV
|
||
# 删除文件
|
||
dest_path = FileTransfer().get_dest_path_by_info(dest=dest, meta_info=meta_info)
|
||
if dest_path and dest_path.find(meta_info.title) != -1:
|
||
rm_parent_dir = False
|
||
if not meta_info.get_season_list():
|
||
# 电影,删除整个目录
|
||
try:
|
||
shutil.rmtree(dest_path)
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
elif not meta_info.get_episode_string():
|
||
# 电视剧但没有集数,删除季目录
|
||
try:
|
||
shutil.rmtree(dest_path)
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
rm_parent_dir = True
|
||
else:
|
||
# 有集数的电视剧,删除对应的集数文件
|
||
for dest_file in PathUtils.get_dir_files(dest_path):
|
||
file_meta_info = MetaInfo(
|
||
os.path.basename(dest_file))
|
||
if file_meta_info.get_episode_list() and set(
|
||
file_meta_info.get_episode_list()
|
||
).issubset(set(meta_info.get_episode_list())):
|
||
try:
|
||
os.remove(dest_file)
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(
|
||
e)
|
||
rm_parent_dir = True
|
||
if rm_parent_dir \
|
||
and not PathUtils.get_dir_files(os.path.dirname(dest_path), exts=RMT_MEDIAEXT):
|
||
# 没有媒体文件时,删除整个目录
|
||
try:
|
||
shutil.rmtree(os.path.dirname(dest_path))
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"retcode": 0}
|
||
|
||
@staticmethod
|
||
def delete_media_file(filedir, filename):
|
||
"""
|
||
删除媒体文件,空目录也支被删除
|
||
"""
|
||
filedir = os.path.normpath(filedir).replace("\\", "/")
|
||
file = os.path.join(filedir, filename)
|
||
try:
|
||
if not os.path.exists(file):
|
||
return False, f"{file} 不存在"
|
||
os.remove(file)
|
||
nfoname = f"{os.path.splitext(filename)[0]}.nfo"
|
||
nfofile = os.path.join(filedir, nfoname)
|
||
if os.path.exists(nfofile):
|
||
os.remove(nfofile)
|
||
# 检查空目录并删除
|
||
if re.findall(r"^S\d{2}|^Season", os.path.basename(filedir), re.I):
|
||
# 当前是季文件夹,判断并删除
|
||
seaon_dir = filedir
|
||
if seaon_dir.count('/') > 1 and not PathUtils.get_dir_files(seaon_dir, exts=RMT_MEDIAEXT):
|
||
shutil.rmtree(seaon_dir)
|
||
# 媒体文件夹
|
||
media_dir = os.path.dirname(seaon_dir)
|
||
else:
|
||
media_dir = filedir
|
||
# 检查并删除媒体文件夹,非根目录且目录大于二级,且没有媒体文件时才会删除
|
||
if media_dir != '/' \
|
||
and media_dir.count('/') > 1 \
|
||
and not re.search(r'[a-zA-Z]:/$', media_dir) \
|
||
and not PathUtils.get_dir_files(media_dir, exts=RMT_MEDIAEXT):
|
||
shutil.rmtree(media_dir)
|
||
return True, f"{file} 删除成功"
|
||
except Exception as e:
|
||
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(list(log.LOG_QUEUE)):
|
||
log_list = list(log.LOG_QUEUE)
|
||
else:
|
||
log_list = list(log.LOG_QUEUE)[-log.LOG_INDEX:]
|
||
log.LOG_INDEX = 0
|
||
else:
|
||
queue_logs = list(log.LOG_QUEUE)
|
||
for message in queue_logs:
|
||
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):
|
||
"""
|
||
检查新版本
|
||
"""
|
||
version, url, flag = WebUtils.get_latest_version()
|
||
if flag:
|
||
return {"code": 0, "version": version, "url": url}
|
||
return {"code": -1, "version": "", "url": ""}
|
||
|
||
def __update_site(self, data):
|
||
"""
|
||
维护站点信息
|
||
"""
|
||
|
||
def __is_site_duplicate(query_name, query_tid):
|
||
# 检查是否重名
|
||
_sites = self.dbhelper.get_site_by_name(name=query_name)
|
||
for site in _sites:
|
||
site_id = site.ID
|
||
if str(site_id) != str(query_tid):
|
||
return True
|
||
return False
|
||
|
||
tid = data.get('site_id')
|
||
name = data.get('site_name')
|
||
site_pri = data.get('site_pri')
|
||
rssurl = data.get('site_rssurl')
|
||
signurl = data.get('site_signurl')
|
||
cookie = data.get('site_cookie')
|
||
note = data.get('site_note')
|
||
if isinstance(note, dict):
|
||
note = json.dumps(note)
|
||
rss_uses = data.get('site_include')
|
||
|
||
if __is_site_duplicate(name, tid):
|
||
return {"code": 400, "msg": "站点名称重复"}
|
||
|
||
if tid:
|
||
sites = self.dbhelper.get_site_by_id(tid)
|
||
# 站点不存在
|
||
if not sites:
|
||
return {"code": 400, "msg": "站点不存在"}
|
||
|
||
old_name = sites[0].NAME
|
||
|
||
ret = self.dbhelper.update_config_site(tid=tid,
|
||
name=name,
|
||
site_pri=site_pri,
|
||
rssurl=rssurl,
|
||
signurl=signurl,
|
||
cookie=cookie,
|
||
note=note,
|
||
rss_uses=rss_uses)
|
||
if ret and (name != old_name):
|
||
# 更新历史站点数据信息
|
||
self.dbhelper.update_site_user_statistics_site_name(
|
||
name, old_name)
|
||
self.dbhelper.update_site_seed_info_site_name(name, old_name)
|
||
self.dbhelper.update_site_statistics_site_name(name, old_name)
|
||
|
||
else:
|
||
ret = self.dbhelper.insert_config_site(name=name,
|
||
site_pri=site_pri,
|
||
rssurl=rssurl,
|
||
signurl=signurl,
|
||
cookie=cookie,
|
||
note=note,
|
||
rss_uses=rss_uses)
|
||
# 生效站点配置
|
||
Sites().init_config()
|
||
# 初始化刷流任务
|
||
BrushTask().init_config()
|
||
return {"code": ret}
|
||
|
||
@staticmethod
|
||
def __get_site(data):
|
||
"""
|
||
查询单个站点信息
|
||
"""
|
||
tid = data.get("id")
|
||
site_free = False
|
||
site_2xfree = False
|
||
site_hr = False
|
||
if tid:
|
||
ret = Sites().get_sites(siteid=tid)
|
||
if ret.get("rssurl"):
|
||
site_attr = Sites().get_grapsite_conf(ret.get("rssurl"))
|
||
if site_attr.get("FREE"):
|
||
site_free = True
|
||
if site_attr.get("2XFREE"):
|
||
site_2xfree = True
|
||
if site_attr.get("HR"):
|
||
site_hr = True
|
||
else:
|
||
ret = []
|
||
return {"code": 0, "site": ret, "site_free": site_free, "site_2xfree": site_2xfree, "site_hr": site_hr}
|
||
|
||
@staticmethod
|
||
def __get_sites(data):
|
||
"""
|
||
查询多个站点信息
|
||
"""
|
||
rss = True if data.get("rss") else False
|
||
brush = True if data.get("brush") else False
|
||
signin = True if data.get("signin") else False
|
||
statistic = True if data.get("statistic") else False
|
||
basic = True if data.get("basic") else False
|
||
if basic:
|
||
sites = Sites().get_site_dict(rss=rss,
|
||
brush=brush,
|
||
signin=signin,
|
||
statistic=statistic)
|
||
else:
|
||
sites = Sites().get_sites(rss=rss,
|
||
brush=brush,
|
||
signin=signin,
|
||
statistic=statistic)
|
||
return {"code": 0, "sites": sites}
|
||
|
||
def __del_site(self, data):
|
||
"""
|
||
删除单个站点信息
|
||
"""
|
||
tid = data.get("id")
|
||
if tid:
|
||
ret = self.dbhelper.delete_config_site(tid)
|
||
Sites().init_config()
|
||
BrushTask().init_config()
|
||
return {"code": ret}
|
||
else:
|
||
return {"code": 0}
|
||
|
||
def __restart(self, data):
|
||
"""
|
||
重启
|
||
"""
|
||
# 退出主进程
|
||
self.restart_server()
|
||
return {"code": 0}
|
||
|
||
def update_system(self, data=None):
|
||
"""
|
||
更新
|
||
"""
|
||
# 升级
|
||
if SystemUtils.is_synology():
|
||
if SystemUtils.execute('/bin/ps -w -x | grep -v grep | grep -w "nastool update" | wc -l') == '0':
|
||
# 调用群晖套件内置命令升级
|
||
os.system('nastool update')
|
||
# 重启
|
||
self.restart_server()
|
||
else:
|
||
# 清除git代理
|
||
os.system("git config --global --unset http.proxy")
|
||
os.system("git config --global --unset https.proxy")
|
||
# 设置git代理
|
||
proxy = Config().get_proxies() or {}
|
||
http_proxy = proxy.get("http")
|
||
https_proxy = proxy.get("https")
|
||
if http_proxy or https_proxy:
|
||
os.system(
|
||
f"git config --global http.proxy {http_proxy or https_proxy}")
|
||
os.system(
|
||
f"git config --global https.proxy {https_proxy or http_proxy}")
|
||
# 清理
|
||
os.system("git clean -dffx")
|
||
# 升级
|
||
branch = "dev" if os.environ.get(
|
||
"NASTOOL_VERSION") == "dev" else "master"
|
||
os.system(f"git fetch --depth 1 origin {branch}")
|
||
os.system(f"git reset --hard origin/{branch}")
|
||
os.system("git submodule update --init --recursive")
|
||
# 安装依赖
|
||
os.system('pip install -r /nas-tools/requirements.txt')
|
||
# 重启
|
||
self.restart_server()
|
||
return {"code": 0}
|
||
|
||
def __reset_db_version(self, data):
|
||
"""
|
||
重置数据库版本
|
||
"""
|
||
try:
|
||
self.dbhelper.drop_table("alembic_version")
|
||
return {"code": 0}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
@staticmethod
|
||
def __logout(data):
|
||
"""
|
||
注销
|
||
"""
|
||
logout_user()
|
||
return {"code": 0}
|
||
|
||
def __update_config(self, data):
|
||
"""
|
||
更新配置信息
|
||
"""
|
||
cfg = Config().get_config()
|
||
cfgs = dict(data).items()
|
||
# 仅测试不保存
|
||
config_test = False
|
||
# 修改配置
|
||
for key, value in cfgs:
|
||
if key == "test" and value:
|
||
config_test = True
|
||
continue
|
||
# 生效配置
|
||
cfg = self.set_config_value(cfg, key, value)
|
||
|
||
# 保存配置
|
||
if not config_test:
|
||
Config().save_config(cfg)
|
||
|
||
return {"code": 0}
|
||
|
||
def __add_or_edit_sync_path(self, data):
|
||
"""
|
||
维护同步目录
|
||
"""
|
||
sid = data.get("sid")
|
||
source = data.get("from")
|
||
dest = data.get("to")
|
||
unknown = data.get("unknown")
|
||
mode = data.get("syncmod")
|
||
rename = 1 if StringUtils.to_bool(data.get("rename"), False) else 0
|
||
enabled = 1 if StringUtils.to_bool(data.get("enabled"), False) else 0
|
||
# 源目录检查
|
||
if not source:
|
||
return {"code": 1, "msg": f'源目录不能为空'}
|
||
if not os.path.exists(source):
|
||
return {"code": 1, "msg": f'{source}目录不存在'}
|
||
# windows目录用\,linux目录用/
|
||
source = os.path.normpath(source)
|
||
# 目的目录检查,目的目录可为空
|
||
if dest:
|
||
dest = os.path.normpath(dest)
|
||
if PathUtils.is_path_in_path(source, dest):
|
||
return {"code": 1, "msg": "目的目录不可包含在源目录中"}
|
||
if unknown:
|
||
unknown = os.path.normpath(unknown)
|
||
|
||
# 硬链接不能跨盘
|
||
if mode == "link" and dest:
|
||
common_path = os.path.commonprefix([source, dest])
|
||
if not common_path or common_path == "/":
|
||
return {"code": 1, "msg": "硬链接不能跨盘"}
|
||
|
||
# 编辑先删再增
|
||
if sid:
|
||
self.dbhelper.delete_config_sync_path(sid)
|
||
# 若启用,则关闭其他相同源目录的同步目录
|
||
if enabled == 1:
|
||
self.dbhelper.check_config_sync_paths(source=source,
|
||
enabled=0)
|
||
# 插入数据库
|
||
self.dbhelper.insert_config_sync_path(source=source,
|
||
dest=dest,
|
||
unknown=unknown,
|
||
mode=mode,
|
||
rename=rename,
|
||
enabled=enabled)
|
||
Sync().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
|
||
def __get_sync_path(self, data):
|
||
"""
|
||
查询同步目录
|
||
"""
|
||
try:
|
||
sid = data.get("sid")
|
||
sync_item = self.dbhelper.get_config_sync_paths(sid=sid)[0]
|
||
syncpath = {'id': sync_item.ID,
|
||
'from': sync_item.SOURCE,
|
||
'to': sync_item.DEST or "",
|
||
'unknown': sync_item.UNKNOWN or "",
|
||
'syncmod': sync_item.MODE,
|
||
'rename': sync_item.RENAME,
|
||
'enabled': sync_item.ENABLED}
|
||
return {"code": 0, "data": syncpath}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": "查询识别词失败"}
|
||
|
||
def __delete_sync_path(self, data):
|
||
"""
|
||
移出同步目录
|
||
"""
|
||
sid = data.get("sid")
|
||
self.dbhelper.delete_config_sync_path(sid)
|
||
Sync().init_config()
|
||
return {"code": 0}
|
||
|
||
def __check_sync_path(self, data):
|
||
"""
|
||
维护同步目录
|
||
"""
|
||
flag = data.get("flag")
|
||
sid = data.get("sid")
|
||
checked = data.get("checked")
|
||
if flag == "rename":
|
||
self.dbhelper.check_config_sync_paths(sid=sid,
|
||
rename=1 if checked else 0)
|
||
Sync().init_config()
|
||
return {"code": 0}
|
||
elif flag == "enable":
|
||
# 若启用,则关闭其他相同源目录的同步目录
|
||
if checked:
|
||
sync_item = self.dbhelper.get_config_sync_paths(sid=sid)[0]
|
||
self.dbhelper.check_config_sync_paths(source=sync_item.SOURCE,
|
||
enabled=0)
|
||
self.dbhelper.check_config_sync_paths(sid=sid,
|
||
enabled=1 if checked else 0)
|
||
Sync().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
def __remove_rss_media(self, data):
|
||
"""
|
||
移除RSS订阅
|
||
"""
|
||
name = data.get("name")
|
||
mtype = data.get("type")
|
||
year = data.get("year")
|
||
season = data.get("season")
|
||
rssid = data.get("rssid")
|
||
page = data.get("page")
|
||
tmdbid = data.get("tmdbid")
|
||
if not str(tmdbid).isdigit():
|
||
tmdbid = None
|
||
if name:
|
||
name = MetaInfo(title=name).get_name()
|
||
if mtype:
|
||
if mtype in MovieTypes:
|
||
self.dbhelper.delete_rss_movie(
|
||
title=name, year=year, rssid=rssid, tmdbid=tmdbid)
|
||
else:
|
||
self.dbhelper.delete_rss_tv(
|
||
title=name, season=season, rssid=rssid, tmdbid=tmdbid)
|
||
return {"code": 0, "page": page, "name": name}
|
||
|
||
def __add_rss_media(self, data):
|
||
"""
|
||
添加RSS订阅
|
||
"""
|
||
name = data.get("name")
|
||
_subscribe = Subscribe()
|
||
year = data.get("year")
|
||
keyword = data.get("keyword")
|
||
season = data.get("season")
|
||
fuzzy_match = data.get("fuzzy_match")
|
||
mediaid = data.get("mediaid")
|
||
rss_sites = data.get("rss_sites")
|
||
search_sites = data.get("search_sites")
|
||
over_edition = data.get("over_edition")
|
||
filter_restype = data.get("filter_restype")
|
||
filter_pix = data.get("filter_pix")
|
||
filter_team = data.get("filter_team")
|
||
filter_rule = data.get("filter_rule")
|
||
save_path = data.get("save_path")
|
||
download_setting = data.get("download_setting")
|
||
total_ep = data.get("total_ep")
|
||
current_ep = data.get("current_ep")
|
||
rssid = data.get("rssid")
|
||
page = data.get("page")
|
||
mtype = MediaType.MOVIE if data.get(
|
||
"type") in MovieTypes else MediaType.TV
|
||
media_info = None
|
||
if isinstance(season, list):
|
||
code = 0
|
||
msg = ""
|
||
for sea in season:
|
||
code, msg, media_info = _subscribe.add_rss_subscribe(mtype=mtype,
|
||
name=name,
|
||
year=year,
|
||
keyword=keyword,
|
||
season=sea,
|
||
fuzzy_match=fuzzy_match,
|
||
mediaid=mediaid,
|
||
rss_sites=rss_sites,
|
||
search_sites=search_sites,
|
||
over_edition=over_edition,
|
||
filter_restype=filter_restype,
|
||
filter_pix=filter_pix,
|
||
filter_team=filter_team,
|
||
filter_rule=filter_rule,
|
||
save_path=save_path,
|
||
download_setting=download_setting,
|
||
rssid=rssid)
|
||
if code != 0:
|
||
break
|
||
else:
|
||
code, msg, media_info = _subscribe.add_rss_subscribe(mtype=mtype,
|
||
name=name,
|
||
year=year,
|
||
keyword=keyword,
|
||
season=season,
|
||
fuzzy_match=fuzzy_match,
|
||
mediaid=mediaid,
|
||
rss_sites=rss_sites,
|
||
search_sites=search_sites,
|
||
over_edition=over_edition,
|
||
filter_restype=filter_restype,
|
||
filter_pix=filter_pix,
|
||
filter_team=filter_team,
|
||
filter_rule=filter_rule,
|
||
save_path=save_path,
|
||
download_setting=download_setting,
|
||
total_ep=total_ep,
|
||
current_ep=current_ep,
|
||
rssid=rssid)
|
||
if not rssid and media_info:
|
||
if mtype == MediaType.MOVIE:
|
||
rssid = self.dbhelper.get_rss_movie_id(
|
||
title=name, tmdbid=media_info.tmdb_id)
|
||
else:
|
||
rssid = self.dbhelper.get_rss_tv_id(
|
||
title=name, tmdbid=media_info.tmdb_id)
|
||
return {"code": code, "msg": msg, "page": page, "name": name, "rssid": rssid}
|
||
|
||
def re_identification(self, data):
|
||
"""
|
||
未识别的重新识别
|
||
"""
|
||
flag = data.get("flag")
|
||
ids = data.get("ids")
|
||
ret_flag = True
|
||
ret_msg = []
|
||
if flag == "unidentification":
|
||
for wid in ids:
|
||
paths = self.dbhelper.get_unknown_path_by_id(wid)
|
||
if paths:
|
||
path = paths[0].PATH
|
||
dest_dir = paths[0].DEST
|
||
rmt_mode = ModuleConf.get_enum_item(
|
||
RmtMode, paths[0].MODE) if paths[0].MODE else None
|
||
else:
|
||
return {"retcode": -1, "retmsg": "未查询到未识别记录"}
|
||
if not dest_dir:
|
||
dest_dir = ""
|
||
if not path:
|
||
return {"retcode": -1, "retmsg": "未识别路径有误"}
|
||
succ_flag, msg = FileTransfer().transfer_media(in_from=SyncType.MAN,
|
||
rmt_mode=rmt_mode,
|
||
in_path=path,
|
||
target_dir=dest_dir)
|
||
if succ_flag:
|
||
self.dbhelper.update_transfer_unknown_state(path)
|
||
else:
|
||
ret_flag = False
|
||
if msg not in ret_msg:
|
||
ret_msg.append(msg)
|
||
elif flag == "history":
|
||
for wid in ids:
|
||
paths = self.dbhelper.get_transfer_path_by_id(wid)
|
||
if paths:
|
||
path = os.path.join(
|
||
paths[0].SOURCE_PATH, paths[0].SOURCE_FILENAME)
|
||
dest_dir = paths[0].DEST
|
||
rmt_mode = ModuleConf.get_enum_item(
|
||
RmtMode, paths[0].MODE) if paths[0].MODE else None
|
||
else:
|
||
return {"retcode": -1, "retmsg": "未查询到转移日志记录"}
|
||
if not dest_dir:
|
||
dest_dir = ""
|
||
if not path:
|
||
return {"retcode": -1, "retmsg": "未识别路径有误"}
|
||
succ_flag, msg = FileTransfer().transfer_media(in_from=SyncType.MAN,
|
||
rmt_mode=rmt_mode,
|
||
in_path=path,
|
||
target_dir=dest_dir)
|
||
if not succ_flag:
|
||
ret_flag = False
|
||
if msg not in ret_msg:
|
||
ret_msg.append(msg)
|
||
if ret_flag:
|
||
return {"retcode": 0, "retmsg": "转移成功"}
|
||
else:
|
||
return {"retcode": 2, "retmsg": "、".join(ret_msg)}
|
||
|
||
def __media_info(self, data):
|
||
"""
|
||
查询媒体信息
|
||
"""
|
||
mediaid = data.get("id")
|
||
mtype = data.get("type")
|
||
title = data.get("title")
|
||
year = data.get("year")
|
||
page = data.get("page")
|
||
rssid = data.get("rssid")
|
||
seasons = []
|
||
link_url = ""
|
||
vote_average = 0
|
||
poster_path = ""
|
||
release_date = ""
|
||
overview = ""
|
||
# 类型
|
||
if mtype in MovieTypes:
|
||
media_type = MediaType.MOVIE
|
||
else:
|
||
media_type = MediaType.TV
|
||
|
||
# 先取订阅信息
|
||
rssid_ok = False
|
||
if rssid:
|
||
rssid = str(rssid)
|
||
if media_type == MediaType.MOVIE:
|
||
rssinfo = Subscribe().get_subscribe_movies(rid=rssid)
|
||
else:
|
||
rssinfo = Subscribe().get_subscribe_tvs(rid=rssid)
|
||
if not rssinfo:
|
||
return {
|
||
"code": 1,
|
||
"retmsg": "无法查询到订阅信息",
|
||
"rssid": rssid,
|
||
"type_str": media_type.value
|
||
}
|
||
overview = rssinfo[rssid].get("overview")
|
||
poster_path = rssinfo[rssid].get("poster")
|
||
title = rssinfo[rssid].get("name")
|
||
vote_average = rssinfo[rssid].get("vote")
|
||
year = rssinfo[rssid].get("year")
|
||
release_date = rssinfo[rssid].get("release_date")
|
||
link_url = Media().get_detail_url(mtype=media_type,
|
||
tmdbid=rssinfo[rssid].get("tmdbid"))
|
||
if overview and poster_path:
|
||
rssid_ok = True
|
||
|
||
# 订阅信息不足
|
||
if not rssid_ok:
|
||
if mediaid:
|
||
media = WebUtils.get_mediainfo_from_id(
|
||
mtype=media_type, mediaid=mediaid)
|
||
else:
|
||
media = Media().get_media_info(
|
||
title=f"{title} {year}", mtype=media_type)
|
||
if not media or not media.tmdb_info:
|
||
return {
|
||
"code": 1,
|
||
"retmsg": "无法查询到TMDB信息",
|
||
"rssid": rssid,
|
||
"type_str": media_type.value
|
||
}
|
||
if not mediaid:
|
||
mediaid = media.tmdb_id
|
||
link_url = media.get_detail_url()
|
||
overview = media.overview
|
||
poster_path = media.get_poster_image()
|
||
title = media.title
|
||
vote_average = round(float(media.vote_average or 0), 1)
|
||
year = media.year
|
||
if media_type != MediaType.MOVIE:
|
||
release_date = media.tmdb_info.get('first_air_date')
|
||
seasons = [{
|
||
"text": "第%s季" % cn2an.an2cn(season.get("season_number"), mode='low'),
|
||
"num": season.get("season_number")} for season in
|
||
Media().get_tmdb_tv_seasons(tv_info=media.tmdb_info)]
|
||
else:
|
||
release_date = media.tmdb_info.get('release_date')
|
||
|
||
# 查订阅信息
|
||
if not rssid:
|
||
if media_type == MediaType.MOVIE:
|
||
rssid = self.dbhelper.get_rss_movie_id(
|
||
title=title, tmdbid=mediaid)
|
||
else:
|
||
rssid = self.dbhelper.get_rss_tv_id(
|
||
title=title, tmdbid=mediaid)
|
||
|
||
return {
|
||
"code": 0,
|
||
"type": mtype,
|
||
"type_str": media_type.value,
|
||
"page": page,
|
||
"title": title,
|
||
"vote_average": vote_average,
|
||
"poster_path": poster_path,
|
||
"release_date": release_date,
|
||
"year": year,
|
||
"overview": overview,
|
||
"link_url": link_url,
|
||
"tmdbid": mediaid,
|
||
"rssid": rssid,
|
||
"seasons": seasons
|
||
}
|
||
|
||
@staticmethod
|
||
def __test_connection(data):
|
||
"""
|
||
测试连通性
|
||
"""
|
||
# 支持两种传入方式:命令数组或单个命令,单个命令时xx|xx模式解析为模块和类,进行动态引入
|
||
command = data.get("command")
|
||
ret = None
|
||
if command:
|
||
try:
|
||
module_obj = None
|
||
if isinstance(command, list):
|
||
for cmd_str in command:
|
||
ret = eval(cmd_str)
|
||
if not ret:
|
||
break
|
||
else:
|
||
if command.find("|") != -1:
|
||
module = command.split("|")[0]
|
||
class_name = command.split("|")[1]
|
||
module_obj = getattr(
|
||
importlib.import_module(module), class_name)()
|
||
if hasattr(module_obj, "init_config"):
|
||
module_obj.init_config()
|
||
ret = module_obj.get_status()
|
||
else:
|
||
ret = eval(command)
|
||
# 重载配置
|
||
Config().init_config()
|
||
if module_obj:
|
||
if hasattr(module_obj, "init_config"):
|
||
module_obj.init_config()
|
||
except Exception as e:
|
||
ret = None
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 0 if ret else 1}
|
||
return {"code": 0}
|
||
|
||
def __user_manager(self, data):
|
||
"""
|
||
用户管理
|
||
"""
|
||
oper = data.get("oper")
|
||
name = data.get("name")
|
||
if oper == "add":
|
||
password = generate_password_hash(str(data.get("password")))
|
||
pris = data.get("pris")
|
||
if isinstance(pris, list):
|
||
pris = ",".join(pris)
|
||
ret = self.dbhelper.insert_user(name, password, pris)
|
||
else:
|
||
ret = self.dbhelper.delete_user(name)
|
||
|
||
if ret == 1 or ret:
|
||
return {"code": 0, "success": False}
|
||
return {"code": -1, "success": False, 'message': '操作失败'}
|
||
|
||
@staticmethod
|
||
def __refresh_rss(data):
|
||
"""
|
||
重新搜索RSS
|
||
"""
|
||
mtype = data.get("type")
|
||
rssid = data.get("rssid")
|
||
page = data.get("page")
|
||
if mtype == "MOV":
|
||
ThreadHelper().start_thread(Subscribe().subscribe_search_movie, (rssid,))
|
||
else:
|
||
ThreadHelper().start_thread(Subscribe().subscribe_search_tv, (rssid,))
|
||
return {"code": 0, "page": page}
|
||
|
||
@staticmethod
|
||
def get_system_message(lst_time):
|
||
messages = MessageCenter().get_system_messages(lst_time=lst_time)
|
||
if messages:
|
||
lst_time = messages[0].get("time")
|
||
return {
|
||
"code": 0,
|
||
"message": messages,
|
||
"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")
|
||
message_html = []
|
||
for message in list(reversed(messages)):
|
||
level = "bg-red" if message.get("level") == "ERROR" else ""
|
||
content = re.sub(r"#+", "<br>",
|
||
re.sub(r"<[^>]+>", "",
|
||
re.sub(r"<br/?>", "####", message.get("content"), flags=re.IGNORECASE)))
|
||
message_html.append(f"""
|
||
<div class="list-group-item">
|
||
<div class="row align-items-center">
|
||
<div class="col-auto">
|
||
<span class="status-dot {level} d-block"></span>
|
||
</div>
|
||
<div class="col text-truncate">
|
||
<span class="text-wrap">{message.get("title")}</span>
|
||
<div class="d-block text-muted text-truncate mt-n1 text-wrap">{content}</div>
|
||
<div class="d-block text-muted text-truncate mt-n1 text-wrap">{message.get("time")}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
""")
|
||
return {"code": 0, "message": message_html, "lst_time": lst_time}
|
||
|
||
@staticmethod
|
||
def __delete_tmdb_cache(data):
|
||
"""
|
||
删除tmdb缓存
|
||
"""
|
||
if MetaHelper().delete_meta_data(data.get("cache_key")):
|
||
MetaHelper().save_meta_data()
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __movie_calendar_data(data):
|
||
"""
|
||
查询电影上映日期
|
||
"""
|
||
tid = data.get("id")
|
||
rssid = data.get("rssid")
|
||
if tid and tid.startswith("DB:"):
|
||
doubanid = tid.replace("DB:", "")
|
||
douban_info = DouBan().get_douban_detail(
|
||
doubanid=doubanid, mtype=MediaType.MOVIE)
|
||
if not douban_info:
|
||
return {"code": 1, "retmsg": "无法查询到豆瓣信息"}
|
||
poster_path = douban_info.get("cover_url") or ""
|
||
title = douban_info.get("title")
|
||
rating = douban_info.get("rating", {}) or {}
|
||
vote_average = rating.get("value") or "无"
|
||
release_date = douban_info.get("pubdate")
|
||
if release_date:
|
||
release_date = re.sub(
|
||
r"\(.*\)", "", douban_info.get("pubdate")[0])
|
||
if not release_date:
|
||
return {"code": 1, "retmsg": "上映日期不正确"}
|
||
else:
|
||
return {"code": 0,
|
||
"type": "电影",
|
||
"title": title,
|
||
"start": release_date,
|
||
"id": tid,
|
||
"year": release_date[0:4] if release_date else "",
|
||
"poster": poster_path,
|
||
"vote_average": vote_average,
|
||
"rssid": rssid
|
||
}
|
||
else:
|
||
if tid:
|
||
tmdb_info = Media().get_tmdb_info(mtype=MediaType.MOVIE, tmdbid=tid)
|
||
else:
|
||
return {"code": 1, "retmsg": "没有TMDBID信息"}
|
||
if not tmdb_info:
|
||
return {"code": 1, "retmsg": "无法查询到TMDB信息"}
|
||
poster_path = TMDB_IMAGE_W500_URL % tmdb_info.get('poster_path') if tmdb_info.get(
|
||
'poster_path') else ""
|
||
title = tmdb_info.get('title')
|
||
vote_average = tmdb_info.get("vote_average")
|
||
release_date = tmdb_info.get('release_date')
|
||
if not release_date:
|
||
return {"code": 1, "retmsg": "上映日期不正确"}
|
||
else:
|
||
return {"code": 0,
|
||
"type": "电影",
|
||
"title": title,
|
||
"start": release_date,
|
||
"id": tid,
|
||
"year": release_date[0:4] if release_date else "",
|
||
"poster": poster_path,
|
||
"vote_average": vote_average,
|
||
"rssid": rssid
|
||
}
|
||
|
||
@staticmethod
|
||
def __tv_calendar_data(data):
|
||
"""
|
||
查询电视剧上映日期
|
||
"""
|
||
tid = data.get("id")
|
||
season = data.get("season")
|
||
name = data.get("name")
|
||
rssid = data.get("rssid")
|
||
if tid and tid.startswith("DB:"):
|
||
doubanid = tid.replace("DB:", "")
|
||
douban_info = DouBan().get_douban_detail(doubanid=doubanid, mtype=MediaType.TV)
|
||
if not douban_info:
|
||
return {"code": 1, "retmsg": "无法查询到豆瓣信息"}
|
||
poster_path = douban_info.get("cover_url") or ""
|
||
title = douban_info.get("title")
|
||
rating = douban_info.get("rating", {}) or {}
|
||
vote_average = rating.get("value") or "无"
|
||
release_date = re.sub(r"\(.*\)", "", douban_info.get("pubdate")[0])
|
||
if not release_date:
|
||
return {"code": 1, "retmsg": "上映日期不正确"}
|
||
else:
|
||
return {"code": 0,
|
||
"type": "电视剧",
|
||
"title": title,
|
||
"start": release_date,
|
||
"id": tid,
|
||
"year": release_date[0:4] if release_date else "",
|
||
"poster": poster_path,
|
||
"vote_average": vote_average,
|
||
"rssid": rssid
|
||
}
|
||
else:
|
||
if tid:
|
||
tmdb_info = Media().get_tmdb_tv_season_detail(tmdbid=tid, season=season)
|
||
else:
|
||
return {"code": 1, "retmsg": "没有TMDBID信息"}
|
||
if not tmdb_info:
|
||
return {"code": 1, "retmsg": "无法查询到TMDB信息"}
|
||
episode_events = []
|
||
air_date = tmdb_info.get("air_date")
|
||
if not tmdb_info.get("poster_path"):
|
||
tv_tmdb_info = Media().get_tmdb_info(mtype=MediaType.TV, tmdbid=tid)
|
||
if tv_tmdb_info:
|
||
poster_path = TMDB_IMAGE_W500_URL % tv_tmdb_info.get(
|
||
"poster_path")
|
||
else:
|
||
poster_path = ""
|
||
else:
|
||
poster_path = TMDB_IMAGE_W500_URL % tmdb_info.get(
|
||
"poster_path")
|
||
year = air_date[0:4] if air_date else ""
|
||
for episode in tmdb_info.get("episodes"):
|
||
episode_events.append({
|
||
"type": "剧集",
|
||
"title": "%s 第%s季第%s集" % (
|
||
name,
|
||
season,
|
||
episode.get("episode_number")
|
||
) if season != 1 else "%s 第%s集" % (
|
||
name,
|
||
episode.get("episode_number")
|
||
),
|
||
"start": episode.get("air_date"),
|
||
"id": tid,
|
||
"year": year,
|
||
"poster": poster_path,
|
||
"vote_average": episode.get("vote_average") or "无",
|
||
"rssid": rssid
|
||
})
|
||
return {"code": 0, "events": episode_events}
|
||
|
||
@staticmethod
|
||
def __rss_detail(data):
|
||
rid = data.get("rssid")
|
||
mtype = data.get("rsstype")
|
||
if mtype in MovieTypes:
|
||
rssdetail = Subscribe().get_subscribe_movies(rid=rid)
|
||
if not rssdetail:
|
||
return {"code": 1}
|
||
rssdetail = list(rssdetail.values())[0]
|
||
rssdetail["type"] = "MOV"
|
||
else:
|
||
rssdetail = Subscribe().get_subscribe_tvs(rid=rid)
|
||
if not rssdetail:
|
||
return {"code": 1}
|
||
rssdetail = list(rssdetail.values())[0]
|
||
rssdetail["type"] = "TV"
|
||
return {"code": 0, "detail": rssdetail}
|
||
|
||
@staticmethod
|
||
def __modify_tmdb_cache(data):
|
||
"""
|
||
修改TMDB缓存的标题
|
||
"""
|
||
if MetaHelper().modify_meta_data(data.get("key"), data.get("title")):
|
||
MetaHelper().save_meta_data(force=True)
|
||
return {"code": 0}
|
||
|
||
def truncate_blacklist(self, data):
|
||
"""
|
||
清空文件转移黑名单记录
|
||
"""
|
||
self.dbhelper.truncate_transfer_blacklist()
|
||
return {"code": 0}
|
||
|
||
def truncate_rsshistory(self, data):
|
||
"""
|
||
清空RSS历史记录
|
||
"""
|
||
self.dbhelper.truncate_rss_history()
|
||
self.dbhelper.truncate_rss_episodes()
|
||
return {"code": 0}
|
||
|
||
def __add_brushtask(self, data):
|
||
"""
|
||
新增刷流任务
|
||
"""
|
||
# 输入值
|
||
brushtask_id = data.get("brushtask_id")
|
||
brushtask_name = data.get("brushtask_name")
|
||
brushtask_site = data.get("brushtask_site")
|
||
brushtask_interval = data.get("brushtask_interval")
|
||
brushtask_downloader = data.get("brushtask_downloader")
|
||
brushtask_totalsize = data.get("brushtask_totalsize")
|
||
brushtask_state = data.get("brushtask_state")
|
||
brushtask_transfer = 'Y' if data.get("brushtask_transfer") else 'N'
|
||
brushtask_sendmessage = 'Y' if data.get(
|
||
"brushtask_sendmessage") else 'N'
|
||
brushtask_forceupload = 'Y' if data.get(
|
||
"brushtask_forceupload") else 'N'
|
||
brushtask_free = data.get("brushtask_free")
|
||
brushtask_hr = data.get("brushtask_hr")
|
||
brushtask_torrent_size = data.get("brushtask_torrent_size")
|
||
brushtask_include = data.get("brushtask_include")
|
||
brushtask_exclude = data.get("brushtask_exclude")
|
||
brushtask_dlcount = data.get("brushtask_dlcount")
|
||
brushtask_peercount = data.get("brushtask_peercount")
|
||
brushtask_seedtime = data.get("brushtask_seedtime")
|
||
brushtask_seedratio = data.get("brushtask_seedratio")
|
||
brushtask_seedsize = data.get("brushtask_seedsize")
|
||
brushtask_dltime = data.get("brushtask_dltime")
|
||
brushtask_avg_upspeed = data.get("brushtask_avg_upspeed")
|
||
brushtask_iatime = data.get("brushtask_iatime")
|
||
brushtask_pubdate = data.get("brushtask_pubdate")
|
||
brushtask_upspeed = data.get("brushtask_upspeed")
|
||
brushtask_downspeed = data.get("brushtask_downspeed")
|
||
# 选种规则
|
||
rss_rule = {
|
||
"free": brushtask_free,
|
||
"hr": brushtask_hr,
|
||
"size": brushtask_torrent_size,
|
||
"include": brushtask_include,
|
||
"exclude": brushtask_exclude,
|
||
"dlcount": brushtask_dlcount,
|
||
"peercount": brushtask_peercount,
|
||
"pubdate": brushtask_pubdate,
|
||
"upspeed": brushtask_upspeed,
|
||
"downspeed": brushtask_downspeed
|
||
}
|
||
# 删除规则
|
||
remove_rule = {
|
||
"time": brushtask_seedtime,
|
||
"ratio": brushtask_seedratio,
|
||
"uploadsize": brushtask_seedsize,
|
||
"dltime": brushtask_dltime,
|
||
"avg_upspeed": brushtask_avg_upspeed,
|
||
"iatime": brushtask_iatime
|
||
}
|
||
# 添加记录
|
||
item = {
|
||
"name": brushtask_name,
|
||
"site": brushtask_site,
|
||
"free": brushtask_free,
|
||
"interval": brushtask_interval,
|
||
"downloader": brushtask_downloader,
|
||
"seed_size": brushtask_totalsize,
|
||
"transfer": brushtask_transfer,
|
||
"state": brushtask_state,
|
||
"rss_rule": rss_rule,
|
||
"remove_rule": remove_rule,
|
||
"sendmessage": brushtask_sendmessage,
|
||
"forceupload": brushtask_forceupload
|
||
}
|
||
self.dbhelper.insert_brushtask(brushtask_id, item)
|
||
|
||
# 重新初始化任务
|
||
BrushTask().init_config()
|
||
return {"code": 0}
|
||
|
||
def __del_brushtask(self, data):
|
||
"""
|
||
删除刷流任务
|
||
"""
|
||
brush_id = data.get("id")
|
||
if brush_id:
|
||
self.dbhelper.delete_brushtask(brush_id)
|
||
# 重新初始化任务
|
||
BrushTask().init_config()
|
||
return {"code": 0}
|
||
return {"code": 1}
|
||
|
||
def __brushtask_detail(self, data):
|
||
"""
|
||
查询刷流任务详情
|
||
"""
|
||
brush_id = data.get("id")
|
||
brushtask = self.dbhelper.get_brushtasks(brush_id)
|
||
if not brushtask:
|
||
return {"code": 1, "task": {}}
|
||
site_info = Sites().get_sites(siteid=brushtask.SITE)
|
||
task = {
|
||
"id": brushtask.ID,
|
||
"name": brushtask.NAME,
|
||
"site": brushtask.SITE,
|
||
"interval": brushtask.INTEVAL,
|
||
"state": brushtask.STATE,
|
||
"downloader": brushtask.DOWNLOADER,
|
||
"transfer": brushtask.TRANSFER,
|
||
"free": brushtask.FREELEECH,
|
||
"rss_rule": eval(brushtask.RSS_RULE),
|
||
"remove_rule": eval(brushtask.REMOVE_RULE),
|
||
"seed_size": brushtask.SEED_SIZE,
|
||
"download_count": brushtask.DOWNLOAD_COUNT,
|
||
"remove_count": brushtask.REMOVE_COUNT,
|
||
"download_size": StringUtils.str_filesize(brushtask.DOWNLOAD_SIZE),
|
||
"upload_size": StringUtils.str_filesize(brushtask.UPLOAD_SIZE),
|
||
"lst_mod_date": brushtask.LST_MOD_DATE,
|
||
"site_url": StringUtils.get_base_url(site_info.get("signurl") or site_info.get("rssurl")),
|
||
"sendmessage": brushtask.SENDMESSAGE,
|
||
"forceupload": brushtask.FORCEUPLOAD
|
||
}
|
||
return {"code": 0, "task": task}
|
||
|
||
def __add_downloader(self, data):
|
||
"""
|
||
添加自定义下载器
|
||
"""
|
||
test = data.get("test")
|
||
dl_id = data.get("id")
|
||
dl_name = data.get("name")
|
||
dl_type = data.get("type")
|
||
if test:
|
||
# 测试
|
||
if dl_type == "qbittorrent":
|
||
downloader = Qbittorrent(
|
||
config={
|
||
"qbhost": data.get("host"),
|
||
"qbport": data.get("port"),
|
||
"qbusername": data.get("username"),
|
||
"qbpassword": data.get("password")
|
||
})
|
||
else:
|
||
downloader = Transmission(
|
||
config={
|
||
"trhost": data.get("host"),
|
||
"trport": data.get("port"),
|
||
"trusername": data.get("username"),
|
||
"trpassword": data.get("password")
|
||
})
|
||
if downloader.get_status():
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
else:
|
||
# 保存
|
||
self.dbhelper.update_user_downloader(
|
||
did=dl_id,
|
||
name=dl_name,
|
||
dtype=dl_type,
|
||
user_config={
|
||
"host": data.get("host"),
|
||
"port": data.get("port"),
|
||
"username": data.get("username"),
|
||
"password": data.get("password"),
|
||
"save_dir": data.get("save_dir")
|
||
},
|
||
note=None)
|
||
BrushTask().init_config()
|
||
return {"code": 0}
|
||
|
||
def __delete_downloader(self, data):
|
||
"""
|
||
删除自定义下载器
|
||
"""
|
||
dl_id = data.get("id")
|
||
if dl_id:
|
||
self.dbhelper.delete_user_downloader(dl_id)
|
||
BrushTask().init_config()
|
||
return {"code": 0}
|
||
|
||
def __get_downloader(self, data):
|
||
"""
|
||
查询自定义下载器
|
||
"""
|
||
dl_id = data.get("id")
|
||
if dl_id:
|
||
info = self.dbhelper.get_user_downloaders(dl_id)
|
||
if info:
|
||
return {"code": 0, "info": info.as_dict()}
|
||
return {"code": 1}
|
||
|
||
def __name_test(self, data):
|
||
"""
|
||
名称识别测试
|
||
"""
|
||
name = data.get("name")
|
||
if not name:
|
||
return {"code": -1}
|
||
media_info = Media().get_media_info(title=name)
|
||
if not media_info:
|
||
return {"code": 0, "data": {"name": "无法识别"}}
|
||
return {"code": 0, "data": self.mediainfo_dict(media_info)}
|
||
|
||
@staticmethod
|
||
def mediainfo_dict(media_info):
|
||
if not media_info:
|
||
return {}
|
||
tmdb_id = media_info.tmdb_id
|
||
tmdb_link = media_info.get_detail_url()
|
||
tmdb_S_E_link = ""
|
||
if tmdb_id:
|
||
if media_info.get_season_string():
|
||
tmdb_S_E_link = "%s/season/%s" % (tmdb_link,
|
||
media_info.get_season_seq())
|
||
if media_info.get_episode_string():
|
||
tmdb_S_E_link = "%s/episode/%s" % (
|
||
tmdb_S_E_link, media_info.get_episode_seq())
|
||
return {
|
||
"type": media_info.type.value if media_info.type else "",
|
||
"name": media_info.get_name(),
|
||
"title": media_info.title,
|
||
"year": media_info.year,
|
||
"season_episode": media_info.get_season_episode_string(),
|
||
"part": media_info.part,
|
||
"tmdbid": tmdb_id,
|
||
"tmdblink": tmdb_link,
|
||
"tmdb_S_E_link": tmdb_S_E_link,
|
||
"category": media_info.category,
|
||
"restype": media_info.resource_type,
|
||
"effect": media_info.resource_effect,
|
||
"pix": media_info.resource_pix,
|
||
"team": media_info.resource_team,
|
||
"video_codec": media_info.video_encode,
|
||
"audio_codec": media_info.audio_encode,
|
||
"org_string": media_info.org_string,
|
||
"ignored_words": media_info.ignored_words,
|
||
"replaced_words": media_info.replaced_words,
|
||
"offset_words": media_info.offset_words
|
||
}
|
||
|
||
@staticmethod
|
||
def __rule_test(data):
|
||
title = data.get("title")
|
||
subtitle = data.get("subtitle")
|
||
size = data.get("size")
|
||
rulegroup = data.get("rulegroup")
|
||
if not title:
|
||
return {"code": -1}
|
||
meta_info = MetaInfo(title=title, subtitle=subtitle)
|
||
meta_info.size = float(size) * 1024 ** 3 if size else 0
|
||
match_flag, res_order, match_msg = \
|
||
Filter().check_torrent_filter(meta_info=meta_info,
|
||
filter_args={"rule": rulegroup})
|
||
return {
|
||
"code": 0,
|
||
"flag": match_flag,
|
||
"text": "匹配" if match_flag else "未匹配",
|
||
"order": 100 - res_order if res_order else 0
|
||
}
|
||
|
||
@staticmethod
|
||
def __net_test(data):
|
||
target = data
|
||
if target == "image.tmdb.org":
|
||
target = target + "/t/p/w500/wwemzKWzjKYJFfCeiB57q3r4Bcm.png"
|
||
if target == "qyapi.weixin.qq.com":
|
||
target = target + "/cgi-bin/message/send"
|
||
target = "https://" + target
|
||
start_time = datetime.datetime.now()
|
||
if target.find("themoviedb") != -1 \
|
||
or target.find("telegram") != -1 \
|
||
or target.find("fanart") != -1 \
|
||
or target.find("tmdb") != -1:
|
||
res = RequestUtils(proxies=Config().get_proxies(),
|
||
timeout=5).get_res(target)
|
||
else:
|
||
res = RequestUtils(timeout=5).get_res(target)
|
||
seconds = int((datetime.datetime.now() -
|
||
start_time).microseconds / 1000)
|
||
if not res:
|
||
return {"res": False, "time": "%s 毫秒" % seconds}
|
||
elif res.ok:
|
||
return {"res": True, "time": "%s 毫秒" % seconds}
|
||
else:
|
||
return {"res": False, "time": "%s 毫秒" % seconds}
|
||
|
||
@staticmethod
|
||
def __get_site_activity(data):
|
||
"""
|
||
查询site活动[上传,下载,魔力值]
|
||
:param data: {"name":site_name}
|
||
:return:
|
||
"""
|
||
if not data or "name" not in data:
|
||
return {"code": 1, "msg": "查询参数错误"}
|
||
|
||
resp = {"code": 0}
|
||
|
||
resp.update(
|
||
{"dataset": SiteUserInfo().get_pt_site_activity_history(data["name"])})
|
||
return resp
|
||
|
||
@staticmethod
|
||
def __get_site_history(data):
|
||
"""
|
||
查询site 历史[上传,下载]
|
||
:param data: {"days":累计时间}
|
||
:return:
|
||
"""
|
||
if not data or "days" not in data or not isinstance(data["days"], int):
|
||
return {"code": 1, "msg": "查询参数错误"}
|
||
|
||
resp = {"code": 0}
|
||
_, _, site, upload, download = SiteUserInfo().get_pt_site_statistics_history(data["days"] + 1)
|
||
|
||
# 调整为dataset组织数据
|
||
dataset = [["site", "upload", "download"]]
|
||
dataset.extend([[site, upload, download]
|
||
for site, upload, download in zip(site, upload, download)])
|
||
resp.update({"dataset": dataset})
|
||
return resp
|
||
|
||
@staticmethod
|
||
def __get_site_seeding_info(data):
|
||
"""
|
||
查询site 做种分布信息 大小,做种数
|
||
:param data: {"name":site_name}
|
||
:return:
|
||
"""
|
||
if not data or "name" not in data:
|
||
return {"code": 1, "msg": "查询参数错误"}
|
||
|
||
resp = {"code": 0}
|
||
|
||
seeding_info = SiteUserInfo().get_pt_site_seeding_info(
|
||
data["name"]).get("seeding_info", [])
|
||
# 调整为dataset组织数据
|
||
dataset = [["seeders", "size"]]
|
||
dataset.extend(seeding_info)
|
||
|
||
resp.update({"dataset": dataset})
|
||
return resp
|
||
|
||
def __add_filtergroup(self, data):
|
||
"""
|
||
新增规则组
|
||
"""
|
||
name = data.get("name")
|
||
default = data.get("default")
|
||
if not name:
|
||
return {"code": -1}
|
||
self.dbhelper.add_filter_group(name, default)
|
||
Filter().init_config()
|
||
return {"code": 0}
|
||
|
||
def __restore_filtergroup(self, data):
|
||
"""
|
||
恢复初始规则组
|
||
"""
|
||
groupids = data.get("groupids")
|
||
init_rulegroups = data.get("init_rulegroups")
|
||
for groupid in groupids:
|
||
try:
|
||
self.dbhelper.delete_filtergroup(groupid)
|
||
except Exception as err:
|
||
ExceptionUtils.exception_traceback(err)
|
||
for init_rulegroup in init_rulegroups:
|
||
if str(init_rulegroup.get("id")) == groupid:
|
||
for sql in init_rulegroup.get("sql"):
|
||
self.dbhelper.excute(sql)
|
||
Filter().init_config()
|
||
return {"code": 0}
|
||
|
||
def __set_default_filtergroup(self, data):
|
||
groupid = data.get("id")
|
||
if not groupid:
|
||
return {"code": -1}
|
||
self.dbhelper.set_default_filtergroup(groupid)
|
||
Filter().init_config()
|
||
return {"code": 0}
|
||
|
||
def __del_filtergroup(self, data):
|
||
groupid = data.get("id")
|
||
self.dbhelper.delete_filtergroup(groupid)
|
||
Filter().init_config()
|
||
return {"code": 0}
|
||
|
||
def __add_filterrule(self, data):
|
||
rule_id = data.get("rule_id")
|
||
item = {
|
||
"group": data.get("group_id"),
|
||
"name": data.get("rule_name"),
|
||
"pri": data.get("rule_pri"),
|
||
"include": data.get("rule_include"),
|
||
"exclude": data.get("rule_exclude"),
|
||
"size": data.get("rule_sizelimit"),
|
||
"free": data.get("rule_free")
|
||
}
|
||
self.dbhelper.insert_filter_rule(ruleid=rule_id, item=item)
|
||
Filter().init_config()
|
||
return {"code": 0}
|
||
|
||
def __del_filterrule(self, data):
|
||
ruleid = data.get("id")
|
||
self.dbhelper.delete_filterrule(ruleid)
|
||
Filter().init_config()
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __filterrule_detail(data):
|
||
rid = data.get("ruleid")
|
||
groupid = data.get("groupid")
|
||
ruleinfo = Filter().get_rules(groupid=groupid, ruleid=rid)
|
||
if ruleinfo:
|
||
ruleinfo['include'] = "\n".join(ruleinfo.get("include"))
|
||
ruleinfo['exclude'] = "\n".join(ruleinfo.get("exclude"))
|
||
return {"code": 0, "info": ruleinfo}
|
||
|
||
def get_recommend(self, data):
|
||
Type = data.get("type")
|
||
SubType = data.get("subtype")
|
||
CurrentPage = data.get("page")
|
||
if not CurrentPage:
|
||
CurrentPage = 1
|
||
else:
|
||
CurrentPage = int(CurrentPage)
|
||
|
||
res_list = []
|
||
if Type in ['MOV', 'TV']:
|
||
if SubType == "hm":
|
||
# TMDB热门电影
|
||
res_list = Media().get_tmdb_hot_movies(CurrentPage)
|
||
elif SubType == "ht":
|
||
# TMDB热门电视剧
|
||
res_list = Media().get_tmdb_hot_tvs(CurrentPage)
|
||
elif SubType == "nm":
|
||
# TMDB最新电影
|
||
res_list = Media().get_tmdb_new_movies(CurrentPage)
|
||
elif SubType == "nt":
|
||
# TMDB最新电视剧
|
||
res_list = Media().get_tmdb_new_tvs(CurrentPage)
|
||
elif SubType == "dbom":
|
||
# 豆瓣正在上映
|
||
res_list = DouBan().get_douban_online_movie(CurrentPage)
|
||
elif SubType == "dbhm":
|
||
# 豆瓣热门电影
|
||
res_list = DouBan().get_douban_hot_movie(CurrentPage)
|
||
elif SubType == "dbht":
|
||
# 豆瓣热门电视剧
|
||
res_list = DouBan().get_douban_hot_tv(CurrentPage)
|
||
elif SubType == "dbdh":
|
||
# 豆瓣热门动画
|
||
res_list = DouBan().get_douban_hot_anime(CurrentPage)
|
||
elif SubType == "dbnm":
|
||
# 豆瓣最新电影
|
||
res_list = DouBan().get_douban_new_movie(CurrentPage)
|
||
elif SubType == "dbtop":
|
||
# 豆瓣TOP250电影
|
||
res_list = DouBan().get_douban_top250_movie(CurrentPage)
|
||
elif SubType == "dbzy":
|
||
# 豆瓣最新电视剧
|
||
res_list = DouBan().get_douban_hot_show(CurrentPage)
|
||
elif SubType == "dbct":
|
||
# 华语口碑剧集榜
|
||
res_list = DouBan().get_douban_chinese_weekly_tv(CurrentPage)
|
||
elif SubType == "dbgt":
|
||
# 全球口碑剧集榜
|
||
res_list = DouBan().get_douban_weekly_tv_global(CurrentPage)
|
||
elif SubType == "sim":
|
||
# 相似推荐
|
||
TmdbId = data.get("tmdbid")
|
||
res_list = self.__media_similar({
|
||
"tmdbid": TmdbId,
|
||
"page": CurrentPage,
|
||
"type": Type
|
||
}).get("data")
|
||
elif SubType == "more":
|
||
# 更多推荐
|
||
TmdbId = data.get("tmdbid")
|
||
res_list = self.__media_recommendations({
|
||
"tmdbid": TmdbId,
|
||
"page": CurrentPage,
|
||
"type": Type
|
||
}).get("data")
|
||
elif SubType == "person":
|
||
# 人物作品
|
||
PersonId = data.get("personid")
|
||
res_list = self.__person_medias({
|
||
"personid": PersonId,
|
||
"type": Type,
|
||
"page": CurrentPage
|
||
}).get("data")
|
||
elif SubType == "bangumi":
|
||
# Bangumi每日放送
|
||
Week = data.get("week")
|
||
res_list = Bangumi().get_bangumi_calendar(page=CurrentPage, week=Week)
|
||
elif Type == "SEARCH":
|
||
# 搜索词条
|
||
Keyword = data.get("keyword")
|
||
Source = data.get("source")
|
||
medias = WebUtils.search_media_infos(
|
||
keyword=Keyword, source=Source, page=CurrentPage)
|
||
res_list = [media.to_dict() for media in medias]
|
||
elif Type == "DOWNLOADED":
|
||
# 近期下载
|
||
res_list = self.get_downloaded({
|
||
"page": CurrentPage
|
||
}).get("Items")
|
||
elif Type == "TRENDING":
|
||
# TMDB流行趋势
|
||
res_list = Media().get_tmdb_trending_all_week(page=CurrentPage)
|
||
elif Type == "DISCOVER":
|
||
# TMDB发现
|
||
mtype = MediaType.MOVIE if SubType in MovieTypes else MediaType.TV
|
||
# 过滤参数 with_genres with_original_language
|
||
params = data.get("params") or {}
|
||
res_list = Media().get_tmdb_discover(mtype=mtype, page=CurrentPage, params=params)
|
||
elif Type == "DOUBANTAG":
|
||
# 豆瓣发现
|
||
mtype = MediaType.MOVIE if SubType in MovieTypes else MediaType.TV
|
||
# 参数
|
||
params = data.get("params") or {}
|
||
# 排序
|
||
sort = params.get("sort") or "T"
|
||
# 选中的分类
|
||
tags = params.get("tags") or ""
|
||
# 过滤参数
|
||
res_list = DouBan().get_douban_disover(mtype=mtype,
|
||
sort=sort,
|
||
tags=tags,
|
||
page=CurrentPage)
|
||
|
||
# 补充存在与订阅状态
|
||
for res in res_list:
|
||
fav, rssid = self.get_media_exists_flag(mtype=Type,
|
||
title=res.get(
|
||
"title"),
|
||
year=res.get(
|
||
"year"),
|
||
mediaid=res.get("id"))
|
||
res.update({
|
||
'fav': fav,
|
||
'rssid': rssid
|
||
})
|
||
return {"code": 0, "Items": res_list}
|
||
|
||
def get_downloaded(self, data):
|
||
page = data.get("page")
|
||
Items = self.dbhelper.get_download_history(page=page)
|
||
if Items:
|
||
return {"code": 0, "Items": [{
|
||
'id': item.TMDBID,
|
||
'orgid': item.TMDBID,
|
||
'tmdbid': item.TMDBID,
|
||
'title': item.TITLE,
|
||
'type': 'MOV' if item.TYPE == "电影" else "TV",
|
||
'media_type': item.TYPE,
|
||
'year': item.YEAR,
|
||
'vote': item.VOTE,
|
||
'image': item.POSTER,
|
||
'overview': item.TORRENT,
|
||
"date": item.DATE,
|
||
"site": item.SITE
|
||
} for item in Items]}
|
||
else:
|
||
return {"code": 0, "Items": []}
|
||
|
||
@staticmethod
|
||
def parse_brush_rule_string(rules: dict):
|
||
if not rules:
|
||
return ""
|
||
rule_filter_string = {"gt": ">", "lt": "<", "bw": ""}
|
||
rule_htmls = []
|
||
if rules.get("size"):
|
||
sizes = rules.get("size").split("#")
|
||
if sizes[0]:
|
||
if sizes[1]:
|
||
sizes[1] = sizes[1].replace(",", "-")
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-blue me-1 mb-1" title="种子大小">种子大小: %s %sGB</span>'
|
||
% (rule_filter_string.get(sizes[0]), sizes[1]))
|
||
if rules.get("pubdate"):
|
||
pubdates = rules.get("pubdate").split("#")
|
||
if pubdates[0]:
|
||
if pubdates[1]:
|
||
pubdates[1] = pubdates[1].replace(",", "-")
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-blue me-1 mb-1" title="发布时间">发布时间: %s %s小时</span>'
|
||
% (rule_filter_string.get(pubdates[0]), pubdates[1]))
|
||
if rules.get("upspeed"):
|
||
rule_htmls.append('<span class="badge badge-outline text-blue me-1 mb-1" title="上传限速">上传限速: %sB/s</span>'
|
||
% StringUtils.str_filesize(int(rules.get("upspeed")) * 1024))
|
||
if rules.get("downspeed"):
|
||
rule_htmls.append('<span class="badge badge-outline text-blue me-1 mb-1" title="下载限速">下载限速: %sB/s</span>'
|
||
% StringUtils.str_filesize(int(rules.get("downspeed")) * 1024))
|
||
if rules.get("include"):
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-green me-1 mb-1 text-wrap text-start" title="包含规则">包含: %s</span>'
|
||
% rules.get("include"))
|
||
if rules.get("hr"):
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-red me-1 mb-1" title="排除HR">排除: HR</span>')
|
||
if rules.get("exclude"):
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-red me-1 mb-1 text-wrap text-start" title="排除规则">排除: %s</span>'
|
||
% rules.get("exclude"))
|
||
if rules.get("dlcount"):
|
||
rule_htmls.append('<span class="badge badge-outline text-blue me-1 mb-1" title="同时下载数量限制">同时下载: %s</span>'
|
||
% rules.get("dlcount"))
|
||
if rules.get("peercount"):
|
||
peer_counts = None
|
||
if rules.get("peercount") == "#":
|
||
peer_counts = None
|
||
elif "#" in rules.get("peercount"):
|
||
peer_counts = rules.get("peercount").split("#")
|
||
peer_counts[1] = peer_counts[1].replace(",", "-") if (len(peer_counts) >= 2 and peer_counts[1]) else \
|
||
peer_counts[1]
|
||
else:
|
||
try:
|
||
# 兼容性代码
|
||
peer_counts = ["lt", int(rules.get("peercount"))]
|
||
except Exception as err:
|
||
ExceptionUtils.exception_traceback(err)
|
||
pass
|
||
if peer_counts:
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-blue me-1 mb-1" title="当前做种人数限制">做种人数: %s %s</span>'
|
||
% (rule_filter_string.get(peer_counts[0]), peer_counts[1]))
|
||
if rules.get("time"):
|
||
times = rules.get("time").split("#")
|
||
if times[0]:
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-orange me-1 mb-1" title="做种时间">做种时间: %s %s小时</span>'
|
||
% (rule_filter_string.get(times[0]), times[1]))
|
||
if rules.get("ratio"):
|
||
ratios = rules.get("ratio").split("#")
|
||
if ratios[0]:
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-orange me-1 mb-1" title="分享率">分享率: %s %s</span>'
|
||
% (rule_filter_string.get(ratios[0]), ratios[1]))
|
||
if rules.get("uploadsize"):
|
||
uploadsizes = rules.get("uploadsize").split("#")
|
||
if uploadsizes[0]:
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-orange me-1 mb-1" title="上传量">上传量: %s %sGB</span>'
|
||
% (rule_filter_string.get(uploadsizes[0]), uploadsizes[1]))
|
||
if rules.get("dltime"):
|
||
dltimes = rules.get("dltime").split("#")
|
||
if dltimes[0]:
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-orange me-1 mb-1" title="下载耗时">下载耗时: %s %s小时</span>'
|
||
% (rule_filter_string.get(dltimes[0]), dltimes[1]))
|
||
if rules.get("avg_upspeed"):
|
||
avg_upspeeds = rules.get("avg_upspeed").split("#")
|
||
if avg_upspeeds[0]:
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-orange me-1 mb-1" title="平均上传速度">平均上传速度: %s %sKB/S</span>'
|
||
% (rule_filter_string.get(avg_upspeeds[0]), avg_upspeeds[1]))
|
||
if rules.get("iatime"):
|
||
iatimes = rules.get("iatime").split("#")
|
||
if iatimes[0]:
|
||
rule_htmls.append(
|
||
'<span class="badge badge-outline text-orange me-1 mb-1" title="未活动时间">未活动时间: %s %s小时</span>'
|
||
% (rule_filter_string.get(iatimes[0]), iatimes[1]))
|
||
|
||
return "<br>".join(rule_htmls)
|
||
|
||
@staticmethod
|
||
def __clear_tmdb_cache(data):
|
||
"""
|
||
清空TMDB缓存
|
||
"""
|
||
try:
|
||
MetaHelper().clear_meta_data()
|
||
os.remove(MetaHelper().get_meta_data_path())
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 0, "msg": str(e)}
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __check_site_attr(data):
|
||
"""
|
||
检查站点标识
|
||
"""
|
||
site_attr = Sites().get_grapsite_conf(data.get("url"))
|
||
site_free = site_2xfree = site_hr = False
|
||
if site_attr.get("FREE"):
|
||
site_free = True
|
||
if site_attr.get("2XFREE"):
|
||
site_2xfree = True
|
||
if site_attr.get("HR"):
|
||
site_hr = True
|
||
return {"code": 0, "site_free": site_free, "site_2xfree": site_2xfree, "site_hr": site_hr}
|
||
|
||
@staticmethod
|
||
def __refresh_process(data):
|
||
"""
|
||
刷新进度条
|
||
"""
|
||
detail = ProgressHelper().get_process(data.get("type"))
|
||
if detail:
|
||
return {"code": 0, "value": detail.get("value"), "text": detail.get("text")}
|
||
else:
|
||
return {"code": 1, "value": 0, "text": "正在处理..."}
|
||
|
||
@staticmethod
|
||
def __restory_backup(data):
|
||
"""
|
||
解压恢复备份文件
|
||
"""
|
||
filename = data.get("file_name")
|
||
if filename:
|
||
config_path = Config().get_config_path()
|
||
temp_path = Config().get_temp_path()
|
||
file_path = os.path.join(temp_path, filename)
|
||
try:
|
||
shutil.unpack_archive(file_path, config_path, format='zip')
|
||
return {"code": 0, "msg": ""}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
finally:
|
||
if os.path.exists(file_path):
|
||
os.remove(file_path)
|
||
|
||
return {"code": 1, "msg": "文件不存在"}
|
||
|
||
@staticmethod
|
||
def __start_mediasync(data):
|
||
"""
|
||
开始媒体库同步
|
||
"""
|
||
ThreadHelper().start_thread(MediaServer().sync_mediaserver, ())
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __mediasync_state(data):
|
||
"""
|
||
获取媒体库同步数据情况
|
||
"""
|
||
status = MediaServer().get_mediasync_status()
|
||
if not status:
|
||
return {"code": 0, "text": "未同步"}
|
||
else:
|
||
return {"code": 0, "text": "电影:%s,电视剧:%s,同步时间:%s" %
|
||
(status.get("movie_count"),
|
||
status.get("tv_count"),
|
||
status.get("time"))}
|
||
|
||
@staticmethod
|
||
def __get_tvseason_list(data):
|
||
"""
|
||
获取剧集季列表
|
||
"""
|
||
tmdbid = data.get("tmdbid")
|
||
title = data.get("title")
|
||
if title:
|
||
title_season = MetaInfo(title=title).begin_season
|
||
else:
|
||
title_season = None
|
||
if not str(tmdbid).isdigit():
|
||
media_info = WebUtils.get_mediainfo_from_id(mtype=MediaType.TV,
|
||
mediaid=tmdbid)
|
||
season_infos = Media().get_tmdb_tv_seasons(media_info.tmdb_info)
|
||
else:
|
||
season_infos = Media().get_tmdb_tv_seasons_byid(tmdbid=tmdbid)
|
||
if title_season:
|
||
seasons = [
|
||
{
|
||
"text": "第%s季" % title_season,
|
||
"num": title_season
|
||
}
|
||
]
|
||
else:
|
||
seasons = [
|
||
{
|
||
"text": "第%s季" % cn2an.an2cn(season.get("season_number"), mode='low'),
|
||
"num": season.get("season_number")
|
||
}
|
||
for season in season_infos
|
||
]
|
||
return {"code": 0, "seasons": seasons}
|
||
|
||
@staticmethod
|
||
def __get_userrss_task(data):
|
||
"""
|
||
获取自定义订阅详情
|
||
"""
|
||
taskid = data.get("id")
|
||
return {"code": 0, "detail": RssChecker().get_rsstask_info(taskid=taskid)}
|
||
|
||
def __delete_userrss_task(self, data):
|
||
"""
|
||
删除自定义订阅
|
||
"""
|
||
if self.dbhelper.delete_userrss_task(data.get("id")):
|
||
RssChecker().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
def __update_userrss_task(self, data):
|
||
"""
|
||
新增或修改自定义订阅
|
||
"""
|
||
uses = data.get("uses")
|
||
params = {
|
||
"id": data.get("id"),
|
||
"name": data.get("name"),
|
||
"address": data.get("address"),
|
||
"parser": data.get("parser"),
|
||
"interval": data.get("interval"),
|
||
"uses": uses,
|
||
"include": data.get("include"),
|
||
"exclude": data.get("exclude"),
|
||
"filter_rule": data.get("rule"),
|
||
"state": data.get("state"),
|
||
"save_path": data.get("save_path"),
|
||
"download_setting": data.get("download_setting"),
|
||
}
|
||
if uses == "D":
|
||
params.update({
|
||
"recognization": data.get("recognization")
|
||
})
|
||
elif uses == "R":
|
||
params.update({
|
||
"over_edition": data.get("over_edition"),
|
||
"sites": data.get("sites"),
|
||
"filter_args": {
|
||
"restype": data.get("restype"),
|
||
"pix": data.get("pix"),
|
||
"team": data.get("team")
|
||
}
|
||
})
|
||
else:
|
||
return {"code": 1}
|
||
if self.dbhelper.update_userrss_task(params):
|
||
RssChecker().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
@staticmethod
|
||
def __get_rssparser(data):
|
||
"""
|
||
获取订阅解析器详情
|
||
"""
|
||
pid = data.get("id")
|
||
return {"code": 0, "detail": RssChecker().get_userrss_parser(pid=pid)}
|
||
|
||
def __delete_rssparser(self, data):
|
||
"""
|
||
删除订阅解析器
|
||
"""
|
||
if self.dbhelper.delete_userrss_parser(data.get("id")):
|
||
RssChecker().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
def __update_rssparser(self, data):
|
||
"""
|
||
新增或更新订阅解析器
|
||
"""
|
||
params = {
|
||
"id": data.get("id"),
|
||
"name": data.get("name"),
|
||
"type": data.get("type"),
|
||
"format": data.get("format"),
|
||
"params": data.get("params")
|
||
}
|
||
if self.dbhelper.update_userrss_parser(params):
|
||
RssChecker().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
@staticmethod
|
||
def __run_userrss(data):
|
||
RssChecker().check_task_rss(data.get("id"))
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __run_brushtask(data):
|
||
BrushTask().check_task_rss(data.get("id"))
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __list_site_resources(data):
|
||
resources = Indexer().list_builtin_resources(index_id=data.get("id"),
|
||
page=data.get("page"),
|
||
keyword=data.get("keyword"))
|
||
if not resources:
|
||
return {"code": 1, "msg": "获取站点资源出现错误,无法连接到站点!"}
|
||
else:
|
||
return {"code": 0, "data": resources}
|
||
|
||
@staticmethod
|
||
def __list_rss_articles(data):
|
||
uses = RssChecker().get_rsstask_info(taskid=data.get("id")).get("uses")
|
||
articles = RssChecker().get_rss_articles(data.get("id"))
|
||
count = len(articles)
|
||
if articles:
|
||
return {"code": 0, "data": articles, "count": count, "uses": uses}
|
||
else:
|
||
return {"code": 1, "msg": "未获取到报文"}
|
||
|
||
def __rss_article_test(self, data):
|
||
taskid = data.get("taskid")
|
||
title = data.get("title")
|
||
if not taskid:
|
||
return {"code": -1}
|
||
if not title:
|
||
return {"code": -1}
|
||
media_info, match_flag, exist_flag = RssChecker(
|
||
).test_rss_articles(taskid=taskid, title=title)
|
||
if not media_info:
|
||
return {"code": 0, "data": {"name": "无法识别"}}
|
||
media_dict = self.mediainfo_dict(media_info)
|
||
media_dict.update({"match_flag": match_flag, "exist_flag": exist_flag})
|
||
return {"code": 0, "data": media_dict}
|
||
|
||
def __list_rss_history(self, data):
|
||
downloads = []
|
||
historys = self.dbhelper.get_userrss_task_history(data.get("id"))
|
||
count = len(historys)
|
||
for history in historys:
|
||
params = {
|
||
"title": history.TITLE,
|
||
"downloader": history.DOWNLOADER,
|
||
"date": history.DATE
|
||
}
|
||
downloads.append(params)
|
||
if downloads:
|
||
return {"code": 0, "data": downloads, "count": count}
|
||
else:
|
||
return {"code": 1, "msg": "无下载记录"}
|
||
|
||
@staticmethod
|
||
def __rss_articles_check(data):
|
||
if not data.get("articles"):
|
||
return {"code": 2}
|
||
res = RssChecker().check_rss_articles(
|
||
flag=data.get("flag"), articles=data.get("articles"))
|
||
if res:
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
@staticmethod
|
||
def __rss_articles_download(data):
|
||
if not data.get("articles"):
|
||
return {"code": 2}
|
||
res = RssChecker().download_rss_articles(
|
||
taskid=data.get("taskid"), articles=data.get("articles"))
|
||
if res:
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
def __add_custom_word_group(self, data):
|
||
try:
|
||
tmdb_id = data.get("tmdb_id")
|
||
tmdb_type = data.get("tmdb_type")
|
||
if tmdb_type == "tv":
|
||
if not self.dbhelper.is_custom_word_group_existed(tmdbid=tmdb_id, gtype=2):
|
||
tmdb_info = Media().get_tmdb_info(mtype=MediaType.TV, tmdbid=tmdb_id)
|
||
if not tmdb_info:
|
||
return {"code": 1, "msg": "添加失败,无法查询到TMDB信息"}
|
||
self.dbhelper.insert_custom_word_groups(title=tmdb_info.get("name"),
|
||
year=tmdb_info.get(
|
||
"first_air_date")[0:4],
|
||
gtype=2,
|
||
tmdbid=tmdb_id,
|
||
season_count=tmdb_info.get("number_of_seasons"))
|
||
return {"code": 0, "msg": ""}
|
||
else:
|
||
return {"code": 1, "msg": "识别词组(TMDB ID)已存在"}
|
||
elif tmdb_type == "movie":
|
||
if not self.dbhelper.is_custom_word_group_existed(tmdbid=tmdb_id, gtype=1):
|
||
tmdb_info = Media().get_tmdb_info(mtype=MediaType.MOVIE, tmdbid=tmdb_id)
|
||
if not tmdb_info:
|
||
return {"code": 1, "msg": "添加失败,无法查询到TMDB信息"}
|
||
self.dbhelper.insert_custom_word_groups(title=tmdb_info.get("title"),
|
||
year=tmdb_info.get(
|
||
"release_date")[0:4],
|
||
gtype=1,
|
||
tmdbid=tmdb_id,
|
||
season_count=0)
|
||
return {"code": 0, "msg": ""}
|
||
else:
|
||
return {"code": 1, "msg": "识别词组(TMDB ID)已存在"}
|
||
else:
|
||
return {"code": 1, "msg": "无法识别媒体类型"}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
def __delete_custom_word_group(self, data):
|
||
try:
|
||
gid = data.get("gid")
|
||
self.dbhelper.delete_custom_word_group(gid=gid)
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
def __add_or_edit_custom_word(self, data):
|
||
try:
|
||
wid = data.get("id")
|
||
gid = data.get("gid")
|
||
group_type = data.get("group_type")
|
||
replaced = data.get("new_replaced")
|
||
replace = data.get("new_replace")
|
||
front = data.get("new_front")
|
||
back = data.get("new_back")
|
||
offset = data.get("new_offset")
|
||
whelp = data.get("new_help")
|
||
wtype = data.get("type")
|
||
season = data.get("season")
|
||
enabled = data.get("enabled")
|
||
regex = data.get("regex")
|
||
# 集数偏移格式检查
|
||
if wtype in ["3", "4"]:
|
||
if not re.findall(r'EP', offset):
|
||
return {"code": 1, "msg": "偏移集数格式有误"}
|
||
if re.findall(r'(?!-|\+|\*|/|[0-9]).', re.sub(r'EP', "", offset)):
|
||
return {"code": 1, "msg": "偏移集数格式有误"}
|
||
if wid:
|
||
self.dbhelper.delete_custom_word(wid=wid)
|
||
# 电影
|
||
if group_type == "1":
|
||
season = -2
|
||
# 屏蔽
|
||
if wtype == "1":
|
||
if not self.dbhelper.is_custom_words_existed(replaced=replaced):
|
||
self.dbhelper.insert_custom_word(replaced=replaced,
|
||
replace="",
|
||
front="",
|
||
back="",
|
||
offset="",
|
||
wtype=wtype,
|
||
gid=gid,
|
||
season=season,
|
||
enabled=enabled,
|
||
regex=regex,
|
||
whelp=whelp if whelp else "")
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
else:
|
||
return {"code": 1, "msg": "识别词已存在\n(被替换词:%s)" % replaced}
|
||
# 替换
|
||
elif wtype == "2":
|
||
if not self.dbhelper.is_custom_words_existed(replaced=replaced):
|
||
self.dbhelper.insert_custom_word(replaced=replaced,
|
||
replace=replace,
|
||
front="",
|
||
back="",
|
||
offset="",
|
||
wtype=wtype,
|
||
gid=gid,
|
||
season=season,
|
||
enabled=enabled,
|
||
regex=regex,
|
||
whelp=whelp if whelp else "")
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
else:
|
||
return {"code": 1, "msg": "识别词已存在\n(被替换词:%s)" % replaced}
|
||
# 集偏移
|
||
elif wtype == "4":
|
||
if not self.dbhelper.is_custom_words_existed(front=front, back=back):
|
||
self.dbhelper.insert_custom_word(replaced="",
|
||
replace="",
|
||
front=front,
|
||
back=back,
|
||
offset=offset,
|
||
wtype=wtype,
|
||
gid=gid,
|
||
season=season,
|
||
enabled=enabled,
|
||
regex=regex,
|
||
whelp=whelp if whelp else "")
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
else:
|
||
return {"code": 1, "msg": "识别词已存在\n(前后定位词:%s@%s)" % (front, back)}
|
||
# 替换+集偏移
|
||
elif wtype == "3":
|
||
if not self.dbhelper.is_custom_words_existed(replaced=replaced):
|
||
self.dbhelper.insert_custom_word(replaced=replaced,
|
||
replace=replace,
|
||
front=front,
|
||
back=back,
|
||
offset=offset,
|
||
wtype=wtype,
|
||
gid=gid,
|
||
season=season,
|
||
enabled=enabled,
|
||
regex=regex,
|
||
whelp=whelp if whelp else "")
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
else:
|
||
return {"code": 1, "msg": "识别词已存在\n(被替换词:%s)" % replaced}
|
||
else:
|
||
return {"code": 1, "msg": ""}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
def __get_custom_word(self, data):
|
||
try:
|
||
wid = data.get("wid")
|
||
word_info = self.dbhelper.get_custom_words(wid=wid)
|
||
if word_info:
|
||
word_info = word_info[0]
|
||
word = {"id": word_info.ID,
|
||
"replaced": word_info.REPLACED,
|
||
"replace": word_info.REPLACE,
|
||
"front": word_info.FRONT,
|
||
"back": word_info.BACK,
|
||
"offset": word_info.OFFSET,
|
||
"type": word_info.TYPE,
|
||
"group_id": word_info.GROUP_ID,
|
||
"season": word_info.SEASON,
|
||
"enabled": word_info.ENABLED,
|
||
"regex": word_info.REGEX,
|
||
"help": word_info.HELP, }
|
||
else:
|
||
word = {}
|
||
return {"code": 0, "data": word}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": "查询识别词失败"}
|
||
|
||
def __delete_custom_word(self, data):
|
||
try:
|
||
wid = data.get("id")
|
||
self.dbhelper.delete_custom_word(wid)
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
def __check_custom_words(self, data):
|
||
try:
|
||
flag_dict = {"enable": 1, "disable": 0}
|
||
ids_info = data.get("ids_info")
|
||
enabled = flag_dict.get(data.get("flag"))
|
||
ids = [id_info.split("_")[1] for id_info in ids_info]
|
||
for wid in ids:
|
||
self.dbhelper.check_custom_word(wid=wid, enabled=enabled)
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": "识别词状态设置失败"}
|
||
|
||
def __export_custom_words(self, data):
|
||
try:
|
||
note = data.get("note")
|
||
ids_info = data.get("ids_info").split("@")
|
||
group_ids = []
|
||
word_ids = []
|
||
for id_info in ids_info:
|
||
wid = id_info.split("_")
|
||
group_ids.append(wid[0])
|
||
word_ids.append(wid[1])
|
||
export_dict = {}
|
||
for group_id in group_ids:
|
||
if group_id == "-1":
|
||
export_dict["-1"] = {"id": -1,
|
||
"title": "通用",
|
||
"type": 1,
|
||
"words": {}, }
|
||
else:
|
||
group_info = self.dbhelper.get_custom_word_groups(
|
||
gid=group_id)
|
||
if group_info:
|
||
group_info = group_info[0]
|
||
export_dict[str(group_info.ID)] = {"id": group_info.ID,
|
||
"title": group_info.TITLE,
|
||
"year": group_info.YEAR,
|
||
"type": group_info.TYPE,
|
||
"tmdbid": group_info.TMDBID,
|
||
"season_count": group_info.SEASON_COUNT,
|
||
"words": {}, }
|
||
for word_id in word_ids:
|
||
word_info = self.dbhelper.get_custom_words(wid=word_id)
|
||
if word_info:
|
||
word_info = word_info[0]
|
||
export_dict[str(word_info.GROUP_ID)]["words"][str(word_info.ID)] = {"id": word_info.ID,
|
||
"replaced": word_info.REPLACED,
|
||
"replace": word_info.REPLACE,
|
||
"front": word_info.FRONT,
|
||
"back": word_info.BACK,
|
||
"offset": word_info.OFFSET,
|
||
"type": word_info.TYPE,
|
||
"season": word_info.SEASON,
|
||
"regex": word_info.REGEX,
|
||
"help": word_info.HELP, }
|
||
export_string = json.dumps(export_dict) + "@@@@@@" + str(note)
|
||
string = base64.b64encode(
|
||
export_string.encode("utf-8")).decode('utf-8')
|
||
return {"code": 0, "string": string}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
@staticmethod
|
||
def __analyse_import_custom_words_code(data):
|
||
try:
|
||
import_code = data.get('import_code')
|
||
string = base64.b64decode(import_code.encode(
|
||
"utf-8")).decode('utf-8').split("@@@@@@")
|
||
note_string = string[1]
|
||
import_dict = json.loads(string[0])
|
||
groups = []
|
||
for group in import_dict.values():
|
||
wid = group.get('id')
|
||
title = group.get("title")
|
||
year = group.get("year")
|
||
wtype = group.get("type")
|
||
tmdbid = group.get("tmdbid")
|
||
season_count = group.get("season_count") or ""
|
||
words = group.get("words")
|
||
if tmdbid:
|
||
link = "https://www.themoviedb.org/%s/%s" % (
|
||
"movie" if int(wtype) == 1 else "tv", tmdbid)
|
||
else:
|
||
link = ""
|
||
groups.append({"id": wid,
|
||
"name": "%s(%s)" % (title, year) if year else title,
|
||
"link": link,
|
||
"type": wtype,
|
||
"seasons": season_count,
|
||
"words": words})
|
||
return {"code": 0, "groups": groups, "note_string": note_string}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
def __import_custom_words(self, data):
|
||
try:
|
||
import_code = data.get('import_code')
|
||
ids_info = data.get('ids_info')
|
||
string = base64.b64decode(import_code.encode(
|
||
"utf-8")).decode('utf-8').split("@@@@@@")
|
||
import_dict = json.loads(string[0])
|
||
import_group_ids = [id_info.split("_")[0] for id_info in ids_info]
|
||
group_id_dict = {}
|
||
for import_group_id in import_group_ids:
|
||
import_group_info = import_dict.get(import_group_id)
|
||
if int(import_group_info.get("id")) == -1:
|
||
group_id_dict["-1"] = -1
|
||
continue
|
||
title = import_group_info.get("title")
|
||
year = import_group_info.get("year")
|
||
gtype = import_group_info.get("type")
|
||
tmdbid = import_group_info.get("tmdbid")
|
||
season_count = import_group_info.get("season_count")
|
||
if not self.dbhelper.is_custom_word_group_existed(tmdbid=tmdbid, gtype=gtype):
|
||
self.dbhelper.insert_custom_word_groups(title=title,
|
||
year=year,
|
||
gtype=gtype,
|
||
tmdbid=tmdbid,
|
||
season_count=season_count)
|
||
group_info = self.dbhelper.get_custom_word_groups(
|
||
tmdbid=tmdbid, gtype=gtype)
|
||
if group_info:
|
||
group_id_dict[import_group_id] = group_info[0].ID
|
||
for id_info in ids_info:
|
||
id_info = id_info.split('_')
|
||
import_group_id = id_info[0]
|
||
import_word_id = id_info[1]
|
||
import_word_info = import_dict.get(
|
||
import_group_id).get("words").get(import_word_id)
|
||
gid = group_id_dict.get(import_group_id)
|
||
replaced = import_word_info.get("replaced")
|
||
replace = import_word_info.get("replace")
|
||
front = import_word_info.get("front")
|
||
back = import_word_info.get("back")
|
||
offset = import_word_info.get("offset")
|
||
whelp = import_word_info.get("help")
|
||
wtype = int(import_word_info.get("type"))
|
||
season = import_word_info.get("season")
|
||
regex = import_word_info.get("regex")
|
||
# 屏蔽, 替换, 替换+集偏移
|
||
if wtype in [1, 2, 3]:
|
||
if self.dbhelper.is_custom_words_existed(replaced=replaced):
|
||
return {"code": 1, "msg": "识别词已存在\n(被替换词:%s)" % replaced}
|
||
# 集偏移
|
||
elif wtype == 4:
|
||
if self.dbhelper.is_custom_words_existed(front=front, back=back):
|
||
return {"code": 1, "msg": "识别词已存在\n(前后定位词:%s@%s)" % (front, back)}
|
||
self.dbhelper.insert_custom_word(replaced=replaced,
|
||
replace=replace,
|
||
front=front,
|
||
back=back,
|
||
offset=offset,
|
||
wtype=wtype,
|
||
gid=gid,
|
||
season=season,
|
||
enabled=1,
|
||
regex=regex,
|
||
whelp=whelp if whelp else "")
|
||
WordsHelper().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1, "msg": str(e)}
|
||
|
||
@staticmethod
|
||
def __get_categories(data):
|
||
if data.get("type") == "电影":
|
||
categories = Category().get_movie_categorys()
|
||
elif data.get("type") == "电视剧":
|
||
categories = Category().get_tv_categorys()
|
||
else:
|
||
categories = Category().get_anime_categorys()
|
||
return {"code": 0, "category": list(categories), "id": data.get("id"), "value": data.get("value")}
|
||
|
||
def __delete_rss_history(self, data):
|
||
rssid = data.get("rssid")
|
||
self.dbhelper.delete_rss_history(rssid=rssid)
|
||
return {"code": 0}
|
||
|
||
def __re_rss_history(self, data):
|
||
rssid = data.get("rssid")
|
||
rtype = data.get("type")
|
||
rssinfo = self.dbhelper.get_rss_history(rtype=rtype, rid=rssid)
|
||
if rssinfo:
|
||
if rtype == "MOV":
|
||
mtype = MediaType.MOVIE
|
||
else:
|
||
mtype = MediaType.TV
|
||
if rssinfo[0].SEASON:
|
||
season = int(str(rssinfo[0].SEASON).replace("S", ""))
|
||
else:
|
||
season = None
|
||
code, msg, _ = Subscribe().add_rss_subscribe(mtype=mtype,
|
||
name=rssinfo[0].NAME,
|
||
year=rssinfo[0].YEAR,
|
||
season=season,
|
||
mediaid=rssinfo[0].TMDBID,
|
||
total_ep=rssinfo[0].TOTAL,
|
||
current_ep=rssinfo[0].START)
|
||
return {"code": code, "msg": msg}
|
||
else:
|
||
return {"code": 1, "msg": "订阅历史记录不存在"}
|
||
|
||
def __share_filtergroup(self, data):
|
||
gid = data.get("id")
|
||
group_info = self.dbhelper.get_config_filter_group(gid=gid)
|
||
if not group_info:
|
||
return {"code": 1, "msg": "规则组不存在"}
|
||
group_rules = self.dbhelper.get_config_filter_rule(groupid=gid)
|
||
if not group_rules:
|
||
return {"code": 1, "msg": "规则组没有对应规则"}
|
||
rules = []
|
||
for rule in group_rules:
|
||
rules.append({
|
||
"name": rule.ROLE_NAME,
|
||
"pri": rule.PRIORITY,
|
||
"include": rule.INCLUDE,
|
||
"exclude": rule.EXCLUDE,
|
||
"size": rule.SIZE_LIMIT,
|
||
"free": rule.NOTE
|
||
})
|
||
rule_json = {
|
||
"name": group_info[0].GROUP_NAME,
|
||
"rules": rules
|
||
}
|
||
json_string = base64.b64encode(json.dumps(
|
||
rule_json).encode("utf-8")).decode('utf-8')
|
||
return {"code": 0, "string": json_string}
|
||
|
||
def __import_filtergroup(self, data):
|
||
content = data.get("content")
|
||
try:
|
||
json_str = base64.b64decode(
|
||
str(content).encode("utf-8")).decode('utf-8')
|
||
json_obj = json.loads(json_str)
|
||
if json_obj:
|
||
if not json_obj.get("name"):
|
||
return {"code": 1, "msg": "数据格式不正确"}
|
||
self.dbhelper.add_filter_group(name=json_obj.get("name"))
|
||
group_id = self.dbhelper.get_filter_groupid_by_name(
|
||
json_obj.get("name"))
|
||
if not group_id:
|
||
return {"code": 1, "msg": "数据内容不正确"}
|
||
if json_obj.get("rules"):
|
||
for rule in json_obj.get("rules"):
|
||
self.dbhelper.insert_filter_rule(item={
|
||
"group": group_id,
|
||
"name": rule.get("name"),
|
||
"pri": rule.get("pri"),
|
||
"include": rule.get("include"),
|
||
"exclude": rule.get("exclude"),
|
||
"size": rule.get("size"),
|
||
"free": rule.get("free")
|
||
})
|
||
Filter().init_config()
|
||
return {"code": 0, "msg": ""}
|
||
except Exception as err:
|
||
ExceptionUtils.exception_traceback(err)
|
||
return {"code": 1, "msg": "数据格式不正确,%s" % str(err)}
|
||
|
||
@staticmethod
|
||
def get_library_spacesize(data=None):
|
||
"""
|
||
查询媒体库存储空间
|
||
"""
|
||
# 磁盘空间
|
||
UsedPercent = 0
|
||
TotalSpaceList = []
|
||
media = Config().get_config('media')
|
||
if media:
|
||
# 电影目录
|
||
movie_paths = media.get('movie_path')
|
||
if not isinstance(movie_paths, list):
|
||
movie_paths = [movie_paths]
|
||
movie_used, movie_total = 0, 0
|
||
for movie_path in movie_paths:
|
||
if not movie_path:
|
||
continue
|
||
used, total = SystemUtils.get_used_of_partition(movie_path)
|
||
if "%s-%s" % (used, total) not in TotalSpaceList:
|
||
TotalSpaceList.append("%s-%s" % (used, total))
|
||
movie_used += used
|
||
movie_total += total
|
||
# 电视目录
|
||
tv_paths = media.get('tv_path')
|
||
if not isinstance(tv_paths, list):
|
||
tv_paths = [tv_paths]
|
||
tv_used, tv_total = 0, 0
|
||
for tv_path in tv_paths:
|
||
if not tv_path:
|
||
continue
|
||
used, total = SystemUtils.get_used_of_partition(tv_path)
|
||
if "%s-%s" % (used, total) not in TotalSpaceList:
|
||
TotalSpaceList.append("%s-%s" % (used, total))
|
||
tv_used += used
|
||
tv_total += total
|
||
# 动漫目录
|
||
anime_paths = media.get('anime_path')
|
||
if not isinstance(anime_paths, list):
|
||
anime_paths = [anime_paths]
|
||
anime_used, anime_total = 0, 0
|
||
for anime_path in anime_paths:
|
||
if not anime_path:
|
||
continue
|
||
used, total = SystemUtils.get_used_of_partition(anime_path)
|
||
if "%s-%s" % (used, total) not in TotalSpaceList:
|
||
TotalSpaceList.append("%s-%s" % (used, total))
|
||
anime_used += used
|
||
anime_total += total
|
||
# 总空间
|
||
TotalSpaceAry = []
|
||
if movie_total not in TotalSpaceAry:
|
||
TotalSpaceAry.append(movie_total)
|
||
if tv_total not in TotalSpaceAry:
|
||
TotalSpaceAry.append(tv_total)
|
||
if anime_total not in TotalSpaceAry:
|
||
TotalSpaceAry.append(anime_total)
|
||
TotalSpace = sum(TotalSpaceAry)
|
||
# 已使用空间
|
||
UsedSapceAry = []
|
||
if movie_used not in UsedSapceAry:
|
||
UsedSapceAry.append(movie_used)
|
||
if tv_used not in UsedSapceAry:
|
||
UsedSapceAry.append(tv_used)
|
||
if anime_used not in UsedSapceAry:
|
||
UsedSapceAry.append(anime_used)
|
||
UsedSapce = sum(UsedSapceAry)
|
||
# 电影电视使用百分比格式化
|
||
if TotalSpace:
|
||
UsedPercent = "%0.1f" % ((UsedSapce / TotalSpace) * 100)
|
||
# 总剩余空间 格式化
|
||
FreeSpace = "{:,} TB".format(
|
||
round((TotalSpace - UsedSapce) / 1024 / 1024 / 1024 / 1024, 2))
|
||
# 总使用空间 格式化
|
||
UsedSapce = "{:,} TB".format(
|
||
round(UsedSapce / 1024 / 1024 / 1024 / 1024, 2))
|
||
# 总空间 格式化
|
||
TotalSpace = "{:,} TB".format(
|
||
round(TotalSpace / 1024 / 1024 / 1024 / 1024, 2))
|
||
|
||
return {"code": 0,
|
||
"UsedPercent": UsedPercent,
|
||
"FreeSpace": FreeSpace,
|
||
"UsedSapce": UsedSapce,
|
||
"TotalSpace": TotalSpace}
|
||
|
||
def get_transfer_statistics(self, data=None):
|
||
"""
|
||
查询转移历史统计数据
|
||
"""
|
||
MovieChartLabels = []
|
||
MovieNums = []
|
||
TvChartData = {}
|
||
TvNums = []
|
||
AnimeNums = []
|
||
for statistic in self.dbhelper.get_transfer_statistics():
|
||
if statistic[0] == "电影":
|
||
MovieChartLabels.append(statistic[1])
|
||
MovieNums.append(statistic[2])
|
||
else:
|
||
if not TvChartData.get(statistic[1]):
|
||
TvChartData[statistic[1]] = {"tv": 0, "anime": 0}
|
||
if statistic[0] == "电视剧":
|
||
TvChartData[statistic[1]]["tv"] += statistic[2]
|
||
elif statistic[0] == "动漫":
|
||
TvChartData[statistic[1]]["anime"] += statistic[2]
|
||
TvChartLabels = list(TvChartData)
|
||
for tv_data in TvChartData.values():
|
||
TvNums.append(tv_data.get("tv"))
|
||
AnimeNums.append(tv_data.get("anime"))
|
||
|
||
return {
|
||
"code": 0,
|
||
"MovieChartLabels": MovieChartLabels,
|
||
"MovieNums": MovieNums,
|
||
"TvChartLabels": TvChartLabels,
|
||
"TvNums": TvNums,
|
||
"AnimeNums": AnimeNums
|
||
}
|
||
|
||
@staticmethod
|
||
def get_library_mediacount(data=None):
|
||
"""
|
||
查询媒体库统计数据
|
||
"""
|
||
MediaServerClient = MediaServer()
|
||
media_counts = MediaServerClient.get_medias_count()
|
||
UserCount = MediaServerClient.get_user_count()
|
||
if media_counts:
|
||
return {
|
||
"code": 0,
|
||
"Movie": "{:,}".format(media_counts.get('MovieCount')),
|
||
"Series": "{:,}".format(media_counts.get('SeriesCount')),
|
||
"Episodes": "{:,}".format(media_counts.get('EpisodeCount')) if media_counts.get(
|
||
'EpisodeCount') else "",
|
||
"Music": "{:,}".format(media_counts.get('SongCount')),
|
||
"User": UserCount
|
||
}
|
||
else:
|
||
return {"code": -1, "msg": "媒体库服务器连接失败"}
|
||
|
||
@staticmethod
|
||
def get_library_playhistory(data=None):
|
||
"""
|
||
查询媒体库播放记录
|
||
"""
|
||
return {"code": 0, "result": MediaServer().get_activity_log(30)}
|
||
|
||
def get_search_result(self, data=None):
|
||
"""
|
||
查询所有搜索结果
|
||
"""
|
||
SearchResults = {}
|
||
res = self.dbhelper.get_search_results()
|
||
total = len(res)
|
||
for item in res:
|
||
# 质量(来源、效果)、分辨率
|
||
if item.RES_TYPE:
|
||
try:
|
||
res_mix = json.loads(item.RES_TYPE)
|
||
except Exception as err:
|
||
ExceptionUtils.exception_traceback(err)
|
||
continue
|
||
respix = res_mix.get("respix") or ""
|
||
video_encode = res_mix.get("video_encode") or ""
|
||
restype = res_mix.get("restype") or ""
|
||
reseffect = res_mix.get("reseffect") or ""
|
||
else:
|
||
restype = ""
|
||
respix = ""
|
||
reseffect = ""
|
||
video_encode = ""
|
||
# 分组标识 (来源,分辨率)
|
||
group_key = re.sub(r"[-.\s@|]", "", f"{respix}_{restype}").lower()
|
||
# 分组信息
|
||
group_info = {
|
||
"respix": respix,
|
||
"restype": restype,
|
||
}
|
||
# 种子唯一标识 (大小,质量(来源、效果),制作组组成)
|
||
unique_key = re.sub(r"[-.\s@|]", "",
|
||
f"{respix}_{restype}_{video_encode}_{reseffect}_{item.SIZE}_{item.OTHERINFO}").lower()
|
||
# 标识信息
|
||
unique_info = {
|
||
"video_encode": video_encode,
|
||
"size": item.SIZE,
|
||
"reseffect": reseffect,
|
||
"releasegroup": item.OTHERINFO
|
||
}
|
||
# 结果
|
||
title_string = f"{item.TITLE}"
|
||
if item.YEAR:
|
||
title_string = f"{title_string} ({item.YEAR})"
|
||
# 电视剧季集标识
|
||
mtype = item.TYPE or ""
|
||
SE_key = item.ES_STRING if item.ES_STRING and mtype != "MOV" else "MOV"
|
||
media_type = {"MOV": "电影", "TV": "电视剧", "ANI": "动漫"}.get(mtype)
|
||
# 种子信息
|
||
torrent_item = {
|
||
"id": item.ID,
|
||
"seeders": item.SEEDERS,
|
||
"enclosure": item.ENCLOSURE,
|
||
"site": item.SITE,
|
||
"torrent_name": item.TORRENT_NAME,
|
||
"description": item.DESCRIPTION,
|
||
"pageurl": item.PAGEURL,
|
||
"uploadvalue": item.UPLOAD_VOLUME_FACTOR,
|
||
"downloadvalue": item.DOWNLOAD_VOLUME_FACTOR,
|
||
"size": item.SIZE,
|
||
"respix": respix,
|
||
"restype": restype,
|
||
"reseffect": reseffect,
|
||
"releasegroup": item.OTHERINFO,
|
||
"video_encode": video_encode
|
||
}
|
||
# 促销
|
||
free_item = {
|
||
"value": f"{item.UPLOAD_VOLUME_FACTOR} {item.DOWNLOAD_VOLUME_FACTOR}",
|
||
"name": MetaBase.get_free_string(item.UPLOAD_VOLUME_FACTOR, item.DOWNLOAD_VOLUME_FACTOR)
|
||
}
|
||
# 季
|
||
filter_season = SE_key.split()[0] if SE_key and SE_key not in [
|
||
"MOV", "TV"] else None
|
||
# 合并搜索结果
|
||
if SearchResults.get(title_string):
|
||
# 种子列表
|
||
result_item = SearchResults[title_string]
|
||
torrent_dict = SearchResults[title_string].get("torrent_dict")
|
||
SE_dict = torrent_dict.get(SE_key)
|
||
if SE_dict:
|
||
group = SE_dict.get(group_key)
|
||
if group:
|
||
unique = group.get("group_torrents").get(unique_key)
|
||
if unique:
|
||
unique["torrent_list"].append(torrent_item)
|
||
group["group_total"] += 1
|
||
else:
|
||
group["group_total"] += 1
|
||
group.get("group_torrents")[unique_key] = {
|
||
"unique_info": unique_info,
|
||
"torrent_list": [torrent_item]
|
||
}
|
||
else:
|
||
SE_dict[group_key] = {
|
||
"group_info": group_info,
|
||
"group_total": 1,
|
||
"group_torrents": {
|
||
unique_key: {
|
||
"unique_info": unique_info,
|
||
"torrent_list": [torrent_item]
|
||
}
|
||
}
|
||
}
|
||
else:
|
||
torrent_dict[SE_key] = {
|
||
group_key: {
|
||
"group_info": group_info,
|
||
"group_total": 1,
|
||
"group_torrents": {
|
||
unique_key: {
|
||
"unique_info": unique_info,
|
||
"torrent_list": [torrent_item]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
# 过滤条件
|
||
torrent_filter = dict(result_item.get("filter"))
|
||
if free_item not in torrent_filter.get("free"):
|
||
torrent_filter["free"].append(free_item)
|
||
if item.SITE not in torrent_filter.get("site"):
|
||
torrent_filter["site"].append(item.SITE)
|
||
if video_encode \
|
||
and video_encode not in torrent_filter.get("video"):
|
||
torrent_filter["video"].append(video_encode)
|
||
if filter_season \
|
||
and filter_season not in torrent_filter.get("season"):
|
||
torrent_filter["season"].append(filter_season)
|
||
else:
|
||
# 是否已存在
|
||
if item.TMDBID:
|
||
exist_flag = MediaServer().check_item_exists(
|
||
title=item.TITLE, year=item.YEAR, tmdbid=item.TMDBID)
|
||
else:
|
||
exist_flag = False
|
||
SearchResults[title_string] = {
|
||
"key": item.ID,
|
||
"title": item.TITLE,
|
||
"year": item.YEAR,
|
||
"type_key": mtype,
|
||
"image": item.IMAGE,
|
||
"type": media_type,
|
||
"vote": item.VOTE,
|
||
"tmdbid": item.TMDBID,
|
||
"backdrop": item.IMAGE,
|
||
"poster": item.POSTER,
|
||
"overview": item.OVERVIEW,
|
||
"exist": exist_flag,
|
||
"torrent_dict": {
|
||
SE_key: {
|
||
group_key: {
|
||
"group_info": group_info,
|
||
"group_total": 1,
|
||
"group_torrents": {
|
||
unique_key: {
|
||
"unique_info": unique_info,
|
||
"torrent_list": [torrent_item]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"filter": {
|
||
"site": [item.SITE],
|
||
"free": [free_item],
|
||
"video": [video_encode] if video_encode else [],
|
||
"season": [filter_season] if filter_season else []
|
||
}
|
||
}
|
||
|
||
# 提升整季的顺序到顶层
|
||
def se_sort(k):
|
||
k = re.sub(r" +|(?<=s\d)\D*?(?=e)|(?<=s\d\d)\D*?(?=e)",
|
||
" ", k[0], flags=re.I).split()
|
||
return (k[0], k[1]) if len(k) > 1 else ("Z" + k[0], "ZZZ")
|
||
|
||
# 开始排序季集顺序
|
||
for title, item in SearchResults.items():
|
||
# 排序筛选器 季
|
||
item["filter"]["season"].sort(reverse=True)
|
||
# 排序种子列 集
|
||
item["torrent_dict"] = sorted(item["torrent_dict"].items(),
|
||
key=se_sort,
|
||
reverse=True)
|
||
return {"code": 0, "total": total, "result": SearchResults}
|
||
|
||
@staticmethod
|
||
def search_media_infos(data):
|
||
"""
|
||
根据关键字搜索相似词条
|
||
"""
|
||
SearchWord = data.get("keyword")
|
||
if not SearchWord:
|
||
return []
|
||
SearchSourceType = data.get("searchtype")
|
||
medias = WebUtils.search_media_infos(keyword=SearchWord,
|
||
source=SearchSourceType)
|
||
|
||
return {"code": 0, "result": [media.to_dict() for media in medias]}
|
||
|
||
@staticmethod
|
||
def get_movie_rss_list(data=None):
|
||
"""
|
||
查询所有电影订阅
|
||
"""
|
||
return {"code": 0, "result": Subscribe().get_subscribe_movies()}
|
||
|
||
@staticmethod
|
||
def get_tv_rss_list(data=None):
|
||
"""
|
||
查询所有电视剧订阅
|
||
"""
|
||
return {"code": 0, "result": Subscribe().get_subscribe_tvs()}
|
||
|
||
def get_rss_history(self, data):
|
||
"""
|
||
查询所有订阅历史
|
||
"""
|
||
mtype = data.get("type")
|
||
return {"code": 0, "result": [rec.as_dict() for rec in self.dbhelper.get_rss_history(rtype=mtype)]}
|
||
|
||
@staticmethod
|
||
def get_downloading(data=None):
|
||
"""
|
||
查询正在下载的任务
|
||
"""
|
||
torrents = Downloader().get_downloading_progress()
|
||
MediaHander = Media()
|
||
for torrent in torrents:
|
||
# 识别
|
||
name = torrent.get("name")
|
||
media_info = MediaHander.get_media_info(title=name)
|
||
if not media_info:
|
||
torrent.update({
|
||
"title": name,
|
||
"image": ""
|
||
})
|
||
continue
|
||
if not media_info.tmdb_info:
|
||
year = media_info.year
|
||
if year:
|
||
title = "%s (%s) %s" % (media_info.get_name(),
|
||
year, media_info.get_season_episode_string())
|
||
else:
|
||
title = "%s %s" % (media_info.get_name(),
|
||
media_info.get_season_episode_string())
|
||
else:
|
||
title = "%s %s" % (media_info.get_title_string(
|
||
), media_info.get_season_episode_string())
|
||
poster_path = media_info.get_poster_image()
|
||
torrent.update({
|
||
"title": title,
|
||
"image": poster_path or ""
|
||
})
|
||
return {"code": 0, "result": torrents}
|
||
|
||
def get_transfer_history(self, data):
|
||
"""
|
||
查询媒体整理历史记录
|
||
"""
|
||
PageNum = data.get("pagenum")
|
||
if not PageNum:
|
||
PageNum = 30
|
||
SearchStr = data.get("keyword")
|
||
CurrentPage = data.get("page")
|
||
if not CurrentPage:
|
||
CurrentPage = 1
|
||
else:
|
||
CurrentPage = int(CurrentPage)
|
||
totalCount, historys = self.dbhelper.get_transfer_history(
|
||
SearchStr, CurrentPage, PageNum)
|
||
historys_list = []
|
||
for history in historys:
|
||
history = history.as_dict()
|
||
sync_mode = history.get("MODE")
|
||
rmt_mode = ModuleConf.get_dictenum_key(
|
||
ModuleConf.RMT_MODES, sync_mode) if sync_mode else ""
|
||
history.update({
|
||
"SYNC_MODE": sync_mode,
|
||
"RMT_MODE": rmt_mode
|
||
})
|
||
historys_list.append(history)
|
||
TotalPage = floor(totalCount / PageNum) + 1
|
||
|
||
return {
|
||
"code": 0,
|
||
"total": totalCount,
|
||
"result": historys_list,
|
||
"totalPage": TotalPage,
|
||
"pageNum": PageNum,
|
||
"currentPage": CurrentPage
|
||
}
|
||
|
||
def get_unknown_list(self, data=None):
|
||
"""
|
||
查询所有未识别记录
|
||
"""
|
||
Items = []
|
||
Records = self.dbhelper.get_transfer_unknown_paths()
|
||
for rec in Records:
|
||
if not rec.PATH:
|
||
continue
|
||
path = rec.PATH.replace("\\", "/") if rec.PATH else ""
|
||
path_to = rec.DEST.replace("\\", "/") if rec.DEST else ""
|
||
sync_mode = rec.MODE or ""
|
||
rmt_mode = ModuleConf.get_dictenum_key(ModuleConf.RMT_MODES,
|
||
sync_mode) if sync_mode else ""
|
||
Items.append({
|
||
"id": rec.ID,
|
||
"path": path,
|
||
"to": path_to,
|
||
"name": path,
|
||
"sync_mode": sync_mode,
|
||
"rmt_mode": rmt_mode,
|
||
})
|
||
|
||
return {"code": 0, "items": Items}
|
||
|
||
def get_unknown_list_by_page(self, data):
|
||
"""
|
||
查询所有未识别记录
|
||
"""
|
||
PageNum = data.get("pagenum")
|
||
if not PageNum:
|
||
PageNum = 30
|
||
SearchStr = data.get("keyword")
|
||
CurrentPage = data.get("page")
|
||
if not CurrentPage:
|
||
CurrentPage = 1
|
||
else:
|
||
CurrentPage = int(CurrentPage)
|
||
totalCount, Records = self.dbhelper.get_transfer_unknown_paths_by_page(
|
||
SearchStr, CurrentPage, PageNum)
|
||
Items = []
|
||
for rec in Records:
|
||
if not rec.PATH:
|
||
continue
|
||
path = rec.PATH.replace("\\", "/") if rec.PATH else ""
|
||
path_to = rec.DEST.replace("\\", "/") if rec.DEST else ""
|
||
sync_mode = rec.MODE or ""
|
||
rmt_mode = ModuleConf.get_dictenum_key(ModuleConf.RMT_MODES,
|
||
sync_mode) if sync_mode else ""
|
||
Items.append({
|
||
"id": rec.ID,
|
||
"path": path,
|
||
"to": path_to,
|
||
"name": path,
|
||
"sync_mode": sync_mode,
|
||
"rmt_mode": rmt_mode,
|
||
})
|
||
TotalPage = floor(totalCount / PageNum) + 1
|
||
|
||
return {
|
||
"code": 0,
|
||
"total": totalCount,
|
||
"items": Items,
|
||
"totalPage": TotalPage,
|
||
"pageNum": PageNum,
|
||
"currentPage": CurrentPage
|
||
}
|
||
|
||
def unidentification(self):
|
||
"""
|
||
重新识别所有未识别记录
|
||
"""
|
||
ItemIds = []
|
||
Records = self.dbhelper.get_transfer_unknown_paths()
|
||
for rec in Records:
|
||
if not rec.PATH:
|
||
continue
|
||
ItemIds.append(rec.ID)
|
||
|
||
if len(ItemIds) > 0:
|
||
WebAction.re_identification(self, {"flag": "unidentification", "ids": ItemIds})
|
||
|
||
def get_customwords(self, data=None):
|
||
words = []
|
||
words_info = self.dbhelper.get_custom_words(gid=-1)
|
||
for word_info in words_info:
|
||
words.append({"id": word_info.ID,
|
||
"replaced": word_info.REPLACED,
|
||
"replace": word_info.REPLACE,
|
||
"front": word_info.FRONT,
|
||
"back": word_info.BACK,
|
||
"offset": word_info.OFFSET,
|
||
"type": word_info.TYPE,
|
||
"group_id": word_info.GROUP_ID,
|
||
"season": word_info.SEASON,
|
||
"enabled": word_info.ENABLED,
|
||
"regex": word_info.REGEX,
|
||
"help": word_info.HELP, })
|
||
groups = [{"id": "-1",
|
||
"name": "通用",
|
||
"link": "",
|
||
"type": "1",
|
||
"seasons": "0",
|
||
"words": words}]
|
||
groups_info = self.dbhelper.get_custom_word_groups()
|
||
for group_info in groups_info:
|
||
gid = group_info.ID
|
||
name = "%s (%s)" % (group_info.TITLE, group_info.YEAR)
|
||
gtype = group_info.TYPE
|
||
if gtype == 1:
|
||
link = "https://www.themoviedb.org/movie/%s" % group_info.TMDBID
|
||
else:
|
||
link = "https://www.themoviedb.org/tv/%s" % group_info.TMDBID
|
||
words = []
|
||
words_info = self.dbhelper.get_custom_words(gid=gid)
|
||
for word_info in words_info:
|
||
words.append({"id": word_info.ID,
|
||
"replaced": word_info.REPLACED,
|
||
"replace": word_info.REPLACE,
|
||
"front": word_info.FRONT,
|
||
"back": word_info.BACK,
|
||
"offset": word_info.OFFSET,
|
||
"type": word_info.TYPE,
|
||
"group_id": word_info.GROUP_ID,
|
||
"season": word_info.SEASON,
|
||
"enabled": word_info.ENABLED,
|
||
"regex": word_info.REGEX,
|
||
"help": word_info.HELP, })
|
||
groups.append({"id": gid,
|
||
"name": name,
|
||
"link": link,
|
||
"type": group_info.TYPE,
|
||
"seasons": group_info.SEASON_COUNT,
|
||
"words": words})
|
||
return {
|
||
"code": 0,
|
||
"result": groups
|
||
}
|
||
|
||
def get_directorysync(self, data=None):
|
||
"""
|
||
查询所有同步目录
|
||
"""
|
||
sync_paths = self.dbhelper.get_config_sync_paths()
|
||
SyncPaths = []
|
||
if sync_paths:
|
||
for sync_item in sync_paths:
|
||
SyncPath = {'id': sync_item.ID,
|
||
'from': sync_item.SOURCE,
|
||
'to': sync_item.DEST or "",
|
||
'unknown': sync_item.UNKNOWN or "",
|
||
'syncmod': sync_item.MODE,
|
||
'syncmod_name': RmtMode[sync_item.MODE.upper()].value,
|
||
'rename': sync_item.RENAME,
|
||
'enabled': sync_item.ENABLED}
|
||
SyncPaths.append(SyncPath)
|
||
SyncPaths = sorted(SyncPaths, key=lambda o: o.get("from"))
|
||
return {"code": 0, "result": SyncPaths}
|
||
|
||
def get_users(self, data=None):
|
||
"""
|
||
查询所有用户
|
||
"""
|
||
user_list = self.dbhelper.get_users()
|
||
Users = []
|
||
for user in user_list:
|
||
pris = str(user.PRIS).split(",")
|
||
Users.append({"id": user.ID, "name": user.NAME, "pris": pris})
|
||
return {"code": 0, "result": Users}
|
||
|
||
@staticmethod
|
||
def get_filterrules(data=None):
|
||
"""
|
||
查询所有过滤规则
|
||
"""
|
||
RuleGroups = Filter().get_rule_infos()
|
||
sql_file = os.path.join(Config().get_script_path(), "init_filter.sql")
|
||
with open(sql_file, "r", encoding="utf-8") as f:
|
||
sql_list = f.read().split(';\n')
|
||
Init_RuleGroups = []
|
||
i = 0
|
||
while i < len(sql_list):
|
||
rulegroup = {}
|
||
rulegroup_info = re.findall(
|
||
r"[0-9]+,'[^\"]+NULL", sql_list[i], re.I)[0].split(",")
|
||
rulegroup['id'] = int(rulegroup_info[0])
|
||
rulegroup['name'] = rulegroup_info[1][1:-1]
|
||
rulegroup['rules'] = []
|
||
rulegroup['sql'] = [sql_list[i]]
|
||
if i + 1 < len(sql_list):
|
||
rules = re.findall(
|
||
r"[0-9]+,'[^\"]+NULL", sql_list[i + 1], re.I)[0].split("),\n (")
|
||
for rule in rules:
|
||
rule_info = {}
|
||
rule = rule.split(",")
|
||
rule_info['name'] = rule[2][1:-1]
|
||
rule_info['include'] = rule[4][1:-1]
|
||
rule_info['exclude'] = rule[5][1:-1]
|
||
rulegroup['rules'].append(rule_info)
|
||
rulegroup["sql"].append(sql_list[i + 1])
|
||
Init_RuleGroups.append(rulegroup)
|
||
i = i + 2
|
||
return {
|
||
"code": 0,
|
||
"ruleGroups": RuleGroups,
|
||
"initRules": Init_RuleGroups
|
||
}
|
||
|
||
def __update_directory(self, data):
|
||
"""
|
||
维护媒体库目录
|
||
"""
|
||
cfg = self.set_config_directory(Config().get_config(),
|
||
data.get("oper"),
|
||
data.get("key"),
|
||
data.get("value"),
|
||
data.get("replace_value"))
|
||
# 保存配置
|
||
Config().save_config(cfg)
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __test_site(data):
|
||
"""
|
||
测试站点连通性
|
||
"""
|
||
flag, msg, times = Sites().test_connection(data.get("id"))
|
||
code = 0 if flag else -1
|
||
return {"code": code, "msg": msg, "time": times}
|
||
|
||
@staticmethod
|
||
def __get_sub_path(data):
|
||
"""
|
||
查询下级子目录
|
||
"""
|
||
r = []
|
||
try:
|
||
ft = data.get("filter") or "ALL"
|
||
d = data.get("dir")
|
||
if not d or d == "/":
|
||
if SystemUtils.get_system() == OsType.WINDOWS:
|
||
partitions = SystemUtils.get_windows_drives()
|
||
if partitions:
|
||
dirs = [os.path.join(partition, "/")
|
||
for partition in partitions]
|
||
else:
|
||
dirs = [os.path.join("C:/", f)
|
||
for f in os.listdir("C:/")]
|
||
else:
|
||
dirs = [os.path.join("/", f) for f in os.listdir("/")]
|
||
else:
|
||
d = os.path.normpath(unquote(d))
|
||
if not os.path.isdir(d):
|
||
d = os.path.dirname(d)
|
||
dirs = [os.path.join(d, f) for f in os.listdir(d)]
|
||
dirs.sort()
|
||
for ff in dirs:
|
||
if os.path.isdir(ff):
|
||
if 'ONLYDIR' in ft or 'ALL' in ft:
|
||
r.append({
|
||
"path": ff.replace("\\", "/"),
|
||
"name": os.path.basename(ff),
|
||
"type": "dir",
|
||
"rel": os.path.dirname(ff).replace("\\", "/")
|
||
})
|
||
else:
|
||
ext = os.path.splitext(ff)[-1][1:]
|
||
flag = False
|
||
if 'ONLYFILE' in ft or 'ALL' in ft:
|
||
flag = True
|
||
elif "MEDIAFILE" in ft and f".{str(ext).lower()}" in RMT_MEDIAEXT:
|
||
flag = True
|
||
elif "SUBFILE" in ft and f".{str(ext).lower()}" in RMT_SUBEXT:
|
||
flag = True
|
||
if flag:
|
||
r.append({
|
||
"path": ff.replace("\\", "/"),
|
||
"name": os.path.basename(ff),
|
||
"type": "file",
|
||
"rel": os.path.dirname(ff).replace("\\", "/"),
|
||
"ext": ext,
|
||
"size": StringUtils.str_filesize(os.path.getsize(ff))
|
||
})
|
||
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {
|
||
"code": -1,
|
||
"message": '加载路径失败: %s' % str(e)
|
||
}
|
||
return {
|
||
"code": 0,
|
||
"count": len(r),
|
||
"data": r
|
||
}
|
||
|
||
@staticmethod
|
||
def __rename_file(data):
|
||
"""
|
||
文件重命名
|
||
"""
|
||
path = data.get("path")
|
||
name = data.get("name")
|
||
if path and name:
|
||
try:
|
||
shutil.move(path, os.path.join(os.path.dirname(path), name))
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": -1, "msg": str(e)}
|
||
return {"code": 0}
|
||
|
||
def __delete_files(self, data):
|
||
"""
|
||
删除文件
|
||
"""
|
||
files = data.get("files")
|
||
if files:
|
||
# 删除文件
|
||
for file in files:
|
||
del_flag, del_msg = self.delete_media_file(filedir=os.path.dirname(file),
|
||
filename=os.path.basename(file))
|
||
if not del_flag:
|
||
log.error(f"【MediaFile】{del_msg}")
|
||
else:
|
||
log.info(f"【MediaFile】{del_msg}")
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __download_subtitle(data):
|
||
"""
|
||
从配置的字幕服务下载单个文件的字幕
|
||
"""
|
||
path = data.get("path")
|
||
name = data.get("name")
|
||
media = Media().get_media_info(title=name)
|
||
if not media or not media.tmdb_info:
|
||
return {"code": -1, "msg": f"{name} 无法从TMDB查询到媒体信息"}
|
||
if not media.imdb_id:
|
||
media.set_tmdb_info(Media().get_tmdb_info(mtype=media.type,
|
||
tmdbid=media.tmdb_id))
|
||
event_item = media.to_dict()
|
||
event_item.update({
|
||
"file": os.path.splitext(path)[0],
|
||
"file_ext": os.path.splitext(name)[-1],
|
||
"bluray": False
|
||
})
|
||
# 触发字幕下载事件
|
||
EventManager().send_event(EventType.SubtitleDownload, event_item)
|
||
return {"code": 0, "msg": "字幕下载任务已提交,正在后台运行。"}
|
||
|
||
@staticmethod
|
||
def __get_download_setting(data):
|
||
sid = data.get("sid")
|
||
if sid:
|
||
download_setting = Downloader().get_download_setting(sid=sid)
|
||
else:
|
||
download_setting = list(
|
||
Downloader().get_download_setting().values())
|
||
return {"code": 0, "data": download_setting}
|
||
|
||
def __update_download_setting(self, data):
|
||
sid = data.get("sid")
|
||
name = data.get("name")
|
||
category = data.get("category")
|
||
tags = data.get("tags")
|
||
content_layout = data.get("content_layout")
|
||
is_paused = data.get("is_paused")
|
||
upload_limit = data.get("upload_limit")
|
||
download_limit = data.get("download_limit")
|
||
ratio_limit = data.get("ratio_limit")
|
||
seeding_time_limit = data.get("seeding_time_limit")
|
||
downloader = data.get("downloader")
|
||
self.dbhelper.update_download_setting(sid=sid,
|
||
name=name,
|
||
category=category,
|
||
tags=tags,
|
||
content_layout=content_layout,
|
||
is_paused=is_paused,
|
||
upload_limit=upload_limit or 0,
|
||
download_limit=download_limit or 0,
|
||
ratio_limit=ratio_limit or 0,
|
||
seeding_time_limit=seeding_time_limit or 0,
|
||
downloader=downloader)
|
||
Downloader().init_config()
|
||
return {"code": 0}
|
||
|
||
def __delete_download_setting(self, data):
|
||
sid = data.get("sid")
|
||
self.dbhelper.delete_download_setting(sid=sid)
|
||
Downloader().init_config()
|
||
return {"code": 0}
|
||
|
||
def __update_message_client(self, data):
|
||
"""
|
||
更新消息设置
|
||
"""
|
||
name = data.get("name")
|
||
cid = data.get("cid")
|
||
ctype = data.get("type")
|
||
config = data.get("config")
|
||
switchs = data.get("switchs")
|
||
interactive = data.get("interactive")
|
||
enabled = data.get("enabled")
|
||
if cid:
|
||
self.dbhelper.delete_message_client(cid=cid)
|
||
self.dbhelper.insert_message_client(name=name,
|
||
ctype=ctype,
|
||
config=config,
|
||
switchs=switchs,
|
||
interactive=interactive,
|
||
enabled=enabled)
|
||
Message().init_config()
|
||
return {"code": 0}
|
||
|
||
def __delete_message_client(self, data):
|
||
"""
|
||
删除消息设置
|
||
"""
|
||
if self.dbhelper.delete_message_client(cid=data.get("cid")):
|
||
Message().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
def __check_message_client(self, data):
|
||
"""
|
||
维护消息设置
|
||
"""
|
||
flag = data.get("flag")
|
||
cid = data.get("cid")
|
||
ctype = data.get("type")
|
||
checked = data.get("checked")
|
||
if flag == "interactive":
|
||
# TG/WX只能开启一个交互
|
||
if checked:
|
||
self.dbhelper.check_message_client(interactive=0, ctype=ctype)
|
||
self.dbhelper.check_message_client(cid=cid,
|
||
interactive=1 if checked else 0)
|
||
Message().init_config()
|
||
return {"code": 0}
|
||
elif flag == "enable":
|
||
self.dbhelper.check_message_client(cid=cid,
|
||
enabled=1 if checked else 0)
|
||
Message().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
@staticmethod
|
||
def __get_message_client(data):
|
||
"""
|
||
获取消息设置
|
||
"""
|
||
cid = data.get("cid")
|
||
return {"code": 0, "detail": Message().get_message_client_info(cid=cid)}
|
||
|
||
@staticmethod
|
||
def __test_message_client(data):
|
||
"""
|
||
测试消息设置
|
||
"""
|
||
ctype = data.get("type")
|
||
config = json.loads(data.get("config"))
|
||
res = Message().get_status(ctype=ctype, config=config)
|
||
if res:
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
@staticmethod
|
||
def __get_indexers(data=None):
|
||
"""
|
||
获取索引器
|
||
"""
|
||
return {"code": 0, "indexers": Indexer().get_indexer_dict()}
|
||
|
||
@staticmethod
|
||
def __get_download_dirs(data):
|
||
"""
|
||
获取下载目录
|
||
"""
|
||
sid = data.get("sid")
|
||
site = data.get("site")
|
||
if not sid and site:
|
||
sid = Sites().get_site_download_setting(site_name=site)
|
||
dirs = Downloader().get_download_dirs(setting=sid)
|
||
return {"code": 0, "paths": dirs}
|
||
|
||
@staticmethod
|
||
def __find_hardlinks(data):
|
||
files = data.get("files")
|
||
file_dir = data.get("dir")
|
||
if not files:
|
||
return []
|
||
if not file_dir and os.name != "nt":
|
||
# 取根目录下一级为查找目录
|
||
file_dir = os.path.commonpath(files).replace("\\", "/")
|
||
if file_dir != "/":
|
||
file_dir = "/" + str(file_dir).split("/")[1]
|
||
else:
|
||
return []
|
||
hardlinks = {}
|
||
if files:
|
||
try:
|
||
for file in files:
|
||
hardlinks[os.path.basename(file)] = SystemUtils(
|
||
).find_hardlinks(file=file, fdir=file_dir)
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1}
|
||
return {"code": 0, "data": hardlinks}
|
||
|
||
@staticmethod
|
||
def __update_sites_cookie_ua(data):
|
||
"""
|
||
更新所有站点的Cookie和UA
|
||
"""
|
||
siteid = data.get("siteid")
|
||
username = data.get("username")
|
||
password = data.get("password")
|
||
twostepcode = data.get("two_step_code")
|
||
ocrflag = data.get("ocrflag")
|
||
# 保存设置
|
||
SystemConfig().set_system_config(key="CookieUserInfo",
|
||
value={
|
||
"username": username,
|
||
"password": password,
|
||
"two_step_code": twostepcode
|
||
})
|
||
retcode, messages = SiteCookie().update_sites_cookie_ua(siteid=siteid,
|
||
username=username,
|
||
password=password,
|
||
twostepcode=twostepcode,
|
||
ocrflag=ocrflag)
|
||
if retcode == 0:
|
||
Sites().init_config()
|
||
return {"code": retcode, "messages": messages}
|
||
|
||
def __update_site_cookie_ua(self, data):
|
||
"""
|
||
更新单个站点的Cookie和UA
|
||
"""
|
||
siteid = data.get("site_id")
|
||
cookie = data.get("site_cookie")
|
||
ua = data.get("site_ua")
|
||
self.dbhelper.update_site_cookie_ua(tid=siteid, cookie=cookie, ua=ua)
|
||
Sites().init_config()
|
||
return {"code": 0, "messages": "请求发送成功"}
|
||
|
||
@staticmethod
|
||
def __set_site_captcha_code(data):
|
||
"""
|
||
设置站点验证码
|
||
"""
|
||
code = data.get("code")
|
||
value = data.get("value")
|
||
SiteCookie().set_code(code=code, value=value)
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __update_torrent_remove_task(data):
|
||
"""
|
||
更新自动删种任务
|
||
"""
|
||
flag, msg = TorrentRemover().update_torrent_remove_task(data=data)
|
||
if not flag:
|
||
return {"code": 1, "msg": msg}
|
||
else:
|
||
TorrentRemover().init_config()
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __get_torrent_remove_task(data=None):
|
||
"""
|
||
获取自动删种任务
|
||
"""
|
||
if data:
|
||
tid = data.get("tid")
|
||
else:
|
||
tid = None
|
||
return {"code": 0, "detail": TorrentRemover().get_torrent_remove_tasks(taskid=tid)}
|
||
|
||
@staticmethod
|
||
def __delete_torrent_remove_task(data):
|
||
"""
|
||
删除自动删种任务
|
||
"""
|
||
tid = data.get("tid")
|
||
flag = TorrentRemover().delete_torrent_remove_task(taskid=tid)
|
||
if flag:
|
||
TorrentRemover().init_config()
|
||
return {"code": 0}
|
||
else:
|
||
return {"code": 1}
|
||
|
||
@staticmethod
|
||
def __get_remove_torrents(data):
|
||
"""
|
||
获取满足自动删种任务的种子
|
||
"""
|
||
tid = data.get("tid")
|
||
flag, torrents = TorrentRemover().get_remove_torrents(taskid=tid)
|
||
if not flag or not torrents:
|
||
return {"code": 1, "msg": "未获取到符合处理条件种子"}
|
||
return {"code": 0, "data": torrents}
|
||
|
||
@staticmethod
|
||
def __auto_remove_torrents(data):
|
||
"""
|
||
执行自动删种任务
|
||
"""
|
||
tid = data.get("tid")
|
||
TorrentRemover().auto_remove_torrents(taskids=tid)
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def __get_site_favicon(data):
|
||
"""
|
||
获取站点图标
|
||
"""
|
||
sitename = data.get("name")
|
||
return {"code": 0, "icon": Sites().get_site_favicon(site_name=sitename)}
|
||
|
||
def get_douban_history(self, data=None):
|
||
"""
|
||
查询豆瓣同步历史
|
||
"""
|
||
results = self.dbhelper.get_douban_history()
|
||
return {"code": 0, "result": [item.as_dict() for item in results]}
|
||
|
||
def __delete_douban_history(self, data):
|
||
"""
|
||
删除豆瓣同步历史
|
||
"""
|
||
self.dbhelper.delete_douban_history(data.get("id"))
|
||
return {"code": 0}
|
||
|
||
def __list_brushtask_torrents(self, data):
|
||
"""
|
||
获取刷流任务的种子明细
|
||
"""
|
||
results = self.dbhelper.get_brushtask_torrents(brush_id=data.get("id"),
|
||
active=False)
|
||
if not results:
|
||
return {"code": 1, "msg": "未下载种子或未获取到种子明细"}
|
||
return {"code": 0, "data": [item.as_dict() for item in results]}
|
||
|
||
@staticmethod
|
||
def __set_system_config(data):
|
||
"""
|
||
设置系统设置(数据库)
|
||
"""
|
||
key = data.get("key")
|
||
value = data.get("value")
|
||
if not key or not value:
|
||
return {"code": 1}
|
||
try:
|
||
SystemConfig().set_system_config(key=key, value=value)
|
||
return {"code": 0}
|
||
except Exception as e:
|
||
ExceptionUtils.exception_traceback(e)
|
||
return {"code": 1}
|
||
|
||
@staticmethod
|
||
def get_site_user_statistics(data):
|
||
"""
|
||
获取站点用户统计信息
|
||
"""
|
||
sites = data.get("sites")
|
||
encoding = data.get("encoding") or "RAW"
|
||
sort_by = data.get("sort_by")
|
||
sort_on = data.get("sort_on")
|
||
site_hash = data.get("site_hash")
|
||
statistics = SiteUserInfo().get_site_user_statistics(sites=sites, encoding=encoding)
|
||
if sort_by and sort_on in ["asc", "desc"]:
|
||
if sort_on == "asc":
|
||
statistics.sort(key=lambda x: x[sort_by])
|
||
else:
|
||
statistics.sort(key=lambda x: x[sort_by], reverse=True)
|
||
if site_hash == "Y":
|
||
for item in statistics:
|
||
item["site_hash"] = StringUtils.md5_hash(item.get("site"))
|
||
return {"code": 0, "data": statistics}
|
||
|
||
@staticmethod
|
||
def send_custom_message(data):
|
||
"""
|
||
发送自定义消息
|
||
"""
|
||
title = data.get("title")
|
||
text = data.get("text") or ""
|
||
image = data.get("image") or ""
|
||
Message().send_custom_message(title=title, text=text, image=image)
|
||
return {"code": 0}
|
||
|
||
@staticmethod
|
||
def get_rmt_modes():
|
||
RmtModes = ModuleConf.RMT_MODES_LITE if SystemUtils.is_lite_version(
|
||
) else ModuleConf.RMT_MODES
|
||
return [{
|
||
"value": value,
|
||
"name": name.value
|
||
} for value, name in RmtModes.items()]
|
||
|
||
def __cookiecloud_sync(self, data):
|
||
"""
|
||
CookieCloud数据同步
|
||
"""
|
||
server = data.get("server")
|
||
key = data.get("key")
|
||
password = data.get("password")
|
||
# 保存设置
|
||
SystemConfig().set_system_config(key="CookieCloud",
|
||
value={
|
||
"server": server,
|
||
"key": key,
|
||
"password": password
|
||
})
|
||
# 同步数据
|
||
contents, retmsg = CookieCloudHelper(server=server,
|
||
key=key,
|
||
password=password).download_data()
|
||
if not contents:
|
||
return {"code": 1, "msg": retmsg}
|
||
success_count = 0
|
||
for domain, content_list in contents.items():
|
||
if domain.startswith('.'):
|
||
domain = domain[1:]
|
||
cookie_str = ""
|
||
for content in content_list:
|
||
cookie_str += content.get("name") + \
|
||
"=" + content.get("value") + ";"
|
||
if not cookie_str:
|
||
continue
|
||
site_info = Sites().get_sites(siteurl=domain)
|
||
if not site_info:
|
||
continue
|
||
self.dbhelper.update_site_cookie_ua(tid=site_info.get("id"),
|
||
cookie=cookie_str)
|
||
success_count += 1
|
||
if success_count:
|
||
# 重载站点信息
|
||
Sites().init_config()
|
||
return {"code": 0, "msg": f"成功更新 {success_count} 个站点的Cookie数据"}
|
||
return {"code": 0, "msg": "同步完成,但未更新任何站点的Cookie!"}
|
||
|
||
def media_detail(self, data):
|
||
"""
|
||
获取媒体详情
|
||
"""
|
||
# TMDBID 或 DB:豆瓣ID
|
||
tmdbid = data.get("tmdbid")
|
||
mtype = MediaType.MOVIE if data.get(
|
||
"type") in MovieTypes else MediaType.TV
|
||
if not tmdbid:
|
||
return {"code": 1, "msg": "未指定媒体ID"}
|
||
media_info = WebUtils.get_mediainfo_from_id(
|
||
mtype=mtype, mediaid=tmdbid)
|
||
# 检查TMDB信息
|
||
if not media_info or not media_info.tmdb_info:
|
||
return {
|
||
"code": 1,
|
||
"msg": "无法查询到TMDB信息"
|
||
}
|
||
# 查询存在及订阅状态
|
||
fav, rssid = self.get_media_exists_flag(mtype=mtype,
|
||
title=media_info.title,
|
||
year=media_info.year,
|
||
mediaid=media_info.tmdb_id)
|
||
MediaHander = Media()
|
||
return {
|
||
"code": 0,
|
||
"data": {
|
||
"tmdbid": media_info.tmdb_id,
|
||
"douban_id": media_info.douban_id,
|
||
"background": MediaHander.get_tmdb_backdrops(tmdbinfo=media_info.tmdb_info),
|
||
"image": media_info.get_poster_image(),
|
||
"vote": media_info.vote_average,
|
||
"year": media_info.year,
|
||
"title": media_info.title,
|
||
"genres": MediaHander.get_tmdb_genres_names(tmdbinfo=media_info.tmdb_info),
|
||
"overview": media_info.overview,
|
||
"runtime": StringUtils.str_timehours(media_info.runtime),
|
||
"fact": MediaHander.get_tmdb_factinfo(media_info),
|
||
"crews": MediaHander.get_tmdb_crews(tmdbinfo=media_info.tmdb_info, nums=6),
|
||
"actors": MediaHander.get_tmdb_cats(mtype=mtype, tmdbid=media_info.tmdb_id),
|
||
"link": media_info.get_detail_url(),
|
||
"douban_link": media_info.get_douban_detail_url(),
|
||
"fav": fav,
|
||
"rssid": rssid
|
||
}
|
||
}
|
||
|
||
@staticmethod
|
||
def __media_similar(data):
|
||
"""
|
||
查询TMDB相似媒体
|
||
"""
|
||
tmdbid = data.get("tmdbid")
|
||
page = data.get("page") or 1
|
||
mtype = MediaType.MOVIE if data.get(
|
||
"type") in MovieTypes else MediaType.TV
|
||
if not tmdbid:
|
||
return {"code": 1, "msg": "未指定TMDBID"}
|
||
if mtype == MediaType.MOVIE:
|
||
result = Media().get_movie_similar(tmdbid=tmdbid, page=page)
|
||
else:
|
||
result = Media().get_tv_similar(tmdbid=tmdbid, page=page)
|
||
return {"code": 0, "data": result}
|
||
|
||
@staticmethod
|
||
def __media_recommendations(data):
|
||
"""
|
||
查询TMDB同类推荐媒体
|
||
"""
|
||
tmdbid = data.get("tmdbid")
|
||
page = data.get("page") or 1
|
||
mtype = MediaType.MOVIE if data.get(
|
||
"type") in MovieTypes else MediaType.TV
|
||
if not tmdbid:
|
||
return {"code": 1, "msg": "未指定TMDBID"}
|
||
if mtype == MediaType.MOVIE:
|
||
result = Media().get_movie_recommendations(tmdbid=tmdbid, page=page)
|
||
else:
|
||
result = Media().get_tv_recommendations(tmdbid=tmdbid, page=page)
|
||
return {"code": 0, "data": result}
|
||
|
||
@staticmethod
|
||
def __media_person(data):
|
||
"""
|
||
查询TMDB媒体所有演员
|
||
"""
|
||
tmdbid = data.get("tmdbid")
|
||
mtype = MediaType.MOVIE if data.get(
|
||
"type") in MovieTypes else MediaType.TV
|
||
if not tmdbid:
|
||
return {"code": 1, "msg": "未指定TMDBID"}
|
||
return {"code": 0, "data": Media().get_tmdb_cats(tmdbid=tmdbid,
|
||
mtype=mtype)}
|
||
|
||
@staticmethod
|
||
def __person_medias(data):
|
||
"""
|
||
查询演员参演作品
|
||
"""
|
||
personid = data.get("personid")
|
||
page = data.get("page") or 1
|
||
mtype = MediaType.MOVIE if data.get(
|
||
"type") in MovieTypes else MediaType.TV
|
||
if not personid:
|
||
return {"code": 1, "msg": "未指定演员ID"}
|
||
return {"code": 0, "data": Media().get_person_medias(personid=personid,
|
||
mtype=mtype,
|
||
page=page)}
|
||
|
||
@staticmethod
|
||
def __save_user_script(data):
|
||
"""
|
||
保存用户自定义脚本
|
||
"""
|
||
script = data.get("javascript") or ""
|
||
css = data.get("css") or ""
|
||
SystemConfig().set_system_config(key="CustomScript",
|
||
value={
|
||
"css": css,
|
||
"javascript": script
|
||
})
|
||
return {"code": 0, "msg": "保存成功"}
|
||
|
||
@staticmethod
|
||
def __run_directory_sync(data):
|
||
"""
|
||
执行单个目录的目录同步
|
||
"""
|
||
Sync().transfer_all_sync(sid=data.get("sid"))
|
||
return {"code": 0, "msg": "执行成功"}
|
||
|
||
@staticmethod
|
||
def __update_plugin_config(data):
|
||
"""
|
||
保存插件配置
|
||
"""
|
||
plugin_id = data.get("plugin")
|
||
config = data.get("config")
|
||
if not plugin_id:
|
||
return {"code": 1, "msg": "数据错误"}
|
||
PluginManager().save_plugin_config(pid=plugin_id, conf=config)
|
||
PluginManager().reload_plugin(plugin_id)
|
||
return {"code": 0, "msg": "保存成功"}
|
||
|
||
def get_media_exists_flag(self, mtype, title, year, mediaid):
|
||
"""
|
||
获取媒体存在标记:是否存在、是否订阅
|
||
:param: mtype 媒体类型
|
||
:param: title 媒体标题
|
||
:param: year 媒体年份
|
||
:param: mediaid TMDBID/DB:豆瓣ID/BG:Bangumi的ID
|
||
:return: 1-已订阅/2-已下载/0-不存在未订阅, RSSID
|
||
"""
|
||
if str(mediaid).isdigit():
|
||
tmdbid = mediaid
|
||
else:
|
||
tmdbid = None
|
||
if mtype in MovieTypes:
|
||
rssid = self.dbhelper.get_rss_movie_id(title=title, year=year, tmdbid=tmdbid)
|
||
else:
|
||
if not tmdbid:
|
||
meta_info = MetaInfo(title=title)
|
||
title = meta_info.get_name()
|
||
season = meta_info.get_season_string()
|
||
if season:
|
||
year = None
|
||
else:
|
||
season = None
|
||
rssid = self.dbhelper.get_rss_tv_id(title=title, year=year, season=season, tmdbid=tmdbid)
|
||
if rssid:
|
||
# 已订阅
|
||
fav = "1"
|
||
elif MediaServer().check_item_exists(title=title, year=year, tmdbid=tmdbid):
|
||
# 已下载
|
||
fav = "2"
|
||
else:
|
||
# 未订阅、未下载
|
||
fav = "0"
|
||
return fav, rssid
|