import { VueModule } from '../common-types' /** * 当查询 video 元素且被灰度了 WasmPlayer 时, 更换为对 bwp-video 的查询, 否则会找不到 video 元素 * @param selector 选择器 */ export const bwpVideoFilter = (selector: string) => { // if (!unsafeWindow.__ENABLE_WASM_PLAYER__) { // return selector // } const map = { video: ', bwp-video', '.bilibili-player-video video': ', .bilibili-player-video bwp-video,.bpx-player-video-area bwp-video', } const suffix = map[selector] if (suffix) { return selector + suffix } return selector } type DocumentQuerySelector = { /** * 同 `document.querySelector`, 对 `` 有额外处理 * @param selector 选择器 */ (selector: string): Element | null /** * 在指定元素上进行 `querySelector`, 对 `` 有额外处理 * @param selector 元素 * @param scopedSelector 选择器 */ (element: Element, scopedSelector: string): Element | null } export const dq: DocumentQuerySelector = ( selectorOrElement: string | Element, scopedSelector?: string, ) => { if (!scopedSelector) { return document.querySelector(bwpVideoFilter(selectorOrElement as string)) } return (selectorOrElement as Element).querySelector(bwpVideoFilter(scopedSelector)) } type DocumentQuerySelectorAll = { /** * 同 `document.querySelectorAll` (返回转换过的真数组), 对 `` 有额外处理 * @param selector 选择器 */ (selector: string): Element[] /** * 在指定元素上进行`querySelectorAll` (返回转换过的真数组), 对 `` 有额外处理 * @param selector 元素 * @param scopedSelector 选择器 */ (element: Element, scopedSelector: string): Element[] } export const dqa: DocumentQuerySelectorAll = ( selectorOrElement: Element | string, scopedSelector?: string, ) => { if (!scopedSelector) { return Array.from(document.querySelectorAll(bwpVideoFilter(selectorOrElement as string))) } return Array.from((selectorOrElement as Element).querySelectorAll(bwpVideoFilter(scopedSelector))) } type DocumentEvaluate = { (xpathExpression: string): XPathResult (xpathExpression: string, contextNode: Node): XPathResult (xpathExpression: string, contextNode: Node, type: number): XPathResult (xpathExpression: string, contextNode: Node, type: number, result: XPathResult): XPathResult } export const de: DocumentEvaluate = ( xpathExpression: string, contextNode?: Node, type?: number, result?: XPathResult, ) => document.evaluate(xpathExpression, contextNode, null, type, result) type DocumentEvaluateAll = { (xpathExpression: string): Node[] (xpathExpression: string, contextNode: Node): Node[] (xpathExpression: string, contextNode: Node, order: boolean): Node[] (xpathExpression: string, contextNode: Node, order: boolean, result: XPathResult): Node[] } export const dea: DocumentEvaluateAll = ( xpathExpression: string, contextNode?: Node, order?: boolean, result?: XPathResult, ) => { const xpathResult = de( xpathExpression, contextNode, order ? XPathResult.ORDERED_NODE_SNAPSHOT_TYPE : XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, result, ) return Array.from({ length: xpathResult.snapshotLength }, (_, i) => xpathResult.snapshotItem(i)) } type DocumentEvaluateAllIterable = { (xpathExpression: string): Iterable (xpathExpression: string, contextNode: Node): Iterable (xpathExpression: string, contextNode: Node, order: boolean): Iterable (xpathExpression: string, contextNode: Node, order: boolean, result: XPathResult): Iterable } export const deai: DocumentEvaluateAllIterable = ( xpathExpression: string, contextNode?: Node, order?: boolean, result?: XPathResult, ) => { const xpathResult = de( xpathExpression, contextNode, order ? XPathResult.ORDERED_NODE_ITERATOR_TYPE : XPathResult.UNORDERED_NODE_ITERATOR_TYPE, result, ) return { [Symbol.iterator]: () => ({ next: () => { let node = null do { node = xpathResult.iterateNext() return node ? ({ done: false, value: node } as { done: false; value: Node }) : ({ done: true } as { done: true; value: any }) } while (node) }, }), } } type DocumentEvaluateSingle = { (xpathExpression: string): T | null (xpathExpression: string, contextNode: Node): T | null (xpathExpression: string, contextNode: Node, result: XPathResult): T | null } export const des: DocumentEvaluateSingle = ( xpathExpression: string, contextNode?: Node, result?: XPathResult, ) => de(xpathExpression, contextNode, XPathResult.FIRST_ORDERED_NODE_TYPE, result) .singleNodeValue as T | null /** 空函数 */ export const none = () => { // Do nothing } /** 页面是否使用了 Wasm 播放器 */ export const isBwpVideo = async () => { const { hasVideo } = await import('../spin-query') if (!(await hasVideo())) { return false } return ( // eslint-disable-next-line no-underscore-dangle (unsafeWindow.__ENABLE_WASM_PLAYER__ as boolean) || Boolean(dq('#bilibili-player bwp-video')) ) } /** * 等待一定时间 * @param time 延迟的毫秒数 */ export const delay = (time = 0) => new Promise(r => setTimeout(() => r(), time)) /** 测试字符串是否包含子串或匹配正则 * @param str 字符串 * @param pattern 子串或正则表达式 */ export const matchPattern = (str: string, pattern: string | RegExp) => { if (typeof pattern === 'string') { return str.includes(pattern) } return pattern.test(str) } /** 以`document.URL`作为被测字符串, 移除URL查询参数并调用`matchPattern` */ export const matchUrlPattern = (pattern: string | RegExp) => matchPattern(document.URL.replace(window.location.search, ''), pattern) /** 创建Vue组件的实例 * @param module Vue组件模块对象 * @param target 组件的挂载目标元素, 省略时不挂载直接返回 */ export const mountVueComponent = (module: VueModule, target?: Element | string): Vue & T => { const obj = 'default' in module ? module.default : module const getInstance = (o: any) => { if (o instanceof Function) { // eslint-disable-next-line new-cap return new o() } if (o.functional) { return new (Vue.extend(o))() } return new Vue(o) } return getInstance(obj).$mount(target) as Vue & T } /** 是否处于其他网站的内嵌播放器中 */ export const isEmbeddedPlayer = () => window.location.host === 'player.bilibili.com' || document.URL.startsWith('https://www.bilibili.com/html/player.html') /** 是否处于`