Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch
// ==UserScript== // @name ViSearch/Google of Google // @name:zh-CN ViSearch/Google of Google // @name:zh-TW ViSearch/Google of Google // @name:fr ViSearch/Google of Google // @name:es ViSearch/Google of Google // @name:th ViSearch/Google of Google // @namespace https://github.com/new4u // @version 7.117.2 // @description Now on Chrome extensions!!! **https://chrome.google.com/webstore/search/visearch** ! Beyond the ChatGPT/AI with eyes.ViSearch as the Free/Lightweight alternatives to ChatGPT,has the potential to be even more intuitive than ChatGPT in the future. // @description:zh-cn Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch // @description:zh-tw Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch // @description:fr Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch // @description:es Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch // @description:th Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch // @author new4u本爷有空 // @connect google.com // @connect google.com.hk // @connect google.com.jp // @include *://encrypted.google.*/search* // @include *://*.google*/search* // @include *://*.google*/webhp* // @match *www.google.com* // @icon https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/WikiProject_Sociology_Babel_%28Deus_WikiProjects%29.png/240px-WikiProject_Sociology_Babel_%28Deus_WikiProjects%29.png // @require https://unpkg.com/[email protected]/build/d3.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js // @require http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js // @require http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js // @resource http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css // @grant none // @copyright 2015-2023, new4u // @license GPL-3.0-only // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw // @note 2023-05-25-v.7.117 发布到了chrome应用商店,并且修复了一个关键词和chrome界面语种的冲突,支持多语种. // @note 2023-2-21-v1.117 发布迁移到userscript以来第一个正式版本 // @note 2016 Java -> 2019 Node.js -> 2023-2-09 各种各样的历史更新记录,从一个版本迭代到另一个版本 // @grant none // ==/UserScript== let styleSheet = ` body { padding: 30px 40px; font-family: OpenSans-Light, PingFang SC, Hiragino Sans GB, Microsoft Yahei, Microsoft Jhenghei, sans-serif; } .links line { stroke: rgb(255, 255, 255); stroke-opacity: 0.5; } .links line.inactive { stroke-opacity: 0; } .nodes circle { stroke: #fff; stroke-width: 1.5px; } .nodes circle:hover { cursor: pointer; } .nodes circle.inactive { display: none !important; } @media screen and (max-width: 600px) { .text { font-size: 8px; /* 当屏幕宽度小于600px时 最小字体为8px */ } } .texts text { font-size: 12px; /* 最小字体为12px */ min-font-size: 8px; /* 最小字体不能小于8px */ max-font-size: 36px; font-weight:bold; font-family:"Microsoft YaHei"; text-shadow: 0 0 3px #fff, 0 0 10px #fff; } .texts text:hover { cursor: pointer; } .texts text.inactive { display: none !important; } #mode { position: absolute; top: 160px; left: 60px; } #mode span { display: inline-block; border: 1px solid #fff; color: #fff; padding: 6px 10px; border-radius: 4px; font-size: 14px; transition: color, background-color .3s; -o-transition: color, background-color .3s; -ms-transition: color, background-color .3s; -moz-transition: color, background-color .3s; -webkit-transition: color, background-color .3s; } #info { position: absolute; bottom: 40px; right: 30px; text-align: right; width: 270px; } #info h4 { color: #fff; } #info p { color: #fff; font-size: 12px; margin-bottom: 5px; } #info p span { color: #888; margin-right: 10px; } #svg g.row:hover { stroke-width: 1px; stroke: #fff; } `; let s = document.createElement('style'); s.type = "text/css"; s.innerHTML = styleSheet; (document.head || document.documentElement).appendChild(s); const colors = ["#6ca46c", "#4e88af", "#c72eca", "#d2907c"]; //临时半径R大小控制,之后改成连接数量影响大小 const sizes = [30, 20, 20, 0.5]; const forceRate = -1000; // 选择 input 元素中 class 为 gLFyf 的最后一个元素 const input = document.querySelector("input.gLFyf:last-of-type"); let searchtext = input ? input.value : ""; if (searchtext === "") { console.log("searchtext is empty"); } else { console.log("searchtext:", searchtext); // 执行其他逻辑 } function parseWebPage(Searchtext) { //data //***开始解析网页 /* 谷歌获取 */ //成功在page之外获取到页面元素内容,发现百度的下一页,就是 let elements = Array.from(document.querySelectorAll(".g")); // console.log(elements); //读取数组里内容map为value let dataPage = elements.map((element) => { // console.log(element); //搜索到文章的标题 let title = element.querySelector(".LC20lb"); title !== null ? (title = title.innerText) : (title = null); // console.log(title); //搜索到文章的url let url = element.querySelector("a"); url !== null ? (url = url.href) : (url = null); // console.log(url); //搜索到的文章的来源网站r.split(" - ")[1] let siteName = title; siteName !== null ? (siteName = siteName.split(" - ")[1]) : (siteName = null); // console.log(siteName); //搜索到的文章的发布日期 // let time = element.querySelector(".c-abstract>.newTimeFactor_before_abs"); //之前的query let time = element.querySelector(".MUxGbd.wuQ4Ob.WZ8Tjf > span"); time !== null ? (time = time.innerText) : (time = null); //google几天前时间可以计算一下 // console.log(time); //搜索到的文章的摘要 let abstract = element.querySelector( ".VwiC3b.yXK7lf.MUxGbd.yDYNvb.lyLwlc.lEBKkf span:nth-child(2)" ); abstract !== null ? (abstract = abstract.innerText) : (abstract = null); //搜索到关键词如果2023年05月25日更新到多语种 let keyWords = Array.from(element.querySelectorAll("em")); keyWords !== null ? (keyWords = keyWords.map((item) => { return item.innerText; })) : (keyWords = null); // console.log(keyWords); let elementEach = { title, url, siteName, time, abstract, keyWords, }; // console.log(elementEach); return elementEach; }); console.log(dataPage); let keyWords = []; for (let i in dataPage) { let temp = dataPage[i].keyWords; keyWords = keyWords.concat(temp); } let keyWordsSet = Array.from(new Set(keyWords)); let nodes = []; let links = []; let id = 0; let sanidstart = id; let sanNode = []; for (let j = 0; j < dataPage.length; j++) { sanNode.push({ category: 4, id: "san" + j, name: dataPage[j].title, value: dataPage[j].abstract, origin: dataPage[j].siteName, time: dataPage[j].time, year: dataPage[j].time !== null && dataPage[j].time.replace(/[^0-9\u4e00-\u9fa5]/gi, "") !== "" ? dataPage[j].time.replace(/[^0-9\u4e00-\u9fa5]/gi, "").substr(0, 4) : null, url: dataPage[j].url, //为了前面能找到,再加一条,也可以用这个循环,就少了一个!,这样资料全一些 keyWords: dataPage[j].keyWords, type: "san", }); } let keyidstart = id; let keyNode = []; for (let i = 0; i < keyWordsSet.length; i++) { if (keyWordsSet[i].length <= 4) { keyNode.push({ category: 3, id: "key" + i, name: keyWordsSet[i], value: 30000 + i + "", type: "key", }); // !!! link 如果 keyNode.name 在dataPage.keywords li indexof 就连接(先写san) // for (k) for (let j = 0; j < sanNode.length; j++) { //keywords是数组,不知道行不行,可以 if (sanNode[j].keyWords.indexOf(keyWordsSet[i]) !== -1) { links.push({ source: "key" + i, target: "san" + j, value: 1, }); } } } } let tagid = id; //2,tag,id=20000-29999,tag就是长度大于4的keywordsSet let tagNode = []; for (let i = 0; i < keyWordsSet.length; i++) { if (keyWordsSet[i].length > 4) { tagNode.push({ category: 2, id: "tag" + i, type: "tag", name: keyWordsSet[i], value: id, }); for (let j = 0; j < sanNode.length; j++) { //keywords是数组,不知道行不行,可以 if (sanNode[j].keyWords.indexOf(keyWordsSet[i]) !== -1) { links.push({ source: "tag" + i, target: "san" + j, value: 1, }); } } for (let j = 0; j < keyNode.length; j++) { //如果tagNode.name包含了keyNode.name(indexof),就连一条线 if (keyWordsSet[i].indexOf(keyNode[j].name) !== -1) { links.push({ source: "tag" + i, target: keyNode[j].id, value: 1, }); } } } } //1,id=10000,不能用10000+"",,只能用i+"".直接"10000"就好了 let newsNode = { category: 1, id: "news", //searchtext在前面定义前面获取 name: searchtext, value: id, type: "news", }; //和key和tag都建立连接 for (let i = 0; i < keyNode.length; i++) { links.push({ source: "news", target: keyNode[i].id, value: 1, }); } for (let i = 0; i < tagNode.length; i++) { links.push({ source: "news", target: tagNode[i].id, value: 1, }); } // 合并4个[],用concat(), nodes = nodes .concat(newsNode) .concat(tagNode) .concat(keyNode) .concat(sanNode); //data end return { nodes, links, }; } function renderD3Graph(nodes, links, graph) { // 获取 graph 元素的宽度和高度 const width = parseInt(graph.style.width); const height = parseInt(graph.style.height); d3.selectAll("graph > *").remove(); // // 创建 D3.js SVG 元素,并设置其大小和位置 const bbox = graph.getBoundingClientRect(); const svg = d3.select(graph).append("svg") .attr("id","graph-svg") .attr("width", bbox.width) .attr("height", bbox.height) .style("position", "fixed") .style("top", bbox.y + "px") .style("left", bbox.x + "px") .style("z-index", "999"); // const svg = d3 // .select(graph) // .append("svg") // .attr("width", width) // .attr("height", height) // .style("position", "fixed") // .style("top", "0") // .style("right", "0") // .style("z-index", "999"); function dragStarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragging(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragEnded(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } const simulation = d3 .forceSimulation(nodes) .force( "link", d3 .forceLink(links) .id((d) => d.id) .distance(50) .strength(1) .iterations(1) ) .force("charge", d3.forceManyBody().strength(forceRate)) .force("center", d3.forceCenter(width / 2, height / 2)) .alphaDecay(0.05); const link = svg .append("g") .attr("stroke", "#999") .attr("stroke-opacity", 0.6) .selectAll("line") .data(links) .enter() .append("line") .attr("stroke-width", (d) => Math.sqrt(d.value)); const node = svg .append("g") .selectAll("circle") .data(nodes) .enter() .append("circle") // 试着给每种type加一个class,要放在数据读取之后 .attr("class", function (d) { // return "nodes"; return "nodes " + d.type; }) .attr("r", function (d) { // make radius of node circle return sizes[d.category - 1]; // return r; }) .attr("fill", function (d) { // 配合现在category从1开始,今后可以重新设计一下category make color of node circle,也可以加到后面,统一修改 // console.log(d.category); return colors[d.category - 1]; }) .attr("stroke", "none") .attr("name", function (d) { // make text of node return d.name; }) .call( d3 .drag() .on("start", dragStarted) .on("drag", dragging) .on("end", dragEnded) ); //固定中心文章位置,用class来控制 //固定中心文章位置,fx可以设置哈哈,或者大面积的可以用tick,详见https://stackoverflow.com/questions/10392505/fix-node-position-in-d3-force-directed-layout,实验证明,还可以用type属性来控制fx,fy svg .select(".news") .attr("fx", function (d) { return (d.fx = width / 2); }) .attr("fy", function (d) { return (d.fy = height / 2); }); //san文章点击, 没有name这个 //存放三度文章size svg .selectAll(".san") .attr("r", function (d) { let uniqueWords = new Set(d.keyWords); let radius = uniqueWords.size * 10; // console.log("radius:", radius); return radius; }) .style("fill-opacity", 0.5); // 显示所有的文本... const text = svg .append("g") .attr("class", "texts") .selectAll("text") .data(nodes) .enter() .append("text") .attr("font-size", function (d) { // return d.size; let uniqueWords = new Set(d.keyWords); let radius = uniqueWords.size * 2; let fontSize = radius * sizes[d.category - 1]; // console.log("d:", d, ";font-size return", fontSize); return fontSize; }) .attr("fill", function (d) { // return "red"; return colors[d.category - 1]; }) .attr("name", function (d) { return d.time; }) .text(function (d) { return d.time ? d.time + d.name : d.name; }) .attr("text-anchor", "center") .on("click", function (d) { if (d.url) { window.open(d.url, "_blank"); } }) .call( d3 .drag() .on("start", dragStarted) .on("drag", dragging) .on("end", dragEnded) ); //圆增加title... node.append("title").text(function (d) { return d.time + d.name; }); /* //点击任意一个node, 不与之相连的节点和连线都变透明,怎么做 可以在点击node事件处理函数中通过改变对应元素的透明度实现: 获取点击的node的相邻节点 对于不相邻的节点,修改其透明度 对于不相邻的连线,修改其透明度 代码示例: */ var isTransparent = false; // 为每个node绑定点击事件 node.on("click", function(d) { // 根据当前状态进行相应的操作 if (!isTransparent) { link.style("opacity", function(l) { if (d === l.source || d === l.target) { return 1; } else { return 0.1; } }); node.style("opacity", function(n) { // 只对与点击的圆圈不相关的圆圈透明度进行更改 var linked = false; link.each(function(l) { if (d === l.source || d === l.target) { linked = true; return; } }); if (!linked) { return 0.1; } else { return 1; } }); isTransparent = true; } else { link.style("opacity", 1); node.style("opacity", 1); isTransparent = false; } }); simulation.on("tick", () => { link .attr("x1", (d) => d.source.x) .attr("y1", (d) => d.source.y) .attr("x2", (d) => d.target.x) .attr("y2", (d) => d.target.y); node.attr("cx", (d) => d.x).attr("cy", (d) => d.y); text.attr("transform", function (d) { // return 'translate(' + d.x + ',' + (d.y + d.size / 2) + ')'; return "translate(" + d.x + "," + (d.y + sizes[d.category - 1] / 2) + ")"; }); }); } (function () { let button = document.createElement("button"); button.innerHTML = "ViSearch"; button.style.cssText = "position: fixed; top: 0; right: 0; z-index: 999; width: 100px; height: 50px; background-color: green; color: white; font-size: 20px;"; // Add button to page document.body.appendChild(button); // Create graph element const graph = document.createElement("div"); graph.style.position = "fixed" graph.style.top = "50px" graph.style.right = "0px" graph.style.width = "500px" graph.style.height = "500px" graph.style.background = "#fff" graph.style.border = "1px solid #ccc" graph.style.display = "none" graph.style.opacity = 0.9; // Add graph to page document.body.appendChild(graph); let graphVisible = false; // Toggle graph on button click button.addEventListener("click", () => { if (graphVisible) { graph.style.display = "none"; graphVisible = false; } else { graph.style.display = "block"; graphVisible = true; data = parseWebPage(searchtext); console.log("data", data); nodes = data.nodes; links = data.links; //remove the previous canvs d3.select("#graph-svg").remove(); console.log("#graph-svg", d3.select("#graph-svg")); renderD3Graph(nodes, links, graph); } }); // Hide graph on outside click document.addEventListener("click", (event) => { if ( graphVisible && event.target !== graph && !graph.contains(event.target) && event.target !== button ) { graph.style.display = "none"; graphVisible = false; } }); })();