Save chats, messages and media to IndexedDB
This commit is contained in:
parent
819c16d02f
commit
3a1ed0903c
9 changed files with 348 additions and 67 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" user-scalable=no>
|
||||||
<title>Pandora</title>
|
<title>Pandora</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<link rel="stylesheet" href="loading.css">
|
<link rel="stylesheet" href="loading.css">
|
||||||
<link rel="stylesheet" href="metroicons.css">
|
<link rel="stylesheet" href="metroicons.css">
|
||||||
<script src="https://unpkg.com/lucide@latest" defer></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script defer>
|
<script defer>
|
||||||
|
|
@ -153,6 +152,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Client-side script loaded as ES Module -->
|
<!-- Client-side script loaded as ES Module -->
|
||||||
<script type="module" src="app.js"></script>
|
<script type="module" src="./js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,31 @@ import { waha } from "./waha.js";
|
||||||
import { ui, elements } from "./ui.js";
|
import { ui, elements } from "./ui.js";
|
||||||
import { websocket } from "./websocket.js";
|
import { websocket } from "./websocket.js";
|
||||||
import { compensateMessageOrdering, formatTime } from "./utils.js";
|
import { compensateMessageOrdering, formatTime } from "./utils.js";
|
||||||
|
import { fetchChats, getAppUser, getChatMessages, getChats } from "./storage.js";
|
||||||
|
import { upsertMessages } from "./db.js";
|
||||||
|
|
||||||
let chatsState = [];
|
|
||||||
let activeChatState = null;
|
let activeChatState = null;
|
||||||
let userInfo;
|
let userInfo;
|
||||||
const messageTone = new Audio("./message.ogg");
|
const messageTone = new Audio("./message.ogg");
|
||||||
|
|
||||||
// Initialize app when DOM is ready
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
try {
|
try {
|
||||||
userInfo = await waha.getMyInfo();
|
elements.loggedUserName.textContent = await getAppUser().pushName;
|
||||||
fetchChats();
|
loadChats();
|
||||||
checkWahaStatus();
|
checkWahaStatus();
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
|
} finally {
|
||||||
|
elements.chatsLoader.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadChats() {
|
||||||
|
try {
|
||||||
|
elements.chatsLoader.classList.remove('hidden');
|
||||||
|
fetchChats().then(() => {
|
||||||
|
ui.renderChatList(getChats(), activeChatState, selectChat);
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load chats:', error);
|
console.error('Failed to load chats:', error);
|
||||||
elements.chatList.innerHTML = `
|
elements.chatList.innerHTML = `
|
||||||
|
|
@ -31,7 +42,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
} finally {
|
} finally {
|
||||||
elements.chatsLoader.classList.add('hidden');
|
elements.chatsLoader.classList.add('hidden');
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
function setupEventListeners() {
|
function setupEventListeners() {
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
|
|
@ -43,16 +54,15 @@ function setupEventListeners() {
|
||||||
|
|
||||||
elements.refreshChatsBtn.addEventListener('click', () => {
|
elements.refreshChatsBtn.addEventListener('click', () => {
|
||||||
elements.refreshChatsBtn.classList.add('spinning');
|
elements.refreshChatsBtn.classList.add('spinning');
|
||||||
fetchChats().finally(() => {
|
loadChats()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
elements.refreshChatsBtn.classList.remove('spinning');
|
elements.refreshChatsBtn.classList.remove('spinning');
|
||||||
}, 600);
|
}, 600);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
elements.chatSearch.addEventListener('input', (e) => {
|
elements.chatSearch.addEventListener('input', (e) => {
|
||||||
const query = e.target.value.toLowerCase();
|
const query = e.target.value.toLowerCase();
|
||||||
const filtered = chatsState.filter(chat =>
|
const filtered = getChats().filter(chat =>
|
||||||
chat.name.toLowerCase().includes(query)
|
chat.name.toLowerCase().includes(query)
|
||||||
);
|
);
|
||||||
ui.renderChatList(filtered, activeChatState, selectChat);
|
ui.renderChatList(filtered, activeChatState, selectChat);
|
||||||
|
|
@ -99,6 +109,7 @@ function initWebSocket() {
|
||||||
const ev = data.event;
|
const ev = data.event;
|
||||||
if (ev === 'message' || ev === 'message.any' || ev === 'message.ack') {
|
if (ev === 'message' || ev === 'message.any' || ev === 'message.ack') {
|
||||||
handleIncomingMessage(data.payload);
|
handleIncomingMessage(data.payload);
|
||||||
|
upsertMessages([data.payload]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +126,7 @@ function handleIncomingMessage(msg) {
|
||||||
if (!msg) return;
|
if (!msg) return;
|
||||||
|
|
||||||
// console.log('[WS] handleIncomingMessage payload:', msg);
|
// console.log('[WS] handleIncomingMessage payload:', msg);
|
||||||
|
|
||||||
ui.updateChatInChatList(msg);
|
ui.updateChatInChatList(msg);
|
||||||
|
|
||||||
const rawChatId = msg.chatId || msg.from || (msg.chat && msg.chat.id);
|
const rawChatId = msg.chatId || msg.from || (msg.chat && msg.chat.id);
|
||||||
|
|
@ -139,48 +150,15 @@ function handleIncomingMessage(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatIndex = chatsState.findIndex(c => c.id === msgChatId);
|
const chatIndex = getChats().findIndex(c => c.id === msgChatId);
|
||||||
if (chatIndex !== -1) {
|
if (chatIndex !== -1) {
|
||||||
const chat = chatsState[chatIndex];
|
const chat = getChats()[chatIndex];
|
||||||
chat.lastMessage = msg.body || msg.text || 'Media message';
|
chat.lastMessage = msg.body || msg.text || 'Media message';
|
||||||
chat.timestamp = msg.timestamp ? (msg.timestamp * 1000) : Date.now();
|
chat.timestamp = msg.timestamp ? (msg.timestamp * 1000) : Date.now();
|
||||||
|
|
||||||
if (!msg.fromMe && (!activeChatState || activeChatState.id !== msgChatId)) {
|
if (!msg.fromMe && (!activeChatState || activeChatState.id !== msgChatId)) {
|
||||||
chat.unreadCount = (chat.unreadCount || 0) + 1;
|
chat.unreadCount = (chat.unreadCount || 0) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
chatsState.sort((a, b) => {
|
|
||||||
const tA = new Date(a.timestamp).getTime();
|
|
||||||
const tB = new Date(b.timestamp).getTime();
|
|
||||||
return tB - tA;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ui.renderChatList(chatsState, activeChatState, selectChat);
|
|
||||||
} else {
|
|
||||||
// fetchChats();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchChats() {
|
|
||||||
try {
|
|
||||||
elements.loggedUserName.textContent = userInfo.pushName;
|
|
||||||
// elements.userIcon.innerText = userInfo.pushName[0];
|
|
||||||
elements.chatsLoader.classList.remove('hidden');
|
|
||||||
chatsState = await waha.getChats();
|
|
||||||
ui.renderChatList(chatsState, activeChatState, selectChat);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load chats:', error);
|
|
||||||
elements.chatList.innerHTML = `
|
|
||||||
<li class="loading-chats" style="color: var(--text-primary); text-align: center; padding: 20px;">
|
|
||||||
<p>Connection to WAHA failed.</p>
|
|
||||||
<p style="font-size: 0.75rem; color: var(--text-primary); margin-top: 8px;">
|
|
||||||
Ensure WAHA server is running and CORS is enabled, or click Settings to configure.
|
|
||||||
</p>
|
|
||||||
<p style="font-size: 0.75rem; color: var(--text-muted); margin-top: 8px;">${error.message}</p>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
} finally {
|
|
||||||
elements.chatsLoader.classList.add('hidden');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,9 +191,9 @@ async function selectChat(chat) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rawMessages = await waha.getChatMessages(chat.id);
|
const rawMessages = await getChatMessages(chat.id);
|
||||||
const processedMessages = compensateMessageOrdering(rawMessages);
|
const processedMessages = compensateMessageOrdering(rawMessages);
|
||||||
ui.renderMessages(processedMessages, chat.name, userInfo.id, chat.id);
|
ui.renderMessages(processedMessages, chat.name, getAppUser().id, chat.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load messages:', error);
|
console.error('Failed to load messages:', error);
|
||||||
elements.messagesContainer.innerHTML = '<div class="loading-chats">Error loading messages</div>';
|
elements.messagesContainer.innerHTML = '<div class="loading-chats">Error loading messages</div>';
|
||||||
|
|
@ -276,13 +254,6 @@ async function sendMessage() {
|
||||||
|
|
||||||
activeChatState.lastMessage = text;
|
activeChatState.lastMessage = text;
|
||||||
activeChatState.timestamp = new Date();
|
activeChatState.timestamp = new Date();
|
||||||
|
|
||||||
chatsState.sort((a, b) => {
|
|
||||||
const tA = new Date(a.timestamp).getTime();
|
|
||||||
const tB = new Date(b.timestamp).getTime();
|
|
||||||
return tB - tA;
|
|
||||||
});
|
|
||||||
// ui.renderChatList(chatsState, activeChatState, selectChat);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to send message:', error);
|
console.error('Failed to send message:', error);
|
||||||
const tempBubble = document.getElementById(tempMsg.id);
|
const tempBubble = document.getElementById(tempMsg.id);
|
||||||
|
|
@ -337,7 +308,7 @@ function saveSettings() {
|
||||||
elements.inputApiKey.value
|
elements.inputApiKey.value
|
||||||
);
|
);
|
||||||
ui.toggleModal(false);
|
ui.toggleModal(false);
|
||||||
fetchChats();
|
loadChats();
|
||||||
checkWahaStatus();
|
checkWahaStatus();
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
}
|
}
|
||||||
204
js/db.js
Normal file
204
js/db.js
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
const DB_NAME = "pandora";
|
||||||
|
const DB_VERSION = 6;
|
||||||
|
let dbPromise = null;
|
||||||
|
|
||||||
|
function normalizeId(raw) {
|
||||||
|
if (!raw) return null;
|
||||||
|
if (typeof raw === 'object') {
|
||||||
|
return raw._serialized || raw.user || JSON.stringify(raw);
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDb() {
|
||||||
|
if (dbPromise) return dbPromise;
|
||||||
|
|
||||||
|
dbPromise = new Promise((resolve, reject) => {
|
||||||
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
|
|
||||||
|
req.onupgradeneeded = (e) => {
|
||||||
|
const db = req.result;
|
||||||
|
const tx = req.transaction;
|
||||||
|
|
||||||
|
if (!db.objectStoreNames.contains("chats")) {
|
||||||
|
const store = db.createObjectStore("chats", { keyPath: "id" });
|
||||||
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
let msgStore;
|
||||||
|
if (!db.objectStoreNames.contains("messages")) {
|
||||||
|
msgStore = db.createObjectStore("messages", { keyPath: "id" });
|
||||||
|
msgStore.createIndex("from", "from", { unique: false });
|
||||||
|
msgStore.createIndex("fingerprint", ["from", "timestamp"], { unique: false });
|
||||||
|
} else {
|
||||||
|
msgStore = tx.objectStore("messages");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!msgStore.indexNames.contains("chatId_timestamp")) {
|
||||||
|
msgStore.createIndex("chatId_timestamp", ["chatId", "timestamp"], { unique: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.objectStoreNames.contains("messages")) {
|
||||||
|
msgStore.openCursor().onsuccess = (event) => {
|
||||||
|
const cursor = event.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
const m = cursor.value;
|
||||||
|
const from = normalizeId(m.from);
|
||||||
|
const to = normalizeId(m.to);
|
||||||
|
const chatId = normalizeId(m.chatId) || (m.fromMe ? to : from);
|
||||||
|
if (!m.chatId && chatId) {
|
||||||
|
m.chatId = chatId;
|
||||||
|
cursor.update(m);
|
||||||
|
}
|
||||||
|
cursor.continue();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!db.objectStoreNames.contains("media")) {
|
||||||
|
db.createObjectStore("media", { keyPath: "reqId" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
req.onsuccess = () => resolve(req.result);
|
||||||
|
req.onerror = () => reject(req.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return dbPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertChats(chats) {
|
||||||
|
const db = await openDb();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("chats", "readwrite");
|
||||||
|
const store = tx.objectStore("chats");
|
||||||
|
|
||||||
|
for (const c of chats) {
|
||||||
|
store.put({
|
||||||
|
id: c.id,
|
||||||
|
name: c.name,
|
||||||
|
lastMessage: c.lastMessage,
|
||||||
|
timestamp: c.timestamp,
|
||||||
|
unreadCount: c.unreadCount ?? 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.oncomplete = () => resolve();
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadChatsSorted() {
|
||||||
|
const db = await openDb();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("chats", "readonly");
|
||||||
|
const store = tx.objectStore("chats");
|
||||||
|
const idx = store.index("timestamp");
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
idx.openCursor(null, "prev").onsuccess = (e) => {
|
||||||
|
const cursor = e.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
result.push(cursor.value);
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapMessage(m) {
|
||||||
|
const from = normalizeId(m.from);
|
||||||
|
const to = normalizeId(m.to);
|
||||||
|
const chatId = normalizeId(m.chatId) || (m.fromMe ? to : from);
|
||||||
|
|
||||||
|
return {
|
||||||
|
_data: m._data,
|
||||||
|
id: m.id,
|
||||||
|
timestamp: m.timestamp,
|
||||||
|
body: m.body,
|
||||||
|
from: from,
|
||||||
|
fromMe: m.fromMe,
|
||||||
|
ack: m.ack,
|
||||||
|
hasMedia: m.hasMedia,
|
||||||
|
media: m.media,
|
||||||
|
chatId: chatId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertMessages(messages) {
|
||||||
|
const db = await openDb();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("messages", "readwrite");
|
||||||
|
const store = tx.objectStore("messages");
|
||||||
|
|
||||||
|
for (const m of messages) {
|
||||||
|
store.put(mapMessage(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.oncomplete = () => resolve();
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadLatestMessages(chatId, limit = 50) {
|
||||||
|
const db = await openDb();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("messages", "readonly");
|
||||||
|
const store = tx.objectStore("messages");
|
||||||
|
const idx = store.index("chatId_timestamp");
|
||||||
|
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
const range = IDBKeyRange.bound([chatId, -Infinity], [chatId, Infinity]);
|
||||||
|
|
||||||
|
idx.openCursor(range, "prev").onsuccess = (e) => {
|
||||||
|
const cursor = e.target.result;
|
||||||
|
if (!cursor) return resolve(out);
|
||||||
|
|
||||||
|
out.push(cursor.value);
|
||||||
|
if (out.length >= limit) resolve(out);
|
||||||
|
else cursor.continue();
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertMedia(reqId, blob, filename) {
|
||||||
|
const db = await openDb();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("media", "readwrite");
|
||||||
|
const store = tx.objectStore("media");
|
||||||
|
|
||||||
|
store.put({
|
||||||
|
reqId: reqId,
|
||||||
|
blob: blob,
|
||||||
|
filename: filename
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.oncomplete = () => resolve();
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadMedia(reqId) {
|
||||||
|
const db = await openDb();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("media", "readonly");
|
||||||
|
const store = tx.objectStore("media");
|
||||||
|
const req = store.get(reqId);
|
||||||
|
|
||||||
|
req.onsuccess = () => resolve(req.result);
|
||||||
|
req.onerror = () => reject(req.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
108
js/storage.js
Normal file
108
js/storage.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { loadChatsSorted, loadLatestMessages, loadMedia, upsertChats, upsertMedia, upsertMessages } from "./db.js";
|
||||||
|
import { waha } from "./waha.js";
|
||||||
|
|
||||||
|
let user;
|
||||||
|
let chats = [];
|
||||||
|
|
||||||
|
export async function fetchChats() {
|
||||||
|
try {
|
||||||
|
await getRemoteChats()
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
chats = await loadChatsSorted();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRemoteChats() {
|
||||||
|
const u = await waha.getChats();
|
||||||
|
|
||||||
|
const mapped = u.map(chat => ({
|
||||||
|
id: chat.id,
|
||||||
|
name: chat.name,
|
||||||
|
lastMessage: chat.lastMessage,
|
||||||
|
timestamp: chat.timestamp,
|
||||||
|
unreadCount: chat.unreadCount ?? 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
await upsertChats(mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUsers() {
|
||||||
|
return chats.filter(c => c.id.endsWith("@c.us"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGroups() {
|
||||||
|
return chats.filter(c => c.id.endsWith("@g.us"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUser(number) {
|
||||||
|
return users.find(user => user.id === number);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChats() {
|
||||||
|
return chats;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAppUser() {
|
||||||
|
if (user) {
|
||||||
|
return user;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return await waha.getMyInfo();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return {
|
||||||
|
pushName: "Unknown",
|
||||||
|
id: "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMessage(chatId, msgId, downloadMedia) {
|
||||||
|
try {
|
||||||
|
const newMessage = await waha.getSingleChatMessage(chatId, msgId, downloadMedia);
|
||||||
|
upsertMessages([newMessage]);
|
||||||
|
return newMessage;
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
id: `${Date.now()}-temp`,
|
||||||
|
body: "You're offline",
|
||||||
|
from: "system",
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMedia(reqId) {
|
||||||
|
try {
|
||||||
|
const media = await waha.downloadMedia(reqId);
|
||||||
|
upsertMedia(reqId, media.blob, media.filename);
|
||||||
|
return media;
|
||||||
|
} catch (error) {
|
||||||
|
const cached = await loadMedia(reqId);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getChatMessages(chatId) {
|
||||||
|
try {
|
||||||
|
const newMessages = await waha.getChatMessages(chatId);
|
||||||
|
upsertMessages(newMessages);
|
||||||
|
return newMessages;
|
||||||
|
} catch (error) {
|
||||||
|
return await loadLatestMessages(chatId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getChatPicture(chatId) {
|
||||||
|
try {
|
||||||
|
return await waha.getChatPicture(chatId);
|
||||||
|
} catch (error) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { formatTime } from "./utils.js";
|
import { formatTime } from "./utils.js";
|
||||||
import { waha } from "./waha.js";
|
import { waha } from "./waha.js";
|
||||||
import { config } from "./config.js";
|
import { config } from "./config.js";
|
||||||
|
import { getChatPicture, getMessage, getMedia } from "./storage.js";
|
||||||
|
|
||||||
// DOM Selector Cache
|
// DOM Selector Cache
|
||||||
export const elements = {
|
export const elements = {
|
||||||
|
|
@ -92,12 +93,11 @@ export const ui = {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const chat of chats) {
|
for (const chat of chats) {
|
||||||
console.log(chat);
|
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = `chat-item ${activeChat && activeChat.id === chat.id ? 'active' : ''}`;
|
li.className = `chat-item ${activeChat && activeChat.id === chat.id ? 'active' : ''}`;
|
||||||
li.dataset.id = chat.id;
|
li.dataset.id = chat.id;
|
||||||
|
|
||||||
const picture = await waha.getChatPicture(chat.id);
|
const picture = await getChatPicture(chat.id);
|
||||||
const initials = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?';
|
const initials = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?';
|
||||||
const hasUnread = chat.unreadCount && chat.unreadCount > 0;
|
const hasUnread = chat.unreadCount && chat.unreadCount > 0;
|
||||||
const timeStr = formatTime(chat.timestamp || new Date());
|
const timeStr = formatTime(chat.timestamp || new Date());
|
||||||
|
|
@ -141,7 +141,6 @@ export const ui = {
|
||||||
this.appendSingleMessage(msg, activeChatName, userID, chatId);
|
this.appendSingleMessage(msg, activeChatName, userID, chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
lucide.createIcons();
|
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -171,6 +170,7 @@ export const ui = {
|
||||||
},
|
},
|
||||||
|
|
||||||
generateMessage(msg, activeChatName, userID, chatId, isLocal = false) {
|
generateMessage(msg, activeChatName, userID, chatId, isLocal = false) {
|
||||||
|
console.log(msg);
|
||||||
const isOutgoing = msg.fromMe || msg.sender === 'me';
|
const isOutgoing = msg.fromMe || msg.sender === 'me';
|
||||||
|
|
||||||
function getPrevMessageElem() {
|
function getPrevMessageElem() {
|
||||||
|
|
@ -238,12 +238,11 @@ export const ui = {
|
||||||
const clickListener = async (e) => {
|
const clickListener = async (e) => {
|
||||||
a.removeEventListener('click', clickListener);
|
a.removeEventListener('click', clickListener);
|
||||||
a.innerText = `[Downloading]`;
|
a.innerText = `[Downloading]`;
|
||||||
const mediaMsg = msg.media ? msg : await waha.getSingleChatMessage(chatId, msg.id, true);
|
const mediaMsg = msg.media ? msg : await getMessage(chatId, msg.id, true);
|
||||||
console.log(mediaMsg.media.url);
|
|
||||||
const url = new URL(mediaMsg.media.url);
|
const url = new URL(mediaMsg.media.url);
|
||||||
const reqID = url.pathname.split('/').filter(Boolean).pop();
|
const reqID = url.pathname.split('/').filter(Boolean).pop();
|
||||||
|
|
||||||
const { blob, filename } = await waha.downloadMedia(reqID);
|
const { blob, filename } = await getMedia(reqID);
|
||||||
|
|
||||||
const objectUrl = URL.createObjectURL(blob);
|
const objectUrl = URL.createObjectURL(blob);
|
||||||
e.target.href = objectUrl;
|
e.target.href = objectUrl;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue