Ported to Cordova, it sucks though, I'm trying Capacitor tomorrow.
27
.gitignore
vendored
|
|
@ -1,2 +1,25 @@
|
||||||
.env
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
node_modules
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Generated by package manager
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Generated by Cordova
|
||||||
|
/plugins/
|
||||||
|
/platforms/
|
||||||
|
|
|
||||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 864 B |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 983 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 471 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 983 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 5 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
45
config.xml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<widget id="org.adrianvictor.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
|
<name>Pandora</name>
|
||||||
|
<description>
|
||||||
|
A messaging client for WAHA API.
|
||||||
|
</description>
|
||||||
|
<author email="adrianvictor@disroot.org" href="https://adrianvic.github.io">
|
||||||
|
Adrian Victor (tenkuma)
|
||||||
|
</author>
|
||||||
|
<content src="index.html" />
|
||||||
|
<allow-intent href="http://*/*" />
|
||||||
|
<allow-intent href="https://*/*" />
|
||||||
|
<platform name="android">
|
||||||
|
<adaptive-icon background="#000000" foreground="resources/android/icon.png" />
|
||||||
|
<icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
|
||||||
|
<icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
|
||||||
|
<icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
|
||||||
|
<icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
|
||||||
|
<icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
|
||||||
|
<icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
|
||||||
|
<splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
|
||||||
|
<splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
|
||||||
|
<splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
|
||||||
|
<splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
|
||||||
|
<splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
|
||||||
|
<splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
|
||||||
|
<splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
|
||||||
|
<splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
|
||||||
|
<splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
|
||||||
|
<splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
|
||||||
|
<splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
|
||||||
|
<splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
|
||||||
|
<icon background="resources/android/icon/ldpi-background.png" density="ldpi" foreground="resources/android/icon/ldpi-foreground.png" />
|
||||||
|
<icon background="resources/android/icon/mdpi-background.png" density="mdpi" foreground="resources/android/icon/mdpi-foreground.png" />
|
||||||
|
<icon background="resources/android/icon/hdpi-background.png" density="hdpi" foreground="resources/android/icon/hdpi-foreground.png" />
|
||||||
|
<icon background="resources/android/icon/xhdpi-background.png" density="xhdpi" foreground="resources/android/icon/xhdpi-foreground.png" />
|
||||||
|
<icon background="resources/android/icon/xxhdpi-background.png" density="xxhdpi" foreground="resources/android/icon/xxhdpi-foreground.png" />
|
||||||
|
<icon background="resources/android/icon/xxxhdpi-background.png" density="xxxhdpi" foreground="resources/android/icon/xxxhdpi-foreground.png" />
|
||||||
|
<preference name="AndroidWindowSplashScreenAnimatedIcon" value="resources/icon.png" />
|
||||||
|
<preference name="AndroidWindowSplashScreenBackground" value="#000000" />
|
||||||
|
<preference name="AndroidWindowSplashScreenAnimationDuration" value="200" />
|
||||||
|
<preference name="AndroidWindowSplashScreenAnimatedIcon" value="resources/android/splash/icon.png" />
|
||||||
|
<preference name="SplashScreenBackgroundColor" value="#000000" />
|
||||||
|
</platform>
|
||||||
|
</widget>
|
||||||
21
index.js
|
|
@ -1,21 +0,0 @@
|
||||||
import express from "express";
|
|
||||||
import path from "path";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
const PORT = process.env.PORT || 3200;
|
|
||||||
|
|
||||||
// Resolve __dirname in ES modules
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
|
|
||||||
// Serve static assets from the public folder
|
|
||||||
app.use(express.static(path.join(__dirname, "docs")));
|
|
||||||
|
|
||||||
// Start static file server
|
|
||||||
app.listen(PORT, () => {
|
|
||||||
console.log(`==================================================`);
|
|
||||||
console.log(` Pandora Chat Static Server is running!`);
|
|
||||||
console.log(` URL: http://localhost:${PORT}`);
|
|
||||||
console.log(`==================================================`);
|
|
||||||
});
|
|
||||||
1960
package-lock.json
generated
31
package.json
|
|
@ -1,19 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "pandora",
|
"name": "org.adrianvictor.pandorabox",
|
||||||
|
"displayName": "Pandora",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"description": "A messaging client for WAHA API.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"runAndroid": "cordova run android --device",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"runBrowser": "cordova run browser"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [
|
||||||
"author": "",
|
"ecosystem:cordova"
|
||||||
"license": "ISC",
|
],
|
||||||
"description": "",
|
"author": "Apache Cordova Team",
|
||||||
"dependencies": {
|
"license": "Apache-2.0",
|
||||||
"axios": "^1.18.1",
|
"devDependencies": {
|
||||||
"dotenv": "^17.4.2",
|
"cordova-android": "github:apache/cordova-android#14.0.1",
|
||||||
"express": "^5.2.1"
|
"cordova-browser": "^7.0.0"
|
||||||
|
},
|
||||||
|
"cordova": {
|
||||||
|
"platforms": [
|
||||||
|
"browser",
|
||||||
|
"android"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
resources/android/icon-background.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
resources/android/icon-foreground.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
resources/android/icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
resources/android/icon/drawable-hdpi-icon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
resources/android/icon/drawable-ldpi-icon.png
Normal file
|
After Width: | Height: | Size: 706 B |
BIN
resources/android/icon/drawable-mdpi-icon.png
Normal file
|
After Width: | Height: | Size: 983 B |
BIN
resources/android/icon/drawable-xhdpi-icon.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
resources/android/icon/drawable-xxhdpi-icon.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
resources/android/icon/drawable-xxxhdpi-icon.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
resources/android/icon/hdpi-background.png
Normal file
|
After Width: | Height: | Size: 864 B |
BIN
resources/android/icon/hdpi-foreground.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/android/icon/ldpi-background.png
Normal file
|
After Width: | Height: | Size: 353 B |
BIN
resources/android/icon/ldpi-foreground.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/android/icon/mdpi-background.png
Normal file
|
After Width: | Height: | Size: 471 B |
BIN
resources/android/icon/mdpi-foreground.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/android/icon/xhdpi-background.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
resources/android/icon/xhdpi-foreground.png
Normal file
|
After Width: | Height: | Size: 5 KiB |
BIN
resources/android/icon/xxhdpi-background.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
resources/android/icon/xxhdpi-foreground.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
resources/android/icon/xxxhdpi-background.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
resources/android/icon/xxxhdpi-foreground.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
resources/android/splash.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
resources/android/splash/drawable-land-hdpi-screen.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
resources/android/splash/drawable-land-ldpi-screen.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
resources/android/splash/drawable-land-mdpi-screen.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
resources/android/splash/drawable-land-xhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
resources/android/splash/drawable-land-xxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
resources/android/splash/drawable-land-xxxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
resources/android/splash/drawable-port-hdpi-screen.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
resources/android/splash/drawable-port-ldpi-screen.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
resources/android/splash/drawable-port-mdpi-screen.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
resources/android/splash/drawable-port-xhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
resources/android/splash/drawable-port-xxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
resources/android/splash/drawable-port-xxxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
|
|
@ -1,77 +0,0 @@
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
const session = "session_01kxc62bk5fs8vh4v127k88a7j";
|
|
||||||
const wahaBaseUrl = "http://inspiran.beetal-castor.ts.net:3100";
|
|
||||||
const apiKey = process.env.WAHA_API_KEY;
|
|
||||||
|
|
||||||
const client = axios.create({
|
|
||||||
baseURL: wahaBaseUrl,
|
|
||||||
headers: {
|
|
||||||
"X-API-KEY": apiKey,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const wahaService = {
|
|
||||||
getConfigStatus() {
|
|
||||||
return {
|
|
||||||
wahaBaseUrl,
|
|
||||||
session,
|
|
||||||
hasApiKey: !!apiKey
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async getChats() {
|
|
||||||
const response = await client.get(`/api/${session}/chats`, { timeout: 5000 });
|
|
||||||
|
|
||||||
if (!Array.isArray(response.data)) {
|
|
||||||
throw new Error("Invalid chats response format from WAHA API");
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.map(chat => {
|
|
||||||
let chatId = chat.id;
|
|
||||||
if (chatId && typeof chatId === "object") {
|
|
||||||
chatId = chatId._serialized || chatId.user || JSON.stringify(chatId);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: chatId || chat.chatId || chat.name,
|
|
||||||
name: chat.name || "Unknown Contact",
|
|
||||||
unreadCount: chat.unreadCount || 0,
|
|
||||||
lastMessage: chat.lastMessage?.body || chat.lastMessageText || "Click to open chat",
|
|
||||||
timestamp: chat.lastMessage?.timestamp || new Date()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async getChatMessages(chatId) {
|
|
||||||
const response = await client.get(`/api/${session}/chats/${chatId}/messages?downloadMedia=false&merge=true&limit=10`);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getMyInfo(chatId) {
|
|
||||||
const response = await client.get(`/api/${session}/me`);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
async startTyping(chatId) {
|
|
||||||
await client.post("/api/startTyping", {
|
|
||||||
chatId,
|
|
||||||
session
|
|
||||||
}, { timeout: 3000 });
|
|
||||||
},
|
|
||||||
|
|
||||||
async stopTyping(chatId) {
|
|
||||||
await client.post("/api/stopTyping", {
|
|
||||||
chatId,
|
|
||||||
session
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
},
|
|
||||||
|
|
||||||
async sendTextMessage(chatId, text) {
|
|
||||||
const response = await client.post("/api/sendText", {
|
|
||||||
chatId,
|
|
||||||
text
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -10,11 +10,26 @@ let userInfo;
|
||||||
|
|
||||||
// Initialize app when DOM is ready
|
// Initialize app when DOM is ready
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
userInfo = await waha.getMyInfo();
|
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
|
try {
|
||||||
|
userInfo = await waha.getMyInfo();
|
||||||
fetchChats();
|
fetchChats();
|
||||||
checkWahaStatus();
|
checkWahaStatus();
|
||||||
initWebSocket();
|
initWebSocket();
|
||||||
|
} 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');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupEventListeners() {
|
function setupEventListeners() {
|
||||||
|
|
@ -155,7 +170,18 @@ async function selectChat(chat) {
|
||||||
elements.activeChatName.textContent = chat.name.toUpperCase();
|
elements.activeChatName.textContent = chat.name.toUpperCase();
|
||||||
elements.activeChatAvatar.textContent = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?';
|
elements.activeChatAvatar.textContent = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?';
|
||||||
|
|
||||||
elements.messagesContainer.innerHTML = '<div class="loading-chats"><div class="spinner"></div><span>Loading messages...</span></div>';
|
elements.messagesContainer.innerHTML = `
|
||||||
|
<div class="loading-chats">
|
||||||
|
<div class='dots'>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<span>Loading messages...
|
||||||
|
</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
if (window.innerWidth <= 768) {
|
if (window.innerWidth <= 768) {
|
||||||
elements.sidebar.classList.add('hidden');
|
elements.sidebar.classList.add('hidden');
|
||||||
|
|
@ -164,7 +190,7 @@ async function selectChat(chat) {
|
||||||
try {
|
try {
|
||||||
const rawMessages = await waha.getChatMessages(chat.id);
|
const rawMessages = await waha.getChatMessages(chat.id);
|
||||||
const processedMessages = compensateMessageOrdering(rawMessages);
|
const processedMessages = compensateMessageOrdering(rawMessages);
|
||||||
ui.renderMessages(processedMessages, chat.name, userInfo.id);
|
ui.renderMessages(processedMessages, chat.name, userInfo.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>';
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
document.querySelectorAll(".dots").forEach(el => {
|
document.querySelectorAll(".dots").forEach(el => {
|
||||||
el.classList.add("animate");
|
el.classList.add("animate");
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 100);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
|
|
@ -77,29 +77,29 @@ export const ui = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
async renderChatList(chats, activeChat, onChatSelect) {
|
||||||
* Render sidebar contact chats list
|
|
||||||
*/
|
|
||||||
renderChatList(chats, activeChat, onChatSelect) {
|
|
||||||
elements.chatList.innerHTML = '';
|
elements.chatList.innerHTML = '';
|
||||||
|
|
||||||
|
chats.sort((a, b) => b.timestamp - a.timestamp);
|
||||||
|
|
||||||
if (chats.length === 0) {
|
if (chats.length === 0) {
|
||||||
elements.chatList.innerHTML = `<li class="loading-chats">No chats found</li>`;
|
elements.chatList.innerHTML = `<li class="loading-chats">No chats found</li>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
chats.forEach(async (chat) => {
|
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 waha.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());
|
||||||
|
|
||||||
li.innerHTML = `
|
li.innerHTML = `
|
||||||
<div class="avatar"><img src="${picture.url}"></div>
|
<div class="avatar"><img src="${picture.url}" alt="${initials}"></div>
|
||||||
<div class="chat-item-info">
|
<div class="chat-item-info">
|
||||||
<div class="chat-item-meta">
|
<div class="chat-item-meta">
|
||||||
<span class="chat-item-name">${chat.name}</span>
|
<span class="chat-item-name">${chat.name}</span>
|
||||||
|
|
@ -114,13 +114,13 @@ export const ui = {
|
||||||
|
|
||||||
li.addEventListener('click', () => onChatSelect(chat));
|
li.addEventListener('click', () => onChatSelect(chat));
|
||||||
elements.chatList.appendChild(li);
|
elements.chatList.appendChild(li);
|
||||||
});
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render chat message log inside chat view container
|
* Render chat message log inside chat view container
|
||||||
*/
|
*/
|
||||||
async renderMessages(messages, activeChatName, userID) {
|
async renderMessages(messages, activeChatName, userID, chatId) {
|
||||||
elements.messagesContainer.innerHTML = '';
|
elements.messagesContainer.innerHTML = '';
|
||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
|
|
@ -129,7 +129,7 @@ export const ui = {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
this.appendSingleMessage(msg, activeChatName, userID);
|
this.appendSingleMessage(msg, activeChatName, userID, chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
|
|
@ -139,7 +139,7 @@ export const ui = {
|
||||||
/**
|
/**
|
||||||
* Append a single message (used for optimistic updates immediately upon sending)
|
* Append a single message (used for optimistic updates immediately upon sending)
|
||||||
*/
|
*/
|
||||||
appendSingleMessage(msg, activeChatName, userID) {
|
appendSingleMessage(msg, activeChatName, userID, chatId) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
const isOutgoing = msg.fromMe || msg.sender === 'me';
|
const isOutgoing = msg.fromMe || msg.sender === 'me';
|
||||||
|
|
||||||
|
|
@ -179,12 +179,14 @@ export const ui = {
|
||||||
contentEl.appendChild(textEl);
|
contentEl.appendChild(textEl);
|
||||||
bubble.appendChild(contentEl);
|
bubble.appendChild(contentEl);
|
||||||
|
|
||||||
if (msg.media?.url) {
|
if (msg.hasMedia) {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.innerText = `Request media (${msg.media.filename})`;
|
a.innerText = `[Request media]`;
|
||||||
|
|
||||||
a.addEventListener('click', async (e) => {
|
a.addEventListener('click', async (e) => {
|
||||||
const url = new URL(msg.media.url);
|
const mediaMsg = await waha.getSingleChatMessage(chatId, msg.id, true);
|
||||||
|
|
||||||
|
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 waha.downloadMedia(reqID);
|
||||||
|
|
@ -199,7 +201,7 @@ export const ui = {
|
||||||
img.src = objectUrl;
|
img.src = objectUrl;
|
||||||
a.appendChild(img);
|
a.appendChild(img);
|
||||||
} else {
|
} else {
|
||||||
e.target.textContent = filename || `Download ${msg.media.filename}`;
|
e.target.textContent = filename || `Download ${mediaMsg.media.filename}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
@ -89,7 +89,11 @@ export const waha = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async getChatMessages(chatId) {
|
async getChatMessages(chatId) {
|
||||||
return request(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=true&limit=40`);
|
return request(`/api/${config.session}/chats/${chatId}/messages?downloadMedia=false&limit=40`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getSingleChatMessage(chatId, messageId, downladMedia) {
|
||||||
|
return request(`/api/${config.session}/chats/${chatId}/messages/${messageId}?downloadMedia=${downladMedia}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
async getChatPicture(chatId) {
|
async getChatPicture(chatId) {
|
||||||