// ==UserScript== // @name Replace ddys Origin Player // @namespace https://github.com/s0urcelab/replace-ddys-origin-player // @version 1.3 // @description 替换ddys播放器,移除Adblock屏蔽,修复滚轮和全屏快捷键失效bug,优化选集和线路功能,自动记忆选集 // @author s0urce // @match https://ddys.art/* // @icon https://www.google.com/s2/favicons?sz=64&domain=ddys.art // @grant GM_addStyle // @grant GM_xmlhttpRequest // @require https://fastly.jsdelivr.net/npm/xgplayer@2.31.2/browser/index.min.js // @run-at document-end // @downloadURL none // ==/UserScript== const QS = (q) => document.querySelector(q) const QSA = (q) => document.querySelectorAll(q) const domain = window.location.hostname const src4domain = `v.ddys.pro` const globalStyle = ` .wp-playlist-tracks { display: none!important; } .wp-video-playlist { display: flex; padding: 0!important; border: none!important; background: none!important; } .entry > p { display: none; } .player-sider { width: 220px; display: flex; flex-direction: column; background-color: #2e2e2e; border-radius: 8px; margin-left: 10px; padding: 4px; } .tab-item { cursor: pointer; margin-bottom: 6px; padding: 8px; color: white; background-color: #5a5a5a; border-radius: 5px; } .tab-item.playing { font-weight: bold; color: #3a8fb7; background-color: #232323; } .tab-item:not(.playing):hover { background-color: #232323; } .tab-item > .indicator { height: 14px; width: 14px; font-size: 14px; margin-right: 5px; } .switch-root { cursor: pointer; user-select: none; position: relative; background: #e0e0e0; border-radius: 26px; width: 174px; padding: 2px; } .sw-group { position: absolute; top: 0; left: 0; padding: 2px; width: 170px; display: flex; justify-content: space-between; } .sw-item { line-height: 30px; padding: 0 10px; color: #666; } .sw-item.active { font-weight: bold; color: #333; } .switch-root > .indicator { transition: all 0.2s ease; margin-left: 0; width: 86px; height: 30px; background: #fff; border-radius: 26px; box-shadow: 0px 0px 6px -2px #111; } .switch-root > .indicator.overseas { margin-left: 84px; } .ep-tip { margin: 10px 0; color: white; } ` function parseResUrl(region, d) { // type 4 if (d.srctype === '4') return { ...d, url: `https://${src4domain}${d.src3}` } // 海外线路 if (region == 'overseas') return { ...d, url: `https://w.ddys.art${d.src0}?ddrkey=${d.src2}` } return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', responseType: 'json', headers: { 'referer': `https://${domain}/` }, url: `https://${domain}/getvddr2/video?id=${d.src1}&type=json`, onload: res => { resolve({ ...d, url: res.response.url }) }, onerror: function (error) { reject(error) }, }) }) } class Tabs { constructor(init) { this.root = init.root this.data = init.data this.onSelect = init.onSelect this.selectedKey = init.data[0].key } render(key = this.selectedKey) { // update selectedKey this.selectedKey = key // render dom this.root.innerHTML = this.data.reduce((acc, curr) => { const isTarget = key === curr.key return `${acc}
${isTarget ? '' : ''} ${curr.label}
` }, '') // bind click const self = this for (const tabElment of this.root.children) { tabElment.onclick = function() { const tabKey = tabElment.dataset.tabKey const record = self.data.find(v => v.key === tabKey) self.render(tabKey) self.onSelect(tabKey, record) } } } } class Switch { constructor(init) { this.root = init.root this.data = init.data this.onSelect = init.onSwitch this.selectedKey = init.data[0].key } render(key = this.selectedKey) { // update selectedKey this.selectedKey = key // render dom const group = this.data.reduce((acc, curr) => { const isTarget = key === curr.key return `${acc}
${curr.label}
` }, '') this.root.innerHTML = `
${group}
` // bind click const self = this for (const swElment of this.root.querySelector('.sw-group').children) { swElment.onclick = function() { const swKey = swElment.dataset.swKey const record = self.data.find(v => v.key === swKey) self.render(swKey) self.onSelect(swKey, record) } } } } ; (async function () { 'use strict'; const originContainer = QS('.wp-video-playlist') // cannot found Player, quit if (!originContainer) return; // inject global style GM_addStyle(globalStyle) // hide origin container for (const item of originContainer.children) { item.style.display = 'none' } // append container for xgplayer originContainer.innerHTML += `

选集:

` // get video resource from page data const res = JSON.parse(QS('.wp-playlist-script').textContent) const resPromise = res.tracks .map((track, idx) => ({ ...track, key: `${idx + 1}`, label: track.caption })) .map(d => parseResUrl(window.localStorage['region'], d)) const resGroups = await Promise.all(resPromise) // init xgplayer const initVolume = window.localStorage['volume'] ? parseFloat(window.localStorage['volume']) : 1 const isWatched = window.localStorage[location.pathname] const initEp = isWatched ? JSON.parse(isWatched).ep : '1' const initPlayUrl = resGroups.find(v => v.key === initEp).url console.warn(`当前播放资源url:${initPlayUrl}`) const player = new window.Player({ id: 'xgplayer', url: initPlayUrl, volume: initVolume, fluid: true, videoInit: true, lastPlayTimeHideDelay: 3, ...isWatched && {lastPlayTime: JSON.parse(isWatched).seek}, }) // init switch const switchs = new Switch({ root: QS('.switch-root'), data: [{ key: 'domestic', label: '国内线路' }, { key: 'overseas', label: '海外线路' }], onSwitch: (key, record) => { console.warn(`切换线路:${record.label},即将刷新页面`) window.localStorage['region'] = key window.location.reload() } }) switchs.render(window.localStorage['region']) // init tabs const tabs = new Tabs({ root: QS('.tabs-root'), data: resGroups, onSelect: (key, record) => { console.warn(`切换选集:【${key}】${record.label}`) player.src = record.url console.warn(`当前播放资源url:${record.url}`) player.play() } }) // render tabs tabs.render(initEp) // update video progress player.on('timeupdate', function({ currentTime }) { window.localStorage[location.pathname] = JSON.stringify({ seek: currentTime, ep: tabs.selectedKey, }) }) // update volume player.on('volumechange', function({ volume }) { window.localStorage['volume'] = volume }) })()