// ==UserScript==
// @name General Calculator
// @namespace https://greasyfork.org/en/users/1291009
// @version 2.2.1
// @description Calculator that should work on every page :p
// @author BadOrBest
// @license MIT
// @icon https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSITQfCUO2ak5KcOYOskQl6nrLCyC7v73kkB0eIUyu5rsErnN45
// @match *://*/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM.registerMenuCommand
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
var MAX_MESSAGES = 400; // Maximum number of messages to store
var chatBox = document.createElement('div');
chatBox.id = 'calculator-chat-box';
chatBox.style.position = 'fixed';
chatBox.style.bottom = '10vh'; // Adjusted for mobile viewport
chatBox.style.right = '5vw'; // Adjusted for mobile viewport
chatBox.style.width = 'calc(90vw - 10px)'; // Adjusted for mobile viewport
chatBox.style.maxWidth = '300px';
chatBox.style.overflowY = 'hidden';
chatBox.style.backgroundColor = '#222';
chatBox.style.borderRadius = '20px';
chatBox.style.padding = '0';
chatBox.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
chatBox.style.zIndex = '9999';
chatBox.style.display = 'flex';
chatBox.style.flexDirection = 'column';
// Draggable functionality
var isDragging = false;
var initialX, initialY;
var offsetX = 0;
var offsetY = 0;
var selectedForArrowKeys = false; // Added variable to track if chatBox is selected for arrow key dragging
chatBox.addEventListener('mousedown', startDragging);
chatBox.addEventListener('touchstart', startDragging);
chatBox.addEventListener('click', selectForArrowKeys); // Listen for click to select chatBox for arrow key dragging
// Listen for click events on the document to unselect the chatbox
document.addEventListener('click', function(event) {
if (!chatBox.contains(event.target)) {
selectedForArrowKeys = false; // Clicked outside of the chatbox, unselect it
}
});
document.addEventListener('keydown', handleArrowKeys);
function startDragging(event) {
if (event.target === chatInput) {
return; // Don't start dragging if clicking on the input field
}
event.preventDefault();
if (event.type === 'mousedown') {
initialX = event.clientX - offsetX;
initialY = event.clientY - offsetY;
} else if (event.type === 'touchstart') {
initialX = event.touches[0].clientX - offsetX;
initialY = event.touches[0].clientY - offsetY;
}
isDragging = true;
chatBox.classList.add('chat-dragging');
document.addEventListener('mousemove', drag);
document.addEventListener('touchmove', drag);
document.addEventListener('mouseup', stopDragging);
document.addEventListener('touchend', stopDragging);
}
function drag(event) {
event.preventDefault();
if (isDragging) {
var currentX, currentY;
if (event.type === 'mousemove') {
currentX = event.clientX - initialX;
currentY = event.clientY - initialY;
} else if (event.type === 'touchmove') {
currentX = event.touches[0].clientX - initialX;
currentY = event.touches[0].clientY - initialY;
}
offsetX = currentX;
offsetY = currentY;
chatBox.style.transform = `translate(${currentX}px, ${currentY}px)`;
}
}
function stopDragging() {
isDragging = false;
chatBox.classList.remove('chat-dragging');
document.removeEventListener('mousemove', drag);
document.removeEventListener('touchmove', drag);
document.removeEventListener('mouseup', stopDragging);
document.removeEventListener('touchend', stopDragging);
}
function handleArrowKeys(event) {
if (!selectedForArrowKeys) {
return; // If chatBox is not selected for arrow key dragging, exit function
}
var step = 10;
switch (event.key) {
case 'ArrowUp':
offsetY -= step;
break;
case 'ArrowDown':
offsetY += step;
break;
case 'ArrowLeft':
offsetX -= step;
break;
case 'ArrowRight':
offsetX += step;
break;
}
chatBox.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
}
function selectForArrowKeys(event) {
selectedForArrowKeys = true; // Set chatBox as selected for arrow key dragging
}
// Title
var chatTitle = document.createElement('div');
chatTitle.style.background = '#444';
chatTitle.style.color = 'white';
chatTitle.style.padding = '10px';
chatTitle.style.textAlign = 'center';
chatTitle.style.fontWeight = 'bold';
chatTitle.textContent = 'Calculator';
chatBox.appendChild(chatTitle);
// Collapse Button
var collapseButton = document.createElement('button');
collapseButton.textContent = '▼';
collapseButton.style.position = 'absolute';
collapseButton.style.top = '0';
collapseButton.style.right = '0';
collapseButton.style.margin = '10px';
collapseButton.style.width = '30px';
collapseButton.style.height = '30px';
collapseButton.style.fontSize = '20px';
collapseButton.style.border = 'none';
collapseButton.style.backgroundColor = '#444';
collapseButton.style.color = 'white';
collapseButton.style.cursor = 'pointer';
collapseButton.style.borderRadius = '50%';
// Add click event listener
collapseButton.addEventListener('click', function(event) {
toggleChatBox(event);
});
// Add touch event listener
collapseButton.addEventListener('touchstart', function(event) {
toggleChatBox(event);
});
chatBox.appendChild(collapseButton);
// Toggle chat box visibility
function toggleChatBox(event) {
// Stop propagation of the click event to prevent it from triggering dragging
event.stopPropagation();
if (chatBox.style.height === 'auto' || chatBox.style.height === '') {
chatBox.style.height = '70px'; // Set to a fixed height
collapseButton.textContent = '▲';
chatInput.style.display = 'none'; // Hide the input field
} else {
chatBox.style.height = 'auto';
collapseButton.textContent = '▼';
chatInput.style.display = 'block'; // Show the input field
}
}
// Chat history container
var chatHistory = document.createElement('div');
chatHistory.id = 'chat-history';
chatHistory.style.height = 'calc(50vh - 110px)';
chatHistory.style.overflowY = 'auto';
chatHistory.style.backgroundColor = '#333';
chatBox.appendChild(chatHistory);
// Function to load messages from localStorage
function loadMessagesFromStorage() {
var messages = [];
try {
messages = JSON.parse(localStorage.getItem('chatHistory')) || [];
} catch (error) {
console.error('Error loading chat history from localStorage:', error);
// Clear localStorage if there's an error (e.g., quota exceeded)
clearLocalStorage();
}
return messages;
}
// Function to clear localStorage
function clearLocalStorage() {
try {
localStorage.removeItem('chatHistory');
} catch (error) {
console.error('Error clearing localStorage:', error);
}
}
// Register menu commands using GM.registerMenuCommand
GM.registerMenuCommand('Clear Chat History', clearLocalStorage);
// Function to add message to conversation and save to localStorage
function addMessage(message, isInput) {
var messageElement = document.createElement('div');
messageElement.className = 'message ' + (isInput ? 'input' : 'output');
messageElement.innerHTML = message;
chatHistory.appendChild(messageElement);
if (!isInput) {
var line = document.createElement('hr');
line.style.borderTop = '1px solid white';
line.style.margin = '5px 0';
chatHistory.appendChild(line);
}
// Function to scroll to the bottom of the chat history
function scrollToBottom() {
chatHistory.scrollTop = chatHistory.scrollHeight;
}
// Function to check and scroll to bottom if necessary
function checkAndScrollToBottom() {
// Check if user is already at the bottom or if there's a new message
if (chatHistory.scrollTop + chatHistory.clientHeight === chatHistory.scrollHeight) {
scrollToBottom();
}
}
// Initial scroll to bottom when the page loads or chat initializes
scrollToBottom();
// Save message to localStorage
var messages = loadMessagesFromStorage();
messages.push({ message: message, isInput: isInput });
// Limit messages to MAX_MESSAGES
if (messages.length > MAX_MESSAGES) {
messages = messages.slice(-MAX_MESSAGES);
}
try {
localStorage.setItem('chatHistory', JSON.stringify(messages));
} catch (error) {
console.error('Error saving chat history to localStorage:', error);
// Clear localStorage if there's an error (e.g., quota exceeded)
clearLocalStorage();
}
}
// Function to evaluate and display the result
function evaluateExpression(expression) {
try {
// Replace 'x' with '*' for mathematical operations
expression = expression.replace(/(x)/g, '*');
// Handling algebraic expressions
var result;
if (/\b[A-Za-z]+\b/.test(expression)) {
// If expression contains letters
var match = expression.match(/\b[A-Za-z]+\b/);
var letter = match[0];
var equation = expression.replace(/\b[A-Za-z]+\b/g, '0'); // Replace letters with '0' for evaluation
result = solveEquation(equation, letter);
} else {
// If expression is purely mathematical
result = eval(expression);
}
addMessage('<span class="bot-label">(Calc):</span> ' + result, false);
} catch (error) {
addMessage('<span class="bot-label">(Calc):</span> Error: ' + error.message, false);
}
}
// Function to solve algebraic equation
function solveEquation(equation, letter) {
// For simplicity, let's assume linear equations of the form 'ax = b'
var sides = equation.split('=');
var a = eval(sides[0]);
var b = eval(sides[1]);
var result = b / a;
return letter + ' = ' + result;
}
// Function to send message
function sendMessage() {
var expression = chatInput.value.trim();
if (expression !== '') {
addMessage('<span class="user-label">(User):</span> ' + expression, true);
evaluateExpression(expression);
chatInput.value = '';
}
}
// Input field
var chatInput = document.createElement('input');
chatInput.type = 'text';
chatInput.placeholder = 'Type here...';
chatInput.style.width = 'calc(100% - 20px)';
chatInput.style.padding = '10px';
chatInput.style.margin = '10px auto'; // Centered horizontally
chatInput.style.borderRadius = '10px';
chatInput.style.border = 'none';
chatInput.style.backgroundColor = '#444';
chatInput.style.color = 'white';
chatInput.style.zIndex = '10000'; // Ensure input field is above other elements
chatBox.appendChild(chatInput);
// Listen for Enter key press to send message
chatInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
sendMessage();
}
});
// CSS style for user and bot messages
var style = document.createElement('style');
style.innerHTML = `
.message {
clear: both;
padding: 5px 10px;
color: white;
}
.input {
text-align: left;
}
.bot-label {
float: left;
margin-left: 5px;
background-color: blue;
color: white;
border-radius: 10px;
padding: 3px 6px;
}
.user-label {
float: left;
margin-left: 5px;
background-color: green;
color: white;
border-radius: 10px;
padding: 3px 6px;
}
hr {
border-top: 1px solid white;
margin: 5px 0;
}
.chat-dragging * {
/* Removed user-select: none; to enable touch interactions */
}
`;
document.head.appendChild(style);
// Append chatBox to body
document.body.appendChild(chatBox);
// Load messages from localStorage on page load
window.addEventListener('load', function() {
var messages = loadMessagesFromStorage();
messages.forEach(function(msg) {
addMessage(msg.message, msg.isInput);
});
});
})();
// Mathematical functions
Math.sinh = function(x) { return (Math.exp(x) - Math.exp(-x)) / 2; };
Math.cosh = function(x) { return (Math.exp(x) + Math.exp(-x)) / 2; };
Math.tanh = function(x) { return (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x)); };
Math.asinh = function(x) { return Math.log(x + Math.sqrt(x * x + 1)); };
Math.acosh = function(x) { return Math.log(x + Math.sqrt(x * x - 1)); };
Math.atanh = function(x) { return Math.log((1 + x) / (1 - x)) / 2; };
// Mathematical constants
var E = Math.E; // Euler's number
var PI = Math.PI; // Pi
var SQRT2 = Math.SQRT2; // Square root of 2
var SQRT1_2 = Math.SQRT1_2; // Square root of 1/2
// Additional mathematical operations
Math.factorial = function(n) {
if (n === 0 || n === 1)
return 1;
for (var i = n - 1; i >= 1; i--) {
n *= i;
}
return n;
};
Math.permutation = function(n, r) {
return Math.factorial(n) / Math.factorial(n - r);
};
Math.combination = function(n, r) {
return Math.factorial(n) / (Math.factorial(r) * Math.factorial(n - r));
};
Math.roundTo = function(value, decimalPlaces) {
var multiplier = Math.pow(10, decimalPlaces);
return Math.round(value * multiplier) / multiplier;
};
// Trigonometric functions
Math.degToRad = function(degrees) {
return degrees * (Math.PI / 180);
};
Math.radToDeg = function(radians) {
return radians * (180 / Math.PI);
};
// Logarithmic functions
Math.log10 = function(x) {
return Math.log(x) / Math.log(10);
};
Math.log2 = function(x) {
return Math.log(x) / Math.log(2);
};
// Exponential functions
Math.exp10 = function(x) {
return Math.pow(10, x);
};
Math.exp2 = function(x) {
return Math.pow(2, x);
};
// Additional mathematical functions
Math.abs = function(x) { return Math.abs(x); };
Math.ceil = function(x) { return Math.ceil(x); };
Math.floor = function(x) { return Math.floor(x); };
Math.max = function(...args) { return Math.max(...args); };
Math.min = function(...args) { return Math.min(...args); };
Math.pow = function(x, y) { return Math.pow(x, y); };
Math.sqrt = function(x) { return Math.sqrt(x); };
Math.sign = function(x) { return Math.sign(x); };
Math.randomInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; };
Math.clamp = function(x, min, max) { return Math.max(min, Math.min(max, x)); };
Math.hypot = function(...args) { return Math.hypot(...args); };
// Constants
var LN2 = Math.LN2; // Natural logarithm of 2
var LN10 = Math.LN10; // Natural logarithm of 10
var LOG2E = Math.LOG2E; // Base 2 logarithm of E
var LOG10E = Math.LOG10E; // Base 10 logarithm of E
var SQRT3 = Math.SQRT3; // Square root of 3
// Exponential constants
var EXP = Math.exp(1); // Euler's number
// Trigonometric constants
var DEG_PER_RAD = 180 / Math.PI; // Degrees per radian
var RAD_PER_DEG = Math.PI / 180; // Radians per degree
var TWO_PI = 2 * Math.PI; // 2 * Pi
// Logarithmic constants
var LN_2 = Math.LN2; // Natural logarithm of 2
var LN_10 = Math.LN10; // Natural logarithm of 10
var LOG2_E = Math.LOG2E; // Base 2 logarithm of E
var LOG10_E = Math.LOG10E; // Base 10 logarithm of E
// Exponential constants
var SQRT_2 = Math.SQRT2; // Square root of 2
var SQRT_1_2 = Math.SQRT1_2; // Square root of 1/2
// Additional mathematical operations
Math.lcm = function(a, b) { return (!a || !b) ? 0 : Math.abs((a * b) / Math.gcd(a, b)); };
Math.gcd = function(a, b) { return (!b) ? a : Math.gcd(b, a % b); };
Math.factorial = function(n) { return (n !== 1 && n !== 0) ? n * Math.factorial(n - 1) : 1; };
Math.toDegrees = function(radians) { return radians * DEG_PER_RAD; };
Math.toRadians = function(degrees) { return degrees * RAD_PER_DEG; };
Math.isPrime = function(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 === 0 || n % 3 === 0) return false;
let i = 5;
while (i * i <= n) {
if (n % i === 0 || n % (i + 2) === 0) return false;
i += 6;
}
return true;
};