506 lines
16 KiB
Lua
506 lines
16 KiB
Lua
local utils = require 'mp.utils'
|
|
local msg = require 'mp.msg'
|
|
|
|
local cached_series_playlinks = {}
|
|
|
|
local Source = {
|
|
["b 站"] = "bilibili1",
|
|
["芒果TV"] = "imgo",
|
|
["腾讯"] = "qq",
|
|
["爱奇艺"] = "qiyi",
|
|
["优酷"] = "youku",
|
|
}
|
|
|
|
local function load_extra_danmaku(url, episode, number, class, id, site, title, year)
|
|
local play_url = nil
|
|
if url:match("^.-%.html") then
|
|
play_url = url:match("^(.-%.html).*")
|
|
elseif url:match("^https?://v%.youku%.com/") and url:match("[?&]vid=") then
|
|
-- 转换 youku 的短链接形式 video?vid=... 到真实播放页 v_show/id_*.html
|
|
local vid = url:match("[?&]vid=([^&]+)")
|
|
if vid then
|
|
play_url = "https://v.youku.com/v_show/id_" .. vid .. ".html"
|
|
else
|
|
play_url = url:gsub("%?bsource=360ogvys$",""):gsub("&.*$","")
|
|
end
|
|
else
|
|
play_url = url:gsub("%?bsource=360ogvys$",""):gsub("&.*$","")
|
|
end
|
|
|
|
ENABLED = true
|
|
DANMAKU.anime = title .. " (" .. year .. ")"
|
|
DANMAKU.episode = "第" .. episode .. "话"
|
|
DANMAKU.source = site
|
|
DANMAKU.extra = {
|
|
id = id,
|
|
site = site,
|
|
year = year,
|
|
class = class,
|
|
title = title,
|
|
number = tonumber(number),
|
|
episodenum = tonumber(episode),
|
|
}
|
|
write_history()
|
|
add_danmaku_source(play_url, true)
|
|
end
|
|
|
|
local function query_tmdb(title, class, menu)
|
|
local encoded_title = url_encode(title)
|
|
local url = string.format("https://api.tmdb.org/3/search/%s?api_key=%s&query=%s&language=zh-CN",
|
|
class, Base64.decode(options.tmdb_api_key), encoded_title)
|
|
|
|
local cmd = {
|
|
"curl",
|
|
"-s",
|
|
"-H", "accept: application/json",
|
|
url
|
|
}
|
|
|
|
if options.proxy ~= "" then
|
|
table.insert(cmd, '-x')
|
|
table.insert(cmd, options.proxy)
|
|
end
|
|
|
|
local res = mp.command_native({
|
|
name = "subprocess",
|
|
args = cmd,
|
|
capture_stdout = true,
|
|
capture_stderr = true,
|
|
})
|
|
|
|
local data = utils.parse_json(res.stdout)
|
|
if not res.status or res.status ~= 0 or not data.results or #data.results == 0 then
|
|
local message = "获取 tmdb 中文数据失败"
|
|
if uosc_available then
|
|
update_menu_uosc(menu.type, menu.title, message, menu.footnote, menu.cmd, title)
|
|
else
|
|
show_message(message, 3)
|
|
end
|
|
msg.error("获取 tmdb 中文数据失败:" .. res.stdout)
|
|
else
|
|
if class == "tv" then
|
|
return data.results[1].name
|
|
else
|
|
return data.results[1].title
|
|
end
|
|
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)
|
|
|
|
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("Failed to fetch data: " .. (res.stderr or "unknown error"))
|
|
return nil
|
|
end
|
|
|
|
local result = utils.parse_json(res.stdout)
|
|
if result and result.data and result.data.allupinfo then
|
|
return tonumber(result.data.allupinfo[site])
|
|
end
|
|
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))
|
|
|
|
local url = string.format("https://api.so.360kan.com/episodesv2?v_ap=1&s=%s", url_encode(s_param))
|
|
|
|
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.warn("Failed to fetch episodesv2: " .. (res.stderr or "unknown error"))
|
|
return nil
|
|
end
|
|
|
|
local parsed = utils.parse_json(res.stdout)
|
|
if not parsed then
|
|
msg.warn("episodesv2: 解析返回失败: " .. (res.stdout or ""))
|
|
return nil
|
|
end
|
|
|
|
local episodes = {}
|
|
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
|
|
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
|
|
end
|
|
|
|
if #episodes == 0 then
|
|
return nil
|
|
end
|
|
return episodes
|
|
end
|
|
|
|
function get_details(class, id, site, title, year, number, episodenum)
|
|
local message = episodenum and "查询弹幕中..." or "加载数据中..."
|
|
local menu_type = "menu_details"
|
|
local menu_title = "剧集信息"
|
|
local footnote = "使用 / 打开筛选"
|
|
if uosc_available and not episodenum then
|
|
update_menu_uosc(menu_type, menu_title, message, footnote, nil, nil, "spinner")
|
|
else
|
|
show_message(message, 3)
|
|
end
|
|
|
|
local cat = 0
|
|
if class == "电影" then
|
|
cat = 1
|
|
elseif class == "电视剧" then
|
|
cat = 2
|
|
-- elseif class == "综艺" then
|
|
-- cat = 3
|
|
elseif class == "动漫" then
|
|
cat = 4
|
|
end
|
|
|
|
local items = {}
|
|
local episodes = nil
|
|
local episode_rows = nil
|
|
|
|
-- 优先尝试使用搜索时缓存的 seriesPlaylinks(若存在且站点匹配)
|
|
if cat == 2 or cat == 4 then
|
|
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
|
|
|
|
-- 若未命中缓存,则继续使用 episodesv2/v1 的原有流程
|
|
if not episode_rows then
|
|
if cat == 2 or cat == 4 then
|
|
episodes = get_episodes_v2(cat, id, site)
|
|
end
|
|
|
|
-- 统一构建 episode_rows:优先使用 episodesv2 返回的数据,否则使用 v1/detail
|
|
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
|
|
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
|
|
|
|
if episode_rows and #episode_rows > 0 then
|
|
if episodenum then
|
|
for _, ep in ipairs(episode_rows) do
|
|
if tonumber(ep.index) == tonumber(episodenum) then
|
|
load_extra_danmaku(ep.url, ep.index, number, class, id, site, title, year)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
table.insert(items, {
|
|
title = "← 返回搜索结果",
|
|
value = { "script-message-to", "uosc", "open-menu", latest_menu_anime },
|
|
keep_open = false,
|
|
selectable = true,
|
|
})
|
|
|
|
for _, ep in ipairs(episode_rows) do
|
|
table.insert(items, {
|
|
title = "第" .. ep.index .. "集",
|
|
hint = ep.index,
|
|
value = {
|
|
"script-message-to",
|
|
mp.get_script_name(),
|
|
"add-extra-event",
|
|
ep.url, ep.index, tostring(number), class, id, site, title, year
|
|
},
|
|
})
|
|
end
|
|
end
|
|
if #items > 0 then
|
|
if uosc_available and not episodenum then
|
|
update_menu_uosc(menu_type, menu_title, items, footnote)
|
|
elseif not episodenum then
|
|
show_message("", 0)
|
|
mp.add_timeout(0.1, function()
|
|
open_menu_select(items)
|
|
end)
|
|
end
|
|
else
|
|
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("无结果")
|
|
end
|
|
end
|
|
|
|
local function search_query(query, class, menu)
|
|
local url = string.format("https://api.so.360kan.com/index?force_v=1&kw=%s", query)
|
|
if class ~= nil then
|
|
url = url .. "&type=" .. class
|
|
end
|
|
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 then
|
|
update_menu_uosc(menu.type, menu.title, message, menu.footnote, menu.cmd, query)
|
|
else
|
|
show_message(message, 3)
|
|
end
|
|
msg.verbose("无结果")
|
|
return
|
|
end
|
|
|
|
local result = utils.parse_json(res.stdout)
|
|
local items = {}
|
|
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, {
|
|
title = item.titleTxt,
|
|
hint = item.cat_name .. " | " .. item.year .. " | 来源:" .. source_name,
|
|
value = {
|
|
"script-message-to",
|
|
mp.get_script_name(),
|
|
"get-extra-event",
|
|
item.cat_name, item.en_id, item.playlinks[source_id], source_id,
|
|
item.titleTxt, item.year,
|
|
},
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if #items > 0 then
|
|
if uosc_available then
|
|
latest_menu_anime = update_menu_uosc(menu.type, menu.title, items, menu.footnote, menu.cmd, query)
|
|
else
|
|
show_message("", 0)
|
|
mp.add_timeout(0.1, function()
|
|
open_menu_select(items)
|
|
end)
|
|
end
|
|
else
|
|
local message = "无结果"
|
|
if uosc_available then
|
|
update_menu_uosc(menu.type, menu.title, message, menu.footnote, menu.cmd, query)
|
|
else
|
|
show_message(message, 3)
|
|
end
|
|
msg.verbose("无结果")
|
|
end
|
|
end
|
|
|
|
function query_extra(name, class)
|
|
local name = name:gsub("%s*%(%d-%)%s*$", "")
|
|
local title = nil
|
|
local class = class and class:lower()
|
|
local message = "加载数据中..."
|
|
local menu = {
|
|
type = "menu_anime",
|
|
title = "在此处输入番剧名称",
|
|
footnote = "使用enter或ctrl+enter进行搜索"
|
|
}
|
|
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, "spinner")
|
|
else
|
|
show_message(message, 30)
|
|
end
|
|
|
|
if is_chinese(name) then
|
|
search_query(name, class, menu)
|
|
return
|
|
end
|
|
|
|
|
|
if options.tmdb_api_key == "" or #Base64.decode(options.tmdb_api_key) < 32 then
|
|
local message = "请正确设置 tmdb_api_key 或尝试使用中文搜索"
|
|
if uosc_available then
|
|
update_menu_uosc(menu.type, menu.title, message, menu.footnote, menu.cmd, name)
|
|
else
|
|
show_message(message, 3)
|
|
end
|
|
return
|
|
end
|
|
|
|
if class == "dy" then
|
|
title = query_tmdb(name, "movie", menu)
|
|
else
|
|
title = query_tmdb(name, "tv", menu)
|
|
end
|
|
|
|
if title then
|
|
search_query(title, class, menu)
|
|
end
|
|
end
|
|
|
|
mp.register_script_message("get-extra-event", function(cat, id, playlink, source_id, title, year)
|
|
if uosc_available then
|
|
mp.commandv("script-message-to", "uosc", "close-menu", "menu_anime")
|
|
end
|
|
if cat == "电影" then
|
|
if playlink:match("^.-%.html") then
|
|
playlink = playlink:match("^(.-%.html).*")
|
|
else
|
|
playlink = playlink:gsub("%?bsource=360ogvys$","")
|
|
end
|
|
DANMAKU.anime = title .. " (" .. year .. ")"
|
|
DANMAKU.episode = "电影"
|
|
DANMAKU.source = source_id
|
|
write_history()
|
|
add_danmaku_source(playlink, true)
|
|
else
|
|
get_details(cat, id, source_id, title, year)
|
|
end
|
|
end)
|
|
|
|
mp.register_script_message("add-extra-event", function(url, episode, number, class, id, site, title, year)
|
|
if uosc_available then
|
|
mp.commandv("script-message-to", "uosc", "close-menu", "menu_details")
|
|
end
|
|
load_extra_danmaku(url, episode, number, class, id, site, title, year)
|
|
end) |