Files
telegram-scraper/webui/viewer.js
T

138 lines
4.8 KiB
JavaScript

const viewerState = {
channels: [],
channelId: null,
oldestMessageId: null,
};
async function api(path) {
const response = await fetch(path);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Request failed");
}
return data;
}
function textNodeWithBreaks(text) {
const fragment = document.createDocumentFragment();
const parts = (text || "").split("\n");
parts.forEach((part, index) => {
if (index > 0) fragment.appendChild(document.createElement("br"));
fragment.appendChild(document.createTextNode(part));
});
return fragment;
}
function renderChannelList() {
const root = document.getElementById("viewer-channel-list");
root.innerHTML = "";
const template = document.getElementById("viewer-channel-template");
viewerState.channels.forEach((channel) => {
const node = template.content.firstElementChild.cloneNode(true);
node.querySelector(".viewer-channel-name").textContent = channel.name;
node.querySelector(".viewer-channel-meta").textContent = `${channel.message_count} messages`;
if (channel.channel_id === viewerState.channelId) {
node.classList.add("active");
}
node.addEventListener("click", () => {
viewerState.oldestMessageId = null;
loadChannel(channel.channel_id);
});
root.appendChild(node);
});
}
function renderMessages(payload, append = false) {
const root = document.getElementById("messages-list");
const template = document.getElementById("message-template");
if (!append) root.innerHTML = "";
const channel = payload.channel;
document.getElementById("viewer-title").textContent = channel?.name || payload.channel_id;
document.getElementById("viewer-subtitle").textContent = channel
? `${channel.message_count} messages, latest: ${channel.last_date || "-"}`
: "No local database found for this channel yet.";
payload.messages.forEach((message) => {
const node = template.content.firstElementChild.cloneNode(true);
node.dataset.messageId = String(message.message_id);
node.querySelector(".message-author").textContent = message.sender_name || "Unknown";
node.querySelector(".message-date").textContent = message.date || "";
const reply = node.querySelector(".message-reply");
if (message.reply_to) {
reply.textContent = `Reply to message #${message.reply_to}`;
}
node.querySelector(".message-text").appendChild(textNodeWithBreaks(message.text || ""));
const media = node.querySelector(".message-media");
if (message.media_url && message.media_kind === "image") {
const img = document.createElement("img");
img.src = message.media_url;
img.loading = "lazy";
media.appendChild(img);
} else if (message.media_url && message.media_kind === "video") {
const video = document.createElement("video");
video.src = message.media_url;
video.controls = true;
video.preload = "metadata";
media.appendChild(video);
} else if (message.media_url) {
const link = document.createElement("a");
link.href = message.media_url;
link.target = "_blank";
link.rel = "noreferrer";
link.textContent = "Open file";
media.appendChild(link);
}
const footerParts = [];
if (message.views) footerParts.push(`views: ${message.views}`);
if (message.forwards) footerParts.push(`forwards: ${message.forwards}`);
if (message.reactions) footerParts.push(`reactions: ${message.reactions}`);
node.querySelector(".message-footer").textContent = footerParts.join(" | ");
if (append) {
root.prepend(node);
} else {
root.appendChild(node);
}
});
if (payload.messages.length) {
viewerState.oldestMessageId = payload.messages[0].message_id;
}
document.getElementById("load-older-btn").disabled = !payload.messages.length;
}
async function loadChannel(channelId, append = false) {
viewerState.channelId = channelId;
renderChannelList();
const before = append && viewerState.oldestMessageId ? `&before=${viewerState.oldestMessageId}` : "";
const payload = await api(`/api/channels/${encodeURIComponent(channelId)}/messages?limit=80${before}`);
renderMessages(payload, append);
}
async function main() {
viewerState.channels = await api("/api/channels");
const params = new URLSearchParams(window.location.search);
viewerState.channelId = params.get("channel") || viewerState.channels[0]?.channel_id || null;
renderChannelList();
if (viewerState.channelId) {
await loadChannel(viewerState.channelId);
}
document.getElementById("load-older-btn").addEventListener("click", async () => {
if (!viewerState.channelId || !viewerState.oldestMessageId) return;
await loadChannel(viewerState.channelId, true);
});
}
main().catch((error) => {
console.error(error);
alert(error.message);
});