@@ -93,13 +107,12 @@
@@ -112,7 +125,6 @@
@@ -127,7 +139,7 @@
-
⚠️ Note: Requests are sent directly from your browser to the WAHA server. Make sure CORS is enabled on your WAHA instance.
+
Note: Requests are sent directly from your browser to the WAHA server. Make sure CORS is enabled on your WAHA instance.
`;
-
+
li.addEventListener('click', () => onChatSelect(chat));
elements.chatList.appendChild(li);
});
},
-
+
/**
- * Render chat message log inside chat view container
- */
- renderMessages(messages, activeChatName) {
+ * Render chat message log inside chat view container
+ */
+ async renderMessages(messages, activeChatName, userID) {
elements.messagesContainer.innerHTML = '';
if (messages.length === 0) {
elements.messagesContainer.innerHTML = '
No messages. Say hello!
';
return;
}
-
- messages.forEach(msg => {
- const isOutgoing = msg.fromMe || msg.sender === 'me';
- const groupDiv = document.createElement('div');
- groupDiv.className = `message-group ${isOutgoing ? 'outgoing' : 'incoming'}`;
- groupDiv.id = msg.id;
-
- const senderName = isOutgoing ? 'Me' : (msg.from || activeChatName);
- const timeStr = formatTime(msg.timestamp || new Date());
-
- let statusCheck = '';
- if (isOutgoing) {
- if (msg.status === 'read') {
- statusCheck = '
';
- } else if (msg.status === 'delivered') {
- statusCheck = '
';
- } else {
- statusCheck = '
';
- }
- }
-
- groupDiv.innerHTML = `
-
-
- ${!isOutgoing ? `
${senderName}` : ''}
- ${msg.body || msg.text || "
Empty message, maybe something shows up on your phone..."}
-
- ${timeStr}
- ${statusCheck}
-
-
- `;
-
- elements.messagesContainer.appendChild(groupDiv);
- });
-
+
+ for (const msg of messages) {
+ this.appendSingleMessage(msg, activeChatName, userID);
+ }
+
lucide.createIcons();
this.scrollToBottom();
},
-
+
/**
- * Append a single message (used for optimistic updates immediately upon sending)
- */
- appendSingleMessage(msg, activeChatName) {
- if (elements.messagesContainer.querySelector('.loading-chats')) {
- elements.messagesContainer.innerHTML = '';
- }
-
+ * Append a single message (used for optimistic updates immediately upon sending)
+ */
+ appendSingleMessage(msg, activeChatName, userID) {
+ console.log(msg);
const isOutgoing = msg.fromMe || msg.sender === 'me';
+
const groupDiv = document.createElement('div');
groupDiv.className = `message-group ${isOutgoing ? 'outgoing' : 'incoming'}`;
groupDiv.id = msg.id;
+ groupDiv.dataset.from = msg.participant || msg.from;
- const senderName = isOutgoing ? 'Me' : activeChatName;
+ const senderName = isOutgoing ? userID : (msg._data.notifyName || activeChatName);
const timeStr = formatTime(msg.timestamp || new Date());
- groupDiv.innerHTML = `
- ${!isOutgoing ? `
${senderName}` : ''}
-
- ${msg.body || msg.text}
-
- ${timeStr}
-
-
-
- `;
-
+ let statusCheck = '';
+ if (isOutgoing) {
+ if (msg.status === 'read') {
+ statusCheck = '
';
+ } else if (msg.status === 'delivered') {
+ statusCheck = '
';
+ } else {
+ statusCheck = '
';
+ }
+ }
+
+ const bubble = document.createElement('div');
+ bubble.className = 'message-bubble';
+
+ if (!isOutgoing) {
+ const senderEl = document.createElement('span');
+ senderEl.className = 'message-sender';
+ senderEl.textContent = senderName;
+ bubble.appendChild(senderEl);
+ }
+
+ const contentEl = document.createElement('div');
+ contentEl.classList.add('message-content');
+ const textEl = document.createElement('div');
+ textEl.innerHTML = msg.body || msg.text || "";
+ contentEl.appendChild(textEl);
+ bubble.appendChild(contentEl);
+
+ if (msg.media?.url) {
+ const a = document.createElement('a');
+ a.innerText = `Request media (${msg.media.filename})`;
+
+ a.addEventListener('click', async (e) => {
+ const url = new URL(msg.media.url);
+ const reqID = url.pathname.split('/').filter(Boolean).pop();
+
+ const { blob, filename } = await waha.downloadMedia(reqID);
+
+ const objectUrl = URL.createObjectURL(blob);
+ e.target.href = objectUrl;
+
+ if (blob.type.startsWith('image/')) {
+ a.textContent = "";
+ const img = document.createElement('img');
+ img.classList.add('message-image-attachement');
+ img.src = objectUrl;
+ a.appendChild(img);
+ } else {
+ e.target.textContent = filename || `Download ${msg.media.filename}`;
+ }
+
+ })
+
+ contentEl.appendChild(a);
+ }
+
+ const meta = document.createElement('div');
+ meta.className = 'message-meta';
+ meta.innerHTML = `
${timeStr}${statusCheck}`;
+
+ bubble.appendChild(meta);
+ let uid = "";
+
+ function getPrevMessageElem() {
+ return elements.messagesContainer.lastElementChild; // <‑‑ key change
+ }
+
+ const prevMsgEl = getPrevMessageElem();
+
+ if (isOutgoing) {
+ if (prevMsgEl && prevMsgEl.classList.contains('outgoing')) {
+ } else {
+ groupDiv.appendChild(document.createElement('div')).className = 'message-indicator';
+ }
+ } else if (msg.participant) {
+ uid = msg.participant;
+ if (!prevMsgEl || uid !== prevMsgEl.dataset.from) {
+ groupDiv.appendChild(document.createElement('div')).className = 'message-indicator';
+ }
+ } else {
+ uid = msg.from;
+ if (!prevMsgEl || uid !== prevMsgEl.dataset.from) {
+ groupDiv.appendChild(document.createElement('div')).className = 'message-indicator';
+ }
+ }
+
+ groupDiv.appendChild(bubble);
+
elements.messagesContainer.appendChild(groupDiv);
- lucide.createIcons();
}
};
diff --git a/docs/waha.js b/docs/waha.js
index e746aab..ff4292d 100644
--- a/docs/waha.js
+++ b/docs/waha.js
@@ -28,13 +28,51 @@ async function request(path, options = {}) {
return response.json();
}
+async function downloadFile(path, options = {}) {
+ const url = `${config.wahaUrl}${path}`;
+
+ const headers = {
+ 'Content-Type': options.headers?.['Content-Type'] ?? 'application/json',
+ 'accept': '*/*',
+ ...options.headers
+ };
+
+ if (config.apiKey) headers['X-Api-Key'] = config.apiKey;
+
+ console.log(`[WAHA] ${options.method || 'GET'} ${url}`);
+
+ const response = await fetch(url, { ...options, headers });
+
+ if (!response.ok) {
+ let errorDetail = '';
+ try {
+ errorDetail = JSON.stringify(await response.json());
+ } catch (_) {
+ errorDetail = await response.text().catch(() => '');
+ }
+ throw new Error(`WAHA API returned ${response.status}: ${response.statusText} — ${errorDetail}`);
+ }
+
+ const blob = await response.blob();
+
+ let filename = 'download';
+ const cd = response.headers.get('content-disposition');
+ if (cd) {
+ const m = cd.match(/filename\*=UTF-8''([^;]+)|filename="?([^"]+)"?/i);
+ filename = decodeURIComponent(m?.[1] || m?.[2] || filename);
+ }
+
+ return { blob, filename };
+}
+
+
export const waha = {
async getVersion() {
return request('/api/version');
},
async getChats() {
- const data = await request(`/api/${config.session}/chats`);
+ const data = await request(`/api/${config.session}/chats?sortBy=conversationTimestamp`);
return data.map(chat => {
let chatId = chat.id;
if (chatId && typeof chatId === "object") {
@@ -51,7 +89,7 @@ export const waha = {
},
async getChatMessages(chatId) {
- return request(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=false&limit=40`);
+ return request(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=true&limit=40`);
},
async getChatPicture(chatId) {
@@ -65,6 +103,11 @@ export const waha = {
});
},
+ async downloadMedia(file) {
+ const { blob, filename } = await downloadFile(`/api/files/${config.session}/${file}`);
+ return { blob, filename };
+ },
+
async getMyInfo() {
return request(`/api/sessions/${config.session}/me`);
},