Bangumi快捷播放

首页追番卡片显示中文标题,浮动卡片增加资源搜索,可添加对应集数播放源,浮动卡片状态实时改变无需刷新

  1. // ==UserScript==
  2. // @name Bangumi快捷播放
  3. // @description 首页追番卡片显示中文标题,浮动卡片增加资源搜索,可添加对应集数播放源,浮动卡片状态实时改变无需刷新
  4. // @namespace https://github.com/RiverYale/Userscripts/
  5. // @homepage https://riveryale.github.io/Userscripts/
  6. // @version 3.3
  7. // @author RiverYale
  8. // @match *://bangumi.tv
  9. // @match *://bgm.tv
  10. // @match *://chii.in
  11. // @icon https://riveryale.github.io/Userscripts/assets/pic/BangumiEasyPlay/icon.png
  12. // @run-at document-end
  13. // @compatible chrome
  14. // @compatible edge
  15. // @license MIT License
  16. // ==/UserScript==
  17.  
  18. /*================= 更新脚本前注意保存自己修改的内容! =================*/
  19.  
  20. var autoMark = true; // 默认点击链接后自动标记为看过
  21. var authSrc = ""; // 若404则表明未更新资源,全部移除,若无需验证则删除引号中的内容
  22. var src_dict = { // 网址格式,番剧ID: [资源ID, 播放线路, 总体集数偏移, [集数, 增加偏移]...]
  23. "AGE动漫": {
  24. pattern: "https://www.agemys.org/play/${id}/${ch}/${ep}",
  25. search: "https://www.agemys.org/search?query=${keyword}",
  26. 373247: [20220248, 1, 1], // 无职转生:到了异世界就拿出真本事 ~ 第二季
  27. 441233: [20230154, 1, 0], // 总之就是非常可爱 女子高中篇
  28. 376433: [20230138, 1, 0], // 政宗君的复仇R
  29. 403225: [20230131, 1, -12], // 打工吧!!魔王大人 2nd Season
  30. 376739: [20220244, 1, -87], // 进击的巨人 最终季 Part.3
  31. 414461: [20230128, 1, 0], // 僵尸百分百~变成僵尸之前想做的100件事~
  32. 386809: [20230077, 1, 0], // 我推的孩子
  33. },
  34. "Bimi": {
  35. pattern: "http://m.dodoge.me/bangumi/${id}/play/${ch}/${ep}/",
  36. search: "http://m.dodoge.me/vod/search/wd/${keyword}",
  37. },
  38. "MX动漫": {
  39. pattern: "http://www.mxdm9.com/dongmanplay/${id}-${ch}-${ep}.html",
  40. search: "http://www.mxdm9.com/search/-------------.html?wd=${keyword}",
  41. 373247: [8183, 1, 1], // 无职转生:到了异世界就拿出真本事 ~ 第二季
  42. 441233: [7112, 1, 12], // 总之就是非常可爱 女子高中篇
  43. 376433: [7622, 1, 0], // 政宗君的复仇R
  44. 403225: [8181, 1, -12], // 打工吧!!魔王大人 2nd Season
  45. 376739: [8049, 1, -87], // 进击的巨人 最终季 Part.3
  46. 414461: [8169, 1, 0], // 僵尸百分百~变成僵尸之前想做的100件事~
  47. 386809: [7963, 1, 0], // 我推的孩子
  48. },
  49. "橘子动漫": {
  50. pattern: "https://www.mgnacg.com/bangumi/${id}-${ch}-${ep}/",
  51. search: "https://www.mgnacg.com/search/-------------/?wd=${keyword}",
  52. },
  53. "樱花动漫": {
  54. pattern: "https://www.vdm8.com/play/${id}-${ch}-${ep}.html",
  55. search: "https://www.vdm8.com/search/${keyword}-------------.html",
  56. },
  57. "宫下动漫": {
  58. pattern: "https://arlnigdm.com/vodplay/${id}-${ch}-${ep}.html",
  59. search: "https://arlnigdm.com/vodsearch/-------------.html?wd=${keyword}",
  60. },
  61. "新番组": {
  62. pattern: "https://bangumi.online",
  63. search: "https://bangumi.online",
  64. },
  65. };
  66. /*================= 更新脚本前注意保存自己修改的内容! =================*/
  67.  
  68. if($(".loginPanel").length == 1) return;
  69.  
  70. /* 标题中日文对调 */
  71. var titles_A = Array.from($(".tinyHeader .textTip:not(.prgCheckIn)")) // 平铺模式
  72. .concat(Array.from($(".l.textTip"))) // 列表模式右侧
  73. if(titles_A.length == 0) return;
  74. var handleTitle_A = function(title) {
  75. var text = $(title).text();
  76. var data_original_title = $(title).attr("data-original-title");
  77. if(!data_original_title){
  78. return true;
  79. }
  80. $(title).text(data_original_title);
  81. $(title).attr("data-original-title", text);
  82. }
  83.  
  84. var titles_B = Array.from($(".subjectItem.title.textTip")) // 列表模式左侧 - 1, 2, 3
  85. var handleTitle_B = function(title) {
  86. var text = $(title).find('span').text();
  87. var data_original_title = $(title).attr("data-original-title");
  88. if(!data_original_title){
  89. return true;
  90. }
  91. $(title).find('span').text(data_original_title);
  92. $(title).attr('data-original-title', text);
  93.  
  94. var preALink = $(title).prev().prev();
  95. var preALink_title = $(preALink).attr('data-original-title');
  96. $(preALink).attr('data-original-title', preALink_title.replace(text, data_original_title));
  97. }
  98.  
  99. /* 生成配置模板 */
  100. var _ul = $('<ul style="display:inline-block; float:right;"></ul>');
  101. var _btn = $('<div style="padding: 3px 12px;margin: 8px 5px 0;;background: #F09199;color: white;border-radius: 50px;font-size: 11px;cursor: pointer;">生成模板</div>');
  102. $(_ul).append(_btn);
  103. $("#prgManagerHeader").append(_ul);
  104. $(_btn).click((e) => {
  105. var resStr = "";
  106. document.querySelectorAll('.subjectItem[data-subject-id]').forEach((i) => {
  107. const id = i.getAttribute("data-subject-id");
  108. const name = i.getAttribute("data-subject-name-cn");
  109. resStr += `${id}: [0, 1, 0], // ${name}\n`;
  110. })
  111. let allowCopy = confirm("将复制以下内容到剪切板:\n" + resStr);
  112. if (allowCopy) {
  113. navigator.clipboard.writeText(resStr);
  114. console.log(resStr);
  115. alert("请自行修改模板方括号内容。如果复制失败,请按F12打开控制台查看输出,手动复制。")
  116. }
  117. })
  118.  
  119. /* 点击链接后是否自动标记为[看过] */
  120. var _ul = $('<ul style="display:inline-block; float:right;"></ul>');
  121. var _label = $('<label style="display:block; margin:10px;"></label>');
  122. var _input = $('<input type="checkbox" style="vertical-align:middle;">');
  123. var _span = $('<span style="vertical-align:middle;"> 自动标记</span>');
  124. $(_ul).append(_label);
  125. $(_label).append(_input);
  126. $(_label).append(_span);
  127. $(_label).attr("title", "点击播放链接后自动标记为[看过]");
  128. $("#prgManagerHeader").append(_ul);
  129. $(_input).prop("checked", autoMark);
  130. $(_input).click(() => {
  131. autoMark = $(_input).prop("checked");
  132. });
  133.  
  134.  
  135. /* 为已出集数添加资源链接 */
  136. var epLinkList = Array.from($(".load-epinfo"));
  137. var getSrcHref = function(dict, subid, ep) {
  138. var value = dict[subid];
  139. ep = Number(ep);
  140. if(value.constructor == Array) {
  141. var pattern = dict["pattern"];
  142. var resId = value[0];
  143. var resCh = value[1];
  144. var resEp = ep + value[2];
  145. for(let i=3; i<value.length; i++) {
  146. if(ep >= value[i][0]) {
  147. resEp += value[i][1];
  148. }
  149. }
  150. return pattern.replace(/\$\{id\}/g, resId).replace(/\$\{ch\}/g, resCh).replace(/\$\{ep\}/g, resEp);
  151. } else if(value.constructor == String) {
  152. return value;
  153. }
  154. return '';
  155. }
  156. var handleEpLinkList = function(epLink) {
  157. // 标记该条目下集数的偏移量
  158. $(epLink.rel).attr("ep_offset", $(epLink).parent().index());
  159.  
  160. // 未知集数跳过
  161. if(epLink.className.indexOf("epBtnNA") > -1) {
  162. return true;
  163. }
  164. var subid = $("#"+epLink.id).attr("subject_id");
  165. var ep = Number($("#"+epLink.id).text());
  166. var srcPanel = $("<div><hr class='board'></div>");
  167. for(let srcName in src_dict) {
  168. // 根据资源字典添加资源链接
  169. var dict = src_dict[srcName];
  170. if(dict[subid] == undefined) {
  171. var subjectName = $(`[data-subject-id=${subid}][class=textTip]`).text();
  172. if(dict['search'] == undefined) {
  173. $(srcPanel).append(`<a href="javascript:alert('未添加搜索地址格式!');" style="margin-right:10px; display:inline-block; color:#555; font-style:italic;">${srcName}</a>`);
  174. } else {
  175. var searchHref = dict['search'].replace(/\$\{keyword\}/g, subjectName)
  176. $(srcPanel).append(`<a href="${searchHref}" style="margin-right:10px; display:inline-block; color:#555; font-style:italic;" target="_blank">${srcName}</a>`);
  177. }
  178. continue;
  179. }
  180. var srcHref = getSrcHref(dict, subid, ep);
  181. var alink = $('<a style="margin-right:10px; display:inline-block; font-weight:bold;" target="_blank"></a>');
  182. $(alink).attr("href", srcHref);
  183. $(alink).text(srcName);
  184. $(srcPanel).append(alink);
  185.  
  186. // 点击资源链接后标记为[看过]
  187. $(alink).click(() => {
  188. var watchedLink = "#Watched_" + epLink.id.slice(4);
  189. if(autoMark && $(watchedLink).length>0) {
  190. $(watchedLink).click();
  191. }
  192. });
  193.  
  194. // 已看集数的不测试资源是否可达
  195. if(epLink.className.indexOf("epBtnWatched")==-1 && srcName==authSrc) {
  196. var authHref = srcHref;
  197. }
  198. }
  199. $(epLink.rel).append(srcPanel);
  200.  
  201. // 测试资源是否可达
  202. if(authHref != undefined && authHref != "") {
  203. var isRunUrl = function(url){
  204. return new Promise(function (resolve, reject) {
  205. var dom = document.createElement('link');
  206. dom.href = url;
  207. dom.rel = 'stylesheet';
  208. dom.onload = function () {
  209. document.head.removeChild(dom);
  210. resolve();
  211. }
  212. dom.onerror = reject;
  213. document.head.appendChild(dom);
  214. });
  215. }
  216. isRunUrl(authHref).then(data => {}).catch(data => {
  217. // $(srcPanel).remove();
  218. $(epLink).css({"color":"red"});
  219. // $(epLink).css({"color":"#00F", "background-color":"#e0e0e0", "border":"1px solid #b6b6b6"});
  220. });
  221. }
  222. }
  223.  
  224.  
  225. // 根据状态更新单集进度情况面板
  226. var updataEpStatusTool = function(prg, type) {
  227. var epid = prg.id.slice(8);
  228. var gh = $("a:first", prg).attr("href").split('=')[1];
  229. var epStatusTool = $(".epStatusTool", prg);
  230.  
  231. var type_dict = { "看过": 1, "想看": 2, "抛弃": 3, "撤消": 0 };
  232. var status = type_dict[type];
  233. var statusLink = $(".epStatusTool a", prg)[0];
  234. statusLink = $(statusLink).clone(true);
  235.  
  236. var epGrid = $(`[rel="#prginfo_${epid}"]`).parent().parent().parent();
  237. var progressText = $("#prgsPercentNum", epGrid).text();
  238. var progress = progressText.slice(1,-1).split('/');
  239. var old_type = $("p", epStatusTool).text();
  240. var old_status = type_dict[old_type] || 0;
  241. if((old_status == 1 || old_status == 3) && (status != 1 && status != 3)) {
  242. progress[0] = Number(progress[0]) - 1;
  243. } else if((old_status != 1 && old_status != 3) && (status == 1 || status == 3)) {
  244. progress[0] = Number(progress[0]) + 1;
  245. }
  246. $("#prgsPercentNum", epGrid).text(`[${progress[0]}/${progress[1]}]`);
  247.  
  248. $(epStatusTool).empty();
  249. if(status == 0) {
  250. $(epStatusTool).append(`<p id="epBtnCu_${epid}"></p>`);
  251. } else {
  252. $(epStatusTool).append(`<p id="epBtnCu_${epid}" class="epBtnCu">${type}</p>`);
  253. }
  254. if(status != 1) {
  255. var watched = $(statusLink).clone(true);
  256. $(watched).attr({href: `/subject/ep/${epid}/status/watched?gh=${gh}`, id: `Watched_${epid}`});
  257. $(watched).text("看过")
  258. $(epStatusTool).append(watched);
  259.  
  260. var watchedTill = $(statusLink).clone(true);
  261. $(watchedTill).attr({href: `/subject/ep/${epid}/status/watched?gh=${gh}`, id: `WatchedTill_${epid}`});
  262. $(watchedTill).text("看到")
  263. $(epStatusTool).append(watchedTill);
  264. }
  265. if(status != 2) {
  266. var queue = $(statusLink).clone(true);
  267. $(queue).attr({href: `/subject/ep/${epid}/status/queue?gh=${gh}`, id: `Queue_${epid}`});
  268. $(queue).text("想看")
  269. $(epStatusTool).append(queue);
  270. }
  271. if(status != 3) {
  272. var drop = $(statusLink).clone(true);
  273. $(drop).attr({href: `/subject/ep/${epid}/status/drop?gh=${gh}`, id: `Drop_${epid}`});
  274. $(drop).text("抛弃")
  275. $(epStatusTool).append(drop);
  276. }
  277. if(status != 0) {
  278. var remove = $(statusLink).clone(true);
  279. $(remove).attr({href: `/subject/ep/${epid}/status/remove?gh=${gh}`, id: `remove_${epid}`});
  280. $(remove).text("撤消")
  281. $(epStatusTool).append(remove);
  282. }
  283. }
  284.  
  285.  
  286. /* 修改进度后实时修改面板状态 */
  287. var prgList = Array.from($("#subject_prg_content").children());
  288. var handleprgList = function(prg) {
  289. $(".epStatusTool a", prg).click((event) => {
  290. var type = event.currentTarget.innerText;
  291. if(type == '看到') {
  292. var offset = Number($(prg).attr("ep_offset"));
  293. var curI = $(prg).index();
  294. while(curI >= 0 && offset >= 0) {
  295. if (offset == Number($(prgList[curI]).attr("ep_offset"))) {
  296. if ($(".epStatusTool p", prgList[curI]).text() != '抛弃') {
  297. updataEpStatusTool(prgList[curI], "看过");
  298. }
  299. offset -= 1;
  300. }
  301. curI -= 1;
  302. }
  303. } else {
  304. updataEpStatusTool(prg, type);
  305. }
  306.  
  307. // 更新显示的面板
  308. var prg_new = $(prg).clone(true);
  309. $(prg_new).css("display", "block");
  310. $("#cluetip-inner").empty();
  311. $("#cluetip-inner").append(prg_new);
  312. return false;
  313. })
  314. }
  315.  
  316.  
  317. /* 功能运行进度指示文本 */
  318. class Task {
  319. constructor(func, ...args) {
  320. this.func = func;
  321. this.args = args;
  322. this.delayMillsec = 0;
  323. }
  324. delay(millsec) {
  325. if (millsec) this.delayMillsec = millsec;
  326. return this.delayMillsec;
  327. }
  328. execute() {
  329. return this.func(...this.args);
  330. }
  331. }
  332.  
  333. var taskList = [];
  334. titles_A.forEach(title => {
  335. taskList.push(new Task(handleTitle_A, title));
  336. });
  337. titles_B.forEach(title => {
  338. taskList.push(new Task(handleTitle_B, title));
  339. });
  340. epLinkList.forEach(epLink => {
  341. taskList.push(new Task(handleEpLinkList, epLink));
  342. });
  343. prgList.forEach(prg => {
  344. taskList.push(new Task(handleprgList, prg));
  345. });
  346.  
  347. var _progressUl = $('<ul style="display:inline-block; float:right; padding:10px"></ul>');
  348. var _progressSpan = $('<span style="vertical-align:middle;">处理中...0%</span>');
  349. if (taskList.length > 0) {
  350. $("#prgManagerHeader").append(_progressUl);
  351. $(_progressUl).append(_progressSpan);
  352. $(_progressSpan).attr("maxVal", taskList.length);
  353. $(_progressSpan).attr("curVal", 0);
  354. }
  355.  
  356. var incProgress = function(val = 1) {
  357. let maxVal = Number($(_progressSpan).attr("maxVal"));
  358. let curVal = Number($(_progressSpan).attr("curVal"));
  359. curVal = Math.max(0, Math.min(curVal + val, maxVal));
  360. let progress = Math.round(curVal / maxVal * 100);
  361. $(_progressSpan).attr("curVal", curVal);
  362. $(_progressSpan).text(`处理中...${progress}%`);
  363. if (curVal == maxVal) {
  364. setTimeout(() => {
  365. $(_progressUl).css("transition", "0.5s ease");
  366. $(_progressUl).css("color", "rgba(0, 0, 0, 0)");
  367. }, 1000);
  368. }
  369. }
  370.  
  371. var executor = function(taskList) {
  372. if (!taskList || taskList.length < 1) return;
  373. var task = taskList.shift();
  374. setTimeout(() => {
  375. task.execute();
  376. incProgress();
  377. executor(taskList);
  378. }, task.delay());
  379. }
  380. executor(taskList);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址