动漫弹幕播放

自动匹配加载动漫剧集对应弹幕并播放,目前支持樱花动漫、风车动漫

  1. // ==UserScript==
  2. // @name 动漫弹幕播放
  3. // @namespace https://github.com/LesslsMore/anime-danmu-play
  4. // @version 0.3.9
  5. // @author lesslsmore
  6. // @description 自动匹配加载动漫剧集对应弹幕并播放,目前支持樱花动漫、风车动漫
  7. // @license MIT
  8. // @icon https://cdn.yinghuazy.xyz/webjs/stui_tpl/statics/img/favicon.ico
  9. // @include /^https:\/\/www\.dmla.*\.com\/play\/.*$/
  10. // @include https://www.tt776b.com/play/*
  11. // @include https://www.dm539.com/play/*
  12. // @require https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.js
  13. // @require https://cdn.jsdelivr.net/npm/artplayer@5.1.1/dist/artplayer.js
  14. // @require https://cdn.jsdelivr.net/npm/artplayer-plugin-danmuku@5.0.1/dist/artplayer-plugin-danmuku.js
  15. // @require https://cdn.jsdelivr.net/npm/dexie@4.0.8/dist/dexie.min.js
  16. // @require https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js
  17. // @connect https://api.dandanplay.net/*
  18. // @connect https://danmu.yhdmjx.com/*
  19. // @connect http://v16m-default.akamaized.net/*
  20. // @connect self
  21. // @connect *
  22. // @grant GM_getValue
  23. // @grant GM_setValue
  24. // @grant GM_xmlhttpRequest
  25. // @run-at document-end
  26. // ==/UserScript==
  27.  
  28. (async function (CryptoJS, artplayerPluginDanmuku, Artplayer, saveAs, Dexie) {
  29. 'use strict';
  30.  
  31. (function() {
  32. var originalSetItem = localStorage.setItem;
  33. var originalRemoveItem = localStorage.removeItem;
  34. localStorage.setItem = function(key2, value) {
  35. var event = new Event("itemInserted");
  36. event.key = key2;
  37. event.value = value;
  38. document.dispatchEvent(event);
  39. originalSetItem.apply(this, arguments);
  40. };
  41. localStorage.removeItem = function(key2) {
  42. var event = new Event("itemRemoved");
  43. event.key = key2;
  44. document.dispatchEvent(event);
  45. originalRemoveItem.apply(this, arguments);
  46. };
  47. })();
  48. function get_anime_info() {
  49. let url2 = window.location.href;
  50. let episode2 = parseInt(url2.split("-").pop().split(".")[0]);
  51. let include = [
  52. /^https:\/\/www\.dmla.*\.com\/play\/.*$/,
  53. // 风车动漫
  54. "https://www.tt776b.com/play/*",
  55. // 风车动漫
  56. "https://www.dm539.com/play/*"
  57. // 樱花动漫
  58. ];
  59. let els = [
  60. document.querySelector(".stui-player__detail.detail > h1 > a"),
  61. document.querySelector("body > div.myui-player.clearfix > div > div > div.myui-player__data.hidden-xs.clearfix > h3 > a"),
  62. document.querySelector(".myui-panel__head.active.clearfix > h3 > a")
  63. ];
  64. let el;
  65. let title2;
  66. for (let i = 0; i < include.length; i++) {
  67. if (url2.match(include[i])) {
  68. el = els[i];
  69. }
  70. }
  71. if (el != void 0) {
  72. title2 = el.text;
  73. } else {
  74. title2 = "";
  75. console.log("没有自动匹配到动漫名称");
  76. }
  77. let anime_url = url2.split("-")[0];
  78. let anime_id2 = parseInt(anime_url.split("/")[4]);
  79. console.log({
  80. anime_id: anime_id2,
  81. episode: episode2,
  82. title: title2,
  83. url: url2
  84. });
  85. return {
  86. anime_id: anime_id2,
  87. episode: episode2,
  88. title: title2,
  89. url: url2
  90. };
  91. }
  92. function re_render(container) {
  93. let player = document.querySelector(".stui-player__video.clearfix");
  94. if (player == void 0) {
  95. player = document.querySelector("#player-left");
  96. }
  97. let div = player.querySelector("div");
  98. let h = div.offsetHeight;
  99. let w = div.offsetWidth;
  100. player.removeChild(div);
  101. let app = `<div style="height: ${h}px; width: ${w}px;" class="${container}"></div>`;
  102. player.innerHTML = app;
  103. }
  104. var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  105. var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  106. var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
  107. function xhr_get(url2) {
  108. return new Promise((resolve, reject) => {
  109. _GM_xmlhttpRequest({
  110. url: url2,
  111. method: "GET",
  112. headers: {},
  113. onload: function(xhr) {
  114. resolve(xhr.responseText);
  115. }
  116. });
  117. });
  118. }
  119. function request(opts) {
  120. let { url: url2, method, params } = opts;
  121. if (params) {
  122. let u = new URL(url2);
  123. Object.keys(params).forEach((key2) => {
  124. const value = params[key2];
  125. if (value !== void 0 && value !== null) {
  126. u.searchParams.set(key2, params[key2]);
  127. }
  128. });
  129. url2 = u.toString();
  130. }
  131. console.log("请求地址: ", url2);
  132. return new Promise((resolve, reject) => {
  133. _GM_xmlhttpRequest({
  134. url: url2,
  135. method: method || "GET",
  136. responseType: "json",
  137. onload: (res) => {
  138. resolve(res.response);
  139. },
  140. onerror: reject
  141. });
  142. });
  143. }
  144. let end_point = "https://lesslsmore-api.vercel.app/proxy";
  145. let API_comment = "/api/v2/comment/";
  146. let API_search_episodes = `/api/v2/search/episodes`;
  147. function get_episodeId(animeId, id) {
  148. id = id.toString().padStart(4, "0");
  149. let episodeId = `${animeId}${id}`;
  150. return episodeId;
  151. }
  152. async function get_search_episodes(anime, episode2) {
  153. const res = await request({
  154. url: `${end_point}${API_search_episodes}`,
  155. params: { anime, episode: episode2 }
  156. });
  157. return res.animes;
  158. }
  159. async function get_comment(episodeId) {
  160. const res = await request({
  161. url: `${end_point}${API_comment}${episodeId}?withRelated=true&chConvert=1`
  162. });
  163. return res.comments;
  164. }
  165. const key$1 = CryptoJS.enc.Utf8.parse("57A891D97E332A9D");
  166. const iv = CryptoJS.enc.Utf8.parse("844182a9dfe9c5ca");
  167. async function get_yhdmjx_url(url2) {
  168. let body = await xhr_get(url2);
  169. let m3u8 = get_m3u8_url(body);
  170. if (m3u8) {
  171. let body2 = await xhr_get(m3u8);
  172. let aes_data = get_encode_url(body2);
  173. if (aes_data) {
  174. let url3 = Decrypt(aes_data);
  175. let src = url3.split(".net/")[1];
  176. let src_url2 = `http://v16m-default.akamaized.net/${src}`;
  177. return src_url2;
  178. }
  179. }
  180. }
  181. function get_m3u8_url(data) {
  182. let regex = /"url":"([^"]+)","url_next":"([^"]+)"/g;
  183. const matches = data.match(regex);
  184. if (matches) {
  185. let play = JSON.parse(`{${matches[0]}}`);
  186. let m3u8 = `https://danmu.yhdmjx.com/m3u8.php?url=${play.url}`;
  187. console.log("m3u8", m3u8);
  188. return m3u8;
  189. } else {
  190. console.log("No matches found.");
  191. }
  192. }
  193. function get_encode_url(data) {
  194. let regex = /getVideoInfo\("([^"]+)"/;
  195. const matches = data.match(regex);
  196. if (matches) {
  197. return matches[1];
  198. } else {
  199. console.log("No matches found.");
  200. }
  201. }
  202. function Decrypt(srcs) {
  203. let decrypt = CryptoJS.AES.decrypt(srcs, key$1, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
  204. let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
  205. return decryptedStr.toString();
  206. }
  207. function update_danmu(art2, danmus) {
  208. art2.plugins.artplayerPluginDanmuku.config({
  209. danmuku: danmus
  210. });
  211. art2.plugins.artplayerPluginDanmuku.load();
  212. }
  213. function add_danmu(art2) {
  214. let plug = artplayerPluginDanmuku({
  215. danmuku: [],
  216. speed: 5,
  217. // 弹幕持续时间,单位秒,范围在[1 ~ 10]
  218. opacity: 1,
  219. // 弹幕透明度,范围在[0 ~ 1]
  220. fontSize: 25,
  221. // 字体大小,支持数字和百分比
  222. color: "#FFFFFF",
  223. // 默认字体颜色
  224. mode: 0,
  225. // 默认模式,0-滚动,1-静止
  226. margin: [10, "25%"],
  227. // 弹幕上下边距,支持数字和百分比
  228. antiOverlap: true,
  229. // 是否防重叠
  230. useWorker: true,
  231. // 是否使用 web worker
  232. synchronousPlayback: false,
  233. // 是否同步到播放速度
  234. filter: (danmu) => danmu.text.length < 50,
  235. // 弹幕过滤函数,返回 true 则可以发送
  236. lockTime: 5,
  237. // 输入框锁定时间,单位秒,范围在[1 ~ 60]
  238. maxLength: 100,
  239. // 输入框最大可输入的字数,范围在[0 ~ 500]
  240. minWidth: 200,
  241. // 输入框最小宽度,范围在[0 ~ 500],填 0 则为无限制
  242. maxWidth: 600,
  243. // 输入框最大宽度,范围在[0 ~ Infinity],填 0 则为 100% 宽度
  244. theme: "light",
  245. // 输入框自定义挂载时的主题色,默认为 dark,可以选填亮色 light
  246. heatmap: true,
  247. // 是否开启弹幕热度图, 默认为 false
  248. beforeEmit: (danmu) => !!danmu.text.trim()
  249. // 发送弹幕前的自定义校验,返回 true 则可以发送
  250. // 通过 mount 选项可以自定义输入框挂载的位置,默认挂载于播放器底部,仅在当宽度小于最小值时生效
  251. // mount: document.querySelector('.artplayer-danmuku'),
  252. });
  253. art2.plugins.add(plug);
  254. art2.on("artplayerPluginDanmuku:emit", (danmu) => {
  255. console.info("新增弹幕", danmu);
  256. });
  257. art2.on("artplayerPluginDanmuku:error", (error) => {
  258. console.info("加载错误", error);
  259. });
  260. art2.on("artplayerPluginDanmuku:config", (option) => {
  261. });
  262. }
  263. const db_name = "anime";
  264. const db_schema = {
  265. info: "&anime_id",
  266. // 主键 索引
  267. url: "&anime_id",
  268. // 主键 索引
  269. danmu: "[anime_id+episode_id]"
  270. // 组合键 索引
  271. };
  272. const db_obj = {
  273. [db_name]: get_db(db_name, db_schema)
  274. };
  275. const db_url = db_obj[db_name].url;
  276. const db_info = db_obj[db_name].info;
  277. const db_danmu = db_obj[db_name].danmu;
  278. function get_db(db_name2, db_schema2, db_ver = 1) {
  279. let db = new Dexie(db_name2);
  280. db.version(db_ver).stores(db_schema2);
  281. return db;
  282. }
  283. const db_url_put = db_url.put.bind(db_url);
  284. const db_url_get = db_url.get.bind(db_url);
  285. db_url.put = async function(key2, value, expiryInMinutes = 60) {
  286. const now = /* @__PURE__ */ new Date();
  287. const item = {
  288. anime_id: key2,
  289. value,
  290. expiry: now.getTime() + expiryInMinutes * 6e4
  291. };
  292. const result = await db_url_put(item);
  293. const event = new Event("db_yhdm_put");
  294. event.key = key2;
  295. event.value = value;
  296. document.dispatchEvent(event);
  297. return result;
  298. };
  299. db_url.get = async function(key2) {
  300. const item = await db_url_get(key2);
  301. const event = new Event("db_yhdm_get");
  302. event.key = key2;
  303. event.value = item ? item.value : null;
  304. document.dispatchEvent(event);
  305. if (!item) {
  306. return null;
  307. }
  308. const now = /* @__PURE__ */ new Date();
  309. if (now.getTime() > item.expiry) {
  310. await db_url.delete(key2);
  311. return null;
  312. }
  313. return item.value;
  314. };
  315. const db_info_put = db_info.put.bind(db_info);
  316. const db_info_get = db_info.get.bind(db_info);
  317. db_info.put = async function(key2, value, expiryInMinutes = 60 * 24 * 7) {
  318. const now = /* @__PURE__ */ new Date();
  319. const item = {
  320. anime_id: key2,
  321. value,
  322. expiry: now.getTime() + expiryInMinutes * 6e4
  323. };
  324. const result = await db_info_put(item);
  325. const event = new Event("db_info_put");
  326. event.key = key2;
  327. event.value = value;
  328. document.dispatchEvent(event);
  329. return result;
  330. };
  331. db_info.get = async function(key2) {
  332. const item = await db_info_get(key2);
  333. const event = new Event("db_info_get");
  334. event.key = key2;
  335. event.value = item ? item.value : null;
  336. document.dispatchEvent(event);
  337. if (!item) {
  338. return null;
  339. }
  340. const now = /* @__PURE__ */ new Date();
  341. if (now.getTime() > item.expiry) {
  342. await db_info.delete(key2);
  343. return null;
  344. }
  345. return item.value;
  346. };
  347. const db_danmu_put = db_danmu.put.bind(db_danmu);
  348. const db_danmu_get = db_danmu.get.bind(db_danmu);
  349. db_danmu.put = async function(anime_id2, episode_id, value, expiryInMinutes = 60 * 24 * 7) {
  350. const now = /* @__PURE__ */ new Date();
  351. const item = {
  352. anime_id: anime_id2,
  353. episode_id,
  354. value,
  355. expiry: now.getTime() + expiryInMinutes * 6e4
  356. };
  357. const result = await db_danmu_put(item);
  358. const event = new Event("db_danmu_put");
  359. event.key = key;
  360. event.value = value;
  361. document.dispatchEvent(event);
  362. return result;
  363. };
  364. db_danmu.get = async function(anime_id2, episode_id) {
  365. const key2 = { anime_id: anime_id2, episode_id };
  366. const item = await db_danmu_get(key2);
  367. const event = new Event("db_danmu_get");
  368. event.key = key2;
  369. event.value = item ? item.value : null;
  370. document.dispatchEvent(event);
  371. if (!item) {
  372. return null;
  373. }
  374. const now = /* @__PURE__ */ new Date();
  375. if (now.getTime() > item.expiry) {
  376. await db_danmu.delete(key2);
  377. return null;
  378. }
  379. return item.value;
  380. };
  381. function NewPlayer(src_url2, container) {
  382. var art2 = new Artplayer({
  383. container,
  384. url: src_url2,
  385. // autoplay: true,
  386. // muted: true,
  387. autoSize: true,
  388. fullscreen: true,
  389. fullscreenWeb: true,
  390. autoOrientation: true,
  391. flip: true,
  392. playbackRate: true,
  393. aspectRatio: true,
  394. setting: true,
  395. controls: [
  396. {
  397. position: "right",
  398. html: "上传",
  399. click: function() {
  400. const input = document.createElement("input");
  401. input.type = "file";
  402. input.accept = ".json, .xml";
  403. input.addEventListener("change", () => {
  404. const file = input.files[0];
  405. if (!file)
  406. return;
  407. const reader = new FileReader();
  408. reader.onload = () => {
  409. const content = reader.result;
  410. if (file.name.endsWith(".json")) {
  411. let json = JSON.parse(content);
  412. let comments;
  413. if (json.length === 1) {
  414. comments = json[0].comments;
  415. } else {
  416. comments = json;
  417. }
  418. const dm = bilibiliDanmuParseFromJson(comments);
  419. console.log("Parsed JSON danmaku:", dm);
  420. art2.plugins.artplayerPluginDanmuku.config({
  421. danmuku: dm
  422. });
  423. art2.plugins.artplayerPluginDanmuku.load();
  424. } else if (file.name.endsWith(".xml")) {
  425. const dm = bilibiliDanmuParseFromXml(content);
  426. console.log("Parsed XML danmaku:", dm);
  427. art2.plugins.artplayerPluginDanmuku.config({
  428. danmuku: dm
  429. });
  430. art2.plugins.artplayerPluginDanmuku.load();
  431. } else {
  432. console.error("Unsupported file format. Please upload a .json or .xml file.");
  433. }
  434. };
  435. reader.readAsText(file);
  436. });
  437. input.click();
  438. }
  439. },
  440. {
  441. position: "right",
  442. html: "下载",
  443. click: async function() {
  444. let $episodes2 = document.querySelector("#episodes");
  445. const episodeId = $episodes2.value;
  446. let { anime_id: anime_id2, episode: episode2, title: title2, url: url2 } = get_anime_info();
  447. let danmu = await db_danmu.get(anime_id2, episodeId);
  448. const blob = new Blob([JSON.stringify(danmu)], { type: "text/plain;charset=utf-8" });
  449. saveAs(blob, `${title2} - ${episode2}.json`);
  450. }
  451. }
  452. ],
  453. contextmenu: [
  454. {
  455. name: "搜索",
  456. html: `<div id="k-player-danmaku-search-form">
  457. <label>
  458. <span>搜索番剧名称</span>
  459. <input type="text" id="animeName" class="k-input" />
  460. </label>
  461. <div style="min-height:24px; padding-top:4px">
  462. <span id="tips"></span>
  463. </div>
  464. <label>
  465. <span>番剧名称</span>
  466. <select id="animes" class="k-select"></select>
  467. </label>
  468. <label>
  469. <span>章节</span>
  470. <select id="episodes" class="k-select"></select>
  471. </label>
  472. <label>
  473. <span class="open-danmaku-list">
  474. <span>弹幕列表</span><small id="count"></small>
  475. </span>
  476. </label>
  477. <span class="specific-thanks">弹幕服务由 弹弹play 提供</span>
  478. </div>`
  479. }
  480. ]
  481. });
  482. return art2;
  483. }
  484. function getMode(key2) {
  485. switch (key2) {
  486. case 1:
  487. case 2:
  488. case 3:
  489. return 0;
  490. case 4:
  491. case 5:
  492. return 1;
  493. default:
  494. return 0;
  495. }
  496. }
  497. function bilibiliDanmuParseFromXml(xmlString) {
  498. if (typeof xmlString !== "string")
  499. return [];
  500. const matches = xmlString.matchAll(/<d (?:.*? )??p="(?<p>.+?)"(?: .*?)?>(?<text>.+?)<\/d>/gs);
  501. return Array.from(matches).map((match) => {
  502. const attr = match.groups.p.split(",");
  503. if (attr.length >= 8) {
  504. const text = match.groups.text.trim().replaceAll("&quot;", '"').replaceAll("&apos;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
  505. return {
  506. text,
  507. time: Number(attr[0]),
  508. mode: getMode(Number(attr[1])),
  509. fontSize: Number(attr[2]),
  510. color: `#${Number(attr[3]).toString(16)}`,
  511. timestamp: Number(attr[4]),
  512. pool: Number(attr[5]),
  513. userID: attr[6],
  514. rowID: Number(attr[7])
  515. };
  516. } else {
  517. return null;
  518. }
  519. }).filter(Boolean);
  520. }
  521. function bilibiliDanmuParseFromJson(jsonString) {
  522. return jsonString.map((comment) => {
  523. let attr = comment.p.split(",");
  524. return {
  525. text: comment.m,
  526. time: Number(attr[0]),
  527. mode: getMode(Number(attr[1])),
  528. fontSize: Number(25),
  529. color: `#${Number(attr[2]).toString(16)}`,
  530. timestamp: Number(comment.cid),
  531. pool: Number(0),
  532. userID: attr[3],
  533. rowID: Number(0)
  534. };
  535. });
  536. }
  537. function createStorage(storage) {
  538. function getItem(key2, defaultValue) {
  539. try {
  540. const value = storage.getItem(key2);
  541. if (value)
  542. return JSON.parse(value);
  543. return defaultValue;
  544. } catch (error) {
  545. return defaultValue;
  546. }
  547. }
  548. return {
  549. getItem,
  550. setItem(key2, value) {
  551. storage.setItem(key2, JSON.stringify(value));
  552. },
  553. removeItem: storage.removeItem.bind(storage),
  554. clear: storage.clear.bind(storage)
  555. };
  556. }
  557. createStorage(window.sessionStorage);
  558. const local = createStorage(window.localStorage);
  559. let gm;
  560. try {
  561. gm = { getItem: _GM_getValue, setItem: _GM_setValue };
  562. } catch (error) {
  563. gm = local;
  564. }
  565. let { anime_id, episode, title, url } = get_anime_info();
  566. let db_anime_url = {
  567. "episodes": {}
  568. };
  569. let db_url_value = await( db_url.get(anime_id));
  570. if (db_url_value != null) {
  571. db_anime_url = db_url_value;
  572. }
  573. let src_url;
  574. if (!db_anime_url["episodes"].hasOwnProperty(url)) {
  575. src_url = await( get_yhdmjx_url(url));
  576. if (src_url) {
  577. db_anime_url["episodes"][url] = src_url;
  578. db_url.put(anime_id, db_anime_url);
  579. }
  580. } else {
  581. src_url = db_anime_url["episodes"][url];
  582. }
  583. let db_anime_info = {
  584. "animes": [{ "animeTitle": title }],
  585. "idx": 0,
  586. "episode_dif": 0
  587. };
  588. let db_info_value = await( db_info.get(anime_id));
  589. if (db_info_value != null) {
  590. db_anime_info = db_info_value;
  591. } else {
  592. db_info.put(anime_id, db_anime_info);
  593. }
  594. console.log("db_anime_info", db_anime_info);
  595. console.log("src_url", src_url);
  596. re_render("artplayer-app");
  597. let art = NewPlayer(src_url, ".artplayer-app");
  598. add_danmu(art);
  599. let $count = document.querySelector("#count");
  600. let $animeName = document.querySelector("#animeName");
  601. let $animes = document.querySelector("#animes");
  602. let $episodes = document.querySelector("#episodes");
  603. function art_msgs(msgs) {
  604. art.notice.show = msgs.join(",\n\n");
  605. }
  606. let UNSEARCHED = ["未搜索到番剧弹幕", "请按右键菜单", "手动搜索番剧名称"];
  607. let SEARCHED = () => {
  608. try {
  609. return [`番剧:${$animes.options[$animes.selectedIndex].text}`, `章节: ${$episodes.options[$episodes.selectedIndex].text}`, `已加载 ${$count.textContent} 条弹幕`];
  610. } catch (e) {
  611. console.log(e);
  612. return [];
  613. }
  614. };
  615. init();
  616. get_animes();
  617. async function update_episode_danmu() {
  618. const new_idx = $episodes.selectedIndex;
  619. const db_anime_info2 = await db_info.get(anime_id);
  620. const { episode_dif } = db_anime_info2;
  621. let dif = new_idx + 1 - episode;
  622. if (dif !== episode_dif) {
  623. db_anime_info2["episode_dif"] = dif;
  624. db_info.put(anime_id, db_anime_info2);
  625. }
  626. const episodeId = $episodes.value;
  627. console.log("episodeId: ", episodeId);
  628. let danmu;
  629. try {
  630. danmu = await get_comment(episodeId);
  631. await db_danmu.put(anime_id, episodeId, danmu);
  632. } catch (error) {
  633. console.log("接口请求失败,尝试使用缓存数据");
  634. danmu = await db_danmu.get(anime_id, episodeId);
  635. if (!danmu) {
  636. throw new Error("无法获取弹幕数据");
  637. }
  638. }
  639. let danmus = bilibiliDanmuParseFromJson(danmu);
  640. update_danmu(art, danmus);
  641. }
  642. function get_animes() {
  643. const { animes, idx } = db_anime_info;
  644. const { animeTitle } = animes[idx];
  645. if (!animes[idx].hasOwnProperty("animeId")) {
  646. console.log("没有缓存,请求接口");
  647. get_animes_new(animeTitle);
  648. } else {
  649. console.log("有缓存,请求弹幕");
  650. updateAnimes(animes, idx);
  651. }
  652. }
  653. async function get_animes_new(title2) {
  654. try {
  655. const animes = await get_search_episodes(title2);
  656. if (animes.length === 0) {
  657. art_msgs(UNSEARCHED);
  658. } else {
  659. db_anime_info["animes"] = animes;
  660. db_info.put(anime_id, db_anime_info);
  661. }
  662. return animes;
  663. } catch (error) {
  664. console.log("弹幕服务异常,稍后再试");
  665. }
  666. }
  667. function init() {
  668. art.on("artplayerPluginDanmuku:loaded", (danmus) => {
  669. console.info("加载弹幕", danmus.length);
  670. $count.textContent = danmus.length;
  671. if ($count.textContent === "") {
  672. art_msgs(UNSEARCHED);
  673. } else {
  674. art_msgs(SEARCHED());
  675. }
  676. });
  677. art.on("pause", () => {
  678. if ($count.textContent === "") {
  679. art_msgs(UNSEARCHED);
  680. } else {
  681. art_msgs(SEARCHED());
  682. }
  683. });
  684. $animeName.addEventListener("keypress", (e) => {
  685. if (e.key === "Enter") {
  686. get_animes_new($animeName.value);
  687. }
  688. });
  689. $animeName.addEventListener("blur", () => {
  690. get_animes_new($animeName.value);
  691. });
  692. $animeName.value = db_anime_info["animes"][db_anime_info["idx"]]["animeTitle"];
  693. $animes.addEventListener("change", async () => {
  694. const new_idx = $animes.selectedIndex;
  695. const { idx, animes } = db_anime_info;
  696. if (new_idx !== idx) {
  697. db_anime_info["idx"] = new_idx;
  698. db_info.put(anime_id, db_anime_info);
  699. updateEpisodes(animes[new_idx]);
  700. }
  701. });
  702. $episodes.addEventListener("change", update_episode_danmu);
  703. document.addEventListener("db_info_put", async function(e) {
  704. let { animes: old_animes } = await db_info.get(anime_id);
  705. let { animes: new_animes, idx: new_idx } = e.value;
  706. if (new_animes !== old_animes) {
  707. updateAnimes(new_animes, new_idx);
  708. }
  709. });
  710. document.addEventListener("updateAnimes", function(e) {
  711. console.log("updateAnimes 事件");
  712. updateEpisodes(e.value);
  713. });
  714. document.addEventListener("updateEpisodes", function(e) {
  715. console.log("updateEpisodes 事件");
  716. update_episode_danmu();
  717. });
  718. }
  719. function updateAnimes(animes, idx) {
  720. const html = animes.reduce((html2, anime) => html2 + `<option value="${anime.animeId}">${anime.animeTitle}</option>`, "");
  721. $animes.innerHTML = html;
  722. $animes.value = animes[idx]["animeId"];
  723. const event = new Event("updateAnimes");
  724. event.value = animes[idx];
  725. console.log(animes[idx]);
  726. document.dispatchEvent(event);
  727. }
  728. async function updateEpisodes(anime) {
  729. const { animeId, episodes } = anime;
  730. const html = episodes.reduce((html2, episode2) => html2 + `<option value="${episode2.episodeId}">${episode2.episodeTitle}</option>`, "");
  731. $episodes.innerHTML = html;
  732. const db_anime_info2 = await db_info.get(anime_id);
  733. const { episode_dif } = db_anime_info2;
  734. let episodeId = get_episodeId(animeId, episode_dif + episode);
  735. $episodes.value = episodeId;
  736. const event = new Event("updateEpisodes");
  737. document.dispatchEvent(event);
  738. }
  739.  
  740. })(CryptoJS, artplayerPluginDanmuku, Artplayer, saveAs, Dexie);

QingJ © 2025

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