ViSearch/Google of Google

Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch

  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 7.117.2
  10. // @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.
  11. // @description:zh-cn Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch
  12. // @description:zh-tw Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch
  13. // @description:fr Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch
  14. // @description:es Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch
  15. // @description:th Beyond the ChatGPT/AI with eyes Now:ViSearch on Chrome extensions https://chrome.google.com/webstore/search/visearch
  16. // @author new4u本爷有空
  17. // @connect google.com
  18. // @connect google.com.hk
  19. // @connect google.com.jp
  20. // @include *://encrypted.google.*/search*
  21. // @include *://*.google*/search*
  22. // @include *://*.google*/webhp*
  23. // @match *www.google.com*
  24. // @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
  25. // @require https://unpkg.com/d3@4.13.0/build/d3.min.js
  26. // @require https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js
  27. // @require http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js
  28. // @require http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js
  29. // @resource http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css
  30. // @grant none
  31. // @copyright 2015-2023, new4u
  32. // @license GPL-3.0-only
  33. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw
  34. // @note 2023-05-25-v.7.117 发布到了chrome应用商店,并且修复了一个关键词和chrome界面语种的冲突,支持多语种.
  35. // @note 2023-2-21-v1.117 发布迁移到userscript以来第一个正式版本
  36. // @note 2016 Java -> 2019 Node.js -> 2023-2-09 各种各样的历史更新记录,从一个版本迭代到另一个版本
  37. // @grant none
  38. // ==/UserScript==
  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. //搜索到关键词如果2023年05月25日更新到多语种
  245. let keyWords = Array.from(element.querySelectorAll("em"));
  246. keyWords !== null
  247. ? (keyWords = keyWords.map((item) => {
  248. return item.innerText;
  249. }))
  250. : (keyWords = null);
  251. // console.log(keyWords);
  252.  
  253. let elementEach = {
  254. title,
  255. url,
  256. siteName,
  257. time,
  258. abstract,
  259. keyWords,
  260. };
  261.  
  262. // console.log(elementEach);
  263. return elementEach;
  264. });
  265.  
  266. console.log(dataPage);
  267.  
  268. let keyWords = [];
  269. for (let i in dataPage) {
  270. let temp = dataPage[i].keyWords;
  271. keyWords = keyWords.concat(temp);
  272. }
  273. let keyWordsSet = Array.from(new Set(keyWords));
  274.  
  275. let nodes = [];
  276. let links = [];
  277. let id = 0;
  278.  
  279. let sanidstart = id;
  280. let sanNode = [];
  281.  
  282. for (let j = 0; j < dataPage.length; j++) {
  283. sanNode.push({
  284. category: 4,
  285. id: "san" + j,
  286. name: dataPage[j].title,
  287. value: dataPage[j].abstract,
  288. origin: dataPage[j].siteName,
  289. time: dataPage[j].time,
  290.  
  291. year:
  292. dataPage[j].time !== null &&
  293. dataPage[j].time.replace(/[^0-9\u4e00-\u9fa5]/gi, "") !== ""
  294. ? dataPage[j].time.replace(/[^0-9\u4e00-\u9fa5]/gi, "").substr(0, 4)
  295. : null,
  296. url: dataPage[j].url,
  297. //为了前面能找到,再加一条,也可以用这个循环,就少了一个!,这样资料全一些
  298. keyWords: dataPage[j].keyWords,
  299. type: "san",
  300. });
  301. }
  302. let keyidstart = id;
  303. let keyNode = [];
  304. for (let i = 0; i < keyWordsSet.length; i++) {
  305. if (keyWordsSet[i].length <= 4) {
  306. keyNode.push({
  307. category: 3,
  308. id: "key" + i,
  309. name: keyWordsSet[i],
  310. value: 30000 + i + "",
  311. type: "key",
  312. });
  313. // !!! link 如果 keyNode.name 在dataPage.keywords li indexof 就连接(先写san)
  314. // for (k)
  315. for (let j = 0; j < sanNode.length; j++) {
  316. //keywords是数组,不知道行不行,可以
  317. if (sanNode[j].keyWords.indexOf(keyWordsSet[i]) !== -1) {
  318. links.push({
  319. source: "key" + i,
  320. target: "san" + j,
  321. value: 1,
  322. });
  323. }
  324. }
  325. }
  326. }
  327.  
  328. let tagid = id;
  329. //2,tag,id=20000-29999,tag就是长度大于4的keywordsSet
  330. let tagNode = [];
  331. for (let i = 0; i < keyWordsSet.length; i++) {
  332. if (keyWordsSet[i].length > 4) {
  333. tagNode.push({
  334. category: 2,
  335. id: "tag" + i,
  336. type: "tag",
  337. name: keyWordsSet[i],
  338. value: id,
  339. });
  340. for (let j = 0; j < sanNode.length; j++) {
  341. //keywords是数组,不知道行不行,可以
  342. if (sanNode[j].keyWords.indexOf(keyWordsSet[i]) !== -1) {
  343. links.push({
  344. source: "tag" + i,
  345. target: "san" + j,
  346. value: 1,
  347. });
  348. }
  349. }
  350.  
  351. for (let j = 0; j < keyNode.length; j++) {
  352. //如果tagNode.name包含了keyNode.name(indexof),就连一条线
  353. if (keyWordsSet[i].indexOf(keyNode[j].name) !== -1) {
  354. links.push({
  355. source: "tag" + i,
  356. target: keyNode[j].id,
  357. value: 1,
  358. });
  359. }
  360. }
  361. }
  362. }
  363.  
  364. //1,id=10000,不能用10000+"",,只能用i+"".直接"10000"就好了
  365. let newsNode = {
  366. category: 1,
  367. id: "news",
  368. //searchtext在前面定义前面获取
  369. name: searchtext,
  370. value: id,
  371. type: "news",
  372. };
  373. //和key和tag都建立连接
  374. for (let i = 0; i < keyNode.length; i++) {
  375. links.push({
  376. source: "news",
  377. target: keyNode[i].id,
  378. value: 1,
  379. });
  380. }
  381. for (let i = 0; i < tagNode.length; i++) {
  382. links.push({
  383. source: "news",
  384. target: tagNode[i].id,
  385. value: 1,
  386. });
  387. }
  388.  
  389. // 合并4个[],用concat(),
  390. nodes = nodes
  391. .concat(newsNode)
  392. .concat(tagNode)
  393. .concat(keyNode)
  394. .concat(sanNode);
  395.  
  396. //data end
  397.  
  398. return {
  399. nodes,
  400. links,
  401. };
  402. }
  403.  
  404.  
  405.  
  406.  
  407.  
  408.  
  409.  
  410.  
  411.  
  412.  
  413.  
  414.  
  415.  
  416.  
  417.  
  418.  
  419.  
  420.  
  421.  
  422. function renderD3Graph(nodes, links, graph) {
  423. // 获取 graph 元素的宽度和高度
  424. const width = parseInt(graph.style.width);
  425. const height = parseInt(graph.style.height);
  426.  
  427. d3.selectAll("graph > *").remove();
  428.  
  429. // // 创建 D3.js SVG 元素,并设置其大小和位置
  430. const bbox = graph.getBoundingClientRect();
  431.  
  432. const svg = d3.select(graph).append("svg")
  433. .attr("id","graph-svg")
  434. .attr("width", bbox.width)
  435. .attr("height", bbox.height)
  436. .style("position", "fixed")
  437. .style("top", bbox.y + "px")
  438. .style("left", bbox.x + "px")
  439. .style("z-index", "999");
  440.  
  441.  
  442. // const svg = d3
  443. // .select(graph)
  444. // .append("svg")
  445. // .attr("width", width)
  446. // .attr("height", height)
  447. // .style("position", "fixed")
  448. // .style("top", "0")
  449. // .style("right", "0")
  450. // .style("z-index", "999");
  451.  
  452. function dragStarted(d) {
  453. if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  454. d.fx = d.x;
  455. d.fy = d.y;
  456. }
  457.  
  458. function dragging(d) {
  459. d.fx = d3.event.x;
  460. d.fy = d3.event.y;
  461. }
  462.  
  463. function dragEnded(d) {
  464. if (!d3.event.active) simulation.alphaTarget(0);
  465. d.fx = null;
  466. d.fy = null;
  467. }
  468.  
  469. const simulation = d3
  470. .forceSimulation(nodes)
  471. .force(
  472. "link",
  473. d3
  474. .forceLink(links)
  475. .id((d) => d.id)
  476. .distance(50)
  477. .strength(1)
  478. .iterations(1)
  479. )
  480. .force("charge", d3.forceManyBody().strength(forceRate))
  481. .force("center", d3.forceCenter(width / 2, height / 2))
  482. .alphaDecay(0.05);
  483.  
  484. const link = svg
  485. .append("g")
  486. .attr("stroke", "#999")
  487. .attr("stroke-opacity", 0.6)
  488. .selectAll("line")
  489. .data(links)
  490. .enter()
  491. .append("line")
  492. .attr("stroke-width", (d) => Math.sqrt(d.value));
  493.  
  494. const node = svg
  495. .append("g")
  496. .selectAll("circle")
  497. .data(nodes)
  498. .enter()
  499. .append("circle")
  500. // 试着给每种type加一个class,要放在数据读取之后
  501. .attr("class", function (d) {
  502. // return "nodes";
  503. return "nodes " + d.type;
  504. })
  505. .attr("r", function (d) {
  506. // make radius of node circle
  507. return sizes[d.category - 1]; // return r;
  508. })
  509. .attr("fill", function (d) {
  510. // 配合现在category从1开始,今后可以重新设计一下category make color of node circle,也可以加到后面,统一修改
  511. // console.log(d.category);
  512. return colors[d.category - 1];
  513. })
  514. .attr("stroke", "none")
  515. .attr("name", function (d) {
  516. // make text of node
  517. return d.name;
  518. })
  519.  
  520. .call(
  521. d3
  522. .drag()
  523. .on("start", dragStarted)
  524. .on("drag", dragging)
  525. .on("end", dragEnded)
  526. );
  527.  
  528. //固定中心文章位置,用class来控制
  529. //固定中心文章位置,fx可以设置哈哈,或者大面积的可以用tick,详见https://stackoverflow.com/questions/10392505/fix-node-position-in-d3-force-directed-layout,实验证明,还可以用type属性来控制fx,fy
  530.  
  531. svg
  532. .select(".news")
  533. .attr("fx", function (d) {
  534. return (d.fx = width / 2);
  535. })
  536. .attr("fy", function (d) {
  537. return (d.fy = height / 2);
  538. });
  539.  
  540. //san文章点击, 没有name这个
  541. //存放三度文章size
  542. svg
  543. .selectAll(".san")
  544. .attr("r", function (d) {
  545. let uniqueWords = new Set(d.keyWords);
  546. let radius = uniqueWords.size * 10;
  547. // console.log("radius:", radius);
  548. return radius;
  549. })
  550. .style("fill-opacity", 0.5);
  551.  
  552. // 显示所有的文本...
  553. const text = svg
  554. .append("g")
  555. .attr("class", "texts")
  556. .selectAll("text")
  557. .data(nodes)
  558. .enter()
  559. .append("text")
  560. .attr("font-size", function (d) {
  561. // return d.size;
  562. let uniqueWords = new Set(d.keyWords);
  563. let radius = uniqueWords.size * 2;
  564. let fontSize = radius * sizes[d.category - 1];
  565.  
  566. // console.log("d:", d, ";font-size return", fontSize);
  567. return fontSize;
  568. })
  569. .attr("fill", function (d) {
  570. // return "red";
  571. return colors[d.category - 1];
  572. })
  573. .attr("name", function (d) {
  574. return d.time;
  575. })
  576. .text(function (d) {
  577. return d.time ? d.time + d.name : d.name;
  578. })
  579. .attr("text-anchor", "center")
  580. .on("click", function (d) {
  581. if (d.url) {
  582. window.open(d.url, "_blank");
  583. }
  584. })
  585. .call(
  586. d3
  587. .drag()
  588. .on("start", dragStarted)
  589. .on("drag", dragging)
  590. .on("end", dragEnded)
  591. );
  592.  
  593. //圆增加title...
  594. node.append("title").text(function (d) {
  595. return d.time + d.name;
  596. });
  597.  
  598.  
  599. /* //点击任意一个node, 不与之相连的节点和连线都变透明,怎么做
  600.  
  601. 可以在点击node事件处理函数中通过改变对应元素的透明度实现:
  602.  
  603. 获取点击的node的相邻节点
  604. 对于不相邻的节点,修改其透明度
  605. 对于不相邻的连线,修改其透明度
  606. 代码示例: */
  607.  
  608. var isTransparent = false;
  609.  
  610. // 为每个node绑定点击事件
  611. node.on("click", function(d) {
  612. // 根据当前状态进行相应的操作
  613. if (!isTransparent) {
  614. link.style("opacity", function(l) {
  615. if (d === l.source || d === l.target) {
  616. return 1;
  617. } else {
  618. return 0.1;
  619. }
  620. });
  621. node.style("opacity", function(n) {
  622. // 只对与点击的圆圈不相关的圆圈透明度进行更改
  623. var linked = false;
  624. link.each(function(l) {
  625. if (d === l.source || d === l.target) {
  626. linked = true;
  627. return;
  628. }
  629. });
  630. if (!linked) {
  631. return 0.1;
  632. } else {
  633. return 1;
  634. }
  635. });
  636. isTransparent = true;
  637. } else {
  638. link.style("opacity", 1);
  639. node.style("opacity", 1);
  640. isTransparent = false;
  641. }
  642. });
  643.  
  644. simulation.on("tick", () => {
  645. link
  646. .attr("x1", (d) => d.source.x)
  647. .attr("y1", (d) => d.source.y)
  648. .attr("x2", (d) => d.target.x)
  649. .attr("y2", (d) => d.target.y);
  650.  
  651. node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
  652. text.attr("transform", function (d) {
  653. // return 'translate(' + d.x + ',' + (d.y + d.size / 2) + ')';
  654. return "translate(" + d.x + "," + (d.y + sizes[d.category - 1] / 2) + ")";
  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.  
  689. (function () {
  690. let button = document.createElement("button");
  691. button.innerHTML = "ViSearch";
  692. button.style.cssText =
  693. "position: fixed; top: 0; right: 0; z-index: 999; width: 100px; height: 50px; background-color: green; color: white; font-size: 20px;";
  694.  
  695. // Add button to page
  696. document.body.appendChild(button);
  697.  
  698.  
  699. // Create graph element
  700. const graph = document.createElement("div");
  701. graph.style.position = "fixed"
  702. graph.style.top = "50px"
  703. graph.style.right = "0px"
  704. graph.style.width = "500px"
  705. graph.style.height = "500px"
  706. graph.style.background = "#fff"
  707. graph.style.border = "1px solid #ccc"
  708. graph.style.display = "none"
  709. graph.style.opacity = 0.9;
  710.  
  711.  
  712. // Add graph to page
  713. document.body.appendChild(graph);
  714. let graphVisible = false;
  715.  
  716. // Toggle graph on button click
  717. button.addEventListener("click", () => {
  718. if (graphVisible) {
  719. graph.style.display = "none";
  720. graphVisible = false;
  721. } else {
  722. graph.style.display = "block";
  723. graphVisible = true;
  724. data = parseWebPage(searchtext);
  725. console.log("data", data);
  726. nodes = data.nodes;
  727. links = data.links;
  728. //remove the previous canvs
  729. d3.select("#graph-svg").remove();
  730. console.log("#graph-svg", d3.select("#graph-svg"));
  731.  
  732.  
  733. renderD3Graph(nodes, links, graph);
  734. }
  735. });
  736.  
  737. // Hide graph on outside click
  738. document.addEventListener("click", (event) => {
  739. if (
  740. graphVisible &&
  741. event.target !== graph &&
  742. !graph.contains(event.target) &&
  743. event.target !== button
  744. ) {
  745. graph.style.display = "none";
  746. graphVisible = false;
  747. }
  748. });
  749. })();