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)