// ==UserScript==
// @name 💡WebPreview - 信息直达
// @namespace https://ez118.github.io/
// @version 0.15
// @description 支持国内主流搜索引擎的搜索结果快速预览(直达网页大纲)。只需点击搜索结果旁的小灯泡按钮,即可在右侧速览窗中快速查看目标网站所含的图片、链接、标题大纲、文本。
// @author ZZY_WISU
// @match https://*.bing.com/*
// @match https://www.google.com/*
// @match https://www.baidu.com/*
// @match https://www.so.com/*
// @match https://www.sogou.com/*
// @connect *
// @license GNU GPLv3
// @icon https://cn.bing.com/sa/simg/favicon-trans-bg-blue-mg.ico
// @run-at document-end
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js
// ==/UserScript==
/* ============================================= */
/**
* 生成随机数
* @param len 长度
* @constructor
*/
function randomString(len) {
len = len || 32;
/****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
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;
}
/**
* 标题元素
* @param tag 标签,h1 ==> 1 , h2 ==> 2
* @param title 标题
* @param level 层级
* @param id 随机生成的id
* @constructor
*/
function TitleElement(tag, title, level, id) {
this.tag = tag;
this.title = title;
this.level = level;
this.id = id;
}
/* 是否是标题元素 */
function isTitleTag(tag) {
if (tag.is("h1")) { return true }
if (tag.is("h2")) { return true }
if (tag.is("h3")) { return true }
if (tag.is("h4")) { return true }
if (tag.is("h5")) { return true }
if (tag.is("h6")) { return true }
if (tag.is("h7")) { return true }
return false
}
/**
* 生成大纲
* @param $articleContent 文章容器
* @constructor
*/
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;
}
}
})
console.log(titleElementArr);
return titleElementArr;
}
/* ============================================= */
var ReaderFlag1 = false; /* 用于存储阅读器元素是否被创建 */
var ReaderFlag2 = false; /* 用于存储阅读器是否为开启状态 */
var VideoSupport = [
["https://v.youku.com/v_show/*.html", "https://player.youku.com/embed/*"],
["https://v.qq.com/x/page/*.html", "http://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 stringToHtml(str) {
var parser = new DOMParser();
var doc = parser.parseFromString(str, 'text/html');
return doc;
}
function mobileDevice(){
var info = navigator.userAgent;
var isPhone = /mobile/i.test(info);
return isPhone;
}
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 escapeHtml(str) {
return str.replace(/[&<>"']/g, function(match) {
switch (match) {
case '&':
//return '&';
return '&';
case '<':
return '<';
case '>':
return '>';
case '"':
return '"';
case '\'':
return ''';
default:
return '';
}
});
}
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;
/* 获取所有链接 */
/*links = txt.match(/href\=\"(https?|http|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g);
for(let i = 0; i < links.length; i ++) {
links[i] = links[i].replace("href=\"", "");
}*/
links = [];
txt.replace(/<a [^>]*href=['"]([^'"]+)[^>]*>/g,function(match, capture){
links.push(capture);
});
//console.log(links);
/* 获取所有图片 */
//images = txt.match(/<img[^<>]*src=[\"]([^\"]+)[\"][^<>]*>/im);
images = [];
txt.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/g,function(match, capture){
images.push(capture);
});
//console.log(images);
/* 获取文本 */
content = txt.replace(/<\/div>/g, "</div>\n").replace(/<\/table>/g, "</table>\n")
content = content.replace(/<\/h3>/g, "</h3>\n").replace(/<\/p>/g, "</p>\n")
content = content.replace(/<\/li>/g, "</li>\n").replace(/<br>/g, "\n")
content = content.replace(/<br\/>/g, "\n").replace(/<script(.*?)<\/script>/gis, "")
content = content.replace(/<style(.*?)<\/style>/gis, "").replace(/<nav(.*?)<\/nav>/gis, "");
// 现在代码:
//content = content.replace(/<(?!\/?a\b)[^>]+>/g, ''); /* 删除除了a以外的标签 */
content = content.replace(/<(?!\/?(a|img)\b)[^>]+>/g, ''); /* 删除除了a和img以外的标签 */
// 原先代码:
//content = content.replace(/<[^>]+>/g, ""); /* 删除所有标签 */
//content = escapeHtml(content); /* 转义 */
var content_len = content.length;
content = content.substring(0, content_len);
const search_txt = [" ", " ", "\n\n", "\r\r", "\t\t", "\n\r\n\r", "\r\n\r\n", "\t\r\n"];
const replace_txt = ["", "", "", "", "", "", "", ""];
for (let i = 0; i < search_txt.length; i++) {
content = content.split(search_txt[i]).join(replace_txt[i]);
}
content = content.replace(/\r\n/g,"<br/>").replace(/\n/g,"<br/>").replace(/<br\/><br\/><br\/>/g, "");
//console.log(content);
outline = getOutline($(txt));
return {"link":links,"image":images,"content":content,"outline":outline};
}
function openReader(url) {
/* 打开阅读器 */
/* 判断阅读器元素是否已经被创建过 */
if (ReaderFlag1 != true) {
/* 若元素不存在,则创建元素 */
let previewReader = document.createElement("div");
previewReader.setAttribute("class", "userscript-webPreviewReader"); previewReader.setAttribute("style", "display:block;"); previewReader.setAttribute("id", "userscript-webPreviewReader");
document.body.appendChild(previewReader);
ReaderFlag1 = true; ReaderFlag2 = true;
} else {
/* 若存在,则显示元素 */
let previewReader = document.getElementById("userscript-webPreviewReader");
previewReader.setAttribute("style", "display:block;");
ReaderFlag2 = true;
}
/* 阅读器加载提示 */
var previewReader = document.getElementById("userscript-webPreviewReader");
previewReader.innerHTML = "<p style='font-size:22px;margin-top:33%;' align='center'>加载中...<br/><span>" + url + "</span></p>";
/* 判断当前链接是支持预览的视频网站,并作出对应处理 */
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.innerHTML = `
<div id="FadeInContainer" style="display:none; height:100%;">
<button class="CloseButton" style="margin:5px;" onclick='this.parentNode.parentNode.setAttribute("style", "display:none;");'>关闭</button>
<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">在原网站中继续 ▶ </a><br/>
<a href="` + frameUrl + `" class="link" id="GoToLink" target="_blank">在播放器中继续 🎦 </a>
</div> `;
/* 淡入 */
$("#FadeInContainer").fadeIn(700);
} else {
/* 普通网站的处理 */
runAsync(url, "GET", "").then((result)=>{ return result; }).then(function(result){
var rh;
if (url.split("/")[2] == "blog.csdn.net"){
try{ result = stringToHtml(result).getElementById("article_content").innerHTML; }catch(e){}
}else if (url.split("/")[2] == "zhuanlan.zhihu.com"){
try{ result = stringToHtml(result).getElementsByClass("Post-RichTextContainer")[0].innerHTML; }catch(e){}
}else if (url.split("/")[2] == "jingyan.baidu.com"){
try{ result = stringToHtml(result).getElementById("format-exp").innerHTML; }catch(e){}
}else if (url.split("/")[2] == "www.bilibili.com"){
try{ result = stringToHtml(result).getElementById("article-content").innerHTML; }catch(e){}
}
/* 用函数解析网页 */
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("https://") || link_tmp.includes("http://")*/link_tmp.includes("//")){
linkhtml += "<a class='link' target='_blank' href='" + link_tmp + "'> 🔗 " + 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 += "  "; }
outlinehtml += space + "+ " + reslist.outline[i].title + "<br/>"
}
/* 将所有结果添加进阅读器,并显示 */
previewReader.innerHTML = `
<div id="FadeInContainer" style="display:none;">
<button class="CloseButton" onclick='this.parentNode.parentNode.setAttribute("style", "display:none;");'>关闭</button>
<br>
<br>
<div class="ImageList" style="max-height:103px;">
<p class='ShowImgList' align='right' style='' onclick='this.parentNode.setAttribute("style", "");'>所有图片</p>
` + imghtml + `
</div>
<div class="LinkList" style="max-height:286px;">
<p class='ShowImgList' align='right' style='' onclick='this.parentNode.setAttribute("style", "");'>所有链接</p>
` + linkhtml + `
</div>
<div class="ImageList">
<b>大纲: </b><br/>
` + outlinehtml + `
</div>
<div class="ContentShow">
<b>文本: </b>
` + reslist.content + `
</div>
</div>`;
/* 淡入 */
$("#FadeInContainer").fadeIn(250);
});
}
/* 执行结束 */
}
(function() {
'use strict';
var url = window.location.href;
var paths = url.split("/");
GM_addStyle(`.userscript-webPreviewBtn{ background:#FFF; padding:3px 13px; margin-left:5px; border-radius:10px; border:2px solid #555; cursor:pointer; }
.userscript-webPreviewBtn:active{ background:#111; border:2px solid #AAA; }
.userscript-webPreviewReader{ position:fixed; top:8%; right:10px; bottom:0px; z-index:9999; width:35%; height:calc(100% - 8%); min-width:350px; background:#FFF; color:#333; overflow:hidden; box-shadow:0px 2px 5px #000; border-radius:15px 15px 0px 0px; }
.userscript-webPreviewReader img.error{ display:none; }
.ShowImgList{ margin:0;padding:0;width:100%;cursor:pointer;color:#138AF1;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:#001ba0; margin-left: 5px; }
.link:hover{ text-decoration:underline; }
.ImageList{ padding:10px; margin:5px; border:2px solid #f4effb; background:#f4effb; border-radius:15px; overflow:hidden; }
.LinkList{ padding:10px; margin:5px; border:2px solid #f4f1f6; background:#f4f1f6; border-radius:15px; overflow:hidden; }
.ContentShow{ padding:10px; margin:5px; border:2px solid #f2f2f2; background:#f2f2f2; border-radius:15px; }
.ContentShow img{ max-width:90%!important; position:relative!important; top:0!important; left:0!important; }
.ContentShow img::after{ content: ""; display: block; clear: both; }
.CloseButton{ position:fixed; top:calc(8% + 5px); right:15px; background:#FFF; color:#000; padding:3px 13px; margin:5px 5px; margin-bottom:0px; border-radius:10px; border:2px solid #555; cursor:pointer; }
.CloseButton:hover{ background:#138AF1; border:2px solid #106ebe; color:#FFF; }
#videoFrame{ width: calc(100% - 10px); height: calc(100% - 0px); border:1px solid #CCC; border-radius:15px; }
#FadeInContainer { overflow-y:auto; overflow-x:hidden; border-radius:15px 15px 0px 0px; width:100%; height:100%; }
`);
if (paths[2].includes("bing.com") && paths[3].includes("search")) {
let resultItems = document.getElementsByClassName("b_algo");
for(let i = 0; i < resultItems.length; i ++) {
let resultItemLink = resultItems[i].getElementsByTagName("a")[0].href;
let resultItemTitleEle = resultItems[i].getElementsByTagName("h2")[0];
/* 向每一个搜索结果的标题部分添加按钮 */
let previewBtn = document.createElement("button");
let previewBtnTxt = document.createTextNode("💡");
previewBtn.setAttribute("class", "userscript-webPreviewBtn");
previewBtn.setAttribute("link-data", resultItemLink);
previewBtn.appendChild(previewBtnTxt);
/* 适配必应手机网页 */
if(mobileDevice()){ resultItemTitleEle.parentNode.parentNode.appendChild(previewBtn); }
else { resultItemTitleEle.appendChild(previewBtn); }
previewBtn.addEventListener("click", function(evt){
let linkData = previewBtn.getAttribute("link-data");
openReader(linkData);
}, true);
}
} else if (paths[2].includes("google.com")) {
let resultItems = document.getElementsByClassName("MjjYud");
for(let i = 0; i < resultItems.length; i ++) {
try{
let resultItemLink = resultItems[i].getElementsByTagName("a")[1].getAttribute("href");
let resultItemTitleEle = resultItems[i].getElementsByTagName("h3")[0];
if(resultItemLink == null || resultItemLink == "" || resultItemLink == undefined){ continue; }
/* 向每一个搜索结果的标题部分添加按钮 */
let previewBtn = document.createElement("button");
let previewBtnTxt = document.createTextNode("💡");
previewBtn.setAttribute("class", "userscript-webPreviewBtn");
previewBtn.setAttribute("link-data", resultItemLink);
resultItemTitleEle.parentNode.parentNode.appendChild(previewBtn);
previewBtn.appendChild(previewBtnTxt);
previewBtn.addEventListener("click", function(evt){
let linkData = previewBtn.getAttribute("link-data");
openReader(linkData);
}, true);
} catch(e) {
console.log("[ERROR] ELE(" + i + ")");
}
}
} else if (paths[2].includes("baidu.com") && paths[3].includes("s?")) {
let resultItems = document.getElementsByClassName("c-container");
//let resultItems = document.getElementsByClassName("c-container new-pmd");
for(let i = 0; i < resultItems.length; i ++) {
try{
let resultItemLink = resultItems[i].getAttribute("mu");
let resultItemTitleEle = resultItems[i].getElementsByTagName("h3")[0];
if(resultItemLink == null || resultItemLink == "" || resultItemLink == undefined){ continue; }
/* 向每一个搜索结果的标题部分添加按钮 */
let previewBtn = document.createElement("button");
let previewBtnTxt = document.createTextNode("💡");
previewBtn.setAttribute("class", "userscript-webPreviewBtn");
previewBtn.setAttribute("link-data", resultItemLink);
resultItemTitleEle.appendChild(previewBtn);
previewBtn.appendChild(previewBtnTxt);
previewBtn.addEventListener("click", function(evt){
let linkData = previewBtn.getAttribute("link-data");
openReader(linkData);
}, true);
} catch(e) {
console.log("[ERROR] ELE(" + i + ")");
}
}
} else if (paths[2].includes("so.com") && paths[3].includes("s?")) {
let resultItems = document.getElementsByClassName("res-list");
for(let i = 0; i < resultItems.length; i ++) {
try{
let resultItemLink = resultItems[i].getElementsByTagName("a")[0].getAttribute("data-mdurl");
let resultItemTitleEle = resultItems[i].getElementsByTagName("h3")[0];
/* 向每一个搜索结果的标题部分添加按钮 */
let previewBtn = document.createElement("button");
let previewBtnTxt = document.createTextNode("💡");
previewBtn.setAttribute("class", "userscript-webPreviewBtn");
previewBtn.setAttribute("link-data", resultItemLink);
resultItemTitleEle.appendChild(previewBtn);
previewBtn.appendChild(previewBtnTxt);
previewBtn.addEventListener("click", function(evt){
let linkData = previewBtn.getAttribute("link-data");
openReader(linkData);
}, true);
} catch(e) {
console.log("[ERROR] ELE(" + i + ") \n" + e);
}
}
} else if (paths[2].includes("sogou.com")) {
let resultItems = document.getElementsByClassName("vrwrap");
for(let i = 0; i < resultItems.length; i ++) {
try{
let resultItemLink = resultItems[i].getElementsByClassName("ext_query")[0].getAttribute("data-url");
let resultItemTitleEle = resultItems[i].getElementsByTagName("h3")[0];
/* 向每一个搜索结果的标题部分添加按钮 */
let previewBtn = document.createElement("button");
let previewBtnTxt = document.createTextNode("💡");
previewBtn.setAttribute("class", "userscript-webPreviewBtn");
previewBtn.setAttribute("link-data", resultItemLink);
resultItemTitleEle.appendChild(previewBtn);
previewBtn.appendChild(previewBtnTxt);
previewBtn.addEventListener("click", function(evt){
let linkData = previewBtn.getAttribute("link-data");
openReader(linkData);
}, true);
} catch(e) {
console.log("[ERROR] ELE(" + i + ")");
}
}
}
})();