youtube.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. const key_path = "hiker://files/rules/js/TyrantGenesis_YouTube-api-key.js"
  2. const channels_path = "hiker://files/rules/js/TyrantGenesis_YouTube频道.js"
  3. let key
  4. if (fetch(key_path)) {
  5. key = fetch(key_path)
  6. } else {
  7. key = "AIzaSyDAVB60lCVpHO0nnsWyGtDWC9DTxH8vWlg"
  8. writeFile(key_path, key)
  9. }
  10. const baseParse = _ => {
  11. let d = [];
  12. let channels = []
  13. let script = ""
  14. if (fetch(channels_path)) {
  15. script = fetch(channels_path)
  16. eval(script)
  17. channels = local_channels || []
  18. } else {
  19. const defaultChannels = [
  20. {title: 'J. Cole', channelId: 'UCnc6db-y3IU7CkT_yeVXdVg', uploadsId: 'UUnc6db-y3IU7CkT_yeVXdVg', icon: 'https://yt3.ggpht.com/ytc/AAUvwniDYxWC2x4VZF7ecutGEaLpssNmrptdeuVFJI999g=s88-c-k-c0x00ffffff-no-rj-mo'},
  21. {title: 'Lofi Girl', channelId: 'UCSJ4gkVC6NrvII8umztf0Ow', uploadsId: 'UUSJ4gkVC6NrvII8umztf0Ow', icon: 'https://yt3.ggpht.com/ytc/AAUvwnhGIymQGp3jRMECbTCBSRAUqi8sKbATpWowQG44CA=s88-c-k-c0x00ffffff-no-rj'},
  22. {title: 'HatsuneMiku', channelId: 'UCJwGWV914kBlV4dKRn7AEFA', uploadsId: 'UUJwGWV914kBlV4dKRn7AEFA', icon: 'https://yt3.ggpht.com/ytc/AAUvwnjlsiW6yKsmkrfqn2foSm-ONTTWLeK_G70PF6TXBg=s800-c-k-c0x00ffffff-no-rj-mo'}
  23. ]
  24. script = `const local_channels = `+ JSON.stringify(defaultChannels)
  25. writeFile(channels_path, script)
  26. channels = defaultChannels
  27. }
  28. const channel_select = getVar("tyrantgenesis.youtube.channel_select", "0")
  29. const max_results = getVar("tyrantgenesis.youtube.max_results", "50")
  30. const page_token = getVar("tyrantgenesis.youtube.page_token", "")
  31. const channel_show = getVar("tyrantgenesis.youtube.channel_show", "1") // 0:关闭,1:展示,2:取消,3:置顶
  32. const search_select = getVar("tyrantgenesis.youtube.search_select", "video") // channel
  33. const search_show = getVar("tyrantgenesis.youtube.search_show", "1")
  34. let button_1_title = '', button_2_title = '', channel_prefix_status = false, channel_prefix = '', button_1_status = '', button_2_status = ''
  35. switch(channel_show) {
  36. case '0': {
  37. button_1_title = '关注频道'
  38. button_2_title = '取消关注'
  39. channel_prefix_status = false
  40. channel_prefix = ''
  41. button_1_status = '1'
  42. button_2_status = '2'
  43. break
  44. }
  45. case '1': {
  46. button_1_title = '‘‘’’<strong><font color="red">关注频道</font></strong>'
  47. button_2_title = '取消关注'
  48. channel_prefix_status = false
  49. channel_prefix = '✓'
  50. button_1_status = '0'
  51. button_2_status = '2'
  52. break
  53. }
  54. case '2': {
  55. button_1_title = '关注频道'
  56. button_2_title = '‘‘’’<strong><font color="red">取消关注</font></strong>'
  57. channel_prefix_status = true
  58. channel_prefix = '❌'
  59. button_1_status = '1'
  60. button_2_status = '3'
  61. break
  62. }
  63. case '3': {
  64. button_1_title = '关注频道'
  65. button_2_title = '‘‘’’<strong><font color="red">置顶关注</font></strong>'
  66. channel_prefix_status = true
  67. channel_prefix = '🔝'
  68. button_1_status = '1'
  69. button_2_status = '2'
  70. break
  71. }
  72. }
  73. if (search_show === '1') {
  74. /* d.push({
  75. title: search_select === 'video' ? '‘‘’’<strong>搜索:<font color="red">视频</font></strong>' : '搜索:视频',
  76. url: $('').lazyRule(_ => {
  77. putVar("tyrantgenesis.youtube.search_select", "video")
  78. refreshPage(false)
  79. return "hiker://empty"
  80. }),
  81. col_type: 'text_2'
  82. })
  83. d.push({
  84. title: search_select === 'channel' ? '‘‘’’<strong>搜索:<font color="red">频道</font></strong>' : '搜索:频道',
  85. url: $('').lazyRule(_ => {
  86. putVar("tyrantgenesis.youtube.search_select", "video")
  87. refreshPage(false)
  88. return 'toast://频道搜索未完成'
  89. }),
  90. col_type: 'text_2'
  91. }) */
  92. d.push({
  93. url: "input.trim() ? $('hiker://empty').rule(params => {eval(fetch('hiker://files/TyrantG/TEST/youtube.js'));searchParse(params);}, {input: input.trim(), search_select: '"+search_select+"'}) : 'toast://请输入搜索内容'",
  94. col_type: "input"
  95. });
  96. }
  97. d.push({
  98. title: button_1_title,
  99. url: $("").lazyRule(params => {
  100. putVar("tyrantgenesis.youtube.channel_show", params.button_1_status)
  101. refreshPage(false)
  102. return "hiker://empty"
  103. }, {
  104. button_1_status: button_1_status
  105. }),
  106. col_type: 'scroll_button',
  107. })
  108. d.push({
  109. title: button_2_title,
  110. url: $("").lazyRule(params => {
  111. putVar("tyrantgenesis.youtube.channel_show", params.button_2_status)
  112. refreshPage(false)
  113. return "hiker://empty"
  114. }, {
  115. button_2_status: button_2_status
  116. }),
  117. col_type: 'scroll_button',
  118. })
  119. d.push({
  120. title: search_show === '1' ? '隐藏搜索' : '显示搜索',
  121. url: $("").lazyRule(params => {
  122. putVar("tyrantgenesis.youtube.search_show", params.search_show === '1' ? '0' : '1')
  123. refreshPage(false)
  124. return "hiker://empty"
  125. }, {
  126. search_show: search_show
  127. }),
  128. col_type: 'scroll_button',
  129. })
  130. d.push({
  131. title: '设置',
  132. url: $('hiker://empty').rule(_ => {
  133. eval(fetch('hiker://files/TyrantG/TEST/youtube.js'))
  134. let d = [];
  135. d.push({
  136. title: "使用须知:规则使用的接口来自 Google 开发者平台官方接口,配额限制为每日10000,除搜索外每次操作会消耗1次配额,而搜索会消耗100配额。首先请使用自己申请的密钥,其次尽量少用搜索,多用订阅。另外有美国信用卡或者其他国外支付途径的可以将自己的开发者账号绑定 Google Cloud 以获得更多配额。",
  137. col_type: "long_text"
  138. })
  139. d.push({
  140. col_type: "line"
  141. });
  142. d.push({
  143. title: "申请 Google YouTube API 教程",
  144. url: "https://www.tyrantg.com/post/7",
  145. col_type: "text_1"
  146. })
  147. d.push({
  148. title: "确认",
  149. desc: "请输入密钥",
  150. url: "input.trim() ? $('hiker://empty').lazyRule(params => {const key_path = 'hiker://files/rules/js/TyrantGenesis_YouTube-api-key.js';writeFile(key_path, params.input);back(true);}, {input: input.trim()}) : 'toast://请输入搜索内容'",
  151. col_type: "input"
  152. });
  153. setResult(d);
  154. }),
  155. col_type: 'scroll_button',
  156. })
  157. if (channel_show !== '0') {
  158. channels.forEach((item, index) => {
  159. d.push({
  160. title: (parseInt(channel_select) === index || channel_prefix_status) ? channel_prefix+item.title : item.title,
  161. pic_url: item.icon,
  162. url: $("").lazyRule(params => {
  163. const channels_path = "hiker://files/rules/js/TyrantGenesis_YouTube频道.js"
  164. if (params.channel_show === '1') {
  165. putVar("tyrantgenesis.youtube.channel_select", params.index.toString())
  166. putVar("tyrantgenesis.youtube.page_token", "")
  167. } else if (params.channel_show === '2') {
  168. params.channels.splice(params.index, 1)
  169. let script = `const local_channels = `+JSON.stringify(params.channels)
  170. writeFile(channels_path, script)
  171. putVar("tyrantgenesis.youtube.channel_select", "0")
  172. putVar("tyrantgenesis.youtube.page_token", "")
  173. } else if (params.channel_show === '3') {
  174. let current = params.channels[params.index]
  175. params.channels.splice(params.index, 1)
  176. params.channels.unshift(current)
  177. let script = `const local_channels = `+JSON.stringify(params.channels)
  178. writeFile(channels_path, script)
  179. putVar("tyrantgenesis.youtube.channel_select", "0")
  180. putVar("tyrantgenesis.youtube.page_token", "")
  181. }
  182. refreshPage(false)
  183. return "hiker://empty"
  184. }, {
  185. index: index,
  186. channel_show: channel_show,
  187. channels: channels,
  188. }),
  189. col_type: 'icon_round_4',
  190. })
  191. })
  192. d.push({
  193. col_type:"blank_block"
  194. })
  195. }
  196. if (channels.length > 0) {
  197. const url = "https://www.googleapis.com/youtube/v3/playlistItems?key=" + key + "&part=snippet&maxResults=" + max_results + "&playlistId=" + channels[channel_select].uploadsId + "&pageToken=" + page_token
  198. const video_item_json = fetch(url)
  199. const video_item = JSON.parse(video_item_json)
  200. if (video_item.error) {
  201. if (search_show === '1') {
  202. putVar("tyrantgenesis.youtube.search_show", '0')
  203. refreshPage(false)
  204. return "hiker://empty"
  205. }
  206. if (video_item.error.code === 403) {
  207. d.push({
  208. title: "接口配额已超量,请进入设置 按照教程申请 Google YouTube API 并在设置中输入密钥",
  209. col_type: "long_text"
  210. })
  211. } else if (video_item.error.code === 400) {
  212. d.push({
  213. title: "api key 无效,请进入设置 按照教程申请 Google YouTube API 并在设置中输入密钥",
  214. col_type: "long_text"
  215. })
  216. }
  217. } else {
  218. const list = video_item.items
  219. if (video_item.prevPageToken) {
  220. d.push({
  221. title: '上一页',
  222. url: $("").lazyRule(video_item => {
  223. putVar("tyrantgenesis.youtube.page_token", video_item.prevPageToken)
  224. refreshPage(true)
  225. return "hiker://empty"
  226. }, video_item),
  227. col_type: 'text_center_1',
  228. })
  229. d.push({
  230. col_type: 'blank_block',
  231. })
  232. }
  233. list.forEach(item => {
  234. let thumbnails = item.snippet.thumbnails
  235. let pic_url = thumbnails[Object.keys(thumbnails)[Object.keys(thumbnails).length - 1]].url
  236. let video_id = item.snippet.resourceId.videoId
  237. let video_url = "https://www.googleapis.com/youtube/v3/videos?key="+key+"&part=snippet&part=snippet&id="+video_id
  238. d.push({
  239. title: item.snippet.title,
  240. pic_url: pic_url,
  241. url: $(video_url).rule(params => {
  242. eval(fetch('hiker://files/TyrantG/TEST/youtube.js'))
  243. secParse(params)
  244. }, {
  245. video_id: video_id,
  246. channel_id: item.snippet.channelId,
  247. }),
  248. col_type: 'movie_2',
  249. })
  250. })
  251. if (video_item.nextPageToken) {
  252. d.push({
  253. col_type: 'blank_block',
  254. })
  255. d.push({
  256. title: '下一页',
  257. url: $("").lazyRule(video_item => {
  258. putVar("tyrantgenesis.youtube.page_token", video_item.nextPageToken)
  259. refreshPage(true)
  260. return "hiker://empty"
  261. }, video_item),
  262. col_type: 'text_center_1',
  263. })
  264. }
  265. }
  266. } else {
  267. d.push({
  268. title: '还没有关注的频道',
  269. col_type: 'long_text',
  270. })
  271. }
  272. setResult(d);
  273. }
  274. const secParse = params => {
  275. let d = [];
  276. // local_channels
  277. eval(fetch(channels_path))
  278. const channels = local_channels || []
  279. // video
  280. const video_desc_json = getResCode()
  281. const video_desc = JSON.parse(video_desc_json)
  282. const snippet = video_desc.items[0].snippet
  283. let thumbnails = snippet.thumbnails
  284. let pic_url = thumbnails[Object.keys(thumbnails)[Object.keys(thumbnails).length - 1]].url
  285. let ori_url = "https://m.youtube.com/watch?v="+params.video_id
  286. // 频道
  287. const channel_url = "https://www.googleapis.com/youtube/v3/channels?key="+key+"&part=snippet,contentDetails&id="+params.channel_id
  288. const channel_desc = JSON.parse(fetch(channel_url)).items[0]
  289. // const channel_desc = JSON.parse(fetch(channel_url))
  290. let channel_thumbnails = channel_desc.snippet.thumbnails
  291. let channel_upload_id = channel_desc.contentDetails.relatedPlaylists.uploads
  292. let channel_pic_url = channel_thumbnails[Object.keys(channel_thumbnails)[Object.keys(channel_thumbnails).length - 1]].url
  293. d.push({
  294. title: channel_desc.snippet.title,
  295. pic_url: channel_pic_url,
  296. url: "https://m.youtube.com/channel/"+params.channel_id,
  297. col_type: "icon_2_round"
  298. })
  299. let has_collect = false
  300. channels.forEach(item => {
  301. if (item.channelId === params.channel_id) has_collect = true
  302. })
  303. d.push({
  304. title: has_collect ? "已关注" : "关注频道",
  305. url: $("").lazyRule(params => {
  306. const channels_path = "hiker://files/rules/js/TyrantGenesis_YouTube频道.js"
  307. if (params.has_collect) {
  308. refreshPage(false)
  309. return 'toast://已关注'
  310. } else {
  311. params.channels.push({
  312. title: params.channel_desc.snippet.title,
  313. channelId: params.channel_id,
  314. uploadsId: params.channel_upload_id,
  315. icon: params.channel_pic_url,
  316. })
  317. script = `const local_channels = `+JSON.stringify(params.channels)
  318. writeFile(channels_path, script)
  319. refreshPage(false)
  320. return 'toast://关注成功'
  321. }
  322. }, {
  323. has_collect: has_collect,
  324. channel_upload_id: channel_upload_id,
  325. channel_id: params.channel_id,
  326. channel_pic_url: channel_pic_url,
  327. channels: channels,
  328. channel_desc: channel_desc,
  329. }),
  330. col_type: "text_2"
  331. })
  332. d.push({
  333. title: snippet.title,
  334. pic_url: pic_url,
  335. url: ori_url,
  336. desc: snippet.description,
  337. col_type: 'pic_1'
  338. })
  339. /*const videoParse = fetch("https://www.youtubemy.com/search?url="+ori_url)
  340. const video_list = parseDomForArray(videoParse, '.video_files&&a')
  341. video_list.forEach(video => {
  342. let audio = parseDomForHtml(video, 'img&&src').indexOf("yy.png") !== -1
  343. d.push({
  344. title: parseDomForHtml(video, 'a&&Text').replace(/\(.*?\)/, '') + (audio ? '(有声)' : '(无声)'),
  345. url: parseDomForHtml(video, 'a&&href'),
  346. col_type: 'text_2'
  347. })
  348. })*/
  349. const videoParse = fetch("https://www.y2mate.com/mates/analyze/ajax", {
  350. headers: {
  351. "User-Agent": PC_UA,
  352. },
  353. body: 'ajax=1&q_auto=1&url='+ori_url,
  354. method: 'POST'
  355. })
  356. const html = JSON.parse(videoParse).result
  357. const id_res = html.match(/var k__id = "(.*?)"/)
  358. const _id = id_res ? id_res[1] : ''
  359. const video_list = parseDomForArray(html, 'tbody&&tr')
  360. video_list.forEach(video => {
  361. let quality = parseDomForHtml(video, 'a&&data-fquality')
  362. d.push({
  363. title: quality,
  364. url: $("").lazyRule(params => {
  365. const videoParse = fetch("https://www.y2mate.com/mates/convert", {
  366. headers: {
  367. "User-Agent": PC_UA,
  368. },
  369. body: 'type=youtube&ftype=mp4&ajax=1&v_id='+params.v_id+'&fquality='+params.fquality+'&_id='+params._id,
  370. method: 'POST'
  371. })
  372. const html = JSON.parse(videoParse).result
  373. const url_res = html.match(/<a href="(.*?)"/)
  374. return url_res ? url_res[1]+"#isVideo=true#" : "toast://该清晰度解析失败,请使用更低清晰度"
  375. }, {
  376. v_id: params.video_id,
  377. fquality: quality,
  378. _id: _id,
  379. }),
  380. col_type: 'text_2'
  381. })
  382. })
  383. // d.pop()
  384. /*let res_json = fetch('https://s6.save.tube/ajax/getLinks.php?video='+encodeURIComponent(ori_url)+'&rand=xCAasG22rQg2XeV', {
  385. headers: {
  386. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
  387. 'Origin': 'https://save.tube',
  388. 'Referer': 'https://save.tube',
  389. }
  390. })
  391. log(res_json)
  392. let res = JSON.parse(res_json)
  393. if (res.status === 'success') {
  394. let data = res.data.av
  395. data.forEach(item => {
  396. d.push({
  397. title: item.quality+'.'+item.ext,
  398. url: item.url+'#isVideo=true#',
  399. col_type: 'text_2'
  400. })
  401. })
  402. } else {
  403. log(res)
  404. res_json = fetch(res.data)
  405. log(res_json)
  406. res = JSON.parse(res_json)
  407. if (res.status === 'success') {
  408. let data = res.data.av
  409. data.forEach(item => {
  410. d.push({
  411. title: item.quality+'.'+item.ext,
  412. url: item.url+'#isVideo=true#',
  413. col_type: 'text_2'
  414. })
  415. })
  416. } else {
  417. d.push({
  418. title: '解析失败',
  419. url: 'hiker://empty',
  420. col_type: 'text_center_1',
  421. })
  422. }
  423. }*/
  424. setResult(d);
  425. }
  426. const searchParse = params => {
  427. let d = []
  428. const search = params.input
  429. const type = params.search_select
  430. const max_results = getVar("tyrantgenesis.youtube.max_results", "50")
  431. const search_page_token = getVar("tyrantgenesis.youtube.search_page_token", "")
  432. const url = "https://www.googleapis.com/youtube/v3/search?key="+key+"&part=snippet,id&maxResults="+max_results+"&type="+type+"&q="+search+"&pageToken="+search_page_token
  433. const search_json = fetch(url)
  434. const search_item = JSON.parse(search_json)
  435. const list = search_item.items
  436. if (search_item.prevPageToken) {
  437. d.push({
  438. title: '上一页',
  439. url: $("").lazyRule(search_item => {
  440. putVar("tyrantgenesis.youtube.search_page_token", search_item.prevPageToken)
  441. refreshPage(true)
  442. return "hiker://empty"
  443. }, search_item),
  444. col_type: 'text_center_1',
  445. })
  446. d.push({
  447. col_type: 'blank_block',
  448. })
  449. }
  450. if (type === 'video') {
  451. list.forEach(item => {
  452. let thumbnails = item.snippet.thumbnails
  453. let pic_url = thumbnails[Object.keys(thumbnails)[Object.keys(thumbnails).length - 1]].url
  454. let video_id = item.id.videoId
  455. let video_url = "https://www.googleapis.com/youtube/v3/videos?key="+key+"&part=snippet&part=snippet&id="+video_id
  456. d.push({
  457. title: item.snippet.title,
  458. pic_url: pic_url,
  459. url: $(video_url).rule(params => {
  460. eval(fetch('hiker://files/TyrantG/TEST/youtube.js'))
  461. secParse(params)
  462. }, {
  463. video_id: video_id,
  464. channel_id: item.snippet.channelId,
  465. }),
  466. col_type: 'movie_2',
  467. })
  468. })
  469. }
  470. if (search_item.nextPageToken) {
  471. d.push({
  472. col_type: 'blank_block',
  473. })
  474. d.push({
  475. title: '下一页',
  476. url: $("").lazyRule(search_item => {
  477. putVar("tyrantgenesis.youtube.search_page_token", search_item.nextPageToken)
  478. refreshPage(true)
  479. return "hiker://empty"
  480. }, search_item),
  481. col_type: 'text_center_1',
  482. })
  483. }
  484. setResult(d);
  485. }