(.*)")
162 | if title ~= nil then source["title"] = title end
163 | end
164 | local _, _, player_info = string.find(content, "player_aaaa%s*=%s*(%{.-%})")
165 | if player_info == nil then error("视频信息解析失败: player_info") end
166 | local err, player_info_obj = kiko.json2table(player_info)
167 | if err ~= nil then error("视频信息解析失败: " .. err) end
168 | local video_url = player_info_obj["url"]
169 | if video_url == nil then error("视频信息解析失败: video_url") end
170 |
171 | local err, reply = kiko.httpget("https://bf.bfdm.xyz/m3u8.php", {["url"]=video_url}, {["Referer"]=string.format("https://%s/", settings["latest_addr"])})
172 | if err ~= nil then error(err) end
173 | local content = reply["content"]
174 | local _, _, dm_id = string.find(content, "\"id\"%s*:%s*\"(.-)\",")
175 | if dm_id == nil then error("视频信息解析失败: dm_id") end
176 |
177 | source_obj["dm_id"] = dm_id
178 | local _, data_str = kiko.table2json(source_obj)
179 | source["data"] = data_str
180 | return source, downloadDanmu(source_obj["dm_id"])
181 | end
182 |
--------------------------------------------------------------------------------
/danmu/5dm.lua:
--------------------------------------------------------------------------------
1 | info = {
2 | ["name"] = "5dm",
3 | ["id"] = "Kikyou.d.5dm",
4 | ["desc"] = "5dm弹幕脚本",
5 | ["version"] = "0.3",
6 | ["min_kiko"] = "2.0.0",
7 | ["label_color"] = "0xEB5D56",
8 | }
9 |
10 | supportedURLsRe = {
11 | "(https?://)?www\\.5dm\\.link/(bangumi|end)/dv(\\d+)(\\?(link=[0-9]+)?)?",
12 | "(https?://)?www\\.5dm\\.link/(bangumi|end)/dv(\\d+)(\\?line=\\d+)&(link=[0-9]+)?"
13 | }
14 |
15 | sampleSupporedURLs = {
16 | "https://www.5dm.link/bangumi/dv56062?link=6",
17 | "https://www.5dm.link/end/dv17582?line=1&link=2"
18 | }
19 |
20 | function unescape(str)
21 | str = string.gsub( str, '<', '<' )
22 | str = string.gsub( str, '>', '>' )
23 | str = string.gsub( str, '"', '"' )
24 | str = string.gsub( str, ''', "'" )
25 | str = string.gsub( str, '(%d+);', function(n) return utf8.char(n) end )
26 | str = string.gsub( str, '(%x+);', function(n) return utf8.char(tonumber(n,16)) end )
27 | str = string.gsub( str, '&', '&' ) -- Be sure to do this after all others
28 | return str
29 | end
30 |
31 |
32 | function epinfo(source)
33 | local err, source_obj = kiko.json2table(source["data"])
34 | if err ~= nil then error(err) end
35 |
36 | local baseUrl = "https://www.5dm.link/bangumi/dv" .. source_obj["dv"]
37 | local headers = {
38 | ["User-Agent"] = kiko.browser.ua(),
39 | }
40 | local err, reply = kiko.httpget(baseUrl, {}, headers)
41 | if err ~= nil then error(err) end
42 |
43 | local content = reply["content"]
44 | local _, _, epContent = string.find(content, "
(.-) | ")
45 | local results = {}
46 | if epContent ~= nil then
47 | local parser = kiko.htmlparser(epContent)
48 | while not parser:atend() do
49 | if parser:curnode()=="a" and parser:start() then
50 | local href = parser:curproperty("href")
51 | local _, _, dv, link = string.find(href, "dv(%d+)%?line=1&link=(%d+)")
52 | if dv ~= nil and link ~= nil then
53 | local data = {
54 | ["dv"] = dv,
55 | ["link"] = link
56 | }
57 | local _, data_str = kiko.table2json(data)
58 | title = parser:readuntil('a', false)
59 | title = string.gsub(title,"<.->", "")
60 | title = string.trim(title)
61 | table.insert(results, {
62 | ["title"] = title,
63 | ["data"] = data_str
64 | })
65 | end
66 | end
67 | parser:readnext()
68 | end
69 | end
70 | return results
71 | end
72 |
73 | function urlinfo(url)
74 | local pattens = {
75 | ["https?://www%.5dm%.link/bangumi/dv(%d+)%?link=(%d+)"]="dv_link",
76 | ["www%.5dm%.link/bangumi/dv(%d+)%?link=(%d+)"]="dv_link",
77 | ["https?://www%.5dm%.link/bangumi/dv(%d+)%?line=%d+&link=(%d+)"]="dv_link",
78 | ["www%.5dm%.link/bangumi/dv(%d+)%?line=%d+&link=(%d+)"]="dv_link",
79 | ["https?://www%.5dm%.link/bangumi/dv(%d+)"]="dv",
80 | ["www%.5dm%.link/bangumi/dv(%d+)"]="dv",
81 | ["https?://www%.5dm%.link/end/dv(%d+)%?link=(%d+)"]="dv_link",
82 | ["www%.5dm%.link/end/dv(%d+)%?link=(%d+)"]="dv_link",
83 | ["https?://www%.5dm%.link/end/dv(%d+)%?line=%d+&link=(%d+)"]="dv_link",
84 | ["www%.5dm%.link/end/dv(%d+)%?line=%d+&link=(%d+)"]="dv_link",
85 | ["https?://www%.5dm%.link/end/dv(%d+)"]="dv",
86 | ["www%.5dm%.link/end/dv(%d+)"]="dv",
87 | }
88 | local matched = nil
89 | for pv, k in pairs(pattens) do
90 | s, e = string.find(url, pv)
91 | if s then
92 | if e - s + 1 == #url then
93 | matched = k
94 | break
95 | end
96 | end
97 | end
98 | if matched == nil then error("不支持的URL") end
99 | local _, _, dv = string.find(url, "dv(%d+)")
100 | local data = {
101 | ["dv"] = dv
102 | }
103 | if matched == "dv_link" then
104 | local _, _, link = string.find(url, "link=(%d+)")
105 | data["link"] = link
106 | end
107 | local _, data_str = kiko.table2json(data)
108 | return epinfo({
109 | ["data"] = data_str
110 | })
111 | end
112 |
113 | function downloadDanmu(cid)
114 | local danmuUrl = "https://www.5dm.link/player/nxml.php"
115 | local query = {
116 | ["id"] = cid
117 | }
118 | local headers = {
119 | ["Accept"]="application/json",
120 | ["User-Agent"] = kiko.browser.ua(),
121 | }
122 |
123 | local err, reply = kiko.httpget(danmuUrl, query, headers)
124 | if err ~= nil then error(err) end
125 |
126 | local danmuContent = reply["content"]
127 | local err, obj = kiko.json2table(danmuContent)
128 | local danmuArray = obj["data"]
129 |
130 | local danmus = {}
131 |
132 | for _, dmObj in ipairs(danmuArray) do
133 | local time = tonumber(dmObj[1]) * 1000
134 |
135 | local dmType = 0
136 | if dmObj[2] == "top" then
137 | dmType = 1
138 | elseif dmObj[2] == "bottom" then
139 | dmType = 2
140 | end
141 |
142 | local color = dmObj[3]
143 | if color == "#fff" then
144 | color = 0xffffff
145 | else
146 | if string.sub(color, 1, 1) == "#" then
147 | color = tonumber(string.sub(color, 2), 16)
148 | else
149 | if string.sub(color, 1, 3) == "rgb" then
150 | color = string.sub(color, 5, -2)
151 | color = string.gsub(color, " ", "")
152 | local r, g, b = string.match(color, "(%d+),(%d+),(%d+)")
153 | color = tonumber(r) << 16 | tonumber(g) << 8 | tonumber(b)
154 | end
155 | end
156 | end
157 |
158 | local sender = tostring(dmObj[4])
159 |
160 | local text = unescape(dmObj[5])
161 | if text == "文明追番,请勿剧透!" then
162 | goto continue
163 | end
164 |
165 | table.insert(danmus, {
166 | ["text"]=text,
167 | ["time"]=time,
168 | ["color"]=color,
169 | ["fontsize"]=1,
170 | ["type"]=dmType,
171 | ["sender"]=sender
172 | })
173 |
174 | ::continue::
175 | end
176 | return danmus
177 | end
178 |
179 | function danmu(source)
180 | local err, source_obj = kiko.json2table(source["data"])
181 | if err ~= nil then error(err) end
182 |
183 | if source_obj["cid"] ~= nil then
184 | return nil, downloadDanmu(source_obj["cid"])
185 | end
186 |
187 | local url = "https://www.5dm.link/bangumi/dv" .. source_obj["dv"]
188 | local query = {}
189 | if source_obj["link"] ~= nil then
190 | query['line'] = "1"
191 | query["link"] = source_obj["link"]
192 | end
193 | local headers = {
194 | ["User-Agent"] = kiko.browser.ua(),
195 | }
196 | local err, reply = kiko.httpget(url, query, headers)
197 |
198 | if err ~= nil then error(err) end
199 | local content = reply["content"]
200 |
201 | local _, _, cid = string.find(content, "cid=(.-)&")
202 | if cid == nil then error("cid解析失败") end
203 | source_obj["cid"] = cid
204 | local _, data_str = kiko.table2json(source_obj)
205 | source["data"] = data_str
206 |
207 | return source, downloadDanmu(cid)
208 | end
209 |
--------------------------------------------------------------------------------
/danmu/iqiyi.lua:
--------------------------------------------------------------------------------
1 | info = {
2 | ["name"] = "爱奇艺",
3 | ["id"] = "Kikyou.d.Iqiyi",
4 | ["desc"] = "爱奇艺弹幕脚本",
5 | ["version"] = "0.4",
6 | ["min_kiko"] = "2.0.0",
7 | ["label_color"] = "0x00da5a",
8 | }
9 |
10 | supportedURLsRe = {
11 | "(https?://)?www\\.iqiyi\\.com/(v|w)_.+\\.html"
12 | }
13 |
14 | sampleSupporedURLs = {
15 | "https://www.iqiyi.com/v_19rrofvlmo.html",
16 | }
17 |
18 | function str2time(time_str)
19 | local timeArray = string.split(time_str, ':')
20 | local duration, base = 0, 0
21 | for i = #timeArray,1,-1 do
22 | duration = duration + 60^base*timeArray[i]
23 | base = base + 1
24 | end
25 | return duration
26 | end
27 |
28 | function search(keyword)
29 | local query = {
30 | ["key"] = keyword,
31 | ["if"] = "html5",
32 | }
33 | local err, reply = kiko.httpget("https://search.video.iqiyi.com/o", query)
34 | if err ~= nil then error(err) end
35 | local err, obj = kiko.json2table(reply.content)
36 | if err ~= nil then error(err) end
37 |
38 | local res = {}
39 | if obj["data"] == nil or obj["data"]["docinfos"] == nil then
40 | return res
41 | end
42 |
43 | for _, doc in ipairs(obj["data"]["docinfos"]) do
44 | local albumDocInfo = doc["albumDocInfo"]
45 | if albumDocInfo ~= nil and albumDocInfo["videoinfos"] ~= nil then
46 | local items = {}
47 | for _, video in ipairs(albumDocInfo["videoinfos"]) do
48 | if video["tvId"] ~= nil then
49 | local source_obj = {
50 | ["vid"] = string.format("%d", video["tvId"]),
51 | ["pieces"] = math.ceil(video["timeLength"] / 300 + 1),
52 | }
53 | local _, data_str = kiko.table2json(source_obj)
54 | local source = {
55 | ["title"] = video["itemshortTitle"],
56 | ["duration"] = video["timeLength"],
57 | ["data"] = data_str,
58 | }
59 | table.insert(items, source)
60 | end
61 | end
62 | if #items == 1 then
63 | table.insert(res, items[1])
64 | elseif #items > 1 then
65 | local _, data_str = kiko.table2json(items)
66 | table.insert(res, {
67 | ["title"] = albumDocInfo["albumTitle"],
68 | ["desc"] = string.format("共 %d 集", #items),
69 | ["data"] = data_str
70 | })
71 | end
72 | end
73 | end
74 |
75 | return res
76 | end
77 |
78 | function epinfo(source)
79 | local err, obj = kiko.json2table(source["data"])
80 | if err ~= nil then error(err) end
81 | if obj["url"] ~= nil then
82 | return {source}
83 | elseif obj["vid"] ~= nil then
84 | return {source}
85 | end
86 | return obj
87 | end
88 |
89 | function urlinfo(url)
90 | local pattens = {
91 | ["https?://www%.iqiyi%.com/v_.+%.html"]="iqy",
92 | ["https?://www%.iqiyi%.com/w_.+%.html"]="iqy",
93 | ["www%.iqiyi%.com/v_.+%.html"]="iqy",
94 | ["www%.iqiyi%.com/w_.+%.html"]="iqy"
95 | }
96 | local matched = nil
97 | for pv, k in pairs(pattens) do
98 | s, e = string.find(url, pv)
99 | if s then
100 | if e - s + 1 == #url then
101 | matched = k
102 | break
103 | end
104 | end
105 | end
106 | if matched == nil then error("不支持的URL") end
107 | local results = {}
108 | local data = { ["url"] = url }
109 | local _, data_str = kiko.table2json(data)
110 | table.insert(results, {
111 | ["title"] = "unknown",
112 | ["data"] = data_str
113 | })
114 | return results
115 | end
116 |
117 | function decodeDanmu(content, danmuList)
118 | local err, danmuContent = kiko.decompress(content)
119 | if err ~= nil then return danmuList end
120 | local xmlreader = kiko.xmlreader(danmuContent)
121 | local curDate, curText, curTime, curColor, curUID = nil, nil, nil, nil, nil
122 | while not xmlreader:atend() do
123 | if xmlreader:startelem() then
124 | if xmlreader:name()=="contentId" then
125 | curDate = string.sub(xmlreader:elemtext(), 1, 10)
126 | elseif xmlreader:name()=="content" then
127 | curText = xmlreader:elemtext()
128 | elseif xmlreader:name()=="showTime" then
129 | curTime = tonumber(xmlreader:elemtext()) * 1000
130 | elseif xmlreader:name()=="color" then
131 | curColor = tonumber(xmlreader:elemtext(), 16)
132 | elseif xmlreader:name()=="uid" then
133 | curUID = "[iqiyi]" .. xmlreader:elemtext()
134 | end
135 | elseif xmlreader:endelem() then
136 | if xmlreader:name()=="bulletInfo" then
137 | table.insert(danmuList, {
138 | ["text"]=curText,
139 | ["time"]=curTime,
140 | ["color"]=curColor,
141 | ["date"]=curDate,
142 | ["sender"]=curUID
143 | })
144 | end
145 | end
146 | xmlreader:readnext()
147 | end
148 | return danmuList
149 | end
150 |
151 | function downloadDanmu(id, pieces)
152 | local tvid = "0000" .. id
153 | local s1 = string.sub(tvid, -4, -3)
154 | local s2 = string.sub(tvid, -2)
155 | local url = string.format("http://cmts.iqiyi.com/bullet/%s/%s/%s_300_%s.z", s1, s2, id, "%d")
156 | local danmuList = {}
157 | if pieces == nil then
158 | local i = 1
159 | while true do
160 | local err, reply = kiko.httpget(string.format(url, i))
161 | if err ~= nil then break end
162 | local content = reply["content"]
163 | i = i+1
164 | danmuList = decodeDanmu(content, danmuList)
165 | end
166 | else
167 | local urls = {}
168 | for i=1,pieces do
169 | table.insert(urls, string.format(url, i))
170 | end
171 | local _, rets = kiko.httpgetbatch(urls)
172 | for _, v in ipairs(rets) do
173 | if not v["hasError"] then
174 | danmuList = decodeDanmu(v["content"], danmuList)
175 | end
176 | end
177 | end
178 | return danmuList
179 | end
180 |
181 | function danmu(source)
182 | local err, source_obj = kiko.json2table(source["data"])
183 | if err ~= nil then error(err) end
184 |
185 | if source_obj["vid"] ~= nil then
186 | return nil, downloadDanmu(source_obj["vid"], source_obj["pieces"])
187 | end
188 |
189 |
190 | local headers = {
191 | ["Referer"] = source_obj["url"],
192 | }
193 |
194 | local err, reply = kiko.httpget("https://mesh.if.iqiyi.com/player/lw/lwplay/accelerator.js", {["format"]="json"}, headers)
195 | if err ~= nil then error(err) end
196 | local err, obj = kiko.json2table(reply.content)
197 | if err ~= nil then error(err) end
198 |
199 | local videoInfo = obj["videoInfo"]
200 | if videoInfo == nil or videoInfo["tvId"] == nil then
201 | error("视频信息获取失败")
202 | end
203 |
204 | local err, reply = kiko.httpget(string.format("https://pcw-api.iqiyi.com/video/video/baseinfo/%d", videoInfo["tvId"]))
205 | if err ~= nil then error(err) end
206 | local err, obj = kiko.json2table(reply.content)
207 | if err ~= nil then error(err) end
208 | local data_obj = obj["data"]
209 |
210 | source["title"] = videoInfo["title"]
211 | source["duration"] = str2time(data_obj["duration"])
212 | source_obj["vid"] = string.format("%d", videoInfo["tvId"])
213 | source_obj["pieces"] = math.ceil(source["duration"] / 300 + 1)
214 | local _, data_str = kiko.table2json(source_obj)
215 | source["data"] = data_str
216 | return source, downloadDanmu(source_obj["vid"], source_obj["pieces"])
217 | end
218 |
--------------------------------------------------------------------------------
/meta.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "desc": "AcFun弹幕脚本",
4 | "id": "Kikyou.d.AcFun",
5 | "min_kiko": "2.0.0",
6 | "name": "AcFun",
7 | "type": "danmu",
8 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/acfun.lua",
9 | "version": "0.3"
10 | },
11 | {
12 | "desc": "巴哈姆特动画疯弹幕脚本",
13 | "id": "Kikyou.d.Gamer",
14 | "min_kiko": "2.0.0",
15 | "name": "动画疯",
16 | "type": "danmu",
17 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/bahamut.lua",
18 | "version": "0.2"
19 | },
20 | {
21 | "desc": "Bilibili弹幕脚本",
22 | "id": "Kikyou.d.Bilibili",
23 | "min_kiko": "2.0.0",
24 | "name": "Bilibili",
25 | "type": "danmu",
26 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/bilibili.lua",
27 | "version": "0.3.2"
28 | },
29 | {
30 | "desc": "弹弹Play弹幕脚本",
31 | "id": "Kikyou.d.Dandan",
32 | "min_kiko": "2.0.0",
33 | "name": "Dandan",
34 | "type": "danmu",
35 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/dandan.lua",
36 | "version": "0.5"
37 | },
38 | {
39 | "desc": "爱奇艺弹幕脚本",
40 | "id": "Kikyou.d.Iqiyi",
41 | "min_kiko": "2.0.0",
42 | "name": "爱奇艺",
43 | "type": "danmu",
44 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/iqiyi.lua",
45 | "version": "0.4"
46 | },
47 | {
48 | "desc": "芒果TV弹幕脚本",
49 | "id": "Kikyou.d.mgtv",
50 | "min_kiko": "2.0.0",
51 | "name": "芒果TV",
52 | "type": "danmu",
53 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/mgtv.lua",
54 | "version": "0.2"
55 | },
56 | {
57 | "desc": "腾讯视频弹幕脚本",
58 | "id": "Kikyou.d.Tencent",
59 | "min_kiko": "2.0.0",
60 | "name": "腾讯视频",
61 | "type": "danmu",
62 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/tencent.lua",
63 | "version": "0.4"
64 | },
65 | {
66 | "desc": "Tucao弹幕脚本",
67 | "id": "Kikyou.d.Tucao",
68 | "min_kiko": "2.0.0",
69 | "name": "Tucao",
70 | "type": "danmu",
71 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/tucao.lua",
72 | "version": "0.3"
73 | },
74 | {
75 | "desc": "优酷弹幕脚本",
76 | "id": "Kikyou.d.youku",
77 | "min_kiko": "2.0.0",
78 | "name": "优酷",
79 | "type": "danmu",
80 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/youku.lua",
81 | "version": "0.2"
82 | },
83 | {
84 | "desc": "5dm弹幕脚本,www.5dm.link",
85 | "id": "Kikyou.d.5dm",
86 | "min_kiko": "2.0.0",
87 | "name": "5dm",
88 | "type": "danmu",
89 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/5dm.lua",
90 | "version": "0.3"
91 | },
92 | {
93 | "desc": "异世界动漫弹幕脚本,www.dmmiku.com",
94 | "id": "Kikyou.d.ysjdm",
95 | "min_kiko": "2.0.0",
96 | "name": "异世界动漫",
97 | "type": "danmu",
98 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/danmu/ysjdm.lua",
99 | "version": "0.3.3"
100 | },
101 | {
102 | "desc": "Bangumi脚本,从bgm.tv中获取动画信息",
103 | "id": "Kikyou.l.Bangumi",
104 | "min_kiko": "0.9.1",
105 | "name": "Bangumi",
106 | "type": "library",
107 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/library/bangumi.lua",
108 | "version": "0.3.1"
109 | },
110 | {
111 | "desc": "弹弹Play动画识别脚本,利用弹弹Play API根据文件信息获取匹配的动画信息",
112 | "id": "Kikyou.m.DDMatch",
113 | "min_kiko": "2.0.0",
114 | "name": "弹弹Match",
115 | "type": "match",
116 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/library/dandan_match.lua",
117 | "version": "0.1"
118 | },
119 | {
120 | "desc": "TMDb+ 文件识别脚本",
121 | "id": "Kikyou.m.TMDb",
122 | "min_kiko": "2.0.0",
123 | "name": "TMDB文件识别",
124 | "type": "match",
125 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/library/tmdb_match.lua",
126 | "version": "0.2.29"
127 | },
128 | {
129 | "desc": "豆瓣电影脚本,从 movie.douban.com 中获取视频信息",
130 | "id": "Kikyou.l.Douban",
131 | "min_kiko": "2.0.0",
132 | "name": "豆瓣电影",
133 | "type": "library",
134 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/library/douban.lua",
135 | "version": "0.2"
136 | },
137 | {
138 | "desc": "TMDb+ 资料刮削脚本 - Edited by: kafovin \n从 The Movie Database (TMDb) 刮削影剧元数据,也可设置选择刮削fanart的媒体图片、Jellyfin/Emby的本地元数据、TVmaze的剧集演员。",
139 | "id": "Kikyou.l.TMDb",
140 | "min_kiko": "0.9.1",
141 | "name": "TMDb+Lib",
142 | "type": "library",
143 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/library/tmdb.lua",
144 | "version": "0.2.29"
145 | },
146 | {
147 | "desc": "漫猫BT搜索, www.comicat.org",
148 | "id": "Kikyou.r.Comicat",
149 | "min_kiko": "2.0.0",
150 | "name": "漫猫BT",
151 | "type": "resource",
152 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/resource/comicat.lua",
153 | "version": "0.2"
154 | },
155 | {
156 | "desc": "Mikan Project资源搜索, mikanani.me",
157 | "id": "Kikyou.r.Mikan",
158 | "min_kiko": "",
159 | "name": "Mikan",
160 | "type": "resource",
161 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/resource/mikan.lua",
162 | "version": "0.2"
163 | },
164 | {
165 | "desc": "Nyaa搜索, nyaa.si",
166 | "id": "Kikyou.r.Nyaa",
167 | "min_kiko": "",
168 | "name": "Nyaa",
169 | "type": "resource",
170 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/resource/nyaa.lua",
171 | "version": "0.1"
172 | },
173 | {
174 | "desc": "TPBsource 资源信息脚本(测试中,不稳定) Edited by: anonymous\n从 thePirateBay 刮削媒体资源信息。",
175 | "id": "Kikyou.r.TPBsource",
176 | "min_kiko": "0.9.1",
177 | "name": "TPBsrc",
178 | "type": "resource",
179 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/resource/tpbsource.lua",
180 | "version": "0.0.05"
181 | },
182 | {
183 | "desc": "bgmlist 番组日历",
184 | "id": "Kikyou.b.Bgmlist",
185 | "min_kiko": "",
186 | "name": "bgmlist",
187 | "type": "bgm_calendar",
188 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/bgm_calendar/bgmlist.lua",
189 | "version": "0.1"
190 | },
191 | {
192 | "desc": "Trakt 媒体日历脚本(测试中,不稳定) Edited by: kafovin \n从 trakt.tv 刮削媒体的日历时间表,可在日历中自动标记Trakt账户里已关注的媒体。",
193 | "id": "Kikyou.b.TraktList",
194 | "min_kiko": "0.9.1",
195 | "name": "TraktList",
196 | "type": "bgm_calendar",
197 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/bgm_calendar/traktlist.lua",
198 | "version": "0.1.06"
199 | },
200 | {
201 | "desc": "TVmaze 剧集日历脚本(测试中,不稳定) Edited by: kafovin \n从 tvmaze.com 刮削剧集的日历时间表。",
202 | "id": "Kikyou.b.TVmazeList",
203 | "min_kiko": "0.9.1",
204 | "name": "TVmazeList",
205 | "type": "bgm_calendar",
206 | "url": "https://raw.githubusercontent.com/KikoPlayProject/KikoPlayScript/master/bgm_calendar/traktlist.lua",
207 | "version": "0.1.22"
208 | }
209 | ]
210 |
--------------------------------------------------------------------------------
/bgm_calendar/bgmlist.lua:
--------------------------------------------------------------------------------
1 | info = {
2 | ["name"] = "BgmList",
3 | ["id"] = "Kikyou.b.Bgmlist",
4 | ["desc"] = "bgmlist 番组日历",
5 | ["version"] = "0.1",
6 | }
7 |
8 | settings = {
9 | ["load_last_season"]={
10 | ["title"]="加载上一季未完结动画",
11 | ["default"]="y",
12 | ["desc"]="在当季番组中添加上一季未完结动画",
13 | ["choices"]="y,n"
14 | }
15 | }
16 |
17 | site_json = [[{"acfun":{"title":"AcFun","urlTemplate":"https://www.acfun.cn/bangumi/aa{{id}}","regions":["CN"],"type":"onair"},"bilibili":{"title":"哔哩哔哩","urlTemplate":"https://www.bilibili.com/bangumi/media/md{{id}}/","regions":["CN"],"type":"onair"},"bilibili_hk_mo_tw":{"title":"哔哩哔哩(港澳台)","urlTemplate":"https://www.bilibili.com/bangumi/media/md{{id}}/","regions":["HK","MO","TW"],"type":"onair"},"sohu":{"title":"搜狐视频","urlTemplate":"https://tv.sohu.com/{{id}}","regions":["CN"],"type":"onair"},"youku":{"title":"优酷","urlTemplate":"https://list.youku.com/show/id_z{{id}}.html","regions":["CN"],"type":"onair"},"qq":{"title":"腾讯视频","urlTemplate":"https://v.qq.com/detail/{{id}}.html","regions":["CN"],"type":"onair"},"iqiyi":{"title":"爱奇艺","urlTemplate":"https://www.iqiyi.com/{{id}}.html","regions":["CN"],"type":"onair"},"letv":{"title":"乐视","urlTemplate":"https://www.le.com/comic/{{id}}.html","regions":["CN"],"type":"onair"},"pptv":{"title":"PPTV","urlTemplate":"http://v.pptv.com/page/{{id}}.html","regions":["CN"],"type":"onair"},"mgtv":{"title":"芒果tv","urlTemplate":"https://www.mgtv.com/h/{{id}}.html","regions":["CN"],"type":"onair"},"nicovideo":{"title":"Niconico","urlTemplate":"https://ch.nicovideo.jp/{{id}}","regions":["JP"],"type":"onair"},"netflix":{"title":"Netflix","urlTemplate":"https://www.netflix.com/title/{{id}}","type":"onair"},"gamer":{"title":"動畫瘋","urlTemplate":"https://acg.gamer.com.tw/acgDetail.php?s={{id}}","regions":["TW"],"type":"onair"},"muse_hk":{"title":"木棉花 HK","urlTemplate":"https://www.youtube.com/playlist?list={{id}}","regions":["HK","MO"],"type":"onair"},"ani_one_asia":{"title":"Ani-One Asia","urlTemplate":"https://www.youtube.com/playlist?list={{id}}","regions":["HK","TW","MO","SG","MY","PH","TH","ID","VN","KH","BD","BN","BT","FJ","FM","IN","KH","LA","LK","MH","MM","MN","MV","NP","NR","PG","PK","PW","SB","TL","TO","TV","VU","WS"],"type":"onair"},"viu":{"title":"Viu","urlTemplate":"https://www.viu.com/ott/hk/zh-hk/vod/{{id}}/","regions":["HK","SG","MY","IN","PH","TH","MM","BH","EG","JO","KW","OM","QA","SA","AE","ZA"],"type":"onair"}}]]
18 | _, site_map = kiko.json2table(site_json)
19 |
20 | function getseason()
21 | local err, reply = kiko.httpget("https://bgmlist.com/api/v1/bangumi/season")
22 | if err ~= nil then error(err) end
23 | local content = reply["content"]
24 | local err, obj = kiko.json2table(content)
25 | if err ~= nil then
26 | error(err)
27 | end
28 | local seasons = {}
29 | month_tb = {
30 | ["q1"]="01",
31 | ["q2"]="04",
32 | ["q3"]="07",
33 | ["q4"]="10"
34 | }
35 | for _, season in pairs(obj["items"]) do
36 | local year = string.sub(season, 1, 4)
37 | local month = month_tb[string.sub(season, 5, 6)]
38 | table.insert(seasons, {
39 | ["title"]=string.format("%s-%s", year, month)
40 | })
41 | end
42 | return seasons
43 | end
44 |
45 | function timestamp(dateStringArg)
46 | local inYear, inMonth, inDay, inHour, inMinute, inSecond, inZone =
47 | string.match(dateStringArg, '^(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)(.-)$')
48 | local zHours, zMinutes = string.match(inZone, '^(.-):(%d%d)$')
49 | if tonumber(inYear) < 1970 then
50 | return 0
51 | end
52 | local returnTime = os.time({year=inYear, month=inMonth, day=inDay, hour=inHour, min=inMinute, sec=inSecond, isdst=false})
53 | if zHours then
54 | returnTime = returnTime - ((tonumber(zHours)*3600) + (tonumber(zMinutes)*60))
55 | end
56 | return returnTime
57 | end
58 |
59 | function isnew(syear, smonth, ts)
60 | if tonumber(syear) < 1970 then
61 | return true
62 | end
63 | local start = os.time({year=tonumber(syear), month=tonumber(smonth), day=1, hour=0, isdst=false})
64 | return ts >= start
65 | end
66 |
67 | function getbgmlist(season)
68 | local season_title = season["title"]
69 | local year = string.sub(season_title, 1, 4)
70 | local month = string.sub(season_title, 6, 7)
71 | local q_tb = {
72 | ["01"]="q1",
73 | ["04"]="q2",
74 | ["07"]="q3",
75 | ["10"]="q4"
76 | }
77 | local url = string.format("https://bgmlist.com/api/v1/bangumi/archive/%s%s", year, q_tb[month])
78 | local err, reply = kiko.httpget(url)
79 | if err ~= nil then error(err) end
80 | local content = reply["content"]
81 | local err, obj = kiko.json2table(content)
82 | if err ~= nil then
83 | error(err)
84 | end
85 | local bgmlist = {}
86 | for _, bgm in pairs(obj["items"]) do
87 | local title = bgm["title"]
88 | if bgm["titleTranslate"] ~= nil then
89 | local tt = bgm["titleTranslate"]
90 | if tt["zh-Hans"] ~= nil then
91 | title = tt["zh-Hans"][1]
92 | end
93 | end
94 | local airTimestamp = timestamp(bgm["begin"]) + 8*3600
95 | local airTime = os.date("*t", airTimestamp)
96 | local weekDay = airTime["wday"]
97 | weekDay = weekDay - 1 -- 0~6
98 | local bgmid = ""
99 | local sites = {}
100 | if bgm["sites"] ~= nil then
101 | for _, site in pairs(bgm["sites"]) do
102 | local site_name = site["site"]
103 | if site_name == nil then
104 | goto continue
105 | end
106 | if site_name=="bangumi" then
107 | bgmid=site["id"]
108 | goto continue
109 | else
110 | local siteinfo = site_map[site_name]
111 | if siteinfo ~= nil then
112 | local template = siteinfo["urlTemplate"]
113 | if site["id"]~=nil then
114 | local url = string.gsub(template, "{{id}}", site["id"])
115 | table.insert(sites, {
116 | ["name"]=siteinfo["title"],
117 | ["url"]=url
118 | })
119 | end
120 | end
121 | end
122 | ::continue::
123 | end
124 | end
125 | local endTimestamp = -1
126 | if bgm["end"]~=nil and #bgm["end"]>0 then
127 | endTimestamp = timestamp(bgm["end"]) + 8*3600
128 | end
129 | table.insert(bgmlist, {
130 | ["title"]=title,
131 | ["weekday"]=weekDay,
132 | ["time"]=string.format("%02d:%02d", airTime["hour"], airTime["min"]),
133 | ["date"]=string.format("%d-%02d-%02d", airTime["year"], airTime["month"], airTime["day"]),
134 | ["isnew"]=isnew(year, month, airTimestamp),
135 | ["bgmid"]=bgmid,
136 | ["sites"]=sites,
137 | ["end"]=endTimestamp
138 | })
139 | end
140 | if settings["load_last_season"]=='y' then
141 | local last_month_tb = {
142 | ["01"]="10",
143 | ["04"]="01",
144 | ["07"]="04",
145 | ["10"]="07"
146 | }
147 | local last_year = tonumber(year)
148 | if month=="01" then
149 | last_year = last_year-1
150 | end
151 | local last_season = {
152 | ["title"]=string.format("%d-%s", last_year, last_month_tb[month])
153 | }
154 | kiko.log("load_last_season", last_season["title"])
155 | settings["load_last_season"]='n'
156 | local states, last_bgms = pcall(getbgmlist, last_season)
157 | if states then
158 | for _, l_bgm in pairs(last_bgms) do
159 | if l_bgm["end"]==-1 or isnew(year, month, l_bgm["end"]) then
160 | l_bgm["isnew"]=false
161 | table.insert(bgmlist, l_bgm)
162 | end
163 | end
164 | else
165 | kiko.log(string.format("get last season %s failed: ", last_season["title"]), last_bgms)
166 | end
167 | settings["load_last_season"]='y'
168 | end
169 | return bgmlist
170 | end
--------------------------------------------------------------------------------
/danmu/youku.lua:
--------------------------------------------------------------------------------
1 | info = {
2 | ["name"] = "优酷",
3 | ["id"] = "Kikyou.d.youku",
4 | ["desc"] = "优酷弹幕脚本",
5 | ["version"] = "0.2",
6 | ["min_kiko"] = "2.0.0",
7 | ["label_color"] = "0x2683FF",
8 | }
9 |
10 | supportedURLsRe = {
11 | "(https?://)?v.youku.com/v_show/id_(.+)\\.html\\??(.*)?"
12 | }
13 |
14 | sampleSupporedURLs = {
15 | "https://v.youku.com/v_show/id_XNTkxMjQxNzM4MA==.html"
16 | }
17 |
18 | settings = {
19 | ["skip_ad_time"]={
20 | ["title"]="跳过广告时长(s)",
21 | ["desc"]="跳过广告时长(s),弹幕播放时间会减去这个时长",
22 | ["default"]="0"
23 | },
24 | ["user_agent"]={
25 | ["title"]="UA",
26 | ["desc"]="User Agent",
27 | ["default"]="Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/108.0"
28 | }
29 | }
30 |
31 | function get_ts(date_str)
32 | --date_str = "2022-10-17 18:23:26"
33 | local year, month, day, hour, min, sec = date_str:match("(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)")
34 | local timestamp = os.time({
35 | year = tonumber(year),
36 | month = tonumber(month),
37 | day = tonumber(day),
38 | hour = tonumber(hour),
39 | min = tonumber(min),
40 | sec = tonumber(sec),
41 | })
42 | return timestamp
43 | end
44 |
45 | function escape(str)
46 | str = string.gsub (str, "([^0-9a-zA-Z !'()*._~-])", -- locale independent
47 | function (c) return string.format ("%%%02X", string.byte(c)) end)
48 | str = string.gsub (str, " ", "+")
49 | return str
50 | end
51 |
52 | function get_cookies(cookie_str)
53 | local cookies = {}
54 | if cookie_str == nil then return cookies end
55 | local cookie_rows = string.split(cookie_str, '\n')
56 | for _, cookie_row in ipairs(cookie_rows) do
57 | local cookie_items = string.split(cookie_row, ';')
58 | if #cookie_items > 0 then
59 | local kvs = string.split(cookie_items[1], '=')
60 | if #kvs > 1 then
61 | cookies[kvs[1]] = kvs[2]
62 | end
63 | end
64 | end
65 | return cookies
66 | end
67 |
68 | function get_video_info(vid)
69 | local info_url = string.format("https://openapi.youku.com/v2/videos/show.json?client_id=53e6cc67237fc59a&package=com.huawei.hwvplayer.youku&ext=show&video_id=%s", vid)
70 | local err, reply = kiko.httpget(info_url, {}, {["User-Agent"] = settings["user_agent"]})
71 | if err ~= nil then error(err) end
72 | local err, obj = kiko.json2table(reply["content"])
73 | if err ~= nil then error(err) end
74 | if obj["duration"] == nil then error("未找到视频时长") end
75 | local duration = tonumber(obj["duration"])
76 | local title = obj["title"]
77 | if title == nil then title = "unknown" end
78 | return title, duration
79 | end
80 |
81 | function download_seg(cna, vid, seg, danmuList)
82 | local msg = {
83 | ["ctime"] = tostring(os.time() * 1000),
84 | ["ctype"] = 10004,
85 | ["cver"] = "v1.0",
86 | ["guid"] = cna,
87 | ["mat"] = seg,
88 | ["mcount"] = 1,
89 | ["pid"] = 0,
90 | ["sver"] = "3.1.0",
91 | ["type"] = 1,
92 | ["vid"] = vid
93 | }
94 | local err, msg_json = kiko.table2json(msg, "compact")
95 | local err, msg_b64 = kiko.base64(msg_json, "to")
96 | msg["msg"] = msg_b64
97 | local err, sign = kiko.hashdata(msg_b64 .. "MkmC9SoIw6xCkSKHhJ7b5D2r51kBiREr", false)
98 | msg["sign"] = sign
99 | local err, reply = kiko.httpget("https://acs.youku.com/h5/mtop.com.youku.aplatform.weakget/1.0/?jsv=2.5.1&appKey=24679788", {}, {["User-Agent"] = settings["user_agent"]})
100 | if err ~= nil then
101 | kiko.log(string.format("vid: %s, seg: %d, http error: %s", vid, seg, err))
102 | return danmuList
103 | end
104 | local cookies = get_cookies(reply["headers"]["Set-Cookie"])
105 | if cookies["_m_h5_tk_enc"] == nil or cookies["_m_h5_tk"] == nil then
106 | kiko.log(string.format("vid: %s, seg: %d, error: Cookie not found, %s", vid, seg, err))
107 | return danmuList
108 | end
109 | local cookie_header = ""
110 | for k, v in pairs(cookies) do
111 | local c = string.format("%s=%s", k, v)
112 | if #cookie_header == 0 then
113 | cookie_header = c
114 | else
115 | cookie_header = cookie_header .. ";" .. c
116 | end
117 | end
118 | local headers = {
119 | ["Content-Type"] = "application/x-www-form-urlencoded",
120 | ["Cookie"] = cookie_header,
121 | ["Referer"] = "https://v.youku.com",
122 | ["User-Agent"] = settings["user_agent"]
123 | }
124 | local err, data = kiko.table2json(msg, "compact")
125 | local t = tostring(os.time() * 1000)
126 | local t_sing_hash_data = string.sub(cookies["_m_h5_tk"], 1, 32) .. "&" .. t .. "&" .. "24679788" .. "&" .. data
127 | local err, t_sign = kiko.hashdata(t_sing_hash_data, false)
128 | local params = {
129 | ["jsv"] = "2.5.6",
130 | ["appKey"] = "24679788",
131 | ["t"] = t,
132 | ["sign"] = t_sign,
133 | ["api"] = "mopen.youku.danmu.list",
134 | ["v"] = "1.0",
135 | ["type"] = "originaljson",
136 | ["dataType"] = "jsonp",
137 | ["timeout"] = "20000",
138 | ["jsonpIncPrefix"] = "utility"
139 | }
140 | local err, reply = kiko.httppost("https://acs.youku.com/h5/mopen.youku.danmu.list/1.0/", "data=" .. escape(data), headers, params)
141 | if err ~= nil then
142 | kiko.log(string.format("vid: %s, seg: %d, error: %s", vid, seg, err))
143 | return danmuList
144 | end
145 | local err, obj = kiko.json2table(reply["content"])
146 | if err ~= nil then
147 | kiko.log(string.format("dm content error, vid: %s, seg: %d, error: %s", vid, seg, err))
148 | return danmuList
149 | end
150 | local err, dobj = kiko.json2table(obj["data"]["result"])
151 | if err ~= nil then
152 | kiko.log(string.format("dmobj error, vid: %s, seg: %d, error: %s", vid, seg, err))
153 | return danmuList
154 | end
155 | local skip_time = tonumber(settings["skip_ad_time"]) * 1000
156 | for _, dm in ipairs(dobj["data"]["result"]) do
157 | local color = 0xffffff
158 | local size = 0
159 | local err, pobj = kiko.json2table(dm["propertis"])
160 | if err==nil then
161 | if pobj["color"] ~= nil then
162 | color = tonumber(pobj["color"])
163 | end
164 | if pobj["size"] ~= nil and tonumber(pobj["size"]) > 2 then
165 | size = 2
166 | end
167 | end
168 | table.insert(danmuList, {
169 | ["text"]=dm["content"],
170 | ["time"]=dm["playat"]-skip_time,
171 | ["date"]=tostring(get_ts(dm["createtime"])),
172 | ["color"]=color,
173 | ["fontsize"]=size,
174 | ["sender"]="[Youku]" .. dm["uid2"]
175 | })
176 | end
177 | return danmuList
178 | end
179 |
180 | function urlinfo(url)
181 | local reg = kiko.regex("[\\s\\S]+?youku.com/v_show/id_(.+?)\\.html")
182 | local _, _, vid = reg:find(url)
183 |
184 | if vid == nil then error("不支持的URL") end
185 | local title, duration = get_video_info(vid)
186 | if title == nil then title = "unknown" end
187 |
188 | local data = { ["vid"] = vid, ["duration"] = duration }
189 | local _, data_str = kiko.table2json(data, "compact")
190 |
191 | local results = {}
192 | table.insert(results, {
193 | ["title"] = title,
194 | ["duration"] = duration,
195 | ["data"] = data_str
196 | })
197 | return results
198 | end
199 |
200 | function danmu(source)
201 | local err, source_obj = kiko.json2table(source["data"])
202 | if err ~= nil then error(err) end
203 |
204 | local err, reply = kiko.httpget("https://log.mmstat.com/eg.js", {}, {["User-Agent"] = settings["user_agent"], ["Accept-Encoding"] = "gzip, deflate", ["Accept"] = "*/*", ["Connection"] = "keep-alive"})
205 | local cookie_header = reply["headers"]["Set-Cookie"] or reply["headers"]["set-cookie"]
206 | local cookies = get_cookies(cookie_header)
207 | if cookies["cna"] == nil then error("get cna cookie failed") end
208 |
209 | local segs = math.floor(source_obj["duration"] / 60) + 1
210 | local danmuList = {}
211 | for i = 1,segs do
212 | download_seg(cookies["cna"], source_obj["vid"], i, danmuList)
213 | end
214 | return nil, danmuList
215 | end
216 |
--------------------------------------------------------------------------------
/danmu/bahamut.lua:
--------------------------------------------------------------------------------
1 | info = {
2 | ["name"] = "动画疯",
3 | ["id"] = "Kikyou.d.Gamer",
4 | ["desc"] = "巴哈姆特动画疯弹幕脚本",
5 | ["version"] = "0.2",
6 | ["min_kiko"] = "2.0.0",
7 | ["label_color"] = "0x4798AA",
8 | }
9 |
10 | supportedURLsRe = {
11 | "(https?://)?ani\\.gamer\\.com\\.tw/animeVideo\\.php\\?sn=[0-9]+/?"
12 | }
13 |
14 | sampleSupporedURLs = {
15 | "https://ani.gamer.com.tw/animeVideo.php?sn=9285"
16 | }
17 |
18 | scriptmenus = {
19 | {["title"]="打开动画疯网站", ["id"]="open_gamer"},
20 | {["title"]="登录", ["id"]="gamer_login"},
21 | }
22 |
23 | cur_dm_cookie = ''
24 |
25 | function scriptmenuclick(menuid)
26 | if menuid == "open_gamer" then
27 | kiko.execute(true, "cmd", {"/c", "start", "https://ani.gamer.com.tw/"})
28 | elseif menuid == "gamer_login" then
29 | local b = kiko.browser.create()
30 | local succ = b:load("https://user.gamer.com.tw/login.php")
31 | b:show("登录成功后关闭页面")
32 | cur_dm_cookie = b:runjs("document.cookie")
33 | kiko.log(cur_dm_cookie)
34 | end
35 | end
36 |
37 | function search(keyword)
38 | local _, tradKw = kiko.sttrans(keyword, false)
39 | local query = {
40 | ["keyword"]=tradKw
41 | }
42 |
43 | local cookies = kiko.browser.cookie(".gamer.com.tw")
44 | local cookie_str = ""
45 | for k, v in pairs(cookies) do
46 | cookie_str = cookie_str .. string.format("%s=%s; ", k, v)
47 | end
48 |
49 | local browser_search = function()
50 | local b = kiko.browser.create()
51 | local succ = b:load("https://ani.gamer.com.tw/search.php", query, 20000)
52 | local content = b:html()
53 | local _, _, searchContent = string.find(content, "
(.+)
")
54 | if searchContent == nil then
55 | _, _, searchContent = string.find(content, "
(.+)