perplexa

Greasemonkey script to get Perplexity answers within Google search

  1. // ==UserScript==
  2. // @name perplexa
  3. // @match https://www.google.com/search*
  4. // @namespace guebosch
  5. // @version 2024-08-11
  6. // @description Greasemonkey script to get Perplexity answers within Google search
  7. // @author guebosch, https://github.com/adtac
  8. // @grant none
  9. // @license GPLv3
  10. // ==/UserScript==
  11.  
  12. // Draft: Fix the errors below
  13.  
  14. async function getID() {
  15. const resp = await fetch("https://www.perplexity.ai/socket.io/?EIO=4&transport=polling", {
  16. credentials: "include",
  17. });
  18. const text = await resp.text();
  19. const json = JSON.parse(text.substr(text.indexOf("{")));
  20. return json.sid;
  21. }
  22.  
  23. async function postID(sid) {
  24. const resp = await fetch("https://www.perplexity.ai/socket.io/?EIO=3&transport=polling&sid=" + sid, {
  25. method: "POST",
  26. body: '40{"jwt":"anonymous-ask-user"}',
  27. credentials: "include",
  28. });
  29. return resp.status == 200;
  30. };
  31.  
  32. async function show(query) {
  33. const root = document.createElement("div");
  34. root.style.position = "absolute";
  35. root.style.right = "32px";
  36. root.style.top = (document.getElementById("appbar").offsetTop + 16) + "px";
  37. root.style.borderRadius = "4px";
  38. root.style.background = "white";
  39. root.style.width = "24rem";
  40. root.style.border = "1px solid #e0e0e0";
  41. root.style.padding = "0";
  42. root.style.margin = "0";
  43. root.style.overflowY = "hidden";
  44. root.style.minHeight = "6rem";
  45. root.style.maxHeight = "6rem";
  46. const main = document.createElement("p");
  47. main.style.fontSize = "13px";
  48. main.style.position = "relative";
  49. main.style.padding = "0";
  50. main.style.margin = "0";
  51. main.style.padding = "12px 24px";
  52. root.appendChild(main);
  53. const more = document.createElement("button");
  54. more.innerText = "Expand";
  55. more.style.color = "#808080";
  56. more.style.position = "absolute";
  57. more.style.top = "2rem";
  58. more.style.right = "0px";
  59. more.style.width = "100%";
  60. more.style.height = "4rem";
  61. more.style.background = "linear-gradient(180deg, transparent, white 50%)";
  62. more.style.border = "none";
  63. more.style.borderRadius = "4px";
  64. more.style.cursor = "pointer";
  65. more.style.paddingTop = "2rem";
  66. more.style.fontSize = "10px";
  67. root.appendChild(more);
  68. more.addEventListener("click", () => {
  69. root.removeChild(more);
  70. root.style.overflowY = "unset";
  71. root.style.minHeight = "unset";
  72. root.style.maxHeight = "unset";
  73. })
  74. document.body.appendChild(root);
  75. const sid = await getID();
  76. await postID(sid);
  77. const ws = new WebSocket("wss://www.perplexity.ai/socket.io/?EIO=4&transport=websocket&sid=" + sid);
  78. ws.addEventListener("open", (e) => {
  79. ws.send("2probe");
  80. ws.send("5");
  81. ws.send("421" + JSON.stringify([
  82. "perplexity_ask",
  83. query,
  84. {
  85. "version": "2.5",
  86. "search_focus": "internet",
  87. "mode": "concise",
  88. "prompt_source": "user",
  89. "query_source": "home",
  90. },
  91. ]));
  92. });
  93. ws.addEventListener("message", (e) => {
  94. if (e.data == "2") {
  95. ws.send("3");
  96. return;
  97. }
  98. let s = e.data;
  99. while (/^\d/.test(s)) {
  100. s = s.substr(1);
  101. }
  102. if (s == "") {
  103. return;
  104. }
  105. try { s = JSON.parse(s); } catch (e) {}
  106. if (Object.prototype.toString.call(s) != "[object Array]") {
  107. return;
  108. }
  109. let t = null;
  110. if (s[0] == "query_progress") {
  111. t = "query_progress"
  112. s = s.slice(1);
  113. }
  114. const { status } = s[0];
  115. let data = null;
  116. switch (status) {
  117. case "pending":
  118. data = JSON.parse(s[0].text);
  119. case "completed":
  120. data = JSON.parse(s[0].text);
  121. }
  122. if (data == null) {
  123. return;
  124. }
  125. const p = document.createElement("p");
  126. main.innerHTML = "";
  127. main.appendChild(p);
  128. let ans = data.answer;
  129. while (true) {
  130. const repl = ans.replace(/(\[\d+\])\./, ".$1"); // "[1]." -> ".[1]" for nicer formatting
  131. if (repl == ans) {
  132. break;
  133. }
  134. ans = repl;
  135. }
  136. while (ans.length > 0) {
  137. const m = ans.match(/\[\d+\]/);
  138. let end = ans.length;
  139. if (m) {
  140. end = m.index;
  141. }
  142. if (end == 0) {
  143. const cite = parseInt(ans.substr(1, ans.indexOf("]")));
  144. const { name, url } = data.web_results[cite-1];
  145. const a = document.createElement("a");
  146. a.style.margin = "4px";
  147. a.style.float = "right";
  148. a.style.width = "24px";
  149. a.style.height = "24px";
  150. a.style.outline = "1px solid #f0f0f0";
  151. a.style.borderRadius = "4px";
  152. a.style.background = "center url(https://www.google.com/s2/favicons?sz=128&domain=" + (new URL(url)).hostname + ")";
  153. a.style.backgroundSize = "cover";
  154. a.title = name;
  155. a.href = url;
  156. p.appendChild(a);
  157. ans = ans.substr(cite.toString().length + 2)
  158. } else {
  159. const span = document.createElement("span");
  160. span.style.width = "calc(100% - 5rem)";
  161. span.style.float = "left";
  162. span.style.borderRight = "1px solid #f0f0f0";
  163. span.style.padding = "4px 1rem 4px 0px";
  164. span.style.marginBottom = "1rem";
  165. span.innerText = ans.substr(0, end).trim();
  166. p.appendChild(span);
  167. ans = ans.substr(end);
  168. }
  169. }
  170. if (status == "completed") {
  171. const a = document.createElement("a");
  172. a.style.margin = "4px";
  173. a.style.fontSize = "10px";
  174. a.style.color = "#1d4ed8";
  175. a.style.width = "24px";
  176. a.style.height = "24px";
  177. a.style.border = "1px solid #f0f0f0";
  178. a.style.borderRadius = "4px";
  179. a.style.background = "center url(https://www.google.com/s2/favicons?sz=128&domain=perplexity.ai)";
  180. a.style.backgroundSize = "cover";
  181. a.href = "https://perplexity.ai/search?q=" + encodeURIComponent(query);
  182. const el = document.createElement("p");
  183. el.style.display = "flex";
  184. el.style.justifyContent = "flex-end";
  185. el.style.width = "100%";
  186. el.style.textAlign = "right";
  187. el.marginTop = "4px";
  188. el.appendChild(a);
  189. p.appendChild(el);
  190. ws.close();
  191. }
  192. });
  193. }
  194.  
  195. function main() {
  196. const search = document.location.search;
  197. const params = new URLSearchParams(search);
  198. show(params.get("q"));
  199. }
  200.  
  201. main();