- // ==UserScript==
- // @name 七象影视解析
- // @namespace qx-parse
- // @version 0.0.1
- // @description 优酷、爱奇艺、腾讯、B站等视频网站视频解析,悬浮面板
- // @author 通天教主
- // @icon data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDMyIDMyIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPgoJPHRpdGxlPmJyb3dzZXItY2hyb21lPC90aXRsZT4KCTxzdHlsZT4KCQkuczAgeyBmaWxsOiAjYWZjZGZmIH0gCgkJLnMxIHsgZmlsbDogIzM4ODNmZiB9IAoJPC9zdHlsZT4KCTxwYXRoIGlkPSLlm77lsYIgNCIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGFzcz0iczAiIGQ9Im02IDBoMjBjMy4zIDAgNiAyLjcgNiA2djIwYzAgMy4zLTIuNyA2LTYgNmgtMjBjLTMuMyAwLTYtMi43LTYtNnYtMjBjMC0zLjMgMi43LTYgNi02eiIvPgoJPHBhdGggaWQ9IuW9oueKtiAxIiBjbGFzcz0iczEiIGQ9Im0yMiAxNy41YzAuNS0wLjcgMC41LTEuOSAwLTIuN2wtNy40LTYuMmMtMC42LTAuNS0xLjUtMC42LTIuMy0wLjMtMC44IDAuMi0xLjMgMC44LTEuMyAxLjR2MTIuOWMwIDAuNyAwLjUgMS4zIDEuMyAxLjUgMC44IDAuMyAxLjcgMC4xIDIuMy0wLjMgMCAwIDcuNC02LjMgNy40LTYuM3oiLz4KPC9zdmc+
- // @match *://*.youku.com/*
- // @match *://*.iqiyi.com/v_*
- // @match *://*.iqiyi.com/w_*
- // @match *://*.iqiyi.com/a_*
- // @match *://*.iqiyi.com/resource/pcw/play/*
- // @match *://*.iq.com/*
- // @match *://v.qq.com/x/cover/*
- // @match *://v.qq.com/x/page/*
- // @match *://v.qq.com/tv/*
- // @match *://m.v.qq.com/x/cover/*
- // @match *://m.v.qq.com/x/page/*
- // @match *://m.v.qq.com/*
- // @match *://*.bilibili.com/**
- // @match *://*.mgtv.com/b/*
- // @match *://*.le.com/ptv/vplay/*
- // @match *://*.tudou.com/listplay/*
- // @match *://*.tudou.com/albumplay/*
- // @match *://*.tudou.com/programs/view/*
- // @match *://*.pptv.com/show/*
- // @match *://*.1905.com/video/*
- // @match *://*.1905.com/play/*
- // @match *://*.1905.com/*/play/*
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_registerMenuCommand
- // @license GPLv3
- // ==/UserScript==
-
- (function () {
- "use strict";
-
- const isMobile = /Android|webOS|iPhone|iPod|BlackBerry/i.test(
- navigator.userAgent
- );
-
- const parseApiListStr = GM_getValue("parseApiList", "[]");
-
- let parseApiList = JSON.parse(parseApiListStr);
-
- function parseApi2Text(parseApiList) {
- let texts = [];
- parseApiList.forEach((api) => {
- let line = api.name + "," + api.url;
- texts.push(line);
- });
- return texts.join("\n");
- }
-
- function text2ParseApi(text) {
- let lines = text.split("\n");
- let apis = [];
- lines.forEach((line) => {
- let res = line.split(",");
- if (res.length !== 2) {
- if (res.length === 1) {
- res.unshift(getSLDFromUrl(res[0]));
- } else return;
- }
- let [name, url] = res;
- apis.push({
- name: name.trim(),
- url: url.trim(),
- });
- });
- return apis;
- }
-
- let parseBtns = [];
-
- function setParseBtns() {
- const singleParsePanel = document.getElementById("qxjfal-singleParsePanel");
- parseBtns.forEach((btn) => singleParsePanel.removeChild(btn));
- parseBtns = [];
- parseApiList.forEach((api, index) => {
- const parseBtn = document.createElement("button");
- parseBtn.textContent = api.name;
- parseBtn.title = api.name + " " + api.url;
- if (index === fastUrlIndex) {
- parseBtn.style.backgroundColor = "#ff9999";
- }
- parseBtn.addEventListener("click", () => {
- fastUrlIndex = index;
- GM_setValue("fastUrlIndex", index);
- parseBtns.forEach((btn) => {
- btn.style.backgroundColor = "";
- });
- parseBtns[index].style.backgroundColor = "#ff9999";
- const fastbootBtn = document.getElementById("qxjfal-fastboot");
- fastbootBtn.title = `快速开始(当前所选接口:${api.name})`;
- parseVideo(api.url, showMode);
- });
- parseBtns.push(parseBtn);
- singleParsePanel.appendChild(parseBtn);
- });
- }
-
- function openSettingPanel() {
- const settingPanel = document.createElement("div");
- settingPanel.id = "qxjfal-setting-panel";
- const settingHtml = `
- <div class='qxjfal-setting-panel-header'>
- <div class='qxjfal-setting-panel-title'>设置自定义解析接口</div>
- <div class='qxjfal-setting-panel-closebtn'>X</div>
- </div>
- <div style='padding: 15px;'>
- <div>
- <p>自定义解析接口
- </p>
- <p>数据格式:[名字] + [,] + [接口地址]</p>
- <p>例如:名字,https://xxxxxx?url=</p>
- <p>一行一个自定义接口,如果不提供名字,则自动将二级域名作为名字
- </p>
- </div>
- <div>
- <textarea class="qxjfal-setting-panel-textarea" rows="10" cols="50"></textarea>
- </div>
- <div>
- <button class="qxjfal-setting-savebtn">保存</button>
- </div>
- </div>
- `;
- settingPanel.innerHTML = settingHtml;
- const settingPanelHeader = settingPanel.querySelector(
- ".qxjfal-setting-panel-header"
- );
- makeDraggable({
- element: settingPanel,
- handle: settingPanelHeader,
- enableX: true,
- enableY: true,
- });
- const settingPanelCloseBtn = settingPanel.querySelector(
- ".qxjfal-setting-panel-closebtn"
- );
- settingPanelCloseBtn.addEventListener("click", () => {
- document.body.removeChild(settingPanel);
- });
- const settingPanelTextarea = settingPanel.querySelector(
- ".qxjfal-setting-panel-textarea"
- );
- settingPanelTextarea.value = parseApi2Text(parseApiList);
- const settingSaveBtn = settingPanel.querySelector(
- ".qxjfal-setting-savebtn"
- );
- settingSaveBtn.addEventListener("click", () => {
- parseApiList = text2ParseApi(settingPanelTextarea.value);
- GM_setValue("parseApiList", JSON.stringify(parseApiList));
- setParseBtns();
- settingPanelCloseBtn.click();
- });
- document.body.appendChild(settingPanel);
- }
-
- const parseVideoAgainLater = () => {
- setTimeout(parseVideoAgain, 1000);
- };
-
- // 网站与解析规则的映射
- const siteRules = {
- "v.qq.com": {
- node: [".player__container", "#player-container"],
- area: "playlist-list",
- },
- "iqiyi.com": { node: ["#video"], area: "" },
- "iq.com": { node: [".intl-video-wrap"], area: "m-sliding-list" },
- "youku.com": { node: ["#ykPlayer"], area: "new-box-anthology-items" },
- "bilibili.com": {
- node: ["#bilibili-player", ".bpx-player-primary-area"],
- area: "video-episode-card",
- },
- "mgtv.com": { node: ["#mgtv-player-wrap"], area: "episode-items" },
- "le.com": { node: ["#le_playbox"], area: "juji_grid" },
- "tudou.com": { node: ["#player"], area: "" },
- "pptv.com": { node: ["#pptv_playpage_box"], area: "" },
- "1905.com": { node: ["#player", "#vodPlayer"], area: "" },
- };
-
- let floatVideoContainer = null;
- let originalVideoContainer = null;
- let originalVideoContainerSelector = null;
- let currentIframeContainer = null;
- let distanceTop = null;
- let distanceLeft = null;
- let videoContainerWidth = null;
- let videoContainerHeight = null;
- let hidePanelTimeout = null; // 隐藏面板的定时器
- let lastUrl = "";
- let parsed = false;
- let lastWindow = null;
- let parseAutoPause = GM_getValue("parseAutoPause", true);
- let parseAutoMute = GM_getValue("parseAutoMute", true);
- let showMode = GM_getValue("showMode", "emb"); // 1 为悬浮播放,2 为新窗口,3 为新标签页
- let fastUrlIndex = GM_getValue("fastUrlIndex", -1);
-
- function getSiteRule(host) {
- return (
- siteRules[Object.keys(siteRules).find((key) => host.includes(key))] ||
- null
- );
- }
-
- function getDomainFromUrl(url) {
- let domain;
- try {
- let parsedUrl = new URL(url);
- domain = parsedUrl.hostname;
- } catch (error) {
- console.error("Invalid URL", error);
- }
- return domain;
- }
-
- function getSLDFromUrl(url) {
- const domain = getDomainFromUrl(url);
- const domainLs = domain.split(".");
- if (domainLs.length >= 2) {
- return domainLs[domainLs.length - 2];
- } else {
- return "😊";
- }
- }
-
- function createParseElements() {
- const iconSize = isMobile ? 30 : GM_getValue("iconWidth", 24);
- const iconTop = isMobile ? 360 : GM_getValue("iconTop", 100);
- const iconPosition = isMobile
- ? "left"
- : GM_getValue("iconPosition", "left");
-
- const iconStyle = `
- #qxjfal-iconContainer {
- background-color: #fff;
- border: 1px solid #ccc;
- border-radius: 6px;
- padding: 0px;
- text-align: center;
- opacity: ${isMobile ? 1 : GM_getValue("iconOpacity", 100) / 100};
- width: ${iconSize}px;
- box-sizing: border-box;
- opacity: 0.5;
- /* transition: 0.1s; */
- }
- #qxjfal-iconContainer:hover {
- opacity: 1;
- }
- #qxjfal-optionIcons {
- cursor: pointer;
- }
- #qxjfal-optionIcons>div {
- padding: 6px 0px;
- }
- #qxjfal-container {
- position: fixed;
- top: ${iconTop}px;
- ${iconPosition}: 0px;
- z-index: 999999;
- display: flex;
- flex-direction: ${iconPosition === "left" ? "row" : "row-reverse"};
- }
- #qxjfal-dragIcon {
- cursor: move;
- }
- #qxjfal-dragIcon:hover {
- transform: scale(1.2);
- }
- #qxjfal-fastboot:hover {
- transform: scale(1.2);
- }
- #qxjfal-vidParseIcon:hover {
- transform: scale(1.2);
- }
-
- #qxjfal-parsePanel {
- position: fixed; /* 绝对定位 */
- top: 0px; /* 图标高度+5px的间距*/
- ${
- iconPosition === "left" ? "left" : "right"
- }: ${iconSize}px; /* 根据图标位置调整 */
- z-index: 999998;
- background-color: #fff;
- border: 1px solid #ccc;
- padding: 12px 15px;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
- border-radius: 6px;
- width: 310px; /* 调整面板宽度 */
- height: 100vh;
- overflow: auto;
- display: none; /* 初始隐藏 */
- box-sizing: border-box;
- }
-
- #qxjfal-parsePanel button, #qxjfal-setting-panel button {
- margin: 3px 0;
- padding: 8px 18px;
- background-color: #285aa6;
- color: #fff;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- transition: background-color 0.3s;
- width: 100%;
- box-sizing: border-box;
- }
- #qxjfal-parsePanel button:hover {
- background-color: #1e4888;
- }
- #qxjfal-parsePanel * {
- color: #333333;
- }
-
- #qxjfal-configPanel {
- margin-top: 15px;
- padding-top: 10px;
- border-top: 1px solid #eee;
- }
-
- #qxjfal-configPanel label {
- display: block;
- margin-bottom: 8px;
- color: #333;
- }
- #qxjfal-configPanel input[type="radio"] {
- margin-right: 6px;
- }
-
- #qxjfal-saveConfigBtn {
- background-color: #4CAF50 !important;
- }
- #qxjfal-saveConfigBtn:hover {
- background-color: #45a049 !important;
- }
-
- #qxjfal-aboutPanel, #qxjfal-singleParsePanel {
- margin: 3px 0;
- padding: 15px;
- background-color: #f8f9fa;
- border-radius: 4px;
- }
-
- #qxjfal-aboutPanel h4 {
- margin-top: 0;
- color: #333333;
- }
-
- #qxjfal-aboutPanel p {
- color: #333333;
- line-height: 1.6;
- }
-
- #qxjfal-singleParsePanel {
- padding: 6px;
- max-height: 300px;
- overflow: auto;
- }
-
- #qxjfal-singleParsePanel button {
- padding: 6px 8px;
- width: 60px;
- overflow: hidden;
- text-overflow: ellipsis;
- border-radius: 6px;
- text-wrap: nowrap;
- background-color: #ffffff;
- color: #333333;
- margin: 3px;
- }
-
- #qxjfal-singleParsePanel button:hover {
- background-color: #dfeffd;
- }
-
- #qxjfal-telegramLink {
- color: #007bff;
- text-decoration: underline;
- cursor: pointer;
- }
-
- #qxjfal-showmode-select {
- border: 1px solid #999;
- padding: 4px 10px;
- border-radius: 4px;
- margin: 3px 0;
- }
-
- #qxjfal-parse-autopause {
- margin: 3px 0;
- }
-
- #qxjfal-parse-automute {
- margin: 3px 0;
- }
-
- /* ... 其他样式保持不变 ... */
- #qxjfal-float-video-container {
- position: absolute;
- top: 0;
- left: 0;
- width: 200px;
- height: 100px;
- z-index: 999997;
- display: flex;
- flex-direction: column;
- }
-
- .qxjfal-video-top-handle {
- background: #333333;
- width: 100%;
- box-sizing: border-box;
- padding: 4px;
- }
-
- .qxjfal-video-expand-handle {
- user-select: none;
- width: 50px;
- box-sizing: border-box;
- padding: 4px 6px;
- text-align: center;
- cursor: pointer;
- }
-
- .qxjfal-video-drag-handle {
- text-align: center;
- box-sizing: border-box;
- padding: 4px 6px;
- width: 50px;
- background: #333333;
- cursor: move;
- }
- .qxjfal-video-drag-title {
- color: #fff;
- display: none;
- }
-
- .qxjfal-iframe-container {
- flex: 1;
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- grid-template-rows: repeat(2, auto);
- grid-auto-rows: minmax(200px, auto);
- grid-gap: 1px;
- width: 100%;
- height: 100%;
- }
- .qxjfal-one-chunk {
- grid-template-columns: repeat(1, 1fr);
- grid-template-rows: repeat(1, auto);
- }
- .qxjfal-one-chunk .qxjfal-iframe-option {
- display: none;
- }
- .qxjfal-four-chunk {
- grid-template-columns: repeat(2, 1fr);
- grid-template-rows: repeat(2, auto);
- }
- .qxjfal-six-chunk {
- grid-template-columns: repeat(3, 1fr);
- grid-template-rows: repeat(2, auto);
- }
- .qxjfal-iframe-container iframe {
- flex: 1;
- border: 1px solid #ddd;
- }
- /* 可选:添加响应式设计 */
- @media (max-width: 768px) {
- .qxjfal-iframe-container {
- grid-template-columns: repeat(2, 1fr); /* 在小屏幕上显示两列 */
- }
- }
-
- @media (max-width: 480px) {
- .qxjfal-iframe-container {
- grid-template-columns: 1fr; /* 在非常小的屏幕上显示一列 */
- }
- }
-
- .qxjfal-iframe-wrapper {
- display: flex;
- flex-direction: column;
- items-align: stretch;
- }
-
- .qxjfal-iframe-wrapper button {
- margin: 0;
- padding: 2px 6px;
- background-color: #2871a6;
- color: #fff;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- transition: background-color 0.3s;
- box-sizing: border-box;
- }
-
- .qxjfal-iframe-option {
- display: flex;
- column-gap: 4px;
- background-color: #777777;
- color: white;
- text-align: center;
- padding: 4px;
- transition: 0.3s;
- }
- .qxjfal-expand-button {
- flex: 1;
- }
- .qxjfal-eliminate-button {
- background-color: #333333 !important;
- flex: 1;
- }
-
- #qxjfal-setting-panel {
- font-size: 14px;
- position: fixed;
- top: 0;
- ${iconPosition === "left" ? "left" : "right"}: 0px;
- margin: 0 auto;
- max-height: 100%;
- width: 100%;
- max-width: 500px;
- background-color: #ffffff;
- border-radius: 6px;
- overflow: auto;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
- z-index: 999999;
- box-sizing: border-box;
- }
-
- .qxjfal-setting-panel-header {
- padding: 4px 10px;
- text-align: center;
- background-color: #efefef;
- }
-
- .qxjfal-setting-panel-title {
- display: inline-block;
- font-weight: bold;
- user-select: none;
- }
- .qxjfal-setting-panel-closebtn {
- font-size: 1.2em;
- line-height: 1.2em;
- vertical-align: middle;
- float: right;
- cursor: pointer;
- }
- .qxjfal-setting-panel-textarea {
- width: 99%;
- margin: 0 auto;
- }
- `;
-
- const styleEl = document.createElement("style");
- styleEl.textContent = iconStyle;
- document.head.appendChild(styleEl);
-
- const iconHtml = `
- <div id="qxjfal-iconContainer">
- <div id="qxjfal-optionIcons">
- <div id="qxjfal-dragIcon" title="拖拽调整位置">Ⓜ️</div>
- <div id="qxjfal-fastboot" title="快速开始(当前所选接口:${
- fastUrlIndex === -1 ? "无" : parseApiList[fastUrlIndex].name
- })">🩷</div>
- <div id="qxjfal-vidParseIcon" title="解析">✨</div>
- </div>
- </div>
- <div id="qxjfal-parsePanel">
- <div>
- <button id="qxjfal-parseBtn">👉解析</button>
- <button id="qxjfal-restoreBtn" style="background:#5a6268;">还原</button>
- </div>
- <div id="qxjfal-singleParsePanel">
- </div>
- <div>
- <select id="qxjfal-showmode-select" name="showmode">
- <option value="emb">当前页面中</option>
- <option value="win">新窗口打开</option>
- <option value="tab">新标签打开</option>
- </select>
- </div>
- <div>
- <label><input type="checkbox" id="qxjfal-parse-autopause" ${
- parseAutoPause ? "checked" : ""
- }> 解析时自动暂停原视频(部分网站可能无法成功暂停)</label>
- </div>
- <div>
- <label><input type="checkbox" id="qxjfal-parse-automute" ${
- parseAutoMute ? "checked" : ""
- }> 解析时自动静音原视频</label>
- </div>
- <div id="qxjfal-configPanel">
- <label><input type="radio" name="qxjfal-iframeCount" value="6"> 6个格子解析</label>
- <label><input type="radio" name="qxjfal-iframeCount" value="4"> 4个格子解析</label>
- <label><input type="radio" name="qxjfal-iframeCount" value="1"> 1个格子解析</label>
- <button id="qxjfal-saveConfigBtn">保存配置</button>
- <div id="qxjfal-configTips" style="margin-top: 10px; padding: 5px 10px; color: red; display: none;font-size:12px;">配置已保存并生效!</div>
- </div>
- <div>
- <button id="qxjfal-setting-btn">更多设置</button>
- </div>
- <div id="qxjfal-aboutPanel">
- <h4>🎥 视频解析工具</h4>
- ${
- GM_getValue("qxjfal-disclaimer", null) === "true"
- ? ""
- : `<p><b>免责声明:</b></p>
- <p>
- 1、<b style='color:red;'>需要使用视频解析的,请在更多设置自行添加接口</b>,版权问题请联系相关解析接口所有者,脚本不承担相关责任!"<br>
- 2、为创造良好的创作氛围,请大家支持正版!<br>
- 3、脚本仅限个人学习交流,使用即已代表您已经充分了解相关问题,否则后果自负,特此声明!<br>
- </p>
- <button id="qxjfal-disclaimer-btn" title="点击后表示确认,不再展示">确认</button>
- `
- }
- </div>
- </div>
- `;
-
- const container = document.createElement("div");
- container.id = "qxjfal-container";
- container.innerHTML = iconHtml;
- document.body.appendChild(container);
-
- const parsePanel = document.getElementById("qxjfal-parsePanel");
- const dragIcon = document.getElementById("qxjfal-dragIcon");
- const vidParseIcon = document.getElementById("qxjfal-vidParseIcon");
- const parseBtn = document.getElementById("qxjfal-parseBtn");
- const configPanel = document.getElementById("qxjfal-configPanel");
- const saveConfigBtn = document.getElementById("qxjfal-saveConfigBtn");
- const restoreBtn = document.getElementById("qxjfal-restoreBtn");
- const fastbootBtn = document.getElementById("qxjfal-fastboot");
- const settingBtn = document.getElementById("qxjfal-setting-btn");
- settingBtn.addEventListener("click", openSettingPanel);
-
- const showmodeSelector = document.getElementById("qxjfal-showmode-select");
- showmodeSelector.value = showMode;
- showmodeSelector.addEventListener("change", (e) => {
- showMode = e.target.value;
- GM_setValue("showMode", showMode);
- });
-
- const parseAutoPauseInput = document.getElementById(
- "qxjfal-parse-autopause"
- );
- parseAutoPauseInput.checked = parseAutoPause;
- parseAutoPauseInput.addEventListener("change", (e) => {
- parseAutoPause = e.target.checked;
- GM_setValue("parseAutoPause", parseAutoPause);
- });
-
- const parseAutoMuteInput = document.getElementById("qxjfal-parse-automute");
- parseAutoMuteInput.checked = parseAutoMute;
- parseAutoMuteInput.addEventListener("change", (e) => {
- parseAutoMute = e.target.checked;
- GM_setValue("parseAutoMute", parseAutoMute);
- });
-
- const icon = dragIcon;
-
- const disclaimerBtn = document.getElementById("qxjfal-disclaimer-btn");
- disclaimerBtn.addEventListener("click", () => {
- GM_setValue("qxjfal-disclaimer", "true");
- });
-
- setParseBtns();
-
- fastbootBtn.addEventListener("click", () => {
- if (fastUrlIndex === -1) {
- alert("请先使用一个解析接口!快速开始会自动使用最近一次使用的解析接口");
- return;
- } else {
- parseVideo(parseApiList[fastUrlIndex].url);
- }
- });
-
- // 初始化配置
- const iframeCount = GM_getValue("iframeCount", "6");
- configPanel.querySelector(`input[value="${iframeCount}"]`).checked = true;
-
- // 鼠标移入图标:显示面板,清除隐藏定时器
- icon.addEventListener("mouseover", () => {
- clearTimeout(hidePanelTimeout);
- parsePanel.style.display = "block";
- });
-
- // 鼠标移出图标:启动隐藏面板定时器
- icon.addEventListener("mouseleave", () => {
- hidePanelTimeout = setTimeout(() => {
- parsePanel.style.display = "none";
- }, 300);
- });
-
- // 鼠标移入面板:清除隐藏定时器
- parsePanel.addEventListener("mouseover", () => {
- clearTimeout(hidePanelTimeout);
- });
-
- // 鼠标移出面板:启动隐藏面板定时器
- parsePanel.addEventListener("mouseleave", () => {
- hidePanelTimeout = setTimeout(() => {
- parsePanel.style.display = "none";
- }, 300);
- });
-
- // 保存配置
- saveConfigBtn.addEventListener("click", () => {
- const newIframeCount = configPanel.querySelector(
- 'input[name="qxjfal-iframeCount"]:checked'
- ).value;
- GM_setValue("iframeCount", newIframeCount);
- if (originalVideoContainer) {
- parseVideoAgain();
- }
- // 获取提示元素
- const tips = document.getElementById("qxjfal-configTips");
- tips.style.display = "block";
- // 3秒后隐藏
- setTimeout(() => {
- tips.style.display = "none";
- }, 3000);
- });
-
- parsePanel.addEventListener("click", (e) => {
- e.stopPropagation();
- });
- parseBtn.addEventListener("click", (e) => parseVideo());
- vidParseIcon.addEventListener("click", (e) => parseVideo());
- restoreBtn.addEventListener("click", restoreVideo);
-
- makeDraggable({
- element: container,
- handle: dragIcon,
- enableY: true,
- rememberY: "iconTop",
- });
- }
-
- function getVideoContainer() {
- const siteRule = getSiteRule(location.hostname);
- if (!siteRule) {
- console.log("未找到匹配的网站规则");
- return null;
- }
- let videoContainer = null;
- for (const node of siteRule.node) {
- videoContainer = document.querySelector(node);
- if (videoContainer) {
- originalVideoContainerSelector = node;
- distanceTop =
- videoContainer.getBoundingClientRect().top + window.scrollY;
- distanceLeft =
- videoContainer.getBoundingClientRect().left + window.scrollX;
- videoContainerWidth = videoContainer.offsetWidth;
- videoContainerHeight = videoContainer.offsetHeight;
- break;
- }
- }
- return videoContainer;
- }
-
- function expandIframe(iframeWrappers, index) {
- const videoContainer = getVideoContainer();
- if (!videoContainer) return;
- iframeWrappers.forEach((iframeWrapper, id) => {
- if (id !== index) currentIframeContainer.removeChild(iframeWrapper);
- // else {
- // const iframeOption = iframeWrapper.querySelector(
- // ".qxjfal-iframe-option"
- // );
- // iframeWrapper.removeChild(iframeOption);
- // }
- });
- currentIframeContainer.className =
- "qxjfal-iframe-container qxjfal-one-chunk";
- }
-
- function stopVideos() {
- let videos = document.querySelectorAll("video");
- for (let i = 0; i < videos.length; i++) {
- videos[i].pause();
- }
- videos = null;
- }
- function muteVideos() {
- let videos = document.querySelectorAll("video");
- for (let i = 0; i < videos.length; i++) {
- videos[i].muted = true;
- }
- videos = null;
- }
-
- function parseVideoAgain() {
- if (!parsed) return;
- if (lastUrl !== "") parseVideo(lastUrl, showMode);
- else parseVideo();
- }
-
- function parseVideo(url, showMode = "emb") {
- const videoContainer = getVideoContainer();
- if (parseAutoPause) stopVideos();
- if (parseAutoMute) muteVideos();
- parsed = true;
- if (url) lastUrl = url;
- else lastUrl = "";
-
- if (floatVideoContainer !== null) {
- document.body.removeChild(floatVideoContainer);
- floatVideoContainer = null;
- }
-
- if (showMode === "emb") {
- if (!videoContainer) return;
- // if (!originalVideoContainer) {
- // originalVideoContainer = videoContainer.innerHTML;
- // }
-
- let iframeCount = 0;
- let urls = [];
- if (url) {
- iframeCount = 1;
- urls = [url];
- } else {
- iframeCount = parseInt(GM_getValue("iframeCount", "6"));
- urls = parseApiList.slice(0, iframeCount).map((api) => api.url);
- }
-
- let gridClass = "qxjfal-one-chunk";
- if (iframeCount === 6) {
- gridClass = "qxjfal-six-chunk";
- } else if (iframeCount === 4) {
- gridClass = "qxjfal-four-chunk";
- }
-
- let iframeHTML = `
- <div class="qxjfal-video-top-handle">
- <span class="qxjfal-video-drag-handle">Ⓜ️<span class="qxjfal-video-drag-title">拖拽窗口</span></span>
- <span class="qxjfal-video-expand-handle" title="收起/展开">🚥</span>
- </div>
- `;
- iframeHTML += `<div class="qxjfal-iframe-container ${gridClass}">`;
- urls.forEach((url) => {
- iframeHTML += `
- <div class="qxjfal-iframe-wrapper">
- <iframe src="${url}${encodeURIComponent(
- location.href
- )}" allowfullscreen allowtransparency></iframe>
- <div class="qxjfal-iframe-option">
- <button class="qxjfal-expand-button">⬆️用这个视频继续播放</button>
- </div>
- </div>
- `;
- });
- iframeHTML += "</div>";
-
- floatVideoContainer = document.createElement("div");
- floatVideoContainer.id = "qxjfal-float-video-container";
- floatVideoContainer.style.top = `${distanceTop}px`;
- floatVideoContainer.style.left = `${distanceLeft}px`;
- floatVideoContainer.style.width = `${videoContainerWidth}px`;
- floatVideoContainer.style.height = `${videoContainerHeight}px`;
- floatVideoContainer.innerHTML = iframeHTML;
-
- document.body.appendChild(floatVideoContainer);
-
- const videoDragHandle = floatVideoContainer.querySelector(
- ".qxjfal-video-drag-handle"
- );
- const videoDragTitle = floatVideoContainer.querySelector(
- ".qxjfal-video-drag-title"
- );
- const videoExpandHandle = floatVideoContainer.querySelector(
- ".qxjfal-video-expand-handle"
- );
- currentIframeContainer = floatVideoContainer.querySelector(
- ".qxjfal-iframe-container"
- );
-
- videoExpandHandle.addEventListener("click", function (e) {
- currentIframeContainer.style.display =
- currentIframeContainer.style.display === "" ? "none" : "";
- floatVideoContainer.style.height =
- floatVideoContainer.style.height === "28px"
- ? `${videoContainerHeight}px`
- : "28px";
- });
-
- videoDragHandle.addEventListener("mousedown", function (e) {
- videoDragHandle.style.position = "absolute";
- videoDragHandle.style.top = "0px";
- videoDragHandle.style.left = "0px";
- videoDragHandle.style.width = "100%";
- videoDragHandle.style.height = "100%";
- videoDragHandle.style.borderRadius = "0";
- videoDragHandle.style.textAlign = "center";
- videoDragTitle.style.display = "inline-block";
- });
- videoDragHandle.addEventListener("mouseup", function (e) {
- videoDragHandle.style.cssText = "";
- videoDragTitle.style.cssText = "";
- });
-
- makeDraggable({
- element: floatVideoContainer,
- handle: videoDragHandle,
- enableX: true,
- enableY: true,
- });
-
- //videoContainer.innerHTML = iframeHTML;
-
- const expandButtons = floatVideoContainer.querySelectorAll(
- ".qxjfal-expand-button"
- );
- expandButtons.forEach((button, index) => {
- button.addEventListener("click", () => {
- expandIframe(
- floatVideoContainer.querySelectorAll(".qxjfal-iframe-wrapper"),
- index
- );
- });
- });
- } else if (showMode === "win") {
- let windowOption = "width=600,height=400,top=100,left=100,resizable=yes";
- if (videoContainer)
- windowOption = `width=${videoContainerWidth - 10},height=${Math.max(
- 400,
- videoContainerHeight - 150
- )},top=${distanceTop + 150},left=${distanceLeft},resizable=yes`;
- if (!url) url = parseApiList[0].url;
- if (lastWindow !== null) lastWindow.close();
- lastWindow = window.open(
- `${url}${encodeURIComponent(location.href)}`,
- "qx_parse_win",
- windowOption
- );
- } else if (showMode === "tab") {
- if (!url) url = parseApiList[0].url;
- if (lastWindow !== null) lastWindow.close();
- lastWindow = window.open(
- `${url}${encodeURIComponent(location.href)}`,
- "_blank"
- );
- }
-
- const siteRule = getSiteRule(location.hostname);
- if (siteRule && siteRule.area) {
- const areaSelector = `.${siteRule.area}`;
- if (!videoContainer.dataset.eventBound) {
- const bindAreaEvent = () => {
- const areaElement = document.querySelector(areaSelector);
- if (areaElement) {
- areaElement.addEventListener("click", parseVideoAgainLater);
- videoContainer.dataset.eventBound = "true";
- }
- };
- bindAreaEvent();
- const observer = new MutationObserver(bindAreaEvent);
- observer.observe(document.body, { childList: true, subtree: true });
- }
- }
- }
-
- function restoreVideo() {
- // 直接刷新页面
- location.reload();
- }
-
- function makeDraggable({
- element,
- handle,
- enableX,
- enableY,
- rememberX,
- rememberY,
- }) {
- let isDragging = false;
- let startX, startY, startTop, startLeft;
-
- handle.addEventListener("mousedown", (e) => {
- e.preventDefault();
- if (e.button !== 0) return;
-
- isDragging = true;
- if (enableX) {
- startX = e.clientX;
- startLeft = element.offsetLeft;
- }
- if (enableY) {
- startY = e.clientY;
- startTop = element.offsetTop;
- }
-
- document.addEventListener("mousemove", onMouseMoveWrapper);
- document.addEventListener("mouseup", onMouseUp);
- });
-
- function onMouseMoveX(e) {
- const deltaX = e.clientX - startX;
- let newLeft = startLeft + deltaX;
- const maxWidth = window.innerWidth - element.offsetWidth - 20;
- newLeft = Math.max(0, Math.min(newLeft, maxWidth));
- element.style.left = `${newLeft}px`;
- }
-
- function onMouseMoveY(e) {
- const deltaY = e.clientY - startY;
- let newTop = startTop + deltaY;
- const maxHeight = window.innerHeight - element.offsetHeight - 10;
- newTop = Math.max(0, Math.min(newTop, maxHeight));
- element.style.top = `${newTop}px`;
- }
-
- function getMouseMoveHandle() {
- if (enableX && enableY) {
- return function (e) {
- onMouseMoveX(e);
- onMouseMoveY(e);
- };
- } else if (enableX) {
- return onMouseMoveX;
- } else {
- return onMouseMoveY;
- }
- }
-
- function onMouseMoveWrapper(e) {
- if (!isDragging) return;
- const onMouseMove = getMouseMoveHandle();
- onMouseMove(e);
- }
-
- function onMouseUp() {
- isDragging = false;
- document.removeEventListener("mousemove", onMouseMoveWrapper);
- document.removeEventListener("mouseup", onMouseUp);
- if (rememberX) GM_setValue(rememberX, element.offsetLeft);
- if (rememberY) GM_setValue(rememberY, element.offsetTop);
- }
- }
-
- window.addEventListener("load", () => {
- if (getSiteRule(location.hostname)) {
- createParseElements();
-
- const siteRule = getSiteRule(location.hostname);
- if (siteRule && siteRule.area) {
- const areaSelector = `.${siteRule.area}`;
- const videoContainer = getVideoContainer();
- if (videoContainer && !videoContainer.dataset.eventBound) {
- const bindAreaEvent = () => {
- const areaElement = document.querySelector(areaSelector);
- if (areaElement) {
- areaElement.addEventListener("click", parseVideoAgainLater);
- videoContainer.dataset.eventBound = "true";
- }
- };
-
- bindAreaEvent();
- const observer = new MutationObserver(bindAreaEvent);
- observer.observe(document.body, { childList: true, subtree: true });
- }
- }
- }
- });
-
- GM_registerMenuCommand("设置解析线路", openSettingPanel);
- })();