commit c778b61eb18887100e1dd82830e4d6189b3bd11e Author: Rodrigo Rodriguez (Pragmatismo) Date: Wed Dec 3 23:18:01 2025 -0300 Initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a707c07 --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +/* +General Bots Chrome Extension +Copyright (C) 2025 pragmatismo.com.br + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..628da1b --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# General Bots Chrome Extension + +A professional-grade Chrome extension developed by [pragmatismo.com.br](https://pragmatismo.com.br) that enhances WhatsApp Web with server-side message processing capabilities and UI improvements. + +## Features + +- Message Interception: Captures messages before they're sent +- Server Processing: Sends message content to your server for processing +- Message Replacement: Updates the message with processed content before sending +- UI Enhancement: Option to hide the contact list for more chat space +- User-friendly Settings: Simple configuration through the extension popup + +## Installation + +### Developer Mode Installation + +1. Clone or download this repository +2. Open Chrome and navigate to `chrome://extensions/` +3. Enable "Developer mode" in the top-right corner +4. Click "Load unpacked" and select the extension directory + +### Chrome Web Store Installation + +(Coming soon) + +## Configuration + +1. Click the General Bots icon in your Chrome toolbar +2. Enter your processing server URL +3. Toggle message processing on/off +4. Toggle contact list visibility + +## Server API Requirements + +Your server endpoint should: + +1. Accept POST requests with JSON payload: `{ "text": "message content", "timestamp": 1621234567890 }` +2. Return JSON response: `{ "processedText": "updated message content" }` + +## License + +This project is licensed under the [GNU Affero General Public License](LICENSE) - see the LICENSE file for details. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Contact + +For support or questions, please contact [pragmatismo.com.br](https://pragmatismo.com.br). diff --git a/background.js b/background.js new file mode 100644 index 0000000..9c53644 --- /dev/null +++ b/background.js @@ -0,0 +1,461 @@ +// General Bots - Background Service Worker +// Handles authentication, LLM communication, and message processing + +// Default configuration +const DEFAULT_CONFIG = { + serverUrl: "https://api.generalbots.com", + gbServerUrl: "https://api.pragmatismo.com.br", + enableProcessing: true, + hideContacts: false, + autoMode: false, + grammarCorrection: true, + whatsappNumber: "", + authToken: "", + instanceId: "", +}; + +// Initialize extension on install +chrome.runtime.onInstalled.addListener(async (details) => { + console.log("General Bots: Extension installed/updated", details.reason); + + // Set default settings on installation + const existing = await chrome.storage.sync.get(DEFAULT_CONFIG); + await chrome.storage.sync.set({ ...DEFAULT_CONFIG, ...existing }); + + // Create context menu items + chrome.contextMenus?.create({ + id: "gb-correct-grammar", + title: "Correct Grammar with AI", + contexts: ["selection"], + }); + + chrome.contextMenus?.create({ + id: "gb-translate", + title: "Translate with AI", + contexts: ["selection"], + }); +}); + +// Listen for tab updates +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if ( + changeInfo.status === "complete" && + tab.url?.includes("web.whatsapp.com") + ) { + console.log("General Bots: WhatsApp Web detected, initializing..."); + + // Notify content script that tab is ready + chrome.tabs.sendMessage(tabId, { action: "tabReady" }).catch(() => { + // Content script may not be loaded yet, that's okay + }); + + // Check for auto-authentication + checkAutoAuth(tabId); + } +}); + +// Handle messages from content script and popup +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + console.log("General Bots: Received message", message.action); + + switch (message.action) { + case "processText": + handleProcessText(message.text, message.options) + .then(sendResponse) + .catch((err) => sendResponse({ error: err.message })); + return true; // Will respond asynchronously + + case "correctGrammar": + handleGrammarCorrection(message.text) + .then(sendResponse) + .catch((err) => sendResponse({ error: err.message })); + return true; + + case "authenticate": + handleAuthentication(message.whatsappNumber) + .then(sendResponse) + .catch((err) => sendResponse({ error: err.message })); + return true; + + case "getAuthStatus": + getAuthStatus() + .then(sendResponse) + .catch((err) => sendResponse({ error: err.message })); + return true; + + case "generateAutoReply": + handleAutoReply(message.context, message.lastMessages) + .then(sendResponse) + .catch((err) => sendResponse({ error: err.message })); + return true; + + case "getSettings": + chrome.storage.sync.get(DEFAULT_CONFIG).then(sendResponse); + return true; + + case "saveSettings": + chrome.storage.sync.set(message.settings).then(() => { + broadcastSettingsUpdate(message.settings); + sendResponse({ success: true }); + }); + return true; + + case "showNotification": + showNotification(message.title, message.message, message.type); + sendResponse({ success: true }); + return false; + } + + return false; +}); + +// Context menu click handler +chrome.contextMenus?.onClicked.addListener(async (info, tab) => { + if (!info.selectionText) return; + + switch (info.menuItemId) { + case "gb-correct-grammar": + const corrected = await handleGrammarCorrection(info.selectionText); + if (corrected.processedText && tab?.id) { + chrome.tabs.sendMessage(tab.id, { + action: "replaceSelection", + text: corrected.processedText, + }); + } + break; + + case "gb-translate": + // Could implement translation here + break; + } +}); + +// Process text through LLM +async function handleProcessText(text, options = {}) { + const settings = await chrome.storage.sync.get(DEFAULT_CONFIG); + + if (!settings.enableProcessing) { + return { processedText: text, changed: false }; + } + + try { + const response = await fetch(`${settings.serverUrl}/api/v1/llm/process`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${settings.authToken}`, + }, + body: JSON.stringify({ + text, + instanceId: settings.instanceId, + options: { + grammarCorrection: settings.grammarCorrection, + ...options, + }, + }), + }); + + if (!response.ok) { + throw new Error(`Server error: ${response.status}`); + } + + const data = await response.json(); + return { + processedText: data.processedText || text, + changed: data.processedText !== text, + corrections: data.corrections || [], + }; + } catch (error) { + console.error("General Bots: Process text error", error); + // Fallback - return original text + return { processedText: text, changed: false, error: error.message }; + } +} + +// Grammar correction specifically +async function handleGrammarCorrection(text) { + const settings = await chrome.storage.sync.get(DEFAULT_CONFIG); + + try { + const response = await fetch(`${settings.serverUrl}/api/v1/llm/grammar`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${settings.authToken}`, + }, + body: JSON.stringify({ + text, + instanceId: settings.instanceId, + language: "auto", + }), + }); + + if (!response.ok) { + throw new Error(`Server error: ${response.status}`); + } + + const data = await response.json(); + return { + processedText: data.correctedText || text, + original: text, + corrections: data.corrections || [], + language: data.detectedLanguage, + }; + } catch (error) { + console.error("General Bots: Grammar correction error", error); + return { processedText: text, error: error.message }; + } +} + +// Auto-reply generation +async function handleAutoReply(context, lastMessages = []) { + const settings = await chrome.storage.sync.get(DEFAULT_CONFIG); + + if (!settings.autoMode) { + return { reply: null, autoModeDisabled: true }; + } + + try { + const response = await fetch( + `${settings.serverUrl}/api/v1/llm/auto-reply`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${settings.authToken}`, + }, + body: JSON.stringify({ + context, + lastMessages, + instanceId: settings.instanceId, + whatsappNumber: settings.whatsappNumber, + }), + }, + ); + + if (!response.ok) { + throw new Error(`Server error: ${response.status}`); + } + + const data = await response.json(); + return { + reply: data.suggestedReply, + confidence: data.confidence, + autoSend: data.autoSend && settings.autoMode, + }; + } catch (error) { + console.error("General Bots: Auto-reply error", error); + return { reply: null, error: error.message }; + } +} + +// Authentication with General Bots via WhatsApp +async function handleAuthentication(whatsappNumber) { + const settings = await chrome.storage.sync.get(DEFAULT_CONFIG); + + try { + // Request authentication via General Bots WhatsApp bot + const response = await fetch( + `${settings.gbServerUrl}/api/v1/auth/whatsapp/request`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + whatsappNumber, + extensionId: chrome.runtime.id, + timestamp: Date.now(), + }), + }, + ); + + if (!response.ok) { + throw new Error(`Authentication request failed: ${response.status}`); + } + + const data = await response.json(); + + // Save pending auth state + await chrome.storage.sync.set({ + whatsappNumber, + authPending: true, + authRequestId: data.requestId, + }); + + // Show notification to user + showNotification( + "Authentication Requested", + "Check your WhatsApp for a message from General Bots to complete authentication.", + "info", + ); + + // Start polling for auth completion + pollAuthCompletion(data.requestId); + + return { success: true, requestId: data.requestId }; + } catch (error) { + console.error("General Bots: Authentication error", error); + return { success: false, error: error.message }; + } +} + +// Poll for authentication completion +async function pollAuthCompletion(requestId, attempts = 0) { + if (attempts > 60) { + // 5 minutes max + await chrome.storage.sync.set({ authPending: false }); + showNotification("Authentication Timeout", "Please try again.", "error"); + return; + } + + const settings = await chrome.storage.sync.get(DEFAULT_CONFIG); + + try { + const response = await fetch( + `${settings.gbServerUrl}/api/v1/auth/whatsapp/status/${requestId}`, + ); + + if (response.ok) { + const data = await response.json(); + + if (data.status === "completed") { + await chrome.storage.sync.set({ + authToken: data.token, + instanceId: data.instanceId, + authPending: false, + authenticated: true, + }); + + showNotification( + "Authentication Complete", + "You are now connected to General Bots!", + "success", + ); + + // Broadcast to all tabs + broadcastSettingsUpdate({ authenticated: true }); + return; + } else if (data.status === "failed") { + await chrome.storage.sync.set({ authPending: false }); + showNotification( + "Authentication Failed", + data.message || "Please try again.", + "error", + ); + return; + } + } + } catch (error) { + console.error("General Bots: Poll auth error", error); + } + + // Continue polling + setTimeout(() => pollAuthCompletion(requestId, attempts + 1), 5000); +} + +// Check auth status +async function getAuthStatus() { + const settings = await chrome.storage.sync.get([ + "authToken", + "authenticated", + "whatsappNumber", + "instanceId", + ]); + + if (!settings.authToken) { + return { authenticated: false }; + } + + // Verify token is still valid + try { + const response = await fetch( + `${DEFAULT_CONFIG.gbServerUrl}/api/v1/auth/verify`, + { + headers: { + Authorization: `Bearer ${settings.authToken}`, + }, + }, + ); + + if (response.ok) { + return { + authenticated: true, + whatsappNumber: settings.whatsappNumber, + instanceId: settings.instanceId, + }; + } + } catch (error) { + console.error("General Bots: Verify auth error", error); + } + + // Token invalid, clear auth + await chrome.storage.sync.set({ + authToken: "", + authenticated: false, + }); + + return { authenticated: false }; +} + +// Check for auto-authentication when WhatsApp loads +async function checkAutoAuth(tabId) { + const settings = await chrome.storage.sync.get([ + "authenticated", + "autoMode", + "whatsappNumber", + ]); + + if (settings.authenticated && settings.autoMode) { + // Notify content script that auto mode is active + setTimeout(() => { + chrome.tabs + .sendMessage(tabId, { + action: "enableAutoMode", + whatsappNumber: settings.whatsappNumber, + }) + .catch(() => {}); + }, 2000); + } +} + +// Broadcast settings update to all WhatsApp tabs +async function broadcastSettingsUpdate(settings) { + const tabs = await chrome.tabs.query({ url: "https://web.whatsapp.com/*" }); + + for (const tab of tabs) { + chrome.tabs + .sendMessage(tab.id, { + action: "settingsUpdated", + settings, + }) + .catch(() => {}); + } +} + +// Show browser notification +function showNotification(title, message, type = "info") { + const iconPath = type === "error" ? "icons/icon48.png" : "icons/icon48.png"; + + chrome.notifications?.create({ + type: "basic", + iconUrl: iconPath, + title: `General Bots - ${title}`, + message: message, + priority: type === "error" ? 2 : 1, + }); +} + +// Alarm for periodic tasks +chrome.alarms?.create("checkAuth", { periodInMinutes: 30 }); + +chrome.alarms?.onAlarm.addListener(async (alarm) => { + if (alarm.name === "checkAuth") { + const status = await getAuthStatus(); + if (!status.authenticated) { + console.log("General Bots: Auth token expired or invalid"); + } + } +}); + +console.log("General Bots: Background service worker initialized"); diff --git a/content.js b/content.js new file mode 100644 index 0000000..dfd83e3 --- /dev/null +++ b/content.js @@ -0,0 +1,719 @@ +// General Bots - Content Script for WhatsApp Web +// Provides grammar correction, auto-mode, contact hiding, and LLM integration + +(function () { + "use strict"; + + // Global settings + let settings = { + serverUrl: "https://api.generalbots.com", + enableProcessing: true, + hideContacts: false, + autoMode: false, + grammarCorrection: true, + whatsappNumber: "", + authenticated: false, + }; + + // State management + const state = { + initialized: false, + currentContact: null, + autoModeContacts: new Set(), + originalMessages: new Map(), + processingQueue: [], + isProcessing: false, + }; + + // Selectors for WhatsApp Web elements + const SELECTORS = { + contactList: "#pane-side", + chatContainer: ".copyable-area", + messageInput: 'div[contenteditable="true"][data-tab="10"]', + sendButton: 'button[data-tab="11"]', + messageOut: ".message-out", + messageIn: ".message-in", + messageText: ".selectable-text", + contactName: "header ._amig span", + chatHeader: "header._amid", + conversationPanel: "#main", + searchBox: 'div[data-tab="3"]', + }; + + // Initialize the extension + async function init() { + if (state.initialized) return; + + console.log("General Bots: Initializing content script..."); + + // Load settings from storage + await loadSettings(); + + // Apply initial UI modifications + applyUIModifications(); + + // Setup observers and listeners + setupInputListener(); + setupMessageObserver(); + setupContactObserver(); + + // Inject custom UI elements + injectControlPanel(); + + state.initialized = true; + console.log("General Bots: Content script initialized"); + } + + // Load settings from chrome storage + async function loadSettings() { + return new Promise((resolve) => { + chrome.storage.sync.get(settings, (items) => { + settings = { ...settings, ...items }; + console.log("General Bots: Settings loaded", settings); + resolve(settings); + }); + }); + } + + // Apply UI modifications based on settings + function applyUIModifications() { + applyContactVisibility(); + applyAutoModeIndicator(); + } + + // Hide/show contact list + function applyContactVisibility() { + const contactList = document.querySelector(SELECTORS.contactList); + if (contactList) { + const parent = contactList.parentElement; + if (settings.hideContacts) { + parent.classList.add("gb-hide-contacts"); + parent.style.cssText = + "width: 0 !important; min-width: 0 !important; overflow: hidden !important;"; + } else { + parent.classList.remove("gb-hide-contacts"); + parent.style.cssText = ""; + } + } + } + + // Show auto-mode indicator + function applyAutoModeIndicator() { + let indicator = document.getElementById("gb-auto-mode-indicator"); + + if (settings.autoMode) { + if (!indicator) { + indicator = document.createElement("div"); + indicator.id = "gb-auto-mode-indicator"; + indicator.className = "gb-auto-indicator"; + indicator.innerHTML = ` + + Auto Mode Active + `; + document.body.appendChild(indicator); + } + indicator.style.display = "flex"; + } else if (indicator) { + indicator.style.display = "none"; + } + } + + // Setup input field listener for message processing + function setupInputListener() { + const observer = new MutationObserver(() => { + const inputField = document.querySelector(SELECTORS.messageInput); + if (inputField && !inputField.getAttribute("gb-monitored")) { + setupFieldMonitoring(inputField); + inputField.setAttribute("gb-monitored", "true"); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + + // Check immediately + const inputField = document.querySelector(SELECTORS.messageInput); + if (inputField && !inputField.getAttribute("gb-monitored")) { + setupFieldMonitoring(inputField); + inputField.setAttribute("gb-monitored", "true"); + } + } + + // Monitor input field for messages + function setupFieldMonitoring(inputField) { + console.log("General Bots: Setting up input field monitoring"); + + // Listen for keydown events + inputField.addEventListener("keydown", async (event) => { + if (event.key === "Enter" && !event.shiftKey) { + const originalText = inputField.textContent.trim(); + + if (originalText.length > 0 && settings.enableProcessing) { + if (settings.grammarCorrection) { + event.preventDefault(); + event.stopPropagation(); + + try { + showProcessingIndicator(inputField); + const result = await processMessageWithLLM(originalText); + + if ( + result.processedText && + result.processedText !== originalText + ) { + // Show correction preview + const shouldSend = await showCorrectionPreview( + originalText, + result.processedText, + inputField, + ); + + if (shouldSend) { + setInputText(inputField, result.processedText); + // Store original for reference + state.originalMessages.set(Date.now(), { + original: originalText, + corrected: result.processedText, + }); + } + } + + hideProcessingIndicator(); + // Send the message + simulateEnterPress(inputField); + } catch (error) { + console.error("General Bots: Error processing message", error); + hideProcessingIndicator(); + simulateEnterPress(inputField); + } + } + } + } + }); + + // Add visual indicator that field is being monitored + inputField.classList.add("gb-monitored-input"); + } + + // Process message through LLM for grammar correction + async function processMessageWithLLM(text) { + return new Promise((resolve) => { + chrome.runtime.sendMessage( + { + action: "correctGrammar", + text: text, + }, + (response) => { + if (chrome.runtime.lastError) { + console.error( + "General Bots: Runtime error", + chrome.runtime.lastError, + ); + resolve({ processedText: text }); + return; + } + resolve(response || { processedText: text }); + }, + ); + }); + } + + // Show correction preview modal + async function showCorrectionPreview(original, corrected, inputField) { + return new Promise((resolve) => { + // If texts are very similar, auto-accept + if (levenshteinDistance(original, corrected) < 3) { + resolve(true); + return; + } + + const modal = document.createElement("div"); + modal.className = "gb-correction-modal"; + modal.innerHTML = ` +
+
+ + Grammar Correction +
+
+
+
+ +

${escapeHtml(original)}

+
+
+ +

${escapeHtml(corrected)}

+
+
+
+
+ + +
+
+ `; + + document.body.appendChild(modal); + + // Auto-close after 5 seconds with corrected text + const autoClose = setTimeout(() => { + modal.remove(); + resolve(true); + }, 5000); + + document.getElementById("gb-accept").addEventListener("click", () => { + clearTimeout(autoClose); + modal.remove(); + resolve(true); + }); + + document.getElementById("gb-reject").addEventListener("click", () => { + clearTimeout(autoClose); + modal.remove(); + resolve(false); + }); + }); + } + + // Setup observer for incoming messages (for auto-reply) + function setupMessageObserver() { + const observer = new MutationObserver((mutations) => { + if (!settings.autoMode) return; + + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + const incomingMsg = node.querySelector + ? node.querySelector(SELECTORS.messageIn) + : null; + if ( + incomingMsg || + (node.classList && node.classList.contains("message-in")) + ) { + handleIncomingMessage(incomingMsg || node); + } + } + } + } + }); + + // Start observing when chat container is available + const waitForChat = setInterval(() => { + const chatContainer = document.querySelector(SELECTORS.chatContainer); + if (chatContainer) { + clearInterval(waitForChat); + observer.observe(chatContainer, { + childList: true, + subtree: true, + }); + console.log("General Bots: Message observer started"); + } + }, 1000); + } + + // Handle incoming message for auto-reply + async function handleIncomingMessage(messageElement) { + if (!settings.autoMode || !settings.authenticated) return; + + const currentContact = getCurrentContactName(); + if (!currentContact) return; + + // Check if auto-mode is enabled for this contact + if (!state.autoModeContacts.has(currentContact)) return; + + const messageText = messageElement.querySelector(SELECTORS.messageText); + if (!messageText) return; + + const text = messageText.textContent.trim(); + if (!text) return; + + console.log( + "General Bots: Processing incoming message for auto-reply", + text, + ); + + // Get conversation context + const context = getConversationContext(); + + // Request auto-reply from LLM + chrome.runtime.sendMessage( + { + action: "generateAutoReply", + context: { + contact: currentContact, + lastMessage: text, + }, + lastMessages: context, + }, + async (response) => { + if (response && response.reply && response.autoSend) { + await sendAutoReply(response.reply); + } + }, + ); + } + + // Get conversation context (last few messages) + function getConversationContext() { + const messages = []; + const messageElements = document.querySelectorAll( + `${SELECTORS.messageIn}, ${SELECTORS.messageOut}`, + ); + + const recentMessages = Array.from(messageElements).slice(-10); + + for (const msg of recentMessages) { + const textEl = msg.querySelector(SELECTORS.messageText); + if (textEl) { + messages.push({ + type: msg.classList.contains("message-out") ? "sent" : "received", + text: textEl.textContent.trim(), + }); + } + } + + return messages; + } + + // Send auto-reply + async function sendAutoReply(text) { + const inputField = document.querySelector(SELECTORS.messageInput); + if (!inputField) return; + + setInputText(inputField, text); + + // Small delay before sending + await new Promise((r) => setTimeout(r, 500)); + + simulateEnterPress(inputField); + } + + // Setup contact observer for auto-mode toggle per contact + function setupContactObserver() { + const observer = new MutationObserver(() => { + const header = document.querySelector(SELECTORS.chatHeader); + if (header && !header.querySelector(".gb-contact-controls")) { + injectContactControls(header); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + } + + // Inject control buttons for current contact + function injectContactControls(header) { + const contactName = getCurrentContactName(); + if (!contactName) return; + + const controls = document.createElement("div"); + controls.className = "gb-contact-controls"; + + const isAutoEnabled = state.autoModeContacts.has(contactName); + + controls.innerHTML = ` + + `; + + header.appendChild(controls); + + document + .getElementById("gb-toggle-auto") + .addEventListener("click", function () { + if (state.autoModeContacts.has(contactName)) { + state.autoModeContacts.delete(contactName); + this.classList.remove("active"); + this.querySelector(".gb-label").textContent = "Auto OFF"; + } else { + state.autoModeContacts.add(contactName); + this.classList.add("active"); + this.querySelector(".gb-label").textContent = "Auto ON"; + } + }); + } + + // Inject main control panel + function injectControlPanel() { + const panel = document.createElement("div"); + panel.id = "gb-control-panel"; + panel.className = "gb-panel"; + panel.innerHTML = ` +
+ + General Bots + +
+
+
+ + ${settings.authenticated ? "Connected" : "Not Connected"} +
+ +
+ + + + + +
+ + ${ + !settings.authenticated + ? ` +
+

Connect with your General Bots account:

+ + +
+ ` + : "" + } +
+ `; + + document.body.appendChild(panel); + + // Setup panel event listeners + setupPanelListeners(); + } + + // Setup control panel event listeners + function setupPanelListeners() { + // Panel toggle + document + .getElementById("gb-panel-toggle") + ?.addEventListener("click", function () { + const body = document.getElementById("gb-panel-body"); + if (body.style.display === "none") { + body.style.display = "block"; + this.textContent = "−"; + } else { + body.style.display = "none"; + this.textContent = "+"; + } + }); + + // Grammar toggle + document + .getElementById("gb-grammar-toggle") + ?.addEventListener("change", function () { + settings.grammarCorrection = this.checked; + saveSettings(); + }); + + // Contacts toggle + document + .getElementById("gb-contacts-toggle") + ?.addEventListener("change", function () { + settings.hideContacts = this.checked; + applyContactVisibility(); + saveSettings(); + }); + + // Auto mode toggle + document + .getElementById("gb-auto-toggle") + ?.addEventListener("change", function () { + settings.autoMode = this.checked; + applyAutoModeIndicator(); + saveSettings(); + }); + + // Auth button + document + .getElementById("gb-auth-btn") + ?.addEventListener("click", async function () { + const numberInput = document.getElementById("gb-whatsapp-number"); + const number = numberInput?.value.trim(); + + if (!number) { + alert("Please enter your WhatsApp number"); + return; + } + + this.disabled = true; + this.textContent = "Authenticating..."; + + chrome.runtime.sendMessage( + { + action: "authenticate", + whatsappNumber: number, + }, + (response) => { + if (response.success) { + this.textContent = "Check WhatsApp"; + } else { + this.textContent = "Authenticate"; + this.disabled = false; + alert( + "Authentication failed: " + (response.error || "Unknown error"), + ); + } + }, + ); + }); + } + + // Save settings to storage + function saveSettings() { + chrome.storage.sync.set(settings); + chrome.runtime.sendMessage({ + action: "saveSettings", + settings: settings, + }); + } + + // Get current contact name + function getCurrentContactName() { + const nameEl = document.querySelector(SELECTORS.contactName); + return nameEl ? nameEl.textContent.trim() : null; + } + + // Set text in input field + function setInputText(inputField, text) { + inputField.textContent = text; + inputField.dispatchEvent(new InputEvent("input", { bubbles: true })); + } + + // Simulate Enter key press + function simulateEnterPress(element) { + const enterEvent = new KeyboardEvent("keydown", { + key: "Enter", + code: "Enter", + keyCode: 13, + which: 13, + bubbles: true, + cancelable: true, + }); + element.dispatchEvent(enterEvent); + } + + // Show processing indicator + function showProcessingIndicator(inputField) { + let indicator = document.getElementById("gb-processing"); + if (!indicator) { + indicator = document.createElement("div"); + indicator.id = "gb-processing"; + indicator.className = "gb-processing-indicator"; + indicator.innerHTML = ` +
+ Processing with AI... + `; + inputField.parentElement.appendChild(indicator); + } + indicator.style.display = "flex"; + } + + // Hide processing indicator + function hideProcessingIndicator() { + const indicator = document.getElementById("gb-processing"); + if (indicator) { + indicator.style.display = "none"; + } + } + + // Utility: Escape HTML + function escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + + // Utility: Levenshtein distance for text comparison + function levenshteinDistance(str1, str2) { + const m = str1.length; + const n = str2.length; + const dp = Array(m + 1) + .fill(null) + .map(() => Array(n + 1).fill(0)); + + for (let i = 0; i <= m; i++) dp[i][0] = i; + for (let j = 0; j <= n; j++) dp[0][j] = j; + + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (str1[i - 1] === str2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1; + } + } + } + + return dp[m][n]; + } + + // Listen for messages from background script + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + switch (message.action) { + case "tabReady": + init(); + break; + + case "settingsUpdated": + settings = { ...settings, ...message.settings }; + applyUIModifications(); + break; + + case "enableAutoMode": + settings.autoMode = true; + settings.whatsappNumber = message.whatsappNumber; + applyAutoModeIndicator(); + break; + + case "replaceSelection": + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + range.deleteContents(); + range.insertNode(document.createTextNode(message.text)); + } + break; + + case "authCompleted": + settings.authenticated = true; + // Refresh control panel + const panel = document.getElementById("gb-control-panel"); + if (panel) { + panel.remove(); + injectControlPanel(); + } + break; + } + + sendResponse({ success: true }); + return true; + }); + + // Initialize on DOM ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + // Also try to initialize after a delay (WhatsApp Web loads dynamically) + setTimeout(init, 2000); +})(); diff --git a/icons/icon128.png b/icons/icon128.png new file mode 100644 index 0000000..d575403 Binary files /dev/null and b/icons/icon128.png differ diff --git a/icons/icon16.png b/icons/icon16.png new file mode 100644 index 0000000..d575403 Binary files /dev/null and b/icons/icon16.png differ diff --git a/icons/icon48.png b/icons/icon48.png new file mode 100644 index 0000000..d575403 Binary files /dev/null and b/icons/icon48.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..079cf80 --- /dev/null +++ b/manifest.json @@ -0,0 +1,44 @@ +{ + "manifest_version": 3, + "name": "General Bots", + "version": "2.0.0", + "description": "AI-powered browser assistant with grammar correction, auto-reply, and WhatsApp integration", + "author": "pragmatismo.com.br", + "icons": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + }, + "permissions": ["storage", "tabs", "activeTab", "notifications", "identity"], + "host_permissions": [ + "https://web.whatsapp.com/*", + "https://*.pragmatismo.com.br/*", + "https://api.generalbots.com/*" + ], + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } + }, + "content_scripts": [ + { + "matches": ["https://web.whatsapp.com/*"], + "js": ["content.js"], + "css": ["styles.css"], + "run_at": "document_end" + } + ], + "background": { + "service_worker": "background.js" + }, + "web_accessible_resources": [ + { + "resources": ["icons/*", "styles.css"], + "matches": ["https://web.whatsapp.com/*"] + } + ], + "options_page": "options.html" +} diff --git a/options.html b/options.html new file mode 100644 index 0000000..4b0e5b3 --- /dev/null +++ b/options.html @@ -0,0 +1,1135 @@ + + + + + + General Bots - Options + + + +
+
+ General Bots +
+

General Bots Options

+

Configure advanced settings for the extension

+
+
+ +
+ + + + +
+ + +
+
+

+ 🌐 Server Configuration +

+ +
+ + +

+ The main API endpoint for General Bots services +

+
+ +
+ + +

+ Secondary API endpoint for authentication +

+
+ +
+
+ + +
+
+ + +
+
+
+ +
+

+ 🔐 Authentication +

+ +
+
+
Connection Status
+
+ Checking... +
+
+ Unknown +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+
+

+ Grammar Correction +

+ +
+
+
+ Enable Grammar Correction +
+
+ Automatically correct spelling and grammar in + messages +
+
+ +
+ +
+
+
Show Preview
+
+ Show correction preview before sending +
+
+ +
+ +
+ + +
+
+ +
+

+ 🤖 Auto-Reply Mode +

+ +
+ ⚠️ Auto-reply mode requires authentication and should be + used responsibly. +
+ +
+
+
Enable Auto-Reply
+
+ Automatically reply to messages using AI +
+
+ +
+ +
+
+
+ Require Manual Approval +
+
+ Review auto-replies before sending +
+
+ +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+
+

+ 👁️ Privacy Settings +

+ +
+
+
Hide Contact List
+
+ Hide the contact list for privacy +
+
+ +
+ +
+
+
+ Hide Processing Indicators +
+
+ Don't show AI processing status to others +
+
+ +
+ +
+
+
Local Processing Only
+
+ Process messages locally when possible (limited + features) +
+
+ +
+
+ +
+

+ 📊 Data Management +

+ +
+
+
+ Store Original Messages +
+
+ Keep original messages for reference +
+
+ +
+ +
+
+
Collect Analytics
+
+ Help improve General Bots with anonymous usage + data +
+
+ +
+ +
+ + +
+
+
+ + +
+
+

+ 🔧 Developer Options +

+ +
+
+
Debug Mode
+
+ Enable verbose logging for debugging +
+
+ +
+ +
+
+
Show Control Panel
+
+ Display floating control panel on WhatsApp +
+
+ +
+ +
+ + +

+ Click and press keys to set shortcut +

+
+
+ +
+

+ 📋 Activity Log +

+ +
+
+ --:--:-- + INFO + Waiting for activity... +
+
+ +
+ + +
+
+ +
+

+ 🔄 Reset +

+ +

+ Reset all settings to their default values. This action + cannot be undone. +

+ + +
+
+ + +
+ + +
+
+ + + + diff --git a/popup.css b/popup.css new file mode 100644 index 0000000..7b81349 --- /dev/null +++ b/popup.css @@ -0,0 +1,465 @@ +/* General Bots - Popup Styles */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary: #3b82f6; + --primary-hover: #2563eb; + --secondary: #64748b; + --success: #22c55e; + --warning: #f59e0b; + --error: #ef4444; + --bg-dark: #1e293b; + --bg-darker: #0f172a; + --bg-card: #334155; + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --border: #475569; + --radius: 8px; + --radius-lg: 12px; + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --transition: all 0.2s ease; +} + +body { + width: 360px; + min-height: 480px; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, sans-serif; + background: var(--bg-dark); + color: var(--text-primary); + font-size: 14px; + line-height: 1.5; +} + +.container { + display: flex; + flex-direction: column; + padding: 0; +} + +/* Header */ +.header { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + background: linear-gradient(135deg, var(--primary), var(--primary-hover)); +} + +.header img { + width: 40px; + height: 40px; + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.1); + padding: 4px; +} + +.header-text { + flex: 1; +} + +.header h1 { + font-size: 18px; + font-weight: 600; + color: white; +} + +.header .version { + font-size: 11px; + color: rgba(255, 255, 255, 0.7); + margin-top: 2px; +} + +/* Status Bar */ +.status-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + background: var(--bg-darker); + font-size: 12px; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--secondary); + transition: var(--transition); +} + +.status-bar.connected .status-dot { + background: var(--success); + box-shadow: 0 0 8px var(--success); +} + +.status-bar.disconnected .status-dot { + background: var(--error); +} + +.status-text { + color: var(--text-secondary); +} + +/* Sections */ +.section { + padding: 16px 20px; + border-bottom: 1px solid var(--border); +} + +.section h3 { + font-size: 13px; + font-weight: 600; + margin-bottom: 12px; + color: var(--text-primary); +} + +.section .description { + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 16px; +} + +/* Auth Section */ +.auth-section { + background: rgba(59, 130, 246, 0.05); +} + +.input-group { + margin-bottom: 12px; +} + +.input-group label { + display: block; + font-size: 12px; + font-weight: 500; + margin-bottom: 6px; + color: var(--text-secondary); +} + +.input-group input { + width: 100%; + padding: 10px 12px; + background: var(--bg-darker); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-primary); + font-size: 14px; + transition: var(--transition); +} + +.input-group input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); +} + +.input-group input.error { + border-color: var(--error); +} + +.input-group small { + display: block; + font-size: 11px; + color: var(--text-secondary); + margin-top: 4px; +} + +.input-group small.error-text { + color: var(--error); +} + +/* Settings */ +.settings-section { + padding-bottom: 8px; +} + +.setting-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.setting-item:last-child { + border-bottom: none; +} + +.setting-info { + flex: 1; +} + +.setting-label { + display: block; + font-size: 13px; + font-weight: 500; + color: var(--text-primary); +} + +.setting-hint { + display: block; + font-size: 11px; + color: var(--text-secondary); + margin-top: 2px; +} + +.input-small { + width: 100%; + padding: 8px 10px; + background: var(--bg-darker); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-primary); + font-size: 12px; + margin-top: 8px; +} + +.input-small:focus { + outline: none; + border-color: var(--primary); +} + +/* Toggle Switch */ +.switch { + position: relative; + width: 44px; + height: 24px; + flex-shrink: 0; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--secondary); + transition: var(--transition); +} + +.slider.round { + border-radius: 24px; +} + +.slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background: white; + transition: var(--transition); + border-radius: 50%; +} + +input:checked + .slider { + background: var(--primary); +} + +input:checked + .slider:before { + transform: translateX(20px); +} + +input:focus + .slider { + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); +} + +/* Stats Section */ +.stats-section { + background: var(--bg-darker); +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.stat-item { + text-align: center; + padding: 12px 8px; + background: var(--bg-card); + border-radius: var(--radius); +} + +.stat-value { + display: block; + font-size: 20px; + font-weight: 700; + color: var(--primary); +} + +.stat-label { + display: block; + font-size: 10px; + color: var(--text-secondary); + margin-top: 4px; +} + +/* Actions */ +.actions { + display: flex; + gap: 10px; + padding: 16px 20px; +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 16px; + border: none; + border-radius: var(--radius); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + flex: 1; +} + +.btn-icon { + font-size: 14px; +} + +.btn-primary { + background: var(--primary); + color: white; +} + +.btn-primary:hover { + background: var(--primary-hover); +} + +.btn-primary:disabled { + background: var(--secondary); + cursor: not-allowed; +} + +.btn-secondary { + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--border); +} + +.btn-secondary:hover { + background: var(--bg-darker); +} + +.btn-success { + background: var(--success) !important; +} + +.btn-error { + background: var(--error) !important; +} + +/* Footer */ +.footer { + padding: 12px 20px; + text-align: center; + border-top: 1px solid var(--border); +} + +.footer .copyright { + font-size: 11px; + color: var(--text-secondary); +} + +.footer a { + color: var(--primary); + text-decoration: none; +} + +.footer a:hover { + text-decoration: underline; +} + +/* Notification */ +.popup-notification { + position: fixed; + bottom: 20px; + left: 20px; + right: 20px; + padding: 12px 16px; + background: var(--bg-card); + border-radius: var(--radius); + font-size: 13px; + text-align: center; + box-shadow: var(--shadow); + animation: slideUp 0.3s ease; + z-index: 1000; +} + +.popup-notification.success { + background: var(--success); + color: white; +} + +.popup-notification.error { + background: var(--error); + color: white; +} + +.popup-notification.warning { + background: var(--warning); + color: var(--bg-dark); +} + +.popup-notification.fade-out { + animation: fadeOut 0.3s ease forwards; +} + +@keyframes slideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: var(--bg-darker); +} + +::-webkit-scrollbar-thumb { + background: var(--secondary); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..87b5a70 --- /dev/null +++ b/popup.html @@ -0,0 +1,175 @@ + + + + + + General Bots + + + +
+
+ General Bots Logo +
+

