Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Secure Chat 🌿</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #38b2ac 0%, #48bb78 100%); | |
| height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| } | |
| .chat-container { | |
| width: 100%; | |
| max-width: 480px; | |
| height: 700px; | |
| background: #f0fff4; | |
| border-radius: 15px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| .chat-header { | |
| background: #2f855a; | |
| color: white; | |
| padding: 15px 20px; | |
| text-align: center; | |
| position: relative; | |
| } | |
| .security-badge { | |
| position: absolute; | |
| top: 10px; | |
| right: 15px; | |
| background: #68d391; | |
| padding: 4px 8px; | |
| border-radius: 12px; | |
| font-size: 0.7em; | |
| font-weight: bold; | |
| } | |
| .encryption-notice { | |
| text-align: center; | |
| padding: 8px; | |
| background: #c6f6d5; | |
| color: #22543d; | |
| font-size: 0.8em; | |
| border-bottom: 1px solid #9ae6b4; | |
| } | |
| .key-input { | |
| width: 90%; | |
| margin: 10px auto; | |
| display: block; | |
| padding: 10px 12px; | |
| border: 2px solid #9ae6b4; | |
| border-radius: 10px; | |
| font-size: 14px; | |
| text-align: center; | |
| background: #fafffa; | |
| transition: border-color 0.3s; | |
| } | |
| .key-input:focus { | |
| border-color: #48bb78; | |
| outline: none; | |
| } | |
| .messages-container { | |
| flex: 1; | |
| padding: 20px; | |
| overflow-y: auto; | |
| background: #f7faf7; | |
| } | |
| .message { | |
| margin-bottom: 15px; | |
| padding: 10px 15px; | |
| border-radius: 15px; | |
| max-width: 80%; | |
| word-wrap: break-word; | |
| } | |
| .message.own { | |
| background: #68d391; | |
| color: #1a202c; | |
| margin-left: auto; | |
| border-bottom-right-radius: 5px; | |
| } | |
| .message.other { | |
| background: #e6fffa; | |
| color: #2f855a; | |
| border-bottom-left-radius: 5px; | |
| } | |
| .message-info { | |
| font-size: 0.8em; | |
| opacity: 0.7; | |
| margin-bottom: 3px; | |
| } | |
| .input-container { | |
| padding: 15px 20px; | |
| background: white; | |
| border-top: 1px solid #c6f6d5; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .message-input { | |
| width: 100%; | |
| height: 70px; | |
| resize: none; | |
| padding: 12px 15px; | |
| border: 2px solid #c6f6d5; | |
| border-radius: 12px; | |
| outline: none; | |
| font-size: 15px; | |
| line-height: 1.4; | |
| transition: border-color 0.3s; | |
| background: #fafffa; | |
| } | |
| .message-input:focus { | |
| border-color: #48bb78; | |
| } | |
| .button-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .send-button, .clear-button { | |
| padding: 10px 20px; | |
| border: none; | |
| border-radius: 25px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: background 0.3s; | |
| } | |
| .send-button { | |
| background: #48bb78; | |
| color: white; | |
| } | |
| .send-button:hover { background: #38a169; } | |
| .clear-button { | |
| background: #e53e3e; | |
| color: white; | |
| } | |
| .clear-button:hover { background: #c53030; } | |
| .messages-container::-webkit-scrollbar { width: 6px; } | |
| .messages-container::-webkit-scrollbar-thumb { background: #a0aec0; border-radius: 3px; } | |
| .messages-container::-webkit-scrollbar-thumb:hover { background: #718096; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="chat-container"> | |
| <div class="chat-header"> | |
| <h2>🌿 Secure Chat</h2> | |
| <div class="security-badge">AES ШИФРОВАНИЕ</div> | |
| </div> | |
| <div class="encryption-notice">Введите общий ключ шифрования ниже</div> | |
| <input type="password" id="key-input" class="key-input" placeholder="🔑 Ключ шифрования (один и тот же у обоих)" /> | |
| <div class="messages-container" id="messages"></div> | |
| <div class="input-container"> | |
| <textarea class="message-input" id="message-input" placeholder="Введите сообщение..."></textarea> | |
| <div class="button-row"> | |
| <button class="send-button" id="send-btn">Отправить</button> | |
| <button class="clear-button" id="clear-btn">🗑 Очистить</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const userId = "{{ user_id }}"; | |
| let lastId = 0; | |
| let encryptionKey = ''; | |
| const keyInput = document.getElementById('key-input'); | |
| const msgInput = document.getElementById('message-input'); | |
| const messagesDiv = document.getElementById('messages'); | |
| document.getElementById('send-btn').onclick = sendMessage; | |
| document.getElementById('clear-btn').onclick = clearChat; | |
| keyInput.oninput = () => encryptionKey = keyInput.value.trim(); | |
| async function sendMessage() { | |
| const text = msgInput.value.trim(); | |
| if (!text || !encryptionKey) return alert('Введите сообщение и ключ'); | |
| const encrypted = CryptoJS.AES.encrypt(text, encryptionKey).toString(); | |
| await fetch('/send_message', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ user_id: userId, encrypted_text: encrypted }) | |
| }); | |
| msgInput.value = ''; | |
| loadNewMessages(); | |
| } | |
| async function loadMessages() { | |
| const res = await fetch('/get_messages'); | |
| const data = await res.json(); | |
| renderMessages(data.messages); | |
| } | |
| async function loadNewMessages() { | |
| const res = await fetch(`/get_new_messages?last_id=${lastId}`); | |
| const data = await res.json(); | |
| if (data.messages.length) renderMessages(data.messages, false); | |
| } | |
| async function clearChat() { | |
| if (!confirm('Очистить чат?')) return; | |
| await fetch('/clear_chat', { method: 'POST' }); | |
| messagesDiv.innerHTML = ''; | |
| lastId = 0; | |
| } | |
| function renderMessages(msgs, clear = true) { | |
| if (clear) messagesDiv.innerHTML = ''; | |
| msgs.forEach(m => { | |
| // если это уже было добавлено — пропускаем | |
| if (document.getElementById('msg-' + m.id)) return; | |
| lastId = Math.max(lastId, m.id); | |
| let decrypted = '[🔒 Сообщение зашифровано]'; | |
| if (encryptionKey) { | |
| try { | |
| decrypted = CryptoJS.AES.decrypt(m.encrypted_text, encryptionKey).toString(CryptoJS.enc.Utf8) || decrypted; | |
| } catch {} | |
| } | |
| const div = document.createElement('div'); | |
| div.id = 'msg-' + m.id; // уникальный ID для проверки | |
| div.className = 'message ' + (m.user_id === userId ? 'own' : 'other'); | |
| div.innerHTML = `<div><b>${m.username}</b> • ${m.timestamp}</div><div>${decrypted}</div>`; | |
| messagesDiv.appendChild(div); | |
| }); | |
| messagesDiv.scrollTop = messagesDiv.scrollHeight; | |
| } | |
| setInterval(loadNewMessages, 2000); | |
| loadMessages(); | |
| </script> | |
| </body> | |
| </html> | |