720 lines
21 KiB
JavaScript
720 lines
21 KiB
JavaScript
|
|
// 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 = `
|
|||
|
|
<span class="gb-auto-dot"></span>
|
|||
|
|
<span>Auto Mode Active</span>
|
|||
|
|
`;
|
|||
|
|
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 = `
|
|||
|
|
<div class="gb-correction-content">
|
|||
|
|
<div class="gb-correction-header">
|
|||
|
|
<span class="gb-correction-icon">✨</span>
|
|||
|
|
<span>Grammar Correction</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="gb-correction-body">
|
|||
|
|
<div class="gb-text-compare">
|
|||
|
|
<div class="gb-original">
|
|||
|
|
<label>Original:</label>
|
|||
|
|
<p>${escapeHtml(original)}</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="gb-corrected">
|
|||
|
|
<label>Corrected:</label>
|
|||
|
|
<p>${escapeHtml(corrected)}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="gb-correction-actions">
|
|||
|
|
<button class="gb-btn gb-btn-secondary" id="gb-reject">Keep Original</button>
|
|||
|
|
<button class="gb-btn gb-btn-primary" id="gb-accept">Use Corrected</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
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 = `
|
|||
|
|
<button class="gb-contact-btn ${isAutoEnabled ? "active" : ""}"
|
|||
|
|
id="gb-toggle-auto"
|
|||
|
|
title="Toggle Auto Mode for this contact">
|
|||
|
|
<span class="gb-icon">🤖</span>
|
|||
|
|
<span class="gb-label">${isAutoEnabled ? "Auto ON" : "Auto OFF"}</span>
|
|||
|
|
</button>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
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 = `
|
|||
|
|
<div class="gb-panel-header">
|
|||
|
|
<img src="${chrome.runtime.getURL("icons/icon48.png")}" alt="GB" class="gb-panel-logo">
|
|||
|
|
<span>General Bots</span>
|
|||
|
|
<button class="gb-panel-toggle" id="gb-panel-toggle">−</button>
|
|||
|
|
</div>
|
|||
|
|
<div class="gb-panel-body" id="gb-panel-body">
|
|||
|
|
<div class="gb-status ${settings.authenticated ? "connected" : "disconnected"}">
|
|||
|
|
<span class="gb-status-dot"></span>
|
|||
|
|
<span>${settings.authenticated ? "Connected" : "Not Connected"}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="gb-controls">
|
|||
|
|
<label class="gb-switch-label">
|
|||
|
|
<span>Grammar Correction</span>
|
|||
|
|
<input type="checkbox" id="gb-grammar-toggle" ${settings.grammarCorrection ? "checked" : ""}>
|
|||
|
|
<span class="gb-switch"></span>
|
|||
|
|
</label>
|
|||
|
|
|
|||
|
|
<label class="gb-switch-label">
|
|||
|
|
<span>Hide Contacts</span>
|
|||
|
|
<input type="checkbox" id="gb-contacts-toggle" ${settings.hideContacts ? "checked" : ""}>
|
|||
|
|
<span class="gb-switch"></span>
|
|||
|
|
</label>
|
|||
|
|
|
|||
|
|
<label class="gb-switch-label">
|
|||
|
|
<span>Auto Mode</span>
|
|||
|
|
<input type="checkbox" id="gb-auto-toggle" ${settings.autoMode ? "checked" : ""}>
|
|||
|
|
<span class="gb-switch"></span>
|
|||
|
|
</label>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
${
|
|||
|
|
!settings.authenticated
|
|||
|
|
? `
|
|||
|
|
<div class="gb-auth-section">
|
|||
|
|
<p>Connect with your General Bots account:</p>
|
|||
|
|
<input type="text" id="gb-whatsapp-number" placeholder="Your WhatsApp number" value="${settings.whatsappNumber}">
|
|||
|
|
<button class="gb-btn gb-btn-primary" id="gb-auth-btn">Authenticate</button>
|
|||
|
|
</div>
|
|||
|
|
`
|
|||
|
|
: ""
|
|||
|
|
}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
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 = `
|
|||
|
|
<div class="gb-spinner"></div>
|
|||
|
|
<span>Processing with AI...</span>
|
|||
|
|
`;
|
|||
|
|
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);
|
|||
|
|
})();
|