General Bots

+

v2.0.0

+
+
+ + +
+ + Checking connection... +
+ + +
+

🔐 Connect to General Bots

+

+ Authenticate using your WhatsApp number to enable AI + features. +

+ +
+ + + Include country code +
+ + +
+ + +
+

⚙️ Settings

+ +
+
+ Server URL + General Bots API endpoint +
+ +
+ +
+
+ ✨ Grammar Correction + Fix spelling & grammar with AI +
+ +
+ +
+
+ 📝 Enable Processing + Process messages before sending +
+ +
+ +
+
+ 👁️ Hide Contacts + Hide contact list for privacy +
+ +
+ +
+
+ 🤖 Auto Mode + Enable automatic replies +
+ +
+
+ + +
+

📊 Statistics

+
+
+ 0 + Messages Processed +
+
+ 0 + Corrections Made +
+
+ 0 + Auto Replies +
+
+
+ + +
+ + +
+ + +
+ + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..a035d47 --- /dev/null +++ b/popup.js @@ -0,0 +1,293 @@ +// General Bots - Popup Script +// Handles settings management, authentication, and UI updates + +document.addEventListener("DOMContentLoaded", async function () { + // Default settings + const DEFAULT_SETTINGS = { + serverUrl: "https://api.generalbots.com", + gbServerUrl: "https://api.pragmatismo.com.br", + enableProcessing: true, + hideContacts: false, + autoMode: false, + grammarCorrection: true, + whatsappNumber: "", + authToken: "", + instanceId: "", + authenticated: false, + stats: { + messagesProcessed: 0, + correctionsMade: 0, + autoReplies: 0, + }, + }; + + // Load settings and update UI + await loadSettings(); + await checkAuthStatus(); + loadStats(); + + // Event listeners + setupEventListeners(); + + // Load saved settings + async function loadSettings() { + return new Promise((resolve) => { + chrome.storage.sync.get(DEFAULT_SETTINGS, function (items) { + // Update form fields + document.getElementById("server-url").value = + items.serverUrl || DEFAULT_SETTINGS.serverUrl; + document.getElementById("whatsapp-number").value = + items.whatsappNumber || ""; + document.getElementById("grammar-correction").checked = + items.grammarCorrection; + document.getElementById("enable-processing").checked = + items.enableProcessing; + document.getElementById("hide-contacts").checked = items.hideContacts; + document.getElementById("auto-mode").checked = items.autoMode; + + resolve(items); + }); + }); + } + + // Check authentication status + async function checkAuthStatus() { + return new Promise((resolve) => { + chrome.runtime.sendMessage({ action: "getAuthStatus" }, (response) => { + const statusBar = document.getElementById("status-bar"); + const authSection = document.getElementById("auth-section"); + const statusDot = statusBar.querySelector(".status-dot"); + const statusText = statusBar.querySelector(".status-text"); + + if (response && response.authenticated) { + statusBar.classList.add("connected"); + statusBar.classList.remove("disconnected"); + statusDot.style.background = "#22c55e"; + statusText.textContent = `Connected (${response.whatsappNumber || "Authenticated"})`; + authSection.style.display = "none"; + } else { + statusBar.classList.add("disconnected"); + statusBar.classList.remove("connected"); + statusDot.style.background = "#ef4444"; + statusText.textContent = "Not connected"; + authSection.style.display = "block"; + } + + resolve(response); + }); + }); + } + + // Load statistics + function loadStats() { + chrome.storage.local.get(["stats"], function (result) { + const stats = result.stats || DEFAULT_SETTINGS.stats; + document.getElementById("messages-processed").textContent = + stats.messagesProcessed || 0; + document.getElementById("corrections-made").textContent = + stats.correctionsMade || 0; + document.getElementById("auto-replies").textContent = + stats.autoReplies || 0; + }); + } + + // Setup event listeners + function setupEventListeners() { + // Save settings button + document + .getElementById("save-settings") + .addEventListener("click", saveSettings); + + // Auth button + document + .getElementById("auth-btn") + .addEventListener("click", handleAuthentication); + + // Open options page + document.getElementById("open-options").addEventListener("click", () => { + chrome.runtime.openOptionsPage(); + }); + + // Toggle switches - save immediately on change + const toggles = [ + "grammar-correction", + "enable-processing", + "hide-contacts", + "auto-mode", + ]; + toggles.forEach((id) => { + document.getElementById(id).addEventListener("change", function () { + saveSettings(true); // silent save + }); + }); + } + + // Save settings + async function saveSettings(silent = false) { + const settings = { + serverUrl: document.getElementById("server-url").value.trim(), + grammarCorrection: document.getElementById("grammar-correction").checked, + enableProcessing: document.getElementById("enable-processing").checked, + hideContacts: document.getElementById("hide-contacts").checked, + autoMode: document.getElementById("auto-mode").checked, + whatsappNumber: document.getElementById("whatsapp-number").value.trim(), + }; + + return new Promise((resolve) => { + chrome.storage.sync.set(settings, function () { + // Notify content script about settings change + chrome.tabs.query( + { url: "https://web.whatsapp.com/*" }, + function (tabs) { + tabs.forEach((tab) => { + chrome.tabs.sendMessage(tab.id, { + action: "settingsUpdated", + settings: settings, + }); + }); + }, + ); + + if (!silent) { + showFeedback("save-settings", "Saved!", "success"); + } + + resolve(settings); + }); + }); + } + + // Handle authentication + async function handleAuthentication() { + const numberInput = document.getElementById("whatsapp-number"); + const authBtn = document.getElementById("auth-btn"); + const number = numberInput.value.trim(); + + // Validate number + if (!number) { + showError(numberInput, "Please enter your WhatsApp number"); + return; + } + + // Clean number (remove spaces, dashes) + const cleanNumber = number.replace(/[\s\-\(\)]/g, ""); + + // Basic validation + if (!/^\+?[0-9]{10,15}$/.test(cleanNumber)) { + showError(numberInput, "Invalid phone number format"); + return; + } + + // Update button state + authBtn.disabled = true; + authBtn.innerHTML = ' Sending request...'; + + // Send auth request + chrome.runtime.sendMessage( + { + action: "authenticate", + whatsappNumber: cleanNumber, + }, + (response) => { + if (response && response.success) { + authBtn.innerHTML = + '📱 Check your WhatsApp'; + authBtn.classList.add("btn-success"); + + // Start polling for auth completion + pollAuthStatus(); + } else { + authBtn.disabled = false; + authBtn.innerHTML = + '🤖 Authenticate via WhatsApp'; + + const errorMsg = response?.error || "Authentication failed"; + showNotification(errorMsg, "error"); + } + }, + ); + } + + // Poll for authentication status + function pollAuthStatus(attempts = 0) { + if (attempts > 60) { + // 5 minutes max + showNotification("Authentication timed out. Please try again.", "error"); + resetAuthButton(); + return; + } + + setTimeout(async () => { + const response = await checkAuthStatus(); + + if (response && response.authenticated) { + showNotification("Successfully connected!", "success"); + resetAuthButton(); + } else { + pollAuthStatus(attempts + 1); + } + }, 5000); + } + + // Reset auth button + function resetAuthButton() { + const authBtn = document.getElementById("auth-btn"); + authBtn.disabled = false; + authBtn.classList.remove("btn-success"); + authBtn.innerHTML = + '🤖 Authenticate via WhatsApp'; + } + + // Show error on input + function showError(input, message) { + input.classList.add("error"); + const small = input.parentElement.querySelector("small"); + if (small) { + small.textContent = message; + small.classList.add("error-text"); + } + + setTimeout(() => { + input.classList.remove("error"); + if (small) { + small.textContent = "Include country code"; + small.classList.remove("error-text"); + } + }, 3000); + } + + // Show feedback on button + function showFeedback(buttonId, message, type = "success") { + const button = document.getElementById(buttonId); + const originalHTML = button.innerHTML; + const originalDisabled = button.disabled; + + button.disabled = true; + button.innerHTML = `${type === "success" ? "✓" : "✗"} ${message}`; + button.classList.add(`btn-${type}`); + + setTimeout(() => { + button.innerHTML = originalHTML; + button.disabled = originalDisabled; + button.classList.remove(`btn-${type}`); + }, 1500); + } + + // Show notification + function showNotification(message, type = "info") { + // Remove existing notification + const existing = document.querySelector(".popup-notification"); + if (existing) existing.remove(); + + const notification = document.createElement("div"); + notification.className = `popup-notification ${type}`; + notification.textContent = message; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.classList.add("fade-out"); + setTimeout(() => notification.remove(), 300); + }, 3000); + } +}); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..3e03de3 --- /dev/null +++ b/styles.css @@ -0,0 +1,666 @@ +/* General Bots - WhatsApp Web Extension Styles */ + +/* ============================================ + CSS Variables + ============================================ */ +:root { + --gb-primary: #3b82f6; + --gb-primary-hover: #2563eb; + --gb-secondary: #64748b; + --gb-success: #22c55e; + --gb-warning: #f59e0b; + --gb-error: #ef4444; + --gb-bg-dark: #1e293b; + --gb-bg-light: #f8fafc; + --gb-text-light: #f1f5f9; + --gb-text-dark: #1e293b; + --gb-border: #334155; + --gb-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --gb-shadow-lg: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --gb-radius: 8px; + --gb-radius-lg: 12px; + --gb-transition: all 0.2s ease; +} + +/* ============================================ + Hide Contacts Mode + ============================================ */ +.gb-hide-contacts { + width: 0 !important; + min-width: 0 !important; + overflow: hidden !important; + opacity: 0 !important; + pointer-events: none !important; + transition: var(--gb-transition); +} + +/* ============================================ + Control Panel + ============================================ */ +#gb-control-panel { + position: fixed; + top: 80px; + right: 20px; + width: 280px; + background: var(--gb-bg-dark); + border-radius: var(--gb-radius-lg); + box-shadow: var(--gb-shadow-lg); + z-index: 99999; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, sans-serif; + color: var(--gb-text-light); + overflow: hidden; + border: 1px solid var(--gb-border); +} + +.gb-panel-header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + background: linear-gradient( + 135deg, + var(--gb-primary), + var(--gb-primary-hover) + ); + cursor: move; +} + +.gb-panel-logo { + width: 24px; + height: 24px; + border-radius: 4px; +} + +.gb-panel-header span { + flex: 1; + font-weight: 600; + font-size: 14px; +} + +.gb-panel-toggle { + background: rgba(255, 255, 255, 0.2); + border: none; + color: white; + width: 24px; + height: 24px; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + line-height: 1; + transition: var(--gb-transition); +} + +.gb-panel-toggle:hover { + background: rgba(255, 255, 255, 0.3); +} + +.gb-panel-body { + padding: 16px; +} + +/* Status indicator */ +.gb-status { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + background: rgba(0, 0, 0, 0.2); + border-radius: var(--gb-radius); + margin-bottom: 16px; + font-size: 13px; +} + +.gb-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--gb-secondary); +} + +.gb-status.connected .gb-status-dot { + background: var(--gb-success); + box-shadow: 0 0 8px var(--gb-success); +} + +.gb-status.disconnected .gb-status-dot { + background: var(--gb-error); +} + +/* Controls */ +.gb-controls { + display: flex; + flex-direction: column; + gap: 12px; +} + +.gb-switch-label { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 13px; + cursor: pointer; +} + +.gb-switch-label input { + display: none; +} + +.gb-switch { + position: relative; + width: 44px; + height: 24px; + background: var(--gb-secondary); + border-radius: 12px; + transition: var(--gb-transition); +} + +.gb-switch::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 20px; + height: 20px; + background: white; + border-radius: 50%; + transition: var(--gb-transition); +} + +.gb-switch-label input:checked + .gb-switch { + background: var(--gb-primary); +} + +.gb-switch-label input:checked + .gb-switch::after { + left: 22px; +} + +/* Auth section */ +.gb-auth-section { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--gb-border); +} + +.gb-auth-section p { + font-size: 12px; + color: var(--gb-secondary); + margin-bottom: 12px; +} + +.gb-auth-section input { + width: 100%; + padding: 10px 12px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid var(--gb-border); + border-radius: var(--gb-radius); + color: var(--gb-text-light); + font-size: 13px; + margin-bottom: 12px; + box-sizing: border-box; +} + +.gb-auth-section input::placeholder { + color: var(--gb-secondary); +} + +.gb-auth-section input:focus { + outline: none; + border-color: var(--gb-primary); +} + +/* ============================================ + Buttons + ============================================ */ +.gb-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 16px; + border: none; + border-radius: var(--gb-radius); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: var(--gb-transition); + width: 100%; +} + +.gb-btn-primary { + background: var(--gb-primary); + color: white; +} + +.gb-btn-primary:hover { + background: var(--gb-primary-hover); +} + +.gb-btn-primary:disabled { + background: var(--gb-secondary); + cursor: not-allowed; +} + +.gb-btn-secondary { + background: rgba(255, 255, 255, 0.1); + color: var(--gb-text-light); + border: 1px solid var(--gb-border); +} + +.gb-btn-secondary:hover { + background: rgba(255, 255, 255, 0.15); +} + +/* ============================================ + Correction Modal + ============================================ */ +.gb-correction-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 999999; + backdrop-filter: blur(4px); + animation: gb-fade-in 0.2s ease; +} + +@keyframes gb-fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.gb-correction-content { + background: var(--gb-bg-dark); + border-radius: var(--gb-radius-lg); + width: 90%; + max-width: 500px; + box-shadow: var(--gb-shadow-lg); + border: 1px solid var(--gb-border); + animation: gb-slide-up 0.3s ease; + overflow: hidden; +} + +@keyframes gb-slide-up { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.gb-correction-header { + display: flex; + align-items: center; + gap: 10px; + padding: 16px 20px; + background: linear-gradient( + 135deg, + var(--gb-primary), + var(--gb-primary-hover) + ); + color: white; + font-weight: 600; + font-size: 15px; +} + +.gb-correction-icon { + font-size: 20px; +} + +.gb-correction-body { + padding: 20px; +} + +.gb-text-compare { + display: flex; + flex-direction: column; + gap: 16px; +} + +.gb-text-compare label { + display: block; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--gb-secondary); + margin-bottom: 6px; +} + +.gb-text-compare p { + margin: 0; + padding: 12px; + background: rgba(0, 0, 0, 0.2); + border-radius: var(--gb-radius); + font-size: 14px; + line-height: 1.5; + color: var(--gb-text-light); +} + +.gb-original p { + border-left: 3px solid var(--gb-error); + text-decoration: line-through; + opacity: 0.7; +} + +.gb-corrected p { + border-left: 3px solid var(--gb-success); +} + +.gb-correction-actions { + display: flex; + gap: 12px; + padding: 16px 20px; + background: rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--gb-border); +} + +.gb-correction-actions .gb-btn { + flex: 1; +} + +/* ============================================ + Auto Mode Indicator + ============================================ */ +.gb-auto-indicator { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: var(--gb-success); + color: white; + border-radius: 20px; + font-size: 12px; + font-weight: 500; + box-shadow: var(--gb-shadow); + z-index: 99999; + animation: gb-pulse 2s infinite; +} + +@keyframes gb-pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.8; + } +} + +.gb-auto-dot { + width: 8px; + height: 8px; + background: white; + border-radius: 50%; + animation: gb-blink 1s infinite; +} + +@keyframes gb-blink { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.3; + } +} + +/* ============================================ + Contact Controls + ============================================ */ +.gb-contact-controls { + display: flex; + align-items: center; + margin-left: auto; + padding-right: 16px; +} + +.gb-contact-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + color: rgba(255, 255, 255, 0.8); + font-size: 12px; + cursor: pointer; + transition: var(--gb-transition); +} + +.gb-contact-btn:hover { + background: rgba(255, 255, 255, 0.2); +} + +.gb-contact-btn.active { + background: var(--gb-success); + border-color: var(--gb-success); + color: white; +} + +.gb-contact-btn .gb-icon { + font-size: 14px; +} + +/* ============================================ + Processing Indicator + ============================================ */ +.gb-processing-indicator { + position: absolute; + bottom: 100%; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + padding: 10px; + background: var(--gb-bg-dark); + border-radius: var(--gb-radius) var(--gb-radius) 0 0; + color: var(--gb-text-light); + font-size: 13px; + z-index: 100; +} + +.gb-spinner { + width: 16px; + height: 16px; + border: 2px solid var(--gb-border); + border-top-color: var(--gb-primary); + border-radius: 50%; + animation: gb-spin 0.8s linear infinite; +} + +@keyframes gb-spin { + to { + transform: rotate(360deg); + } +} + +/* ============================================ + Processed Message Indicator + ============================================ */ +.gb-processed-indicator { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + color: var(--gb-success); + margin-top: 4px; + padding: 2px 6px; + background: rgba(34, 197, 94, 0.1); + border-radius: 4px; +} + +.gb-processed-message { + position: relative; +} + +.gb-processed-message::before { + content: ""; + position: absolute; + left: -4px; + top: 0; + bottom: 0; + width: 3px; + background: var(--gb-success); + border-radius: 2px; +} + +/* ============================================ + Monitored Input Indicator + ============================================ */ +.gb-monitored-input { + position: relative; +} + +.gb-monitored-input::after { + content: "✨"; + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + opacity: 0.5; + pointer-events: none; +} + +/* ============================================ + Tooltip + ============================================ */ +.gb-tooltip { + position: absolute; + background: var(--gb-bg-dark); + color: var(--gb-text-light); + padding: 8px 12px; + border-radius: var(--gb-radius); + font-size: 12px; + white-space: nowrap; + z-index: 100000; + box-shadow: var(--gb-shadow); + pointer-events: none; + animation: gb-fade-in 0.2s ease; +} + +.gb-tooltip::before { + content: ""; + position: absolute; + bottom: -6px; + left: 50%; + transform: translateX(-50%); + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid var(--gb-bg-dark); +} + +/* ============================================ + Notification Toast + ============================================ */ +.gb-toast { + position: fixed; + bottom: 20px; + right: 20px; + display: flex; + align-items: center; + gap: 12px; + padding: 14px 20px; + background: var(--gb-bg-dark); + border-radius: var(--gb-radius); + box-shadow: var(--gb-shadow-lg); + color: var(--gb-text-light); + font-size: 14px; + z-index: 999999; + animation: gb-slide-in 0.3s ease; + border-left: 4px solid var(--gb-primary); +} + +.gb-toast.success { + border-left-color: var(--gb-success); +} + +.gb-toast.error { + border-left-color: var(--gb-error); +} + +.gb-toast.warning { + border-left-color: var(--gb-warning); +} + +@keyframes gb-slide-in { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.gb-toast-close { + background: none; + border: none; + color: var(--gb-secondary); + cursor: pointer; + padding: 4px; + font-size: 18px; + line-height: 1; +} + +.gb-toast-close:hover { + color: var(--gb-text-light); +} + +/* ============================================ + Dark Mode Overrides for WhatsApp + ============================================ */ +[data-theme="dark"] #gb-control-panel, +.dark #gb-control-panel { + background: #0b141a; + border-color: #222d34; +} + +[data-theme="dark"] .gb-panel-body, +.dark .gb-panel-body { + background: #0b141a; +} + +/* ============================================ + Responsive Design + ============================================ */ +@media (max-width: 768px) { + #gb-control-panel { + width: 260px; + right: 10px; + top: 70px; + } + + .gb-correction-content { + width: 95%; + margin: 10px; + } + + .gb-auto-indicator { + font-size: 11px; + padding: 6px 12px; + } +}