- // ==UserScript==
- // @name 💖 VIP视频解析
- // @namespace https://gf.qytechs.cn/zh-CN/users/1409010-i-breathe
- // @version 5.1
- // @description 自用视频解析、多源切换、简洁易用、UI美观(支持"爱优腾芒"等多平台多解析源切换)双击可能更好用哦!
- // @author I-Breathe
- // @include http*://*.iqiyi.*/*
- // @include http*://*.qq.*/*
- // @include http*://*.youku.*/*
- // @include http*://*.bilibili.*/*
- // @include http*://*.mgtv.*/*
- // @include http*://*.sohu.*/*
- // @include http*://*.pptv.*/*
- // @include http*://*.le.*/*
- // @include http*://*.1905.*/*
- // @include http*://*.acfun.*/*
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_deleteValue
- // @run-at document-start
- // ==/UserScript==
-
- (() => {
- 'use strict';
- const CONFIG = {
- buttonSize: 50,
- buttonRight: '25px',
- buttonBottom: '35px',
- imageUrl: GM_getValue('imageUrl', 'https://img13.360buyimg.com/ddimg/jfs/t1/121241/11/19612/181715/5fbac680E636138b5/267dd280e727aff4.jpg'),
- opacity: 1,
- buttonRadius: '50%',
- listBlur: '10px',
- listBgColor: 'rgba(20,20,20,0.5)',
- listColor: '#00000099',
- listFontSize: '14px',
- itemHoverBg: 'rgba(255,255,255,0.2)',
- selectedColor: '#FFFFFA',
- breatheColors: ['#FF00FF95', '#00FAFF95', '#FFFF0095', '#00FFFF95', '#00FF0095'],
- breatheDuration: 12,
- glowSize: 7,
- parseUrl: GM_getValue('selectedParseUrl', 'https://jx.2s0.cn/player/?url='),
- parseUrls: GM_getValue('storedParseUrls', [
- ["https://bd.jx.cn/?url=", "冰豆弹幕"],
- ["https://am1907.top/?jx=", "1907解析"],
- ["https://jx.xmflv.cc/?url=", "虾米解析"],
- ["https://jx.xymp4.cc/?url=", "咸鱼解析"],
- ["https://www.yemu.xyz/?url=", "夜幕解析"],
- ["https://jx.77flv.cc/?url=", "77云解析"],
- ["https://www.8090g.cn/jiexi/?url=", "8090g"],
- ["https://jx.playerjy.com/?url=", "PlayerJy"],
- ["https://www.ckplayer.vip/jiexi/?url=", "CkPlay"],
- ["https://www.pangujiexi.com/jiexi/?url=", "盘古解析"],
- ["https://jx.hls.one/?url=", "hls解析"],
- ["https://jx.973973.xyz/?url=", "973播放"],
- ["https://jx.nnxv.cn/tv.php?url=", "七哥解析"],
- ["https://jx.2s0.cn/player/?url=", "极速高清"],
- ["https://rdfplayer.mrgaocloud.com/player/?url=", "红狐解析"],
- ["https://jx.m3u8.tv/jiexi/?url=", "M3U8"],
- ["https://www.pouyun.com/?url=", "剖云解析"],
- ["https://www.playm3u8.cn/jiexi.php?url=", "playm3u8"],
- ["https://yparse.ik9.cc/?url=", "ik9云解析"],
- ["https://xiaoapi.cn/API/jx_txsp.php?url=", "腾讯API解析"],
- ["https://xiaoapi.cn/API/jx_yk.php?url=", "优酷API解析"],
- ["https://xiaoapi.cn/API/zs_ewm.php?msg=", "网址生成二维码"],
- ["https://bd.jx.cn/?url=", "+"],
- ["set", "• • •"]
- ])
- };
-
- let floatingButton, clickTimer, idleTimer;
-
- const createStyle = (id, content) => {
- const style = document.createElement('style');
- if (id) style.id = id;
- style.textContent = content;
- document.head.appendChild(style);
- };
-
- createStyle('base-style', `.floating-button{position:fixed;z-index:999999;cursor:pointer;transition:all 0.3s ease;box-shadow:0 0 ${CONFIG.glowSize}px ${CONFIG.glowSize}px ${CONFIG.breatheColors[0]}}.source-list{position:fixed;border-radius:16px;border:1px solid rgba(255,255,255,0.1);box-shadow:0 2px 10px rgba(0,0,0,0.2);z-index:999999;padding:1px;min-width:150px;font-family:"Microsoft YaHei",sans-serif}.source-item{padding:4px 20px;border-radius:50px;cursor:pointer;white-space:nowrap;transition:all 0.2s;text-align:center}@keyframes breathe{0%{box-shadow:0 0 ${CONFIG.glowSize}px ${CONFIG.glowSize}px ${CONFIG.breatheColors[0]}}25%{box-shadow:0 0 ${CONFIG.glowSize}px ${CONFIG.glowSize}px ${CONFIG.breatheColors[1]}}50%{box-shadow:0 0 ${CONFIG.glowSize}px ${CONFIG.glowSize}px ${CONFIG.breatheColors[2]}}75%{box-shadow:0 0 ${CONFIG.glowSize}px ${CONFIG.glowSize}px ${CONFIG.breatheColors[1]}}100%{box-shadow:0 0 ${CONFIG.glowSize}px ${CONFIG.glowSize}px ${CONFIG.breatheColors[0]}}}`);
-
- const updateStyles = () => createStyle('dynamic-styles', `.floating-button{border-radius:${GM_getValue('buttonRadius',CONFIG.buttonRadius)}}.source-list{backdrop-filter:blur(${GM_getValue('listBlur',CONFIG.listBlur)});background:${GM_getValue('listBgColor',CONFIG.listBgColor)};color:${GM_getValue('listColor',CONFIG.listColor)};font-size:${GM_getValue('listFontSize',CONFIG.listFontSize)}}.source-item:hover{background:${GM_getValue('itemHoverBg',CONFIG.itemHoverBg)};color:#FFFFFA;font-weight:bold}.selected{color:${GM_getValue('selectedColor',CONFIG.selectedColor)}!important;font-weight:bold}`);
- updateStyles();
-
- const createElement = (tag, props) => {
- const el = document.createElement(tag);
- Object.entries(props).forEach(([k, v]) => el[k] = v);
- return el;
- };
-
- const createFloatingButton = () => {
- return createElement('img', {
- className: 'floating-button',
- src: GM_getValue('imageUrl', CONFIG.imageUrl),
- style: `right:${GM_getValue('buttonRight',CONFIG.buttonRight)};bottom:${GM_getValue('buttonBottom',CONFIG.buttonBottom)};width:${GM_getValue('buttonSize',CONFIG.buttonSize)}px;height:${GM_getValue('buttonSize',CONFIG.buttonSize)}px;opacity:${GM_getValue('opacity',CONFIG.opacity)};animation:breathe ${GM_getValue('breatheDuration',CONFIG.breatheDuration)}s infinite`
- });
- };
-
- const createListItem = ([url, name], isSetting) => {
- const item = createElement('div', {
- className: `source-item${url === CONFIG.parseUrl && !isSetting ? ' selected' : ''}`,
- textContent: name
- });
-
- if (url === 'set') {
- let clickTimer;
- item.addEventListener('click', (e) => {
- e.stopPropagation();
- clearTimeout(clickTimer);
- clickTimer = setTimeout(() => {
- const input = prompt('输入格式:名称,URL丨示例:解析1,https://jx.hls.one/?url=');
- if (input) {
- const [n, u] = input.split(',').map(s => s.trim());
- if (n && u) {
- CONFIG.parseUrls.splice(-2, 0, [u, n]);
- GM_setValue('storedParseUrls', CONFIG.parseUrls);
- }
- }
- }, 200);
- });
-
- item.addEventListener('dblclick', (e) => {
- clearTimeout(clickTimer);
- showSettingsPanel();
- });
- return item;
- }
-
- if (!isSetting && url !== 'help') {
- item.addEventListener('dblclick', (e) => {
- e.stopPropagation();
- if (confirm(`确认删除 ${name} 解析源?`)) {
- CONFIG.parseUrls = CONFIG.parseUrls.filter(v => v[0] !== url);
- GM_setValue('storedParseUrls', CONFIG.parseUrls);
- item.remove();
- }
- });
- }
-
- item.addEventListener('click', (e) => {
- if (item.dataset.func) return;
- e.stopPropagation();
- if (!isSetting) {
- document.querySelectorAll('.source-item').forEach(i => i.classList.remove('selected'));
- item.classList.add('selected');
- GM_setValue('selectedParseUrl', CONFIG.parseUrl = url);
- }
- document.getElementById('parse-source-list')?.remove();
- });
-
- return item;
- };
-
- const showSettingsPanel = () => {
- document.querySelectorAll('#parse-source-list').forEach(p => p.remove());
- const panel = createElement('div', {
- id: 'parse-source-list',
- className: 'source-list',
- style: `right:${CONFIG.buttonRight};bottom:calc(${CONFIG.buttonBottom} + ${CONFIG.buttonSize + 10}px)`
- });
-
- const settings = [
- ['parseList', '解析列表'],
- ['listBgColor', '背景颜色'],
- ['listBlur', '模糊强度'],
- ['listFontSize', '字体大小'],
- ['listColor', '字体颜色'],
- ['selectedColor', '选中颜色'],
- ['itemHoverBg', '悬停背景'],
- ['imageUrl', '更换图标'],
- ['buttonRadius', '图标圆角'],
- ['reset', '恢复默认']
- ];
-
- settings.forEach(([key, label]) => {
- const item = createListItem([key, label], true);
- item.addEventListener('click', () => {
- if (key === 'reset') {
- if (confirm('即将清除所有配置!确认重置吗?')) {
- GM_deleteValue('storedParseUrls');
- GM_deleteValue('selectedParseUrl');
- Object.keys(CONFIG).forEach(k => GM_deleteValue(k));
- location.reload(true);
- }
- return;
- }
-
- if (key === 'parseList') {
- showParseListPanel();
- return;
- }
-
- const val = prompt(`设置${key} (当前:${GM_getValue(key, CONFIG[key])})`, GM_getValue(key, CONFIG[key]));
- if (val !== null) {
- GM_setValue(key, val);
- updateStyles();
- if (key === 'imageUrl') floatingButton.src = val;
- }
- panel.remove();
- });
- panel.appendChild(item);
- });
-
- const closeHandler = (e) => {
- if (!panel.contains(e.target) && e.target !== floatingButton) {
- panel.remove();
- document.removeEventListener('click', closeHandler);
- }
- };
- document.addEventListener('click', closeHandler);
- document.body.appendChild(panel);
- };
-
- const showParseListPanel = () => {
- document.querySelectorAll('#parse-source-list').forEach(p => p.remove());
-
- const panel = createElement('div', {
- id: 'parse-source-list',
- className: 'source-list',
- style: `
- right: ${CONFIG.buttonRight};
- bottom: calc(${CONFIG.buttonBottom} + ${CONFIG.buttonSize + 10}px);
- max-width: 366px;
- max-height: 60vh;
- overflow-y: auto;
- `
- });
-
- const title = createElement('div', {
- className: 'source-item',
- textContent: '当前解析源(双击删除)',
- style: 'font-weight:bold;color: #000;'
- });
- panel.appendChild(title);
-
- CONFIG.parseUrls
- .filter(([url]) => url !== 'set')
- .forEach(([url, name]) => {
- const item = createElement('div', {
- className: 'source-item',
- textContent: `${name} : ${url}`,
- title: '双击删除此解析源'
- });
-
- item.addEventListener('dblclick', () => {
- CONFIG.parseUrls = CONFIG.parseUrls.filter(([u]) => u !== url);
- GM_setValue('storedParseUrls', CONFIG.parseUrls);
- item.remove();
- showParseListPanel();
- });
-
- panel.appendChild(item);
- });
-
- const addForm = createElement('div', {
- className: 'source-item',
- style: 'display:flex;gap:5px;padding:1px 8px;'
- });
-
- const input = createElement('input', {
- type: 'text',
- placeholder: '格式:名称,URL',
- style: 'flex:1;text-align:center;border-radius:18px;border:none;'
- });
-
- const addBtn = createElement('button', {
- textContent: '+ 添加',
- style: 'text-align:center;padding:3px 8px;border-radius:12px;background:#2196F3;color:white;border:none;',
- onclick: () => {
- const [name, url] = input.value.split(',').map(s => s.trim());
- if (name && url) {
- CONFIG.parseUrls.unshift([url, name]);
- GM_setValue('storedParseUrls', CONFIG.parseUrls);
- input.value = '';
- showParseListPanel();
- }
- }
- });
-
- addForm.appendChild(input);
- addForm.appendChild(addBtn);
- panel.appendChild(addForm);
-
- const closeHandler = (e) => {
- if (!panel.contains(e.target) && e.target !== floatingButton) {
- panel.remove();
- document.removeEventListener('click', closeHandler);
- }
- };
-
- document.addEventListener('click', closeHandler);
- document.body.appendChild(panel);
- };
-
- const initButtonEvents = () => {
- floatingButton.addEventListener('mouseenter', () => {
- floatingButton.style.opacity = '1';
- floatingButton.style.transform = 'scale(1.1)';
- });
-
- floatingButton.addEventListener('mouseleave', () => {
- floatingButton.style.opacity = GM_getValue('opacity', CONFIG.opacity);
- floatingButton.style.transform = 'none';
- });
-
- floatingButton.addEventListener('click', () => {
- clearTimeout(clickTimer);
- clickTimer = setTimeout(() => window.open(GM_getValue('parseUrl', CONFIG.parseUrl) + location.href), 250);
- });
-
- floatingButton.addEventListener('dblclick', () => {
- clearTimeout(clickTimer);
- document.querySelectorAll('#parse-source-list').forEach(p => p.remove());
-
- const list = createElement('div', {
- id: 'parse-source-list',
- className: 'source-list',
- style: `
- right: ${CONFIG.buttonRight};
- bottom: calc(${CONFIG.buttonBottom} + ${CONFIG.buttonSize + 10}px);
- `
- });
-
- CONFIG.parseUrls.forEach(item => list.appendChild(createListItem(item)));
-
- const closeHandler = (e) => {
- if (!list.contains(e.target) && e.target !== floatingButton) {
- list.remove();
- document.removeEventListener('click', closeHandler);
- }
- };
-
- document.addEventListener('click', closeHandler);
- document.body.appendChild(list);
- });
-
- const initIdleMode = () => {
- const enterIdle = () => {
- floatingButton.style.opacity = '0.8';
- floatingButton.style.animationPlayState = 'paused';
- };
-
- const exitIdle = () => {
- floatingButton.style.opacity = GM_getValue('opacity', CONFIG.opacity);
- floatingButton.style.animationPlayState = 'running';
- };
-
- const resetTimer = () => {
- clearTimeout(idleTimer);
- exitIdle();
- idleTimer = setTimeout(enterIdle, 10000);
- };
-
- ['mouseenter', 'click', 'dblclick'].forEach(evt =>
- floatingButton.addEventListener(evt, resetTimer)
- );
-
- resetTimer();
- };
-
- initIdleMode();
- };
-
- const init = () => {
- floatingButton = createFloatingButton();
- document.body.appendChild(floatingButton);
- initButtonEvents();
- };
-
- document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();
- })();