您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
增强agefans播放功能,实现自动换集、画中画、历史记录、断点续播等功能
当前为
// ==UserScript== // @name agefans Enhance // @namespace https://github.com/IronKinoko/agefans-enhance // @version 1.2.0 // @description 增强agefans播放功能,实现自动换集、画中画、历史记录、断点续播等功能 // @author IronKinoko // @match https://www.agefans.net/* // @match https://www.agefans.net/play/* // @match https://www.agefans.net/detail/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; function renderHistroyStyle() { // add a tag visited style let styleDom = document.createElement('style'); styleDom.innerHTML = `.movurl li a:visited { color: red; }`; document.head.appendChild(styleDom); } function detailModule() { renderHistroyStyle(); } class History { constructor() { this.cacheKey = 'v-his'; } get his() { return JSON.parse(localStorage.getItem(this.cacheKey) || '[]') } set his(value) { if (Array.isArray(value)) { localStorage.setItem(this.cacheKey, JSON.stringify(value.slice(0, 100))); } } getAll() { return this.his } get(id) { return this.his.find((o) => o.id === id) } setTime(id, time = 0) { const his = this.his; his.find((o) => o.id === id).time = time; this.his = his; } log(item) { const his = this.his; his.unshift(item); this.his = his; } refresh(id, data) { const his = this.his; const index = his.findIndex((o) => o.id === id); const item = his.splice(index, 1)[0]; his.unshift(data || item); this.his = his; } has(id) { return Boolean(this.his.find((o) => o.id === id)) } logHistory() { const id = location.pathname.match(/\/play\/(\d*)/)?.[1]; if (!id) return const hisItem = {}; hisItem.id = id; hisItem.title = $('#detailname a').text(); hisItem.href = location.href; hisItem.section = $('li a[style*="color: rgb(238, 0, 0);"]').text(); hisItem.time = 0; hisItem.logo = $('#play_poster_img').attr('src'); if (this.has(id)) { const oldItem = this.get(id); if (oldItem.href !== hisItem.href) { this.refresh(id, hisItem); } else { this.refresh(id); } } else { this.log(hisItem); } } } const his = new History(); function parseTime(time = 0) { return `${Math.floor(time / 60) .toString() .padStart(2, '0')}:${(time % 60).toString().padStart(2, '0')}` } function renderHistoryList() { $('#history') .html('') .append(() => { /** @type {any[]} */ const histories = his.getAll(); let html = ''; histories.forEach((o) => { html += `<a class="history-item" href="${o.href}"> <img referrerpolicy="no-referrer" src="${o.logo}" alt="${o.title}" title="${o.title}" /> <div class="desc"> <div class="title">${o.title}</div> <div class="position">${o.section} ${parseTime(o.time)}</div> </div> </a> `; }); return `<div class="history-list">${ html || '<center>暂无数据</center>' }</div>` }); } function renderHistoryPage() { const currentDom = $('.nav_button_current'); $( '<style>.nav_button{cursor: pointer;}#history{background:#202020;border:4px solid #303030;}.history-list{padding:16px;display:flex;flex-wrap:wrap;}.history-item{width:115px;display:inline-block;margin:4px}.history-item img{width: 100%;border-radius:2px}.history-item .desc .title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:14px;margin:4px 0}.history-item .desc .position{font-size:14px}</style>' ).appendTo('head'); $('<div id="history"></div>').insertBefore('#footer').hide(); $(`<a class="nav_button">历史</a>`) .appendTo('#nav') .on('click', (e) => { if ($('#history').is(':visible')) { $('#container').show(); $('#history').hide(); changeActive(currentDom); } else { renderHistoryList(); $('#container').hide(); $('#history').show(); changeActive($(e.currentTarget)); } }); $('.nav_button_current') .on('click', (e) => { $('#container').show(); $('#history').hide(); changeActive(e.currentTarget); }) .removeAttr('href'); } function changeActive(dom) { $('.nav_button_current').removeClass('nav_button_current'); $(dom).addClass('nav_button_current'); } function historyModule() { renderHistoryPage(); renderHistoryList(); } function copyToClipboard(element) { var $temp = $("<textarea>"); $("body").append($temp); $temp.val($(element).text()).trigger('select'); document.execCommand("copy"); $temp.remove(); } function checkCSS() { if ($('#k-modal-css').length === 0) { $(` <style id="k-modal-css"> .k-modal { position: fixed; left: 0; right: 0; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center; } .k-modal * { color: rgba(0, 0, 0, 0.85); } .k-modal .k-modal-mask { position: absolute; z-index: 100; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.45); } .k-modal .k-modal-container { position: absolute; z-index: 101; width: 520px; min-height: 100px; background: white; border-radius: 2px; } .k-modal .k-modal-header { font-size: 16px; padding: 16px; border-bottom: 1px solid #f1f1f1; display: flex; justify-content: space-between; align-items: center; } .k-modal .k-modal-close, .k-modal .k-modal-mask { cursor: pointer; } .k-modal .k-modal-body, .k-modal .k-modal-footer { padding: 16px; font-size: 14px; } .k-modal .k-modal-footer { border-top: 1px solid #f1f1f1; display: flex; justify-content: flex-end; } .k-modal .k-modal-btn { display: flex; align-items: center; justify-content: center; height: 32px; border-radius: 2px; border: 1px solid #2af; background: #2af; color: white; min-width: 64px; cursor: pointer; } </style> `).appendTo('head'); } } function modal({ title, content, onClose, onOk }) { checkCSS(); const ID = Math.random().toString(16).slice(2); $(` <div class="k-modal" role="dialog" id="${ID}"> <div class="k-modal-mask"></div> <div class="k-modal-container"> <div class="k-modal-header"> <div class="k-modal-title"></div> <a class="k-modal-close">X</a> </div> <div class="k-modal-body"> </div> </div> </div>`).appendTo('body'); $(`#${ID} .k-modal-title`).append(title); $(`#${ID} .k-modal-body`).append(content); $(`#${ID} .k-modal-close`).on('click', () => { $(`#${ID}`).remove(); onClose && onClose(); }); $(`#${ID} .k-modal-mask`).on('click', () => { $(`#${ID}`).remove(); onClose && onClose(); }); if (onOk) { $(`#${ID} .k-modal-container`).append(` <div class="k-modal-footer"> <button class="k-modal-btn k-modal-ok">确 定</button> </div> `); $(`#${ID} .k-modal-ok`).on('click', () => { onOk(); $(`#${ID}`).remove(); }); } } /** * @typedef {{title:string,href:string}} ATag * */ function __setCookie(name, value, _in_days) { var Days = _in_days; var exp = new Date(); exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000); document.cookie = name + '=' + escape(value) + ';expires=' + exp.toGMTString() + ';path=/'; } function __getCookie(name) { var arr, reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); if ((arr = document.cookie.match(reg))) { return unescape(arr[2]) } else { return null } } function getCookie2(name) { return __getCookie(name) } function FEI2(in_epi) { // var hf_epi = Number(in_epi); const time_curr = new Date().getTime(); var fa_t = Number(getCookie2('fa_t')); if (!fa_t) { fa_t = time_curr; } var fa_c = Number(getCookie2('fa_c')); if (!fa_c) { fa_c = 0; } // if (time_curr - fa_t > 6000) { fa_t = 0; fa_c = 0; } // fa_c += 1; fa_t = time_curr; // if (fa_c > 10) { fa_t = 0; fa_c = 0; // if (hf_epi > 1) { hf_epi = time_curr % hf_epi; if (!hf_epi) { hf_epi = 1; } } } __setCookie('fa_t', fa_t, 1); __setCookie('fa_c', fa_c, 1); return hf_epi } function getPlayUrl(_url) { const _rand = Math.random(); var _getplay_url = _url.replace( /.*\/play\/(\d+?)\?playid=(\d+)_(\d+).*/, '/_getplay?aid=$1&playindex=$2&epindex=$3' ) + '&r=' + _rand; var re_resl = _getplay_url.match(/[&?]+epindex=(\d+)/); const hf_epi = '' + FEI2(re_resl[1]); const t_epindex_ = 'epindex='; _getplay_url = _getplay_url.replace( t_epindex_ + re_resl[1], t_epindex_ + hf_epi ); return _getplay_url } function insertBtn() { $(` <div class="baseblock"> <div class="blockcontent"> <div id="wangpan-div" class="baseblock2"> <div class="blocktitle">获取全部视频链接:</div> <div class="blockcontent"> <a id="open-modal" class="res_links_a" style="cursor:pointer">获取全部视频链接</a> <span>|</span> <a id="all-select" class="res_links_a" style="cursor:pointer">复制内容</a> <div id="url-list" style="width:100%; max-height:400px; overflow:auto;"></div> </div> </div> </div> </div> `).insertAfter($('.baseblock:contains(网盘资源)')); $('#all-select').on('click', function () { copyToClipboard($('#url-list')); $(this).text('已复制'); setTimeout(() => { $(this).text('复制内容'); }, 1000); }); $('#open-modal').on('click', function () { modal({ title: '选择需要的链接', content: insertModalForm(), onOk: () => { let list = []; $('#modal-form input').each(function (_, el) { if (el.checked) { list.push({ title: $(this).data('title'), href: $(this).attr('name'), }); } }); insertResult(list); }, }); }); } /** * @return {ATag[]} */ function getAllVideoUrlList() { const $aTagList = $('.movurl:visible li a'); const aTags = []; $aTagList.each(function (index, aTag) { aTags.push({ title: aTag.textContent, href: aTag.getAttribute('href'), }); }); return aTags } function insertModalForm() { const list = getAllVideoUrlList(); let $dom = $(` <div id="modal-form"> <ul> ${list .map( (aTag) => ` <li> <label><input type="checkbox" name="${aTag.href}" data-title="${aTag.title}" checked />${aTag.title}</label> </li>` ) .join('')} </ul> </div> `); return $dom } function genUrlItem(title, content = '加载中...') { return `<div> <div style="white-space: nowrap;">[${title}]</div> <div class="url" data-status='0' style="word-break:break-all; word-wrap:break-word;">${content}</div> </div>` } /** * @param {ATag[]} list */ function insertResult(list) { const $parent = $('#url-list'); $parent.empty(); list.forEach((item) => { let $dom = $(genUrlItem(item.title)).appendTo($parent); let $msg = $dom.find('.url'); function _getUrl() { fetch(getPlayUrl(item.href)) .then((res) => res.json()) .then((res) => { const url = decodeURIComponent(res.vurl); saveLocal(item.href, item.title, url); $msg.text(url); $msg.data('status', '1'); }) .catch((error) => { console.error(error); $msg.empty(); $msg.data('status', '2'); $(`<a style="cursor:pointer">加载出错,重试</a>`) .appendTo($msg) .on('click', () => { _getUrl(); }); }); } _getUrl(); }); } const PLAY_URL_KEY = 'play-url-key'; function saveLocal(href, title, url) { const map = JSON.parse(window.localStorage.getItem(PLAY_URL_KEY) || '{}'); map[href] = { title, url }; window.localStorage.setItem(PLAY_URL_KEY, JSON.stringify(map)); } function getLocal() { return JSON.parse(window.localStorage.getItem(PLAY_URL_KEY) || '{}') } function insertLocal() { const map = getLocal(); const list = getAllVideoUrlList(); const $parent = $('#url-list'); $( list .map((item) => { if (map[item.href]) { return genUrlItem(item.title, map[item.href].url) } else { return '' } }) .join('') ).appendTo($parent); } function initGetAllVideoURL() { insertBtn(); insertLocal(); } function replacePlayer() { const dom = document.getElementById('age_playfram'); dom.setAttribute('allow', 'autoplay'); const prefix = 'https://ironkinoko.github.io/agefans-enhance/?url='; const fn = () => { let url = new URL(dom.src); if (url.hostname.includes('agefans')) { let videoURL = url.searchParams.get('url'); if (videoURL) { dom.src = prefix + encodeURIComponent(videoURL); showCurrentLink(videoURL); } } // 移除版权规避提示 if ($(dom).css('display') === 'none') { $(dom).show(); } }; const mutationOb = new MutationObserver(fn); mutationOb.observe(dom, { attributes: true }); fn(); } function showCurrentLink(url) { $(` <div class="baseblock"> <div class="blockcontent"> <div id="wangpan-div" class="baseblock2"> <div class="blocktitle">本集链接:</div> <div class="blockcontent"> <span class="res_links"> ${decodeURIComponent(url)} </span> <br> </div> </div> </div> </div> `).insertBefore($('.baseblock:contains(网盘资源)')); } function gotoNextPart() { const dom = document.querySelector("li a[style*='color: rgb(238, 0, 0);']") .parentElement.nextElementSibling; if (dom) { dom.children[0].click(); } } function toggleFullScreen() { let dom = document.querySelector('.fullscn'); dom.click(); } function notifyChildToggleFullScreen(isFull) { const dom = document.getElementById('age_playfram'); dom.contentWindow.postMessage({ code: 666, isFull }, '*'); } function initPlayPageStyle() { let dom = document.querySelector('.fullscn'); dom.onclick = () => { if (document.body.style.overflow === 'hidden') { document.body.style.overflow = ''; notifyChildToggleFullScreen(false); } else { document.body.style.overflow = 'hidden'; notifyChildToggleFullScreen(true); } }; dom.style.opacity = 0; let ageframediv = document.getElementById('ageframediv'); let { width } = ageframediv.getBoundingClientRect(); ageframediv.style.height = (width / 16) * 9 + 'px'; } function prerenderNextPartHTML() { const dom = document.querySelector("li a[style*='color: rgb(238, 0, 0);']") .parentElement.nextElementSibling; if (dom) { const link = document.createElement('link'); link.rel = 'prerender'; link.href = dom.children[0].href; document.head.appendChild(link); } } function updateTime(time = 0) { const id = location.pathname.match(/\/play\/(\d*)/)?.[1]; if (!id) return his.setTime(id, Math.floor(time)); } function notifyChildJumpToHistoryPosition() { const id = location.pathname.match(/\/play\/(\d*)/)?.[1]; if (!id) return if (his.get(id)?.time && his.get(id)?.time > 3) { const dom = document.getElementById('age_playfram'); dom.contentWindow.postMessage({ code: 999, time: his.get(id).time }, '*'); } } function addListener() { window.addEventListener('message', (e) => { if (e.data?.code === 233) { gotoNextPart(); } if (e.data?.code === 200) { notifyChildJumpToHistoryPosition(); } if (e.data?.code === 666) { toggleFullScreen(); } if (e.data?.code === 999) { updateTime(e.data.time); } }); } function removeCpraid() { $('#cpraid').remove(); } function playModule() { addListener(); his.logHistory(); initPlayPageStyle(); replacePlayer(); prerenderNextPartHTML(); removeCpraid(); initGetAllVideoURL(); } if (parent === self) { historyModule(); // log page to history if (location.pathname.startsWith('/play')) { playModule(); } // in detail pages show view history if (location.pathname.startsWith('/detail')) { detailModule(); } } }());