ViSearch/Google of Google

Beyond the ChatGPT/AI with eyes

目前为 2023-02-25 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name ViSearch/Google of Google
  3. // @name:zh-CN ViSearch/Google of Google
  4. // @name:zh-TW ViSearch/Google of Google
  5. // @name:fr ViSearch/Google of Google
  6. // @name:es ViSearch/Google of Google
  7. // @name:th ViSearch/Google of Google
  8. // @namespace https://github.com/new4u
  9. // @version 4.117
  10. // @description:zh-cn Beyond the ChatGPT/AI with eyes
  11. // @description:zh-tw Beyond the ChatGPT/AI with eyes
  12. // @description:fr Beyond the ChatGPT/AI with eyes
  13. // @description:es Beyond the ChatGPT/AI with eyes
  14. // @description:th Beyond the ChatGPT/AI with eyes
  15. // @author new4u本爷有空
  16. // @connect google.com
  17. // @connect google.com.hk
  18. // @connect google.com.jp
  19. // @include *://encrypted.google.*/search*
  20. // @include *://*.google*/search*
  21. // @include *://*.google*/webhp*
  22. // @match *www.google.com*
  23. // @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
  24. // @require https://unpkg.com/d3@4.13.0/build/d3.min.js
  25. // @require https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js
  26. // @require http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js
  27. // @require http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js
  28. // @resource http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css
  29. // @grant none
  30. // @copyright 2015-2023, new4u
  31. // @license GPL-3.0-only
  32. // @description Beta version.ViSearch/Google of Google...Beyond the AI with eyes
  33. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw
  34. // @note 2023-2-21-v1.117 发布迁移到userscript以来第一个正式版本
  35. // @note 2016 Java -> 2019 Node.js -> 2023-2-09 各种各样的历史更新记录,从一个版本迭代到另一个版本
  36. // @grant none
  37. // ==/UserScript==
  38. // @require https://d3js.org/d3.v6.min.js
  39.  
  40. let styleSheet = `
  41. body {
  42. padding: 30px 40px;
  43. font-family: OpenSans-Light, PingFang SC, Hiragino Sans GB, Microsoft Yahei, Microsoft Jhenghei, sans-serif;
  44. }
  45.  
  46. .links line {
  47. stroke: rgb(255, 255, 255);
  48. stroke-opacity: 0.5;
  49. }
  50.  
  51. .links line.inactive {
  52. stroke-opacity: 0;
  53. }
  54.  
  55. .nodes circle {
  56. stroke: #fff;
  57. stroke-width: 1.5px;
  58. }
  59.  
  60. .nodes circle:hover {
  61. cursor: pointer;
  62. }
  63.  
  64. .nodes circle.inactive {
  65. display: none !important;
  66. }
  67.  
  68.  
  69. @media screen and (max-width: 600px) {
  70. .text {
  71. font-size: 8px; /* 当屏幕宽度小于600px时 最小字体为8px */
  72. }
  73. }
  74.  
  75. .texts text {
  76. font-size: 12px; /* 最小字体为12px */
  77. min-font-size: 8px; /* 最小字体不能小于8px */
  78. max-font-size: 36px;
  79. font-weight:bold;
  80. font-family:"Microsoft YaHei";
  81. text-shadow: 0 0 3px #fff, 0 0 10px #fff;
  82. }
  83.  
  84.  
  85.  
  86. .texts text:hover {
  87. cursor: pointer;
  88. }
  89.  
  90. .texts text.inactive {
  91. display: none !important;
  92. }
  93.  
  94.  
  95. #mode {
  96. position: absolute;
  97. top: 160px;
  98. left: 60px;
  99. }
  100.  
  101. #mode span {
  102. display: inline-block;
  103. border: 1px solid #fff;
  104. color: #fff;
  105. padding: 6px 10px;
  106. border-radius: 4px;
  107. font-size: 14px;
  108. transition: color, background-color .3s;
  109. -o-transition: color, background-color .3s;
  110. -ms-transition: color, background-color .3s;
  111. -moz-transition: color, background-color .3s;
  112. -webkit-transition: color, background-color .3s;
  113. }
  114.  
  115.  
  116.  
  117. #info {
  118. position: absolute;
  119. bottom: 40px;
  120. right: 30px;
  121. text-align: right;
  122. width: 270px;
  123. }
  124.  
  125. #info h4 {
  126. color: #fff;
  127. }
  128.  
  129. #info p {
  130. color: #fff;
  131. font-size: 12px;
  132. margin-bottom: 5px;
  133. }
  134.  
  135. #info p span {
  136. color: #888;
  137. margin-right: 10px;
  138. }
  139.  
  140. #svg g.row:hover {
  141. stroke-width: 1px;
  142. stroke: #fff;
  143. }
  144. `;
  145.  
  146.  
  147.  
  148.  
  149.  
  150.  
  151.  
  152.  
  153.  
  154.  
  155.  
  156.  
  157.  
  158.  
  159.  
  160.  
  161.  
  162.  
  163.  
  164.  
  165.  
  166.  
  167.  
  168.  
  169.  
  170. let s = document.createElement('style');
  171. s.type = "text/css";
  172. s.innerHTML = styleSheet;
  173. (document.head || document.documentElement).appendChild(s);
  174.  
  175. const colors = ["#6ca46c", "#4e88af", "#c72eca", "#d2907c"];
  176. //临时半径R大小控制,之后改成连接数量影响大小
  177. const sizes = [30, 20, 20, 0.5];
  178. const forceRate = -1000;
  179. // 选择 input 元素中 class 为 gLFyf 的最后一个元素
  180. const input = document.querySelector("input.gLFyf:last-of-type");
  181. let searchtext = input ? input.value : "";
  182. if (searchtext === "") {
  183. console.log("searchtext is empty");
  184. } else {
  185. console.log("searchtext:", searchtext);
  186. // 执行其他逻辑
  187. }
  188.  
  189.  
  190.  
  191.  
  192.  
  193.  
  194.  
  195.  
  196.  
  197.  
  198.  
  199.  
  200. function parseWebPage(Searchtext) {
  201. //data
  202. //***开始解析网页
  203. /* 谷歌获取 */
  204.  
  205. //成功在page之外获取到页面元素内容,发现百度的下一页,就是
  206. let elements = Array.from(document.querySelectorAll(".g"));
  207. // console.log(elements);
  208. //读取数组里内容map为value
  209. let dataPage = elements.map((element) => {
  210. // console.log(element);
  211.  
  212. //搜索到文章的标题
  213. let title = element.querySelector(".LC20lb");
  214. title !== null ? (title = title.innerText) : (title = null);
  215.  
  216. // console.log(title);
  217.  
  218. //搜索到文章的url
  219. let url = element.querySelector("a");
  220. url !== null ? (url = url.href) : (url = null);
  221.  
  222. // console.log(url);
  223.  
  224. //搜索到的文章的来源网站r.split(" - ")[1]
  225. let siteName = title;
  226. siteName !== null
  227. ? (siteName = siteName.split(" - ")[1])
  228. : (siteName = null);
  229. // console.log(siteName);
  230.  
  231. //搜索到的文章的发布日期
  232. // let time = element.querySelector(".c-abstract>.newTimeFactor_before_abs"); //之前的query
  233. let time = element.querySelector(".MUxGbd.wuQ4Ob.WZ8Tjf > span");
  234. time !== null ? (time = time.innerText) : (time = null); //google几天前时间可以计算一下
  235.  
  236. // console.log(time);
  237.  
  238. //搜索到的文章的摘要
  239. let abstract = element.querySelector(
  240. ".VwiC3b.yXK7lf.MUxGbd.yDYNvb.lyLwlc.lEBKkf span:nth-child(2)"
  241. );
  242. abstract !== null ? (abstract = abstract.innerText) : (abstract = null);
  243.  
  244. let keyWords = Array.from(element.querySelectorAll(".qkunPe"));
  245. keyWords !== null
  246. ? (keyWords = keyWords.map((item) => {
  247. return item.innerText;
  248. }))
  249. : (keyWords = null);
  250. // console.log(keyWords);
  251.  
  252. let elementEach = {
  253. title,
  254. url,
  255. siteName,
  256. time,
  257. abstract,
  258. keyWords,
  259. };
  260.  
  261. // console.log(elementEach);
  262. return elementEach;
  263. });
  264.  
  265. console.log(dataPage);
  266.  
  267. let keyWords = [];
  268. for (let i in dataPage) {
  269. let temp = dataPage[i].keyWords;
  270. keyWords = keyWords.concat(temp);
  271. }
  272. let keyWordsSet = Array.from(new Set(keyWords));
  273.  
  274. let nodes = [];
  275. let links = [];
  276. let id = 0;
  277.  
  278. let sanidstart = id;
  279. let sanNode = [];
  280.  
  281. for (let j = 0; j < dataPage.length; j++) {
  282. sanNode.push({
  283. category: 4,
  284. id: "san" + j,
  285. name: dataPage[j].title,
  286. value: dataPage[j].abstract,
  287. origin: dataPage[j].siteName,
  288. time: dataPage[j].time,
  289.  
  290. year:
  291. dataPage[j].time !== null &&
  292. dataPage[j].time.replace(/[^0-9\u4e00-\u9fa5]/gi, "") !== ""
  293. ? dataPage[j].time.replace(/[^0-9\u4e00-\u9fa5]/gi, "").substr(0, 4)
  294. : null,
  295. url: dataPage[j].url,
  296. //为了前面能找到,再加一条,也可以用这个循环,就少了一个!,这样资料全一些
  297. keyWords: dataPage[j].keyWords,
  298. type: "san",
  299. });
  300. }
  301. let keyidstart = id;
  302. let keyNode = [];
  303. for (let i = 0; i < keyWordsSet.length; i++) {
  304. if (keyWordsSet[i].length <= 4) {
  305. keyNode.push({
  306. category: 3,
  307. id: "key" + i,
  308. name: keyWordsSet[i],
  309. value: 30000 + i + "",
  310. type: "key",
  311. });
  312. // !!! link 如果 keyNode.name 在dataPage.keywords li indexof 就连接(先写san)
  313. // for (k)
  314. for (let j = 0; j < sanNode.length; j++) {
  315. //keywords是数组,不知道行不行,可以
  316. if (sanNode[j].keyWords.indexOf(keyWordsSet[i]) !== -1) {
  317. links.push({
  318. source: "key" + i,
  319. target: "san" + j,
  320. value: 1,
  321. });
  322. }
  323. }
  324. }
  325. }
  326.  
  327. let tagid = id;
  328. //2,tag,id=20000-29999,tag就是长度大于4的keywordsSet
  329. let tagNode = [];
  330. for (let i = 0; i < keyWordsSet.length; i++) {
  331. if (keyWordsSet[i].length > 4) {
  332. tagNode.push({
  333. category: 2,
  334. id: "tag" + i,
  335. type: "tag",
  336. name: keyWordsSet[i],
  337. value: id,
  338. });
  339. for (let j = 0; j < sanNode.length; j++) {
  340. //keywords是数组,不知道行不行,可以
  341. if (sanNode[j].keyWords.indexOf(keyWordsSet[i]) !== -1) {
  342. links.push({
  343. source: "tag" + i,
  344. target: "san" + j,
  345. value: 1,
  346. });
  347. }
  348. }
  349.  
  350. for (let j = 0; j < keyNode.length; j++) {
  351. //如果tagNode.name包含了keyNode.name(indexof),就连一条线
  352. if (keyWordsSet[i].indexOf(keyNode[j].name) !== -1) {
  353. links.push({
  354. source: "tag" + i,
  355. target: keyNode[j].id,
  356. value: 1,
  357. });
  358. }
  359. }
  360. }
  361. }
  362.  
  363. //1,id=10000,不能用10000+"",,只能用i+"".直接"10000"就好了
  364. let newsNode = {
  365. category: 1,
  366. id: "news",
  367. //searchtext在前面定义前面获取
  368. name: searchtext,
  369. value: id,
  370. type: "news",
  371. };
  372. //和key和tag都建立连接
  373. for (let i = 0; i < keyNode.length; i++) {
  374. links.push({
  375. source: "news",
  376. target: keyNode[i].id,
  377. value: 1,
  378. });
  379. }
  380. for (let i = 0; i < tagNode.length; i++) {
  381. links.push({
  382. source: "news",
  383. target: tagNode[i].id,
  384. value: 1,
  385. });
  386. }
  387.  
  388. // 合并4个[],用concat(),
  389. nodes = nodes
  390. .concat(newsNode)
  391. .concat(tagNode)
  392. .concat(keyNode)
  393. .concat(sanNode);
  394.  
  395. //data end
  396.  
  397. return {
  398. nodes,
  399. links,
  400. };
  401. }
  402.  
  403.  
  404.  
  405.  
  406.  
  407.  
  408.  
  409.  
  410.  
  411.  
  412.  
  413.  
  414.  
  415.  
  416.  
  417.  
  418.  
  419.  
  420.  
  421. function renderD3Graph(nodes, links, graph) {
  422. // 获取 graph 元素的宽度和高度
  423. const width = parseInt(graph.style.width);
  424. const height = parseInt(graph.style.height);
  425.  
  426. d3.selectAll("graph > *").remove();
  427.  
  428. // // 创建 D3.js SVG 元素,并设置其大小和位置
  429. const bbox = graph.getBoundingClientRect();
  430.  
  431. const svg = d3.select(graph).append("svg")
  432. .attr("id","graph-svg")
  433. .attr("width", bbox.width)
  434. .attr("height", bbox.height)
  435. .style("position", "fixed")
  436. .style("top", bbox.y + "px")
  437. .style("left", bbox.x + "px")
  438. .style("z-index", "999");
  439.  
  440.  
  441. // const svg = d3
  442. // .select(graph)
  443. // .append("svg")
  444. // .attr("width", width)
  445. // .attr("height", height)
  446. // .style("position", "fixed")
  447. // .style("top", "0")
  448. // .style("right", "0")
  449. // .style("z-index", "999");
  450.  
  451. function dragStarted(d) {
  452. if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  453. d.fx = d.x;
  454. d.fy = d.y;
  455. }
  456.  
  457. function dragging(d) {
  458. d.fx = d3.event.x;
  459. d.fy = d3.event.y;
  460. }
  461.  
  462. function dragEnded(d) {
  463. if (!d3.event.active) simulation.alphaTarget(0);
  464. d.fx = null;
  465. d.fy = null;
  466. }
  467.  
  468. const simulation = d3
  469. .forceSimulation(nodes)
  470. .force(
  471. "link",
  472. d3
  473. .forceLink(links)
  474. .id((d) => d.id)
  475. .distance(50)
  476. .strength(1)
  477. .iterations(1)
  478. )
  479. .force("charge", d3.forceManyBody().strength(forceRate))
  480. .force("center", d3.forceCenter(width / 2, height / 2))
  481. .alphaDecay(0.05);
  482.  
  483. const link = svg
  484. .append("g")
  485. .attr("stroke", "#999")
  486. .attr("stroke-opacity", 0.6)
  487. .selectAll("line")
  488. .data(links)
  489. .enter()
  490. .append("line")
  491. .attr("stroke-width", (d) => Math.sqrt(d.value));
  492.  
  493. const node = svg
  494. .append("g")
  495. .selectAll("circle")
  496. .data(nodes)
  497. .enter()
  498. .append("circle")
  499. // 试着给每种type加一个class,要放在数据读取之后
  500. .attr("class", function (d) {
  501. // return "nodes";
  502. return "nodes " + d.type;
  503. })
  504. .attr("r", function (d) {
  505. // make radius of node circle
  506. return sizes[d.category - 1]; // return r;
  507. })
  508. .attr("fill", function (d) {
  509. // 配合现在category从1开始,今后可以重新设计一下category make color of node circle,也可以加到后面,统一修改
  510. // console.log(d.category);
  511. return colors[d.category - 1];
  512. })
  513. .attr("stroke", "none")
  514. .attr("name", function (d) {
  515. // make text of node
  516. return d.name;
  517. })
  518.  
  519. .call(
  520. d3
  521. .drag()
  522. .on("start", dragStarted)
  523. .on("drag", dragging)
  524. .on("end", dragEnded)
  525. );
  526.  
  527. //固定中心文章位置,用class来控制
  528. //固定中心文章位置,fx可以设置哈哈,或者大面积的可以用tick,详见https://stackoverflow.com/questions/10392505/fix-node-position-in-d3-force-directed-layout,实验证明,还可以用type属性来控制fx,fy
  529.  
  530. svg
  531. .select(".news")
  532. .attr("fx", function (d) {
  533. return (d.fx = width / 2);
  534. })
  535. .attr("fy", function (d) {
  536. return (d.fy = height / 2);
  537. });
  538.  
  539. //san文章点击, 没有name这个
  540. //存放三度文章size
  541. svg
  542. .selectAll(".san")
  543. .attr("r", function (d) {
  544. let uniqueWords = new Set(d.keyWords);
  545. let radius = uniqueWords.size * 10;
  546. // console.log("radius:", radius);
  547. return radius;
  548. })
  549. .style("fill-opacity", 0.5);
  550.  
  551. // 显示所有的文本...
  552. const text = svg
  553. .append("g")
  554. .attr("class", "texts")
  555. .selectAll("text")
  556. .data(nodes)
  557. .enter()
  558. .append("text")
  559. .attr("font-size", function (d) {
  560. // return d.size;
  561. let uniqueWords = new Set(d.keyWords);
  562. let radius = uniqueWords.size * 2;
  563. let fontSize = radius * sizes[d.category - 1];
  564.  
  565. // console.log("d:", d, ";font-size return", fontSize);
  566. return fontSize;
  567. })
  568. .attr("fill", function (d) {
  569. // return "red";
  570. return colors[d.category - 1];
  571. })
  572. .attr("name", function (d) {
  573. return d.time;
  574. })
  575. .text(function (d) {
  576. return d.time ? d.time + d.name : d.name;
  577. })
  578. .attr("text-anchor", "center")
  579. .on("click", function (d) {
  580. if (d.url) {
  581. window.open(d.url, "_blank");
  582. }
  583. })
  584. .call(
  585. d3
  586. .drag()
  587. .on("start", dragStarted)
  588. .on("drag", dragging)
  589. .on("end", dragEnded)
  590. );
  591.  
  592. //圆增加title...
  593. node.append("title").text(function (d) {
  594. return d.time + d.name;
  595. });
  596.  
  597.  
  598. /* //点击任意一个node, 不与之相连的节点和连线都变透明,怎么做
  599.  
  600. 可以在点击node事件处理函数中通过改变对应元素的透明度实现:
  601.  
  602. 获取点击的node的相邻节点
  603. 对于不相邻的节点,修改其透明度
  604. 对于不相邻的连线,修改其透明度
  605. 代码示例: */
  606.  
  607. var isTransparent = false;
  608.  
  609. // 为每个node绑定点击事件
  610. node.on("click", function(d) {
  611. // 根据当前状态进行相应的操作
  612. if (!isTransparent) {
  613. link.style("opacity", function(l) {
  614. if (d === l.source || d === l.target) {
  615. return 1;
  616. } else {
  617. return 0.1;
  618. }
  619. });
  620. node.style("opacity", function(n) {
  621. // 只对与点击的圆圈不相关的圆圈透明度进行更改
  622. var linked = false;
  623. link.each(function(l) {
  624. if (d === l.source || d === l.target) {
  625. linked = true;
  626. return;
  627. }
  628. });
  629. if (!linked) {
  630. return 0.1;
  631. } else {
  632. return 1;
  633. }
  634. });
  635. isTransparent = true;
  636. } else {
  637. link.style("opacity", 1);
  638. node.style("opacity", 1);
  639. isTransparent = false;
  640. }
  641. });
  642.  
  643. simulation.on("tick", () => {
  644. link
  645. .attr("x1", (d) => d.source.x)
  646. .attr("y1", (d) => d.source.y)
  647. .attr("x2", (d) => d.target.x)
  648. .attr("y2", (d) => d.target.y);
  649.  
  650. node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
  651. text.attr("transform", function (d) {
  652. // return 'translate(' + d.x + ',' + (d.y + d.size / 2) + ')';
  653. return "translate(" + d.x + "," + (d.y + sizes[d.category - 1] / 2) + ")";
  654. });
  655. });
  656. }
  657.  
  658.  
  659.  
  660.  
  661.  
  662.  
  663.  
  664.  
  665.  
  666.  
  667.  
  668.  
  669.  
  670.  
  671.  
  672.  
  673.  
  674.  
  675.  
  676.  
  677.  
  678.  
  679.  
  680.  
  681.  
  682.  
  683.  
  684.  
  685.  
  686.  
  687.  
  688. (function () {
  689. let button = document.createElement("button");
  690. button.innerHTML = "ViSearch";
  691. button.style.cssText =
  692. "position: fixed; top: 0; right: 0; z-index: 999; width: 100px; height: 50px; background-color: green; color: white; font-size: 20px;";
  693.  
  694. // Add button to page
  695. document.body.appendChild(button);
  696.  
  697.  
  698. // Create graph element
  699. const graph = document.createElement("div");
  700. graph.style.position = "fixed"
  701. graph.style.top = "50px"
  702. graph.style.right = "0px"
  703. graph.style.width = "500px"
  704. graph.style.height = "500px"
  705. graph.style.background = "#fff"
  706. graph.style.border = "1px solid #ccc"
  707. graph.style.display = "none"
  708. graph.style.opacity = 0.9;
  709.  
  710.  
  711. // Add graph to page
  712. document.body.appendChild(graph);
  713. let graphVisible = false;
  714.  
  715. // Toggle graph on button click
  716. button.addEventListener("click", () => {
  717. if (graphVisible) {
  718. graph.style.display = "none";
  719. graphVisible = false;
  720. } else {
  721. graph.style.display = "block";
  722. graphVisible = true;
  723. data = parseWebPage(searchtext);
  724. console.log("data", data);
  725. nodes = data.nodes;
  726. links = data.links;
  727. //remove the previous canvs
  728. d3.select("#graph-svg").remove();
  729. console.log("#graph-svg", d3.select("#graph-svg"));
  730.  
  731.  
  732. renderD3Graph(nodes, links, graph);
  733. }
  734. });
  735.  
  736. // Hide graph on outside click
  737. document.addEventListener("click", (event) => {
  738. if (
  739. graphVisible &&
  740. event.target !== graph &&
  741. !graph.contains(event.target) &&
  742. event.target !== button
  743. ) {
  744. graph.style.display = "none";
  745. graphVisible = false;
  746. }
  747. });
  748. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址