From eb69a89d912c9530e31866671a5500e89f3681e2 Mon Sep 17 00:00:00 2001 From: mr-forust Date: Sat, 27 Jun 2026 15:20:56 +0200 Subject: [PATCH] feat: add multi-account message viewer support - viewer.html shows account name in header - viewer.js loads account list from /api/accounts, auto-selects first account - Account-aware API endpoints (/api/accounts/{id}/... or legacy /api/...) - Chat list redesign with avatar initials, preview text, time, message count - loadViewerAccount() helper fetches account info and user ID --- webui/viewer.html | 16 +++++++-- webui/viewer.js | 86 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/webui/viewer.html b/webui/viewer.html index 1dfe75e..c69d597 100644 --- a/webui/viewer.html +++ b/webui/viewer.html @@ -4,7 +4,7 @@ Telegram Scraper Viewer - +
@@ -13,6 +13,7 @@
Export Viewer

Messages

+

Account: Loading...

Dashboard @@ -36,8 +37,17 @@ diff --git a/webui/viewer.js b/webui/viewer.js index 4e211b0..fe62648 100644 --- a/webui/viewer.js +++ b/webui/viewer.js @@ -1,6 +1,8 @@ if ("scrollRestoration" in history) history.scrollRestoration = "manual"; const viewerState = { + accountId: null, + accounts: [], channels: [], channelId: null, oldestMessageId: null, @@ -42,6 +44,24 @@ function formatTime(dateStr) { return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }); } +function chatListTime(dateStr) { + if (!dateStr) return ""; + const d = new Date(dateStr.replace(" ", "T")); + if (isNaN(d.getTime())) return ""; + const now = new Date(); + if (d.toDateString() === now.toDateString()) { + return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }); + } + return d.toLocaleDateString("en-US", { month: "short", day: "numeric" }); +} + +function initials(value) { + const source = String(value || "?").replace(/^@/, "").trim(); + const words = source.split(/\s+/).filter(Boolean); + if (words.length > 1) return (words[0][0] + words[1][0]).toUpperCase(); + return source.slice(0, 2).toUpperCase(); +} + function dateKey(dateStr) { if (!dateStr) return ""; return dateStr.slice(0, 10); @@ -75,8 +95,14 @@ function renderChannelList() { if (!template) return; viewerState.channels.forEach((channel) => { const node = template.content.firstElementChild.cloneNode(true); + const preview = + channel.last_message_preview || + (channel.has_database ? "No text in the last saved message" : "No local database yet"); + node.querySelector(".viewer-channel-avatar").textContent = initials(channel.name || channel.channel_id); node.querySelector(".viewer-channel-name").textContent = channel.name; - node.querySelector(".viewer-channel-meta").textContent = `${channel.message_count} messages`; + node.querySelector(".viewer-channel-time").textContent = chatListTime(channel.last_date); + node.querySelector(".viewer-channel-preview").textContent = preview; + node.querySelector(".viewer-channel-count").textContent = String(channel.message_count || 0); if (channel.channel_id === viewerState.channelId) { node.classList.add("active"); } @@ -90,6 +116,50 @@ function renderChannelList() { }); } +function messageEndpoint(channelId, before = "") { + if (viewerState.accountId) { + return `/api/accounts/${encodeURIComponent(viewerState.accountId)}/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`; + } + return `/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`; +} + +function channelsEndpoint() { + if (viewerState.accountId) { + return `/api/accounts/${encodeURIComponent(viewerState.accountId)}/channels`; + } + return "/api/channels"; +} + +async function loadViewerAccount(params) { + const requested = params.get("account"); + try { + const payload = await api("/api/accounts"); + viewerState.accounts = payload.accounts || []; + } catch { + viewerState.accounts = []; + } + viewerState.accountId = + requested || + viewerState.accounts[0]?.id || + null; + + const accountName = + viewerState.accounts.find((item) => item.id === viewerState.accountId)?.label || + viewerState.accountId || + "Legacy"; + const accountEl = document.getElementById("viewer-account-name"); + if (accountEl) accountEl.textContent = accountName; + + try { + const authData = viewerState.accountId + ? await api(`/api/accounts/${encodeURIComponent(viewerState.accountId)}/auth`) + : await api("/api/auth"); + viewerState.userId = authData.user_id || null; + } catch (e) { + console.warn("Could not fetch user ID:", e); + } +} + function buildMessageNode(message, root) { const template = document.getElementById("message-template"); const wrap = template.content.firstElementChild.cloneNode(true); @@ -240,9 +310,7 @@ async function loadChannel(channelId, append = false) { ? `&before=${viewerState.oldestMessageId}` : ""; try { - const payload = await api( - `/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}` - ); + const payload = await api(messageEndpoint(channelId, before)); if (append) { const list = document.getElementById("messages-list"); const prevScrollHeight = list.scrollHeight; @@ -285,15 +353,9 @@ function setupInfiniteScroll() { } async function main() { - try { - const authData = await api("/api/auth"); - viewerState.userId = authData.user_id || null; - } catch (e) { - console.warn("Could not fetch user ID:", e); - } - - viewerState.channels = await api("/api/channels"); const params = new URLSearchParams(window.location.search); + await loadViewerAccount(params); + viewerState.channels = await api(channelsEndpoint()); viewerState.channelId = params.get("channel") || viewerState.channels[0]?.channel_id ||