七象影视解析

优酷、爱奇艺、腾讯、B站等视频网站视频解析,悬浮面板

目前為 2025-03-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 七象影视解析
  3. // @namespace qx-parse
  4. // @version 0.0.1
  5. // @description 优酷、爱奇艺、腾讯、B站等视频网站视频解析,悬浮面板
  6. // @author 通天教主
  7. // @icon data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDMyIDMyIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPgoJPHRpdGxlPmJyb3dzZXItY2hyb21lPC90aXRsZT4KCTxzdHlsZT4KCQkuczAgeyBmaWxsOiAjYWZjZGZmIH0gCgkJLnMxIHsgZmlsbDogIzM4ODNmZiB9IAoJPC9zdHlsZT4KCTxwYXRoIGlkPSLlm77lsYIgNCIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGFzcz0iczAiIGQ9Im02IDBoMjBjMy4zIDAgNiAyLjcgNiA2djIwYzAgMy4zLTIuNyA2LTYgNmgtMjBjLTMuMyAwLTYtMi43LTYtNnYtMjBjMC0zLjMgMi43LTYgNi02eiIvPgoJPHBhdGggaWQ9IuW9oueKtiAxIiBjbGFzcz0iczEiIGQ9Im0yMiAxNy41YzAuNS0wLjcgMC41LTEuOSAwLTIuN2wtNy40LTYuMmMtMC42LTAuNS0xLjUtMC42LTIuMy0wLjMtMC44IDAuMi0xLjMgMC44LTEuMyAxLjR2MTIuOWMwIDAuNyAwLjUgMS4zIDEuMyAxLjUgMC44IDAuMyAxLjcgMC4xIDIuMy0wLjMgMCAwIDcuNC02LjMgNy40LTYuM3oiLz4KPC9zdmc+
  8. // @match *://*.youku.com/*
  9. // @match *://*.iqiyi.com/v_*
  10. // @match *://*.iqiyi.com/w_*
  11. // @match *://*.iqiyi.com/a_*
  12. // @match *://*.iqiyi.com/resource/pcw/play/*
  13. // @match *://*.iq.com/*
  14. // @match *://v.qq.com/x/cover/*
  15. // @match *://v.qq.com/x/page/*
  16. // @match *://v.qq.com/tv/*
  17. // @match *://m.v.qq.com/x/cover/*
  18. // @match *://m.v.qq.com/x/page/*
  19. // @match *://m.v.qq.com/*
  20. // @match *://*.bilibili.com/**
  21. // @match *://*.mgtv.com/b/*
  22. // @match *://*.le.com/ptv/vplay/*
  23. // @match *://*.tudou.com/listplay/*
  24. // @match *://*.tudou.com/albumplay/*
  25. // @match *://*.tudou.com/programs/view/*
  26. // @match *://*.pptv.com/show/*
  27. // @match *://*.1905.com/video/*
  28. // @match *://*.1905.com/play/*
  29. // @match *://*.1905.com/*/play/*
  30. // @grant GM_getValue
  31. // @grant GM_setValue
  32. // @grant GM_registerMenuCommand
  33. // @license GPLv3
  34. // ==/UserScript==
  35.  
  36. (function () {
  37. "use strict";
  38.  
  39. const isMobile = /Android|webOS|iPhone|iPod|BlackBerry/i.test(
  40. navigator.userAgent
  41. );
  42.  
  43. const parseApiListStr = GM_getValue("parseApiList", "[]");
  44.  
  45. let parseApiList = JSON.parse(parseApiListStr);
  46.  
  47. function parseApi2Text(parseApiList) {
  48. let texts = [];
  49. parseApiList.forEach((api) => {
  50. let line = api.name + "," + api.url;
  51. texts.push(line);
  52. });
  53. return texts.join("\n");
  54. }
  55.  
  56. function text2ParseApi(text) {
  57. let lines = text.split("\n");
  58. let apis = [];
  59. lines.forEach((line) => {
  60. let res = line.split(",");
  61. if (res.length !== 2) {
  62. if (res.length === 1) {
  63. res.unshift(getSLDFromUrl(res[0]));
  64. } else return;
  65. }
  66. let [name, url] = res;
  67. apis.push({
  68. name: name.trim(),
  69. url: url.trim(),
  70. });
  71. });
  72. return apis;
  73. }
  74.  
  75. let parseBtns = [];
  76.  
  77. function setParseBtns() {
  78. const singleParsePanel = document.getElementById("qxjfal-singleParsePanel");
  79. parseBtns.forEach((btn) => singleParsePanel.removeChild(btn));
  80. parseBtns = [];
  81. parseApiList.forEach((api, index) => {
  82. const parseBtn = document.createElement("button");
  83. parseBtn.textContent = api.name;
  84. parseBtn.title = api.name + " " + api.url;
  85. if (index === fastUrlIndex) {
  86. parseBtn.style.backgroundColor = "#ff9999";
  87. }
  88. parseBtn.addEventListener("click", () => {
  89. fastUrlIndex = index;
  90. GM_setValue("fastUrlIndex", index);
  91. parseBtns.forEach((btn) => {
  92. btn.style.backgroundColor = "";
  93. });
  94. parseBtns[index].style.backgroundColor = "#ff9999";
  95. const fastbootBtn = document.getElementById("qxjfal-fastboot");
  96. fastbootBtn.title = `快速开始(当前所选接口:${api.name})`;
  97. parseVideo(api.url, showMode);
  98. });
  99. parseBtns.push(parseBtn);
  100. singleParsePanel.appendChild(parseBtn);
  101. });
  102. }
  103.  
  104. function openSettingPanel() {
  105. const settingPanel = document.createElement("div");
  106. settingPanel.id = "qxjfal-setting-panel";
  107. const settingHtml = `
  108. <div class='qxjfal-setting-panel-header'>
  109. <div class='qxjfal-setting-panel-title'>设置自定义解析接口</div>
  110. <div class='qxjfal-setting-panel-closebtn'>X</div>
  111. </div>
  112. <div style='padding: 15px;'>
  113. <div>
  114. <p>自定义解析接口
  115. </p>
  116. <p>数据格式:[名字] + [,] + [接口地址]</p>
  117. <p>例如:名字,https://xxxxxx?url=</p>
  118. <p>一行一个自定义接口,如果不提供名字,则自动将二级域名作为名字
  119. </p>
  120. </div>
  121. <div>
  122. <textarea class="qxjfal-setting-panel-textarea" rows="10" cols="50"></textarea>
  123. </div>
  124. <div>
  125. <button class="qxjfal-setting-savebtn">保存</button>
  126. </div>
  127. </div>
  128. `;
  129. settingPanel.innerHTML = settingHtml;
  130. const settingPanelHeader = settingPanel.querySelector(
  131. ".qxjfal-setting-panel-header"
  132. );
  133. makeDraggable({
  134. element: settingPanel,
  135. handle: settingPanelHeader,
  136. enableX: true,
  137. enableY: true,
  138. });
  139. const settingPanelCloseBtn = settingPanel.querySelector(
  140. ".qxjfal-setting-panel-closebtn"
  141. );
  142. settingPanelCloseBtn.addEventListener("click", () => {
  143. document.body.removeChild(settingPanel);
  144. });
  145. const settingPanelTextarea = settingPanel.querySelector(
  146. ".qxjfal-setting-panel-textarea"
  147. );
  148. settingPanelTextarea.value = parseApi2Text(parseApiList);
  149. const settingSaveBtn = settingPanel.querySelector(
  150. ".qxjfal-setting-savebtn"
  151. );
  152. settingSaveBtn.addEventListener("click", () => {
  153. parseApiList = text2ParseApi(settingPanelTextarea.value);
  154. GM_setValue("parseApiList", JSON.stringify(parseApiList));
  155. setParseBtns();
  156. settingPanelCloseBtn.click();
  157. });
  158. document.body.appendChild(settingPanel);
  159. }
  160.  
  161. const parseVideoAgainLater = () => {
  162. setTimeout(parseVideoAgain, 1000);
  163. };
  164.  
  165. // 网站与解析规则的映射
  166. const siteRules = {
  167. "v.qq.com": {
  168. node: [".player__container", "#player-container"],
  169. area: "playlist-list",
  170. },
  171. "iqiyi.com": { node: ["#video"], area: "" },
  172. "iq.com": { node: [".intl-video-wrap"], area: "m-sliding-list" },
  173. "youku.com": { node: ["#ykPlayer"], area: "new-box-anthology-items" },
  174. "bilibili.com": {
  175. node: ["#bilibili-player", ".bpx-player-primary-area"],
  176. area: "video-episode-card",
  177. },
  178. "mgtv.com": { node: ["#mgtv-player-wrap"], area: "episode-items" },
  179. "le.com": { node: ["#le_playbox"], area: "juji_grid" },
  180. "tudou.com": { node: ["#player"], area: "" },
  181. "pptv.com": { node: ["#pptv_playpage_box"], area: "" },
  182. "1905.com": { node: ["#player", "#vodPlayer"], area: "" },
  183. };
  184.  
  185. let floatVideoContainer = null;
  186. let originalVideoContainer = null;
  187. let originalVideoContainerSelector = null;
  188. let currentIframeContainer = null;
  189. let distanceTop = null;
  190. let distanceLeft = null;
  191. let videoContainerWidth = null;
  192. let videoContainerHeight = null;
  193. let hidePanelTimeout = null; // 隐藏面板的定时器
  194. let lastUrl = "";
  195. let parsed = false;
  196. let lastWindow = null;
  197. let parseAutoPause = GM_getValue("parseAutoPause", true);
  198. let parseAutoMute = GM_getValue("parseAutoMute", true);
  199. let showMode = GM_getValue("showMode", "emb"); // 1 为悬浮播放,2 为新窗口,3 为新标签页
  200. let fastUrlIndex = GM_getValue("fastUrlIndex", -1);
  201.  
  202. function getSiteRule(host) {
  203. return (
  204. siteRules[Object.keys(siteRules).find((key) => host.includes(key))] ||
  205. null
  206. );
  207. }
  208.  
  209. function getDomainFromUrl(url) {
  210. let domain;
  211. try {
  212. let parsedUrl = new URL(url);
  213. domain = parsedUrl.hostname;
  214. } catch (error) {
  215. console.error("Invalid URL", error);
  216. }
  217. return domain;
  218. }
  219.  
  220. function getSLDFromUrl(url) {
  221. const domain = getDomainFromUrl(url);
  222. const domainLs = domain.split(".");
  223. if (domainLs.length >= 2) {
  224. return domainLs[domainLs.length - 2];
  225. } else {
  226. return "😊";
  227. }
  228. }
  229.  
  230. function createParseElements() {
  231. const iconSize = isMobile ? 30 : GM_getValue("iconWidth", 24);
  232. const iconTop = isMobile ? 360 : GM_getValue("iconTop", 100);
  233. const iconPosition = isMobile
  234. ? "left"
  235. : GM_getValue("iconPosition", "left");
  236.  
  237. const iconStyle = `
  238. #qxjfal-iconContainer {
  239. background-color: #fff;
  240. border: 1px solid #ccc;
  241. border-radius: 6px;
  242. padding: 0px;
  243. text-align: center;
  244. opacity: ${isMobile ? 1 : GM_getValue("iconOpacity", 100) / 100};
  245. width: ${iconSize}px;
  246. box-sizing: border-box;
  247. opacity: 0.5;
  248. /* transition: 0.1s; */
  249. }
  250. #qxjfal-iconContainer:hover {
  251. opacity: 1;
  252. }
  253. #qxjfal-optionIcons {
  254. cursor: pointer;
  255. }
  256. #qxjfal-optionIcons>div {
  257. padding: 6px 0px;
  258. }
  259. #qxjfal-container {
  260. position: fixed;
  261. top: ${iconTop}px;
  262. ${iconPosition}: 0px;
  263. z-index: 999999;
  264. display: flex;
  265. flex-direction: ${iconPosition === "left" ? "row" : "row-reverse"};
  266. }
  267. #qxjfal-dragIcon {
  268. cursor: move;
  269. }
  270. #qxjfal-dragIcon:hover {
  271. transform: scale(1.2);
  272. }
  273. #qxjfal-fastboot:hover {
  274. transform: scale(1.2);
  275. }
  276. #qxjfal-vidParseIcon:hover {
  277. transform: scale(1.2);
  278. }
  279.  
  280. #qxjfal-parsePanel {
  281. position: fixed; /* 绝对定位 */
  282. top: 0px; /* 图标高度+5px的间距*/
  283. ${
  284. iconPosition === "left" ? "left" : "right"
  285. }: ${iconSize}px; /* 根据图标位置调整 */
  286. z-index: 999998;
  287. background-color: #fff;
  288. border: 1px solid #ccc;
  289. padding: 12px 15px;
  290. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
  291. border-radius: 6px;
  292. width: 310px; /* 调整面板宽度 */
  293. height: 100vh;
  294. overflow: auto;
  295. display: none; /* 初始隐藏 */
  296. box-sizing: border-box;
  297. }
  298.  
  299. #qxjfal-parsePanel button, #qxjfal-setting-panel button {
  300. margin: 3px 0;
  301. padding: 8px 18px;
  302. background-color: #285aa6;
  303. color: #fff;
  304. border: none;
  305. border-radius: 4px;
  306. cursor: pointer;
  307. transition: background-color 0.3s;
  308. width: 100%;
  309. box-sizing: border-box;
  310. }
  311. #qxjfal-parsePanel button:hover {
  312. background-color: #1e4888;
  313. }
  314. #qxjfal-parsePanel * {
  315. color: #333333;
  316. }
  317.  
  318. #qxjfal-configPanel {
  319. margin-top: 15px;
  320. padding-top: 10px;
  321. border-top: 1px solid #eee;
  322. }
  323.  
  324. #qxjfal-configPanel label {
  325. display: block;
  326. margin-bottom: 8px;
  327. color: #333;
  328. }
  329. #qxjfal-configPanel input[type="radio"] {
  330. margin-right: 6px;
  331. }
  332.  
  333. #qxjfal-saveConfigBtn {
  334. background-color: #4CAF50 !important;
  335. }
  336. #qxjfal-saveConfigBtn:hover {
  337. background-color: #45a049 !important;
  338. }
  339.  
  340. #qxjfal-aboutPanel, #qxjfal-singleParsePanel {
  341. margin: 3px 0;
  342. padding: 15px;
  343. background-color: #f8f9fa;
  344. border-radius: 4px;
  345. }
  346.  
  347. #qxjfal-aboutPanel h4 {
  348. margin-top: 0;
  349. color: #333333;
  350. }
  351.  
  352. #qxjfal-aboutPanel p {
  353. color: #333333;
  354. line-height: 1.6;
  355. }
  356.  
  357. #qxjfal-singleParsePanel {
  358. padding: 6px;
  359. max-height: 300px;
  360. overflow: auto;
  361. }
  362.  
  363. #qxjfal-singleParsePanel button {
  364. padding: 6px 8px;
  365. width: 60px;
  366. overflow: hidden;
  367. text-overflow: ellipsis;
  368. border-radius: 6px;
  369. text-wrap: nowrap;
  370. background-color: #ffffff;
  371. color: #333333;
  372. margin: 3px;
  373. }
  374.  
  375. #qxjfal-singleParsePanel button:hover {
  376. background-color: #dfeffd;
  377. }
  378.  
  379. #qxjfal-telegramLink {
  380. color: #007bff;
  381. text-decoration: underline;
  382. cursor: pointer;
  383. }
  384.  
  385. #qxjfal-showmode-select {
  386. border: 1px solid #999;
  387. padding: 4px 10px;
  388. border-radius: 4px;
  389. margin: 3px 0;
  390. }
  391.  
  392. #qxjfal-parse-autopause {
  393. margin: 3px 0;
  394. }
  395.  
  396. #qxjfal-parse-automute {
  397. margin: 3px 0;
  398. }
  399.  
  400. /* ... 其他样式保持不变 ... */
  401. #qxjfal-float-video-container {
  402. position: absolute;
  403. top: 0;
  404. left: 0;
  405. width: 200px;
  406. height: 100px;
  407. z-index: 999997;
  408. display: flex;
  409. flex-direction: column;
  410. }
  411.  
  412. .qxjfal-video-top-handle {
  413. background: #333333;
  414. width: 100%;
  415. box-sizing: border-box;
  416. padding: 4px;
  417. }
  418.  
  419. .qxjfal-video-expand-handle {
  420. user-select: none;
  421. width: 50px;
  422. box-sizing: border-box;
  423. padding: 4px 6px;
  424. text-align: center;
  425. cursor: pointer;
  426. }
  427.  
  428. .qxjfal-video-drag-handle {
  429. text-align: center;
  430. box-sizing: border-box;
  431. padding: 4px 6px;
  432. width: 50px;
  433. background: #333333;
  434. cursor: move;
  435. }
  436. .qxjfal-video-drag-title {
  437. color: #fff;
  438. display: none;
  439. }
  440.  
  441. .qxjfal-iframe-container {
  442. flex: 1;
  443. display: grid;
  444. grid-template-columns: repeat(3, 1fr);
  445. grid-template-rows: repeat(2, auto);
  446. grid-auto-rows: minmax(200px, auto);
  447. grid-gap: 1px;
  448. width: 100%;
  449. height: 100%;
  450. }
  451. .qxjfal-one-chunk {
  452. grid-template-columns: repeat(1, 1fr);
  453. grid-template-rows: repeat(1, auto);
  454. }
  455. .qxjfal-one-chunk .qxjfal-iframe-option {
  456. display: none;
  457. }
  458. .qxjfal-four-chunk {
  459. grid-template-columns: repeat(2, 1fr);
  460. grid-template-rows: repeat(2, auto);
  461. }
  462. .qxjfal-six-chunk {
  463. grid-template-columns: repeat(3, 1fr);
  464. grid-template-rows: repeat(2, auto);
  465. }
  466. .qxjfal-iframe-container iframe {
  467. flex: 1;
  468. border: 1px solid #ddd;
  469. }
  470. /* 可选:添加响应式设计 */
  471. @media (max-width: 768px) {
  472. .qxjfal-iframe-container {
  473. grid-template-columns: repeat(2, 1fr); /* 在小屏幕上显示两列 */
  474. }
  475. }
  476.  
  477. @media (max-width: 480px) {
  478. .qxjfal-iframe-container {
  479. grid-template-columns: 1fr; /* 在非常小的屏幕上显示一列 */
  480. }
  481. }
  482.  
  483. .qxjfal-iframe-wrapper {
  484. display: flex;
  485. flex-direction: column;
  486. items-align: stretch;
  487. }
  488.  
  489. .qxjfal-iframe-wrapper button {
  490. margin: 0;
  491. padding: 2px 6px;
  492. background-color: #2871a6;
  493. color: #fff;
  494. border: none;
  495. border-radius: 4px;
  496. cursor: pointer;
  497. transition: background-color 0.3s;
  498. box-sizing: border-box;
  499. }
  500.  
  501. .qxjfal-iframe-option {
  502. display: flex;
  503. column-gap: 4px;
  504. background-color: #777777;
  505. color: white;
  506. text-align: center;
  507. padding: 4px;
  508. transition: 0.3s;
  509. }
  510. .qxjfal-expand-button {
  511. flex: 1;
  512. }
  513. .qxjfal-eliminate-button {
  514. background-color: #333333 !important;
  515. flex: 1;
  516. }
  517.  
  518. #qxjfal-setting-panel {
  519. font-size: 14px;
  520. position: fixed;
  521. top: 0;
  522. ${iconPosition === "left" ? "left" : "right"}: 0px;
  523. margin: 0 auto;
  524. max-height: 100%;
  525. width: 100%;
  526. max-width: 500px;
  527. background-color: #ffffff;
  528. border-radius: 6px;
  529. overflow: auto;
  530. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
  531. z-index: 999999;
  532. box-sizing: border-box;
  533. }
  534.  
  535. .qxjfal-setting-panel-header {
  536. padding: 4px 10px;
  537. text-align: center;
  538. background-color: #efefef;
  539. }
  540.  
  541. .qxjfal-setting-panel-title {
  542. display: inline-block;
  543. font-weight: bold;
  544. user-select: none;
  545. }
  546. .qxjfal-setting-panel-closebtn {
  547. font-size: 1.2em;
  548. line-height: 1.2em;
  549. vertical-align: middle;
  550. float: right;
  551. cursor: pointer;
  552. }
  553. .qxjfal-setting-panel-textarea {
  554. width: 99%;
  555. margin: 0 auto;
  556. }
  557. `;
  558.  
  559. const styleEl = document.createElement("style");
  560. styleEl.textContent = iconStyle;
  561. document.head.appendChild(styleEl);
  562.  
  563. const iconHtml = `
  564. <div id="qxjfal-iconContainer">
  565. <div id="qxjfal-optionIcons">
  566. <div id="qxjfal-dragIcon" title="拖拽调整位置">Ⓜ️</div>
  567. <div id="qxjfal-fastboot" title="快速开始(当前所选接口:${
  568. fastUrlIndex === -1 ? "无" : parseApiList[fastUrlIndex].name
  569. })">🩷</div>
  570. <div id="qxjfal-vidParseIcon" title="解析">✨</div>
  571. </div>
  572. </div>
  573. <div id="qxjfal-parsePanel">
  574. <div>
  575. <button id="qxjfal-parseBtn">👉解析</button>
  576. <button id="qxjfal-restoreBtn" style="background:#5a6268;">还原</button>
  577. </div>
  578. <div id="qxjfal-singleParsePanel">
  579. </div>
  580. <div>
  581. <select id="qxjfal-showmode-select" name="showmode">
  582. <option value="emb">当前页面中</option>
  583. <option value="win">新窗口打开</option>
  584. <option value="tab">新标签打开</option>
  585. </select>
  586. </div>
  587. <div>
  588. <label><input type="checkbox" id="qxjfal-parse-autopause" ${
  589. parseAutoPause ? "checked" : ""
  590. }> 解析时自动暂停原视频(部分网站可能无法成功暂停)</label>
  591. </div>
  592. <div>
  593. <label><input type="checkbox" id="qxjfal-parse-automute" ${
  594. parseAutoMute ? "checked" : ""
  595. }> 解析时自动静音原视频</label>
  596. </div>
  597. <div id="qxjfal-configPanel">
  598. <label><input type="radio" name="qxjfal-iframeCount" value="6"> 6个格子解析</label>
  599. <label><input type="radio" name="qxjfal-iframeCount" value="4"> 4个格子解析</label>
  600. <label><input type="radio" name="qxjfal-iframeCount" value="1"> 1个格子解析</label>
  601. <button id="qxjfal-saveConfigBtn">保存配置</button>
  602. <div id="qxjfal-configTips" style="margin-top: 10px; padding: 5px 10px; color: red; display: none;font-size:12px;">配置已保存并生效!</div>
  603. </div>
  604. <div>
  605. <button id="qxjfal-setting-btn">更多设置</button>
  606. </div>
  607. <div id="qxjfal-aboutPanel">
  608. <h4>🎥 视频解析工具</h4>
  609. ${
  610. GM_getValue("qxjfal-disclaimer", null) === "true"
  611. ? ""
  612. : `<p><b>免责声明:</b></p>
  613. <p>
  614. 1、<b style='color:red;'>需要使用视频解析的,请在更多设置自行添加接口</b>,版权问题请联系相关解析接口所有者,脚本不承担相关责任!"<br>
  615. 2、为创造良好的创作氛围,请大家支持正版!<br>
  616. 3、脚本仅限个人学习交流,使用即已代表您已经充分了解相关问题,否则后果自负,特此声明!<br>
  617. </p>
  618. <button id="qxjfal-disclaimer-btn" title="点击后表示确认,不再展示">确认</button>
  619. `
  620. }
  621. </div>
  622. </div>
  623. `;
  624.  
  625. const container = document.createElement("div");
  626. container.id = "qxjfal-container";
  627. container.innerHTML = iconHtml;
  628. document.body.appendChild(container);
  629.  
  630. const parsePanel = document.getElementById("qxjfal-parsePanel");
  631. const dragIcon = document.getElementById("qxjfal-dragIcon");
  632. const vidParseIcon = document.getElementById("qxjfal-vidParseIcon");
  633. const parseBtn = document.getElementById("qxjfal-parseBtn");
  634. const configPanel = document.getElementById("qxjfal-configPanel");
  635. const saveConfigBtn = document.getElementById("qxjfal-saveConfigBtn");
  636. const restoreBtn = document.getElementById("qxjfal-restoreBtn");
  637. const fastbootBtn = document.getElementById("qxjfal-fastboot");
  638. const settingBtn = document.getElementById("qxjfal-setting-btn");
  639. settingBtn.addEventListener("click", openSettingPanel);
  640.  
  641. const showmodeSelector = document.getElementById("qxjfal-showmode-select");
  642. showmodeSelector.value = showMode;
  643. showmodeSelector.addEventListener("change", (e) => {
  644. showMode = e.target.value;
  645. GM_setValue("showMode", showMode);
  646. });
  647.  
  648. const parseAutoPauseInput = document.getElementById(
  649. "qxjfal-parse-autopause"
  650. );
  651. parseAutoPauseInput.checked = parseAutoPause;
  652. parseAutoPauseInput.addEventListener("change", (e) => {
  653. parseAutoPause = e.target.checked;
  654. GM_setValue("parseAutoPause", parseAutoPause);
  655. });
  656.  
  657. const parseAutoMuteInput = document.getElementById("qxjfal-parse-automute");
  658. parseAutoMuteInput.checked = parseAutoMute;
  659. parseAutoMuteInput.addEventListener("change", (e) => {
  660. parseAutoMute = e.target.checked;
  661. GM_setValue("parseAutoMute", parseAutoMute);
  662. });
  663.  
  664. const icon = dragIcon;
  665.  
  666. const disclaimerBtn = document.getElementById("qxjfal-disclaimer-btn");
  667. disclaimerBtn.addEventListener("click", () => {
  668. GM_setValue("qxjfal-disclaimer", "true");
  669. });
  670.  
  671. setParseBtns();
  672.  
  673. fastbootBtn.addEventListener("click", () => {
  674. if (fastUrlIndex === -1) {
  675. alert("请先使用一个解析接口!快速开始会自动使用最近一次使用的解析接口");
  676. return;
  677. } else {
  678. parseVideo(parseApiList[fastUrlIndex].url);
  679. }
  680. });
  681.  
  682. // 初始化配置
  683. const iframeCount = GM_getValue("iframeCount", "6");
  684. configPanel.querySelector(`input[value="${iframeCount}"]`).checked = true;
  685.  
  686. // 鼠标移入图标:显示面板,清除隐藏定时器
  687. icon.addEventListener("mouseover", () => {
  688. clearTimeout(hidePanelTimeout);
  689. parsePanel.style.display = "block";
  690. });
  691.  
  692. // 鼠标移出图标:启动隐藏面板定时器
  693. icon.addEventListener("mouseleave", () => {
  694. hidePanelTimeout = setTimeout(() => {
  695. parsePanel.style.display = "none";
  696. }, 300);
  697. });
  698.  
  699. // 鼠标移入面板:清除隐藏定时器
  700. parsePanel.addEventListener("mouseover", () => {
  701. clearTimeout(hidePanelTimeout);
  702. });
  703.  
  704. // 鼠标移出面板:启动隐藏面板定时器
  705. parsePanel.addEventListener("mouseleave", () => {
  706. hidePanelTimeout = setTimeout(() => {
  707. parsePanel.style.display = "none";
  708. }, 300);
  709. });
  710.  
  711. // 保存配置
  712. saveConfigBtn.addEventListener("click", () => {
  713. const newIframeCount = configPanel.querySelector(
  714. 'input[name="qxjfal-iframeCount"]:checked'
  715. ).value;
  716. GM_setValue("iframeCount", newIframeCount);
  717. if (originalVideoContainer) {
  718. parseVideoAgain();
  719. }
  720. // 获取提示元素
  721. const tips = document.getElementById("qxjfal-configTips");
  722. tips.style.display = "block";
  723. // 3秒后隐藏
  724. setTimeout(() => {
  725. tips.style.display = "none";
  726. }, 3000);
  727. });
  728.  
  729. parsePanel.addEventListener("click", (e) => {
  730. e.stopPropagation();
  731. });
  732. parseBtn.addEventListener("click", (e) => parseVideo());
  733. vidParseIcon.addEventListener("click", (e) => parseVideo());
  734. restoreBtn.addEventListener("click", restoreVideo);
  735.  
  736. makeDraggable({
  737. element: container,
  738. handle: dragIcon,
  739. enableY: true,
  740. rememberY: "iconTop",
  741. });
  742. }
  743.  
  744. function getVideoContainer() {
  745. const siteRule = getSiteRule(location.hostname);
  746. if (!siteRule) {
  747. console.log("未找到匹配的网站规则");
  748. return null;
  749. }
  750. let videoContainer = null;
  751. for (const node of siteRule.node) {
  752. videoContainer = document.querySelector(node);
  753. if (videoContainer) {
  754. originalVideoContainerSelector = node;
  755. distanceTop =
  756. videoContainer.getBoundingClientRect().top + window.scrollY;
  757. distanceLeft =
  758. videoContainer.getBoundingClientRect().left + window.scrollX;
  759. videoContainerWidth = videoContainer.offsetWidth;
  760. videoContainerHeight = videoContainer.offsetHeight;
  761. break;
  762. }
  763. }
  764. return videoContainer;
  765. }
  766.  
  767. function expandIframe(iframeWrappers, index) {
  768. const videoContainer = getVideoContainer();
  769. if (!videoContainer) return;
  770. iframeWrappers.forEach((iframeWrapper, id) => {
  771. if (id !== index) currentIframeContainer.removeChild(iframeWrapper);
  772. // else {
  773. // const iframeOption = iframeWrapper.querySelector(
  774. // ".qxjfal-iframe-option"
  775. // );
  776. // iframeWrapper.removeChild(iframeOption);
  777. // }
  778. });
  779. currentIframeContainer.className =
  780. "qxjfal-iframe-container qxjfal-one-chunk";
  781. }
  782.  
  783. function stopVideos() {
  784. let videos = document.querySelectorAll("video");
  785. for (let i = 0; i < videos.length; i++) {
  786. videos[i].pause();
  787. }
  788. videos = null;
  789. }
  790. function muteVideos() {
  791. let videos = document.querySelectorAll("video");
  792. for (let i = 0; i < videos.length; i++) {
  793. videos[i].muted = true;
  794. }
  795. videos = null;
  796. }
  797.  
  798. function parseVideoAgain() {
  799. if (!parsed) return;
  800. if (lastUrl !== "") parseVideo(lastUrl, showMode);
  801. else parseVideo();
  802. }
  803.  
  804. function parseVideo(url, showMode = "emb") {
  805. const videoContainer = getVideoContainer();
  806. if (parseAutoPause) stopVideos();
  807. if (parseAutoMute) muteVideos();
  808. parsed = true;
  809. if (url) lastUrl = url;
  810. else lastUrl = "";
  811.  
  812. if (floatVideoContainer !== null) {
  813. document.body.removeChild(floatVideoContainer);
  814. floatVideoContainer = null;
  815. }
  816.  
  817. if (showMode === "emb") {
  818. if (!videoContainer) return;
  819. // if (!originalVideoContainer) {
  820. // originalVideoContainer = videoContainer.innerHTML;
  821. // }
  822.  
  823. let iframeCount = 0;
  824. let urls = [];
  825. if (url) {
  826. iframeCount = 1;
  827. urls = [url];
  828. } else {
  829. iframeCount = parseInt(GM_getValue("iframeCount", "6"));
  830. urls = parseApiList.slice(0, iframeCount).map((api) => api.url);
  831. }
  832.  
  833. let gridClass = "qxjfal-one-chunk";
  834. if (iframeCount === 6) {
  835. gridClass = "qxjfal-six-chunk";
  836. } else if (iframeCount === 4) {
  837. gridClass = "qxjfal-four-chunk";
  838. }
  839.  
  840. let iframeHTML = `
  841. <div class="qxjfal-video-top-handle">
  842. <span class="qxjfal-video-drag-handle">Ⓜ️<span class="qxjfal-video-drag-title">拖拽窗口</span></span>
  843. <span class="qxjfal-video-expand-handle" title="收起/展开">🚥</span>
  844. </div>
  845. `;
  846. iframeHTML += `<div class="qxjfal-iframe-container ${gridClass}">`;
  847. urls.forEach((url) => {
  848. iframeHTML += `
  849. <div class="qxjfal-iframe-wrapper">
  850. <iframe src="${url}${encodeURIComponent(
  851. location.href
  852. )}" allowfullscreen allowtransparency></iframe>
  853. <div class="qxjfal-iframe-option">
  854. <button class="qxjfal-expand-button">⬆️用这个视频继续播放</button>
  855. </div>
  856. </div>
  857. `;
  858. });
  859. iframeHTML += "</div>";
  860.  
  861. floatVideoContainer = document.createElement("div");
  862. floatVideoContainer.id = "qxjfal-float-video-container";
  863. floatVideoContainer.style.top = `${distanceTop}px`;
  864. floatVideoContainer.style.left = `${distanceLeft}px`;
  865. floatVideoContainer.style.width = `${videoContainerWidth}px`;
  866. floatVideoContainer.style.height = `${videoContainerHeight}px`;
  867. floatVideoContainer.innerHTML = iframeHTML;
  868.  
  869. document.body.appendChild(floatVideoContainer);
  870.  
  871. const videoDragHandle = floatVideoContainer.querySelector(
  872. ".qxjfal-video-drag-handle"
  873. );
  874. const videoDragTitle = floatVideoContainer.querySelector(
  875. ".qxjfal-video-drag-title"
  876. );
  877. const videoExpandHandle = floatVideoContainer.querySelector(
  878. ".qxjfal-video-expand-handle"
  879. );
  880. currentIframeContainer = floatVideoContainer.querySelector(
  881. ".qxjfal-iframe-container"
  882. );
  883.  
  884. videoExpandHandle.addEventListener("click", function (e) {
  885. currentIframeContainer.style.display =
  886. currentIframeContainer.style.display === "" ? "none" : "";
  887. floatVideoContainer.style.height =
  888. floatVideoContainer.style.height === "28px"
  889. ? `${videoContainerHeight}px`
  890. : "28px";
  891. });
  892.  
  893. videoDragHandle.addEventListener("mousedown", function (e) {
  894. videoDragHandle.style.position = "absolute";
  895. videoDragHandle.style.top = "0px";
  896. videoDragHandle.style.left = "0px";
  897. videoDragHandle.style.width = "100%";
  898. videoDragHandle.style.height = "100%";
  899. videoDragHandle.style.borderRadius = "0";
  900. videoDragHandle.style.textAlign = "center";
  901. videoDragTitle.style.display = "inline-block";
  902. });
  903. videoDragHandle.addEventListener("mouseup", function (e) {
  904. videoDragHandle.style.cssText = "";
  905. videoDragTitle.style.cssText = "";
  906. });
  907.  
  908. makeDraggable({
  909. element: floatVideoContainer,
  910. handle: videoDragHandle,
  911. enableX: true,
  912. enableY: true,
  913. });
  914.  
  915. //videoContainer.innerHTML = iframeHTML;
  916.  
  917. const expandButtons = floatVideoContainer.querySelectorAll(
  918. ".qxjfal-expand-button"
  919. );
  920. expandButtons.forEach((button, index) => {
  921. button.addEventListener("click", () => {
  922. expandIframe(
  923. floatVideoContainer.querySelectorAll(".qxjfal-iframe-wrapper"),
  924. index
  925. );
  926. });
  927. });
  928. } else if (showMode === "win") {
  929. let windowOption = "width=600,height=400,top=100,left=100,resizable=yes";
  930. if (videoContainer)
  931. windowOption = `width=${videoContainerWidth - 10},height=${Math.max(
  932. 400,
  933. videoContainerHeight - 150
  934. )},top=${distanceTop + 150},left=${distanceLeft},resizable=yes`;
  935. if (!url) url = parseApiList[0].url;
  936. if (lastWindow !== null) lastWindow.close();
  937. lastWindow = window.open(
  938. `${url}${encodeURIComponent(location.href)}`,
  939. "qx_parse_win",
  940. windowOption
  941. );
  942. } else if (showMode === "tab") {
  943. if (!url) url = parseApiList[0].url;
  944. if (lastWindow !== null) lastWindow.close();
  945. lastWindow = window.open(
  946. `${url}${encodeURIComponent(location.href)}`,
  947. "_blank"
  948. );
  949. }
  950.  
  951. const siteRule = getSiteRule(location.hostname);
  952. if (siteRule && siteRule.area) {
  953. const areaSelector = `.${siteRule.area}`;
  954. if (!videoContainer.dataset.eventBound) {
  955. const bindAreaEvent = () => {
  956. const areaElement = document.querySelector(areaSelector);
  957. if (areaElement) {
  958. areaElement.addEventListener("click", parseVideoAgainLater);
  959. videoContainer.dataset.eventBound = "true";
  960. }
  961. };
  962. bindAreaEvent();
  963. const observer = new MutationObserver(bindAreaEvent);
  964. observer.observe(document.body, { childList: true, subtree: true });
  965. }
  966. }
  967. }
  968.  
  969. function restoreVideo() {
  970. // 直接刷新页面
  971. location.reload();
  972. }
  973.  
  974. function makeDraggable({
  975. element,
  976. handle,
  977. enableX,
  978. enableY,
  979. rememberX,
  980. rememberY,
  981. }) {
  982. let isDragging = false;
  983. let startX, startY, startTop, startLeft;
  984.  
  985. handle.addEventListener("mousedown", (e) => {
  986. e.preventDefault();
  987. if (e.button !== 0) return;
  988.  
  989. isDragging = true;
  990. if (enableX) {
  991. startX = e.clientX;
  992. startLeft = element.offsetLeft;
  993. }
  994. if (enableY) {
  995. startY = e.clientY;
  996. startTop = element.offsetTop;
  997. }
  998.  
  999. document.addEventListener("mousemove", onMouseMoveWrapper);
  1000. document.addEventListener("mouseup", onMouseUp);
  1001. });
  1002.  
  1003. function onMouseMoveX(e) {
  1004. const deltaX = e.clientX - startX;
  1005. let newLeft = startLeft + deltaX;
  1006. const maxWidth = window.innerWidth - element.offsetWidth - 20;
  1007. newLeft = Math.max(0, Math.min(newLeft, maxWidth));
  1008. element.style.left = `${newLeft}px`;
  1009. }
  1010.  
  1011. function onMouseMoveY(e) {
  1012. const deltaY = e.clientY - startY;
  1013. let newTop = startTop + deltaY;
  1014. const maxHeight = window.innerHeight - element.offsetHeight - 10;
  1015. newTop = Math.max(0, Math.min(newTop, maxHeight));
  1016. element.style.top = `${newTop}px`;
  1017. }
  1018.  
  1019. function getMouseMoveHandle() {
  1020. if (enableX && enableY) {
  1021. return function (e) {
  1022. onMouseMoveX(e);
  1023. onMouseMoveY(e);
  1024. };
  1025. } else if (enableX) {
  1026. return onMouseMoveX;
  1027. } else {
  1028. return onMouseMoveY;
  1029. }
  1030. }
  1031.  
  1032. function onMouseMoveWrapper(e) {
  1033. if (!isDragging) return;
  1034. const onMouseMove = getMouseMoveHandle();
  1035. onMouseMove(e);
  1036. }
  1037.  
  1038. function onMouseUp() {
  1039. isDragging = false;
  1040. document.removeEventListener("mousemove", onMouseMoveWrapper);
  1041. document.removeEventListener("mouseup", onMouseUp);
  1042. if (rememberX) GM_setValue(rememberX, element.offsetLeft);
  1043. if (rememberY) GM_setValue(rememberY, element.offsetTop);
  1044. }
  1045. }
  1046.  
  1047. window.addEventListener("load", () => {
  1048. if (getSiteRule(location.hostname)) {
  1049. createParseElements();
  1050.  
  1051. const siteRule = getSiteRule(location.hostname);
  1052. if (siteRule && siteRule.area) {
  1053. const areaSelector = `.${siteRule.area}`;
  1054. const videoContainer = getVideoContainer();
  1055. if (videoContainer && !videoContainer.dataset.eventBound) {
  1056. const bindAreaEvent = () => {
  1057. const areaElement = document.querySelector(areaSelector);
  1058. if (areaElement) {
  1059. areaElement.addEventListener("click", parseVideoAgainLater);
  1060. videoContainer.dataset.eventBound = "true";
  1061. }
  1062. };
  1063.  
  1064. bindAreaEvent();
  1065. const observer = new MutationObserver(bindAreaEvent);
  1066. observer.observe(document.body, { childList: true, subtree: true });
  1067. }
  1068. }
  1069. }
  1070. });
  1071.  
  1072. GM_registerMenuCommand("设置解析线路", openSettingPanel);
  1073. })();

QingJ © 2025

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