// ==UserScript== // @name agefans Enhance // @namespace https://github.com/IronKinoko/agefans-enhance // @icon https://www.agemys.com/favicon.ico // @version 1.27.3 // @description 增强agefans播放功能,实现自动换集、无缝换集、画中画、历史记录、断点续播、弹幕等功能 // @author IronKinoko // @include https://www.age.tv/* // @include https://www.agefans.* // @include https://www.agemys.* // @include *://*.yhdm.so/* // @include *://*.yinghuacd.com/* // @include https://www.yhdmp.cc/vp/* // @include http://www.imomoe.live/player/* // @include http://www.88dmw.com/* // @include https://new-ani.me/* // @include https://bangumi.online/* // @include https://danmu.4dm.cc/m3u8.php* // @include http*://www.ntyou.* // @include https://www.dm233.* // @include https://www.olevod.com* // @include https://www.bimiacg4.net* // @include https://*omofun.tv/* // @run-at document-body // @require https://unpkg.com/jquery@3.6.0/dist/jquery.min.js // @require https://unpkg.com/plyr@3.6.4/dist/plyr.min.js // @require https://unpkg.com/hls.js@1.0.9/dist/hls.min.js // @require https://unpkg.com/@ironkinoko/danmaku@1.2.6/dist/danmaku.umd.js // @resource plyrCSS https://unpkg.com/plyr@3.6.4/dist/plyr.css // @grant GM_getResourceText // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @connect api.acplay.net // @connect chinacloudsites.cn // @license MIT // @downloadURL none // ==/UserScript== /** * 权限声明: * 1. GM_xmlhttpRequest * 脚本会请求有限的网络权限。仅用于访问弹幕查询功能需要链接到的 api.acplay.net 与 chinacloudsites.cn 第三方域名 * 你可以从 脚本编辑/设置/XHR安全 中管理网络权限 * * 2. GM_getResourceText, GM_addStyle * 获取播放器样式文件,用于播放器样式渲染 * * 3. GM_getValue, GM_setValue * 脚本会使用本地存储功能,用于在不同页面间保存“播放器配置”与“agefans 历史浏览记录”。 * * 4. @include * 脚本还匹配了 agefans 以外的一些链接,用于提供相同视频资源搜索功能 */ (function() { let plyrCSS = GM_getResourceText('plyrCSS') GM_addStyle(plyrCSS) })(); (function (Hls, Plyr, Danmaku) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var Hls__default = /*#__PURE__*/_interopDefaultLegacy(Hls); var Plyr__default = /*#__PURE__*/_interopDefaultLegacy(Plyr); var Danmaku__default = /*#__PURE__*/_interopDefaultLegacy(Danmaku); var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n typeof test === "string" ? target.includes(test) || test === "*" : test.test(target); } class Runtime { constructor() { this.list = []; } register(item) { this.list.push(item); } async getSearchActions() { const isInIframe = parent !== self; const searchs = this.list.map((o) => o.search).filter(Boolean).filter((o) => !(isInIframe && o.disabledInIframe)); const register = this.getActiveRegister(); const info = await this.getCurrentVideoNameAndEpisode(); if (!(info == null ? void 0 : info.name)) return []; let name = info.name; return searchs.filter((search) => search !== register.search && search.search).map((search) => ({ name: search.name, search: () => { const url = search.search(encodeURIComponent(name)); if (!url) return; if (isInIframe) parent.postMessage({ key: "openLink", url }, "*"); else window.open(url); } })); } async getCurrentVideoNameAndEpisode() { var _a, _b, _c; const register = this.getActiveRegister(); if (!((_a = register.search) == null ? void 0 : _a.getSearchName)) return; let rawName = await register.search.getSearchName(); let episode = await ((_c = (_b = register.search).getEpisode) == null ? void 0 : _c.call(_b)) || ""; if (!rawName) return; let name = rawName.replace(/第.季/, "").replace(/[<>《》''‘’""“”\[\]]/g, "").trim(); episode = episode.replace(/[第集话]/g, "").replace(/^0+/, ""); return { name, rawName, episode }; } getActiveRegister() { const registers = this.list.filter(({ domains }) => domains.some(createTest(location.origin))); if (registers.length !== 1) throw new Error("\u6FC0\u6D3B\u7684\u57DF\u540D\u5E94\u8BE5\u5C31\u4E00\u4E2A"); return registers[0]; } getActiveOpts() { const register = this.getActiveRegister(); return register.opts.filter(({ test }) => { const testArr = Array.isArray(test) ? test : [test]; return testArr.some(createTest(location.pathname + location.search)); }); } run() { let setupList = []; let runList = []; const opts = this.getActiveOpts(); opts.forEach(({ run, runInIframe, setup }) => { let needRun = runInIframe ? parent !== self : parent === self; if (needRun) { setup && setupList.push(setup); runList.push(run); } }); setupList.forEach((setup) => setup()); window.addEventListener("DOMContentLoaded", () => { runList.forEach((run) => run()); }); } } const runtime = new Runtime(); var css$f = ".agefans-wrapper .loginout a {\n cursor: pointer;\n text-decoration: underline;\n}\n.agefans-wrapper .loginout a + a {\n margin-left: 8px;\n}\n.agefans-wrapper .nav_button {\n cursor: pointer;\n}\n.agefans-wrapper .res_links {\n word-break: break-all;\n word-wrap: break-word;\n}\n\n@media (max-width: 480px) {\n .nav_button:nth-child(n+6) {\n display: inline-block;\n }\n\n #nav {\n position: relative;\n overflow-x: auto;\n white-space: nowrap;\n height: 91px;\n }\n #nav::-webkit-scrollbar {\n display: none;\n }\n #nav .nav_button {\n white-space: nowrap;\n }\n\n #top_search_from {\n width: calc(100% - 16px);\n float: left;\n margin-top: 10px;\n position: sticky;\n left: 8px;\n margin: 8px;\n }\n\n #new_tip1 {\n margin-top: 10px !important;\n }\n}"; n(css$f,{}); var css$e = ".agefans-wrapper .page-preview-trigger .page-preview {\n position: fixed;\n pointer-events: none;\n background-color: rgb(32, 32, 32);\n border: 1px solid rgb(64, 64, 65);\n z-index: 1000;\n display: flex;\n border-radius: 4px;\n overflow: hidden;\n}\n.agefans-wrapper .page-preview-trigger .page-preview-center {\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.agefans-wrapper .page-preview-trigger .page-preview .baseblock2 {\n border: none;\n border-left: 1px solid #404041;\n border-radius: 0;\n}\n.agefans-wrapper .page-preview-trigger .blocktitle.detail_title1 {\n color: #e0e0e0;\n border-bottom: 1px solid #404041;\n}\n.agefans-wrapper .page-preview-trigger .detail_imform_tag {\n display: inline-block;\n color: #808081;\n min-width: 5em;\n}\n.agefans-wrapper .page-preview-trigger .detail_imform_value {\n color: #e0e0e0;\n}\n.agefans-wrapper .page-preview-trigger .detail_imform_show_full {\n display: none;\n}\n.agefans-wrapper .page-preview-trigger .detail_imform_kv {\n min-width: 200px;\n max-width: 256px;\n display: inline-block;\n margin: 3px 0px;\n word-break: break-all;\n word-wrap: break-word;\n}\n.agefans-wrapper .page-preview-trigger .detail_imform_desc_pre {\n font-size: 15px;\n}\n.agefans-wrapper .page-preview-trigger .detail_imform_desc_pre * {\n color: #e0e0e0;\n}\n.agefans-wrapper .page-preview-trigger .detail_imform_name {\n margin: 0px;\n color: #d0e0f0;\n font-size: 1.2em;\n font-weight: bold;\n display: inline-block;\n}"; n(css$e,{}); function createStorage$1(storage) { function getItem(key, defaultValue) { try { const value = storage.getItem(key); if (value) return JSON.parse(value); return defaultValue; } catch (error) { return defaultValue; } } return { getItem, setItem(key, value) { storage.setItem(key, JSON.stringify(value)); }, removeItem: storage.removeItem.bind(storage), clear: storage.clear.bind(storage) }; } const session = createStorage$1(window.sessionStorage); const local = createStorage$1(window.localStorage); const gm = { getItem: GM_getValue, setItem: GM_setValue }; function parseToURL(url, count = 0) { if (count > 4) throw new Error("url\u89E3\u6790\u5931\u8D25 " + url); try { url = new URL(url); } catch (error) { url = decodeURIComponent(url); url = parseToURL(url, ++count); } return url.toString(); } function ageBlock(params) { const { title, content } = params; return `
${title}
${content}
`; } function set(name, value, _in_days = 1) { var Days = _in_days; var exp = new Date(); exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1e3); document.cookie = name + "=" + escape(String(value)) + ";expires=" + exp.toUTCString() + ";path=/"; } function get(name) { let reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); let arr = document.cookie.match(reg); if (arr) { return decodeURIComponent(arr[2]); } else { return null; } } const Cookie = { get, set, remove: function(name) { set(name, "", 0); } }; 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; Cookie.set("fa_t", Date.now(), 1); Cookie.set("fa_c", 1, 1); return _getplay_url; } function updateCookie(href) { href = href ? location.origin + href : location.href; return new Promise((resolve, reject) => { var _a, _b, _c; const doneFn = () => { resolve(); dom.remove(); }; const dom = document.createElement("iframe"); dom.style.display = "none"; dom.src = href; document.body.append(dom); (_a = dom.contentWindow) == null ? void 0 : _a.addEventListener("DOMContentLoaded", doneFn); (_b = dom.contentWindow) == null ? void 0 : _b.addEventListener("load", doneFn); (_c = dom.contentWindow) == null ? void 0 : _c.addEventListener("error", reject); }); } const LOCAL_PLAY_URL_KEY = "play-url-key"; function insertBTSites() { const title = $("#detailname a").text(); const encodedTitle = encodeURIComponent(title); const sites = [ { title: "\u871C\u67D1\u8BA1\u5212", url: `https://mikanani.me/Home/Search?searchstr=${encodedTitle}` } ]; $(ageBlock({ title: "\u79CD\u5B50\u8D44\u6E90\uFF1A", content: sites.map((site) => `${site.title}`).join("") })).insertAfter(".baseblock:contains(\u7F51\u76D8\u8D44\u6E90)"); } const loadingIcon = ` `; function getLocal(href) { const map = session.getItem(LOCAL_PLAY_URL_KEY, {}); if (href) { const item = map[href]; if (!item) return null; return item.url; } return map; } function saveLocal(href, url) { const map = getLocal(); map[href] = { url }; session.setItem(LOCAL_PLAY_URL_KEY, map); } function removeLocal(href) { const map = getLocal(); delete map[href]; session.setItem(LOCAL_PLAY_URL_KEY, map); } class AGEfansError extends Error { constructor(message) { super(message); this.name = "AGEfans Enhance Exception"; } } async function getVurl(href) { const res = await fetch(getPlayUrl(href), { referrerPolicy: "strict-origin-when-cross-origin" }); const text = await res.text(); if (text.includes("ipchk")) { throw new AGEfansError(`\u4F60\u88AB\u9650\u6D41\u4E86\uFF0C\u8BF75\u5206\u949F\u540E\u91CD\u8BD5\uFF08${text}\uFF09`); } if (text.includes("timeout")) { throw new AGEfansError(`Cookie\u8FC7\u671F\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u91CD\u8BD5\uFF08${text}\uFF09`); } function __qpic_chkvurl_converting(_in_vurl) { const vurl2 = decodeURIComponent(_in_vurl); const match_resl = vurl2.match(/^http.+\.f20\.mp4\?ptype=http\?w5=0&h5=0&state=1$/); return !!match_resl; } const _json_obj = JSON.parse(text); const _purl = _json_obj["purl"]; const _vurl = _json_obj["vurl"]; const _playid = _json_obj["playid"]; if (__qpic_chkvurl_converting(_vurl)) { throw new AGEfansError("\u89C6\u9891\u8F6C\u7801\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5"); } if (_playid === "QLIVE") { throw new AGEfansError("\u811A\u672C\u4E0D\u652F\u6301QLIVE\u6A21\u5F0F\uFF0C\u8BF7\u4F7F\u7528\u5173\u95ED\u811A\u672C\u4F7F\u7528\u539F\u751F\u64AD\u653E"); } let _url = _purl + _vurl; let url = new URL(_url, location.origin); const vurl = url.searchParams.get("url"); return parseToURL(vurl); } async function getVurlWithLocal(href) { let vurl = getLocal(href); if (vurl) { return vurl; } await updateCookie(href); vurl = await getVurl(href); saveLocal(href, vurl); return vurl; } function initGetAllVideoURL() { insertBTSites(); } var css$d = ".k-modal {\n position: fixed;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n text-align: left;\n animation: fadeIn 0.3s ease forwards;\n color: rgba(0, 0, 0, 0.85);\n}\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n.k-modal * {\n color: inherit;\n}\n.k-modal-mask {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n background: rgba(0, 0, 0, 0.45);\n cursor: pointer;\n}\n.k-modal-wrap {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n overflow: auto;\n text-align: center;\n user-select: none;\n}\n.k-modal-wrap::before {\n content: \"\";\n display: inline-block;\n width: 0;\n height: 100%;\n vertical-align: middle;\n}\n.k-modal-container {\n margin: 20px 0;\n display: inline-block;\n vertical-align: middle;\n text-align: left;\n position: relative;\n width: 520px;\n min-height: 100px;\n background: white;\n border-radius: 2px;\n user-select: text;\n}\n.k-modal-header {\n font-size: 16px;\n border-bottom: 1px solid #f1f1f1;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n.k-modal-header-title {\n padding: 16px;\n}\n.k-modal-close {\n cursor: pointer;\n height: 55px;\n width: 55px;\n position: absolute;\n right: 0;\n top: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n user-select: none;\n}\n.k-modal-close * {\n color: rgba(0, 0, 0, 0.45);\n transition: color 0.15s ease;\n}\n.k-modal-close:hover * {\n color: rgba(0, 0, 0, 0.85);\n}\n.k-modal-body {\n padding: 16px;\n font-size: 14px;\n}\n.k-modal-footer {\n padding: 10px 16px;\n font-size: 14px;\n border-top: 1px solid #f1f1f1;\n display: flex;\n justify-content: flex-end;\n}\n.k-modal-btn {\n user-select: none;\n display: flex;\n align-items: center;\n justify-content: center;\n height: 32px;\n line-height: 32px;\n border-radius: 2px;\n border: 1px solid #1890ff;\n background: #1890ff;\n color: white;\n min-width: 64px;\n cursor: pointer;\n padding: 0 8px;\n}"; n(css$d,{}); function modal(opts) { const { title, content, onClose, onOk, okText = "\u786E \u5B9A" } = opts; const store = { width: document.body.style.width, overflow: document.body.style.overflow }; const ID = Math.random().toString(16).slice(2); $(` `).appendTo("body"); $("body").css({ width: `calc(100% - ${window.innerWidth - document.body.clientWidth}px)`, overflow: "hidden" }); if (title) { $(`#${ID} .k-modal-header-title`).append(title); } else $(`#${ID} .k-modal-header-title`).remove(); $(`#${ID} .k-modal-body`).append(content); $(`#${ID} .k-modal-close`).on("click", () => { handleClose(); }); $(`#${ID} .k-modal-container`).on("click", (e) => { e.stopPropagation(); }); $(`#${ID} .k-modal-wrap`).on("click", () => { handleClose(); }); function reset() { $(`#${ID}`).remove(); $("body").css(store); window.removeEventListener("keydown", fn, { capture: true }); } function handleClose() { onClose == null ? void 0 : onClose(); reset(); } function handleOk() { onOk == null ? void 0 : onOk(); reset(); } function fn(e) { if (["Escape"].includes(e.key)) { e.stopPropagation(); handleClose(); } } window.addEventListener("keydown", fn, { capture: true }); if (onOk) { $(`#${ID} .k-modal-container`).append(` `); $(`#${ID} .k-modal-ok`).on("click", () => { handleOk(); }); } } var css$c = ".k-alert {\n margin-bottom: 16px;\n box-sizing: border-box;\n color: black;\n font-size: 14px;\n font-variant: tabular-nums;\n line-height: 1.5715;\n list-style: none;\n font-feature-settings: \"tnum\";\n position: relative;\n display: flex;\n align-items: center;\n padding: 8px 15px;\n word-wrap: break-word;\n border-radius: 2px;\n}\n.k-alert-icon {\n margin-right: 8px;\n display: inline-block;\n color: inherit;\n font-style: normal;\n line-height: 0;\n text-align: center;\n text-transform: none;\n vertical-align: -0.125em;\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n color: var(--k-player-primary-color);\n}\n.k-alert-content {\n flex: 1;\n min-width: 0;\n}\n.k-alert-info {\n background-color: var(--k-player-primary-color-highlight);\n border: 1px solid var(--k-player-primary-color);\n}"; n(css$c,{}); function alert(html) { return `
${html}
`; } var css$b = ".agefans-setting-item {\n cursor: pointer;\n width: 100%;\n white-space: nowrap;\n display: flex;\n align-items: center;\n line-height: 1;\n}\n.agefans-setting-item + .agefans-setting-item {\n margin-top: 12px;\n}\n.agefans-setting-item input {\n margin-right: 4px;\n}"; n(css$b,{}); const LOCAL_SETTING_KEY = "agefans-setting"; const defaultSetting = { usePreview: true }; function ensureDefaultSetting() { let setting = gm.getItem(LOCAL_SETTING_KEY, defaultSetting); setting = Object.assign({}, defaultSetting, setting); gm.setItem(LOCAL_SETTING_KEY, setting); } function setSetting(key, value) { const setting = gm.getItem(LOCAL_SETTING_KEY); setting[key] = value; gm.setItem(LOCAL_SETTING_KEY, setting); } function getSetting(key) { const setting = gm.getItem(LOCAL_SETTING_KEY); if (key) return setting[key]; return setting; } function showSetting() { const setting = getSetting(); const $usePreview = $(``); $usePreview.find("input").prop("checked", setting.usePreview).on("change", (e) => { setSetting("usePreview", e.target.checked); }); const $stopUseKPlayer = $(``); $stopUseKPlayer.find("input").prop("checked", session.getItem("stop-use")).on("change", (e) => { session.setItem("stop-use", e.target.checked); }); modal({ title: "\u811A\u672C\u8BBE\u7F6E", okText: "\u5237\u65B0\u9875\u9762", onOk: () => location.reload(), content: $("
").append(alert("\u8FD9\u4E9B\u914D\u7F6E\u9700\u8981\u5237\u65B0\u9875\u9762\u624D\u80FD\u751F\u6548"), $usePreview, $stopUseKPlayer) }); } function settingModule() { ensureDefaultSetting(); $("\u8BBE\u7F6E").on("click", showSetting).insertBefore(".loginout a"); } function pagePreview(trigger, previewURL) { if (!getSetting("usePreview")) return; const $popover = $(``).appendTo($(trigger)); function caclPosition(e) { const safeArea = 16; const offset = 20; const width = $popover.width() || 0; const height = $popover.height() || 0; const { innerWidth, innerHeight } = window; const { clientX, clientY } = e; const maxLeft = innerWidth - width - safeArea * 2; const maxTop = innerHeight - height - safeArea; const left = Math.min(clientX + offset, maxLeft); const top = clientX + offset > maxLeft && clientY + offset > maxTop ? clientY - offset - height : Math.min(clientY + offset, maxTop); $popover.css({ left, top }); } let isLoaded = false; let timeId; $(trigger).addClass("page-preview-trigger").on("mouseenter", (e) => { $popover.show(); caclPosition(e); if (isLoaded) return; clearTimeout(timeId); timeId = window.setTimeout(async () => { if (isLoaded) return; isLoaded = true; let { img, info } = session.getItem(previewURL, { img: "", info: "" }); if (!info) { const $root = $(await fetch(previewURL).then((r) => r.text())); img = $root.find("#container > div.div_left > div:nth-child(1) > div > img").css({ display: "block", width: 256, height: 356 }).prop("outerHTML"); const $info = $root.find("#container > div.div_left > div:nth-child(2) > div > div").width(256); $info.find(".blocktitle.detail_title1").text($root.find(".detail_imform_name").text()); info = $info.prop("outerHTML"); session.setItem(previewURL, { img, info }); } $popover.empty().append(img, info); caclPosition(e); }, 100); }).on("mousemove", (e) => { caclPosition(e); }).on("mouseleave", () => { clearTimeout(timeId); $popover.hide(); }); } function renderHistroyStyle() { $("