AIGPT Everywhere

Mini A.I. floating menu that can define words, answer questions, translate, and much more in a single click and with your custom prompts. Includes useful click to search on Google and copy selected text buttons, along with Rocker+Mouse Gestures and Units+Currency+Time zone Converters, all features can be easily modified or disabled.

当前为 2024-11-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AIGPT Everywhere
  3. // @namespace OperaBrowserGestures
  4. // @description Mini A.I. floating menu that can define words, answer questions, translate, and much more in a single click and with your custom prompts. Includes useful click to search on Google and copy selected text buttons, along with Rocker+Mouse Gestures and Units+Currency+Time zone Converters, all features can be easily modified or disabled.
  5. // @version 74
  6. // @author hacker09
  7. // @include *
  8. // @exclude https://accounts.google.com/v3/signin/*
  9. // @icon https://i.imgur.com/8iw8GOm.png
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_getResourceText
  12. // @grant GM.xmlHttpRequest
  13. // @grant GM_setClipboard
  14. // @grant GM_deleteValue
  15. // @grant GM_openInTab
  16. // @grant window.close
  17. // @run-at document-end
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @connect google.com
  21. // @connect generativelanguage.googleapis.com
  22. // @resource AIMenuContent https://hacker09.glitch.me/AIMenu.html
  23. // @require https://update.greasyfork.org/scripts/506699/1440902/marked.js
  24. // ==/UserScript==
  25.  
  26. /* jshint esversion: 11 */
  27.  
  28. const BypassTT = window.trustedTypes?.createPolicy('BypassTT', { createHTML: HTML => HTML }); //Bypass trustedTypes
  29.  
  30. if ((location.href === 'https://aistudio.google.com/app/apikey' && document.querySelector(".apikey-link") !== null) && GM_getValue("APIKey") === undefined || GM_getValue("APIKey") === null || GM_getValue("APIKey") === '') { //Set up the API Key
  31. window.onload = setTimeout(function() {
  32. document.querySelectorAll(".apikey-link")[1].click(); //Click on the API Key
  33. setTimeout(function() {
  34. GM_setValue("APIKey", document.querySelector(".apikey-text").innerText); //Store the API Key
  35. (GM_getValue("APIKey") !== undefined && GM_getValue("APIKey") !== null && GM_getValue("APIKey") !== '') ? alert('API Key automatically added!') : alert('Failed to automatically add API Key!');
  36. }, 500);
  37. }, 1000);
  38. }
  39.  
  40. if (GM_getValue("SearchHighlight") === undefined) { //Set up everything on the first run
  41. GM_setValue("SearchHighlight", true);
  42. GM_setValue("MouseGestures", true);
  43. GM_setValue("TimeConverter", true);
  44. GM_setValue("UnitsConverter", true);
  45. GM_setValue("CurrenciesConverter", true);
  46. }
  47.  
  48. function ToggleFeature(feature)
  49. {
  50. GM_setValue(feature, GM_getValue(feature) === true ? false : true);
  51. location.reload();
  52. }
  53.  
  54. //Mouse Gestures__________________________________________________________________________________________________________________________________________________________________________________
  55. GM_registerMenuCommand(`${GM_getValue("MouseGestures") ? "Disable" : "Enable"} Mouse Gestures`, function() { ToggleFeature("MouseGestures"); });
  56.  
  57. if (GM_getValue("MouseGestures") === true) //If the MouseGestures is enabled
  58. {
  59. var link;
  60.  
  61. document.querySelectorAll('a').forEach(el => {
  62. el.addEventListener('mouseover', function() {
  63. link = this.href; //Store the hovered link
  64. });
  65.  
  66. el.addEventListener('mouseout', () => {
  67. const previousLink = link; //Save the hovered link
  68. setTimeout(() => {
  69. if (previousLink === link) { //Check if the same link is still hovered
  70. link = 'about:newtab'; //Open a new tab
  71. }
  72. }, 200);
  73. });
  74. });
  75.  
  76. const funcs = { //Store the MouseGestures functions
  77.  
  78. 'DL': function() { //Detect the Down+Left movement
  79. GM_openInTab(location.href, { incognito: true, });
  80. window.top.close();
  81. },
  82.  
  83. 'L': function() { //Detect the Left movement
  84. window.history.back();
  85. },
  86.  
  87. 'R': function() { //Detect the Right movement
  88. window.history.forward();
  89. },
  90.  
  91. 'D': function(e) { //Detect the Down movement
  92. if (e.shiftKey) {
  93. open(link, '_blank', 'height=' + screen.height + ',width=' + screen.width);
  94. }
  95. else {
  96. GM_openInTab(link, { active: true, insert: true, setParent: true });
  97. }
  98. },
  99.  
  100. 'UD': function() { //Detect the Up+Down movement
  101. location.reload();
  102. },
  103.  
  104. 'DR': function(e) { //Detect the Down+Right movement
  105. top.close();
  106. e.preventDefault();
  107. e.stopPropagation();
  108. },
  109.  
  110. 'DU': function() { //Detect the Down+Up movement
  111. GM_openInTab(link, { active: false, insert: true, setParent: true });
  112. }
  113. };
  114.  
  115. //Math codes to track the mouse movement gestures
  116. var x, y, path;
  117. const TOLERANCE = 3;
  118. const SENSITIVITY = 3;
  119. const s = 1 << ((7 - SENSITIVITY) << 1);
  120. const t1 = Math.tan(0.15708 * TOLERANCE),t2 = 1 / t1;
  121.  
  122. const tracer = function(e) {
  123. var cx = e.clientX, cy = e.clientY, deltaX = cx - x, deltaY = cy - y, distance = deltaX * deltaX + deltaY * deltaY;
  124. if (distance > s) {
  125. var slope = Math.abs(deltaY / deltaX), direction = '';
  126. if (slope > t1) {
  127. direction = deltaY > 0 ? 'D' : 'U';
  128. } else if (slope <= t2) {
  129. direction = deltaX > 0 ? 'R' : 'L';
  130. }
  131. if (path.charAt(path.length - 1) !== direction) {
  132. path += direction;
  133. }
  134. x = cx;
  135. y = cy;
  136. }
  137. };
  138.  
  139. window.addEventListener('mousedown', function(e) {
  140. if (e.which === 3) {
  141. x = e.clientX;
  142. y = e.clientY;
  143. path = "";
  144. window.addEventListener('mousemove', tracer, false); //Detect the mouse position
  145. }
  146. }, false);
  147.  
  148. window.addEventListener('contextmenu', function(e) { //When the right click BTN is released
  149. window.removeEventListener('mousemove', tracer, false); //Track the mouse movements
  150. if (path !== "") {
  151. e.preventDefault();
  152. if (funcs.hasOwnProperty(path)) {
  153. funcs[path](e);
  154. }
  155. }
  156. }, false);
  157. }
  158.  
  159. //Rocker Mouse Gestures___________________________________________________________________________________________________________________________________________________________________________
  160. GM_registerMenuCommand(`${GM_getValue("RockerMouseGestures") ? "Disable" : "Enable"} Rocker Gestures`, function() { ToggleFeature("RockerMouseGestures"); });
  161.  
  162. if (GM_getValue("RockerMouseGestures") === true) //If the RockerMouseGestures is enabled
  163. {
  164. const mouseState = { 0: false, 2: false }; //0: Left, 2: Right
  165.  
  166. window.addEventListener("mouseup", function(e) {
  167. mouseState[e.button] = false; //Update the state for the released button
  168.  
  169. if (mouseState[0] && !mouseState[2]) { //Left clicked, Right released
  170. history.back();
  171. } else if (mouseState[2] && !mouseState[0]) { //Right clicked, Left released
  172. history.forward();
  173. }
  174. }, false);
  175.  
  176. window.addEventListener("mousedown", function(e) {
  177. mouseState[e.button] = true; //Update the state for the pressed button
  178. }, false);
  179. }
  180.  
  181. //Search HighLight + Time + Currencies + Units Converters + Search HighLight + AI menus___________________________________________________________________________________________________________
  182. GM_registerMenuCommand(`${GM_getValue("SearchHighlight") ? "Disable" : "Enable"} Search Highlight`, function() { ToggleFeature("SearchHighlight"); });
  183.  
  184. if (GM_getValue("SearchHighlight") === true) //If the SearchHighlight is enabled
  185. {
  186. var SelectedText;
  187. const Links = new RegExp(/\.org|\.ly|\.net|\.co|\.tv|\.me|\.biz|\.club|\.site|\.br|\.gov|\.io|\.ai|\.jp|\.edu|\.au|\.in|\.it|\.ca|\.mx|\.fr|\.tw|\.il|\.uk|\.zoom\.us|\.youtu\.be|\.com|\.us|\.de|\.cn|\.ru|\.es|\.ch|\.nl|\.se|\.no|\.dk|\.fi|\.pl|\.tr|\.xyz|\.za/i);
  188.  
  189. document.body.addEventListener('mouseup', async function() { //When the user releases the mouse click after selecting something
  190. HtmlMenu.style.display = 'block'; //Display the container div
  191. SelectedText = getSelection().toString().trim(); //Store the selected text
  192. shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ''; //Remove the previous Units/Currency text
  193.  
  194. function ShowConvertion(UnitORCurrency, Type, Result) {
  195. shadowRoot.querySelector("#SearchBTN span")?.remove(); //Return previous HTML
  196. shadowRoot.querySelector("#SearchBTN").innerHTML = (html => BypassTT?.createHTML(html) || html)('<span class="GreyBar">│ </span>' + shadowRoot.querySelector("#SearchBTN").innerHTML);
  197.  
  198. if (UnitORCurrency === 'Currencies') {
  199. const hasSymbol = SelectedText.match(Currencies)[2].match(CurrencySymbols) !== null;
  200. const currencyFormat = Intl.NumberFormat(navigator.language, {
  201. style: 'currency',
  202. currency: GM_getValue("YourLocalCurrency")
  203. }).format(Result);
  204.  
  205. const displayText = hasSymbol ? (Type + ' 🠂 ' + currencyFormat) : currencyFormat;
  206. shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(displayText);
  207. }
  208. else
  209. {
  210. UnitORCurrency === 'Units' ? shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(`${Result} ${Type}`) : shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(Result); //Show the converted time results
  211. }
  212.  
  213. setTimeout(() => { //Wait for Units to show up to get the right offsetWidth
  214. const offsetWidth = shadowRoot.querySelector("#ShowCurrencyORUnits").offsetWidth; //Store the current menu size
  215. shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseover = function() { //When the mouse hovers the unit/currency
  216. shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(`Copy`);
  217. shadowRoot.querySelector("#ShowCurrencyORUnits").style.display = 'inline-flex';
  218. shadowRoot.querySelector("#ShowCurrencyORUnits").style.width = `${offsetWidth}px`; //Maintain the aspect ratio
  219. };
  220. }, 0);
  221.  
  222. const htmlcode = shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML; //Save the converted unit/currency value
  223. shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseout = function() { //When the mouse leaves the unit/currency
  224. shadowRoot.querySelector("#ShowCurrencyORUnits").style.width = ''; //Return the original aspect ratio
  225. shadowRoot.querySelector("#ShowCurrencyORUnits").style.display = ''; //Return the original aspect ratio
  226. shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(htmlcode); //Return the previous html
  227. };
  228.  
  229. shadowRoot.querySelector("#ShowCurrencyORUnits").onclick = function() { //When the unit/currency is clicked
  230. UnitORCurrency.match(/Units|Time/) ? GM_setClipboard(`${Result} ${Type}`) : GM_setClipboard(Intl.NumberFormat(navigator.language, { style: 'currency', currency: GM_getValue("YourLocalCurrency") }).format(Result));
  231. };
  232. }
  233.  
  234. function Get(url) { //Get the final converted time/currency value
  235. return new Promise(resolve => GM.xmlHttpRequest({
  236. method: "GET",
  237. url: url,
  238. onload: response => resolve(new DOMParser().parseFromString(response.responseText, 'text/html'))
  239. }));
  240. }
  241.  
  242. //Time Converter______________________________________________________________________________________________________________________________________________________________________________
  243. GM_registerMenuCommand(`${GM_getValue("TimeConverter") ? "Disable" : "Enable"} Time Converter`, function() { ToggleFeature("TimeConverter"); });
  244. const time = new RegExp(/^[ \t\xA0]*(?=.*?(\d{1,2}:\d{2}(?::\d{2})?\s?(?:[aApP]\.?[mM]\.?)?)|\d{1,2}(?::\d{2}(?::\d{2})?)?\s?(?:[aApP]\.?[mM]\.?)?)(?=.*?(PST|PDT|MST|MDT|CST|CDT|EST|EDT|AST|ADT|NST|NDT|GMT|BST|MET|CET|CEST|EET|EEST|WET|WEST|JST|KST|IST|MSK|UTC))(?:\1[ \t\xA0]*\2|\2[ \t\xA0]*\1)[ \t\xA0]*$/i);
  245.  
  246. if (GM_getValue("TimeConverter") === true && SelectedText.match(time) !== null) //If the TimeConverter is enabled and if the selected text is a time
  247. {
  248. const timeResponse = await Get(`https://www.google.com/search?q=${SelectedText.match(time)[0].replace("WET", "Western European Time")} to local time`);
  249. const ConvertedTime = timeResponse.querySelector(".aCAqKc")?.innerText;
  250. const Secs = SelectedText.match(time)[0].match(/(?:\d{1,2}:\d{2}(:\d{2})\s?[ap]\.?m)/i)?.[1] || '';
  251. ConvertedTime && ShowConvertion('Time', '', ConvertedTime.replace(/(\d{1,2}):(\d{2})\s?([pP][mM])/, (_, h, m) => `${(h % 12 + 12) % 24}:${m}`).match(/[\d:]+/g)[0] + Secs); //Convert PM to 24-hour format if we got the final time convertion value
  252. }
  253.  
  254. //Currencies Converter________________________________________________________________________________________________________________________________________________________________________
  255. GM_registerMenuCommand(`${GM_getValue("CurrenciesConverter") ? "Disable" : "Enable"} Currencies Converter`, function() { ToggleFeature("CurrenciesConverter"); });
  256. const CurrencySymbols = new RegExp(/AU\$|HK\$|US\$|\$US|R\$|\$|¥|€|Rp|Kč|kr(?!w)|zł|£|฿|₩|лв|₪|円|₱|₽|руб|lei|Fr|krw|RON|TRY|₿|Br|₾|₴|₸|₺/i);
  257. const Currencies = new RegExp(/^[ \t\xA0]*\$?(?=.*?(\d+(?:.*\d+)?))(?=(?:\1[ \t\xA0]*)?(Dólares|dolares|dólares|dollars?|AU\$?D?|BGN|BRL|BCH|BTC|BYN|CAD|CHF|Fr|CNY|CZK|DKK|EUR|EGP|ETH|GBP|GEL|HKD|HUF|IDR|ILS|INR|JPY|LTC|KRW|MXN|NOK|NZD|PHP|PLN|RON|RUB|SEK|SGD|THB|TRY|USD|UAH|ZAR|KZT|YTL|\$|R\$|HK\$|US\$|\$US|¥|€|Rp|Kč|kr|krw|zł|£|฿|₩|лв|₪|円|₱|₽|руб|lei|Kč|₿|Br|₾|₴|₸|₺))(?:\1[ \t\xA0]*\2|\2[ \t\xA0]*\1)[ \t\xA0]*$/i); //https://regex101.com/r/6vTbtv/20 Davidebyzero
  258.  
  259. if (GM_getValue("CurrenciesConverter") === true && SelectedText.match(Currencies) !== null) { //If Currencies Converter is enabled and if the selected text is a currency
  260. if (GM_getValue("YourLocalCurrency") === undefined) {
  261. const UserInput = prompt('Write your local currency.\nThe script will always use your local currency to make exchange-rate conversions.\n\n*Currency input examples:\nBRL\nCAD\nUSD\netc...\n\n*Press OK');
  262. GM_setValue("YourLocalCurrency", UserInput);
  263. }
  264. const currencyMap = { 'AU$': 'AUD', '$': 'USD', 'us$': 'USD', '$us': 'USD', 'r$': 'BRL', 'hk$': 'HKD', '¥': 'JPY', '€': 'EUR', 'rp': 'IDR', 'kč': 'CZK', 'kr': 'NOK', 'zł': 'PLN', '£': 'GBP', '฿': 'THB', '₩': 'KRW', 'лв': 'BGN', '₪': 'ILS', '円': 'JPY', '₱': 'PHP', '₽': 'RUB', 'руб': 'RUB', 'lei': 'RON', 'ron': 'Romanian Leu', 'krw': 'KRW', 'fr': 'CHF', '₿': 'BTC', 'Br': 'BYN', '₾': 'GEL', '₴': 'UAH', '₸': 'KZT', '₺': 'YTL', 'try': 'Turkish Lira' };
  265. const CurrencySymbol = currencyMap[SelectedText.match(CurrencySymbols)?.[0].toUpperCase()] || SelectedText.match(Currencies)[2]; //Store the currency symbol
  266.  
  267. const currencyResponse = await Get(`https://www.google.com/search?q=${SelectedText.match(Currencies)[1].replaceAll(/\.|,/g, '')} ${CurrencySymbol} in ${GM_getValue("YourLocalCurrency")}`);
  268. const FinalCurrency = parseFloat(currencyResponse.querySelector(".SwHCTb, .pclqee").innerText.split(' ')[0].replaceAll(',', '')); //Store the FinalCurrency and erase all commas
  269. ShowConvertion('Currencies', CurrencySymbol, FinalCurrency);
  270. }
  271.  
  272. //Units Converter_____________________________________________________________________________________________________________________________________________________________________________
  273. GM_registerMenuCommand(`${GM_getValue("UnitsConverter") ? "Disable" : "Enable"} Units Converter`, function() { ToggleFeature("UnitsConverter"); });
  274. const conversionMap = {};
  275. const Units = new RegExp(/^[ \t\xA0]*(-?\d+(?:[., ]\d+)?)(?:[ \t\xA0]*x[ \t\xA0]*(-?\d+(?:[., ]\d+)?))?[ \t\xA0]*(in|inch|inches|"|”|″|cm|cms|centimeters?|m|mt|mts|meters?|ft|kg|lbs?|pounds?|kilograms?|ounces?|g|ozs?|fl oz|fl oz (us)|fluid ounces?|kphs?|km\/h|kilometers per hours?|mhp|mphs?|meters per hours?|(?:°\s?|º\s?|)(?:degrees?\s+)?(?:celsius|fahrenheit|[CF])|km\/hs?|ml|milliliters?|l|liters?|litres?|gal|gallons?|yards?|yd|Millimeter|millimetre|kilometers?|mi|mm|miles?|km|ft|fl|feets?|grams?|kilowatts?|kws?|brake horsepower|mechanical horsepower|hps?|bhps?|miles per gallons?|mpgs?|liters per 100 kilometers?|lt?\/100km|liquid quarts?|lqs?|qt|foot-? ?pounds?|ft-?lbs?|lb fts?|newton-? ?meters?|n·?m|\^\d+)[ \t\xA0]*(?:\(\w+\)[ \t\xA0]*)?$/i); //https://regex101.com/r/ObpIn5/9 Davidebyzero
  276.  
  277. function addConversion(keys, unit, factor, convert) { //Helper function to add multiple keys with the same value
  278. keys.forEach(key => { conversionMap[key] = { unit, factor, convert }; });
  279. }
  280.  
  281. if (GM_getValue("UnitsConverter") === true && SelectedText.match(Units) !== null) { //If the Units Converter option is enabled and if the selected text is an unit
  282. addConversion(['inch', 'inches', 'in', '"', '”', '″'], 'cm', 2.54);
  283. addConversion(['centimeter', 'centimeters', 'cm', 'cms'], 'in', 1/2.54);
  284. addConversion(['meter', 'meters', 'm', 'mt', 'mts'], 'ft', 3.281);
  285. addConversion(['kilogram', 'kilograms', 'kg'], 'lb', 2.205);
  286. addConversion(['pound', 'pounds', 'lb', 'lbs'], 'kg', 1/2.205);
  287. addConversion(['ounce', 'ounces', 'oz', 'ozs'], 'g', 28.35);
  288. addConversion(['gram', 'grams', 'g'], 'oz', 1/28.35);
  289. addConversion(['kilometer', 'kilometers', 'km'], 'mi', 1/1.609);
  290. addConversion(['kph', 'kphs', 'km/h', 'km/hs', 'kilometers per hour', 'kilometers per hours'], 'mph', 0.621371);
  291. addConversion(['mph', 'mphs', 'meters per hour', 'meters per hours'], 'km/h', 1.609);
  292. addConversion(['mi', 'mile', 'miles'], 'km', 1.609);
  293. addConversion(['°c', 'ºc', 'celsius', 'degrees celsius', '° celsius', 'º celsius'], '°F', null, v => (v*9/5)+32);
  294. addConversion(['°f', 'ºf', 'fahrenheit', 'degrees fahrenheit', '° fahrenheit', 'º fahrenheit'], '°C', null, v => (v-32)*5/9);
  295. addConversion(['milliliter', 'milliliters', 'ml'], 'fl oz (US)', 1/29.574);
  296. addConversion(['fl oz (US)', 'fl oz', 'fl', 'fluid ounce', 'fluid ounces'], 'ml', 29.574);
  297. addConversion(['litre', 'liter', 'litres', 'liters', 'l'], 'gal (US)', 1/3.785);
  298. addConversion(['gal', 'gallon', 'gallons'], 'l', 3.785);
  299. addConversion(['yard', 'yards', 'yd'], 'm', 1/1.094);
  300. addConversion(['millimetre', 'millimeters', 'millimetres', 'mm'], 'in', 1/25.4);
  301. addConversion(['feet', 'feets', 'ft'], 'm', 0.3048);
  302. addConversion(['kilowatt', 'kilowatts', 'kw', 'kws'], 'hp', 1.341);
  303. addConversion(['mhp', 'mhps', 'hp', 'hps', 'brake horsepower', 'mechanical horsepower'], 'kW', 1/1.341);
  304. addConversion(['mpg', 'mpgs', 'miles per gallon', 'miles per gallons'], 'l/100km', null, v => 235.215/v);
  305. addConversion(['l/100km','lt/100km', 'liters per 100 kilometer', 'liters per 100 kilometers'], 'mpg (US)', null, v => 235.215/v);
  306. addConversion(['qt', 'lq', 'lqs', 'liquid quart', 'liquid quarts'], 'l', 1/1.057);
  307. addConversion(['foot-pound', 'foot-pounds', 'foot pound', 'foot pounds', 'ft-lbs', 'ft-lb', 'ft lbs', 'ft lb', 'lb ft', 'lb-ft'], 'Nm', 1.3558179483);
  308. addConversion(['nm','n·m', 'newton-meter', 'newton-meters', 'newton meter', 'newton meters'], 'lbf ft', 1/1.3558179483);
  309.  
  310. const selectedUnitType = SelectedText.match(Units)[3].toLowerCase();
  311. const SelectedUnitValue = SelectedText.match(Units)[1].replaceAll(',', '.');
  312. const SecondSelectedUnitValue = SelectedText.match(Units)[2]?.replaceAll(',', '.') || 0;
  313.  
  314. const convertValue = (value, unitType) => {
  315. const { factor, convert } = conversionMap[unitType] || {};
  316. return convert ? convert(value) : value * factor;
  317. };
  318.  
  319. var NewUnit = conversionMap[selectedUnitType]?.unit || selectedUnitType;
  320. var ConvertedUnit = SecondSelectedUnitValue != 0 ? `${convertValue(parseFloat(SelectedUnitValue), selectedUnitType).toFixed(2)} x ${convertValue(parseFloat(SecondSelectedUnitValue), selectedUnitType).toFixed(2)}` : convertValue(parseFloat(SelectedUnitValue), selectedUnitType).toFixed(2);
  321. ConvertedUnit = SelectedText.match(/\^(\d+\.?\d*)/) ? (NewUnit = 'power', Math.pow(parseFloat(SelectedUnitValue), parseFloat(SelectedText.match(/\^(\d+\.?\d*)/)[1]))) : ConvertedUnit;
  322. ShowConvertion('Units', NewUnit, ConvertedUnit);
  323. }
  324.  
  325. //Mini Menu___________________________________________________________________________________________________________________________________________________________________________________
  326. if (shadowRoot.querySelector("#SearchBTN").innerText === 'Open') //If the Search BTN text is 'Open'
  327. {
  328. shadowRoot.querySelector("#highlight_menu > ul").style.paddingInlineStart = '19px'; //Increase the menu size
  329. shadowRoot.querySelector("#SearchBTN").innerText = 'Search'; //Display the BTN text as Search again
  330. shadowRoot.querySelectorAll(".AI-BG-box button").forEach(button => { button.style.marginLeft = ''; }); //Remove the margin left
  331. shadowRoot.querySelector("#OpenAfter").remove(); //Remove the custom Open white hover overlay
  332. }
  333.  
  334. if (SelectedText.match(Links) !== null) //If the selected text is a link
  335. {
  336. shadowRoot.querySelector("#highlight_menu > ul").style.paddingInlineStart = '27px'; //Increase the menu size
  337. shadowRoot.querySelector("#SearchBTN").innerText = 'Open'; //Change the BTN text to Open
  338. shadowRoot.querySelectorAll(".AI-BG-box button").forEach(button => { button.style.marginLeft = '-2%'; }); //Add a margin left
  339. shadowRoot.innerHTML += (html => BypassTT?.createHTML(html) || html)(` <style id="OpenAfter"> #SearchBTN::after { width: 177% !important; transform: translate(-34%, -71%) !important; } </style> `); //Add a custom Open white hover overlay
  340. }
  341.  
  342. shadowRoot.querySelector("#SearchBTN").onmousedown = function() {
  343. GM_openInTab(SelectedText.match(Links) !== null ? SelectedText.replace(/^(?!https?:\/\/)(.+)$/, 'https://$1') : 'https://www.google.com/search?q=' + SelectedText.replaceAll('&', '%26').replace(/\s+/g, ' '), { active: true, setParent: true, loadInBackground: true }); //Open link or google and search for the selected text
  344. shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the menu
  345. };
  346.  
  347. const menu = shadowRoot.querySelector("#highlight_menu");
  348. if (document.getSelection().toString().trim() !== '') { //If text has been selected
  349. const p = document.getSelection().getRangeAt(0).getBoundingClientRect(); //Store the selected position
  350.  
  351. menu.classList.add('show'); //Show the menu
  352. menu.offsetHeight; //Trigger reflow by forcing a style calculation
  353. menu.style.left = p.left + (p.width / 2) - (menu.offsetWidth / 2) + 'px';
  354. menu.style.top = p.top - menu.offsetHeight - 10 + 'px';
  355. menu.classList.add('highlight_menu_animate');
  356.  
  357. return; //Keep the menu open
  358. }
  359. menu.classList.remove('show'); //Hide the menu
  360. shadowRoot.querySelector("#SearchBTN span")?.remove(); //Return previous HTML
  361. }); //Finishes the mouseup event listener
  362.  
  363. //AI Menu_______________________________________________________________________________________________________________________________________________________________________________________
  364. var desiredVoice = null, isRecognizing = false;
  365. const HtmlMenu = document.createElement('div'); //Create a container div
  366. HtmlMenu.setAttribute('style', `width: 0px; height: 0px; display: none;`); //Hide the container div by default
  367. const shadowRoot = HtmlMenu.attachShadow({ mode: 'closed' });
  368. const UniqueLangs = navigator.languages.filter((l, i, arr) => !arr.slice(0, i).some(e => e.split('-')[0].toLowerCase() === l.split('-')[0].toLowerCase()) ); //Filter unique languages
  369. const Lang = UniqueLangs.length > 1 ? `${UniqueLangs[0]} and into ${UniqueLangs[1]}` : UniqueLangs[0]; //Use 1 or 2 languages
  370.  
  371. shadowRoot.innerHTML = (html => BypassTT?.createHTML(html) || html)(GM_getResourceText("AIMenuContent")); //Set the AI menu HTML+CSS
  372.  
  373. function Generate(Prompt, button) { //Call the AI endpoint
  374. const context = !!shadowRoot.querySelector("#context.show") ? `(You're not allowed to say anything like "Based on the provided text")\n"${Prompt} mainly base yourself on the text below\n${document.body.innerText}` : Prompt; //Add the page context if context is enabled
  375. const isQuestion = Prompt.includes('?') ? 'Give me a very short, then a long detailed answer' : 'Help me further understand/learn a term or topic from the text/word';
  376. const isWord = Prompt.split(' ').length < 5 ? `After showing (in order) "${Prompt}", a few possible "Synonyms: (Don't use bullet points for Synonyms)", "Definition:" and "Example:".` : '';
  377. const AIFunction = button.match('translate') ? `(You're not allowed to say anything like (The text is already in ${UniqueLangs[0]}\nNo translation is needed).\Translate into ${Lang} the following text:\n"${Prompt}".\n${isWord}${UniqueLangs.length > 1 ? `You must answer using only 1 language first, then use only the other language, don't mix both languages! ${Prompt} should be translated for the 2nd language` : ''}` : button.match('Prompt') ? context : `(PS*I'm unable to provide you with more context, so don't ask for it! Also, don't mention that I haven't provided context or anything similar to it!). ${isQuestion}: "${Prompt}"`; //AI prompts
  378. const msg = button.match('translate') ? `Translate this text: "${Prompt.length > 215 ? Prompt.trim().slice(0, 215) + '…' : Prompt.trim()}"` : button.match('Prompt') ? Prompt.length > 240 ? Prompt.trim().slice(0, 240) + '…' : Prompt.trim() : `Help me further explore a term or topic from the text: "${Prompt.length > 180 ? Prompt.trim().slice(0, 180) + '…' : Prompt.trim()}"`; //User text
  379.  
  380. function handleState(state) { //Show #AIMenu + #dictate but hide #TopPause for the load/abort states. Do the opposite for the 'start' state.
  381. ["#AIMenu", "#dictate", "#TopPause"].forEach(el => {
  382. if (el === "#TopPause") {
  383. shadowRoot.querySelector(el).classList[state !== 'start' ? 'remove' : 'add']('show');
  384. } else {
  385. shadowRoot.querySelector(el).classList[state !== 'start' ? 'add' : 'remove']('show');
  386. }
  387. });
  388. }
  389.  
  390. const Generating = setInterval(function() { //Start the interval to change the text
  391. if (shadowRoot.querySelector("#finalanswer").innerText === 'ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ▋') { //Start the if condition
  392. shadowRoot.querySelector("#finalanswer").innerText = 'ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ'; //Remove ▋
  393. } else { //Toggle between showing and hiding ▋
  394. shadowRoot.querySelector("#finalanswer").innerText = 'ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ▋'; //Add ▋
  395. }
  396. }, 200);
  397.  
  398. const request = GM.xmlHttpRequest({ //Call the AI API
  399. method: "POST",
  400. url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:streamGenerateContent?key=${GM_getValue("APIKey")}`,
  401. responseType: 'stream',
  402. headers: {
  403. "Content-Type": "application/json"
  404. },
  405. data: JSON.stringify({
  406. contents: [{
  407. parts: [{
  408. text: `${AIFunction}` //Use the AI prompt
  409. }]
  410. }],
  411. safetySettings: [ //Allow all content
  412. { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
  413. { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
  414. { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
  415. { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" }
  416. ],
  417. }),
  418. onerror: function(err) {
  419. clearInterval(Generating); //Stop showing ▋
  420. shadowRoot.querySelector("#msg").innerHTML = 'Error';
  421. shadowRoot.querySelector("#finalanswer").innerHTML = (html => BypassTT?.createHTML(html) || html)(`<br>Please copy and paste the error below:<br><a class="feedback" href="https://greasyfork.org/scripts/419825/feedback">Click here to report this bug</a><br><br> Prompt: ${Prompt}<br> Button: ${button}<br> Error: <pre>${JSON.stringify(err, null, 2)}</pre><br><br><br>`);
  422. },
  423. onload: function(response) {
  424. handleState('load');
  425. },
  426. onabort: function(response) {
  427. handleState('abort');
  428. clearInterval(Generating); //Stop showing ▋
  429. shadowRoot.querySelector("#finalanswer").innerHTML = (html => BypassTT?.createHTML(html) || html)('<div>ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤResponse has been interrupted.<div>');
  430. },
  431. onloadstart: function(response) {
  432. handleState('start');
  433. shadowRoot.querySelector("#prompt").focus();
  434. shadowRoot.querySelector("#msg").innerHTML = msg;
  435.  
  436. shadowRoot.querySelector("#copyAnswer").onclick = function() {
  437. shadowRoot.querySelector("#copyAnswer").style.display = 'none';
  438. shadowRoot.querySelector("#AnswerCopied").style.display = 'inline-flex';
  439. GM_setClipboard(shadowRoot.querySelector("#finalanswer").innerText.replace('ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ', ''));
  440. setTimeout(() => { //Return play BTN svg
  441. shadowRoot.querySelector("#copyAnswer").style.display = 'inline-flex';
  442. shadowRoot.querySelector("#AnswerCopied").style.display = 'none';
  443. }, 1000);
  444. };
  445.  
  446. const reader = response.response.getReader();
  447. const decoder = new TextDecoder();
  448. var buffer = '', partialMarkdown = '';
  449.  
  450. function readStream() {
  451. reader.read().then(({ value }) => {
  452. buffer += decoder.decode(value, { stream: true });
  453. var startIdx = 0;
  454. while (true) {
  455. const openBrace = buffer.indexOf('{', startIdx);
  456. if (openBrace === -1) break;
  457. var balance = 1, closeBrace = openBrace + 1;
  458.  
  459. while (balance > 0 && closeBrace < buffer.length) {
  460. if (buffer[closeBrace] === '{') balance++;
  461. if (buffer[closeBrace] === '}') balance--;
  462. closeBrace++;
  463. }
  464.  
  465. if (balance !== 0) break; //Incomplete JSON object
  466.  
  467. const jsonString = buffer.substring(openBrace, closeBrace);
  468. const item = JSON.parse(jsonString);
  469. clearInterval(Generating); //Stop showing ▋
  470. partialMarkdown += item.candidates ? item.candidates[0].content.parts[0].text : item.error.message; //If there's no error show the AI answer, else show the error message
  471.  
  472. const tempDiv = document.createElement('div');
  473. tempDiv.innerHTML = window.marked.parse(partialMarkdown);
  474.  
  475. shadowRoot.querySelector("#finalanswer").innerHTML = '';
  476. shadowRoot.querySelector("#finalanswer").appendChild(tempDiv);
  477. startIdx = closeBrace;
  478. }
  479. buffer = buffer.substring(startIdx);
  480. readStream();
  481. });
  482. }
  483.  
  484. readStream();
  485.  
  486. shadowRoot.querySelector("#CloseOverlay").classList.add('show');
  487. shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the mini menu on the page
  488. shadowRoot.querySelectorAll("#AIBox, .animated-border, #AIBox.AnswerBox").forEach(el => el.classList.add('show')); //Show the AI input and box
  489.  
  490. var SpeechRecognition = SpeechRecognition || window.webkitSpeechRecognition;
  491. const recognition = new SpeechRecognition();
  492. recognition.interimResults = true; //Show partial results
  493. recognition.continuous = true; //Keep listening until stopped
  494.  
  495. var transcript = ""; //Add words
  496. shadowRoot.querySelector("#CloseOverlay").onclick = function() {
  497. [...shadowRoot.querySelector("#finalanswer div").childNodes].slice(0, -1).forEach(node => node.remove()); //Reset the text content
  498. shadowRoot.querySelectorAll("#AIBox, .animated-border, #AIBox.AnswerBox").forEach(el => el.classList.remove('show')); //Hide the AI input and box
  499. this.classList.remove('show');
  500. recognition.stop(); //Stop recognizing audio
  501. speechSynthesis.cancel(); //Stop speaking
  502. request.abort(); //Abort any ongoing request
  503. if (shadowRoot.querySelector("#gemini").style.display === 'none') {
  504. shadowRoot.querySelector("#AddContext").remove(); //Return original prompt input styles
  505. shadowRoot.querySelector("#context").classList.remove('show');
  506. shadowRoot.querySelector("#prompt").placeholder = 'Enter your prompt to Gemini'; //Return default placeholder
  507. }
  508. };
  509.  
  510. shadowRoot.querySelector("#TopPause").onclick = function() {
  511. request.abort();
  512. };
  513.  
  514. recognition.onend = function() {
  515. isRecognizing = false;
  516. shadowRoot.querySelectorAll('.state1, .state2, .state3').forEach((state, index) => { //ForEach SVG animation state
  517. index.toString().match(/1|2/) ? state.style.display = 'none' : ''; //Show only the 1 state
  518. state.classList.remove('animate'+index); //Stop the voice recording animation
  519. });
  520.  
  521. transcript !== '' ? Generate(transcript, shadowRoot.querySelector("#prompt").className) : shadowRoot.querySelector("#finalanswer").innerHTML = (html => BypassTT?.createHTML(html) || html)(`<br>No audio detected. Please try again or check your mic settings.ㅤㅤㅤㅤㅤㅤㅤㅤㅤ<br><br>`); //Call the AI API if audio has been detected or show an error message
  522. }; //Finish the recognition end event listener
  523.  
  524. recognition.onresult = function(event) { //Handle voice recognition results
  525. transcript = ""; //Clear the transcript at the start of the event
  526. for (var i = 0; i < event.results.length; i++) { //For all transcript results
  527. transcript += event.results[i][0].transcript + ' '; //Concatenate all intermediate transcripts
  528. }
  529. shadowRoot.querySelector("#msg").innerText = transcript.length > 240 ? transcript.slice(0, 240) + '…' : transcript; //Display recognized words
  530. };
  531.  
  532. shadowRoot.querySelector("#dictate").onclick = function() {
  533. if (isRecognizing) {
  534. recognition.stop();
  535. } else {
  536. isRecognizing = true;
  537. recognition.start();
  538. shadowRoot.querySelectorAll('.state1, .state2, .state3').forEach((state, index) => { //ForEach SVG animation state
  539. state.style.display = 'unset'; //Show all states
  540. state.classList.add('animate'+index); //Start the voice recording animation
  541. });
  542. }
  543. };
  544.  
  545. speechSynthesis.onvoiceschanged = () => desiredVoice = speechSynthesis.getVoices().find(v => v.name === "Microsoft Zira - English (United States)"); //Find and store the desired voice
  546. speechSynthesis.onvoiceschanged(); //Handle cases where the event doesn't fire
  547.  
  548. shadowRoot.querySelectorAll("#speak, #SpeakingPause").forEach(function(el) {
  549. el.onclick = function() { //When the speak or the bottom pause BTNs are clicked
  550. if (speechSynthesis.speaking) {
  551. speechSynthesis.cancel();
  552. shadowRoot.querySelector("#speak").style.display = 'inline-flex'; //Show the play BTN
  553. shadowRoot.querySelector("#SpeakingPause").classList.remove('show'); //Hide the pause BTN
  554. }
  555. else
  556. {
  557. shadowRoot.querySelector("#speak").style.display = 'none'; //Hide the play BTN
  558. shadowRoot.querySelector("#SpeakingPause").classList.add('show');
  559.  
  560. var audio = new SpeechSynthesisUtterance(shadowRoot.querySelector("#finalanswer").innerText.replace(/.*?\(..-..\)|[^\p{L}\p{N}\s%.,!?]/gui, '')); //Play the AI response text, removing non-alphanumeric characters and lang locales for better pronunciation
  561. audio.voice = desiredVoice; //Use the desiredVoice
  562. speechSynthesis.speak(audio); //Speak the text
  563.  
  564. audio.onend = (event) => {
  565. shadowRoot.querySelector("#speak").style.display = 'inline-flex'; //Show the play BTN
  566. shadowRoot.querySelector("#SpeakingPause").classList.remove('show');
  567. };
  568. }
  569. };
  570. });
  571.  
  572. shadowRoot.querySelector("#NewAnswer").onclick = function() {
  573. recognition.stop(); //Stop recognizing audio
  574. speechSynthesis.cancel(); //Stop speaking
  575. Generate(Prompt, button); //Call the AI API
  576. };
  577. } //Finishes the onloadstart event listener
  578. });//Finishes the GM.xmlHttpRequest function
  579. } //Finishes the Generate function
  580.  
  581. shadowRoot.querySelector("#prompt").addEventListener("keydown", (event) => {
  582. if (event.key === "Enter") {
  583. Generate(shadowRoot.querySelector("#prompt").value, shadowRoot.querySelector("#prompt").className); //Call the AI API
  584. shadowRoot.querySelector("#prompt").value = ''; //Erase the prompt text
  585. }
  586. if (event.key === "Tab") {
  587. if (shadowRoot.querySelector("#prompt").placeholder.match('using')) { //If the input bar contains the word "using"
  588. shadowRoot.querySelector("#AddContext").remove(); //Return original prompt input styles
  589. shadowRoot.querySelector("#context").classList.remove('show'); //Hide the context view
  590. shadowRoot.querySelector("#prompt").placeholder = 'Enter your prompt to Gemini'; //Return default placeholder
  591. }
  592. else
  593. {
  594. shadowRoot.querySelector("#context").classList.add('show'); //Show the context view
  595. shadowRoot.querySelector("#prompt").placeholder = `Gemini is using ${location.host.replace('www.','')} for context...`; //Change placeholder
  596. shadowRoot.querySelector("#highlight_menu").insertAdjacentHTML('beforebegin', ` <style id="AddContext"> #gemini { display: none; } #prompt { left: 12%; width: 75%; } #tabcontext { display: none; } .animated-border { --color-OrangeORLilac: #FF8051; /* Change the border effect color to orange */ } </style> `); //Show the context bar
  597. }
  598. }
  599. setTimeout(() => { //Wait for the code above to execute
  600. shadowRoot.querySelector("#prompt").focus(); //Refocus on the input bar
  601. }, 0);
  602. });
  603.  
  604. shadowRoot.querySelectorAll("#AIBTN").forEach(function(button) {
  605. button.onmousedown = function(event, i) { //When the Explore or the Translate BTNs are clicked
  606. if (GM_getValue("APIKey") === undefined || GM_getValue("APIKey") === null || GM_getValue("APIKey") === '') { //Set up the API Key if it isn't already set
  607. GM_setValue("APIKey", prompt('Enter your API key\n*Press OK\n\nYou can get a free API key at https://aistudio.google.com/app/apikey'));
  608. }
  609. if (GM_getValue("APIKey") !== null && GM_getValue("APIKey") !== '') {
  610. Generate(SelectedText, this.className); //Call the AI API
  611. }
  612. };
  613. });
  614.  
  615. if (document.body.textContent !== '' || document.body.innerText !== '') //If the body has any text
  616. {
  617. document.body.appendChild(HtmlMenu); //Add the script menu div container
  618. }
  619.  
  620. shadowRoot.querySelector('#CopyBTN').onmousedown = function() {
  621. GM_setClipboard(SelectedText);
  622. };
  623.  
  624. window.addEventListener('scroll', async function() {
  625. shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the menu
  626. });
  627. }