Greasy Fork

Greasy Fork is available in English.

💡WebPreview - 信息直达

支持国内主流搜索引擎的搜索结果快速预览(直达网页大纲)。只需点击搜索结果旁的小灯泡按钮,即可在右侧速览窗中快速查看目标网站所含的图片、链接、标题大纲、文本。

当前为 2024-07-21 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         💡WebPreview - 信息直达
// @namespace    https://ez118.github.io/
// @version      1.4.2
// @description  支持国内主流搜索引擎的搜索结果快速预览(直达网页大纲)。只需点击搜索结果旁的小灯泡按钮,即可在右侧速览窗中快速查看目标网站所含的图片、链接、标题大纲、文本。
// @author       ZZY_WISU
// @match        *://*/*
// @connect      *
// @license      GNU GPLv3
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAMAAAApB0NrAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAADZQTFRFAAAAn8LgEG6+SZDMg7HaZaDSgrHZkLrdO4jJZqHTgbLZHnbCdKnWLX/FV5jPLX7FSY/LdKnVpsJ+6QAAABJ0Uk5TACb//8j/yoP//8n/8f/////yflwd4QAAAORJREFUeJy1k9EagyAIhfVYGqnV3v9lp2kLjdrFvnGT4Q8coJTqTMP0rov9nRnYebTn2Z3HCSSGzvAsvwjNACuQIH1lGiRBNU+IALwpAXq4hCUCh0VR3y69ykrZbqCFdTHJUNLd5JRGGeH4q4fAdNkDwgWxWLqY6eSJKBcZYTtmz5tuSa1pGkZkUBrJT621WL/U0vW6+nyDkLRm3/YOqXfXeKP8SRGb0M0u+AV14poCZUlaFNMK3YQ9fGwtjLjPwzaYPPynUomp9shQHv4XZl/Oz8yCGGwwj4yKRbL08zGz48t1rjcZWATm0KKJdwAAAABJRU5ErkJggg==
// @run-at document-end
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://update.greasyfork.icu/scripts/499192/1402326/jquery_360.js
// ==/UserScript==

var iconImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAUCAMAAACK2/weAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAHtQTFRFAAAAOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGoffJU8/QAAACl0Uk5TABWJvOy7hhRd8P/tT25TIfzzlXziyPju8c/NgHgY9OMRZvU2WkPQx8o0T8eSAAAAbUlEQVR4nI3OyQ6CQBBF0auASOOEgICoCKLw/19ImqG7TFx4V3UWLymA1dpxXW/jM7YN1Fi409or0wGOJ8vI56xEMYlkykUyI5cMKCSvlJI37g+r6gm1UfPSb7UL39PTC/nJz6SOv/qazuNeXwMWhAmHAXFPJgAAAABJRU5ErkJggg==";
/* 用于存储小灯泡按钮的图片数据 */

const contentEleSelList = {
    "blog.csdn.net": "#article_content",
    "zhuanlan.zhihu.com": ".Post-RichTextContainer",
    "jingyan.baidu.com": "#format-exp",
    "www.bilibili.com": "#article-content",
    "zhidao.baidu.com": "#qb-content",
    "www.cnblogs.com": "#topics",
    "www.sohu.com": "#mp-editor"
}; /* 用于储存指定网站的内容所在父元素(特定博客网站内容优化) */

const VideoSupport = [
	["https://v.youku.com/v_show/*.html", "https://player.youku.com/embed/*"],
	["https://v.qq.com/x/page/*.html", "https://v.qq.com/txp/iframe/player.html?vid=*"],
	["https://www.bilibili.com/video/BV*/", "https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=*"],
	["https://www.bilibili.com/video/av*/", "https://www.bilibili.com/blackboard/html5mobileplayer.html?aid=*"]
]; /* 用于存储阅读器支持直接播放视频的网站及其嵌入播放器代码 */


/* ================[ 文章大纲提取脚本 ]================== */

