// ==UserScript== // @name 聚合网页(美女图聚合展示演化而来)by SeLang // @namespace http://cmsv1.findmd5.com/ // @version 0.08 // @description 目标是聚合网页,省去翻页烦恼。有需要聚合的网址请反馈。 QQ群号:455809302,点击链接加入群【油猴脚本私人定制】:https://jq.qq.com/?_wv=1027&k=45p9bea // @author selang // @include /https?\:\/\/*/ // @require https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/ipfs/0.50.2/index.min.js // @connect * // @grant GM_download // @grant GM_openInTab // @grant GM_getTab // @grant GM_getTabs // @grant GM_saveTab // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant unsafeWindow // @downloadURL none // ==/UserScript== (async function () { if (window.top === window.self) { const NODE = await Ipfs.create(({repo: 'ipfs-monkey-aggregation'})); const RULE_CID_KEY = '聚合网页RULE_CID_KEY'; const RULE_OFFICIAL_CID_KEY = '聚合网页OFFICIAL_RULE_CID_KEY'; const ENV_DEV_STATUS = 'DEV'; const ENV_PRODUCT_STATUS = 'PRODUCT'; const ENV_STATUS = ENV_PRODUCT_STATUS; //日志 function log() { if (true) { console.log.apply(this, arguments); } }; function err() { if (true) { console.error.apply(this, arguments); } } function priorityLog() { console.log.apply(this, arguments); } function getRuleCids() { let officialCIDValue = GM_getValue(RULE_OFFICIAL_CID_KEY); let value = GM_getValue(RULE_CID_KEY); if (value) { let parse = JSON.parse(value); if (officialCIDValue) { return [officialCIDValue, ...parse]; } else { return parse; } } if (officialCIDValue) { return [officialCIDValue]; } else { return []; } } function setRuleCids(key, value) { return GM_setValue(key, value); } const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor; Array.prototype.distinct = function () { let arr = this; let result = []; let obj = {}; for (let i of arr) { if (!obj[i]) { result.push(i); obj[i] = 1; } } return result; } const Alpha_Script = { sleep: function (time = 100) { return new Promise(resolve => { setTimeout(function () { resolve(); }, time); }) }, obtainHtmlAsync: function (options) { options = options || {}; if (!options.url) { throw new Error("参数不合法"); } return new Promise(resolve => { options.headers = options.headers || Alpha_Script.parseHeaders("Accept:image/webp,image/*,*/*;q=0.8\n" + "Accept-Encoding:gzip, deflate, sdch\n" + "Accept-Language:zh-CN,zh;q=0.8\n" + "Referer:" + window.location.href + "\n" + `User-Agent:${window.navigator.userAgent}` ); options.method = options.method || 'GET'; let responseType = options.responseType; switch (responseType) { case "blob": options.onload = options.onload || function (response) { if (response && response.status && response.status >= 200 && response.status < 300) { resolve(response); } } break; default: options.onload = options.onload || function (response) { if (response && response.status && response.status >= 200 && response.status < 300) { let html = response.responseText; resolve(html); } } } GM_xmlhttpRequest(options); }); }, asyncPool: function (poolLimit, array, iteratorFn) { let i = 0; const ret = []; const executing = []; const enqueue = function () { //Boundary processing, array is an empty array if (i === array.length) { return Promise.resolve(); } //Initialize a promise every enqueue const item = array[i]; const p = Promise.resolve(i).then((i) => iteratorFn(item, i, array)); i++; //Put into promises array ret.push(p); //After the promise is executed, remove it from the executing array const e = p.then(() => executing.splice(executing.indexOf(e), 1)); //Insert the executing number to indicate the executing promise executing.push(e); //Using promise.rece, whenever the number of promises in the executing array is less than poollimit, the new promise is instantiated and executed let r = Promise.resolve(); if (executing.length >= poolLimit) { r = Promise.race(executing); } //Recursion until array is traversed return r.then(() => enqueue()); }; return enqueue().then(() => Promise.allSettled(ret)); }, obtainHtml: function (options) { options = options || {}; if (!options.url || !options.method) { throw new Error("参数不合法"); } GM_xmlhttpRequest(options); }, parseHeaders: function (headStr) { let o = {}; let myregexp = /^([^:]+):(.*)$/img; let match = /^([^:]+):(.*)$/img.exec(headStr); while (match != null) { o[match[1].trim()] = match[2].trim(); match = myregexp.exec(headStr); } return o; }, //获取参数 getParam: function (dest, name) { let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); let r = dest.match(reg); if (r != null) return decodeURI(r[2]); return null; }, isArray: function (value) { return Object.prototype.toString.apply(value) === '[object Array]'; } }; (function () { 'use strict'; priorityLog('欢迎进群:455809302交流。一起玩。'); priorityLog('一起玩不论是不是技术人员都欢迎。只要有创意也欢迎加入。点击链接加入群【油猴脚本私人级别定制】:https://jq.qq.com/?_wv=1027&k=460soLy。'); function validateUrl(url) { let validate = true; let lowerCaseUrl = url.toLowerCase(); if (url.startsWith('#')) { validate = false; } else if (lowerCaseUrl.startsWith('//')) { url = `${window.location.protocol}${url}`; } else if (lowerCaseUrl.startsWith("/")) { url = `${window.location.protocol}//${window.location.hostname}${url}`; } else if (lowerCaseUrl.startsWith('https://') || lowerCaseUrl.startsWith('http://')) { } else { let prefixRegex = /(.*?\/)[^\/]*$/i; let __matched = prefixRegex.exec(window.location.href); if (__matched != null) { url = `${__matched[1]}${url}`; } else { url = `${window.location.protocol}//${window.location.hostname}/${url}`; } } return {validate, url}; } async function parseNextPages(nextSelector = 'a:contains("下一页")') { let nextPages = []; let existNextPage = false; nextPages.push({ url: window.location.href, html: $('html').prop("outerHTML") }); let nextEs = $(nextSelector); log('解析下一页开始...'); while (nextEs.length > 0) { existNextPage = true; let nextPageUrl = nextEs.attr('href'); let validateUrlResult = validateUrl(nextPageUrl); if (!validateUrlResult.validate) { break; } nextPageUrl = validateUrlResult.url; log(nextPageUrl); let html = await Alpha_Script.obtainHtmlAsync({url: nextPageUrl}); // console.log('nextPage Html==>', html); let nextPageItem = { url: nextPageUrl, html }; let duplicateNextPage = nextPages.filter(item => item.url == nextPageItem.url); if (duplicateNextPage.length > 0) { break } else { nextPages.push(nextPageItem); let {$doc} = txt2Document(html); nextEs = $doc.find(nextSelector); } // await Alpha_Script.sleep(1000); } log('解析下一页结束...'); return {existNextPage, nextPages}; } /** * 插件图片聚合例子 * @param parseNextPages 解析下一页的公用方法 * @param $ jquery * @param log 日志输出 * @param Alpha_Script 公用对象 * @returns {Promise<*[]>} 图片合集 */ async function example(parseNextPages, $, log, Alpha_Script) { //下一页css选择器 let nextPageSelector = 'a:contains("下一页")'; //要聚合的图片css选择器 let imgSelector = 'img'; let {existNextPage, nextPages} = await parseNextPages(nextPageSelector); if (existNextPage) { nextPages.map(nextPage => { let images = Array.from($($(nextPage.html)).find(imgSelector)).map(e => { let src = e.src; let dataOriginal = $(e).attr('data-original'); if (dataOriginal) { src = dataOriginal; } return src; }); nextPage.imgs = images; }); let imgs = nextPages.flatMap(page => page.imgs); return imgs; } else { return []; } } /** * 校验cid是否符合rule * @param cid * @returns {Promise<{validate: boolean}|{date: *, parseRule: *, url: *, validate: boolean, desc: *}>} */ async function parseRuleFromIPFS(cid) { const {date, desc, parseRule, url, pre, excludeWebsites} = (await NODE.dag.get(cid)).value; if (parseRule && url && desc && date) { return {validate: true, date, desc, parseRule, url, pre, excludeWebsites}; } else { return {validate: false}; } } /** * 获取cid下所有的规则 * @param cid * @returns {Promise<[]>} */ async function obtainRulesFromIPFS(cid) { let rules = []; let cids = []; while (true) { try { let rule = await parseRuleFromIPFS(cid); if (rule.validate) { rules.push(rule); if (rule.pre) { cid = rule.pre; if (cids.includes(cid)) { err('闭环了'); break; } else { cids.push(cid); } } else { break; } } else { break; } } catch (e) { err(e); break; } } return rules; } async function pluginExample() { //这里是一个插件的例子 let cid = await NODE.dag.put( { url: '通用', desc: '通用聚合', parseRule: `return await (${example.toString()})(parseNextPages,$,log,Alpha_Script)`, excludeWebsites: ['https://www.google.com.hk/search', 'https://greasyfork.org/', 'https://xxxxx需要排除的网站'], date: '2020年9月30日' } ); cid = await NODE.dag.put( { url: '这里可以写书写规则的网址', desc: '第二个通用聚合', parseRule: `return await (${example.toString()})(parseNextPages,$,log,Alpha_Script)`, excludeWebsites: ['https://www.google.com.hk/search', 'https://greasyfork.org/', 'https://xxxxx需要排除的网站'], date: '2020年9月30日', pre: cid.toString() } ); let cidStr = cid.toLocaleString(); // cidStr = cid.toLocaleString(); console.log('你的插件分享的地址为:', cidStr); return cidStr; } let isPageResLoadComplete = false; async function waitPageResLoadComplete() { for (; ;) { if (isPageResLoadComplete) { log('加载成功'); return; } else { await Alpha_Script.sleep(500); } } } //热键 function aggregationDisplaySwitchHotkeys() { $(document).keydown(function (e) { if (e.ctrlKey && e.shiftKey) { if (e.which == 76) {//L log("触发快捷键"); aggregationDisplaySwitch(); } } }); } aggregationDisplaySwitchHotkeys(); function aggregationDisplaySwitch() { let aggregationIFRAMEDisplayCss = $('#aggregationIFRAME').css('display'); let aggregationIFRAMEDisplayCssCache = $('#aggregationIFRAME').attr('display-css-cache'); if (aggregationIFRAMEDisplayCss != 'none') { $('#aggregationIFRAME').css('display', 'none'); $('body').children().each(function (index, element) { let displayCss = $(element).attr('display-css-cache'); if (element.id != 'aggregationIFRAME') { $(element).css('display', displayCss); } }); } else { $('#aggregationIFRAME').css('display', aggregationIFRAMEDisplayCssCache); $('body').children().each(function (index, element) { if (element.id != 'aggregationIFRAME') { $(element).css('display', 'none'); } }); } } window.addEventListener("message", async (event) => { let data = event.data; if (data.from == 'page') { if (data.method == 'obtainHtmlAsync') { let message = {result: '', from: 'monkey'}; try { let html = await Alpha_Script.obtainHtmlAsync({url: data.url}); message.result = html; } catch (e) { message.error = e; } finally { event.ports[0].postMessage(message); } } else if (data.method == 'pageResLoadComplete') { isPageResLoadComplete = true; let message = {result: '', from: 'monkey'}; try { let html = event.data; log('html', html); message.result = 'page收到::' + html; } catch (e) { message.error = e; } finally { event.ports[0].postMessage(message); } } else if (data.method == '聚合切换') { aggregationDisplaySwitch(); let message = {result: '', from: 'monkey'}; try { let html = event.data; log('html', html); message.result = 'page收到::' + html; } catch (e) { message.error = e; } finally { event.ports[0].postMessage(message); } } } }, false); function postMsg(msg) { return new Promise((res, rej) => { const {port1, port2} = new MessageChannel(); port1.onmessage = ({data}) => { if (data.from == 'page') { port1.close(); if (data.error) { rej(data); } else { res(data); } } }; msg.from = 'monkey'; window.top.document.getElementById("aggregationIFRAME").contentWindow.postMessage(msg, window.location.origin, [port2]); }); } function MIMEObjectURL(txt, contentType = "text/html; charset=UTF-8") { let txtBlob = new Blob([txt], {type: contentType}); let txtBlobUrl = URL.createObjectURL(txtBlob); return txtBlobUrl; } function txt2Document(txt) { let parser = new DOMParser(); let doc = parser.parseFromString(txt, "text/html"); let $doc = $(doc); return {doc, $doc} } (async () => { // let cidStr = await pluginExample(); await pluginExample(); // 你自己写的或者他人分享的cidPath; let cidStr = "bafyreielhccxya5tbcqoqdfxfqcn7qukjed2jejnbeaffikxm7ssdano2i"; setRuleCids(RULE_OFFICIAL_CID_KEY, cidStr); let ruleCids = getRuleCids(); ruleCids.push(cidStr); let rules = []; for (const ruleCid of ruleCids) { let rule = await obtainRulesFromIPFS(ruleCid); rules = [...rules, ...rule]; } log('当前规则总数:', rules.length); for (let rule of rules) { log('当前执行规则>> %s 编写规则参考地址:%s\r\n 规则内容:%s', rule.desc, rule.url, rule.parseRule); let excludeWebsites = rule.excludeWebsites; if (Alpha_Script.isArray(excludeWebsites)) { let findAnyOnes = excludeWebsites.filter(excludeWebsite => window.location.href.startsWith(excludeWebsite)); if (findAnyOnes.length > 0) { log('当前规则被排除'); continue; } } let ruleFunc = rule.parseRule; let imgs = await (new AsyncFunction('parseNextPages', '$', 'log', 'Alpha_Script', ruleFunc))(parseNextPages, $, log, Alpha_Script); imgs = imgs.distinct(); if (imgs.length > 0) { log('规则找到图片'); let tempHost; if (ENV_STATUS == ENV_DEV_STATUS) { tempHost = 'http://127.0.0.1:8081'; } else { tempHost = 'https://cmsv1.findmd5.com'; } let aggregationTemplateHtml, aggregationTemplateCss, aggregationTemplateJs; //国内IPFS不稳定,临时处理 if (true || ENV_STATUS == ENV_DEV_STATUS) { aggregationTemplateHtml = await Alpha_Script.obtainHtmlAsync({url: `${tempHost}/static/imageAggregation/aggregationTemplate.html`}); aggregationTemplateCss = await Alpha_Script.obtainHtmlAsync({url: `${tempHost}/static/imageAggregation/css/aggregationTemplate.css`}); aggregationTemplateJs = await Alpha_Script.obtainHtmlAsync({url: `${tempHost}/static/imageAggregation/js/aggregationTemplate.js`}); } else { let aggregationTemplateHtmlCid, aggregationTemplateJsCid, aggregationTemplateCssCid; aggregationTemplateHtmlCid = 'bafyreicncdsi25po7rij4oh355v7w7fjfu3da4ncpkxp2gwwoxucb5ulri'; aggregationTemplateJsCid = 'bafyreiblvxkpjinurhnxgwznsgmtap37vmd7l3cvbcqf3wul4ll2bb7fmy'; aggregationTemplateCssCid = 'bafyreihws6twoxkhqoc3klnm3lucsamss3bki4akrpnwexp6u5m5thzpva'; aggregationTemplateHtml = (await NODE.dag.get(aggregationTemplateHtmlCid)).value.data; aggregationTemplateCss = (await NODE.dag.get(aggregationTemplateCssCid)).value.data; aggregationTemplateJs = (await NODE.dag.get(aggregationTemplateJsCid)).value.data; } { let cid = await NODE.dag.put( { data: aggregationTemplateHtml, desc: 'aggregationTemplate.html', } ); log('aggregationTemplate.html cid: ', cid.toString()); cid = await NODE.dag.put( { data: aggregationTemplateJs, desc: 'aggregationTemplate.js', } ); log('aggregationTemplate.js cid: ', cid.toString()); cid = await NODE.dag.put( { data: aggregationTemplateCss, desc: 'aggregationTemplate.css', } ); log('aggregationTemplate.css cid: ', cid.toString()); } if (ENV_STATUS == ENV_DEV_STATUS) { console.log('aggregationTemplateHtml', aggregationTemplateHtml); console.log('aggregationTemplateCss', aggregationTemplateCss); console.log('aggregationTemplateJs', aggregationTemplateJs); } let aggregationTemplateCssBlobUrl = MIMEObjectURL(aggregationTemplateCss, 'text/css; charset=utf-8'); let aggregationTemplateJsBlobUrl = MIMEObjectURL(aggregationTemplateJs, 'application/javascript; charset=utf-8'); { let {doc, $doc} = txt2Document(aggregationTemplateHtml); $doc.find('head').append(``); $doc.find('body').append(``); let outerHTML = doc.querySelector('html').outerHTML; aggregationTemplateHtml = outerHTML; } if (ENV_STATUS == ENV_DEV_STATUS) { console.log('final aggregationTemplateHtml', aggregationTemplateHtml); } let aggregationTemplateHtmlBlobUrl = MIMEObjectURL(aggregationTemplateHtml); $('body').append(``); await waitPageResLoadComplete(); $('body').children().each(function (index, element) { let displayCss = $(element).css('display'); $(element).attr('display-css-cache', displayCss); if (element.id != 'aggregationIFRAME') { $(element).css('display', 'none'); } }); let promiseAllResult = await Alpha_Script.asyncPool(10, imgs, async function (src, i) { let blob = await downloadImg2Blob(src); let url = URL.createObjectURL(blob); // $(`#c_${i}`).append(``); return {url, blob}; }); let imgBlobSrcs = promiseAllResult.filter(p => p.status == 'fulfilled').map(p => p.value); await postMsg({method: 'images', result: imgBlobSrcs}); log('规则执行完毕'); break; // await Alpha_Script.sleep(5000); } } log('执行完毕'); })(); async function downloadImg2Blob(imgSrc) { let response = await Alpha_Script.obtainHtmlAsync({ url: imgSrc, method: 'GET', headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", // "Accept-Encoding": "gzip, deflate, sdch", // "Accept-Language": "zh-CN,zh;q=0.8", "Referer": window.location.href, "User-Agent": window.navigator.userAgent }, responseType: 'blob' }); let responseHeaders = Alpha_Script.parseHeaders(response.responseHeaders); let contentType = responseHeaders['Content-Type']; if (!contentType) { contentType = "image/png"; } let blob = new Blob([response.response], {type: contentType}); return blob; } async function loadHidden(e) { let {height, width} = await imageWidth(e); if (!(height > 500 && width > 500)) { e.style.display = 'none'; } } function getImageWidth(url) { let img = new Image(); img.src = url; return imageWidth(img); } function imageWidth(img) { return new Promise(resolve => { // 如果图片被缓存,则直接返回缓存数据 if (img.complete) { resolve(img); } else { // 完全加载完毕的事件 img.onload = function () { resolve(img); } } }); } GM_registerMenuCommand("规则列表", ruleListFunc, "R"); function ruleListFunc() { let ruleCids = getRuleCids(); let delBtn = ''; let inject = ruleCids.map(ruleCid => `
${ruleCid}${delBtn}
`).join("
"); $('script').remove(); $('head').append(''); $('body').html(inject); $('body').append(''); } })(); } }) ();