/* CHAT SYSTEM CONFIGURATION */ const BIN_ID_CHATS = "ваш_id_чатов_бин"; // Замените на ваш ID const BIN_CHATS_URL = `https://api.jsonbin.io/v3/b/${BIN_ID_CHATS}`; /* Chat data structure */ let chats = []; let currentChatId = 'general'; // ID текущего активного чата let currentChat = null; /* Chat management functions */ // Initialize chat system async function initializeChatSystem() { try { await loadChats(); // If no current chat is set, use general chat if (!currentChatId && chats.length > 0) { currentChatId = chats[0].id; } // Load current chat currentChat = chats.find(c => c.id === currentChatId) || chats[0]; // Update UI updateChatUI(); } catch (error) { console.error('Error initializing chat system:', error); showError('Ошибка инициализации системы чатов'); } } // Load chats from bin async function loadChats() { try { const response = await fetch(BIN_CHATS_URL + "/latest", { headers: { "X-Master-Key": API_KEY } }); if (!response.ok) { throw new Error(`Failed to load chats: ${response.status}`); } const data = await response.json(); chats = Array.isArray(data.record?.chats) ? data.record.chats : []; // If no chats exist, create default general chat if (chats.length === 0) { await createDefaultChat(); await loadChats(); // Reload after creation } updateChatsListUI(); return chats; } catch (error) { console.error('Error loading chats:', error); // Try to create default chat if loading fails try { await createDefaultChat(); return await loadChats(); } catch (createError) { console.error('Failed to create default chat:', createError); showError('Не удалось загрузить чаты'); return []; } } } // Create default general chat async function createDefaultChat() { const defaultChat = { chats: [ { id: 'general', name: 'Общий чат', description: 'Основной чат для всех пользователей', type: 'public', createdBy: 'system', createdAt: Date.now(), members: [], messageCount: 0, settings: { allowImages: true, maxMessageLength: 1000, allowLinks: true, allowEmojis: true, slowMode: false, slowModeInterval: 10 } } ] }; try { await fetch(BIN_CHATS_URL, { method: 'PUT', headers: HEADERS, body: JSON.stringify(defaultChat) }); return true; } catch (error) { console.error('Error creating default chat:', error); throw error; } } // Save chats to bin async function saveChats() { try { const chatData = { chats }; const response = await fetch(BIN_CHATS_URL, { method: 'PUT', headers: HEADERS, body: JSON.stringify(chatData) }); if (!response.ok) { throw new Error(`Failed to save chats: ${response.status}`); } return true; } catch (error) { console.error('Error saving chats:', error); showError('Ошибка сохранения чатов'); return false; } } // Create new chat async function createNewChat(chatData) { try { // Generate unique chat ID const chatId = 'chat_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); const newChat = { id: chatId, name: chatData.name, description: chatData.description || '', type: chatData.type || 'public', createdBy: currentUser.username, createdAt: Date.now(), members: [currentUser.username], messageCount: 0, settings: { allowImages: chatData.allowImages !== undefined ? chatData.allowImages : true, maxMessageLength: chatData.maxMessageLength || 1000, allowLinks: chatData.allowLinks !== undefined ? chatData.allowLinks : true, allowEmojis: chatData.allowEmojis !== undefined ? chatData.allowEmojis : true, slowMode: chatData.slowMode || false, slowModeInterval: chatData.slowModeInterval || 10, password: chatData.password || null, maxMembers: chatData.maxMembers || 0 } }; // Add to chats array chats.push(newChat); // Save to bin const success = await saveChats(); if (success) { updateChatsListUI(); showError('✅ Чат успешно создан!'); return newChat; } else { // Remove from array if save failed chats = chats.filter(c => c.id !== chatId); throw new Error('Failed to save chat'); } } catch (error) { console.error('Error creating chat:', error); showError('❌ Ошибка создания чата'); return null; } } // Join chat async function joinChat(chatId, password = null) { try { const chat = chats.find(c => c.id === chatId); if (!chat) { showError('❌ Чат не найден'); return false; } // Check if chat is private and requires password if (chat.type === 'private') { if (chat.settings.password && chat.settings.password !== password) { showError('❌ Неверный пароль чата'); return false; } } // Check if user is already a member if (!chat.members.includes(currentUser.username)) { // Check member limit if (chat.settings.maxMembers > 0 && chat.members.length >= chat.settings.maxMembers) { showError('❌ Достигнут лимит участников чата'); return false; } chat.members.push(currentUser.username); await saveChats(); showError('✅ Вы присоединились к чату'); } // Switch to this chat switchChat(chatId); return true; } catch (error) { console.error('Error joining chat:', error); showError('❌ Ошибка присоединения к чату'); return false; } } // Leave chat async function leaveChat(chatId) { try { const chatIndex = chats.findIndex(c => c.id === chatId); if (chatIndex === -1) { showError('❌ Чат не найден'); return false; } const chat = chats[chatIndex]; // Remove user from members const memberIndex = chat.members.indexOf(currentUser.username); if (memberIndex !== -1) { chat.members.splice(memberIndex, 1); } // If user was the creator and no members left, delete chat if (chat.createdBy === currentUser.username && chat.members.length === 0) { chats.splice(chatIndex, 1); showError('🗑️ Чат удален (не осталось участников)'); } await saveChats(); // Switch to general chat if leaving current chat if (chatId === currentChatId) { switchChat('general'); } updateChatsListUI(); showError('✅ Вы покинули чат'); return true; } catch (error) { console.error('Error leaving chat:', error); showError('❌ Ошибка выхода из чата'); return false; } } // Delete chat (only for creator or admin) async function deleteChat(chatId) { try { const chatIndex = chats.findIndex(c => c.id === chatId); if (chatIndex === -1) { showError('❌ Чат не найден'); return false; } const chat = chats[chatIndex]; // Check permissions if (chat.createdBy !== currentUser.username && currentUser.username !== 'Owner') { showError('❌ Только создатель чата может его удалить'); return false; } // Ask for confirmation if (!confirm(`Удалить чат "${chat.name}"?\nЭто действие необратимо.`)) { return false; } // Remove chat chats.splice(chatIndex, 1); // Save changes await saveChats(); // Switch to general chat if deleting current chat if (chatId === currentChatId) { switchChat('general'); } updateChatsListUI(); showError('🗑️ Чат удален'); return true; } catch (error) { console.error('Error deleting chat:', error); showError('❌ Ошибка удаления чата'); return false; } } // Switch to another chat async function switchChat(chatId) { try { const chat = chats.find(c => c.id === chatId); if (!chat) { showError('❌ Чат не найден'); return false; } // Check if user is a member (for private chats) if (chat.type === 'private' && !chat.members.includes(currentUser.username)) { // Try to join automatically for public chats if (chat.type === 'public') { const joined = await joinChat(chatId); if (!joined) return false; } else { showError('❌ Вы не являетесь участником этого чата'); return false; } } // Update current chat currentChatId = chatId; currentChat = chat; // Update UI updateChatUI(); // Load messages for this chat await loadAllData(); // Update active chat in list updateChatsListUI(); return true; } catch (error) { console.error('Error switching chat:', error); showError('❌ Ошибка переключения чата'); return false; } } // Update chat information async function updateChat(chatId, updates) { try { const chatIndex = chats.findIndex(c => c.id === chatId); if (chatIndex === -1) { showError('❌ Чат не найден'); return false; } const chat = chats[chatIndex]; // Check permissions if (chat.createdBy !== currentUser.username) { showError('❌ Только создатель чата может его изменять'); return false; } // Apply updates Object.assign(chat, updates); // Save changes await saveChats(); // Update UI if this is the current chat if (chatId === currentChatId) { updateChatUI(); } updateChatsListUI(); showError('✅ Настройки чата обновлены'); return true; } catch (error) { console.error('Error updating chat:', error); showError('❌ Ошибка обновления чата'); return false; } } // Increment message count for chat async function incrementChatMessageCount(chatId) { try { const chat = chats.find(c => c.id === chatId); if (chat) { chat.messageCount = (chat.messageCount || 0) + 1; await saveChats(); updateChatsListUI(); } } catch (error) { console.error('Error incrementing message count:', error); } } // Get chat by ID function getChatById(chatId) { return chats.find(c => c.id === chatId); } // Get all public chats function getPublicChats() { return chats.filter(c => c.type === 'public'); } // Get user's chats function getUserChats() { return chats.filter(c => c.type === 'public' || c.members.includes(currentUser.username) ); } /* UI Functions */ // Update chat UI elements function updateChatUI() { // Update chat name const chatNameElement = document.getElementById('currentChatName'); if (chatNameElement && currentChat) { chatNameElement.textContent = currentChat.name; } // Update chat description const chatDescElement = document.getElementById('currentChatDescription'); if (chatDescElement) { chatDescElement.textContent = currentChat?.description || ''; } // Update member count const memberCountElement = document.getElementById('memberCount'); if (memberCountElement && currentChat) { memberCountElement.textContent = currentChat.members?.length || 0; } // Update settings UI updateChatSettingsUI(); } // Update chat settings UI function updateChatSettingsUI() { if (!currentChat) return; // Update emoji button visibility const emojiBtn = document.getElementById('emojiBtn'); if (emojiBtn) { emojiBtn.style.display = currentChat.settings?.allowEmojis ? 'block' : 'none'; } // Update photo upload visibility const photoInput = document.getElementById('photoInput'); if (photoInput) { photoInput.style.display = currentChat.settings?.allowImages ? 'block' : 'none'; } // Update input max length const messageInput = document.getElementById('input'); if (messageInput && currentChat.settings?.maxMessageLength) { messageInput.maxLength = currentChat.settings.maxMessageLength; } } // Update chats list in sidebar function updateChatsListUI() { const chatsListElement = document.getElementById('chatsList'); if (!chatsListElement) return; // Get chats accessible to current user const userChats = getUserChats(); if (userChats.length === 0) { chatsListElement.innerHTML = `
💬
Нет доступных чатов
`; return; } let html = ''; userChats.forEach(chat => { const isActive = chat.id === currentChatId; const isCreator = chat.createdBy === currentUser.username; const memberCount = chat.members?.length || 0; const isPrivate = chat.type === 'private'; html += `
${isPrivate ? '🔒' : '🌐'}
${chat.name} ${isCreator ? ' 👑' : ''}
👥 ${memberCount} 💬 ${chat.messageCount || 0} ${isPrivate ? '🔒' : ''}
${isActive ? '
' : ''}
`; }); chatsListElement.innerHTML = html; // Add click event listeners document.querySelectorAll('.chat-item').forEach(item => { item.addEventListener('click', () => { const chatId = item.dataset.chatId; switchChat(chatId); }); }); } // Show new chat creation modal function showNewChatModal() { // Create modal HTML const modalHTML = ` `; // Add modal to document document.body.insertAdjacentHTML('beforeend', modalHTML); // Get modal elements const modal = document.getElementById('newChatModal'); const closeBtn = document.getElementById('closeModalBtn'); const cancelBtn = document.getElementById('cancelChatBtn'); const createBtn = document.getElementById('createChatBtn'); const chatTypeSelect = document.getElementById('newChatType'); const privateSettings = document.getElementById('privateChatSettings'); // Toggle private settings visibility chatTypeSelect.addEventListener('change', function() { privateSettings.style.display = this.value === 'private' ? 'block' : 'none'; }); // Close modal functions function closeModal() { modal.style.animation = 'fadeOut 0.3s ease'; modal.querySelector('.modal-content').style.animation = 'slideDown 0.3s ease'; setTimeout(() => { if (modal.parentNode) { modal.parentNode.removeChild(modal); } }, 300); } closeBtn.addEventListener('click', closeModal); cancelBtn.addEventListener('click', closeModal); // Close on background click modal.addEventListener('click', function(e) { if (e.target === modal) { closeModal(); } }); // Create chat function createBtn.addEventListener('click', async function() { const name = document.getElementById('newChatName').value.trim(); const description = document.getElementById('newChatDescription').value.trim(); const type = document.getElementById('newChatType').value; const password = document.getElementById('newChatPassword').value.trim(); const maxMembers = parseInt(document.getElementById('newChatMaxMembers').value) || 0; const allowImages = document.getElementById('newChatAllowImages').checked; const allowEmojis = document.getElementById('newChatAllowEmojis').checked; const allowLinks = document.getElementById('newChatAllowLinks').checked; if (!name) { showError('❌ Введите название чата'); document.getElementById('newChatName').focus(); return; } if (name.length < 3) { showError('❌ Название чата должно быть не менее 3 символов'); return; } if (name.length > 50) { showError('❌ Название чата должно быть не более 50 символов'); return; } // Create chat data object const chatData = { name, description, type, password: password || null, maxMembers, allowImages, allowEmojis, allowLinks }; // Create chat const chat = await createNewChat(chatData); if (chat) { closeModal(); // Switch to new chat await switchChat(chat.id); } }); } // Show chat settings modal function showChatSettingsModal() { if (!currentChat) return; const isCreator = currentChat.createdBy === currentUser.username; const modalHTML = ` `; // Add modal to document document.body.insertAdjacentHTML('beforeend', modalHTML); // Get modal elements const modal = document.getElementById('chatSettingsModal'); const closeBtn = document.getElementById('closeSettingsModalBtn'); const closeSettingsBtn = document.getElementById('closeSettingsBtn'); // Close modal function function closeModal() { modal.style.animation = 'fadeOut 0.3s ease'; modal.querySelector('.modal-content').style.animation = 'slideDown 0.3s ease'; setTimeout(() => { if (modal.parentNode) { modal.parentNode.removeChild(modal); } }, 300); } closeBtn.addEventListener('click', closeModal); closeSettingsBtn.addEventListener('click', closeModal); // Close on background click modal.addEventListener('click', function(e) { if (e.target === modal) { closeModal(); } }); // Add event listeners for modal buttons const editBtn = document.getElementById('editChatBtn'); const viewMembersBtn = document.getElementById('viewMembersBtn'); const deleteBtn = document.getElementById('deleteChatBtn'); const leaveBtn = document.getElementById('leaveChatBtn'); if (editBtn) { editBtn.addEventListener('click', function() { closeModal(); showEditChatModal(); }); } if (viewMembersBtn) { viewMembersBtn.addEventListener('click', function() { closeModal(); showChatMembersModal(); }); } if (deleteBtn) { deleteBtn.addEventListener('click', async function() { if (confirm(`Удалить чат "${currentChat.name}"?\nЭто действие необратимо.`)) { const success = await deleteChat(currentChatId); if (success) { closeModal(); } } }); } if (leaveBtn) { leaveBtn.addEventListener('click', async function() { if (confirm(`Покинуть чат "${currentChat.name}"?`)) { const success = await leaveChat(currentChatId); if (success) { closeModal(); } } }); } } // Show chat members modal function showChatMembersModal() { if (!currentChat) return; const modalHTML = ` `; // Add modal to document document.body.insertAdjacentHTML('beforeend', modalHTML); // Get modal elements const modal = document.getElementById('chatMembersModal'); const closeBtn = document.getElementById('closeMembersModalBtn'); const closeMembersBtn = document.getElementById('closeMembersBtn'); // Close modal function function closeModal() { modal.style.animation = 'fadeOut 0.3s ease'; modal.querySelector('.modal-content').style.animation = 'slideDown 0.3s ease'; setTimeout(() => { if (modal.parentNode) { modal.parentNode.removeChild(modal); } }, 300); } closeBtn.addEventListener('click', closeModal); closeMembersBtn.addEventListener('click', closeModal); // Close on background click modal.addEventListener('click', function(e) { if (e.target === modal) { closeModal(); } }); } // Show edit chat modal function showEditChatModal() { if (!currentChat) return; const modalHTML = ` `; // Add modal to document document.body.insertAdjacentHTML('beforeend', modalHTML); // Get modal elements const modal = document.getElementById('editChatModal'); const closeBtn = document.getElementById('closeEditModalBtn'); const cancelBtn = document.getElementById('cancelEditBtn'); const saveBtn = document.getElementById('saveChatBtn'); // Close modal function function closeModal() { modal.style.animation = 'fadeOut 0.3s ease'; modal.querySelector('.modal-content').style.animation = 'slideDown 0.3s ease'; setTimeout(() => { if (modal.parentNode) { modal.parentNode.removeChild(modal); } }, 300); } closeBtn.addEventListener('click', closeModal); cancelBtn.addEventListener('click', closeModal); // Close on background click modal.addEventListener('click', function(e) { if (e.target === modal) { closeModal(); } }); // Save chat changes saveBtn.addEventListener('click', async function() { const name = document.getElementById('editChatName').value.trim(); const description = document.getElementById('editChatDescription').value.trim(); const allowImages = document.getElementById('editChatAllowImages').checked; const allowEmojis = document.getElementById('editChatAllowEmojis').checked; const allowLinks = document.getElementById('editChatAllowLinks').checked; if (!name) { showError('❌ Введите название чата'); return; } const updates = { name, description, settings: { ...currentChat.settings, allowImages, allowEmojis, allowLinks } }; const success = await updateChat(currentChatId, updates); if (success) { closeModal(); } }); } // Update message sending to include chatId async function sendMessage() { if (!currentChat) { showError('❌ Не выбран активный чат'); return; } const text = input.value.trim(); const file = photoInput.files[0]; if (!text && !file) return; // Check chat settings if (text) { if (!currentChat.settings?.allowLinks && containsLink(text)) { showError('❌ Ссылки запрещены в этом чате'); return; } if (currentChat.settings?.maxMessageLength && text.length > currentChat.settings.maxMessageLength) { showError(`❌ Сообщение слишком длинное (максимум ${currentChat.settings.maxMessageLength} символов)`); return; } } if (file && !currentChat.settings?.allowImages) { showError('❌ Изображения запрещены в этом чате'); photoInput.value = ''; return; } const msg = { userId: currentUser.id, username: currentUser.username, displayName: currentUser.displayName || currentUser.username, avatar: currentUser.avatar || "", time: Date.now(), chatId: currentChatId // Add chat ID to message }; if (text) msg.text = text; if (file) { try { const base64 = await fileToBase64(file); const imgIndex = await pushImageToBin(base64); if (imgIndex === null) throw new Error("image push failed"); msg.imageIndex = imgIndex; } catch (err) { console.error(err); showError("❌ Ошибка при отправке изображения."); return; } } try { await pushMessageToBin(msg); // Increment chat message count await incrementChatMessageCount(currentChatId); input.value = ""; photoInput.value = ""; await loadAllData(); } catch (err) { console.error(err); showError("❌ Ошибка при сохранении сообщения."); } } // Helper function to check for links in text function containsLink(text) { const urlPattern = /(https?:\/\/[^\s]+)/g; return urlPattern.test(text); } // Update message loading to filter by chat async function loadAllData(silent = false) { if (!silent) showUpdateIndicator(); try { const [textRec, imgRec] = await Promise.all([ getBinLatest(BIN_TEXT_URL), getBinLatest(BIN_IMAGES_URL) ]); // Filter messages by current chat const allMessages = Array.isArray(textRec.messages) ? textRec.messages : []; messages = allMessages.filter(m => m.chatId === currentChatId); images = Array.isArray(imgRec.images) ? imgRec.images : []; renderAllMessages(); if (!silent) showUpdateIndicator(); } catch (err) { console.error("loadAllData error:", err); showError("❌ Ошибка загрузки данных."); } } /* Event Listeners and UI Setup */ // Initialize chat system when user logs in function initializeChatUI() { // Add chats list to sidebar const sidebar = document.querySelector('.sidebar'); if (sidebar) { const chatsHTML = `
💬 Чаты
`; // Find where to insert chats (before settings button) const settingsBtn = document.getElementById('settingsBtn'); if (settingsBtn) { settingsBtn.insertAdjacentHTML('beforebegin', chatsHTML); } else { sidebar.insertAdjacentHTML('beforeend', chatsHTML); } } // Add chat info to topbar const topbar = document.querySelector('.topbar'); if (topbar) { const chatInfoHTML = `
Общий чат
0
`; // Clear topbar and add new content topbar.innerHTML = ''; topbar.innerHTML = ` ${chatInfoHTML} `; } // Add event listeners document.addEventListener('click', function(e) { if (e.target.id === 'newChatBtn' || e.target.closest('#newChatBtn')) { showNewChatModal(); } if (e.target.id === 'chatSettingsBtn' || e.target.closest('#chatSettingsBtn')) { showChatSettingsModal(); } }); } /* Initialize chat system when page loads */ document.addEventListener('DOMContentLoaded', function() { // Wait for user to be loaded const checkUser = setInterval(() => { if (currentUser) { clearInterval(checkUser); initializeChatUI(); initializeChatSystem(); } }, 100); }); // Add CSS animations const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes slideDown { from { transform: translateY(0); opacity: 1; } to { transform: translateY(20px); opacity: 0; } } .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); backdrop-filter: blur(10px); z-index: 10002; display: flex; align-items: center; justify-content: center; animation: fadeIn 0.3s ease; } .modal-content { background: #1e293b; color: white; padding: 24px; border-radius: 16px; width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; animation: slideUp 0.3s ease; } .chat-item { padding: 12px; border-radius: 8px; background: rgba(255,255,255,0.05); cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: space-between; } .chat-item:hover { background: rgba(255,255,255,0.1); } .chat-item.active { background: rgba(59, 130, 246, 0.2); border: 1px solid rgba(59, 130, 246, 0.5); } `; document.head.appendChild(style);