const sessionStatusEl = document.querySelector("#session-status"); const sessionUserEl = document.querySelector("#session-user"); const loginFeedbackEl = document.querySelector("#login-feedback"); const sessionSummaryTextEl = document.querySelector("#session-summary-text"); const sessionDetailsEl = document.querySelector("#session-details"); const toggleSessionBtn = document.querySelector("#toggle-session"); const startLoginForm = document.querySelector("#start-login-form"); const verifyLoginForm = document.querySelector("#verify-login-form"); const logoutButton = document.querySelector("#logout-button"); const hooksCountEl = document.querySelector("#hooks-count"); const hooksListEl = document.querySelector("#hooks-list"); const hookTemplate = document.querySelector("#hook-template"); const createHookForm = document.querySelector("#create-hook-form"); const toggleCreateBtn = document.querySelector("#toggle-create"); let sessionDetailsVisible = false; let sessionDetailsTouched = false; let createFormVisible = false; let createFormTouched = false; function setSessionDetailsVisibility(show, { fromUser = false } = {}) { if (!toggleSessionBtn || !sessionDetailsEl) return; sessionDetailsVisible = show; if (fromUser) { sessionDetailsTouched = true; } sessionDetailsEl.classList.toggle("hidden", !show); toggleSessionBtn.setAttribute("aria-expanded", String(show)); toggleSessionBtn.textContent = show ? "Hide session controls" : "Manage session"; } function setCreateFormVisibility(show, { fromUser = false } = {}) { if (!toggleCreateBtn) return; createFormVisible = show; if (fromUser) { createFormTouched = true; } createHookForm.classList.toggle("hidden", !show); toggleCreateBtn.setAttribute("aria-expanded", String(show)); toggleCreateBtn.textContent = show ? "Hide form" : "New Hook"; if (show) { const chatInput = document.querySelector("#hook-chat-id"); if (chatInput) { chatInput.focus(); } } } if (toggleCreateBtn) { toggleCreateBtn.addEventListener("click", () => { setCreateFormVisibility(!createFormVisible, { fromUser: true }); }); setCreateFormVisibility(false); } if (toggleSessionBtn) { toggleSessionBtn.addEventListener("click", () => { setSessionDetailsVisibility(!sessionDetailsVisible, { fromUser: true }); }); setSessionDetailsVisibility(false); } async function fetchJSON(url, options = {}) { const response = await fetch(url, { headers: { "Content-Type": "application/json" }, ...options, }); if (!response.ok) { const message = await response.text(); throw new Error(message || "Request failed"); } return response.status === 204 ? null : response.json(); } function updateSessionUI(status) { if (status.authorized) { sessionStatusEl.textContent = "Authorized"; sessionStatusEl.style.background = "rgba(100, 221, 155, 0.12)"; sessionStatusEl.style.color = "#64dd9b"; if (sessionSummaryTextEl) { sessionSummaryTextEl.textContent = status.user ? `Logged in as ${status.user}` : "Session ready"; } sessionUserEl.textContent = `Logged in as ${status.user}`; loginFeedbackEl.textContent = "Session ready. You can trigger hooks."; startLoginForm.classList.add("hidden"); verifyLoginForm.classList.add("hidden"); logoutButton.classList.remove("hidden"); } else { sessionStatusEl.textContent = status.code_sent ? "Awaiting code" : "Not authorized"; sessionStatusEl.style.background = "rgba(79, 140, 255, 0.15)"; sessionStatusEl.style.color = "#4f8cff"; if (sessionSummaryTextEl) { sessionSummaryTextEl.textContent = status.code_sent ? "Waiting for verification" : "Login required"; } sessionUserEl.textContent = status.phone_number ? `Phone number: ${status.phone_number}` : "Set a phone number to begin"; logoutButton.classList.add("hidden"); if (status.code_sent) { verifyLoginForm.classList.remove("hidden"); loginFeedbackEl.textContent = "Code sent. Check your Telegram messages."; } else { verifyLoginForm.classList.add("hidden"); loginFeedbackEl.textContent = "Start by sending a login code to your phone."; } startLoginForm.classList.remove("hidden"); } const shouldShowDetails = !status.authorized || status.code_sent; if (!sessionDetailsTouched) { setSessionDetailsVisibility(shouldShowDetails); } else if (shouldShowDetails && !sessionDetailsVisible) { setSessionDetailsVisibility(true); } } async function refreshAll() { try { const status = await fetchJSON("/api/status"); updateSessionUI(status); } catch (error) { loginFeedbackEl.textContent = `Failed to refresh status: ${error.message}`; } await loadHooks(); } async function loadHooks() { try { const hooks = await fetchJSON("/api/hooks"); hooksCountEl.textContent = hooks.length; hooksListEl.innerHTML = ""; if (!hooks.length) { if (!createFormTouched) { setCreateFormVisibility(true); } const empty = document.createElement("p"); empty.className = "feedback"; empty.textContent = "No hooks yet. Use the New Hook button above to create one."; hooksListEl.appendChild(empty); return; } hooks.forEach((hook) => { const node = hookTemplate.content.cloneNode(true); node.querySelector("h3").textContent = hook.chat_id; node.querySelector(".hook-date").textContent = new Date(hook.created_at).toLocaleString(); const lastRunEl = node.querySelector(".hook-last-run"); if (lastRunEl) { lastRunEl.textContent = hook.last_triggered_at ? `Last triggered ${new Date(hook.last_triggered_at).toLocaleString()}` : "Never triggered yet"; } node.querySelector(".hook-message").textContent = hook.message; node.querySelector(".hook-url").textContent = hook.action_url; node.querySelector(".hook-id").textContent = hook.hook_id; const feedbackEl = node.querySelector(".hook-feedback"); const editForm = node.querySelector(".edit-hook-form"); const editIdInput = editForm.querySelector(".edit-id"); const editChatInput = editForm.querySelector(".edit-chat"); const editMessageInput = editForm.querySelector(".edit-message"); const saveEditBtn = editForm.querySelector(".save-edit"); const cancelEditBtn = editForm.querySelector(".cancel-edit"); const editDetailsBtn = node.querySelector(".edit-details"); const setFeedback = (text = "", color = "") => { feedbackEl.textContent = text; feedbackEl.style.color = color; }; const toggleEditForm = (show) => { editForm.classList.toggle("hidden", !show); editDetailsBtn.disabled = show; if (show) { editIdInput.value = hook.hook_id; editChatInput.value = hook.chat_id; editMessageInput.value = hook.message; requestAnimationFrame(() => { editIdInput.focus(); }); } }; editDetailsBtn.addEventListener("click", () => { toggleEditForm(true); }); cancelEditBtn.addEventListener("click", () => { toggleEditForm(false); setFeedback(); }); const copyBtn = node.querySelector(".copy"); copyBtn.addEventListener("click", async () => { try { await navigator.clipboard.writeText(hook.action_url); setFeedback("Hook URL copied to clipboard.", "#64dd9b"); setTimeout(() => setFeedback(), 2000); } catch (err) { setFeedback(`Copy failed: ${err.message}`, "#ffbac7"); setTimeout(() => setFeedback(), 2500); } }); const triggerBtn = node.querySelector(".trigger"); triggerBtn.addEventListener("click", async () => { triggerBtn.disabled = true; setFeedback("Sending message…"); try { const result = await fetchJSON(`/action/${hook.hook_id}`); setFeedback(`Status: ${result.status}`, "#64dd9b"); } catch (err) { setFeedback(`Failed: ${err.message}`, "#ffbac7"); } finally { setTimeout(() => { triggerBtn.disabled = false; setFeedback(); }, 2500); } }); editForm.addEventListener("submit", async (event) => { event.preventDefault(); const updatedId = editIdInput.value.trim(); const updatedChat = editChatInput.value.trim(); const updatedMessage = editMessageInput.value.trim(); if (!updatedId || !updatedChat || !updatedMessage) { setFeedback("Hook ID, chat ID, and message are required.", "#ffbac7"); return; } saveEditBtn.disabled = true; cancelEditBtn.disabled = true; const originalSaveText = saveEditBtn.textContent; saveEditBtn.textContent = "Saving…"; setFeedback(); try { await fetchJSON(`/api/hooks/${hook.hook_id}`, { method: "PATCH", body: JSON.stringify({ hook_id: updatedId, chat_id: updatedChat, message: updatedMessage }), }); setFeedback("Hook updated.", "#64dd9b"); toggleEditForm(false); await loadHooks(); } catch (err) { setFeedback(`Update failed: ${err.message}`, "#ffbac7"); } finally { saveEditBtn.disabled = false; cancelEditBtn.disabled = false; saveEditBtn.textContent = originalSaveText; setTimeout(() => { setFeedback(); }, 2500); } }); const deleteBtn = node.querySelector(".delete"); deleteBtn.addEventListener("click", async () => { if (!confirm("Delete this hook?")) return; try { await fetchJSON(`/api/hooks/${hook.hook_id}`, { method: "DELETE" }); await loadHooks(); } catch (err) { alert(`Failed to delete hook: ${err.message}`); } }); hooksListEl.appendChild(node); }); } catch (error) { hooksListEl.innerHTML = ""; const para = document.createElement("p"); para.className = "feedback"; para.textContent = `Failed to load hooks: ${error.message}`; hooksListEl.appendChild(para); } } startLoginForm.addEventListener("submit", async (event) => { event.preventDefault(); const phoneNumber = document.querySelector("#phone-number").value || null; loginFeedbackEl.textContent = "Sending code…"; try { const status = await fetchJSON("/api/login/start", { method: "POST", body: JSON.stringify({ phone_number: phoneNumber }), }); updateSessionUI(status); loginFeedbackEl.textContent = "Code sent successfully."; } catch (error) { loginFeedbackEl.textContent = `Failed to send code: ${error.message}`; } }); verifyLoginForm.addEventListener("submit", async (event) => { event.preventDefault(); const code = document.querySelector("#verification-code").value; const password = document.querySelector("#twofactor-password").value || null; loginFeedbackEl.textContent = "Verifying…"; try { const status = await fetchJSON("/api/login/verify", { method: "POST", body: JSON.stringify({ code, password }), }); updateSessionUI(status); loginFeedbackEl.textContent = status.authorized ? "Login completed." : "Enter your password to finish."; } catch (error) { loginFeedbackEl.textContent = `Verification failed: ${error.message}`; } }); logoutButton.addEventListener("click", async () => { try { const status = await fetchJSON("/api/logout", { method: "POST" }); updateSessionUI(status); loginFeedbackEl.textContent = "Logged out."; } catch (error) { loginFeedbackEl.textContent = `Logout failed: ${error.message}`; } }); createHookForm.addEventListener("submit", async (event) => { event.preventDefault(); const chatId = document.querySelector("#hook-chat-id").value.trim(); const message = document.querySelector("#hook-message").value.trim(); if (!chatId || !message) { alert("Chat ID and message are required."); return; } const submitBtn = createHookForm.querySelector("button[type='submit']"); const originalText = submitBtn.textContent; submitBtn.disabled = true; submitBtn.textContent = "Creating…"; try { await fetchJSON("/api/hooks", { method: "POST", body: JSON.stringify({ chat_id: chatId, message }), }); createHookForm.reset(); await loadHooks(); } catch (error) { alert(`Failed to create hook: ${error.message}`); } finally { submitBtn.disabled = false; submitBtn.textContent = originalText; } }); window.addEventListener("DOMContentLoaded", async () => { await refreshAll(); try { const status = await fetchJSON("/api/status"); const phoneInput = document.querySelector("#phone-number"); if (status.phone_number && !phoneInput.value) { phoneInput.value = status.phone_number; } } catch (err) { console.warn("Unable to preload phone number", err); } });