- // ==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 4.117
- // @description:zh-cn Beyond the ChatGPT/AI with eyes
- // @description:zh-tw Beyond the ChatGPT/AI with eyes
- // @description:fr Beyond the ChatGPT/AI with eyes
- // @description:es Beyond the ChatGPT/AI with eyes
- // @description:th Beyond the ChatGPT/AI with eyes
- // @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/d3@4.13.0/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
- // @description Beta version.ViSearch/Google of Google...Beyond the AI with eyes
- // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw
- // @note 2023-2-21-v1.117 发布迁移到userscript以来第一个正式版本
- // @note 2016 Java -> 2019 Node.js -> 2023-2-09 各种各样的历史更新记录,从一个版本迭代到另一个版本
- // @grant none
- // ==/UserScript==
- // @require https://d3js.org/d3.v6.min.js
-
- 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);
-
- let keyWords = Array.from(element.querySelectorAll(".qkunPe"));
- 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;
- }
- });
- })();