diff --git a/build.js b/build.js index 215d3ad16..2623d84dc 100644 --- a/build.js +++ b/build.js @@ -15,7 +15,7 @@ function handleFolder(folderPath, excludingFileName) { if (item !== excludingFileName) { const fileContent = fs.readFileSync(itemPath, "utf8"); if (item.includes(".css")) css += fileContent + "\r\n"; - if (item.includes(".js")) js += fileContent + "\r\n"; + if (item.includes(".js")) js += fileContent + "\r\n\r\n"; } } }); diff --git a/src/common.js b/src/common.js index acb45e192..a88c77f05 100644 --- a/src/common.js +++ b/src/common.js @@ -133,12 +133,12 @@ function setCookie(cookiename, value){ } function getCookieValue(name){ -   let arr,reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); -    if (arr = document.cookie.match(reg)) { -        return unescape(arr[2]); -    } else { -        return null; -    } + let arr,reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); + if (arr = document.cookie.match(reg)) { + return unescape(arr[2]); + } else { + return null; + } } function getCCN() { // let cookie = document.cookie; @@ -224,11 +224,15 @@ function dateFormat(fmt, date) { "q+": Math.floor((date.getMonth() + 3) / 3), "S": date.getMilliseconds() }; - if (/(y+)/.test(fmt)) - fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); - for (let k in o) - if (new RegExp("(" + k + ")").test(fmt)) - fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + fmt = fmt.replace(/(y+)/, (match, group) => { + return (date.getFullYear() + "").slice(4 - group.length); + }); + for (let k in o) { + fmt = fmt.replace(new RegExp("(" + k + ")"), (match, group) => { + const val = o[k]; + return group.length === 1 ? val : val.padStart(group.length, "0"); + }); + } return fmt; } @@ -282,7 +286,7 @@ function showMessageWindow(title, content, callback){ Notification.requestPermission(function(status) { var notice_ = new Notification(title, { body: content }); notice_.onclick = function() { - callback(); + callback(); } }); } @@ -313,13 +317,11 @@ function getTextareaPosition(element) { } // 否则处理为contenteditable元素 let cursorPos = 0; - // 兼容旧版IE if (document.selection) { const selectRange = document.selection.createRange(); const textRange = element.createTextRange(); const preCaretRange = textRange.duplicate(); - preCaretRange.moveToBookmark(selectRange.getBookmark()); preCaretRange.setEndPoint('EndToEnd', textRange); cursorPos = preCaretRange.text.length; @@ -327,17 +329,14 @@ function getTextareaPosition(element) { // 现代浏览器 else if (window.getSelection) { const selection = window.getSelection(); - if (selection.rangeCount > 0) { const range = selection.getRangeAt(0).cloneRange(); range.selectNodeContents(element); range.setEnd(selection.rangeCount > 0 ? selection.getRangeAt(0).endContainer : element, selection.rangeCount > 0 ? selection.getRangeAt(0).endOffset : 0); - cursorPos = range.toString().length; } } - return cursorPos; } @@ -403,15 +402,11 @@ function debounce(func, wait) { return function() { let context = this; let args = arguments; - if (timer) clearTimeout(timer); - let callNow = !timer; - timer = setTimeout(() => { timer = null; }, wait) - if (callNow) func.apply(context, args); } } @@ -458,9 +453,11 @@ function sheet2blob(sheet, sheetName) { var blob = new Blob([s2ab(wbout)], {type:"application/octet-stream"}); // 字符串转ArrayBuffer function s2ab(s) { - var buf = new ArrayBuffer(s.length); + if (typeof s !== 'string') return new ArrayBuffer(0); + var length = s.length; + var buf = new ArrayBuffer(length); var view = new Uint8Array(buf); - for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; + for (var i = 0; i < length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } return blob; @@ -473,19 +470,19 @@ function downloadFile(name, data) { save_link.href = urlObject.createObjectURL(export_blob); save_link.download = name; - var ev = document.createEvent("MouseEvents"); + var ev = document.createEvent("MouseEvents"); ev.initMouseEvent("click", true, false, unsafeWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null); save_link.dispatchEvent(ev); } function timeText2Ms(text) { let ret = 0; - let arr = text.split(":"); - if (arr.length === 1) { + let arr = text.split(":"), length = arr.length; + if (length === 1) { ret = Number(arr[0]); - } else if (arr.length === 2) { + } else if (length === 2) { ret = Number(arr[0]) * 60 + Number(arr[1]); - } else if (arr.length === 3) { + } else if (length === 3) { ret = Number(arr[0]) * 3600 + Number(arr[1]) * 60 + Number(arr[2]); } return ret * 1000; @@ -516,16 +513,19 @@ function getCsrfToken() { onload: function(response) { // 获取 Set-Cookie const setCookie = response.responseHeaders.match(/set-cookie:[^\n\r]+/gi); - // 从set-cookie中获取csrfToken - let csrfToken = ""; - for (const line of setCookie) { - const match = line.match(/cvl_csrf_token=([^;]+)/); - if (match) { - csrfToken = match[1]; // 返回提取到的 token - break; - } - } - resolve(csrfToken); + // 从set-cookie中获取csrfToken + let csrfToken = ""; + if (setCookie) { + for (let i = 0, len = setCookie.length; i < len; i++) { + const line = setCookie[i]; + const match = line.match(/cvl_csrf_token=([^;]+)/); + if (match) { + csrfToken = match[1]; // 返回提取到的 token + break; + } + } + } + resolve(csrfToken); }, onerror: function(err) { resolve(""); @@ -534,28 +534,496 @@ function getCsrfToken() { }); } +function _rawQuery(selector, isList, rethrow) { + if (typeof selector !== 'string') { + if (rethrow) throw new Error(`Invalid selector: "${selector}"`); + return isList ? [] : null; + } + let length = selector.length; + if (length === 0) { + if (rethrow) throw new Error(`Invalid selector: "${selector}"`); + return isList ? [] : null; + } + let firstCode = selector.charCodeAt(0); + if (firstCode <= 32 || selector.charCodeAt(length - 1) <= 32) { + selector = selector.trim(); + length = selector.length; + if (length === 0) { + if (rethrow) throw new Error(`Invalid selector: "${selector}"`); + return isList ? [] : null; + } + firstCode = selector.charCodeAt(0); + } + if (length > 1 && firstCode === 35) { + const value = selector.slice(1); + if (!/[\s>+~,.#:()[\]\\]/.test(value)) { + const element = document.getElementById(value); + return isList ? (element ? [element] : []) : element; + } + } else if (length > 1 && firstCode === 46) { + const value = selector.slice(1); + if (!/[\s>+~,.#:()[\]\\]/.test(value)) { + const list = document.getElementsByClassName(value); + return isList ? list : (list[0] || null); + } + } else if (/^[a-zA-Z][\w-]*$/.test(selector)) { + const list = document.getElementsByTagName(selector); + return isList ? list : (list[0] || null); + } + try { + return isList ? document.querySelectorAll(selector) + : document.querySelector(selector); + } catch (err) { + if (rethrow) throw err; + return isList ? [] : null; + } +} + function getValidDom(queryList) { - for (const query of queryList) { - let dom = null; - if (typeof query === "string") { - dom = document.querySelector(query); - } else { - dom = query; - } - if (dom) return dom; - } - return null; + if (queryList == null) return null; + if (typeof queryList === 'string') return _rawQuery(queryList); + const length = queryList.length >>> 0; + for (let i = 0; i < length; i++) { + const query = queryList[i]; + if (!query) continue; + if (typeof query === "string") { + const dom = _rawQuery(query); + if (dom) return dom; + } else if (query.nodeType) { + return query; + } else if (typeof query.length === "number" && query[0] && query[0].nodeType) { + return query[0]; + } + } + return null; } function getValidDomList(queryList) { - for (const query of queryList) { - let dom = []; - if (typeof query === "string") { - dom = document.querySelectorAll(query); - } else { - dom = query; - } - if (dom.length > 0) return dom; - } - return []; -} \ No newline at end of file + if (queryList == null) return []; + if (typeof queryList === 'string') return _rawQuery(queryList, true); + const length = queryList.length >>> 0; + for (let i = 0; i < length; i++) { + const query = queryList[i]; + if (!query) continue; + if (typeof query === "string") { + const list = _rawQuery(query, true); + if (list.length > 0) return list; + } else if (query.nodeType) { + return [query]; + } else if (typeof query.length === "number" && query[0] && query[0].nodeType) { + return query; + } + } + return []; +} + +/* + * gDomObserver - 全局 DOM 观察服务单例: + * - 复用单个 MutationObserver 实例观察 DOM 结构变化,避免重复创建观察器; + * - 提供 waitForElement(selector, timeout) 方法,返回 Promise,目标元素出现时 resolve,选择器无效或超时则 reject; + * - 提供 raceForElement(selectors, timeout) 方法,返回 Promise,最先出现的元素 resolve,选择器数组为空或超时则 reject; + * - 使用 Map 统一管理所有等待任务,相同 selector 的调用合并至同一等待组,DOM 变化时批量检查; + * - 按需启动观察服务以节省资源,任务全部完成时自动停止。 + */ +const gDomObserver = (() => { + let _observer = null, _rafId = null, _debug = false; + const _log = (...a) => _debug && console.log ("[gDomObserver]", ...a); + const _warn = (...a) => _debug && console.warn ("[gDomObserver]", ...a); + const _error = (...a) => _debug && console.error("[gDomObserver]", ...a); + const _pendingMap = new Map(); + class Task { + constructor(deadline, resolve, reject) { + this.deadline = deadline; + this.resolve = resolve; + this.reject = reject; + } + } + function _disconnect() { + if (_pendingMap.size === 0 && _observer) { + _observer.disconnect(); + _observer = null; + if (_rafId) { + cancelAnimationFrame(_rafId); + _rafId = null; + } + _log("DouyuEX gDomObserver: 所有任务完成,停止观察实例"); + } + } + function _parseTimeout(timeout) { + if (timeout == null) return null; + if (typeof timeout === "number") return timeout >= 0 ? timeout : null; + if (typeof timeout === "string") { + const num = parseFloat(timeout); + if (isNaN(num) || num < 0) return null; + if (/^\s*\d+(\.\d+)?\s*(s|sec|second|seconds)$/i.test(timeout)) return num * 1000; + if (/^\s*\d+(\.\d+)?\s*(m|min|minute|minutes)$/i.test(timeout)) return num * 60000; + return num; // 默认 ms + } + return null; + } + function _checkElements() { + if (_rafId) cancelAnimationFrame(_rafId); + _rafId = requestAnimationFrame(() => { + const current = performance.now(); + for (const [selector, group] of _pendingMap) { + const element = _rawQuery(selector); + for (const task of group.tasks) { + if (current >= task.deadline) { + _warn("DouyuEX gDomObserver: 计时到达上限,终止等待任务", selector); + task.reject(new Error(`DouyuEX waitForElement: Timeout - "${selector}"`)); + group.tasks.delete(task); + } else if (element) { + _log("DouyuEX gDomObserver: 目标元素出现,完成等待任务", element); + task.resolve(element); + group.tasks.delete(task); + } + } + if (group.tasks.size === 0) _pendingMap.delete(selector); + } + _disconnect(); + _rafId = null; + }); + } + return { + /* + * 异步等待指定选择器对应的元素出现在 DOM 中: + * @description + * - 选择器无效时,不创建监听任务,直接以 rejected 状态返回 Promise; + * - 若元素已存在,不创建监听任务,直接以 resolved 状态返回 Promise; + * - 若元素不存在,则使用 MutationObserver 监听 DOM 变化,元素出现时 resolve,超时或外部中止则 reject; + * - 相同 selector 的调用合并至同一等待组,各自持有独立的 Promise 和超时设定; + * - 未设置超时或传入 null 的任务将无限等待直到元素出现。 + * @param {string} selector - CSS 选择器字符串 + * @param {null|number|string} timeout - 超时时长: + * - `null`:永不超时(默认); + * - `number`:毫秒数,0 表示立即超时,负数则永不超时; + * - `string`:支持 `ms`、`s`、`m`,例如 `"500ms"`、`"2s"`、`"1m"`,解析为 0 则立即超时,负数或无法解析则永不超时。 + * @param {null|AbortSignal} signal - 可选的中止信号,触发后以 AbortError 拒绝 Promise + * @returns {Promise} - 目标元素出现时 resolve,选择器无效、超时或外部中止则 reject + * @example + * const controller = new AbortController(); + * gDomObserver.waitForElement('#id', 5000, controller.signal) // 等待 5 秒 + * .then(element => { console.log('元素已出现:', element); }) + * .catch(error => { console.error('选择器无效、等待超时或已取消:', error); }); + * // 主动取消: + * controller.abort(); + */ + waitForElement(selector, timeout = null, signal = null) { + if (signal && signal.aborted) { + _warn("DouyuEX gDomObserver: 信号已中止,拒绝创建任务", selector); + return Promise.reject(signal.reason || new DOMException("Aborted", "AbortError")); + } + const selectorTrimmed = typeof selector === "string" ? selector.trim() : ""; + let element; + try { + element = _rawQuery(selectorTrimmed, false, true); + } catch (err) { + _error("DouyuEX gDomObserver: 非法的选择器,拒绝创建任务", selector, err); + return Promise.reject(new Error(`DouyuEX waitForElement: Invalid selector - "${selector}", ${err.message}`)); + } + const existing = _pendingMap.get(selectorTrimmed); + if (element) { + if (existing) { + _log("DouyuEX gDomObserver: 目标元素存在,完成已有任务", element); + for (const task of existing.tasks) task.resolve(element); + _pendingMap.delete(selectorTrimmed); + _disconnect(); + } else { + _log("DouyuEX gDomObserver: 目标元素存在,直接返回结果", element); + } + return Promise.resolve(element); + } + const parsedTimeout = _parseTimeout(timeout), current = performance.now(); + if (parsedTimeout === 0) { + return Promise.reject(new Error(`DouyuEX waitForElement: Timeout (immediate) - "${selectorTrimmed}"`)); + } + const deadline = parsedTimeout == null ? Infinity : current + parsedTimeout; + const deadlineLabel = parsedTimeout == null ? "等待不限时长" : `等待时长: ${parsedTimeout}ms`; + let resolveFn, rejectFn; + const promise = new Promise((resolve, reject) => { resolveFn = resolve; rejectFn = reject; }); + const task = new Task(deadline, resolveFn, rejectFn); + if (signal) { + signal.addEventListener("abort", () => { + _warn("DouyuEX gDomObserver: 收到外部信号,终止等待任务", selectorTrimmed); + task.reject(signal.reason || new DOMException("Aborted", "AbortError")); + const group = _pendingMap.get(selectorTrimmed); + if (!group) return; + group.tasks.delete(task); + if (group.tasks.size === 0) _pendingMap.delete(selectorTrimmed); + _disconnect(); + }, { once: true }); + } + if (existing) { + _log("DouyuEX gDomObserver: 等待元素相同,添加新的任务", selectorTrimmed, deadlineLabel); + existing.tasks.add(task); + } else { + _pendingMap.set(selectorTrimmed, { tasks: new Set([task]) }); + if (!_observer) { + const root = document.body || document.documentElement || document; + _observer = new MutationObserver(_checkElements); + _observer.observe(root, { childList: true, subtree: true }); + _log("DouyuEX gDomObserver: 启动观察实例,创建首个任务", selectorTrimmed, deadlineLabel); + } else { + _log("DouyuEX gDomObserver: 复用观察实例,加入任务队列", selectorTrimmed, deadlineLabel); + } + } + return promise; + }, + /* + * 异步等待多个选择器中“最先出现”的那个元素: + * @description + * - 为各 selector 创建独立的等待任务,共享同一个 AbortController; + * - 超时统一由竞速层管理,与各等待任务的生命周期完全独立; + * - 任意元素率先出现后,立即取消其余所有等待任务; + * - 竞速超时后立即取消所有等待任务并 reject。 + * @param {string[]} selectors - CSS 选择器数组,例如 `["#id", ".item", "[data-x]"]` + * - 每个选择器独立等待,竞速结果返回最先出现的元素。 + * @param {null|number|string} timeout - 超时时长,格式与 waitForElement 的 timeout 相同 + * @returns {Promise<{selector: string, element: Element}>} - 返回的 Promise 中包含: + * - `selector`:最先出现元素对应的选择器; + * - `element`:最先出现的元素对象。 + * @example + * gDomObserver.raceForElement([".a", ".b"], 90000) + * .then(({ selector, element }) => { console.log("最先出现的是:", selector, element); }) + * .catch(error => { console.error("竞速失败:", error); }); + */ + raceForElement(selectors, timeout = null) { + if (!Array.isArray(selectors) || selectors.length === 0) { + _error("DouyuEX gDomObserver: 无效的选择器,竞速任务中止"); + return Promise.reject(new Error(`DouyuEX raceForElement: Empty array of selectors - ${selectors}`)); + } + const controller = new AbortController(), parsedTimeout = _parseTimeout(timeout); + let finished = false, rejectedCount = 0, timeoutId = null; + _log("DouyuEX gDomObserver: 启动竞速任务,等待元素列表", selectors); + return new Promise((resolve, reject) => { + if (parsedTimeout != null) { + timeoutId = setTimeout(() => { + if (finished) return; + finished = true; + _warn("DouyuEX gDomObserver: 超过最大时长,竞速任务失败"); + const timeoutError = new Error(`DouyuEX raceForElement: No elements appeared within the timeout - ${selectors}`); + controller.abort(timeoutError); + reject(timeoutError); + }, parsedTimeout); + } + for (const selector of selectors) { + this.waitForElement(selector, null, controller.signal).then(element => { + if (finished) return; + finished = true; + clearTimeout(timeoutId); + _log("DouyuEX gDomObserver: 竞速任务完成,首先出现元素", selector, element); + controller.abort(); + resolve({ selector, element }); + }).catch(err => { + if (finished) return; + if (++rejectedCount === selectors.length) { + finished = true; + clearTimeout(timeoutId); + _warn("DouyuEX gDomObserver: 无效的选择器,竞速任务失败", err); + reject(new Error(`DouyuEX raceForElement: All selectors are invalid - ${selectors}`)); + } + }); + } + }); + }, + /* + * 设置调试日志的开关状态: + * @description + * - 默认关闭,所有内部日志(log / warn / error)均不输出; + * - 开启后,内部运行状态、任务创建、元素匹配、超时及中止等事件均会输出至控制台; + * - 可在任意时刻调用,实时生效,不影响已有的等待任务。 + * @param {any} enabled - 是否开启调试日志,传入任意真值开启,假值关闭 + * @returns {void} + * @example + * gDomObserver.setDebug(true); // 开启,输出所有内部日志 + * gDomObserver.setDebug(false); // 关闭,静默运行(默认) + */ + setDebug(enabled) { _debug = !!enabled; }, + }; +})(); + +/* + * gHotkey - 全局快捷键服务单例: + * - 复用单个 keydown 监听器实例,避免重复绑定; + * - 支持普通按键、F1-F12 以及 Alt / Ctrl / Meta / Shift 的组合; + * - 提供 add / remove / enable / disable / list 等多种管理方法; + * - 相同快捷键可注册多个回调,自动合并,不会覆盖并按顺序触发; + * - 自动忽略输入框、文本域和可编辑区域的普通输入,避免误触。 + * @sample + * // 注册一个或多个快捷键 + * gHotkey.add("ctrl+f5", () => console.log("按下 Ctrl+F5")); + * gHotkey.add({ + * h: () => console.log("按下 H"), + * "ctrl+shift+x": () => console.log("按下 Ctrl+Shift+X") + * }); + * + * // 移除某个快捷键的某个回调 + * const fn = () => console.log("只移除这个回调"); + * gHotkey.add("ctrl+k", fn); + * gHotkey.remove("ctrl+k", fn); + * + * // 移除整个快捷键(所有回调一起删除) + * gHotkey.remove("ctrl+f5"); + * + * // 禁用某个快捷键(不会触发,但仍保留回调) + * gHotkey.disable("ctrl+s"); + * + * // 重新启用快捷键 + * gHotkey.enable("ctrl+s"); + * + * // 查看当前所有快捷键 + * console.table(gHotkey.list()); + */ +const gHotkey = (function () { + let _listener = null; + const _hotkeyMap = new Map(); + // 判断是否应忽略该事件 + function _ignoreEvent(e) { + const t = e.target, tag = t.tagName; + const isInput = t.isContentEditable || tag === "INPUT" || tag === "SELECT" || tag === "TEXTAREA"; + return isInput && !e.altKey && !e.ctrlKey && !e.metaKey; + } + // 判断用户按键是否匹配 + function _matchHotkey(e, item) { + return item.alt === e.altKey && + item.ctrl === e.ctrlKey && + item.meta === e.metaKey && + item.shift === e.shiftKey && + item.key === e.key.toLowerCase(); + } + return { + /* + * 注册快捷键 + * - 自动复用全局 keydown 监听器 + * - 同一个快捷键可绑定多个回调,按注册顺序依次执行 + * @param {string|Object} keyOrCombo + * - 字符串:单个快捷键,如 "h"、"ctrl+h"、"shift+f5" + * - 对象:批量注册,如 { h: callback1, x: callback2 } + * @param {Function} [callback] + * - 当 keyOrCombo 为字符串时,提供回调函数 + * - 当 keyOrCombo 为对象时忽略此参数 + */ + add(keyOrCombo, callback) { + // 支持对象批量注册 + if (typeof keyOrCombo === "object") { + for (const [hotkey, callback] of Object.entries(keyOrCombo)) { + try { + this.add(hotkey, callback); + } catch (err) { + console.warn(`gHotkey: Failed to add "${hotkey}" - ${err.message}`); + } + } + return; + } + if (typeof callback !== "function") { + throw new TypeError(`gHotkey.add: callback must be a function, got ${typeof callback}`); + } + keyOrCombo = keyOrCombo.toLowerCase().replace(/\s/g, ""); + const keys = keyOrCombo.split("+"); + // 快捷键相同时追加 callback + if (_hotkeyMap.has(keyOrCombo)) { + const existing = _hotkeyMap.get(keyOrCombo).callbacks; + if (!existing.includes(callback)) existing.push(callback); + } else { + const modSet = new Set(keys); + const key = keys.find(k => !["alt", "ctrl", "meta", "shift"].includes(k)); + if (!key) throw new Error(`gHotkey.add: Invalid hotkey "${keyOrCombo}", missing main key`); + _hotkeyMap.set(keyOrCombo, { + alt: modSet.has("alt"), + ctrl: modSet.has("ctrl"), + meta: modSet.has("meta"), + shift: modSet.has("shift"), + key: key, + callbacks: [callback], + enabled: true + }); + } + // 复用单例监听器 + if (_listener) return; + _listener = e => { + if (_ignoreEvent(e)) return; + for (const item of _hotkeyMap.values()) { + if (!item.enabled) continue; + if (_matchHotkey(e, item)) { + for (let i = 0, len = item.callbacks.length; i < len; i++) { + item.callbacks[i](e); + } + } + } + }; + document.addEventListener("keydown", _listener); + }, + /* + * 移除某个快捷键或某个回调 + * - 若某个快捷键所有回调被删空,则自动移除该快捷键 + * - 若所有快捷键都被移除,则自动解绑 keydown 监听器 + * @param {string} keyOrCombo - 快捷键,如 "ctrl+s" + * @param {Function} [callback] - 若提供,则仅移除该回调;若省略,则移除整个快捷键 + */ + remove(keyOrCombo, callback) { + keyOrCombo = keyOrCombo.toLowerCase().replace(/\s/g, ""); + const item = _hotkeyMap.get(keyOrCombo); + if (!item) return; + if (!callback) { + _hotkeyMap.delete(keyOrCombo); + } else { + let write = 0, callbacks = item.callbacks; + for (let read = 0, length = callbacks.length; read < length; read++) { + if (callbacks[read] !== callback) { + callbacks[write++] = callbacks[read]; + } + } + callbacks.length = write; + if (write === 0) { + _hotkeyMap.delete(keyOrCombo); + } + } + // 若无任何快捷键,自动解绑监听器 + if (_hotkeyMap.size === 0 && _listener) { + document.removeEventListener("keydown", _listener); + _listener = null; + } + }, + /* + * 禁用某个快捷键(不会触发,但仍保留回调) + * - 回调仍保留,可随时 enable 恢复 + * - 不会影响其他快捷键 + * @param {string} keyOrCombo - 快捷键,如 "ctrl+s" + */ + disable(keyOrCombo) { + keyOrCombo = keyOrCombo.toLowerCase().replace(/\s/g, ""); + const item = _hotkeyMap.get(keyOrCombo); + if (item) item.enabled = false; + }, + /* + * 启用某个快捷键(恢复触发) + * - 恢复之前被 disable 的快捷键,使其重新生效 + * @param {string} keyOrCombo - 快捷键,如 "ctrl+s" + */ + enable(keyOrCombo) { + keyOrCombo = keyOrCombo.toLowerCase().replace(/\s/g, ""); + const item = _hotkeyMap.get(keyOrCombo); + if (item) item.enabled = true; + }, + /* + * 列出所有快捷键 + * - 可用于调试或展示快捷键列表 + * 返回格式: + * [ + * { hotkey: "ctrl+s", callbacks: 2, enabled: true }, + * { hotkey: "h", callbacks: 1, enabled: false } + * ] + */ + list() { + const result = []; + for (const [keyOrCombo, { callbacks, enabled }] of _hotkeyMap.entries()) { + result.push({ + hotkey: keyOrCombo, + callbacks: callbacks.length, + enabled + }); + } + return result; + } + }; +})(); diff --git a/src/main.js b/src/main.js index 4ae6b1800..55865fd4b 100644 --- a/src/main.js +++ b/src/main.js @@ -6,18 +6,19 @@ // @version 2026.03.25.02 // @description 斗鱼直播间增强插件,功能:弹幕自动变色防检测循环发送 一键续牌 查看真实人数/查看主播数据 已播时长 一键签到(直播间/车队/鱼吧/客户端) 一键领取鱼粮(宝箱/气泡/任务) 一键寻宝 送出指定数量的礼物 一键清空背包 屏蔽广告 调节弹幕大小 自动更新 同屏画中画/多直播间小窗观看/可在斗鱼看多个平台直播(虎牙/b站) 获取真实直播流地址 自动抢礼物红包 背包信息扩展 简洁模式 夜间模式 开播提醒 幻神模式 关键词回复 关键词禁言 自动谢礼物 自动抢宝箱 弹幕右键信息扩展 防止下播自动跳转 影院模式 直播时间流控制 弹幕投票 直播滤镜 直播音频流 账号多开/切换 显示粉丝牌获取日期 月消费数据显示 弹幕时速 相机截图录制gif 全景播放器 斗鱼视频下载/弹幕ass下载 直播画面局部缩放 全站抽奖信息 直播音效增强 阻止P2P上传 显示贡献榜贡献值 恢复弹幕显示 斗鱼视频弹幕高能进度条 检测弹幕是否发送成功 查看主播配置信息 自动网页全屏 自动最高画质 弹幕无限收藏 收藏弹幕搜索 支持弹幕带图片 屏蔽弹幕背景 弹幕+1 房间VIP到期提醒 自动钓鱼 防止自动暂停直播 恢复已关闭鱼吧 弹幕小尾巴 屏蔽重复弹幕 画质增强 // @author 小淳 -// @match *://*.douyu.com/0* -// @match *://*.douyu.com/1* -// @match *://*.douyu.com/2* -// @match *://*.douyu.com/3* -// @match *://*.douyu.com/4* -// @match *://*.douyu.com/5* -// @match *://*.douyu.com/6* -// @match *://*.douyu.com/7* -// @match *://*.douyu.com/8* -// @match *://*.douyu.com/9* -// @match *://*.douyu.com/beta/* -// @match *://*.douyu.com/topic/* +// @match *://*.douyu.com/0* +// @match *://*.douyu.com/1* +// @match *://*.douyu.com/2* +// @match *://*.douyu.com/3* +// @match *://*.douyu.com/4* +// @match *://*.douyu.com/5* +// @match *://*.douyu.com/6* +// @match *://*.douyu.com/7* +// @match *://*.douyu.com/8* +// @match *://*.douyu.com/9* +// @match *://*.douyu.com/beta/* +// @match *://*.douyu.com/directory* +// @match *://*.douyu.com/topic/* // @match *://www.douyu.com/member/cp/getFansBadgeList // @match *://passport.douyu.com/* // @match *://msg.douyu.com/* @@ -65,13 +66,12 @@ function init() { initPkg_ShowDanmaku(); Update_checkVersion(); initKillP2P(); - initFullScreen(); - initHighestVideoQuality(); removeAD(); initPkg_Statistics(); initPkg_Console(); initPkg_Menu(); initPkg_FollowList(); + initPkg_HistoryList(); } function initPkg() { initPkg_DanmakuTail(); @@ -126,17 +126,14 @@ function initTimer() { function initStyles() { let style = document.createElement("style"); - style.appendChild( - document.createTextNode(` - body{position:relative;} - /*编译器标记 勿删*/ - `) - ); + style.textContent = ` + body{position:relative;} + /*编译器标记 勿删*/ + `; document.head.appendChild(style); } // 编译器标记 勿删 - (async function () { initRouter(window.location.href); })(); diff --git a/src/packages/BarragePanel/BarragePanel.js b/src/packages/BarragePanel/BarragePanel.js index a079e168e..b241aa580 100644 --- a/src/packages/BarragePanel/BarragePanel.js +++ b/src/packages/BarragePanel/BarragePanel.js @@ -34,7 +34,7 @@ function barragePanel__replaceImageDanmakuInPanel() { } function setBarragePanelCallBack() { - let a = new DomHook("#Ex_BarragePanel", true, (m) => { + new DomHook("#Ex_BarragePanel", true, function(m) { barragePanel__withGuard(() => { let isAttributes = false; if (m.length > 0) { @@ -90,14 +90,13 @@ function setBarragePanelCallBack() { } } }); - }); + }, true); - new DomHook("#Ex_BarragePanel", false, (m) => { + new DomHook("#Ex_BarragePanel", false, () => { barragePanel__withGuard(() => { barragePanel__replaceImageDanmakuInPanel(); }); }) - } function getUserFansMedal(userName) { diff --git a/src/packages/BarragePanel/BarragePanel_Tip.js b/src/packages/BarragePanel/BarragePanel_Tip.js index 597c59e68..3f9fefdce 100644 --- a/src/packages/BarragePanel/BarragePanel_Tip.js +++ b/src/packages/BarragePanel/BarragePanel_Tip.js @@ -3,7 +3,7 @@ function initPkg_BarragePanel_Tip() { } function setBarragePanelTipCallBack() { - let a = new DomHook("#comment-dzjy-container", false, (m) => { + new DomHook("#comment-dzjy-container", false, (m) => { if (m.length <= 0) { return; } @@ -16,25 +16,35 @@ function setBarragePanelTipCallBack() { } function renderBarragePanelTip() { - let a = document.createElement("div"); - a.style.display = "inline-block"; - const labelDoms = document.getElementsByClassName("labelfisrt-407af4"); if (labelDoms.length === 0) return; - const dom = labelDoms[0].parentElement; - dom.appendChild(a); + labelDoms[0].insertAdjacentHTML( + "afterend", + `

|

+
+1
` + ); + + const btnsContainer = labelDoms[0].closest(".btnscontainer-4e2ed0"); + const btnsRect = btnsContainer.getBoundingClientRect(); + const menuCenterViewportX = btnsRect.left + btnsRect.width / 2; + const menuHalfWidth = btnsContainer.offsetWidth / 2; + const initialMenuLeft = menuCenterViewportX - menuHalfWidth; + const initialMenuRight = menuCenterViewportX + menuHalfWidth; - a = document.createElement("p"); - a.className = "sugun-e3fbf6"; - a.innerText = "|"; - dom.appendChild(a); + const SCROLLBAR_OFFSET = 18; + const playerContainer = document.getElementById("js-player-main"); + const boundaryRect = (playerContainer || document.documentElement).getBoundingClientRect(); + const hasScrollbar = !playerContainer && document.documentElement.scrollWidth > document.documentElement.clientWidth; + const boundaryLeft = boundaryRect.left; + const boundaryRight = boundaryRect.right - (hasScrollbar && SCROLLBAR_OFFSET); - a = document.createElement("div"); - a.className = "labelfisrt-407af4 thirdBtn-06cde5 fourBtn-0845d4"; - a.id = "barrage-panel-tip__+1" - a.innerText = "+1"; - dom.appendChild(a); + const offsetX = (initialMenuLeft < boundaryLeft && boundaryLeft - initialMenuLeft) || + (initialMenuRight > boundaryRight && boundaryRight - initialMenuRight); + if(offsetX) { + btnsContainer.style.transform = `translateX(calc(-50% + ${offsetX}px))`; + btnsContainer.querySelector('.btnscontainerrect-6e7730').style.transform = `translateX(calc(-50% - ${offsetX}px))`; + } } function setBarragePanelTipFunc() { diff --git a/src/packages/CopyRealLive/CopyRealLive.js b/src/packages/CopyRealLive/CopyRealLive.js index c8acac965..fbce1b9c4 100644 --- a/src/packages/CopyRealLive/CopyRealLive.js +++ b/src/packages/CopyRealLive/CopyRealLive.js @@ -1,22 +1,28 @@ function initPkg_CopyRealLive() { - initPkg_CopyRealLive_Dom(); - initPkg_CopyRealLive_Func(); + const subTitleContainer = getValidDom([".subTitleContainer__-vzhr", ".Title-col"]) + initPkg_CopyRealLive_Dom(subTitleContainer); + initPkg_CopyRealLive_Func(subTitleContainer); } -function initPkg_CopyRealLive_Dom() { - CopyRealLive_insertIcon(); -} -function CopyRealLive_insertIcon() { - let a = document.createElement("div"); - a.className = "Title-blockInline"; - a.id = "copy-real-live"; - a.innerHTML = '
复制直播流
'; - let b = document.getElementsByClassName("Title-col")[4]; - if (b && b.childNodes.length > 1) { - b.insertBefore(a, b.childNodes[1]); - } else { - b = getValidDom([".subTitleContainer__-vzhr"]); - b.appendChild(a); +function initPkg_CopyRealLive_Dom(subTitleContainer) { + if (!subTitleContainer.querySelector('#copy-real-live')) { + subTitleContainer.insertAdjacentHTML( + "beforeend", + `
+
+
+
+ + + + + + 复制直播流 +
+
+
+
` + ); } } @@ -41,17 +47,14 @@ function CopyRealLive_copyUrl(qn) { }) } -function initPkg_CopyRealLive_Func() { - document.getElementById("copy-real-live").addEventListener("click", function() { - if (document.querySelectorAll(".tipItem-898596 > ul > li").length > 0) { - document.querySelectorAll(".tipItem-898596 > ul > li").forEach(item => { - if (item.className.includes("selected")) { - CopyRealLive_copyUrl(CopyRealLive_getQn(item.innerText)); - } - }) - } else { - CopyRealLive_copyUrl(0); +function initPkg_CopyRealLive_Func(subTitleContainer) { + subTitleContainer.querySelector('#copy-real-live').addEventListener("click", function() { + const item = document.getElementsByClassName("selected-ab049e")[0]; + if (item && item.closest('.tipItem-e17801')) { + CopyRealLive_copyUrl(CopyRealLive_getQn(item.textContent)); + return; } + CopyRealLive_copyUrl(0); }); let titNode = document.getElementsByClassName("RecommendViewTit-04ebd8"); diff --git a/src/packages/ExpandTool/ExpandTool.js b/src/packages/ExpandTool/ExpandTool.js index 1a117243b..63dfa5651 100644 --- a/src/packages/ExpandTool/ExpandTool.js +++ b/src/packages/ExpandTool/ExpandTool.js @@ -1,7 +1,6 @@ - function initPkg_ExpandTool() { initPkg_ExpandTool_Dom(); - initPkg_ExpandTool_Func(); + initPkg_ExpandTool_Func(); initPkg_ExpandTool_Module(); } @@ -12,39 +11,42 @@ function initPkg_ExpandTool_Module() { initPkg_ExpandTool_RedPacket_Room(); initPkg_ExpandTool_AutoFish(); initPkg_ExpandTool_ClearBag(); - initPkg_ExpandTool_SendGift(); - // initPkg_ExpandTool_BarrageSize(); + initPkg_ExpandTool_SendGift(); + // initPkg_ExpandTool_BarrageSize(); initPkg_ExpandTool_TabSwitch(); initPkg_ExpandTool_P2P(); - initPkg_ExpandTool_FullScreen(); + initPkg_ExpandTool_Player(); } function initPkg_ExpandTool_Dom() { // Dom初始化 ExpandTool_insertModal(); - ExpandTool_insertIcon(); - + ExpandTool_insertIcon(); } function ExpandTool_insertModal() { - let a = document.createElement("div"); - a.className = "extool"; - a.innerHTML = `
×
`; - - let b = document.getElementsByClassName("layout-Player-chat")[0]; - b.insertBefore(a, b.childNodes[0]); + document.getElementsByClassName("layout-Player-chat")[0].insertAdjacentHTML( + "afterbegin", + `
+
×
+
` + ); } function ExpandTool_insertIcon() { - let a = document.createElement("div"); - a.className = "extool-icon"; - a.innerHTML = ''; - - let b = document.getElementsByClassName("ex-panel__wrap")[0]; - b.insertBefore(a, b.childNodes[0]); - + document.getElementsByClassName("ex-panel__wrap")[0].insertAdjacentHTML( + "afterbegin", + `
+ + + + + + + + +
` + ); } - - function initPkg_ExpandTool_Func() { // 函数初始化 // 将onclick事件绑定在这里 @@ -52,12 +54,32 @@ function initPkg_ExpandTool_Func() { showExRightPanel("扩展功能"); }); - const closeBtn = document.getElementsByClassName("extool__close")[0]; - if (closeBtn) { - closeBtn.addEventListener("click", (e) => { - e.stopPropagation(); - const panel = document.getElementsByClassName("extool")[0]; - if (panel) panel.style.display = "none"; - }); - } + const closeBtn = document.getElementsByClassName("extool__close")[0]; + if (closeBtn) { + closeBtn.addEventListener("click", (e) => { + e.stopPropagation(); + const panel = document.getElementsByClassName("extool")[0]; + if (panel) panel.style.display = "none"; + }); + } +} + +let expandToolCache = null; +const EXPANDTOOL_KEY = "ExSave_ExpandTool"; +function initExpandToolCache() { + try { + expandToolCache = JSON.parse(localStorage.getItem(EXPANDTOOL_KEY)) || {}; + } catch (err) { + console.warn("DouyuEx: ExSave_ExpandTool JSON 解析失败", err); + expandToolCache = {}; + } +} +function saveData_ExpandTool(key, value) { + if (expandToolCache === null) initExpandToolCache(); + expandToolCache[key] = value; + localStorage.setItem(EXPANDTOOL_KEY, JSON.stringify(expandToolCache)); } +function loadData_ExpandTool(key) { + if (expandToolCache === null) initExpandToolCache(); + return expandToolCache[key]; +} \ No newline at end of file diff --git a/src/packages/ExpandTool/ExpandTool_FullScreen.css b/src/packages/ExpandTool/ExpandTool_FullScreen.css new file mode 100644 index 000000000..78558d1d6 --- /dev/null +++ b/src/packages/ExpandTool/ExpandTool_FullScreen.css @@ -0,0 +1,12 @@ +:fullscreen .stream__T55I3 { + top: var(--stage-info-height) !important; +} + +:fullscreen .large__pcRJn .stream__T55I3 { + --stage-info-height: 0; +} + +:fullscreen .InteractLayout, +:fullscreen .ToolbarGiftArea { + --text-color: var(--mantine-color-dark-5); +} \ No newline at end of file diff --git a/src/packages/ExpandTool/ExpandTool_FullScreen.js b/src/packages/ExpandTool/ExpandTool_FullScreen.js index 2158b1dae..4569dbde4 100644 --- a/src/packages/ExpandTool/ExpandTool_FullScreen.js +++ b/src/packages/ExpandTool/ExpandTool_FullScreen.js @@ -1,134 +1,157 @@ -function initPkg_ExpandTool_FullScreen() { - ExpandTool_FullScreen_insertDom(); - ExpandTool_FullScreen_insertFunc(); - ExpandTool_HighestVideoQuality_insertFunc(); - initPkg_ExpandTool_FullScreen_Set(); +function initPkg_ExpandTool_Player() { + initPkg_ExpandTool_Player_insertDom(); + initPkg_ExpandTool_FullScreenPage_Set(); + initPkg_ExpandTool_FullScreenPage_insertFunc(); initPkg_ExpandTool_HighestVideoQuality_Set(); + initPkg_ExpandTool_HighestVideoQuality_insertFunc(); } -function ExpandTool_FullScreen_insertDom() { - let a = document.createElement("span"); - // a.className = "extool__bsize"; - a.innerHTML = ''; - - let b = document.getElementsByClassName("extool")[0]; - b.insertBefore(a, b.childNodes[0]); +function initPkg_ExpandTool_Player_insertDom() { + document.getElementsByClassName("extool")[0].insertAdjacentHTML( + "afterbegin", + ` + + + + ` + ); } - -function getFullScreen() { - return document.getElementById("extool__fullscreen").checked; -} -function ExpandTool_FullScreen_insertFunc() { - document.getElementById("extool__fullscreen").addEventListener("click", function() { - saveData_FullScreen(); - if (getFullScreen()) { +function initPkg_ExpandTool_FullScreenPage_insertFunc() { + const fullscreenpage = document.getElementById("extool__fullscreenpage"); + const hideplayersidebar = document.getElementById("extool__hideplayersidebar"); + fullscreenpage.addEventListener("click", function() { + const isFullScreenPage = fullscreenpage.checked; + saveData_ExpandTool("isFullScreenPage", isFullScreenPage); + hideplayersidebar.disabled = !isFullScreenPage; + if (isFullScreenPage) { showMessage("刷新页面生效", "success"); } }); -} - -function saveData_FullScreen() { - let data = { - isFullScreen: getFullScreen() - } - localStorage.setItem("ExSave_FullScreen", JSON.stringify(data)); -} -function initPkg_ExpandTool_FullScreen_Set() { - // 设置初始化 - let ret = localStorage.getItem("ExSave_FullScreen"); - if (ret != null) { - let retJson = JSON.parse(ret); - if (retJson.isFullScreen) { - document.getElementById("extool__fullscreen").checked = retJson.isFullScreen; + hideplayersidebar.addEventListener("click", function() { + const isHidePlayerSidebar = hideplayersidebar.checked; + saveData_ExpandTool("isHidePlayerSidebar", isHidePlayerSidebar); + if (isHidePlayerSidebar) { + showMessage("刷新页面生效", "success"); } - } + }); + gHotkey.add("t", () => { + const toggleContainer = document.getElementsByClassName("toggle__P8TKM")[0]; + if (toggleContainer) { + toggleContainer.querySelector('button').click(); + } else if (document.body.classList.contains("is-fullScreen")) { + const stageContainer = document.getElementsByClassName("stage__D8VhO")[0]; + //const playerContainer = stageContainer.getElementsByClassName("player__jsy1T")[0]; + const fullScreenSendor = document.getElementsByClassName("fullScreenSendor-32a8a8")[0]; + const playerToolbar = document.getElementsByClassName("PlayerToolbar")[0]; + stageContainer.classList.toggle("large__pcRJn"); + //playerContainer.classList.toggle("hidden__Kqqgu"); + fullScreenSendor.classList.toggle("fullScreenSendorShow-fb7c32"); + playerToolbar.classList.toggle("is-playerstate1"); + playerToolbar.classList.toggle("is-playerstate2"); + } + }); } - -function initFullScreen() { - let ret = localStorage.getItem("ExSave_FullScreen"); - if (ret != null) { - let retJson = JSON.parse(ret); - if (retJson.isFullScreen) { - fullScreen(); +function initPkg_ExpandTool_FullScreenPage_Set() { + let isFullScreenPage = loadData_ExpandTool("isFullScreenPage"); + // 旧设置迁移 + if (isFullScreenPage === undefined) { + try { + const old = JSON.parse(localStorage.getItem("ExSave_FullScreen") || {}).isFullScreen; + isFullScreenPage = typeof old === "boolean" ? old : false; + } catch (err) { + isFullScreenPage = false; } - } + saveData_ExpandTool("isFullScreenPage", isFullScreenPage); + localStorage.removeItem("ExSave_FullScreen"); + } + // 设置初始化 + const hideplayersidebar = document.getElementById("extool__hideplayersidebar"); + if (isFullScreenPage) { + document.getElementById("extool__fullscreenpage").checked = true; + fullScreenpage(); + } else { + hideplayersidebar.disabled = true; + } + if (loadData_ExpandTool("isHidePlayerSidebar")) { + hideplayersidebar.checked = true; + } } -function fullScreen() { - let count = 0; - let intID1 = setInterval(() => { - count++; - if (count > 100) clearInterval(intID1); - if (getValidDom([".wfs-2a8e83", ".icon-c8be96"])) { - clearInterval(intID1); - let dom = document.querySelector("div.wfs-2a8e83"); - if (dom) { - dom.click(); - } else { - dom = document.querySelectorAll(".icon-c8be96"); - if (dom.length >= 2) { - // 因为网页全屏按钮在倒数第二个 - dom[dom.length - 2].click(); +function fullScreenpage() { + gDomObserver.raceForElement(['.is-fullScreen', '.wfs-2a8e83', '.icon-c8be96:has([d="M20 25h6v-6M14 7H8v6"])']).then(({ selector, element }) => { + if (selector !== '.is-fullScreen') { + //console.log("DouyuEx 自动网页全屏: 点击 fullScreenPageButton", element); + element.click(); + } else { + //console.log("DouyuEx 自动网页全屏: 已是全屏状态,跳过点击脚本", element); + } + if (document.getElementById("extool__hideplayersidebar").checked) { + gDomObserver.waitForElement('.toggle__P8TKM').then(toggleContainer => { + if (!toggleContainer.classList.contains("shrink__Sd0uK")) { + const toggleButton = toggleContainer.querySelector('button'); + //console.log("DouyuEx 自动折叠侧栏: 点击弹幕侧边栏折叠切换按钮", toggleButton); + toggleButton.click(); + } else { + //console.log("DouyuEx 自动折叠侧栏: 已是折叠状态,跳过点击脚本", toggleContainer); } - } + }); } - }, 1000); + }); } -function getHighestVideoQuality() { - return document.getElementById("extool__highestvideoquality").checked; -} -function ExpandTool_HighestVideoQuality_insertFunc() { - document.getElementById("extool__highestvideoquality").addEventListener("click", function() { - saveData_HighestVideoQuality(); - if (getHighestVideoQuality()) { +function initPkg_ExpandTool_HighestVideoQuality_insertFunc() { + const highestvideoquality = document.getElementById("extool__highestvideoquality"); + highestvideoquality.addEventListener("click", function() { + const isHighestVideoQuality = highestvideoquality.checked; + saveData_ExpandTool("isHighestVideoQuality", isHighestVideoQuality); + if (isHighestVideoQuality) { showMessage("刷新页面生效", "success"); } }); } -function saveData_HighestVideoQuality() { - let data = { - isHighestVideoQuality: getHighestVideoQuality() - } - localStorage.setItem("ExSave_HighestVideoQuality", JSON.stringify(data)); -} function initPkg_ExpandTool_HighestVideoQuality_Set() { - // 设置初始化 - let ret = localStorage.getItem("ExSave_HighestVideoQuality"); - if (ret != null) { - let retJson = JSON.parse(ret); - if (retJson.isHighestVideoQuality) { - document.getElementById("extool__highestvideoquality").checked = retJson.isHighestVideoQuality; + let isHighestVideoQuality = loadData_ExpandTool("isHighestVideoQuality"); + // 旧设置迁移 + if (isHighestVideoQuality === undefined) { + try { + const old = JSON.parse(localStorage.getItem("ExSave_HighestVideoQuality") || {}).isHighestVideoQuality; + isHighestVideoQuality = typeof old === "boolean" ? old : false; + } catch (err) { + isHighestVideoQuality = false; } - } -} - -function initHighestVideoQuality() { - let ret = localStorage.getItem("ExSave_HighestVideoQuality"); - if (ret != null) { - let retJson = JSON.parse(ret); - if (retJson.isHighestVideoQuality) { - highestVideoQuality(); - } - } + saveData_ExpandTool("isHighestVideoQuality", isHighestVideoQuality); + localStorage.removeItem("ExSave_HighestVideoQuality"); + } + // 设置初始化 + if (isHighestVideoQuality) { + document.getElementById("extool__highestvideoquality").checked = true; + highestVideoQuality(); + } } function highestVideoQuality() { - let count = 0; - let intID1 = setInterval(() => { - count++; - if (count > 100) clearInterval(intID1); - const qualityContainer = document.querySelector('[class^="tipItem-"]:has([value^="画质"])') || document.querySelector('[class^="tip-"]:has([value^="画质"])'); - if (qualityContainer) { - clearInterval(intID1); - const highestQualityOption = qualityContainer.querySelector('ul > li:first-child'); - if (highestQualityOption) { - const isAlreadySelected = highestQualityOption.matches('[class^="selected-"]'); - if (!isAlreadySelected) highestQualityOption.click(); + gDomObserver.waitForElement('.reload-0876b5').then(reloadDiv => { + //console.log("DouyuEx 自动最高画质: 检测到reloadDiv,直播已开启", reloadDiv); + let reloadDivDomHook = new DomHook(reloadDiv, true, () => { + if (reloadDiv.offsetParent !== null) { + //console.log("DouyuEx 自动最高画质: 直播流异常,点击reloadDiv", reloadDiv); + reloadDiv.click(); + return; } - } - }, 1000); + }, true); + gDomObserver.waitForElement('.selected-ab049e').then(selectedItem => { + const highestQualityOption = selectedItem.parentElement.querySelector(':first-child'); + if (highestQualityOption !== selectedItem) { + //console.log("DouyuEx 自动最高画质: 点击 highestQualityOption", highestQualityOption); + highestQualityOption.click(); + } else { + //console.log("DouyuEx 自动最高画质: 保持 highestQualityOption", highestQualityOption); + } + reloadDivDomHook.closeHook(); + reloadDivDomHook = null; + }); + }); } \ No newline at end of file diff --git a/src/packages/FollowList/FollowList.css b/src/packages/FollowList/FollowList.css new file mode 100644 index 000000000..c4158b343 --- /dev/null +++ b/src/packages/FollowList/FollowList.css @@ -0,0 +1,29 @@ +.Header-follow-listBox { + max-height: var(--followlist-max-height)!important; +} + +#followlist-toolbar { + color: grey; + cursor: default; + position: absolute; + top: 0px; + display: flex; + justify-content: space-between; + width: 100%; + padding: 0 5px; + box-sizing: border-box; +} + +#followlist-checkbox { + cursor: pointer; + display: flex; + align-items: center; +} + +#followlist-checkbox-input { + margin-right: 5px; +} + +.is-videoDynamic .followlist-longclick-tip { + display:none!important; +} \ No newline at end of file diff --git a/src/packages/FollowList/FollowList.js b/src/packages/FollowList/FollowList.js index 7b566ca60..d4654ae47 100644 --- a/src/packages/FollowList/FollowList.js +++ b/src/packages/FollowList/FollowList.js @@ -1,104 +1,93 @@ -let followListHook; function initPkg_FollowList() { - let intID = setInterval(() => { - if (getValidDom([".Header-follow-content", "#js-backpack-enter"])) { - followListHook = new DomHook(".Header-follow-content", false, handleFollowList); - clearInterval(intID); - } - }, 1000); + (function() { + const originalXHROpen = unsafeWindow.XMLHttpRequest.prototype.open; + unsafeWindow.XMLHttpRequest.prototype.open = function(method, url, ...args) { + if (typeof url === 'string' && url.endsWith('/wgapi/vodnc/center/follow/getSubDynamicVodListWithLive')) { + //console.log('DouyuEx 关注列表: 发现视频动态请求,修改URL...'); + url = url.replace('/getSubDynamicVodListWithLive', '/getSubDynamicVodList'); + this._isVideoDynamicListTarget = true; + } + return originalXHROpen.call(this, method, url, ...args); + }; + const originalXHRSend = unsafeWindow.XMLHttpRequest.prototype.send; + unsafeWindow.XMLHttpRequest.prototype.send = function(body) { + if (this._isVideoDynamicListTarget) { + //console.log('DouyuEx 关注列表: 修改视频动态请求上限为20...'); + try { + body = JSON.stringify({ ...JSON.parse(body || '{}'), limit: 20 }); + } catch (e) { + console.error('DouyuEx 关注列表: 解析视频动态body失败,使用原始body:', e); + } + } + return originalXHRSend.call(this, body); + }; + })(); + + gDomObserver.waitForElement('.Header-follow-content').then(followContent => { + new ResizeObserver(() => { + const followListBox = followContent.querySelector('.Header-follow-listBox'); + if (!followListBox || followListBox.childElementCount === 0) return; + handleFollowList(followListBox, followContent); + updateFollowList(followListBox); + }).observe(followContent); + }, 60000); } -function handleFollowList(m) { - let active = document.getElementsByClassName("Header-follow-tab is-active")[0].innerText; - if (active === "特别关注" || active === "视频动态") { - return; - } - let panel = document.getElementsByClassName("Header-follow-listWrap"); - if (panel.length == 0) { - return; +function handleFollowList(followListBox, followContent) { + const listBoxRect = followListBox.getBoundingClientRect(); + const dropMenuRect = followListBox.closest('.public-DropMenu-drop').getBoundingClientRect(); + const spaceAbove = listBoxRect.top; + const spaceBelow = dropMenuRect.bottom - listBoxRect.bottom; + const extraOffset = (document.documentElement.scrollWidth > document.documentElement.clientWidth) ? 22 : 16; + const maxHeightValue = `calc(100dvh - ${Math.round(spaceAbove + spaceBelow + extraOffset)}px)`; + if (maxHeightValue !== followContent.style.getPropertyValue('--followlist-max-height')) { + followContent.style.setProperty('--followlist-max-height', maxHeightValue); } - // panel[0].style.marginTop = "12px"; - document.getElementsByClassName("Header-follow-listBox")[0].style.display = "none"; - setNewFollowList(panel[0]); } -async function setNewFollowList(panel) { + +async function updateFollowList(followListBox) { let loadInCurrentPage = await GM_getValue("Ex_LoadInCurrentPage", false); - let followList = await getFollowList(); - if (followList.error != "0") { - return; - } - const FOLLOWLIST_LIMIT = 10; // 关注列表最多显示个数 - let limit = 0; - let html = ` -
- - 长按弹出同屏播放 -
- `; - let nowTime = Math.floor(Date.now()/1000); - for (let i = 0; i < followList.data.list.length; i++) { - let item = followList.data.list[i]; - if (item.show_status == "1" && item.videoLoop == "0") { - // 开播且非录播 - html += `
  • ${ item.nickname }

    ${ item.online }${ item.room_name }

    ${ item.nickname }已播${ formatSeconds(nowTime - Number(item.show_time)) }

  • ` - // html += `
  • ${ item.nickname }

    ${ item.online }${ item.room_name }

    ${ item.nickname }已播${ formatSeconds(nowTime - Number(item.show_time)) }

  • ` - limit++; - } - if (limit >= FOLLOWLIST_LIMIT) { - break; - } - } - panel.innerHTML += html; + const followListItems = followListBox.getElementsByClassName("DropPaneList"); - const loadInCurrentPageCheckbox = panel.querySelector('#loadInCurrentPageCheckbox'); - if (loadInCurrentPageCheckbox) { - loadInCurrentPageCheckbox.addEventListener("change", async (e) => { - const isChecked = e.target.checked; - await GM_setValue("Ex_LoadInCurrentPage", isChecked); - showMessage(`【关注列表】已${isChecked ? "开启" : "关闭"}当前页加载功能(${isChecked ? "当前页面直接加载关注的直播间" : "使用新网页打开关注的直播间"})`, "info"); + if (!followListBox.querySelector('#followlist-toolbar')) { + followListBox.insertAdjacentHTML( + 'afterbegin', + `
    + + 长按弹出同屏播放 +
    ` + ); + const checkbox = followListBox.querySelector('#followlist-checkbox-input'); + checkbox.checked = loadInCurrentPage; + checkbox.addEventListener("change", () => { + loadInCurrentPage = checkbox.checked; + for (let i = 0; i < followListItems.length; i++) { + const anchor = followListItems[i].querySelector('a[href^="/"]'); + if (anchor) anchor.target = loadInCurrentPage ? '_self' : '_blank'; + } + GM_setValue("Ex_LoadInCurrentPage", loadInCurrentPage); + showMessage(`【关注列表】已${loadInCurrentPage ? "开启" : "关闭"}当前页加载功能(${loadInCurrentPage ? "当前页面直接加载关注的直播间" : "使用新网页打开关注的直播间"})`, "info"); }); } - let followListItems = document.getElementsByClassName("ExFollowListItem"); for (let i = 0; i < followListItems.length; i++) { - let cclick = new CClick(followListItems[i]); - cclick.longClick(() => { - createNewVideo(videoPlayerArr.length, followListItems[i].getAttribute("rid"), "Douyu"); - document.querySelector(".Follow .public-DropMenu").className = "public-DropMenu"; - }); - cclick.click(async (event) => { - event.preventDefault(); - const shouldLoadInCurrentPage = await GM_getValue("Ex_LoadInCurrentPage", false); - const targetUrl = "https://www.douyu.com/" + followListItems[i].getAttribute("rid"); - if (shouldLoadInCurrentPage) { - window.location.href = targetUrl; // 在当前页加载 - } else { - openPage(targetUrl, true); // 在新页面打开 - } - }); - followListItems[i].addEventListener("mousedown", (event) => { - if (event.button == 1) { - // 鼠标中键 - openPage("https://www.douyu.com/" + followListItems[i].getAttribute("rid"), false); - } - }) + const list = followListItems[i]; + const anchor = list.querySelector('a'); + if (!anchor || anchor._enhanced) continue; + anchor._enhanced = true; + const href = anchor.getAttribute('href'); + if (!href || !href.startsWith('/')) continue; + anchor.target = loadInCurrentPage ? '_self' : '_blank'; + if (list.classList.contains("FollowList")) { + const roomId = href.split('/').pop(); + if (!roomId) continue; + new CClick(anchor).longClick(() => { + createNewVideo(videoPlayerArr.length, roomId, "Douyu"); + followListBox.closest(".public-DropMenu").className = "public-DropMenu"; + }); + } } -} - -function getFollowList() { - return new Promise(resolve => { - fetch("https://www.douyu.com/wgapi/livenc/liveweb/follow/list?sort=1&cid1=0", { - method: 'GET', - mode: 'no-cors', - credentials: 'include', - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - }).then(res => { - return res.json(); - }).then(ret => { - resolve(ret); - }) - }) } \ No newline at end of file diff --git a/src/packages/HistoryList/HistoryList.css b/src/packages/HistoryList/HistoryList.css new file mode 100644 index 000000000..f88e19cf3 --- /dev/null +++ b/src/packages/HistoryList/HistoryList.css @@ -0,0 +1,5 @@ +.Header-history-content .DropPane-drop { + max-height: var(--historylist-max-height); + overflow: auto; + scrollbar-width: none; +} \ No newline at end of file diff --git a/src/packages/HistoryList/HistoryList.js b/src/packages/HistoryList/HistoryList.js new file mode 100644 index 000000000..aed7624a3 --- /dev/null +++ b/src/packages/HistoryList/HistoryList.js @@ -0,0 +1,37 @@ +function initPkg_HistoryList() { + (function() { + const originalXHROpen = unsafeWindow.XMLHttpRequest.prototype.open; + unsafeWindow.XMLHttpRequest.prototype.open = function(method, url, ...args) { + if (typeof url === 'string' && /\/(japi\/watchHistory\/apinc\/getHistoryList|wgapi\/vodnc\/front\/live\/history\/getList)/.test(url)) { + //console.log('DouyuEx 历史列表: 发现历史列表请求,修改URL...'); + try { + url = url.replace(/([?&](?:num|limit))=\d+/, '$1=99'); + //console.log('DouyuEx 历史列表: 已将历史列表请求数量改为 99'); + } catch (e) { + console.error('DouyuEx 历史列表: 修改历史列表请求数量失败', e); + } + } + return originalXHROpen.call(this, method, url, ...args); + }; + })(); + + gDomObserver.waitForElement('.Header-history-content').then(historyContent => { + new ResizeObserver(() => { + const dropPane = historyContent.querySelector('.DropPane-drop'); + if (!dropPane || dropPane.childElementCount === 0) return; + handleHistoryList(dropPane, historyContent); + }).observe(historyContent); + }); +} + +function handleHistoryList(dropPane, historyContent) { + const dropPaneRect = dropPane.getBoundingClientRect(); + const dropMenuRect = dropPane.closest('.public-DropMenu-drop').getBoundingClientRect(); + const spaceAbove = dropPaneRect.top; + const spaceBelow = dropMenuRect.bottom - dropPaneRect.bottom; + const extraOffset = (document.documentElement.scrollWidth > document.documentElement.clientWidth) ? 22: 16; + const maxHeightValue = `calc(100dvh - ${Math.round(spaceAbove + spaceBelow + extraOffset)}px)`; + if (maxHeightValue !== historyContent.style.getPropertyValue("--historylist-max-height")) { + historyContent.style.setProperty('--historylist-max-height', maxHeightValue); + } +} \ No newline at end of file diff --git a/src/packages/ImageDanmaku/ImageDanmaku.js b/src/packages/ImageDanmaku/ImageDanmaku.js index 722a2315c..995da7fc9 100644 --- a/src/packages/ImageDanmaku/ImageDanmaku.js +++ b/src/packages/ImageDanmaku/ImageDanmaku.js @@ -20,8 +20,8 @@ function initPkg_ImageDanmaku_Func() { document.addEventListener("paste", async (event) => { if (document.activeElement !== chatDom) return; const items = (event.clipboardData || event.originalEvent.clipboardData).items; - for (let index in items) { - let item = items[index]; + for (let i = 0, len = items.length; i < len; i++) { + let item = items[i]; if (item.kind === "file" && item.type.startsWith("image/")) { chatDom.value += `[DouyuEx图片上传中]`; let blob = item.getAsFile(); diff --git a/src/packages/Refresh/Refresh.js b/src/packages/Refresh/Refresh.js index 9d192f80e..4c75de125 100644 --- a/src/packages/Refresh/Refresh.js +++ b/src/packages/Refresh/Refresh.js @@ -1,25 +1,27 @@ function initPkg_Refresh() { - initPkg_Refresh_BarrageFrame(); - initPkg_Refresh_Video(); initPkg_Refresh_Barrage(); + initPkg_Refresh_Player(); } -function saveData_Refresh() { - // 此处为保存简洁模式的数据,请在每次操作后都调用这个函数以保存状态 - // 数据结构 - // {功能1:{子功能1:{}}} - // 每个子模块需要提供相应的返回数据函数 - let data = { - barrageFrame: { - status: refresh_BarrageFrame_getStatus(), - }, - video: { - status: refresh_Video_getStatus(), - }, - barrage: { - status: refresh_Barrage_getStatus(), - } +let refreshCache = null; +const REFRESH_KEY = "ExSave_Refresh"; +function initRefreshCache() { + try { + refreshCache = JSON.parse(localStorage.getItem(REFRESH_KEY)) || {}; + } catch (err) { + console.warn("DouyuEx: ExSave_Refresh JSON 解析失败", err); + refreshCache = {}; } - - localStorage.setItem("ExSave_Refresh", JSON.stringify(data)); // 存储弹幕列表 +} +function saveData_Refresh() { + if (refreshCache == null) initRefreshCache(); + refreshCache.playerSimple = { status: document.body.classList.contains("is-playerSimple") }; + refreshCache.prefixHidden = { status: document.body.classList.contains("is-prefixHidden") }; + refreshCache.rankHidden = { status: document.body.classList.contains("is-rankHidden") }; + localStorage.setItem(REFRESH_KEY, JSON.stringify(refreshCache)); +} +function loadData_Refresh(key) { + if (refreshCache == null) initRefreshCache(); + const { status = false } = refreshCache[key] || {}; + return status; } \ No newline at end of file diff --git a/src/packages/Refresh/Refresh_Barrage.css b/src/packages/Refresh/Refresh_Barrage.css new file mode 100644 index 000000000..c241909a9 --- /dev/null +++ b/src/packages/Refresh/Refresh_Barrage.css @@ -0,0 +1,89 @@ +.Barrage-toolbar { + opacity: 0.8; +} + +.Barrage-toolbarBtn { + display: inline-flex; + align-items: center; + vertical-align: top; + margin: 0 2px; + padding: 0 8px; + height: 22px; + line-height: 21px; + background-color: #fff; + border: 1px solid #e5e4e4; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + cursor: pointer; + user-select: none; +} + +.live-next-body .Barrage-toolbarBtn { + background-color: var(--front-background-color); + border: 1px solid var(--front-border-color); +} + +#btn-prefixHidden__svg, +#btn-rankHidden__svg { + vertical-align: middle; +} + +#btn-prefixHidden__text, +#btn-rankHidden__text { + vertical-align: top; +} + +.is-prefixHidden .UserCsgoGameDataMedal, +.is-prefixHidden .Barrage-honor, +.is-prefixHidden .Barrage-listItem .Barrage-icon, +.is-prefixHidden .Barrage-listItem .FansMedal.is-made, +.is-prefixHidden .Barrage-listItem .RoomLevel, +.is-prefixHidden .Barrage-listItem .Motor, +.is-prefixHidden .Barrage-listItem .ChatAchievement, +.is-prefixHidden .Barrage-listItem .Barrage-hiIcon, +.is-prefixHidden .Barrage-listItem .Medal, +.is-prefixHidden .Barrage-listItem .MatchSystemTeamMedal, +/* .is-prefixHidden .Barrage-listItem .UserLevel */ +.is-prefixHidden .Barrage-listItem .Baby, +.is-prefixHidden .FansMedalWrap { + display: none !important; +} + +.is-prefixHidden #btn-prefixHidden, +.is-rankHidden #btn-rankHidden { + background: linear-gradient(180deg, rgb(38, 169, 235), rgb(18, 150, 219)); + border-color: rgb(18, 150, 219); + box-shadow: 0 0 0 2px rgba(18, 150, 219, .22), 0 8px 16px rgba(18, 150, 219, .28); + font-weight: 500; +} + +.is-prefixHidden #btn-prefixHidden:hover, +.is-rankHidden #btn-rankHidden:hover { + box-shadow: 0 0 0 2px rgba(18, 150, 219, .28), 0 10px 18px rgba(18, 150, 219, .36); +} + +.is-prefixHidden #btn-prefixHidden::after, +.is-rankHidden #btn-rankHidden::after { + content: ""; + width: 6px; + height: 6px; + margin-left: 6px; + border-radius: 999px; + background: rgba(255, 255, 255, .95); + box-shadow: 0 0 0 2px rgba(255, 255, 255, .22); +} + +.is-prefixHidden #btn-prefixHidden__text, +.is-rankHidden #btn-rankHidden__text { + color: #ccc; +} + +.is-rankHidden .layout-Player-rank, +.is-rankHidden #js-room-activity { + display: none !important; +} + +.is-rankHidden .Barrage { + top: 0 !important; +} \ No newline at end of file diff --git a/src/packages/Refresh/Refresh_Barrage.js b/src/packages/Refresh/Refresh_Barrage.js index fd68aaf18..de05d580c 100644 --- a/src/packages/Refresh/Refresh_Barrage.js +++ b/src/packages/Refresh/Refresh_Barrage.js @@ -1,97 +1,90 @@ -let current_barrage_status = 0; // 0没被简化 1被简化 - function initPkg_Refresh_Barrage() { - initPkg_Refresh_Barrage_Dom(); - initPkg_Refresh_Barrage_Func(); - initPkg_Refresh_Barrage_Set(); + gDomObserver.waitForElement('.Barrage-toolbar').then(toolbar => { + initPkg_Refresh_Barrage_Dom(toolbar); + initPkg_Refresh_Barrage_Func(toolbar); + initPkg_Refresh_Barrage_Set(); + }); } -function initPkg_Refresh_Barrage_Dom() { - Refresh_Barrage_insertIcon(); -} -function Refresh_Barrage_insertIcon() { - let a = document.createElement("a"); - a.className = "refresh-barrage"; - a.id = "refresh-barrage"; - a.innerHTML = '前缀'; - let b = document.getElementsByClassName("Barrage-toolbar")[0]; - b.insertBefore(a, b.childNodes[0]); +function initPkg_Refresh_Barrage_Dom(toolbar) { + if (!toolbar.querySelector(".refresh-barrage")) { + toolbar.insertAdjacentHTML( + "afterbegin", + ` + + + + + 隐藏前缀 + + + + + + + 隐藏榜单 + ` + ); + } } -function initPkg_Refresh_Barrage_Func() { - document.getElementById("refresh-barrage").addEventListener("click", function() { - if (current_barrage_status == 0) { - PostbirdAlertBox.confirm({ - 'title': '提示', - 'content': '是否屏蔽弹幕前缀(如粉丝牌、钻粉、贵族等标志)', - 'okBtn': '确定', - 'cancelBtn': '取消', - 'onConfirm': function () { - setRefreshBarrage(); - saveData_Refresh(); - }, - 'onCancel': function () { - } - }); - } else { - cancelRefreshBarrage(); - saveData_Refresh(); +function initPkg_Refresh_Barrage_Func(toolbar) { + toolbar.addEventListener("click", e => { + if (e.target.closest("#btn-prefixHidden")) { + if (document.body.classList.contains("is-prefixHidden")) { + document.body.classList.remove("is-prefixHidden"); + saveData_Refresh(); + } else { + PostbirdAlertBox.confirm({ + 'title': '提示', + 'content': '是否屏蔽弹幕前缀(如粉丝牌、钻粉、贵族等标志)', + 'okBtn': '确定', + 'cancelBtn': '取消', + 'onConfirm': function () { + document.body.classList.add("is-prefixHidden"); + saveData_Refresh(); + }, + 'onCancel': function () { + } + }); + } + } else if (e.target.closest("#btn-rankHidden")) { + if (document.body.classList.contains("is-rankHidden")) { + document.body.classList.remove("is-rankHidden"); + saveData_Refresh(); + } else { + PostbirdAlertBox.confirm({ + 'title': '提示', + 'content': '是否拉高弹幕框,隐藏日榜周榜', + 'okBtn': '确定', + 'cancelBtn': '取消', + 'onConfirm': function () { + document.body.classList.add("is-rankHidden"); + saveData_Refresh(); + }, + 'onCancel': function () { + } + }); + } } }); -} - - -function refresh_Barrage_getStatus() { - if (current_barrage_status == 1) { - // 被简化 - return true; - } else { - // 没被简化 - return false; - } + gHotkey.add({ + "x": () => { + document.body.classList.toggle("is-prefixHidden"); + saveData_Refresh(); + }, + "g": () => { + document.body.classList.toggle("is-rankHidden"); + saveData_Refresh(); + }, + }); } function initPkg_Refresh_Barrage_Set() { - let ret = localStorage.getItem("ExSave_Refresh"); - if (ret != null) { - let retJson = JSON.parse(ret); - if ("barrage" in retJson == false) { - retJson.barrage = {status: false}; - } - if (retJson.barrage.status == true) { - setRefreshBarrage(); - } + if (loadData_Refresh("prefixHidden")) { + document.body.classList.add("is-prefixHidden"); } -} - -function setRefreshBarrage() { - let cssText = ` - .UserCsgoGameDataMedal,.Barrage-honor,.Barrage-listItem .Barrage-icon,.Barrage-listItem .FansMedal.is-made,.Barrage-listItem .RoomLevel,.Barrage-listItem .Motor,.Barrage-listItem .ChatAchievement,.Barrage-listItem .Barrage-hiIcon,.Barrage-listItem .Medal,.Barrage-listItem .MatchSystemTeamMedal{display:none !important;} - /*.Barrage-listItem .UserLevel{display:none !important;}*/ - .Barrage-listItem .Baby{display:none !important;} - .FansMedalWrap{display:none !important;} - `; - StyleHook_set("Ex_Style_RefreshBarrage", cssText); - current_barrage_status = 1; - document.getElementById("refresh-barrage").classList.add("ex-active"); - document.getElementById("refresh-barrage__text").style.color = "#fff"; - document.getElementById("refresh-barrage__text").innerText = "前缀"; - let svg = document.getElementById("refresh-barrage__svg"); - if (svg) { - let p = svg.getElementsByTagName("path")[0]; - if (p) p.setAttribute("fill", "#ffffff"); + if (loadData_Refresh("rankHidden")) { + document.body.classList.add("is-rankHidden"); } -} - -function cancelRefreshBarrage() { - StyleHook_remove("Ex_Style_RefreshBarrage"); - current_barrage_status = 0; - document.getElementById("refresh-barrage").classList.remove("ex-active"); - document.getElementById("refresh-barrage__text").style.color = ""; - document.getElementById("refresh-barrage__text").innerText = "前缀"; - let svg = document.getElementById("refresh-barrage__svg"); - if (svg) { - let p = svg.getElementsByTagName("path")[0]; - if (p) p.setAttribute("fill", "#AFAFAF"); - } -} +} \ No newline at end of file diff --git a/src/packages/Refresh/Refresh_BarrageFrame.js b/src/packages/Refresh/Refresh_BarrageFrame.js deleted file mode 100644 index d7eb54e40..000000000 --- a/src/packages/Refresh/Refresh_BarrageFrame.js +++ /dev/null @@ -1,100 +0,0 @@ -function initPkg_Refresh_BarrageFrame() { - initPkg_Refresh_BarrageFrame_Dom(); - initPkg_Refresh_BarrageFrame_Func(); - initPkg_Refresh_BarrageFrame_Set(); -} - -function initPkg_Refresh_BarrageFrame_Dom() { - Refresh_BarrageFrame_insertIcon(); -} -function Refresh_BarrageFrame_insertIcon() { - let a = document.createElement("a"); - a.className = "refresh-barrage"; - a.id = "refresh-barrage-frame"; - a.innerHTML = '拉高'; - let b = document.getElementsByClassName("Barrage-toolbar")[0]; - b.insertBefore(a, b.childNodes[0]); -} - -function initPkg_Refresh_BarrageFrame_Func() { - document.getElementById("refresh-barrage-frame").addEventListener("click", function() { - let dom_rank = document.getElementsByClassName("layout-Player-rank")[0]; - let dom_activity = document.getElementById("js-room-activity"); - let dom_topBarrage = document.getElementsByClassName("Barrage")[0]; - if (dom_rank.style.display == "none") { - // 被拉高 - dom_rank.style.display = "block"; - dom_activity.style.display = "block"; - dom_topBarrage.className = "Barrage"; - document.getElementById("refresh-barrage-frame__text").innerText = "拉高"; - document.getElementById("refresh-barrage-frame").classList.remove("ex-active"); - document.getElementById("refresh-barrage-frame__text").style.color = ""; - let svg = document.getElementById("refresh-barrage-frame__svg"); - if (svg) { - let p = svg.getElementsByTagName("path")[0]; - if (p) p.setAttribute("fill", "#AFAFAF"); - } - saveData_Refresh(); - - } else { - PostbirdAlertBox.confirm({ - 'title': '提示', - 'content': '是否拉高弹幕框,隐藏日榜周榜', - 'okBtn': '确定', - 'cancelBtn': '取消', - 'onConfirm': function () { - dom_rank.style.display = "none"; - dom_activity.style.display = "none"; - dom_topBarrage.className = "Barrage top-0-important"; - document.getElementById("refresh-barrage-frame__text").innerText = "拉高"; - document.getElementById("refresh-barrage-frame").classList.add("ex-active"); - document.getElementById("refresh-barrage-frame__text").style.color = "#fff"; - let svg = document.getElementById("refresh-barrage-frame__svg"); - if (svg) { - let p = svg.getElementsByTagName("path")[0]; - if (p) p.setAttribute("fill", "#ffffff"); - } - saveData_Refresh(); - }, - 'onCancel': function () { - } - }); - } - }); -} - - -function refresh_BarrageFrame_getStatus() { - let dom_rank = document.getElementsByClassName("layout-Player-rank")[0]; - if (dom_rank.style.display == "none") { - // 被拉高 - return true; - } else { - // 没拉高 - return false; - } -} - -function initPkg_Refresh_BarrageFrame_Set() { - let ret = localStorage.getItem("ExSave_Refresh"); - if (ret != null) { - let retJson = JSON.parse(ret); - if ("barrageFrame" in retJson == false) { - retJson.barrageFrame = {status: false}; - } - if (retJson.barrageFrame.status == true) { - let dom_rank = document.getElementsByClassName("layout-Player-rank")[0]; - let dom_activity = document.getElementById("js-room-activity"); - dom_rank.style.display = "none"; - dom_activity.style.display = "none"; - document.getElementById("refresh-barrage-frame__text").innerText = "拉高"; - document.getElementById("refresh-barrage-frame").classList.add("ex-active"); - document.getElementById("refresh-barrage-frame__text").style.color = "#fff"; - let svg = document.getElementById("refresh-barrage-frame__svg"); - if (svg) { - let p = svg.getElementsByTagName("path")[0]; - if (p) p.setAttribute("fill", "#ffffff"); - } - } - } -} diff --git a/src/packages/Refresh/Refresh_Video.css b/src/packages/Refresh/Refresh_Video.css index 6588ba038..0e7b7e15a 100644 --- a/src/packages/Refresh/Refresh_Video.css +++ b/src/packages/Refresh/Refresh_Video.css @@ -1,4 +1,27 @@ -/* #refresh-video { +#dialog-playerSimple { + position:absolute; + right:18px; + bottom:58px; + width:32px; + height:32px; + border-radius:8px; + display:flex; + align-items:center; + justify-content:center; + background:rgba(0,0,0,.55); + color:#fff; + z-index:9999; + cursor:pointer; + user-select:none; + font-size:18px; + line-height:32px; + opacity:0; + transform:scale(.9); + transition:opacity .15s ease,transform .15s ease,background-color .15s ease; + pointer-events:none; +} + +/* #menu-playerSimple { float: left; width: 24px; height: 24px; @@ -7,53 +30,56 @@ background-size: contain; } */ -.refresh-barrage { - display: inline-flex; - align-items: center; - vertical-align: top; - margin: 0 2px; - padding: 0 8px; - height: 22px; - line-height: 21px; - background-color: #fff; - border: 1px solid #e5e4e4; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - cursor: pointer; - user-select: none; +#item-playerSimple__path { + display: none; +} + +/* Player Simple Mode Styles */ +.is-playerSimple .stream__T55I3, +.is-playerSimple .layout-Player-video { + bottom: 0 !important; + z-index: 75 !important; +} + +.simple-show #dialog-playerSimple { + opacity: 1; + transform: scale(1); + pointer-events: auto; } -.refresh-barrage.ex-active { - background: linear-gradient(180deg, rgb(38, 169, 235), rgb(18, 150, 219)); - border-color: rgb(18, 150, 219); - box-shadow: 0 0 0 2px rgba(18, 150, 219, .22), 0 8px 16px rgba(18, 150, 219, .28); - font-weight: 700; +.simple-hover #dialog-playerSimple{ + transform: scale(1.08); + background-color: rgba(0,0,0,.7); } -.refresh-barrage.ex-active:hover { - box-shadow: 0 0 0 2px rgba(18, 150, 219, .28), 0 10px 18px rgba(18, 150, 219, .36); +.is-playerSimple #menu-playerSimple::before { + content: "✓ "; } -.refresh-barrage.ex-active::after { - content: ""; - width: 6px; - height: 6px; - margin-left: 6px; - border-radius: 999px; - background: rgba(255, 255, 255, .95); - box-shadow: 0 0 0 2px rgba(255, 255, 255, .22); +.is-playerSimple .case__f4yex { + padding-bottom: 0 !important; } -.live-next-body .refresh-barrage { - background-color: var(--front-background-color); - border: 1px solid var(--front-border-color); +.is-playerSimple #item-playerSimple__rect { + opacity: 1; } -#refresh-barrage__svg { - vertical-align: middle; +.is-playerSimple #item-playerSimple__path { + display: block; } -.top-0-important { - top: 0 !important; +.is-playerSimple .PlayerToolbar-ContentRow { + visibility: hidden !important; } + +.is-playerSimple .PELact, +.is-playerSimple .pushTower-wrapper-gf1HG, +.is-playerSimple .PkView-9f6a2c, +.is-playerSimple .MorePk, +.is-playerSimple .RandomPKBar, +.is-playerSimple .LiveRoomLoopVideo, +.is-playerSimple .LiveRoomDianzan, +.is-playerSimple .maiMaitView-68e80c, +.is-playerSimple .PkView { + display: none !important; +} \ No newline at end of file diff --git a/src/packages/Refresh/Refresh_Video.js b/src/packages/Refresh/Refresh_Video.js index 7f13935e0..a4e0a9334 100644 --- a/src/packages/Refresh/Refresh_Video.js +++ b/src/packages/Refresh/Refresh_Video.js @@ -1,47 +1,54 @@ -let video_num = 0; -function initPkg_Refresh_Video() { - let timer = setInterval(() => { - const controlbar = getValidDom([".right-e7ea5d", ".right-17e251"]); - if (controlbar) { - clearInterval(timer); - initPkg_Refresh_Video_Dom(); - initPkg_Refresh_Video_Func(); - initPkg_Refresh_Video_Set(); - } - video_num++; - if (video_num >= 100) { - clearInterval(timer); - } - }, 1500); +function initPkg_Refresh_Player() { + Promise.all([ + gDomObserver.waitForElement('#js-player-dialog'), + gDomObserver.waitForElement('.menu-da2a9e'), + gDomObserver.waitForElement('.shieldSettingPanel-074097'), + ]).then(([playerDialog, playerMenu, settingPanel]) => { + initPkg_Refresh_Player_Dom(playerDialog, playerMenu, settingPanel); + initPkg_Refresh_Player_Func(playerDialog, playerMenu, settingPanel); + initPkg_Refresh_Player_Set(); + }).catch(err => { + console.error('DouyuEx 简洁模式: 初始化简洁模式失败:', err); + }); } -function initPkg_Refresh_Video_Dom() { - Refresh_Video_insertIcon(); -} -function Refresh_Video_insertIcon() { - let a = document.createElement("li"); - a.id = "refresh-video"; - a.innerText = "简洁模式"; - let b = document.getElementsByClassName("menu-da2a9e")[0]; - b.insertBefore(a, b.childNodes[b.childNodes.length -1]); +function initPkg_Refresh_Player_Dom(playerDialog, playerMenu, settingPanel) { + if (!playerDialog.querySelector("#dialog-playerSimple")) { + playerDialog.insertAdjacentHTML( + "afterbegin", + `
    ` + ); + } + + if (!playerMenu.querySelector("#menu-playerSimple")) { + playerMenu.insertAdjacentHTML( + "beforeend", + `` + ); + } - if (!document.getElementById("refresh-video3")) { - a = document.createElement("div"); - a.id = "refresh-video3"; - a.title = "开启简洁模式"; - a.innerText = "简"; - a.style = "position:absolute;right:18px;bottom:58px;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.55);color:#fff;z-index:9999;cursor:pointer;user-select:none;font-size:18px;line-height:32px;opacity:0;transform:scale(.9);transition:opacity .15s ease,transform .15s ease,background-color .15s ease;pointer-events:none;"; - b = document.getElementById("js-player-dialog"); - if (b) b.insertBefore(a, b.childNodes[0]); + if (!settingPanel.querySelector("#item-playerSimple")) { + settingPanel.insertAdjacentHTML( + "afterbegin", + `
    + + + + + + + +
    ` + ); } } -function initPkg_Refresh_Video_Func() { - new DomHook(".right-e7ea5d", true, () => { - changeToolBarZIndex(); - }); - new DomHook(".right-17e251", true, () => { +function initPkg_Refresh_Player_Func(playerDialog, playerMenu, settingPanel) { +/* 旧版UI + gDomObserver.waitForElement('.right-17e251, .right-e7ea5d').then(rightControlBar => { + new DomHook(rightControlBar, true, () => { changeToolBarZIndex(); + }); }); new DomHook(".video__VfhVg", true, (m) => { for (const record of m) { @@ -53,167 +60,85 @@ function initPkg_Refresh_Video_Func() { function changeToolBarZIndex() { - let video_fullPage = false; + let video_fullPage = !!(document.querySelector(".wfs-2a8e83.removed-9d4c42") || document.querySelector(".toggle__P8TKM")); let video_fullScreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement); - let chatPanel_isHidden = false; - if (document.querySelector(".wfs-2a8e83.removed-9d4c42")) { - video_fullPage = true; - } else if (document.querySelector(".toggle__P8TKM")) { - video_fullPage = true; - } - if(document.querySelector(".shrink__Sd0uK")){ - chatPanel_isHidden = true; - } + let chatPanel_isHidden = !!document.querySelector(".shrink__Sd0uK"); const dom_player_toolbar = document.getElementById("js-player-toolbar"); - dom_player_toolbar.style = video_fullPage? "z-index:20" : "z-index:30"; + dom_player_toolbar.style.zIndex = video_fullPage ? "20" : "30"; const dom_casebar = document.getElementsByClassName("case__f4yex")[0]; - if(dom_casebar){ - dom_casebar.style = (video_fullScreen || (video_fullPage && chatPanel_isHidden)) && refresh_Video_getStatus() ? "bottom: -84px;" : "bottom: 0;"; - } + if (dom_casebar) dom_casebar.style = (video_fullScreen || (video_fullPage && chatPanel_isHidden)) && refresh_Video_getStatus() ? "bottom: -84px;" : "bottom: 0;"; const isBeta = !!document.getElementsByClassName("live-next-body")[0]; - if (isBeta) dom_player_toolbar.parentElement.style = "z-index:20"; - } + if (isBeta) dom_player_toolbar.parentElement.style.zIndex = "20"; + } */ - let dom = getValidDom([".layout-Player-video", ".stream__T55I3"]); - let dom_video = document.getElementsByClassName("room-Player-Box")[0]; - let refresh_video3 = document.getElementById("refresh-video3"); + let dom = playerDialog.closest('.layout-Player-video') || playerDialog.closest('.stream__T55I3'); let timer_timeout = 0; - function hideRefreshVideo3() { - if (!refresh_video3) return; - refresh_video3.style.opacity = "0"; - refresh_video3.style.transform = "scale(.9)"; - refresh_video3.style.pointerEvents = "none"; + dom.addEventListener("mouseenter", () => { + document.body.classList.add("simple-show"); clearTimeout(timer_timeout); - } - - function setRefreshVideo3Show() { - if (!refresh_video3) return; - refresh_video3.style.opacity = "1"; - refresh_video3.style.transform = "scale(1)"; - refresh_video3.style.pointerEvents = "auto"; + }); + dom.addEventListener("mouseleave", () => { + document.body.classList.remove("simple-show"); clearTimeout(timer_timeout); - timer_timeout = setTimeout(() => { - hideRefreshVideo3(); - }, 2000); - } - - if (dom && refresh_video3) { - dom.addEventListener("mouseenter", () => { setRefreshVideo3Show(); }); - dom.addEventListener("mouseleave", () => { hideRefreshVideo3(); }); - } - if (dom_video && refresh_video3) { - dom_video.addEventListener("mousemove", () => { setRefreshVideo3Show(); }); - } - if (refresh_video3) { - refresh_video3.addEventListener("mouseenter", () => { - refresh_video3.style.opacity = "1"; - refresh_video3.style.transform = "scale(1.08)"; - refresh_video3.style.pointerEvents = "auto"; - refresh_video3.style.backgroundColor = "rgba(0,0,0,.7)"; + }); + gDomObserver.waitForElement('.room-Player-Box').then(dom_video => { + dom_video.addEventListener("mousemove", () => { + document.body.classList.add("simple-show"); clearTimeout(timer_timeout); + timer_timeout = setTimeout(() => { + document.body.classList.remove("simple-show"); + }, 2000); }); - refresh_video3.addEventListener("mouseleave", () => { - refresh_video3.style.transform = "scale(1)"; - refresh_video3.style.backgroundColor = "rgba(0,0,0,.55)"; - }); - } - - function toggleRefreshVideo() { - let dom_toolbar = document.getElementsByClassName("PlayerToolbar-ContentRow")[0]; - let dom_video = getValidDom([".layout-Player-video", ".stream__T55I3"]); - let dom_refresh = document.getElementById("refresh-video"); - let dom_refresh3 = document.getElementById("refresh-video3"); - if (!dom_toolbar || !dom_video || !dom_refresh) return; + }); + playerDialog.addEventListener("mouseover", e => { + const dom_dialog = e.target.closest("#dialog-playerSimple"); + if (!dom_dialog || e.relatedTarget && dom_dialog.contains(e.relatedTarget)) return; + document.body.classList.add("simple-hover"); + clearTimeout(timer_timeout); + }); + playerDialog.addEventListener("mouseout", e => { + const dom_dialog = e.target.closest("#dialog-playerSimple"); + if (!dom_dialog || e.relatedTarget && dom_dialog.contains(e.relatedTarget)) return; + document.body.classList.remove("simple-hover"); + }); - if (dom_toolbar.style.visibility == "hidden") { - dom_toolbar.style.visibility = "visible"; - dom_video.style = ""; - if (dom_refresh3) { - dom_refresh3.style.opacity = "0"; - dom_refresh3.style.transform = "scale(.9)"; - dom_refresh3.style.pointerEvents = "none"; - dom_refresh3.title = "开启简洁模式"; - } - dom_refresh.innerText = "简洁模式"; - refresh_Video_removeStyle(); + function toggleSimpleMode() { + document.body.classList.toggle("is-playerSimple"); + const svg = settingPanel.querySelector("#item-playerSimple__svg"); + if (document.body.classList.contains("is-playerSimple")) { + svg.setAttribute("class", "checked-13adb7"); } else { - dom_toolbar.style.visibility = "hidden"; - dom_video.style = "bottom:0;z-index:25"; - dom_refresh.innerText = "✓ 简洁模式"; - if (dom_refresh3) dom_refresh3.title = "关闭简洁模式"; - refresh_Video_setStyle(); + svg.setAttribute("class", "unchecked-b96102"); } - changeToolBarZIndex(); saveData_Refresh(); resizeWindow(); } - document.getElementById("refresh-video").addEventListener("click", (e) => { - toggleRefreshVideo(); + playerDialog.addEventListener("click", e => { + if (!e.target.closest("#dialog-playerSimple")) return; + e.stopPropagation(); + toggleSimpleMode(); }); - - if (refresh_video3) { - refresh_video3.addEventListener("click", (e) => { - e.stopPropagation(); - toggleRefreshVideo(); - }); - } + playerMenu.addEventListener("click", e => { + if (!e.target.closest("#menu-playerSimple")) return; + e.stopPropagation(); + toggleSimpleMode(); + }); + settingPanel.addEventListener("click", e => { + if (!e.target.closest("#item-playerSimple")) return; + e.stopPropagation(); + toggleSimpleMode(); + }); + gHotkey.add("s", () => toggleSimpleMode()); } -function refresh_Video_getStatus() { - let dom_toolbar = document.getElementsByClassName("PlayerToolbar-ContentRow")[0]; - if (dom_toolbar.style.visibility == "hidden") { - return true; - } else { - return false; - } -} // FullPageFollowGuide -function initPkg_Refresh_Video_Set() { - let ret = localStorage.getItem("ExSave_Refresh"); - if (ret != null) { - let retJson = JSON.parse(ret); - if ("video" in retJson == false) { - retJson.video = {status: false}; - } - if (retJson.video.status == true) { - let dom_toolbar = document.getElementsByClassName("PlayerToolbar-ContentRow")[0]; - let dom_video = getValidDom([".layout-Player-video", ".stream__T55I3"]); - let dom_refresh = document.getElementById("refresh-video"); - let dom_refresh3 = document.getElementById("refresh-video3"); - let dom_player_toolbar = document.getElementById("js-player-toolbar"); - dom_toolbar.style.visibility = "hidden"; - dom_video.style = "bottom:0;z-index:25"; - dom_player_toolbar.style = "z-index:30"; - let ret = localStorage.getItem("ExSave_FullScreen"); - if (ret != null) { - let retJson = JSON.parse(ret); - if (retJson.isFullScreen) { - dom_player_toolbar.style = "z-index:20"; - } - } - const isBeta = !!document.getElementsByClassName("live-next-body")[0]; - if (isBeta) dom_player_toolbar.parentElement.style = "z-index:20"; - if (dom_refresh3) { - dom_refresh3.style.opacity = "0"; - dom_refresh3.style.transform = "scale(.9)"; - dom_refresh3.style.pointerEvents = "none"; - dom_refresh3.title = "关闭简洁模式"; - } - dom_refresh.innerText = "✓ 简洁模式"; - refresh_Video_setStyle(); - resizeWindow(); - } +function initPkg_Refresh_Player_Set() { + if (loadData_Refresh("playerSimple")) { + document.body.classList.add("is-playerSimple"); + gDomObserver.waitForElement('#item-playerSimple__svg').then(svg => { + svg.setAttribute("class", "checked-13adb7"); + }); } } - -function refresh_Video_setStyle() { - StyleHook_set("Ex_Style_VideoRefresh", ` - .PELact,.pushTower-wrapper-gf1HG,.PkView-9f6a2c,.MorePk,.RandomPKBar,.LiveRoomLoopVideo,.LiveRoomDianzan,.maiMaitView-68e80c,.PkView{display:none !important;} - `) -} - -function refresh_Video_removeStyle() { - StyleHook_remove("Ex_Style_VideoRefresh"); -} diff --git a/src/packages/RemoveAD/RemoveAD.js b/src/packages/RemoveAD/RemoveAD.js index 70329388a..a3afeaf5f 100644 --- a/src/packages/RemoveAD/RemoveAD.js +++ b/src/packages/RemoveAD/RemoveAD.js @@ -20,7 +20,6 @@ function removeAD() { .TurntableLottery-actTips{display:none !important;} .feedback-e27241{display:none !important;} .FansMedalEnter-maxFlag{display:none !important;} - .Header-follow-listBox{max-height:640px !important;} .GuessGameMiniPanelB-wrapper{display:none !important;} diff --git a/src/packages/Shield/RemoveDanmakuBackground.js b/src/packages/Shield/RemoveDanmakuBackground.js index c424349b6..b80f0c896 100644 --- a/src/packages/Shield/RemoveDanmakuBackground.js +++ b/src/packages/Shield/RemoveDanmakuBackground.js @@ -1,20 +1,21 @@ let isRemoveDanmakuBackground = getLocalIsRemoveDanmakuBackground(); if (isRemoveDanmakuBackground) removeDanmakuBackground(); -function initPkg_Shield_RemoveDanmakuBackground() { - const shieldTool = document.getElementsByClassName("FilterKeywords")[0]; - shieldTool.insertAdjacentHTML( - "afterbegin", - `
    -

    屏蔽弹幕背景

    -
    - ${isRemoveDanmakuBackground ? "已开启" : "未开启"} - - - -
    -
    ` - ); - +function initPkg_Shield_RemoveDanmakuBackground(shieldTool) { + if (!shieldTool.querySelector('#ex-removeDanmakuBackground')) { + shieldTool.insertAdjacentHTML( + "afterbegin", + `
    +

    屏蔽弹幕背景

    +
    + ${isRemoveDanmakuBackground ? "已开启" : "未开启"} + + + +
    +
    ` + ); + } + const dom = document.getElementById("ex-removeDanmakuBackground"); const statusSpan = dom.querySelector(".FilterSwitchStatus-status"); const switchSpan = dom.querySelector(".FilterSwitchStatus-switch"); diff --git a/src/packages/Shield/RemoveEnter.js b/src/packages/Shield/RemoveEnter.js index 6d9aa3674..5d996da3a 100644 --- a/src/packages/Shield/RemoveEnter.js +++ b/src/packages/Shield/RemoveEnter.js @@ -1,25 +1,25 @@ let isRemoveEnterBarrage = getLocalIsRemoveEnterBarrage(); -function initPkg_Shield_RemoveEnter() { - const shieldTool = document.getElementsByClassName("FilterKeywords")[0]; +function initPkg_Shield_RemoveEnter(shieldTool) { let isSupported = window.CSS && window.CSS.supports && window.CSS.supports('--enter-display', 'none'); //CSS变量兼容性检测 let barrageExtendContainer = document.getElementById("js-barrage-extend-container"); - if (shieldTool == undefined || !isSupported) - return; - - shieldTool.insertAdjacentHTML( - "afterbegin", - `
    -

    屏蔽进场弹幕

    -
    - ${isRemoveEnterBarrage ? "已开启" : "未开启"} - - - -
    -
    ` - ); - + if (shieldTool == undefined || !isSupported) return; + + if (!shieldTool.querySelector('#ex-removeEnterBarrage')) { + shieldTool.insertAdjacentHTML( + "afterbegin", + `
    +

    屏蔽进场弹幕

    +
    + ${isRemoveEnterBarrage ? "已开启" : "未开启"} + + + +
    +
    ` + ); + } + if (isRemoveEnterBarrage) { barrageExtendContainer && barrageExtendContainer.style.setProperty("--enter-display", "none", "important"); } else { diff --git a/src/packages/Shield/RemoveRepeatedDanmaku.js b/src/packages/Shield/RemoveRepeatedDanmaku.js index e0fd5a203..8f31f9a2f 100644 --- a/src/packages/Shield/RemoveRepeatedDanmaku.js +++ b/src/packages/Shield/RemoveRepeatedDanmaku.js @@ -16,30 +16,31 @@ let repeatedDanmakuDomHook = null; if (isRemoveRepeatedDanmaku) removeRepeatedDanmaku(); -function initPkg_Shield_RemoveRepeatedDanmaku() { - const shieldTool = document.getElementsByClassName("FilterKeywords")[0]; - shieldTool.insertAdjacentHTML( - "afterbegin", - `
    -

    屏蔽重复弹幕

    -
    - ${isRemoveRepeatedDanmaku ? "已开启" : "未开启"} - - - +function initPkg_Shield_RemoveRepeatedDanmaku(shieldTool) { + if (!shieldTool.querySelector('#ex-removeRepeatedDanmaku')) { + shieldTool.insertAdjacentHTML( + "afterbegin", + `
    +

    屏蔽重复弹幕

    +
    + ${isRemoveRepeatedDanmaku ? "已开启" : "未开启"} + + + +
    -
    -

    - - - 秒内重复的弹幕只显示一次 - - -

    ` - ); +

    + + + 秒内重复的弹幕只显示一次 + + +

    ` + ); + } const dom = document.getElementById("ex-removeRepeatedDanmaku"); const statusSpan = dom.querySelector(".FilterSwitchStatus-status"); diff --git a/src/packages/Shield/Shield.css b/src/packages/Shield/Shield.css new file mode 100644 index 000000000..d57a54c40 --- /dev/null +++ b/src/packages/Shield/Shield.css @@ -0,0 +1,4 @@ +.FilterKeywords { + max-height: var(--filterkeywords-max-height); + overflow-y: auto!important; +} \ No newline at end of file diff --git a/src/packages/Shield/Shield.js b/src/packages/Shield/Shield.js index d16d90dc7..6e619756b 100644 --- a/src/packages/Shield/Shield.js +++ b/src/packages/Shield/Shield.js @@ -7,32 +7,27 @@ function initPkg_Shield() { // } // }, 1000); - let t = setInterval(() => { - if (typeof document.getElementsByClassName("BarrageFilter")[0] !== "undefined") { - clearInterval(t); - new DomHook(".BarrageFilter", false, (m) => { - if (m.length === 0) return; - if (m[0].addedNodes.length > 0 && m[0].removedNodes.length === 0) { - const domFilterKeywords = document.getElementsByClassName("FilterKeywords")[0]; - if (domFilterKeywords) { - initPkg_Shield_Enable(); - } else { - let t2 = setInterval(() => { - const domFilterKeywords = document.getElementsByClassName("FilterKeywords")[0]; - if (domFilterKeywords) { - clearInterval(t2); - initPkg_Shield_Enable(); - } - }, 50); - } - } - }); - } - }, 1000); + gDomObserver.waitForElement('.BarrageFilter').then(barrageFilter => { + new DomHook(barrageFilter, false, () => { + const domFilterKeywords = document.getElementsByClassName("FilterKeywords")[0]; + if (domFilterKeywords) { + initPkg_Shield_Enable(domFilterKeywords); + } + }); + }); } -function initPkg_Shield_Enable() { - initPkg_Shield_RemoveRepeatedDanmaku(); - initPkg_Shield_RemoveEnter(); - initPkg_Shield_RemoveDanmakuBackground(); +function initPkg_Shield_Enable(domFilterKeywords) { + new ResizeObserver(entries => { + const asideMainHeight = entries[0].contentRect.height; + const headerHeight = document.getElementsByClassName("AssembleExpressHeader-head")[0].offsetHeight; + const chatHeight = domFilterKeywords.closest('.layout-Player-chat').offsetHeight; + const maxHeightValue = `calc(${Math.round(asideMainHeight - headerHeight - chatHeight)}px)`; + if (maxHeightValue !== domFilterKeywords.style.getPropertyValue('--filterkeywords-max-height')) { + domFilterKeywords.style.setProperty('--filterkeywords-max-height', maxHeightValue); + } + }).observe(domFilterKeywords.closest('#js-player-asideMain')); + initPkg_Shield_RemoveRepeatedDanmaku(domFilterKeywords); + initPkg_Shield_RemoveEnter(domFilterKeywords); + initPkg_Shield_RemoveDanmakuBackground(domFilterKeywords); } \ No newline at end of file diff --git a/src/packages/VideoTools/Camera/Main/Camera.js b/src/packages/VideoTools/Camera/Main/Camera.js index ce027f197..681803788 100644 --- a/src/packages/VideoTools/Camera/Main/Camera.js +++ b/src/packages/VideoTools/Camera/Main/Camera.js @@ -1,5 +1,4 @@ function initPkg_VideoTools_Camera() { - camera_anchorName = getValidDom([".Title-anchorName", ".anchorName__6NXv9"]).innerText; camera_width = liveVideoNode.videoWidth * 0.25; camera_height = liveVideoNode.videoHeight * 0.25; camera_canvas = document.createElement("canvas"); @@ -109,6 +108,7 @@ function initPkg_VideoTools_Camera_Func() { timer = setInterval(() => {cameraAddFrame(liveVideoNode, camera_canvas, gif, camera_fps)}, camera_fps); }) camera.addEventListener("mouseup", (e) => { + const camera_anchorName = getValidDom([".anchorName__6NXv9", ".Title-anchorName"]).textContent.trim(); if (e.target.id === "ex-camera-close") return; let upTime = new Date().getTime(); clearInterval(timer); diff --git a/src/packages/VideoTools/Camera/Video/Camera.js b/src/packages/VideoTools/Camera/Video/Camera.js index 44b92573e..65fb52278 100644 --- a/src/packages/VideoTools/Camera/Video/Camera.js +++ b/src/packages/VideoTools/Camera/Video/Camera.js @@ -3,7 +3,6 @@ function initPkg_VideoTools_Camera_Video() { liveVideoNode = document.getElementsByTagName("demand-video")[0].shadowRoot.getElementById("__video"); if (liveVideoNode !== undefined && liveVideoNode !== null && liveVideoNode.videoWidth) { clearInterval(timer); - camera_anchorName = document.getElementsByTagName("demand-video-anchor")[0].shadowRoot.querySelector(".anchor-name").innerText; camera_width = liveVideoNode.videoWidth * 0.25; camera_height = liveVideoNode.videoHeight * 0.25; camera_canvas = document.createElement("canvas"); @@ -120,6 +119,7 @@ function initPkg_VideoTools_Camera_Video_Func() { }) camera.addEventListener("mouseup", (e) => { if (e.target.id === "ex-camera-close") return; + const camera_anchorName = document.getElementsByTagName("demand-video-anchor")[0].shadowRoot.getElementsByClassName("anchor-name")[0].textContent.trim(); let upTime = new Date().getTime(); clearInterval(timer); if (upTime - downTime >= 800) { diff --git a/src/packages/VideoTools/Camera/index.css b/src/packages/VideoTools/Camera/index.css index 4f8f8a6bf..771f05569 100644 --- a/src/packages/VideoTools/Camera/index.css +++ b/src/packages/VideoTools/Camera/index.css @@ -2,7 +2,7 @@ background: rgba(0,0,0,0.7); position: absolute; right: 20px; - bottom: 190px; + bottom: 90px; z-index: 10; width: 60px; height: 60px; diff --git a/src/packages/VideoTools/Camera/index.js b/src/packages/VideoTools/Camera/index.js index 2abc66d48..68e524358 100644 --- a/src/packages/VideoTools/Camera/index.js +++ b/src/packages/VideoTools/Camera/index.js @@ -8,7 +8,6 @@ var camera_canvas; var camera_canvas_img; var camera_width; var camera_height; -var camera_anchorName; const camera_fps = 83; // 12fps function cameraAddFrame(node, camera_canvas, gif, fps) { diff --git a/src/require/DomHook/DomHook.js b/src/require/DomHook/DomHook.js index ba33bf596..430f67e78 100644 --- a/src/require/DomHook/DomHook.js +++ b/src/require/DomHook/DomHook.js @@ -1,21 +1,91 @@ class DomHook { - constructor(selector, isSubtree, callback) { - this.selector = selector; - this.isSubtree = isSubtree; - let targetNode = document.querySelector(this.selector); + static _observers = new WeakMap(); + constructor(elementOrSelector, isSubtree, callback, observeAttributes = false) { + if (typeof callback !== 'function') { + console.error("DouyuEx DomHook: callback 不是一个函数", callback); + return; + } + let targetNode = null; + if (elementOrSelector instanceof Element) { + targetNode = elementOrSelector; + } else if (typeof elementOrSelector === "string") { + targetNode = _rawQuery(elementOrSelector); + } if (targetNode == null) { + console.warn("DouyuEx DomHook: 目标节点不存在或已销毁", elementOrSelector); return; } - let observer = new MutationObserver(function(mutations) { - callback(mutations); - }); - this.observer = observer; - this.observer.observe(targetNode, { attributes: true, childList: true, subtree: this.isSubtree }); + + let configMap = DomHook._observers.get(targetNode); + if (!configMap) { + configMap = new Map(); + DomHook._observers.set(targetNode, configMap); + } + + const configKey = `${isSubtree}|${observeAttributes}`; + let observerEntry = configMap.get(configKey); + if (!observerEntry) { + const callbacks = new Map(); + const observer = new MutationObserver(mutations => { + for (const [hook, callback] of callbacks) { + if (!hook._targetNode.isConnected) { + hook.closeHook(); + continue; + } + try { + callback.call(hook, mutations); + } catch (err) { + console.error("DouyuEx DomHook: 观察实例回调执行错误", targetNode, configKey, err); + } + } + }); + + try { + observer.observe(targetNode, { + attributes: observeAttributes, + childList: true, + subtree: isSubtree + }); + observerEntry = { observer, callbacks }; + configMap.set(configKey, observerEntry); + //console.log("DouyuEx DomHook: 目标节点配置唯一,新建观察实例", targetNode, configKey); + } catch (err) { + console.error("DouyuEx DomHook: 观察实例初始化失败", targetNode, configKey, err); + observer.disconnect(); + return; + } + } else { + //console.log("DouyuEx DomHook: 目标节点配置相同,复用观察实例", targetNode, configKey); + } + + observerEntry.callbacks.set(this, callback); + this._targetNode = targetNode; + this._configKey = configKey; } + closeHook() { - if (this.observer) { - this.observer.disconnect(); - this.observer = null; + if (!this._targetNode) return; + const configMap = DomHook._observers.get(this._targetNode); + if (configMap) { + const observerEntry = configMap.get(this._configKey); + if (observerEntry) { + observerEntry.callbacks.delete(this); + if (observerEntry.callbacks.size === 0) { + observerEntry.observer.disconnect(); + configMap.delete(this._configKey); + if (configMap.size === 0) { + DomHook._observers.delete(this._targetNode); + //console.log("DouyuEx DomHook: 目标节点已无其他配置,停止观察实例", this._targetNode); + } else { + //console.log(`DouyuEx DomHook: 移除当前配置观察实例,节点剩余实例: ${configMap.size}`, this._targetNode); + } + } else { + //console.log(`DouyuEx DomHook: 移除节点回调,当前观察实例剩余回调: ${observerEntry.callbacks.size}`, this._targetNode, this._configKey); + } + } else { + console.warn("DouyuEx DomHook: 尝试关闭一个不存在或已销毁的观察实例", this._targetNode, this._configKey); + } } + this._targetNode = null; } } \ No newline at end of file diff --git a/src/require/RealLive/Huya/RealLive_Huya.js b/src/require/RealLive/Huya/RealLive_Huya.js index e6c333065..3afff5894 100644 --- a/src/require/RealLive/Huya/RealLive_Huya.js +++ b/src/require/RealLive/Huya/RealLive_Huya.js @@ -34,7 +34,7 @@ function getRealLive_Huya(rid, qn, reallive_callback) { let msg = ""; let liveUrl = ""; let multiLine = ret.response.data.stream.flv.multiLine; - if (multiLine.length && multiLine.length > 0) { + if (multiLine && multiLine.length > 0) { liveUrl = multiLine[0].url.replace("http", "https"); } if (liveUrl == null || liveUrl == "") { diff --git a/src/require/WebSocket/WebSocket.js b/src/require/WebSocket/WebSocket.js index 8c75095ee..58b29bfcf 100644 --- a/src/require/WebSocket/WebSocket.js +++ b/src/require/WebSocket/WebSocket.js @@ -6,13 +6,13 @@ function WebSocket_Packet(str) { const MSG_TYPE = 689; - let bytesArr = stringToByte(str); - let buffer = new Uint8Array(bytesArr.length + 4 + 4 + 2 + 1 + 1 + 1); - let p_content = new Uint8Array(bytesArr.length); // 消息内容 + let bytesArr = stringToByte(str), length = bytesArr.length; + let buffer = new Uint8Array(length + 4 + 4 + 2 + 1 + 1 + 1); + let p_content = new Uint8Array(length); // 消息内容 for (let i = 0; i < p_content.length; i++) { p_content[i] = bytesArr[i]; } - let p_length = new Uint32Array([bytesArr.length + 4 + 2 + 1 + 1 + 1]); // 消息长度 + let p_length = new Uint32Array([length + 4 + 2 + 1 + 1 + 1]); // 消息长度 let p_type = new Uint32Array([MSG_TYPE]); // 消息类型 buffer.set(new Uint8Array(p_length.buffer), 0); @@ -52,30 +52,31 @@ function byteToString(arr) { if(typeof arr === 'string') { return arr; } - let str = '', - _arr = arr; - for(let i = 0; i < _arr.length; i++) { - let one = _arr[i].toString(2), + let str = ''; + let length = arr.length; + for(let i = 0; i < length; i++) { + let one = arr[i].toString(2), v = one.match(/^1+?(?=0)/); if(v && one.length == 8) { let bytesLength = v[0].length; - let store = _arr[i].toString(2).slice(7 - bytesLength); + let store = arr[i].toString(2).slice(7 - bytesLength); for(let st = 1; st < bytesLength; st++) { - store += _arr[st + i].toString(2).slice(2); + store += arr[st + i].toString(2).slice(2); } str += String.fromCharCode(parseInt(store, 2)); i += bytesLength - 1; } else { - str += String.fromCharCode(_arr[i]); + str += String.fromCharCode(arr[i]); } } -return str; + return str; } function hex2bin(e) { - if ("string" === typeof e && e.length % 8 === 0) { - for (let r = [], t = e.length, o = 0; o < t;) r.push(e.substr(o, 2)), o += 2; + const length = e.length; + if ("string" === typeof e && length % 8 === 0) { + for (let r = [], t = length, o = 0; o < t;) r.push(e.substr(o, 2)), o += 2; for (let n = [], i = r.length, s = 0; s < i;) n.push(parseInt(r.slice(s, s + 4).reverse().join(""), 16)), s += 4; return n } diff --git a/src/routers/router.js b/src/routers/router.js index ed5571d68..f794d955b 100644 --- a/src/routers/router.js +++ b/src/routers/router.js @@ -62,15 +62,11 @@ function initRouter_Motorcade() { function initRouter_DouyuRoom_Popup() { // 画中画 removeAD(); - let intID = setInterval(() => { - if (typeof (document.querySelector('div.wfs-2a8e83')) !== "undefined") { - document.querySelector('div.wfs-2a8e83').click(); - document.querySelector('label.layout-Player-asidetoggleButton').click(); - let l = document.querySelectorAll(".tip-e3420a > ul > li").length; - document.querySelectorAll(".tip-e3420a > ul > li")[l - 1].click(); - clearInterval(intID); - } - }, 1000); + gDomObserver.waitForElement('div.wfs-2a8e83').then(btn => { + btn.click() + document.querySelector('label.layout-Player-asidetoggleButton').click(); + document.querySelector('.tip-e3420a > ul > li:last-child').click(); + }); } @@ -78,21 +74,16 @@ function initRouter_DouyuRoom_Main() { // 主要 document.domain = "douyu.com"; init(); - let intID = setInterval(() => { - let dom1 = document.getElementsByClassName("BackpackButton")[0]; - let dom2 = document.getElementsByClassName("Barrage-main")[0]; - let dom3 = document.querySelector("#js-backpack-enter") - if (!dom2 || (!dom1 && !dom3)) { - return; - } - setTimeout(() => { - initStyles(); - initPkg(); - initPkgSpecial(); - initTimer(); - }, 1500) - clearInterval(intID); - }, 1000); + initStyles(); + gDomObserver.waitForElement(".Barrage-main").then(() => { + gDomObserver.waitForElement("#js-backpack-enter, .BackpackButton").then(() => { + new Promise(r => setTimeout(r, 1500)).then(() => { + initPkg(); + initPkgSpecial(); + initTimer(); + }); + }); + }); } function initPkgSpecial() {