update
This commit is contained in:
@@ -21,47 +21,114 @@ end
|
||||
|
||||
-- 写入history.json
|
||||
-- 读取episodeId获取danmaku
|
||||
function set_episode_id(input, from_menu)
|
||||
function set_episode_id(input, from_menu, api_server)
|
||||
from_menu = from_menu or false
|
||||
DANMAKU.source = "dandanplay"
|
||||
local api_server = options.api_server
|
||||
local selected_server = api_server
|
||||
for url, source in pairs(DANMAKU.sources) do
|
||||
if source.from == "api_server" then
|
||||
if not source.from_history then
|
||||
DANMAKU.sources[url] = nil
|
||||
else
|
||||
DANMAKU.sources[url]["data"] = nil
|
||||
api_server = source.api_server or options.api_server
|
||||
end
|
||||
end
|
||||
end
|
||||
local episodeId = tonumber(input)
|
||||
|
||||
local main_url = api_server .. "/api/v2/comment/" .. episodeId .. "?withRelated=true&chConvert=0"
|
||||
add_source_to_history(main_url, { from = "api_server", api_server = api_server })
|
||||
write_history(episodeId, api_server)
|
||||
if not api_server then
|
||||
if DANMAKU.api_server ~= nil then
|
||||
selected_server = DANMAKU.api_server
|
||||
else
|
||||
local servers = get_api_server_list(options.api_server)
|
||||
if servers and #servers > 0 then
|
||||
selected_server = servers[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
DANMAKU.api_server = selected_server
|
||||
|
||||
local episodeId = tonumber(input)
|
||||
write_history(episodeId, selected_server)
|
||||
set_danmaku_button()
|
||||
fetch_danmaku(episodeId, from_menu, api_server)
|
||||
fetch_danmaku(episodeId, from_menu, selected_server)
|
||||
end
|
||||
|
||||
-- 回退使用额外的弹幕获取方式
|
||||
function get_danmaku_fallback(query)
|
||||
local url = options.fallback_server .. "/?ac=dm&url=" .. query
|
||||
msg.verbose("尝试获取弹幕:" .. url)
|
||||
local function do_fallback()
|
||||
if options.fallback_server == "" then return end
|
||||
local url = options.fallback_server .. "/?ac=dm&url=" .. query
|
||||
msg.verbose("尝试获取弹幕:" .. url)
|
||||
|
||||
local args = make_danmaku_request_args("GET", url)
|
||||
if not args then return end
|
||||
local args = make_danmaku_request_args("GET", url)
|
||||
if not args then return end
|
||||
|
||||
fetch_danmaku_data(args, function(data)
|
||||
if not data or not data["comments"] or data["count"] <= 1 then
|
||||
msg.info("备用服务器无数据或返回格式不正确")
|
||||
show_message("备用服务器无数据或返回格式不正确", 3)
|
||||
return
|
||||
end
|
||||
fetch_danmaku_data(args, function(data)
|
||||
if data ~= nil and data["xml"] ~= nil then
|
||||
if DANMAKU.sources[query] ~= nil then
|
||||
DANMAKU.sources[query]["data"] = data["xml"]
|
||||
else
|
||||
DANMAKU.sources[query] = {from = "user_custom", data = data["xml"]}
|
||||
end
|
||||
load_danmaku(true)
|
||||
return
|
||||
end
|
||||
|
||||
save_danmaku_data(data["comments"], query, "user_custom")
|
||||
load_danmaku(true)
|
||||
end)
|
||||
if not data or not data["comments"] or data["count"] <= 1 then
|
||||
msg.info("备用服务器无数据或返回格式不正确")
|
||||
show_message("备用服务器无数据或返回格式不正确", 3)
|
||||
return
|
||||
end
|
||||
|
||||
save_danmaku_data(data["comments"], query, "user_custom")
|
||||
load_danmaku(true)
|
||||
end)
|
||||
end
|
||||
|
||||
if query:find('bilibili.com') or query:find('bilivideo.c[nom]+') then
|
||||
load_danmaku_for_bilibili(query, function(success)
|
||||
if not success then do_fallback() end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if query:find('bahamut.akamaized.net') then
|
||||
load_danmaku_for_bahamut(query, function(success)
|
||||
if not success then do_fallback() end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if query:find('mgtv.com') then
|
||||
load_danmaku_for_mgtv(query, function(success)
|
||||
if not success then do_fallback() end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if query:find('iqiyi.com') then
|
||||
load_danmaku_for_iqiyi(query, function(success)
|
||||
if not success then do_fallback() end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if query:find('v.qq.com') then
|
||||
load_danmaku_for_tencent(query, function(success)
|
||||
if not success then do_fallback() end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if query:find('v.youku.com') then
|
||||
load_danmaku_for_youku(query, function(success)
|
||||
if not success then do_fallback() end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
do_fallback()
|
||||
end
|
||||
|
||||
-- 返回弹幕请求参数
|
||||
@@ -91,6 +158,8 @@ function make_danmaku_request_args(method, url, headers, body)
|
||||
table.insert(args, 'Content-Type: application/json')
|
||||
end
|
||||
|
||||
table.insert(args, '--compressed')
|
||||
|
||||
if url:find("api%.dandanplay%.") then
|
||||
local time = os.time()
|
||||
local appid = "UgjRIH45lE1BBLNmir1WKw=="
|
||||
@@ -153,8 +222,8 @@ local function normalize_danmaku_response(d)
|
||||
end
|
||||
|
||||
-- 尝试通过解析文件名匹配剧集
|
||||
local function match_episode(animeTitle, bangumiId, episode_num)
|
||||
local url = options.api_server .. "/api/v2/bangumi/" .. bangumiId
|
||||
local function match_episode(animeTitle, bangumiId, episode_num, api_server)
|
||||
local url = api_server .. "/api/v2/bangumi/" .. bangumiId
|
||||
local args = make_danmaku_request_args("GET", url)
|
||||
|
||||
if args == nil then
|
||||
@@ -162,7 +231,6 @@ local function match_episode(animeTitle, bangumiId, episode_num)
|
||||
end
|
||||
|
||||
call_cmd_async(args, function(error, json)
|
||||
async_running = false
|
||||
if error then
|
||||
show_message("HTTP 请求失败,打开控制台查看详情", 5)
|
||||
msg.error(error)
|
||||
@@ -180,7 +248,7 @@ local function match_episode(animeTitle, bangumiId, episode_num)
|
||||
if ep_num and ep_num == tonumber(episode_num) then
|
||||
DANMAKU.anime = animeTitle
|
||||
DANMAKU.episode = episode.episodeTitle
|
||||
set_episode_id(episode.episodeId)
|
||||
set_episode_id(episode.episodeId, nil, api_server)
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -188,9 +256,7 @@ local function match_episode(animeTitle, bangumiId, episode_num)
|
||||
end
|
||||
|
||||
local function match_anime()
|
||||
local animes = {}
|
||||
local anime_type = "tvseries"
|
||||
local type_count = 0
|
||||
local title, season_num, episode_num = parse_title()
|
||||
if not episode_num then
|
||||
msg.info("无法解析剧集信息")
|
||||
@@ -201,44 +267,49 @@ local function match_anime()
|
||||
anime_type = "ova"
|
||||
end
|
||||
|
||||
-- 并发在多个 api_server 上搜索,遇到第一个可接受的匹配就取消其余请求
|
||||
local encoded_query = url_encode(title)
|
||||
local url = options.api_server .. "/api/v2/search/anime"
|
||||
local params = "keyword=" .. encoded_query
|
||||
local full_url = url .. "?" .. params
|
||||
local args = make_danmaku_request_args("GET", full_url)
|
||||
local servers = get_api_server_list(options.api_server)
|
||||
|
||||
if not args then return end
|
||||
local matched = false
|
||||
local cancel_fn = nil
|
||||
|
||||
call_cmd_async(args, function(error, json)
|
||||
async_running = false
|
||||
if error then
|
||||
show_message("HTTP 请求失败,打开控制台查看详情", 5)
|
||||
msg.error(error)
|
||||
local function build_args(server)
|
||||
local url = server .. "/api/v2/search/anime"
|
||||
local full_url = url .. "?keyword=" .. encoded_query
|
||||
return make_danmaku_request_args("GET", full_url)
|
||||
end
|
||||
|
||||
local function per_response(server, err, out)
|
||||
if matched then return end
|
||||
if err then
|
||||
msg.debug(("search anime failed for %s: %s"):format(server, tostring(err)))
|
||||
return
|
||||
end
|
||||
|
||||
local data = utils.parse_json(json)
|
||||
local data = utils.parse_json(out)
|
||||
if not data or not data.animes then
|
||||
msg.info("无结果")
|
||||
return
|
||||
end
|
||||
|
||||
local local_candidates = {}
|
||||
for _, anime in ipairs(data.animes) do
|
||||
if anime.type == anime_type then
|
||||
type_count = type_count + 1
|
||||
table.insert(animes, anime)
|
||||
table.insert(local_candidates, anime)
|
||||
end
|
||||
end
|
||||
|
||||
if type_count == 1 then
|
||||
match_episode(animes[1].animeTitle, animes[1].bangumiId, episode_num)
|
||||
elseif type_count > 1 and season_num then
|
||||
if #local_candidates == 1 then
|
||||
matched = true
|
||||
local a = local_candidates[1]
|
||||
match_episode(a.animeTitle, a.bangumiId, episode_num, server)
|
||||
if cancel_fn then pcall(cancel_fn) end
|
||||
return
|
||||
end
|
||||
if #local_candidates > 1 and season_num then
|
||||
local best_match, best_score = nil, -1
|
||||
local target_title = title
|
||||
if tonumber(season_num) > 1 then
|
||||
target_title = title .. " 第" .. number_to_chinese(season_num) .. "季"
|
||||
end
|
||||
for _, anime in ipairs(animes) do
|
||||
for _, anime in ipairs(local_candidates) do
|
||||
local animeTitle = tostring(anime.animeTitle or "")
|
||||
animeTitle = animeTitle:gsub("^%s*(.-)%s*$", "%1")
|
||||
:gsub("%s*%(.-%)%s*$", "")
|
||||
@@ -253,17 +324,24 @@ local function match_anime()
|
||||
best_match = anime
|
||||
end
|
||||
end
|
||||
|
||||
if best_match and best_score >= 0.75 then
|
||||
matched = true
|
||||
msg.info(("模糊匹配选中: %s (score=%.2f)"):format(best_match.animeTitle, best_score))
|
||||
match_episode(best_match.animeTitle, best_match.bangumiId, episode_num)
|
||||
else
|
||||
msg.info("匹配到多个结果,但相似度不足,请手动搜索")
|
||||
match_episode(best_match.animeTitle, best_match.bangumiId, episode_num, server)
|
||||
if cancel_fn then pcall(cancel_fn) end
|
||||
return
|
||||
end
|
||||
else
|
||||
end
|
||||
-- 未找到可接受匹配,继续等待其他服务器的返回
|
||||
end
|
||||
|
||||
local function final_cb()
|
||||
if not matched then
|
||||
msg.info("没有找到合适的匹配结果")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
cancel_fn = parallel_requests(servers, build_args, per_response, final_cb, { concurrency = 5, per_request_timeout = 15 })
|
||||
end
|
||||
|
||||
-- 执行哈希匹配获取弹幕
|
||||
@@ -271,7 +349,7 @@ local function match_file(file_path, file_name, callback)
|
||||
-- 计算文件哈希
|
||||
local hash = nil
|
||||
local file_info = utils.file_info(file_path)
|
||||
if file_info and file_info.size > 16 * 1024 * 1024 then
|
||||
if file_info and file_info.size >= 16 * 1024 * 1024 then
|
||||
local file, error = io.open(normalize(file_path), 'rb')
|
||||
if file and not error then
|
||||
local m = MD5.new()
|
||||
@@ -300,50 +378,66 @@ local function match_file(file_path, file_name, callback)
|
||||
file_name = title
|
||||
end
|
||||
|
||||
local url = options.api_server .. "/api/v2/match"
|
||||
local args = make_danmaku_request_args("POST", url, {
|
||||
["Content-Type"] = "application/json"
|
||||
}, {
|
||||
local servers = get_api_server_list(options.api_server)
|
||||
|
||||
local matched = false
|
||||
local cancel_fn = nil
|
||||
|
||||
local function build_args(server)
|
||||
local url = server .. "/api/v2/match"
|
||||
return make_danmaku_request_args("POST", url, { ["Content-Type"] = "application/json" }, {
|
||||
fileName = file_name,
|
||||
fileHash = hash or "a1b2c3d4e5f67890abcd1234ef567890",
|
||||
matchMode = "hashAndFileName"
|
||||
}
|
||||
)
|
||||
})
|
||||
end
|
||||
|
||||
if not args then return end
|
||||
|
||||
call_cmd_async(args, function(error, json)
|
||||
async_running = false
|
||||
if error then
|
||||
show_message("HTTP 请求失败,打开控制台查看详情", 5)
|
||||
callback(error)
|
||||
local function per_response(server, err, out)
|
||||
if matched then return end
|
||||
if err then
|
||||
msg.debug(("match failed for %s: %s"):format(server, tostring(err)))
|
||||
return
|
||||
end
|
||||
local data = utils.parse_json(json)
|
||||
local data = utils.parse_json(out)
|
||||
if not data or not data.isMatched then
|
||||
callback("没有匹配的剧集")
|
||||
return
|
||||
end
|
||||
|
||||
matched = true
|
||||
DANMAKU.anime = data.matches[1].animeTitle
|
||||
DANMAKU.episode = data.matches[1].episodeTitle
|
||||
|
||||
-- 获取并加载弹幕数据
|
||||
set_episode_id(data.matches[1].episodeId)
|
||||
end)
|
||||
set_episode_id(data.matches[1].episodeId, nil, server)
|
||||
if cancel_fn then pcall(cancel_fn) end
|
||||
if callback then pcall(callback) end
|
||||
end
|
||||
|
||||
local function final_cb()
|
||||
if not matched then
|
||||
callback("没有匹配的剧集")
|
||||
end
|
||||
end
|
||||
|
||||
cancel_fn = parallel_requests(servers, build_args, per_response, final_cb, { concurrency = 5, per_request_timeout = 15 })
|
||||
end
|
||||
|
||||
-- 异步获取弹幕数据
|
||||
function fetch_danmaku_data(args, callback)
|
||||
call_cmd_async(args, function(error, json)
|
||||
async_running = false
|
||||
if error then
|
||||
show_message("获取数据失败", 3)
|
||||
msg.error("HTTP 请求失败:" .. error)
|
||||
return
|
||||
end
|
||||
local data = utils.parse_json(json)
|
||||
data = normalize_danmaku_response(data)
|
||||
if data ~= nil then
|
||||
data = normalize_danmaku_response(data)
|
||||
else
|
||||
local danmaku = parse_xml_danmaku(json)
|
||||
if #danmaku > 0 then
|
||||
data = {}
|
||||
data["xml"] = danmaku
|
||||
end
|
||||
end
|
||||
callback(data)
|
||||
end)
|
||||
end
|
||||
@@ -359,6 +453,26 @@ function save_danmaku_data(comments, query, danmaku_source)
|
||||
end
|
||||
end
|
||||
|
||||
function save_danmaku_xml(url, xml_string)
|
||||
local danmaku_list = parse_xml_danmaku(xml_string)
|
||||
|
||||
if DANMAKU.sources[url] ~= nil then
|
||||
DANMAKU.sources[url]["data"] = danmaku_list
|
||||
else
|
||||
DANMAKU.sources[url] = {from = "user_custom", data = danmaku_list}
|
||||
end
|
||||
end
|
||||
|
||||
function save_danmaku_json(url, json_string)
|
||||
local danmaku_list = parse_json_danmaku(json_string)
|
||||
|
||||
if DANMAKU.sources[url] ~= nil then
|
||||
DANMAKU.sources[url]["data"] = danmaku_list
|
||||
else
|
||||
DANMAKU.sources[url] = {from = "user_custom", data = danmaku_list}
|
||||
end
|
||||
end
|
||||
|
||||
function save_danmaku_downloaded(url, downloaded_file)
|
||||
local danmaku_list = parse_danmaku_file(downloaded_file)
|
||||
if file_exists(downloaded_file) then
|
||||
@@ -371,39 +485,6 @@ function save_danmaku_downloaded(url, downloaded_file)
|
||||
end
|
||||
end
|
||||
|
||||
-- 处理弹幕数据
|
||||
function handle_danmaku_data(query, data, from_menu)
|
||||
-- 如果没有数据,进行重试
|
||||
if not data or not data["comments"] or data["count"] <= 1 then
|
||||
show_message("服务器无缓存数据,再次尝试请求", 10)
|
||||
msg.verbose("服务器无缓存数据,再次尝试请求")
|
||||
-- 等待 2 秒后重试
|
||||
local start = os.time()
|
||||
while os.time() - start < 2 do
|
||||
-- 空循环,等待 2 秒
|
||||
end
|
||||
-- 重新发起请求
|
||||
local url = options.api_server .. "/api/v2/extcomment?url=" .. url_encode(query)
|
||||
local args = make_danmaku_request_args("GET", url)
|
||||
|
||||
if args == nil then
|
||||
return
|
||||
end
|
||||
|
||||
fetch_danmaku_data(args, function(retry_data)
|
||||
if not retry_data or not retry_data["comments"] or retry_data["count"] <= 1 then
|
||||
get_danmaku_fallback(query)
|
||||
return
|
||||
end
|
||||
save_danmaku_data(retry_data["comments"], query, "user_custom")
|
||||
load_danmaku(from_menu)
|
||||
end)
|
||||
else
|
||||
save_danmaku_data(data["comments"], query, "user_custom")
|
||||
load_danmaku(from_menu)
|
||||
end
|
||||
end
|
||||
|
||||
-- 处理获取到的数据
|
||||
function handle_fetched_danmaku(data, url, from_menu)
|
||||
if data and data["comments"] then
|
||||
@@ -426,7 +507,7 @@ end
|
||||
-- 匹配弹幕库 comment, 仅匹配dandan本身弹幕库
|
||||
-- 通过danmaku api(url)+id获取弹幕
|
||||
function fetch_danmaku(episodeId, from_menu, api_server)
|
||||
local url = (api_server or options.api_server) .. "/api/v2/comment/" .. episodeId .. "?withRelated=true&chConvert=0"
|
||||
local url = api_server .. "/api/v2/comment/" .. episodeId .. "?withRelated=true&chConvert=0"
|
||||
show_message("弹幕加载中...", 30)
|
||||
msg.verbose("尝试获取弹幕:" .. url)
|
||||
local args = make_danmaku_request_args("GET", url)
|
||||
@@ -499,18 +580,60 @@ end
|
||||
--通过输入源url获取弹幕库
|
||||
function add_danmaku_source_online(query, from_menu)
|
||||
set_danmaku_button()
|
||||
local url = options.api_server .. "/api/v2/extcomment?url=" .. url_encode(query)
|
||||
show_message("弹幕加载中...", 30)
|
||||
msg.verbose("尝试获取弹幕:" .. url)
|
||||
local args = make_danmaku_request_args("GET", url)
|
||||
msg.verbose("尝试获取弹幕:" .. query)
|
||||
|
||||
if args == nil then
|
||||
local servers = get_api_server_list(options.api_server)
|
||||
|
||||
-- 过滤掉指向 dandanplay.net 的服务器
|
||||
local filtered = {}
|
||||
for _, s in ipairs(servers) do
|
||||
if type(s) == "string" and not s:lower():find("dandanplay%.net") then
|
||||
table.insert(filtered, s)
|
||||
end
|
||||
end
|
||||
servers = filtered
|
||||
if #servers == 0 then
|
||||
get_danmaku_fallback(query)
|
||||
return
|
||||
end
|
||||
|
||||
fetch_danmaku_data(args, function(data)
|
||||
handle_danmaku_data(query, data, from_menu)
|
||||
end)
|
||||
local matched = false
|
||||
local cancel_fn = nil
|
||||
|
||||
local function build_args(server)
|
||||
local url = server .. "/api/v2/extcomment?url=" .. url_encode(query)
|
||||
return make_danmaku_request_args("GET", url)
|
||||
end
|
||||
|
||||
local function per_response(server, err, out)
|
||||
if matched then return end
|
||||
if err then
|
||||
msg.debug(("extcomment failed for %s: %s"):format(server, tostring(err)))
|
||||
return
|
||||
end
|
||||
local data = utils.parse_json(out)
|
||||
data = normalize_danmaku_response(data)
|
||||
if not data or not data["comments"] or data["count"] <= 1 then
|
||||
return
|
||||
end
|
||||
matched = true
|
||||
-- 保存并加载弹幕
|
||||
save_danmaku_data(data["comments"], query, "user_custom")
|
||||
load_danmaku(from_menu)
|
||||
-- 取消其他未完成请求
|
||||
if cancel_fn then pcall(cancel_fn) end
|
||||
end
|
||||
|
||||
local function final_cb()
|
||||
if not matched then
|
||||
-- 所有服务器都未返回有效弹幕,回退到备用服务器
|
||||
msg.info("所有服务器均无有效弹幕,尝试备用服务器")
|
||||
get_danmaku_fallback(query)
|
||||
end
|
||||
end
|
||||
|
||||
cancel_fn = parallel_requests(servers, build_args, per_response, final_cb, { concurrency = 3, per_request_timeout = 30 })
|
||||
end
|
||||
|
||||
-- 将弹幕转换为 Lua table
|
||||
@@ -578,8 +701,6 @@ function get_danmaku_with_hash(file_name, file_path)
|
||||
end
|
||||
|
||||
call_cmd_async(arg, function(error)
|
||||
async_running = false
|
||||
|
||||
file_path = utils.join_path(DANMAKU_PATH, temp_file)
|
||||
|
||||
match_file(file_path, file_name, function(error)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
local utils = require 'mp.utils'
|
||||
local msg = require 'mp.msg'
|
||||
|
||||
local cached_series_playlinks = {}
|
||||
|
||||
local Source = {
|
||||
["b 站"] = "bilibili1",
|
||||
["芒果TV"] = "imgo",
|
||||
["腾讯"] = "qq",
|
||||
["爱奇艺"] = "qiyi",
|
||||
["优酷"] = "youku",
|
||||
@@ -83,6 +86,31 @@ local function query_tmdb(title, class, menu)
|
||||
end
|
||||
end
|
||||
|
||||
-- 从单个 seriesPlaylinks 项解析出有效的播放 URL
|
||||
local function extract_episode_url(item, playlink)
|
||||
if not item then return nil end
|
||||
if type(item) == 'string' then
|
||||
return playlink or item
|
||||
elseif type(item) == 'table' then
|
||||
return item.url or nil
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- 将 seriesPlaylinks 转换为统一的 episode_rows 列表:{ index=string, url=string }
|
||||
local function build_episode_rows(seriesPlaylinks, playlink)
|
||||
if not seriesPlaylinks or type(seriesPlaylinks) ~= 'table' then return nil end
|
||||
local rows = {}
|
||||
for i, it in ipairs(seriesPlaylinks) do
|
||||
local url = extract_episode_url(it, playlink)
|
||||
if url and url ~= '' then
|
||||
table.insert(rows, { index = tostring(i), url = url })
|
||||
end
|
||||
end
|
||||
if #rows == 0 then return nil end
|
||||
return rows
|
||||
end
|
||||
|
||||
local function get_number(cat, id, site)
|
||||
local url = string.format("https://api.web.360kan.com/v1/detail?cat=%s&id=%s&site=%s",
|
||||
cat, id, site)
|
||||
@@ -107,6 +135,53 @@ local function get_number(cat, id, site)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- 使用 /v1/detail 分批获取集数(每批最多200集)
|
||||
local function get_episodes_v1(cat, id, site, number)
|
||||
if not number or tonumber(number) == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local batch_size = 200
|
||||
local start_idx = 1
|
||||
local episodes = {}
|
||||
while start_idx <= tonumber(number) do
|
||||
local end_idx = math.min(start_idx + batch_size - 1, tonumber(number))
|
||||
local url = string.format("https://api.web.360kan.com/v1/detail?cat=%s&id=%s&start=%s&end=%s&site=%s",
|
||||
cat, id, start_idx, end_idx, site)
|
||||
|
||||
local cmd = { "curl", "-s", url }
|
||||
local res = mp.command_native({
|
||||
name = "subprocess",
|
||||
args = cmd,
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
})
|
||||
|
||||
if not res.status or res.status ~= 0 then
|
||||
msg.error(string.format("Failed to fetch detail batch %d-%d: %s", start_idx, end_idx, res.stderr or "unknown"))
|
||||
if start_idx == 1 then
|
||||
return nil
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local result = utils.parse_json(res.stdout)
|
||||
if result and result.data and result.data.allepidetail and result.data.allepidetail[site] then
|
||||
for _, it in ipairs(result.data.allepidetail[site]) do
|
||||
table.insert(episodes, { index = tostring(it.playlink_num), url = it.url })
|
||||
end
|
||||
end
|
||||
|
||||
start_idx = end_idx + 1
|
||||
end
|
||||
|
||||
if #episodes == 0 then
|
||||
return nil
|
||||
end
|
||||
return episodes
|
||||
end
|
||||
|
||||
local function get_episodes_v2(cat, id, site)
|
||||
local s_param = string.format('[{"cat_id":"%s","ent_id":"%s","site":"%s"}]', tostring(cat), tostring(id), tostring(site))
|
||||
|
||||
@@ -121,22 +196,13 @@ local function get_episodes_v2(cat, id, site)
|
||||
})
|
||||
|
||||
if not res.status or res.status ~= 0 then
|
||||
msg.error("Failed to fetch episodesv2: " .. (res.stderr or "unknown error"))
|
||||
msg.warn("Failed to fetch episodesv2: " .. (res.stderr or "unknown error"))
|
||||
return nil
|
||||
end
|
||||
|
||||
local data_text = res.stdout or ""
|
||||
-- 兼容 JSONP 和 纯 JSON:提取最外层括号内 JSON
|
||||
local json_payload = data_text
|
||||
local first_paren = data_text:find('%(')
|
||||
local last_paren = data_text:match('.*()%)')
|
||||
if first_paren and last_paren and last_paren > first_paren then
|
||||
json_payload = data_text:sub(first_paren + 1, last_paren - 1)
|
||||
end
|
||||
|
||||
local parsed = utils.parse_json(json_payload)
|
||||
local parsed = utils.parse_json(res.stdout)
|
||||
if not parsed then
|
||||
msg.error("episodesv2: 解析返回失败: " .. (res.stdout or ""))
|
||||
msg.warn("episodesv2: 解析返回失败: " .. (res.stdout or ""))
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -144,15 +210,10 @@ local function get_episodes_v2(cat, id, site)
|
||||
if parsed.code == 0 and parsed.data and #parsed.data > 0 then
|
||||
local seriesHTML = parsed.data[1] and parsed.data[1].seriesHTML
|
||||
if seriesHTML and seriesHTML.seriesPlaylinks then
|
||||
for i, ep in ipairs(seriesHTML.seriesPlaylinks) do
|
||||
local episode_url = nil
|
||||
if type(ep) == 'string' then
|
||||
episode_url = ep
|
||||
elseif type(ep) == 'table' and ep.url then
|
||||
episode_url = ep.url
|
||||
end
|
||||
if episode_url and episode_url ~= '' then
|
||||
table.insert(episodes, { index = i, url = episode_url })
|
||||
local rows = build_episode_rows(seriesHTML.seriesPlaylinks)
|
||||
if rows then
|
||||
for _, r in ipairs(rows) do
|
||||
table.insert(episodes, { index = tonumber(r.index), url = r.url })
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -170,7 +231,7 @@ function get_details(class, id, site, title, year, number, episodenum)
|
||||
local menu_title = "剧集信息"
|
||||
local footnote = "使用 / 打开筛选"
|
||||
if uosc_available and not episodenum then
|
||||
update_menu_uosc(menu_type, menu_title, message, footnote)
|
||||
update_menu_uosc(menu_type, menu_title, message, footnote, nil, nil, "spinner")
|
||||
else
|
||||
show_message(message, 3)
|
||||
end
|
||||
@@ -188,59 +249,57 @@ function get_details(class, id, site, title, year, number, episodenum)
|
||||
|
||||
local items = {}
|
||||
local episodes = nil
|
||||
local episode_rows = nil
|
||||
|
||||
-- 优先尝试使用搜索时缓存的 seriesPlaylinks(若存在且站点匹配)
|
||||
if cat == 2 or cat == 4 then
|
||||
episodes = get_episodes_v2(cat, id, site)
|
||||
local cid = tostring(id)
|
||||
local cached = cached_series_playlinks[cid]
|
||||
if cached and cached.seriesPlaylinks and cached.seriesSite and tostring(cached.seriesSite) == tostring(site) then
|
||||
local rows = build_episode_rows(cached.seriesPlaylinks, cached.playlink)
|
||||
if rows then
|
||||
episode_rows = rows
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 统一构建 episode_rows:优先使用 episodesv2 返回的数据,否则使用 v1/detail
|
||||
local episode_rows = nil
|
||||
if episodes then
|
||||
episode_rows = {}
|
||||
for _, ep in ipairs(episodes) do
|
||||
table.insert(episode_rows, { index = tostring(ep.index), url = ep.url })
|
||||
end
|
||||
else
|
||||
if not number and cat ~= 0 then
|
||||
number = get_number(cat, id, site)
|
||||
end
|
||||
if not number or cat == 0 then
|
||||
local message = "无结果"
|
||||
if uosc_available and not episodenum then
|
||||
update_menu_uosc(menu_type, menu_title, message, footnote)
|
||||
else
|
||||
show_message(message, 3)
|
||||
end
|
||||
msg.verbose("无结果")
|
||||
return
|
||||
-- 若未命中缓存,则继续使用 episodesv2/v1 的原有流程
|
||||
if not episode_rows then
|
||||
if cat == 2 or cat == 4 then
|
||||
episodes = get_episodes_v2(cat, id, site)
|
||||
end
|
||||
|
||||
local url = string.format("https://api.web.360kan.com/v1/detail?cat=%s&id=%s&start=1&end=%s&site=%s",
|
||||
cat, id, number, site)
|
||||
|
||||
local cmd = { "curl", "-s", url }
|
||||
local res = mp.command_native({
|
||||
name = "subprocess",
|
||||
args = cmd,
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
})
|
||||
|
||||
if not res.status or res.status ~= 0 then
|
||||
local message = "无结果"
|
||||
if uosc_available and not episodenum then
|
||||
update_menu_uosc(menu_type, menu_title, message, footnote)
|
||||
else
|
||||
show_message(message, 3)
|
||||
end
|
||||
msg.verbose("无结果")
|
||||
return
|
||||
end
|
||||
|
||||
local result = utils.parse_json(res.stdout)
|
||||
if result and result.data and result.data.allepidetail and result.data.allepidetail[site] then
|
||||
-- 统一构建 episode_rows:优先使用 episodesv2 返回的数据,否则使用 v1/detail
|
||||
if episodes then
|
||||
episode_rows = {}
|
||||
for _, it in ipairs(result.data.allepidetail[site]) do
|
||||
table.insert(episode_rows, { index = tostring(it.playlink_num), url = it.url })
|
||||
for _, ep in ipairs(episodes) do
|
||||
table.insert(episode_rows, { index = tostring(ep.index), url = ep.url })
|
||||
end
|
||||
else
|
||||
if not number and cat ~= 0 then
|
||||
number = get_number(cat, id, site)
|
||||
end
|
||||
if not number or cat == 0 then
|
||||
local message = "无结果"
|
||||
if uosc_available and not episodenum then
|
||||
update_menu_uosc(menu_type, menu_title, message, footnote)
|
||||
else
|
||||
show_message(message, 3)
|
||||
end
|
||||
msg.verbose("无结果")
|
||||
return
|
||||
end
|
||||
|
||||
episode_rows = get_episodes_v1(cat, id, site, number)
|
||||
if not episode_rows or #episode_rows == 0 then
|
||||
local message = "无结果"
|
||||
if uosc_available and not episodenum then
|
||||
update_menu_uosc(menu_type, menu_title, message, footnote)
|
||||
else
|
||||
show_message(message, 3)
|
||||
end
|
||||
msg.verbose("无结果")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -325,6 +384,18 @@ local function search_query(query, class, menu)
|
||||
if result and result.data.longData and result.data.longData.rows then
|
||||
for _, item in ipairs(result.data.longData.rows) do
|
||||
if item.playlinks then
|
||||
-- 如果搜索结果中包含 seriesPlaylinks,则缓存它(使用 en_id 作为 key)
|
||||
if item.seriesPlaylinks and item.en_id then
|
||||
local playlink = nil
|
||||
if item.playlinks and item.seriesSite then
|
||||
playlink = item.playlinks[item.seriesSite]
|
||||
end
|
||||
cached_series_playlinks[tostring(item.en_id)] = {
|
||||
seriesPlaylinks = item.seriesPlaylinks,
|
||||
seriesSite = item.seriesSite,
|
||||
playlink = playlink,
|
||||
}
|
||||
end
|
||||
for source_name, source_id in pairs(Source) do
|
||||
if item.playlinks[source_id] then
|
||||
table.insert(items, {
|
||||
@@ -375,7 +446,7 @@ function query_extra(name, class)
|
||||
}
|
||||
menu.cmd = { "script-message-to", mp.get_script_name(), "search-anime-event" }
|
||||
if uosc_available then
|
||||
update_menu_uosc(menu.type, menu.title, message, menu.footnote, menu.cmd, name)
|
||||
update_menu_uosc(menu.type, menu.title, message, menu.footnote, menu.cmd, name, "spinner")
|
||||
else
|
||||
show_message(message, 30)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user