Spaces:
Sleeping
Sleeping
| <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; } | |
| /* Стили для ника */ | |
| .username-section { | |
| padding: 8px 20px; | |
| background: #f0fff4; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| border-bottom: 1px solid #c6f6d5; | |
| } | |
| .username-section span { | |
| font-weight: bold; | |
| color: #2f855a; | |
| font-size: 14px; | |
| white-space: nowrap; | |
| } | |
| .username-section input { | |
| flex: 1; | |
| padding: 6px 10px; | |
| border: 2px solid #9ae6b4; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| background: #fafffa; | |
| } | |
| .username-section input:focus { | |
| border-color: #48bb78; | |
| outline: none; | |
| } | |
| .username-section button { | |
| padding: 6px 16px; | |
| background: #48bb78; | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-weight: bold; | |
| font-size: 13px; | |
| white-space: nowrap; | |
| } | |
| .username-section button:hover { | |
| background: #38a169; | |
| } | |
| </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> | |
| <!-- БЛОК ДЛЯ ВВОДА НИКА --> | |
| <div class="username-section"> | |
| <span>👤 Ваш ник:</span> | |
| <input type="text" id="username-input" placeholder="Введите ник" /> | |
| <button id="set-username-btn">Установить</button> | |
| </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 = ''; | |
| let username = localStorage.getItem('chat_username') || ''; | |
| // === DOM-ЭЛЕМЕНТЫ === | |
| const keyInput = document.getElementById('key-input'); | |
| const msgInput = document.getElementById('message-input'); | |
| const messagesDiv = document.getElementById('messages'); | |
| const usernameInput = document.getElementById('username-input'); | |
| const setUsernameBtn = document.getElementById('set-username-btn'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const clearBtn = document.getElementById('clear-btn'); | |
| // === НИК ПОЛЬЗОВАТЕЛЯ === | |
| function setUsername() { | |
| const name = usernameInput.value.trim(); | |
| if (!name) { | |
| alert('Введите ник!'); | |
| return; | |
| } | |
| username = name; | |
| localStorage.setItem('chat_username', name); | |
| usernameInput.value = name; | |
| } | |
| setUsernameBtn.onclick = setUsername; | |
| usernameInput.onkeydown = function(e) { | |
| if (e.key === 'Enter') setUsername(); | |
| }; | |
| // Если ник уже был сохранён | |
| if (username) { | |
| usernameInput.value = username; | |
| } | |
| // === КЛЮЧ ШИФРОВАНИЯ === | |
| keyInput.oninput = () => encryptionKey = keyInput.value.trim(); | |
| // === ШИФРОВАНИЕ/ДЕШИФРОВАНИЕ === | |
| function encryptMessage(text) { | |
| return CryptoJS.AES.encrypt(text, encryptionKey).toString(); | |
| } | |
| function decryptMessage(encrypted) { | |
| if (!encryptionKey) return '🔑 Введите ключ для расшифровки'; | |
| try { | |
| const decrypted = CryptoJS.AES.decrypt(encrypted, encryptionKey).toString(CryptoJS.enc.Utf8); | |
| return decrypted || '🔒 Не удалось расшифровать'; | |
| } catch { | |
| return '🔒 Ошибка расшифровки'; | |
| } | |
| } | |
| // === ОТПРАВКА СООБЩЕНИЯ === | |
| async function sendMessage() { | |
| const text = msgInput.value.trim(); | |
| if (!text) { | |
| alert('Введите сообщение'); | |
| return; | |
| } | |
| if (!encryptionKey) { | |
| alert('Введите ключ шифрования!'); | |
| return; | |
| } | |
| if (!username) { | |
| alert('Сначала установите ник!'); | |
| return; | |
| } | |
| const encrypted = encryptMessage(text); | |
| await fetch('/send_message', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| user_id: userId, | |
| username: username, | |
| encrypted_text: encrypted | |
| }) | |
| }); | |
| msgInput.value = ''; | |
| loadNewMessages(); | |
| } | |
| // === ЗАГРУЗКА СООБЩЕНИЙ === | |
| async function loadMessages() { | |
| const res = await fetch('/get_messages'); | |
| const data = await res.json(); | |
| renderMessages(data.messages, true); | |
| } | |
| 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); | |
| const decrypted = decryptMessage(m.encrypted_text); | |
| const isOwn = m.username === username; | |
| const div = document.createElement('div'); | |
| div.id = 'msg-' + m.id; | |
| div.className = 'message ' + (isOwn ? 'own' : 'other'); | |
| div.innerHTML = ` | |
| <div class="message-info"> | |
| <b>${m.username}</b> • ${m.timestamp} | |
| </div> | |
| <div>${decrypted}</div> | |
| `; | |
| messagesDiv.appendChild(div); | |
| }); | |
| messagesDiv.scrollTop = messagesDiv.scrollHeight; | |
| } | |
| // === КНОПКИ === | |
| sendBtn.onclick = sendMessage; | |
| clearBtn.onclick = clearChat; | |
| // Отправка по Ctrl+Enter | |
| msgInput.onkeydown = function(e) { | |
| if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { | |
| sendBtn.click(); | |
| } | |
| }; | |
| // === АВТООБНОВЛЕНИЕ === | |
| setInterval(loadNewMessages, 2000); | |
| loadMessages(); | |
| </script> | |
| </body> | |
| </html> |