/* 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 = `
⚙️ Настройки чата
${currentChat.name}
${currentChat.description || 'Нет описания'}
Создатель: ${currentChat.createdBy} • Участников: ${currentChat.members?.length || 0}
${isCreator ? `
` : ''}
${isCreator ? `
` : `
`}
`;
// 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 = `
👥 Участники чата
${currentChat.members?.length ? currentChat.members.map(member => `
${member.charAt(0).toUpperCase()}
${member}
${member === currentChat.createdBy ? '👑 Создатель' : '👤 Участник'}
${member === currentChat.createdBy ? '
👑
' : ''}
`).join('') : `
`}
`;
// 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 = `
`;
// 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);