/* 生成随机字符串 */
function randomString(len) {
    len = len || 32;
    var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
    var maxPos = $chars.length;
    var pwd = '';
    for (let i = 0; i < len; i++) { pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); }
    return pwd;
}
/* 标题元素 */
function TitleElement(tag, title, level, id) {
    this.tag = tag;
    this.title = title;
    this.level = level;
    this.id = id;
}
/* 是否是标题元素 */
function isTitleTag(tag) {
    return tag.is("h1, h2, h3, h4, h5, h6, h7");
}
/* 生成大纲 */
function getOutline($articleContent) {
    /* 全部元素 */
    var $eles = $articleContent.find("*");
    /* 标题元素列表 */
    var titleElementArr = new Array();
    /* 上一个元素 */
    var preTitleElement = null;

    $.each($eles, function(index, item) {
        if (isTitleTag($(item))) {
            var id = randomString(20);
            var level = 1;
            var tag = parseInt($(item).get(0).tagName.replace('h', "").replace('H', ""));
            var title = $(item).text();

            if (null != preTitleElement) {
                var tagPre = preTitleElement.tag;
                var levelPre = preTitleElement.level;

                if (tagPre > tag) { level = levelPre - 1; }
                else if (tagPre < tag) { level = levelPre + 1; }
                else { level = levelPre; }
            }
            if (title.trim().length > 0) {
                $(item).attr("id", id);
                var titleElement = new TitleElement(tag, title, level, id);
                titleElementArr.push(titleElement);
                preTitleElement = titleElement;
            }
        }
    });
    return titleElementArr;
}
/* ============================================= */

function runAsync(url,send_type,data_ry) {
    var p = new Promise((resolve, reject)=> {
        GM_xmlhttpRequest({
            method: send_type, url: url, headers: {"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"}, data: data_ry,
            onload: function(response){resolve(response.responseText);}, onerror: function(response){reject("请求失败");}
        });
    });
    return p;
}

function JudgeVideoSupport(url) {
	var previewFlag = 0;
	/* 判断是否为支持预览视频的网站 */
	for(let i = 0; i < VideoSupport.length; i ++){
		if( url.includes( VideoSupport[i][0].split("*")[0] ) ){
			return { "state":true, "data":i };
			break;
		}
	}

	return { "state":false, "data":-1 };
}

function getWebContents(txt) {
    var links = [];
    var images = [];
    var content;
    var outline;

    /* 获取所有链接 */
    txt.replace(/<a [^>]*href=['"]([^'"]+)['"][^>]*>/g, function(match, capture){
        links.push(capture);
    });

    /* 获取所有图片 */
    txt.replace(/<img [^>]*src=['"]([^'"]+)['"][^>]*>/g, function(match, capture){
        images.push(capture);
    });

    /* 删除多余空格和换行 */
    content = txt.replace(/\s+/g, ' ').trim().replace(/(\r\n|\r|\n){2,}/g, '$1\n');

    /* 获取文本,去掉特定标签 */
    content = content.replace(/<\/div>/g, "</div>\n")
        .replace(/<\/table>/g, "</table>\n")
        .replace(/<\/h3>/g, "</h3>\n")
        .replace(/<\/p>/g, "</p>\n")
        .replace(/<\/li>/g, "</li>\n")
        .replace(/<script.*?>.*?<\/script>/gis, "")
        .replace(/<style.*?>.*?<\/style>/gis, "")
        .replace(/<nav.*?>.*?<\/nav>/gis, "");

    content = content.replace(/<(?!\/?(a|img|code|pre)\b)[^>]+>/g, ''); /* 删除除了a和img以外的标签 */

    /* 将换行符变为换行,删除多余的br */
    content = content.replace(/\n/g,"<br/>").replace(/(<br\/>\s*)+/g, '<br/>');

    /* 获取大纲信息 */
    try{
        outline = getOutline(txt);
    } catch(e){ console.log("[ERROR] 大纲处理问题") }

    var final_data = {"link": links, "image": images, "content": content, "outline": outline};

    return final_data;
}

