- /* eslint-disable no-multi-spaces */
- /* eslint-disable no-useless-call */
-
- // ==UserScript==
- // @name SingleFile - 单文件保存网页
- // @name:en SingleFile - Webpage downloader
- // @name:en-US SingleFile - Webpage downloader
- // @name:en-UK SingleFile - Webpage downloader
- // @name:zh SingleFile - 单文件保存网页
- // @name:zh-CN SingleFile - 单文件保存网页
- // @name:zh-Hans SingleFile - 单文件保存网页
- // @name:zh-TW SingleFile - 單檔案保存網頁
- // @namespace SingleFile
- // @version 2.2
- // @description 将当前网页保存为一个.html网页文件
- // @description:en Save webpages into one .html file
- // @description:en-US Save webpages into one .html file
- // @description:en-UK Save webpages into one .html file
- // @description:zh 将当前网页保存为一个.html网页文件
- // @description:zh-CN 将当前网页保存为一个.html网页文件
- // @description:zh-Hans 将当前网页保存为一个.html网页文件
- // @description:zh-TW 將當前網頁保存為一個.html網頁檔案
- // @author PY-DNG
- // @license MIT
- // @include *
- // @connect *
- // @icon data:image/png;base64,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
- // @grant GM_xmlhttpRequest
- // @grant GM_registerMenuCommand
- // @grant GM_unregisterMenuCommand
- // @grant GM_info
- // @noframes
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // Arguments: level=LogLevel.Info, logContent, asObject=false
- // Needs one call "DoLog();" to get it initialized before using it!
- function DoLog() {
- // Global log levels set
- window.LogLevel = {
- None: 0,
- Error: 1,
- Success: 2,
- Warning: 3,
- Info: 4,
- }
- window.LogLevelMap = {};
- window.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
- window.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
- window.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
- window.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
- window.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
- window.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
-
- // Current log level
- DoLog.logLevel = (unsafeWindow ? unsafeWindow.isPY_DNG : window.isPY_DNG) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
-
- // Log counter
- DoLog.logCount === undefined && (DoLog.logCount = 0);
- if (++DoLog.logCount > 512) {
- console.clear();
- DoLog.logCount = 0;
- }
-
- // Get args
- let level, logContent, asObject;
- switch (arguments.length) {
- case 1:
- level = LogLevel.Info;
- logContent = arguments[0];
- asObject = false;
- break;
- case 2:
- level = arguments[0];
- logContent = arguments[1];
- asObject = false;
- break;
- case 3:
- level = arguments[0];
- logContent = arguments[1];
- asObject = arguments[2];
- break;
- default:
- level = LogLevel.Info;
- logContent = 'DoLog initialized.';
- asObject = false;
- break;
- }
-
- // Log when log level permits
- if (level <= DoLog.logLevel) {
- let msg = '%c' + LogLevelMap[level].prefix;
- let subst = LogLevelMap[level].color;
-
- if (asObject) {
- msg += ' %o';
- } else {
- switch(typeof(logContent)) {
- case 'string': msg += ' %s'; break;
- case 'number': msg += ' %d'; break;
- case 'object': msg += ' %o'; break;
- }
- }
-
- console.log(msg, subst, logContent);
- }
- }
- DoLog();
-
- bypassXB();
- GM_PolyFill('default');
-
- // Inner consts with i18n
- const CONST = {
- Number: {
- Max_XHR: 20,
- MaxUrlLength: 4096
- },
- Text: {
- 'zh-CN': {
- SavePage: '保存此网页',
- Saving: '保存中{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- },
- 'zh-Hans': {
- SavePage: '保存此网页',
- Saving: '保存中{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- },
- 'zh': {
- SavePage: '保存此网页',
- Saving: '保存中{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- },
- 'zh-TW': {
- SavePage: '保存此網頁',
- Saving: '保存中{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- },
- 'en-US': {
- SavePage: 'Save this webpage',
- Saving: 'Saving, please wait{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- },
- 'en-UK': {
- SavePage: 'Save this webpage',
- Saving: 'Saving, please wait{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- },
- 'en': {
- SavePage: 'Save this webpage',
- Saving: 'Saving, please wait{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- },
- 'default': {
- SavePage: 'Save this webpage',
- Saving: 'Saving, please wait{A}',
- About: '<!-- Web Page Saved By {SCNM} Ver.{VRSN}, Author {ATNM} -->\n<!-- Page URL: {LINK} -->'
- .replaceAll('{SCNM}', GM_info.script.name)
- .replaceAll('{VRSN}', GM_info.script.version)
- .replaceAll('{ATNM}', GM_info.script.author)
- .replaceAll('{LINK}', location.href)
- }
- }
- }
-
- // Get i18n code
- let i18n = navigator.language;
- if (!Object.keys(CONST.Text).includes(i18n)) {i18n = 'default';}
-
- // XHRHOOK
- GMXHRHook(CONST.Number.Max_XHR);
-
- main()
- function main() {
- // GUI
- let button = GM_registerMenuCommand(CONST.Text[i18n].SavePage, onclick);
- const SAnime = new SavingAnime;
- SAnime.model = CONST.Text[i18n].Saving;
- SAnime.callback = function(text) {
- GM_unregisterMenuCommand(button);
- button = GM_registerMenuCommand(text, () => {});
- }
-
- function onclick() {
- SAnime.start();
- Generate_Single_File({
- onfinish: (FinalHTML) => {
- saveTextToFile(FinalHTML, 'SingleFile - {Title} - {Time}.html'.replace('{Title}', document.title).replace('{Time}', getTime('-', '-')));
- GM_unregisterMenuCommand(button);
- SAnime.stop();
- button = GM_registerMenuCommand(CONST.Text[i18n].SavePage, onclick);
- }
- });
- }
-
- function SavingAnime() {
- const SA = this;
- SA.model = '{A}';
- SA.time = 1000;
- SA.index = 0;
- SA.frames = ['... ', ' ... ', ' ...', '. ..', '.. .'];
- SA.callback = (frametext) => {console.log(frametext);};
-
- SA.nextframe = function() {
- SA.index++;
- SA.index > SA.frames.length-1 && (SA.index = 0);
- SA.callback(SA.model.replace('{A}', SA.frames[SA.index]));
- return true;
- };
-
- SA.start = function() {
- if (SA.interval) {return false;}
- SA.index = 0;
- SA.interval = setInterval(SA.nextframe, SA.time);
- return true;
- }
-
- SA.stop = function() {
- if (!SA.interval) {return false;}
- clearInterval(SA.interval);
- SA.interval = 0;
- return true;
- }
- };
- }
-
- function Generate_Single_File(details) {
- // Init DOM
- const html = document.querySelector('html').outerHTML;
- const dom = (new DOMParser()).parseFromString(html, 'text/html');
-
- // Functions
- const _J = (args) => {const a = []; for (let i = 0; i < args.length; i++) {a.push(args[i]);}; return a;};
- const $ = function() {return dom.querySelector.apply(dom, _J(arguments))};
- const $_ = function() {return dom.querySelectorAll.apply(dom, _J(arguments))};
- const $C = function() {return dom.createElement.apply(dom, _J(arguments))};
- const $A = (a,b) => (a.appendChild(b));
- const $I = (a,b) => (b.parentElement ? b.parentElement.insertBefore(a, b) : null);
- const $R = (e) => (e.parentElement ? e.parentElement.removeChild(e) : null);
- const ishttp = (s) => (!/^[^\/:]*:/.test(s) || /^https?:\/\//.test(s));
- const ElmProps = new (function() {
- const props = this.props = {};
- const cssMap = this.cssMap = new Map();
-
- this.getCssPath = function(elm) {
- return cssMap.get(elm) || (cssMap.set(elm, cssPath(elm)), cssMap.get(elm));
- }
-
- this.add = function(elm, type, value) {
- const path = cssPath(elm);
- const EPList = props[path] = props[path] || [];
- const EProp = {};
- EProp.type = type;
- EProp.value = value;
- EPList.push(EProp);
- }
- });
-
- // Hook GM_xmlhttpRequest
- const AM = new AsyncManager();
- AM.onfinish = function() {
- // Add applyProps script
- const script = $C('script');
- script.innerText = "window.addEventListener('load', function(){({FUNC})({PROPS});})"
- .replace('{PROPS}', JSON.stringify(ElmProps.props))
- .replace('{FUNC}', `function(c){const funcs={Canvas:{DataUrl:function(a,b){const img=new Image();const ctx=a.getContext('2d');img.onload=()=>{ctx.drawImage(img,0,0)};img.src=b}},Input:{Value:function(a,b){a.value=b}}};for(const[cssPath,propList]of Object.entries(c)){const elm=document.querySelector(cssPath);for(const prop of propList){const type=prop.type;const value=prop.value;const funcPath=type.split('.');let func=funcs;for(let i=0;i<funcPath.length;i++){func=func[funcPath[i]]}func(elm,value)}}}`);
- $A(dom.head, script);
-
- // Generate html
- const FinalHTML = '{ABOUT}\n\n{HTML}'.replace('{ABOUT}', CONST.Text[i18n].About).replace('{HTML}', dom.querySelector('html').outerHTML)
-
- DoLog(LogLevel.Success, 'Single File Generation Complete.')
- DoLog([dom, FinalHTML]);
- details.onfinish(FinalHTML)
- };
-
- // Change document.characterSet to utf8
- DoLog('SingleFile: Setting charset');
- if (document.characterSet !== 'UTF-8') {
- const meta = $('meta[http-equiv="Content-Type"][content*="charset"]');
- meta && (meta.content = meta.content.replace(/charset\s*=\s*[^;\s]*/i, 'charset=UTF-8'));
- }
-
- // Clear scripts
- DoLog('SingleFile: Clearing scripts');
- for (const script of $_('script')) {
- $R(script);
- }
-
- // Clear inline-scripts
- DoLog('SingleFile: Clearing inline scripts');
- for (const elm of $_('*')) {
- const ISKeys = ['onabort', 'onerror', 'onresize', 'onscroll', 'onunload', 'oncancel', 'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'onclose', 'oncuechange', 'ondblclick', 'ondrag', 'ondragend', 'ondragenter', 'ondragexit', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onfocus', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onpause', 'onplay', 'onplaying', 'onprogress', 'onratechange', 'onreset', 'onresize', 'onscroll', 'onseeked', 'onseeking', 'onselect', 'onshow', 'onstalled', 'onsubmit', 'onsuspend', 'ontimeupdate', 'ontoggle', 'onvolumechange', 'onwaiting', 'onbegin', 'onend', 'onrepeat'];
- for (const key of ISKeys) {
- elm.removeAttribute(key);
- elm[key] = undefined;
- }
- }
-
- // Clear preload-scripts
- DoLog('SingleFile: Clearing preload scripts');
- for (const link of $_('link[rel*=modulepreload]')) {
- $R(link);
- }
-
- // Remove "Content-Security-Policy" meta header
- DoLog('SingleFile: Removing "Content-Security-Policy" meta headers');
- for (const m of $_('meta[http-equiv="Content-Security-Policy"]')) {
- $R(m);
- }
-
- // Deal styles
- /*
- DoLog('SingleFile: Dealing linked stylesheets');
- for (const link of $_('link[rel="stylesheet"]')) {
- if (!link.href) {continue;}
- const href = link.href;
- AM.add();
- requestText(href, (t, l) => {
- const s = $C('style');
- s.innerText = t;
- $I(s, l);
- $R(l);
- AM.finish();
- }, link);
- }
- */
-
- // Deal Style url(http) links
- DoLog('SingleFile: Dealing style urls');
- for (const link of $_('link[rel*=stylesheet][href]')) {
- dealLinkedStyle(link)
- }
- for (const elm of $_('style')) {
- elm.innerText && dealStyle(elm.innerText, (style, elm) => (elm.innerHTML = style), elm);
- }
-
- // Deal <link>s
- DoLog('SingleFile: Dealing links');
- for (const link of $_('link[href]')) {
- // Only deal http[s] links
- if (!link.href) {continue;}
- if (!ishttp(link.href)) {continue;}
-
- // Only deal links that rel includes one of the following:
- // icon, apple-touch-icon, apple-touch-startup-image, prefetch, preload, prerender, manifest, stylesheet
- // And in the same time NOT includes any of the following:
- // alternate
- let deal = false;
- const accepts = ['icon', 'apple-touch-icon', 'apple-touch-startup-image', 'prefetch', 'preload', 'prerender', 'manifest', 'stylesheet'];
- const excludes = ['alternate']
- const rels = link.rel.split(' ');
- for (const rel of rels) {
- deal = deal || (accepts.includes(rel) && !excludes.includes(rel));
- }
- if (!deal) {continue;}
-
- // Save original href to link.ohref
- link.ohref = link.href;
-
- AM.add();
- requestDataURL(link.href, function(durl, link) {
- link.href = durl;
-
- // Deal style if links to a stylesheet
- if (rels.includes('stylesheet')) {
- dealLinkedStyle(link);
- }
- AM.finish();
- }, link);
- }
-
- // Deal images' and sources' src
- DoLog('SingleFile: Dealing images\' & sources\' src');
- for (const img of $_('img[src], source[src]')) {
- // Get full src
- if (img.src.length > CONST.Number.MaxUrlLength) {continue;}
- if (!img.src) {continue;}
- if (!ishttp(img.src)) {continue;}
- const src = fullurl(img.src);
-
- // Get original img element
- const path = ElmProps.getCssPath(img);
- const oimg = document.querySelector(path);
-
- // Get data url
- let url;
- try {
- if (!oimg.complete) {throw new Error();}
- url = img2url(oimg);
- img.src = url;
- } catch (e) {
- if (img.src) {
- AM.add();
- requestDataURL(src, (url) => {
- img.src = url;
- AM.finish();
- });
- }
- }
- }
-
- // Deal images' and sources' srcset
- DoLog('SingleFile: Dealing images\' & sources\' srcset');
- for (const img of $_('img[srcset], source[srcset]')) {
- // Check if empty
- if (!img.srcset) {continue;}
-
- // Get all srcs list
- const list = img.srcset.split(',');
- for (let i = 0; i < list.length; i++) {
- const srcitem = list[i].trim();
- if (srcitem.length > CONST.Number.MaxUrlLength) {continue;}
- if (!srcitem) {continue}
- const parts = srcitem.replaceAll(/(\s){2,}/g, '$1').split(' ');
- if (!ishttp(parts[0])) {continue};
- const src = fullurl(parts[0]);
-
- list[i] = {
- src: src,
- rest: parts.slice(1, parts.length).join(' '),
- parts: parts,
- dataurl: null,
- string: null
- };
- }
-
- // Get all data urls into list
- const S_AM = new AsyncManager();
- const dlist = [];
- S_AM.onfinish = function() {
- img.srcset = dlist.join(',');
- AM.finish();
- }
- AM.add();
- for (const srcobj of list) {
- S_AM.add();
- requestDataURL(srcobj.src, (url, srcobj) => {
- srcobj.dataurl = url;
- srcobj.string = [srcobj.dataurl, srcobj.rest].join(' ');
- dlist.push(srcobj.string);
- S_AM.finish();
- }, srcobj);
- }
- S_AM.finishEvent = true;
- }
-
- // Deal canvases
- DoLog('SingleFile: Dealing canvases');
- for (const cvs of $_('canvas')) {
- let url;
- try {
- url = img2url(cvs);
- ElmProps.add(cvs, 'Canvas.DataUrl', url);
- } catch (e) {}
- }
-
- // Deal background-images
- DoLog('SingleFile: Dealing background-images');
- for (const elm of $_('*')) {
- const urlReg = /^\s*url\(\s*['"]?([^\(\)'"]+)['"]?\s*\)\s*$/;
- const bgImage = elm.style.backgroundImage;
- if (!bgImage) {continue;}
- if (bgImage.length > CONST.Number.MaxUrlLength) {continue;}
- if (bgImage === 'url("https://images.weserv.nl/?url=https://ae01.alicdn.com/kf/H3bbe45ee0a3841ec9644e1ea9aa157742.jpg")') {debugger;}
- if (bgImage && urlReg.test(bgImage)) {
- // Get full image url
- let url = bgImage.match(urlReg)[1];
- if (/^data:/.test(url)) {continue;}
- url = fullurl(url);
-
- // Get image
- AM.add();
- requestDataURL(url, function(durl, elm) {
- elm.style.backgroundImage = 'url({U})'.replace('{U}', durl);
- AM.finish();
- }, elm);
- }
- }
-
- // Deal input/textarea/progress values
- DoLog('SingleFile: Dealing values');
- for (const elm of $_('input,textarea,progress')) {
- // Query origin element's value
- const cssPath = ElmProps.getCssPath(elm);
- const oelm = document.querySelector(cssPath);
-
- // Add to property map
- oelm.value && ElmProps.add(elm, 'Input.Value', oelm.value);
- }
-
- // Get favicon.ico if no icon found
- DoLog('SingleFile: Dealing favicon.ico');
- if (!$('link[rel*=icon]')) {
- const I_AM = new AsyncManager();
- GM_xmlhttpRequest({
- method: 'GET',
- url: getHost() + 'favicon.ico',
- responseType: 'blob',
- onload: (e) => {
- if (e.status >= 200 && e.status < 300) {
- blobToDataURL(e.response, (durl) => {
- const icon = $C('link');
- icon.rel = 'icon';
- icon.href = durl;
- $A(dom.head, icon);
- });
- }
- I_AM.finish();
- }
- })
- }
-
- // Start generating the finish event
- DoLog('SingleFile: Waiting for async tasks to be finished');
- AM.finishEvent = true;
-
- function dealStyle(style, callback, args=[]) {
- const re = /url\(\s*['"]?([^\(\)'"]+)['"]?\s*\)/;
- const rg = /url\(\s*['"]?([^\(\)'"]+)['"]?\s*\)/g;
- const replace = (durl, urlexp, arg1, arg2, arg3) => {
- // Replace style text
- const durlexp = 'url("{D}")'.replace('{D}', durl);
- style = style.replaceAll(urlexp, durlexp);
-
- // Get args
- const args = [style];
- for (let i = 2; i < arguments.length; i++) {
- args.push(arguments[i]);
- }
- callback.apply(null, args);
- AM.finish();
- };
-
- const all = style.match(rg);
- if (!all) {return;}
- for (const urlexp of all) {
- // Check url
- if (urlexp.length > CONST.Number.MaxUrlLength) {continue;}
- const osrc = urlexp.match(re)[1];
- const baseurl = args instanceof HTMLLinkElement && args.ohref ? args.ohref : location.href;
- if (!ishttp(osrc)) {continue;}
- const src = fullurl(osrc, baseurl);
-
- // Request
- AM.add();
- requestDataURL(src, replace, [urlexp].concat(args));
- }
- }
- function dealLinkedStyle(link) {
- if (!link.href || !/^data:/.test(link.href)) {return;}
- const durl = link.href;
- const blob = dataURLToBlob(durl);
- const reader = new FileReader();
- reader.onload = () => {
- dealStyle(reader.result, (style, link) => {
- const blob = new Blob([style],{type:"text/css"});
- AM.add();
- blobToDataURL(blob, function(durl, link) {
- link.href = durl;
- AM.finish();
- }, link)
- }, link);
- AM.finish();
- }
- AM.add();
- reader.readAsText(blob);
- }
- }
-
- // This function is expected to be used on output html
- function applyProps(props) {
- const funcs = {
- Canvas: {
- DataUrl: function(elm, value) {
- const img = new Image();
- const ctx = elm.getContext('2d');
- img.onload = () => {ctx.drawImage(img, 0, 0);};
- img.src = value;
- }
- },
- Input: {
- Value: function(elm, value) {
- elm.value = value;
- }
- }
- };
-
- for (const [cssPath, propList] of Object.entries(props)) {
- const elm = document.querySelector(cssPath);
- for (const prop of propList) {
- const type = prop.type;
- const value = prop.value;
-
- // Get function
- const funcPath = type.split('.');
- let func = funcs;
- for (let i = 0; i < funcPath.length; i++) {
- func = func[funcPath[i]];
- }
-
- // Call function
- func(elm, value);
- }
- }
- }
-
- function fullurl(url, baseurl=location.href) {
- if (/^\/{2,}/.test(url)) {url = location.protocol + url;}
- if (!/^https?:\/\//.test(url)) {
- const base = baseurl.replace(/(.+\/).*?$/, '$1');;
- const a = document.createElement('a');
- a.href = base + url;
- url = a.href;
- }
- return url;
- }
-
- function cssPath(el) {
- if (!(el instanceof Element)) return;
- var path = [];
- while (el.nodeType === Node.ELEMENT_NODE) {
- var selector = el.nodeName.toLowerCase();
- if (el.id) {
- selector += '#' + el.id;
- path.unshift(selector);
- break;
- } else {
- var sib = el,
- nth = 1;
- while (sib = sib.previousElementSibling) {
- if (sib.nodeName.toLowerCase() == selector) nth++;
- }
- if (nth != 1) selector += ":nth-of-type(" + nth + ")";
- }
- path.unshift(selector);
- el = el.parentNode;
- }
- return path.join(" > ");
- }
-
- function requestText(url, callback, args=[]) {
- GM_xmlhttpRequest({
- method: 'GET',
- url: url,
- responseType: 'text',
- onload: function(response) {
- const text = response.responseText;
- const argvs = [text].concat(args);
- callback.apply(null, argvs);
- }
- })
- }
-
- function requestDataURL(url, callback, args=[]) {
- GM_xmlhttpRequest({
- method: 'GET',
- url: url,
- responseType: 'blob',
- onload: function(response) {
- const blob = response.response;
- blobToDataURL(blob, function(url) {
- const argvs = [url].concat(args);
- callback.apply(null, argvs);
- })
- }
- })
- }
-
- function blobToDataURL(blob, callback, args=[]) {
- const reader = new FileReader();
- reader.onload = function () {
- callback.apply(null, [reader.result].concat(args));
- }
- reader.readAsDataURL(blob);
- }
-
- function dataURLToBlob(dataurl) {
- let arr = dataurl.split(','),
- mime = arr[0].match(/:(.*?);/)[1],
- bstr = atob(arr[1]),
- n = bstr.length,
- u8arr = new Uint8Array(n)
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n)
- }
- return new Blob([u8arr], { type: mime })
- }
-
- function XHRFinisher() {
- const XHRF = this;
-
- // Ongoing xhr count
- this.xhrCount = 0;
-
- // Whether generate finish events
- this.finishEvent = false;
-
- // Original xhr
- this.GM_xmlhttpRequest = GM_xmlhttpRequest;
-
- // xhr provided for outer scope
- GM_xmlhttpRequest = function(details) {
- DoLog('XHRFinisher: Requesting ' + details.url);
-
- // Hook functions that will be called when xhr stops
- details.onload = wrap(details.onload)
- details.ontimeout = wrap(details.ontimeout)
- details.onerror = wrap(details.onerror)
- details.onabort = wrap(details.onabort)
-
- // Count increase
- XHRF.xhrCount++;
-
- // Start xhr
- XHRF.GM_xmlhttpRequest(details);
-
- function wrap(ofunc) {
- return function(e) {
- DoLog('XHRFinisher: Request ' + details.url + ' finish. ' + (XHRF.xhrCount-1).toString() + ' requests rest. ');
- ofunc(e);
- --XHRF.xhrCount === 0 && XHRF.finishEvent && XHRF.onfinish && XHRF.onfinish();
- }
- }
- }
- }
-
- function AsyncManager() {
- const AM = this;
-
- // Ongoing xhr count
- this.taskCount = 0;
-
- // Whether generate finish events
- let finishEvent = false;
- Object.defineProperty(this, 'finishEvent', {
- configurable: true,
- enumerable: true,
- get: () => (finishEvent),
- set: (b) => {
- finishEvent = b;
- b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
- }
- });
-
- // Add one task
- this.add = () => (++AM.taskCount);
-
- // Finish one task
- this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
- }
-
- function img2url(img) {
- const cvs = document.createElement('canvas');
- const ctx = cvs.getContext('2d');
- cvs.width = img.width;
- cvs.height = img.height;
- ctx.drawImage(img, 0, 0)
- return cvs.toDataURL();
- }
-
- // Get a time text like 1970-01-01 00:00:00
- // if dateSpliter provided false, there will be no date part. The same for timeSpliter.
- function getTime(dateSpliter='-', timeSpliter=':') {
- const d = new Date();
- let fulltime = ''
- fulltime += dateSpliter ? fillNumber(d.getFullYear(), 4) + dateSpliter + fillNumber((d.getMonth() + 1), 2) + dateSpliter + fillNumber(d.getDate(), 2) : '';
- fulltime += dateSpliter && timeSpliter ? ' ' : '';
- fulltime += timeSpliter ? fillNumber(d.getHours(), 2) + timeSpliter + fillNumber(d.getMinutes(), 2) + timeSpliter + fillNumber(d.getSeconds(), 2) : '';
- return fulltime;
- }
-
- // Just stopPropagation and preventDefault
- function destroyEvent(e) {
- if (!e) {return false;};
- if (!e instanceof Event) {return false;};
- e.stopPropagation();
- e.preventDefault();
- }
-
- // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR
- // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting)
- // (If the request is invalid, such as url === '', will return false and will NOT make this request)
- // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event
- // Requires: function delItem(){...} & function uniqueIDMaker(){...}
- function GMXHRHook(maxXHR=5) {
- const GM_XHR = GM_xmlhttpRequest;
- const getID = uniqueIDMaker();
- let todoList = [], ongoingList = [];
- GM_xmlhttpRequest = safeGMxhr;
-
- function safeGMxhr() {
- // Get an id for this request, arrange a request object for it.
- const id = getID();
- const request = {id: id, args: arguments, aborter: null};
-
- // Deal onload function first
- dealEndingEvents(request);
-
- /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES!
- // Stop invalid requests
- if (!validCheck(request)) {
- return false;
- }
- */
-
- // Judge if we could start the request now or later?
- todoList.push(request);
- checkXHR();
- return makeAbortFunc(id);
-
- // Decrease activeXHRCount while GM_XHR onload;
- function dealEndingEvents(request) {
- const e = request.args[0];
-
- // onload event
- const oriOnload = e.onload;
- e.onload = function() {
- reqFinish(request.id);
- checkXHR();
- oriOnload ? oriOnload.apply(null, arguments) : function() {};
- }
-
- // onerror event
- const oriOnerror = e.onerror;
- e.onerror = function() {
- reqFinish(request.id);
- checkXHR();
- oriOnerror ? oriOnerror.apply(null, arguments) : function() {};
- }
-
- // ontimeout event
- const oriOntimeout = e.ontimeout;
- e.ontimeout = function() {
- reqFinish(request.id);
- checkXHR();
- oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {};
- }
-
- // onabort event
- const oriOnabort = e.onabort;
- e.onabort = function() {
- reqFinish(request.id);
- checkXHR();
- oriOnabort ? oriOnabort.apply(null, arguments) : function() {};
- }
- }
-
- // Check if the request is invalid
- function validCheck(request) {
- const e = request.args[0];
-
- if (!e.url) {
- return false;
- }
-
- return true;
- }
-
- // Call a XHR from todoList and push the request object to ongoingList if called
- function checkXHR() {
- if (ongoingList.length >= maxXHR) {return false;};
- if (todoList.length === 0) {return false;};
- const req = todoList.shift();
- const reqArgs = req.args;
- const aborter = GM_XHR.apply(null, reqArgs);
- req.aborter = aborter;
- ongoingList.push(req);
- return req;
- }
-
- // Make a function that aborts a certain request
- function makeAbortFunc(id) {
- return function() {
- let i;
-
- // Check if the request haven't been called
- for (i = 0; i < todoList.length; i++) {
- const req = todoList[i];
- if (req.id === id) {
- // found this request: haven't been called
- delItem(todoList, i);
- return true;
- }
- }
-
- // Check if the request is running now
- for (i = 0; i < ongoingList.length; i++) {
- const req = todoList[i];
- if (req.id === id) {
- // found this request: running now
- req.aborter();
- reqFinish(id);
- checkXHR();
- }
- }
-
- // Oh no, this request is already finished...
- return false;
- }
- }
-
- // Remove a certain request from ongoingList
- function reqFinish(id) {
- let i;
- for (i = 0; i < ongoingList.length; i++) {
- const req = ongoingList[i];
- if (req.id === id) {
- ongoingList = delItem(ongoingList, i);
- return true;
- }
- }
- return false;
- }
- }
- }
-
- function parseDocument(htmlblob, callback, args=[]) {
- const reader = new FileReader();
- reader.onload = function(e) {
- const htmlText = reader.result;
- const dom = new DOMParser().parseFromString(htmlText, 'text/html');
- args = [dom].concat(args);
- callback.apply(null, args);
- //callback(dom, htmlText);
- }
- reader.readAsText(htmlblob, 'GBK');
- }
-
- // Get a url argument from lacation.href
- // also recieve a function to deal the matched string
- // returns defaultValue if name not found
- // Args: name, dealFunc=(function(a) {return a;}), defaultValue=null
- function getUrlArgv(details) {
- typeof(details) === 'string' && (details = {name: details});
- typeof(details) === 'undefined' && (details = {});
- if (!details.name) {return null;};
-
- const url = details.url ? details.url : location.href;
- const name = details.name ? details.name : '';
- const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;});
- const defaultValue = details.defaultValue ? details.defaultValue : null;
- const matcher = new RegExp(name + '=([^&]+)');
- const result = url.match(matcher);
- const argv = result ? dealFunc(result[1]) : defaultValue;
-
- return argv;
- }
-
- // Append a style text to document(<head>) with a <style> element
- function addStyle(css, id) {
- const style = document.createElement("style");
- id && (style.id = id);
- style.textContent = css;
- for (const elm of document.querySelectorAll('#'+id)) {
- elm.parentElement && elm.parentElement.removeChild(elm);
- }
- document.head.appendChild(style);
- }
-
- function saveTextToFile(text, name) {
- const blob = new Blob([text],{type:"text/plain;charset=utf-8"});
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = name;
- a.click();
- }
-
- // File download function
- // details looks like the detail of GM_xmlhttpRequest
- // onload function will be called after file saved to disk
- function downloadFile(details) {
- if (!details.url || !details.name) {return false;};
-
- // Configure request object
- const requestObj = {
- url: details.url,
- responseType: 'blob',
- onload: function(e) {
- // Save file
- saveFile(URL.createObjectURL(e.response), details.name);
-
- // onload callback
- details.onload ? details.onload(e) : function() {};
- }
- }
- if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
- if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
- if (details.onerror ) {requestObj.onerror = details.onerror;};
- if (details.onabort ) {requestObj.onabort = details.onabort;};
- if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
- if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
-
- // Send request
- GM_xmlhttpRequest(requestObj);
- }
-
- // get '/' splited API array from a url
- function getAPI(url=location.href) {
- return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);
- }
-
- // get host part from a url(includes '^https://', '/$')
- function getHost(url=location.href) {
- const match = location.href.match(/https?:\/\/[^\/]+\//);
- return match ? match[0] : match;
- }
-
- // Your code here...
- // Bypass xbrowser's useless GM_functions
- function bypassXB() {
- if (typeof(mbrowser) === 'object') {
- window.unsafeWindow = window.GM_setClipboard = window.GM_openInTab = window.GM_xmlhttpRequest = window.GM_getValue = window.GM_setValue = window.GM_listValues = window.GM_deleteValue = undefined;
- }
- }
-
- // GM_Polyfill By PY-DNG
- // 2021.07.18 - 2021.07.19
- // Simply provides the following GM_functions using localStorage, XMLHttpRequest and window.open:
- // Returns object GM_POLYFILLED which has the following properties that shows you which GM_functions are actually polyfilled:
- // GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_openInTab, GM_setClipboard, unsafeWindow(object)
- // All polyfilled GM_functions are accessable in window object/Global_Scope(only without Tempermonkey Sandboxing environment)
- function GM_PolyFill(name='default') {
- const GM_POLYFILL_KEY_STORAGE = 'GM_STORAGE_POLYFILL';
- let GM_POLYFILL_storage;
- const GM_POLYFILLED = {
- GM_setValue: true,
- GM_getValue: true,
- GM_deleteValue: true,
- GM_listValues: true,
- GM_xmlhttpRequest: true,
- GM_openInTab: true,
- GM_setClipboard: true,
- unsafeWindow: true,
- once: false
- }
-
- // Ignore GM_PolyFill_Once
- window.GM_POLYFILLED && window.GM_POLYFILLED.once && (window.unsafeWindow = window.GM_setClipboard = window.GM_openInTab = window.GM_xmlhttpRequest = window.GM_getValue = window.GM_setValue = window.GM_listValues = window.GM_deleteValue = undefined);
-
- GM_setValue_polyfill();
- GM_getValue_polyfill();
- GM_deleteValue_polyfill();
- GM_listValues_polyfill();
- GM_xmlhttpRequest_polyfill();
- GM_openInTab_polyfill();
- GM_setClipboard_polyfill();
- unsafeWindow_polyfill();
-
- function GM_POLYFILL_getStorage() {
- let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
- gstorage = gstorage ? JSON.parse(gstorage) : {};
- let storage = gstorage[name] ? gstorage[name] : {};
- return storage;
- }
-
- function GM_POLYFILL_saveStorage() {
- let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
- gstorage = gstorage ? JSON.parse(gstorage) : {};
- gstorage[name] = GM_POLYFILL_storage;
- localStorage.setItem(GM_POLYFILL_KEY_STORAGE, JSON.stringify(gstorage));
- }
-
- // GM_setValue
- function GM_setValue_polyfill() {
- typeof (GM_setValue) === 'function' ? GM_POLYFILLED.GM_setValue = false: window.GM_setValue = PF_GM_setValue;;
-
- function PF_GM_setValue(name, value) {
- GM_POLYFILL_storage = GM_POLYFILL_getStorage();
- name = String(name);
- GM_POLYFILL_storage[name] = value;
- GM_POLYFILL_saveStorage();
- }
- }
-
- // GM_getValue
- function GM_getValue_polyfill() {
- typeof (GM_getValue) === 'function' ? GM_POLYFILLED.GM_getValue = false: window.GM_getValue = PF_GM_getValue;
-
- function PF_GM_getValue(name, defaultValue) {
- GM_POLYFILL_storage = GM_POLYFILL_getStorage();
- name = String(name);
- if (GM_POLYFILL_storage.hasOwnProperty(name)) {
- return GM_POLYFILL_storage[name];
- } else {
- return defaultValue;
- }
- }
- }
-
- // GM_deleteValue
- function GM_deleteValue_polyfill() {
- typeof (GM_deleteValue) === 'function' ? GM_POLYFILLED.GM_deleteValue = false: window.GM_deleteValue = PF_GM_deleteValue;
-
- function PF_GM_deleteValue(name) {
- GM_POLYFILL_storage = GM_POLYFILL_getStorage();
- name = String(name);
- if (GM_POLYFILL_storage.hasOwnProperty(name)) {
- delete GM_POLYFILL_storage[name];
- GM_POLYFILL_saveStorage();
- }
- }
- }
-
- // GM_listValues
- function GM_listValues_polyfill() {
- typeof (GM_listValues) === 'function' ? GM_POLYFILLED.GM_listValues = false: window.GM_listValues = PF_GM_listValues;
-
- function PF_GM_listValues() {
- GM_POLYFILL_storage = GM_POLYFILL_getStorage();
- return Object.keys(GM_POLYFILL_storage);
- }
- }
-
- // unsafeWindow
- function unsafeWindow_polyfill() {
- typeof (unsafeWindow) === 'object' ? GM_POLYFILLED.unsafeWindow = false: window.unsafeWindow = window;
- }
-
- // GM_xmlhttpRequest
- // not supported properties of details: synchronous binary nocache revalidate context fetch
- // not supported properties of response(onload arguments[0]): finalUrl
- // ---!IMPORTANT!--- DOES NOT SUPPORT CROSS-ORIGIN REQUESTS!!!!! ---!IMPORTANT!---
- function GM_xmlhttpRequest_polyfill() {
- typeof (GM_xmlhttpRequest) === 'function' ? GM_POLYFILLED.GM_xmlhttpRequest = false: window.GM_xmlhttpRequest = PF_GM_xmlhttpRequest;
-
- // details.synchronous is not supported as Tempermonkey
- function PF_GM_xmlhttpRequest(details) {
- const xhr = new XMLHttpRequest();
-
- // open request
- const openArgs = [details.method, details.url, true];
- if (details.user && details.password) {
- openArgs.push(details.user);
- openArgs.push(details.password);
- }
- xhr.open.apply(xhr, openArgs);
-
- // set headers
- if (details.headers) {
- for (const key of Object.keys(details.headers)) {
- xhr.setRequestHeader(key, details.headers[key]);
- }
- }
- details.cookie ? xhr.setRequestHeader('cookie', details.cookie) : function () {};
- details.anonymous ? xhr.setRequestHeader('cookie', '') : function () {};
-
- // properties
- xhr.timeout = details.timeout;
- xhr.responseType = details.responseType;
- details.overrideMimeType ? xhr.overrideMimeType(details.overrideMimeType) : function () {};
-
- // events
- xhr.onabort = details.onabort;
- xhr.onerror = details.onerror;
- xhr.onloadstart = details.onloadstart;
- xhr.onprogress = details.onprogress;
- xhr.onreadystatechange = details.onreadystatechange;
- xhr.ontimeout = details.ontimeout;
- xhr.onload = function (e) {
- const response = {
- readyState: xhr.readyState,
- status: xhr.status,
- statusText: xhr.statusText,
- responseHeaders: xhr.getAllResponseHeaders(),
- response: xhr.response
- };
- (details.responseType === '' || details.responseType === 'text') ? (response.responseText = xhr.responseText) : function () {};
- (details.responseType === '' || details.responseType === 'document') ? (response.responseXML = xhr.responseXML) : function () {};
- details.onload(response);
- }
-
- // send request
- details.data ? xhr.send(details.data) : xhr.send();
-
- return {
- abort: xhr.abort
- };
- }
- }
-
- // NOTE: options(arg2) is NOT SUPPORTED! if provided, then will just be skipped.
- function GM_openInTab_polyfill() {
- typeof (GM_openInTab) === 'function' ? GM_POLYFILLED.GM_openInTab = false: window.GM_openInTab = PF_GM_openInTab;
-
- function PF_GM_openInTab(url) {
- window.open(url);
- }
- }
-
- // NOTE: needs to be called in an event handler function, and info(arg2) is NOT SUPPORTED!
- function GM_setClipboard_polyfill() {
- typeof (GM_setClipboard) === 'function' ? GM_POLYFILLED.GM_setClipboard = false: window.GM_setClipboard = PF_GM_setClipboard;
-
- function PF_GM_setClipboard(text) {
- // Create a new textarea for copying
- const newInput = document.createElement('textarea');
- document.body.appendChild(newInput);
- newInput.value = text;
- newInput.select();
- document.execCommand('copy');
- document.body.removeChild(newInput);
- }
- }
-
- return GM_POLYFILLED;
- }
-
- // Makes a function that returns a unique ID number each time
- function uniqueIDMaker() {
- let id = 0;
- return makeID;
- function makeID() {
- id++;
- return id;
- }
- }
-
- // Fill number text to certain length with '0'
- function fillNumber(number, length) {
- let str = String(number);
- for (let i = str.length; i < length; i++) {
- str = '0' + str;
- }
- return str;
- }
-
- // Del a item from an array using its index. Returns the array but can NOT modify the original array directly!!
- function delItem(arr, delIndex) {
- arr = arr.slice(0, delIndex).concat(arr.slice(delIndex+1));
- return arr;
- }
- })();