This commit is contained in:
2026-08-15 17:50:48 +02:00
parent 22246060e6
commit 4dc6687de7
35 changed files with 4978 additions and 1509 deletions
+240 -119
View File
@@ -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 apiurl+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)