function openReader(url) {
    /* 打开阅读器 */

    /* 阅读器加载提示 */
    var closeBtn = $("#userscript-closeBtn");
    var previewReader = $("#userscript-webPreviewReader");
    previewReader.html("<p style='font-size:22px;margin-top:33%;' align='center'>正在载入...<br/><span>" + url + "</span></p>");

    previewReader.fadeIn(100);
    closeBtn.fadeIn(100);

    /* 判断当前链接是支持预览的视频网站,并作出对应处理 */
    var SoN = JudgeVideoSupport(url);
    if(SoN.state == true){
        /* 被支持的视频网站的处理 */
        var origUrl = url;
        var frameUrl = "";

        url = url.replace(VideoSupport[SoN.data][0].split("*")[0], "");
		url = url + "?#";
		url = url.split("#")[0].split("?")[0];
		url = url.replace(VideoSupport[SoN.data][0].split("*")[1], "");

        frameUrl = VideoSupport[SoN.data][1].replace("*", url);

        previewReader.html(`
        <div id="FadeInContainer" style="display:none;">
            <div style="height:48px;"></div>
	    	<center style="height: calc(100% - 120px)">
	    		<iframe id="videoFrame" style="min-height:300px;" src="` + frameUrl + `"></iframe>
	    	</center>
	    	<br>

	    	<a href="` + origUrl + `" class="link" id="GoToLink" target="_blank">在原网站中继续 &nbsp; ▶ </a><br/>
            <a href="` + frameUrl + `" class="link" id="GoToLink" target="_blank">在播放器中继续 &nbsp; 🎦 </a>
        </div> `);

        /* 淡入 */
        $("#FadeInContainer").fadeIn(700);
    } else {
        /* 普通网站的处理 */
        runAsync(url, "GET", "").then((result)=>{ return result; }).then(function(result){

            /* 源数据处理(csdn存在利用img的onerror属性注入xss脚本的行为) */
            result = result.replace(/<img\s+[^>]*src\s*=\s*["']{2}[^>]*>/gi, ''); /* 删除src为空的标签 */
            result = result.replace(/<img([^>]*)onerror\s*=\s*(['"]?[^'">]*['"]?)([^>]*)>/gi, '<img$1$3>'); /* 删除所有img标签的onerror属性 */

            /* 对指定网站进行内容过滤,指定元素获取 */
            const domain = url.split("/")[2];
            if (contentEleSelList[domain]) {
                try {
                    const selector = contentEleSelList[domain];
                    result = $(result).find(selector).html();
                } catch (e) { console.log("[ERROR] 特定网站处理问题") }
            }

            /* 用函数解析网页 */
            let reslist = getWebContents(result);
            let linkhtml = "", imghtml = "", outlinehtml = "";

            /* 处理链接列表 */
            for(let i = 0; i < reslist.link.length; i ++){
                let link_tmp = reslist.link[i];
                if(link_tmp.includes("//")){
                    linkhtml += "<a class='link' target='_blank' href='" + link_tmp + "'> 🔗&nbsp;" + link_tmp + " </a><br>";
                }
            }

            /* 处理图片列表 */
            for(let i = 0; i < reslist.image.length; i ++){
                imghtml += "<a href='" + reslist.image[i] + "' target='_blank'><img class='image' src='" + reslist.image[i] + "' onerror='this.remove()'/></a>";
            }

            /* 处理大纲 */
            for(let i = 0; i < reslist.outline.length; i ++){
                let space = "";
                for(let j = 1; j < reslist.outline[i].level; j ++){ space += "&emsp;&emsp;"; }
                outlinehtml += space + "+&nbsp;" + reslist.outline[i].title + "<br/>"
            }

            /* 将所有结果添加进阅读器,并显示 */
            previewReader.html(`
            <div id="FadeInContainer" style="display:none;">
                <div style="height:48px;"></div>
                <div class="ImageList" style="max-height:103px;">
                    <p class='ShowList' align='right' style='' onclick='this.parentNode.setAttribute("style", "");'>所有图片</p>
                    ` + imghtml + `
	        	</div>

		        <div class="LinkList" style="max-height:286px;">
                    <p class='ShowList' align='right' style='' onclick='this.parentNode.setAttribute("style", "");'>所有链接</p>
                    ` + linkhtml + `
	        	</div>

                <div class="OutlineShow">
	        		<b>大纲: </b><br/>
                    ` + outlinehtml + `
	        	</div>

	        	<div class="ContentShow">
	        		<b>文本: </b>
                    ` + reslist.content + `
	        	</div>
            </div>`);

            /* 如果没有链接/图片,那么就隐藏 */
            if(reslist.image.length == 0) { $(".ImageList").hide(); }
            if(reslist.link.length == 0) { $(".LinkList").hide(); }
            if(reslist.outline.length == 0) { $(".OutlineShow").hide(); }

            /* 淡入 */
            $("#FadeInContainer").fadeIn(250);
        });
    }
    /* 执行结束 */
}

/* 自动获取搜索结果(当前元素下) */
function checkSearchResults(parentElement) {
    var classList = [];
    var countList = [];
    for(let i = 0; i < parentElement.children.length; i ++) {
        var child = parentElement.children[i];
        var childClass = child.classList;
        for(let j = 0; j < childClass.length; j ++) {
            if(classList.indexOf(childClass[j]) !== -1) {
                /* 对列表中的class出现次数进行计数 */
                var p = classList.indexOf(childClass[j]);
                countList[p] += 1;
            } else {
                /* 对列表中未出现的class,插入列表 */
                classList.push(childClass[j]);
                countList.push(0);
            }
        }
    }
    var countMax = Math.max.apply(null, countList);
    return (countMax >= 5);
}

/* 遍历元素 */
function traverseElements(element, callback) {
    if (!element || !element.children || element.children.length === 0) {
        return;
    }

    var returnCode = callback(element);
    if (returnCode) { return; }
    /* 如果返回值为true,则代表该元素已包含搜索结果,无需继续遍历 */

    for (let i = 0; i < element.children.length; i++) {
        traverseElements(element.children[i], callback);
    }
}

(function() {
    'use strict';
    var url = window.location.href;
    var paths = url.split("/");


    if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
        GM_addStyle(`
            .userscript-webPreviewBtn{ user-select:none; background-color:#00390a55; color:#7edb7b; padding:6px 18px; font-weight:bold; line-height:16px; height:30px; margin-left:5px; border-radius:30px; border:1px solid #7edb7b; cursor:pointer; }
            .userscript-webPreviewBtn:hover{ background-color:#00390aAA; }
            .userscript-webPreviewBtn:active{ background-color:#7edb7b; }
            .userscript-webPreviewBtn img{ height:16px; }
            .userscript-closeBtn{ position:fixed; top:calc(8% + 5px); right:26px; z-index:100000; background:#7edb7b; color:#00390a; padding:8px 20px; margin:6px; border-radius:30px; font-weight:bold; border:0; border-bottom:1px solid #00390a; cursor:pointer; }
            .userscript-closeBtn:hover{ background:#76cd74; }

            .userscript-webPreviewReader{ font-size:medium; text-align:left; position:fixed; top:8vh; right:10px; bottom:0px; z-index:99999; width:35%; height:calc(100vh - 8%); min-width:340px; background:#1a1c19; border:1px solid #424940; color:#e2e3dd; overflow:hidden; box-shadow: 0 0 0 1px rgba(0,0,0,.1), 0 2px 4px 1px rgba(0,0,0,.18); border-radius:28px 28px 0px 0px; }
            .userscript-webPreviewReader img.error{ display:none; }
            .ShowList{ margin:0;padding:0;width:100%;cursor:pointer;color:#7edb7b; user-select:none; }
            .image{ height:85px; margin-bottom:8px; margin-right:5px; border-radius:15px; object-fit:contain; max-width:calc(100% - 20px); }
            .link{ text-decoration:none; color:#7edb7b!important; margin-left: 5px; }
            .link:hover{ text-decoration:underline; }
            .ImageList, .LinkList, .OutlineShow, .ContentShow{ padding:16px; margin:8px; background:#42494047; border-radius:30px; overflow:hidden; color:#d5e8cf; box-shadow:0 .5px 1.5px 0 rgba(0,0,0,.19),0 0 1px 0 rgba(0,0,0,.039); }
            .ContentShow img{ max-width:90%!important; position:relative!important; top:0!important; left:0!important; border-radius:10px; }
            .ContentShow img::after{ content: ""; display: block; clear: both; }

            .ContentShow a{ color:#7edb7b; text-decoration:underline 1px solid #386a1f; margin:0px 3px; }
            .ContentShow pre { color:#dcdcdc; background:#1e201d; width:90%; padding:5px; margin:5px 0px; overflow-y:auto; height:fit-content; border:1px solid #424940; border-radius:5px;}

            #videoFrame{ width: calc(100% - 10px); height: calc(100% - 0px); border:1px solid #CCC; border-radius:30px; margin:5px; }
            #FadeInContainer { overflow-y:scroll; overflow-x:hidden; border-radius:15px 15px 0px 0px; width:100%; height:100%; }
        `);
    } else {
        GM_addStyle(`
            .userscript-webPreviewBtn{ user-select:none; background-color:#FFFFFFAA; color:#386a1f; padding:6px 18px; font-weight:bold; line-height:16px; height:30px; margin-left:5px; border-radius:30px; border:1px solid #285a0f; cursor:pointer; }
            .userscript-webPreviewBtn:hover{ background-color:#edf1e5; }
            .userscript-webPreviewBtn:active{ background-color:#d7e1cd; }
            .userscript-webPreviewBtn img{ height:16px; }
            .userscript-closeBtn{ position:fixed; top:calc(8% + 5px); right:26px; z-index:100000; background:#386a1f; color:#FFF; padding:8px 20px; margin:6px; border-radius:30px; font-weight:bold; border:0; border-bottom:1px solid #20460e; cursor:pointer; }
            .userscript-closeBtn:hover{ background:#487631; }

            .userscript-webPreviewReader{ font-size:medium; text-align:left; position:fixed; top:8vh; right:10px; bottom:0px; z-index:99999; width:35%; height:calc(100vh - 8%); min-width:340px; background:#fdfdf6; color:#131f0d; overflow:hidden; box-shadow: 0 0 0 1px rgba(0,0,0,.1), 0 2px 4px 1px rgba(0,0,0,.18); border-radius:28px 28px 0px 0px; }
            .userscript-webPreviewReader img.error{ display:none; }
            .ShowList{ margin:0;padding:0;width:100%;cursor:pointer;color:#386a1f; user-select:none; }
            .image{ height:85px; margin-bottom:8px; margin-right:5px; border-radius:15px; object-fit:contain; max-width:calc(100% - 20px); }
            .link{ text-decoration:none; color:#386a1f!important; margin-left: 5px; }
            .link:hover{ text-decoration:underline; }
            .ImageList, .LinkList, .OutlineShow, .ContentShow{ padding:16px; margin:8px; background:rgb(216,231,203); border-radius:30px; overflow:hidden; color:#131f0d; box-shadow:0 .5px 1.5px 0 rgba(0,0,0,.19),0 0 1px 0 rgba(0,0,0,.039); }
            .ContentShow img{ max-width:90%!important; position:relative!important; top:0!important; left:0!important; border-radius:10px; }
            .ContentShow img::after{ content: ""; display: block; clear: both; }

            .ContentShow a{ color:#386a1f; text-decoration:underline 1px solid #386a1f; margin:0px 3px; }
            .ContentShow pre { color:#1a1c19; background:#eeeee8; width:90%; padding:5px; margin:5px 0px; overflow-y:auto; height:fit-content; border:1px solid #424940; border-radius:5px;}

            #videoFrame{ width: calc(100% - 10px); height: calc(100% - 0px); border:1px solid #CCC; border-radius:30px; margin:5px; }
            #FadeInContainer { overflow-y:scroll; overflow-x:hidden; border-radius:15px 15px 0px 0px; width:100%; height:100%; }
        `);
    }


    /* 插入DOM */
    /* 阅读器 */
    var $previewReader = $('<div>', {
        class: 'userscript-webPreviewReader',
        style: 'display:none;',
        id: 'userscript-webPreviewReader'
    }).appendTo('body');

    // 创建 closeBtn 元素
    var $closeBtn = $('<button>', {
        text: '关闭',
        class: 'userscript-closeBtn',
        id: 'userscript-closeBtn',
        style: 'display:none;'
    }).appendTo('body');

    // 添加关闭按钮的点击事件
    $closeBtn.on('click', function() {
        $previewReader.fadeOut(200);
        $closeBtn.hide();
    });


    /* 遍历DOM,获取搜索列表,插入按钮 */
    traverseElements(document.body, function(element) {
        var status = checkSearchResults(element);
        if(status) {
            console.log("存在搜索结果:", status);
            let resultItems = element.children;
            for(let i = 0; i < resultItems.length; i ++) {
                try {
                    let resultItemLink = resultItems[i].getElementsByTagName("a")[0].href;
                    let resultItemTitleEle = resultItems[i].getElementsByTagName("a")[0].parentNode;
                    let resultItemText = resultItems[i].getElementsByTagName("a")[0].innerText;

                    if(resultItemText.length <= 5 ){ continue; }
                    if(resultItemLink.includes("javascript:") && resultItemLink[0] == "j") { continue; }

                    /* 向每一个搜索结果的标题部分添加按钮 */
                    let previewBtn = document.createElement("button");
                    let previewBtnImg = document.createElement("img");
                    previewBtn.setAttribute("class", "userscript-webPreviewBtn");
                    previewBtn.setAttribute("link-data", resultItemLink);
                    previewBtnImg.setAttribute("src", iconImg);
                    resultItemTitleEle.appendChild(previewBtn);
                    previewBtn.appendChild(previewBtnImg);

                    previewBtn.addEventListener("click", function(evt){
                        let linkData = previewBtn.getAttribute("link-data");
                        openReader(linkData);
                    }, true);
                } catch(e) {
                    console.log("[ERROR] ELE(" + i + ") \n" + e);
                }
            }

            return true;
        } else {
            return false;
        }
    });

})();