// ==UserScript==
// @name AI Everywhere
// @namespace OperaBrowserGestures
// @description Highly customizable 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 Converters, all features can be easily modified or disabled.
// @version 71
// @author hacker09
// @include *
// @exclude https://accounts.google.com/v3/signin/*
// @icon https://i.imgur.com/8iw8GOm.png
// @grant GM_registerMenuCommand
// @grant GM_getResourceText
// @grant GM.xmlHttpRequest
// @grant GM_setClipboard
// @grant GM_deleteValue
// @grant GM_openInTab
// @grant window.close
// @run-at document-end
// @grant GM_setValue
// @grant GM_getValue
// @connect google.com
// @connect generativelanguage.googleapis.com
// @resource AIMenuHTMLContent https://hacker09.glitch.me/AIMenuHTML.html
// @require https://update.greasyfork.org/scripts/506699/1440902/marked.js
// @downloadURL none
// ==/UserScript==
/* jshint esversion: 11 */
const BypassTT = window.trustedTypes?.createPolicy('BypassTT', { createHTML: HTML => HTML }); //Bypass trustedTypes
if (GM_getResourceText('AIMenuHTMLContent') === '') {
alert('Failed to load the HTML file resource!\n\nPlease contact your network admin to have the https://glitch.me domain unblocked.');
return; //Stop running
}
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
window.onload = setTimeout(function() {
document.querySelectorAll(".apikey-link")[1].click(); //Click on the API Key
setTimeout(function() {
GM_setValue("APIKey", document.querySelector(".apikey-text").innerText); //Store the API Key
(GM_getValue("APIKey") !== undefined && GM_getValue("APIKey") !== null && GM_getValue("APIKey") !== '') ? alert('API Key automatically added!') : alert('Failed to automatically add API Key!');
}, 500);
}, 1000);
}
//Mouse Gestures__________________________________________________________________________________________________________________________________________________________________________________
GM_registerMenuCommand("Enable/Disable Mouse Gestures", MouseGestures);
function MouseGestures() //Enable/disable MouseGestures
{
if (GM_getValue("MouseGestures") === true) {
GM_setValue("MouseGestures", false);
}
else {
GM_setValue("MouseGestures", true);
location.reload();
}
}
if (GM_getValue("MouseGestures") === true) //If the MouseGestures is enabled
{
const SENSITIVITY = 3;
const TOLERANCE = 3;
const funcs = { //Store the MouseGestures functions
'L': function() { //Detect the Left movement
window.history.back();
},
'R': function() { //Detect the Right movement
window.history.forward();
},
'D': function() { //Detect the Down movement
if (IsShiftNotPressed === true) {
GM_openInTab(link, { active: true, insert: true, setParent: true });
}
},
'UD': function() { //Detect the Up+Down movement
location.reload();
},
'DR': function(e) { //Detect the Down+Right movement
top.close();
e.preventDefault();
e.stopPropagation();
},
'DU': function() { //Detect the Down+Up movement
GM_openInTab(link, {
active: false,
insert: true,
setParent: true
});
}
};
//Math codes to track the mouse movement gestures
var x, y, path;
const s = 1 << ((7 - SENSITIVITY) << 1);
const t1 = Math.tan(0.15708 * TOLERANCE),t2 = 1 / t1;
const tracer = function(e) {
var cx = e.clientX, cy = e.clientY, deltaX = cx - x, deltaY = cy - y, distance = deltaX * deltaX + deltaY * deltaY;
if (distance > s) {
var slope = Math.abs(deltaY / deltaX), direction = '';
if (slope > t1) {
direction = deltaY > 0 ? 'D' : 'U';
} else if (slope <= t2) {
direction = deltaX > 0 ? 'R' : 'L';
}
if (path.charAt(path.length - 1) !== direction) {
path += direction;
}
x = cx;
y = cy;
}
};
window.addEventListener('mousedown', function(e) {
if (e.which === 3) {
x = e.clientX;
y = e.clientY;
path = "";
window.addEventListener('mousemove', tracer, false); //Detect the mouse position
}
}, false);
var IsShiftNotPressed = true; //Store the shift key status
window.addEventListener("contextmenu", function(e) { //When the shift key is/isn't pressed
if (e.shiftKey) {
IsShiftNotPressed = false;
open(link, '_blank', 'height=' + screen.height + ',width=' + screen.width);
}
if (LeftClicked === true) { //If the Left Click was released when the Rocker Mouse Gestures were enabled
e.preventDefault();
e.stopPropagation();
}
setTimeout(function() {
IsShiftNotPressed = true;
}, 500);
}, false);
window.addEventListener('contextmenu', function(e) { //When the right click BTN is released
window.removeEventListener('mousemove', tracer, false); //Track the mouse movements
if (path !== "") {
e.preventDefault();
if (funcs.hasOwnProperty(path)) {
funcs[path]();
}
}
}, false);
var link;
Array.from(document.querySelectorAll('a')).forEach(Element => Element.onmouseover = function() {
link = this.href; //Store the hovered link to a variable
});
Array.from(document.querySelectorAll('a')).forEach(Element => Element.onmouseout = function() {
const PreviousLink = link; //Save the hovered link
setTimeout(function() {
if (PreviousLink === link) //If the hovered link is still the same as the previously hovered Link
{
link = 'about:newtab'; //Make the script open a new browser tab when the mouse leaves any link that was hovered
}
}, 200);
});
}
//Rocker Mouse Gestures___________________________________________________________________________________________________________________________________________________________________________
GM_registerMenuCommand("Enable/Disable Rocker Mouse Gestures", RockerMouseGestures);
function RockerMouseGestures() //Enable/disable RockerMouseGestures
{
if (GM_getValue("RockerMouseGestures") === true) {
GM_setValue("RockerMouseGestures", false);
}
else {
GM_setValue("RockerMouseGestures", true);
location.reload();
}
}
if (GM_getValue("RockerMouseGestures") === true || GM_getValue("SearchHiLight") === true) //If the RockerMouseGestures or the SearchHiLight is enabled
{
var LeftClicked, RightClicked;
window.addEventListener("mousedown", function(e) { //Track which side of the mouse was the first one to be pressed
switch (e.button) {
case 0:
LeftClicked = true;
break;
case 2:
RightClicked = true;
break;
}
}, false);
window.addEventListener("mouseup", function(e) { //Track which side of the mouse was the last one to be released
switch (e.button) {
case 0:
LeftClicked = false;
break;
case 2:
RightClicked = false;
break;
}
if (LeftClicked && RightClicked === false) { //If Left was Clicked and then Right Click was released
history.back();
}
if (RightClicked && LeftClicked === false) { //If Right was Clicked and then Left Click was released
history.forward();
}
}, false);
}
//SearchHighLight + CurrenciesConverter + UnitsConverter__________________________________________________________________________________________________________________________________________
GM_registerMenuCommand("Enable/Disable SearchHiLight", SearchHiLight);
if (GM_getValue("SearchHiLight") === undefined) { //Set up everything on the first run
GM_setValue("SearchHiLight", true);
GM_setValue("MouseGestures", true);
GM_setValue("UnitsConverter", true);
GM_setValue("RockerMouseGestures", false);
GM_setValue("CurrenciesConverter", true);
}
function SearchHiLight() //Enable/disable the SearchHiLight and the Currency/Unit converters
{
if (GM_getValue("SearchHiLight") === true) {
GM_setValue("SearchHiLight", false);
GM_deleteValue('YourLocalCurrency');
GM_setValue("UnitsConverter", false);
GM_setValue("CurrenciesConverter", false);
}
else {
GM_setValue("SearchHiLight", true);
if (confirm('If you want to enable the Currency Converter press OK.'))
{
GM_setValue("CurrenciesConverter", true);
}
else
{
GM_setValue("CurrenciesConverter", false);
}
if (confirm('If you want to enable the Units Converter press OK.'))
{
GM_setValue("UnitsConverter", true);
}
else
{
GM_setValue("UnitsConverter", false);
}
location.reload();
}
}
if (GM_getValue("SearchHiLight") === true) //If the SearchHiLight is enabled
{
var SelectedTextIsLink, FinalCurrency, SelectedText, SelectedTextSearch = '';
const Links = new RegExp(/\.org|\.ly|\.net|\.co|\.tv|\.me|\.biz|\.club|\.site|\.br|\.gov|\.io|\.jp|\.edu|\.au|\.in|\.it|\.ca|\.mx|\.fr|\.tw|\.il|\.uk|\.zoom\.us|\youtu.be/i);
document.body.addEventListener('mouseup', function() { //When the user releases the mouse click after selecting something
HtmlMenu.style.display = 'block'; //Display the container div
SelectedText = getSelection().toString(); //Store the selected text
SelectedTextSearch = getSelection().toString().replaceAll('&', '%26'); //Store the selected text to be opened on Google
shadowRoot.querySelector("#ShowCurrencyORUnits").innerText = ''; //Remove the previous Units/Currency text
const CurrencySymbols = new RegExp(/\$|R\$|HK\$|US\$|\$US|¥|€|Rp|kn|Kč|kr|zł|£|฿|₩/i);
const Currencies = new RegExp(/^[ \t\xA0]*(?=.*?(\d+(?:.\d+)?))(?=(?:\1[ \t\xA0]*)?(Dólares|dolares|dólares|dollars|AUD|BGN|BRL|BCH|BTC|BYN|CAD|CHF|CNY|CZK|DKK|EUR|EGP|ETH|GBP|GEL|HKD|HRK|HUF|IDR|ILS|INR|JPY|LTC|KRW|MXN|MYR|NOK|NZD|PHP|PLN|RON|RM|RUB|SEK|SGD|THB|TRY|USD|UAH|ZAR|KZT|YTL|\$|R\$|HK\$|US\$|\$US|¥|€|Rp|kn|Kč|kr|zł|£|฿|₩))(?:\1[ \t\xA0]*\2|\2[ \t\xA0]*\1)[ \t\xA0]*$/i);
function ShowConvertion(UnitORCurrency, Type, Result) {
shadowRoot.querySelector("#SearchBTN span")?.remove(); //Return previous HTML
shadowRoot.querySelector("#SearchBTN").innerHTML = (html => BypassTT?.createHTML(html) || html)('│ ' + shadowRoot.querySelector("#SearchBTN").innerHTML);
if (UnitORCurrency === 'Currencies') {
const hasSymbol = SelectedText.match(Currencies)[2].match(CurrencySymbols) !== null;
const currencyFormat = Intl.NumberFormat(navigator.language, {
style: 'currency',
currency: GM_getValue("YourLocalCurrency")
}).format(Result);
const displayText = hasSymbol ? (Type + ' 🠂 ' + currencyFormat) : currencyFormat;
shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(displayText);
}
UnitORCurrency === 'Units' ? shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(Result + ' ' + Type) : ''; //Show the converted unit results
setTimeout(() => { //Wait for Units to show up to get the right offsetWidth
const offsetWidth = shadowRoot.querySelector("#ShowCurrencyORUnits").offsetWidth; //Store the current menu size
shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseover = function() { //When the mouse hovers the unit/currency
shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(`Copy`);
shadowRoot.querySelector("#ShowCurrencyORUnits").style.display = 'inline-flex';
shadowRoot.querySelector("#ShowCurrencyORUnits").style.width = `${offsetWidth}px`; //Maintain the aspect ratio
};
}, 0);
const htmlcode = shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML; //Save the converted unit/currency value
shadowRoot.querySelector("#ShowCurrencyORUnits").onmouseout = function() { //When the mouse leaves the unit/currency
shadowRoot.querySelector("#ShowCurrencyORUnits").style.width = ''; //Return the original aspect ratio
shadowRoot.querySelector("#ShowCurrencyORUnits").style.display = ''; //Return the original aspect ratio
shadowRoot.querySelector("#ShowCurrencyORUnits").innerHTML = (html => BypassTT?.createHTML(html) || html)(htmlcode); //Return the previous html
};
shadowRoot.querySelector("#ShowCurrencyORUnits").onclick = function() { //When the unit/currency is clicked
UnitORCurrency === 'Units' ? GM_setClipboard(`${Result} ${Type}`) : GM_setClipboard(Intl.NumberFormat(navigator.language, { style: 'currency', currency: GM_getValue("YourLocalCurrency") }).format(Result));
};
}
//CurrenciesConverter____________________________________________________________________________________________________________________________________________________________________________
if (GM_getValue("CurrenciesConverter") === true && SelectedText.match(Currencies) !== null) { //If Currencies Converter is enabled and if the selected text is a currency
if (GM_getValue("YourLocalCurrency") === undefined) {
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');
GM_setValue("YourLocalCurrency", UserInput);
}
const currencyMap = { '$': 'USD', 'us$': 'USD', '$us': 'USD', 'r$': 'BRL', 'hk$': 'HKD', '¥': 'JPY', '€': 'EUR', 'rp': 'IDR', 'kn': 'HRK', 'kč': 'CZK', 'kr': 'DKK', 'zł': 'PLN', '£': 'GBP', '฿': 'THB', '₩': 'KRW' };
const CurrencySymbol = currencyMap[SelectedText.match(CurrencySymbols)?.[0].toLowerCase()] || SelectedText.match(Currencies)[2]; //Store the currency symbol
GM.xmlHttpRequest({ //Get the final converted currency value
method: "GET",
url: `https://www.google.com/search?q=${SelectedText.match(Currencies)[1]} ${CurrencySymbol} in ${GM_getValue("YourLocalCurrency")}`,
onload: (response) => {
const newDocument = new DOMParser().parseFromString(response.responseText, 'text/html'); //Parse the fetch response
const FinalCurrency = parseFloat(newDocument.querySelector(".SwHCTb").innerText.split(' ')[0].replaceAll(',', '')); //Store the FinalCurrency and erase all commas
ShowConvertion('Currencies', CurrencySymbol, FinalCurrency);
}
});
}
//UnitsConverter_________________________________________________________________________________________________________________________________________________________________________________
const conversionMap = {};
const Units = new RegExp(/^[ \t\xA0]*(-?\d+(?:[., ]\d+)?)(?:[ \t\xA0]*x[ \t\xA0]*(-?\d+(?:[., ]\d+)?))?[ \t\xA0]*(in|inch|inches|cm|cms|centimeters?|mt|mts|meters?|ft|kg|lbs?|pounds?|kilograms?|ounces?|g|ozs?|fl oz|fl oz (us)|fluid ounces?|kphs?|km\/h|kilometers per hours?|mphs?|meters per hours?|°?º?[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?|l\/100km|liquid quarts?|lqs?|foot-?pounds?|ft-?lbs?|lb fts?|newton-?meters?|nm|\^\d+)[ \t\xA0]*(?:\(\w+\)[ \t\xA0]*)?$/i);
function addConversion(keys, unit, factor, convert) { //Helper function to add multiple keys with the same value
keys.forEach(key => { conversionMap[key] = { unit, factor, convert }; });
}
if (GM_getValue("UnitsConverter") === true && SelectedText.match(Units) !== null) { //If the Units Converter option is enabled and if the selected text is an unit
addConversion(['inch', 'inches', 'in', '"', '”'], 'cm', 2.54);
addConversion(['centimeter', 'centimeters', 'cm', 'cms'], 'in', 1/2.54);
addConversion(['meter', 'meters', 'mt', 'mts'], 'ft', 3.281);
addConversion(['kilogram', 'kilograms', 'kg'], 'lb', 2.205);
addConversion(['pound', 'pounds', 'lb', 'lbs'], 'kg', 1/2.205);
addConversion(['ounce', 'ounces', 'oz', 'ozs'], 'g', 28.35);
addConversion(['gram', 'grams', 'g'], 'oz', 1/28.35);
addConversion(['kilometer', 'kilometers', 'km'], 'mi', 1/1.609);
addConversion(['kph', 'kphs', 'km/h', 'km/hs', 'kilometers per hour', 'kilometers per hours'], 'mph', 0.621371);
addConversion(['mph', 'mphs', 'meters per hour', 'meters per hours'], 'km/h', 1*1.609);
addConversion(['mi', 'mile', 'miles'], 'km', 1.609);
addConversion(['°c', '°f', 'ºc', 'ºf'], '°F', v => (v*9/5)+32);
addConversion(['°f', 'ºf'], '°C', v => (v-32)*5/9);
addConversion(['milliliter', 'milliliters', 'ml'], 'fl oz (US)', 1/29.574);
addConversion(['fl oz (US)', 'fl oz', 'fl', 'fluid ounce', 'fluid ounces'], 'ml', 29.574);
addConversion(['litre', 'liter', 'litres', 'liters', 'l'], 'gal (US)', 1/3.785);
addConversion(['gal', 'gallon', 'gallons'], 'lt', 3.785);
addConversion(['yard', 'yards', 'yd'], 'm', 1/1.094);
addConversion(['millimetre', 'millimeters', 'millimetres', 'mm'], 'in', 1/25.4);
addConversion(['feet', 'feets', 'ft'], 'mt', 0.3048);
addConversion(['kilowatt', 'kilowatts', 'kw', 'kws'], 'mhp', 1.341);
addConversion(['mhp', 'mhps', 'hp', 'hps', 'brake horsepower', 'mechanical horsepower'], 'kw', 1/1.341);
addConversion(['mpg', 'mpgs', 'miles per gallon', 'miles per gallons'], 'l/100km', v => 235.215/v);
addConversion(['l/100km', 'liters per 100 kilometer', 'liters per 100 kilometers'], 'US mpg', v => 235.215/v);
addConversion(['lq', 'lqs', 'liquid quart', 'liquid quarts'], 'l', 1/1.057);
addConversion(['foot-pound', 'foot-pounds', 'foot pound', 'foot pounds', 'ft-lbs', 'ft-lb', 'ft lbs', 'ft lb', 'lb ft', 'lb-ft'], 'Nm', 1.3558179483);
addConversion(['nm', 'newton-meter', 'newton-meters', 'newton meter', 'newton meters'], 'ft lb', 1/1.3558179483);
const selectedUnitType = SelectedText.match(Units)[3].toLowerCase();
const SelectedUnitValue = SelectedText.match(Units)[1].replaceAll(',', '.');
const SecondSelectedUnitValue = SelectedText.match(Units)[2]?.replaceAll(',', '.') || 0;
const convertValue = (value, unitType) => {
const { factor, convert } = conversionMap[unitType] || {};
return convert ? convert(value) : value * factor;
};
var NewUnit = conversionMap[selectedUnitType]?.unit || selectedUnitType;
var ConvertedUnit = SecondSelectedUnitValue != 0 ? `${convertValue(parseFloat(SelectedUnitValue), selectedUnitType).toFixed(2)} x ${convertValue(parseFloat(SecondSelectedUnitValue), selectedUnitType).toFixed(2)}` : convertValue(parseFloat(SelectedUnitValue), selectedUnitType).toFixed(2);
ConvertedUnit = SelectedText.match(/\^(\d+\.?\d*)/) ? (NewUnit = 'power', Math.pow(parseFloat(SelectedUnitValue), parseFloat(SelectedText.match(/\^(\d+\.?\d*)/)[1]))) : ConvertedUnit;
ShowConvertion('Units', NewUnit, ConvertedUnit);
}
//Menu___________________________________________________________________________________________________________________________________________________________________________________________
if (shadowRoot.querySelector("#SearchBTN").innerText === 'Open') //If the Search BTN text is 'Open'
{
shadowRoot.querySelector("#highlight_menu > ul").style.paddingInlineStart = '19px'; //Increase the menu size
shadowRoot.querySelector("#SearchBTN").innerText = 'Search'; //Display the BTN text as Search again
shadowRoot.querySelectorAll(".AI-BG-box button").forEach(button => { button.style.marginLeft = ''; }); //Remove the margin left
shadowRoot.querySelector("#OpenAfter").remove(); //Remove the custom Open white hover overlay
SelectedTextIsLink = false; //Make common words searchable again
}
if (SelectedText.match(Links) !== null) //If the selected text is a link
{
SelectedTextIsLink = true;
shadowRoot.querySelector("#highlight_menu > ul").style.paddingInlineStart = '27px'; //Increase the menu size
shadowRoot.querySelector("#SearchBTN").innerText = 'Open'; //Change the BTN text to Open
shadowRoot.querySelectorAll(".AI-BG-box button").forEach(button => { button.style.marginLeft = '-2%'; }); //Add a margin left
shadowRoot.innerHTML += (html => BypassTT?.createHTML(html) || html)(` `); //Add a custom Open white hover overlay
}
shadowRoot.querySelector("#SearchBTN").onmousedown = function() {
var LinkfyOrSearch = 'https://www.google.com/search?q=';
if (SelectedTextIsLink === true)
{
LinkfyOrSearch = 'https://'; //Make the non-HTTP and non-HTTPS links able to be opened
}
if (SelectedText.match(/http:|https:/) !== null) //If the selected text is a link that already has HTTP or HTTPS
{
LinkfyOrSearch = ''; //Remove the https:// that was previously added to this var
}
GM_openInTab(LinkfyOrSearch + SelectedTextSearch, { active: true, setParent: true, loadInBackground: true }); //Open google and search for the selected text
getSelection().removeAllRanges(); //UnSelect the selected text after the search BTN is clicked so that if the user clicks on the past selected text the menu won't show up again.
shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the menu
};
const menu = shadowRoot.querySelector("#highlight_menu");
if (document.getSelection().toString().trim() !== '') { //If text has been selected
const p = document.getSelection().getRangeAt(0).getBoundingClientRect(); //Store the selected position
menu.classList.add('show'); //Show the menu
menu.offsetHeight; //Trigger reflow by forcing a style calculation
menu.style.left = p.left + (p.width / 2) - (menu.offsetWidth / 2) + 'px';
menu.style.top = p.top - menu.offsetHeight - 10 + 'px';
menu.classList.add('highlight_menu_animate');
return; //Keep the menu open
}
menu.classList.remove('show'); //Hide the menu
shadowRoot.querySelector("#SearchBTN span")?.remove(); //Return previous HTML
}); //Finishes the mouseup event listener
//AI Menu_______________________________________________________________________________________________________________________________________________________________________________________
var desiredVoice = null, isRecognizing = false;
const HtmlMenu = document.createElement('div'); //Create a container div
HtmlMenu.setAttribute('style', `width: 0px; height: 0px; display: none;`); //Hide the container div by default
const shadowRoot = HtmlMenu.attachShadow({ mode: 'closed' });
const BGColor = matchMedia('(prefers-color-scheme: dark)').matches ? 'rgb(37, 36, 53)' : '#e7edf1'; //Change AI theme according to the browser theme
const IMGsColor = BGColor === '#e7edf1' ? 'filter: invert(1)' : ''; //If on white mode invert black svg colors to white
const TextColor = BGColor === '#e7edf1' ? 'black' : 'white'; //Depending on the browser theme change the AI menu text color
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
const Lang = UniqueLangs.length > 1 ? `${UniqueLangs[0]} and into ${UniqueLangs[1]}` : UniqueLangs[0]; //Use 1 or 2 languages
const GeminiSVG = '';
shadowRoot.innerHTML = (html => BypassTT?.createHTML(html) || html)(GM_getResourceText("AIMenuHTMLContent").replaceAll(/\$\{(BGColor|TextColor|GeminiSVG|IMGsColor)\}/g, (_, key) => ({BGColor, TextColor, GeminiSVG, IMGsColor, }[key]))); //Set the AI menu html
function Generate(Prompt, button) { //Call the AI endpoint
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
const IsQuestion = Prompt.includes('?') ? 'Give me a very short, then a long detailed answer' : 'Help me further explore a term or topic from the text/word';
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}".\nAfter showing (in order) a few possible "Translations:" also give me a "Definition:" and "Examples:".You must answer using only 1 language first, then use only the other language, don't mix both languages! ` : 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
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
const request = GM.xmlHttpRequest({ //Call the AI API
method: "POST",
url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:streamGenerateContent?key=${GM_getValue("APIKey")}`,
responseType: 'stream',
headers: {
"Content-Type": "application/json"
},
data: JSON.stringify({
contents: [{
parts: [{
text: `${AIFunction}` //Use our AI prompt
}]
}],
safetySettings: [ //Allow all content
{ category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
{ category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" }
],
}),
onerror: function(err) {
shadowRoot.querySelector("#finalanswer").innerHTML = (html => BypassTT?.createHTML(html) || html)(` Please copy and paste the error below: Click here to report this bug
`);
},
onload: function(response) {
shadowRoot.querySelector("#AIMenu").classList.add('show');
shadowRoot.querySelector("#dictate").classList.add('show');
shadowRoot.querySelector("#TopPause").classList.remove('show');
},
onabort: function(response) {
shadowRoot.querySelector("#AIMenu").classList.add('show');
shadowRoot.querySelector("#dictate").classList.add('show');
shadowRoot.querySelector("#TopPause").classList.remove('show');
shadowRoot.querySelector("#finalanswer").innerText = 'ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤResponse has been interrupted.';
},
onloadstart: function(test) {
shadowRoot.querySelector("#prompt").focus();
shadowRoot.querySelector("#msg").innerHTML = msg;
shadowRoot.querySelector("#TopPause").classList.add('show');
shadowRoot.querySelector("#AIMenu").classList.remove('show');
shadowRoot.querySelector("#dictate").classList.remove('show');
shadowRoot.querySelector("#copyAnswer").onclick = function() {
shadowRoot.querySelector("#copyAnswer").style.display = 'none';
shadowRoot.querySelector("#AnswerCopied").style.display = 'inline-flex';
GM_setClipboard(shadowRoot.querySelector("#finalanswer").innerText.replace('ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ', ''));
setTimeout(() => { //Return play BTN svg
shadowRoot.querySelector("#copyAnswer").style.display = 'inline-flex';
shadowRoot.querySelector("#AnswerCopied").style.display = 'none';
}, 1000);
};
const reader = test.response.getReader();
const decoder = new TextDecoder();
var buffer = '', partialMarkdown = '';
function readStream() {
reader.read().then(({ value }) => {
buffer += decoder.decode(value, { stream: true });
var startIdx = 0;
while (true) {
const openBrace = buffer.indexOf('{', startIdx);
if (openBrace === -1) break;
var balance = 1, closeBrace = openBrace + 1;
while (balance > 0 && closeBrace < buffer.length) {
if (buffer[closeBrace] === '{') balance++;
if (buffer[closeBrace] === '}') balance--;
closeBrace++;
}
if (balance !== 0) break; //Incomplete JSON object
const jsonString = buffer.substring(openBrace, closeBrace);
const item = JSON.parse(jsonString);
partialMarkdown += item.candidates[0].content.parts[0].text;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = marked.parse(partialMarkdown);
shadowRoot.querySelector("#finalanswer").innerHTML = '';
shadowRoot.querySelector("#finalanswer").appendChild(tempDiv);
startIdx = closeBrace;
}
buffer = buffer.substring(startIdx);
readStream();
});
}
readStream();
shadowRoot.querySelector("#CloseOverlay").classList.add('show');
shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the mini menu on the page
shadowRoot.querySelectorAll("#AIBox, .animated-border, #AIBox.AnswerBox").forEach(el => el.classList.add('show')); //Show the AI input and box
getSelection().removeAllRanges(); //UnSelect the selected text so that if the user clicks on a previously selected text the menu won't show up again
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.interimResults = true; //Show partial results
recognition.continuous = true; //Keep listening until stopped
var transcript = ""; //Add words
shadowRoot.querySelector("#CloseOverlay").onclick = function() {
[...shadowRoot.querySelector("#finalanswer div").childNodes].slice(0, -1).forEach(node => node.remove()); //Reset the text content
shadowRoot.querySelectorAll("#AIBox, .animated-border, #AIBox.AnswerBox").forEach(el => el.classList.remove('show')); //Hide the AI input and box
this.classList.remove('show');
recognition.stop(); //Stop recognizing audio
speechSynthesis.cancel(); //Stop speaking
request.abort(); //Abort any ongoing request
if (shadowRoot.querySelector("#gemini").style.display === 'none') {
shadowRoot.querySelector("#AddContext").remove(); //Return original prompt input styles
shadowRoot.querySelector("#context").classList.remove('show');
shadowRoot.querySelector("#prompt").placeholder = 'Enter your prompt to Gemini'; //Return default placeholder
}
};
shadowRoot.querySelector("#TopPause").onclick = function() {
shadowRoot.querySelector("#dictate").classList.add('show');
shadowRoot.querySelector("#TopPause").classList.remove('show');
request.abort();
};
recognition.onend = function() {
isRecognizing = false;
shadowRoot.querySelectorAll('.state1, .state2, .state3').forEach((state, index) => { //ForEach SVG animation state
index.toString().match(/1|2/) ? state.style.display = 'none' : ''; //Show only the 1 state
state.classList.remove('animate'+index); //Stop the voice recording animation
});
transcript !== '' ? Generate(transcript, shadowRoot.querySelector("#prompt").className) : shadowRoot.querySelector("#finalanswer").innerHTML = (html => BypassTT?.createHTML(html) || html)(` No audio detected. Please try again or check your mic settings.ㅤㅤㅤㅤㅤㅤㅤㅤㅤ
`); //Call the AI API if audio has been detected or show an error message
}; //Finish the recognition end event listener
recognition.onresult = function(event) { //Handle voice recognition results
transcript = ""; //Clear the transcript at the start of the event
for (var i = 0; i < event.results.length; i++) { //For all transcript results
transcript += event.results[i][0].transcript + ' '; //Concatenate all intermediate transcripts
}
shadowRoot.querySelector("#msg").innerText = transcript.length > 240 ? transcript.slice(0, 240) + '…' : transcript; //Display recognized words
};
shadowRoot.querySelector("#dictate").onclick = function() {
if (isRecognizing) {
recognition.stop();
} else {
isRecognizing = true;
recognition.start();
shadowRoot.querySelectorAll('.state1, .state2, .state3').forEach((state, index) => { //ForEach SVG animation state
state.style.display = 'unset'; //Show all states
state.classList.add('animate'+index); //Start the voice recording animation
});
}
};
speechSynthesis.onvoiceschanged = () => desiredVoice = speechSynthesis.getVoices().find(v => v.name === "Microsoft Zira - English (United States)"); //Get and store the desired voice
speechSynthesis.onvoiceschanged(); //Handle cases where the event doesn't fire
shadowRoot.querySelectorAll("#speak, #SpeakingPause").forEach(function(el) {
el.onclick = function() { //When the speak or the bottom pause BTNs are clicked
if (speechSynthesis.speaking) {
speechSynthesis.cancel();
shadowRoot.querySelector("#speak").style.display = 'inline-flex'; //Show the play BTN
shadowRoot.querySelector("#SpeakingPause").classList.remove('show'); //Hide the pause BTN
}
else
{
shadowRoot.querySelector("#speak").style.display = 'none'; //Hide the play BTN
shadowRoot.querySelector("#SpeakingPause").classList.add('show');
var audio = new SpeechSynthesisUtterance(shadowRoot.querySelector("#finalanswer").innerText.replace(/\(?..-..\)?:?|[^a-zA-Z0-9\s%.,!?]/g, '')); //Play the AI response text, removing non-alphanumeric characters and lang locales for better pronunciation
audio.voice = desiredVoice; //Use the desiredVoice
speechSynthesis.speak(audio); //Speak the text
audio.onend = (event) => {
shadowRoot.querySelector("#speak").style.display = 'inline-flex'; //Show the play BTN
shadowRoot.querySelector("#SpeakingPause").classList.remove('show');
};
}
};
});
shadowRoot.querySelector("#NewAnswer").onclick = function() {
recognition.stop(); //Stop recognizing audio
speechSynthesis.cancel(); //Stop speaking
shadowRoot.querySelector("#speak").style.display = 'inline-flex'; //Show the play BTN
shadowRoot.querySelector("#SpeakingPause").classList.remove('show'); //Hide the pause BTN
shadowRoot.querySelector("#dictate").classList.remove('show');
shadowRoot.querySelector("#TopPause").classList.add('show');
Generate(Prompt, button); //Call the AI API
};
} //Finishes the onloadstart event listener
});//Finishes the GM.xmlHttpRequest function
} //Finishes the Generate function
shadowRoot.querySelector("#prompt").addEventListener("keydown", (event) => {
if (event.key === "Enter") {
Generate(shadowRoot.querySelector("#prompt").value, shadowRoot.querySelector("#prompt").className); //Call the AI API
shadowRoot.querySelector("#prompt").value = ''; //Erase the prompt text
}
if (event.key === "Tab") {
if (shadowRoot.querySelector("#prompt").placeholder.match('using')) { //If the input bar contains the word "using"
shadowRoot.querySelector("#AddContext").remove(); //Return original prompt input styles
shadowRoot.querySelector("#context").classList.remove('show'); //Hide the context view
shadowRoot.querySelector("#prompt").placeholder = 'Enter your prompt to Gemini'; //Return default placeholder
}
else
{
shadowRoot.querySelector("#context").classList.add('show'); //Show the context view
shadowRoot.querySelector("#prompt").placeholder = `Gemini is using ${location.host.replace('www.','')} for context...`; //Change placeholder
shadowRoot.querySelector("#highlight_menu").insertAdjacentHTML('beforebegin', ` `); //Show the context bar
}
}
setTimeout(() => { //Wait for the code above to execute
shadowRoot.querySelector("#prompt").focus(); //Refocus on the input bar
}, 0);
});
shadowRoot.querySelectorAll("#AIBTN").forEach(function(button) {
button.onmousedown = function(event, i) { //When the Explore or the Translate BTNs are clicked
if (GM_getValue("APIKey") === undefined || GM_getValue("APIKey") === null || GM_getValue("APIKey") === '') { //Set up the API Key if it isn't already set
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'));
}
if (GM_getValue("APIKey") !== null && GM_getValue("APIKey") !== '') {
Generate(SelectedText, this.className); //Call the AI API
}
};
});
if (document.body.textContent !== '' || document.body.innerText !== '') //If the body has any text
{
document.body.appendChild(HtmlMenu); //Add the script menu div container
}
shadowRoot.querySelector('#CopyBTN').onmousedown = function() {
GM_setClipboard(getSelection().toString());
};
window.addEventListener('scroll', async function() {
shadowRoot.querySelector("#highlight_menu").classList.remove('show'); //Hide the menu
if (LeftClicked === false && SelectedText !== '') { //If the Left Click isn't being held, and if something is currently selected
getSelection().removeAllRanges(); //UnSelect the selected text when scrolling the page down so that if the user clicks on the past selected text the menu won't show up again
}
});
}