| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Null Messenger</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .message-animation { |
| animation: fadeIn 0.3s ease-in-out; |
| } |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .blink { |
| animation: blink 1s infinite; |
| } |
| @keyframes blink { |
| 0% { opacity: 1; } |
| 50% { opacity: 0.5; } |
| 100% { opacity: 1; } |
| } |
| .scrollbar-hide::-webkit-scrollbar { |
| display: none; |
| } |
| .scrollbar-hide { |
| -ms-overflow-style: none; |
| scrollbar-width: none; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-gray-100 min-h-screen"> |
| <div id="app" class="container mx-auto px-4 py-8 max-w-6xl"> |
| |
| <div id="auth-screen" class="flex flex-col items-center justify-center min-h-[80vh]"> |
| <div class="bg-gray-800 rounded-xl p-8 w-full max-w-md shadow-2xl border border-gray-700"> |
| <div class="text-center mb-8"> |
| <h1 class="text-4xl font-bold text-purple-500 mb-2">NULL</h1> |
| <p class="text-gray-400">Secure anonymous messaging</p> |
| </div> |
| |
| <div id="auth-tabs" class="flex mb-6 border-b border-gray-700"> |
| <button id="login-tab" class="flex-1 py-2 font-medium text-purple-500 border-b-2 border-purple-500">Login</button> |
| <button id="register-tab" class="flex-1 py-2 font-medium text-gray-400 hover:text-gray-300">Register</button> |
| </div> |
| |
| |
| <div id="login-form" class="space-y-4"> |
| <div> |
| <label for="login-id" class="block text-sm font-medium mb-1">ID</label> |
| <input type="number" id="login-id" class="w-full px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| </div> |
| <div> |
| <label for="login-password" class="block text-sm font-medium mb-1">Password</label> |
| <input type="password" id="login-password" class="w-full px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| </div> |
| <button id="login-btn" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-lg font-medium transition duration-200 mt-4"> |
| Login |
| </button> |
| </div> |
| |
| |
| <div id="register-form" class="space-y-4 hidden"> |
| <div> |
| <label for="register-name" class="block text-sm font-medium mb-1">Name (max 20 chars)</label> |
| <input type="text" id="register-name" maxlength="20" class="w-full px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| </div> |
| <div> |
| <label for="register-password" class="block text-sm font-medium mb-1">Password (max 20 chars)</label> |
| <input type="password" id="register-password" maxlength="20" class="w-full px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| </div> |
| <button id="register-btn" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-lg font-medium transition duration-200 mt-4"> |
| Register |
| </button> |
| </div> |
| |
| <div id="auth-message" class="mt-4 text-sm text-center text-red-500 hidden"></div> |
| </div> |
| </div> |
| |
| |
| <div id="chat-screen" class="hidden"> |
| <div class="flex flex-col h-[80vh] border border-gray-700 rounded-xl overflow-hidden"> |
| |
| <div class="bg-gray-800 p-4 flex justify-between items-center border-b border-gray-700"> |
| <div class="flex items-center"> |
| <h2 class="text-xl font-bold text-purple-500">NULL CHAT</h2> |
| <span id="user-badge" class="ml-2 px-2 py-1 text-xs rounded-full bg-gray-700"></span> |
| </div> |
| <div class="flex space-x-2"> |
| <button id="settings-btn" class="p-2 text-gray-400 hover:text-purple-500 rounded-full hover:bg-gray-700 transition"> |
| <i class="fas fa-cog"></i> |
| </button> |
| <button id="logout-btn" class="p-2 text-gray-400 hover:text-purple-500 rounded-full hover:bg-gray-700 transition"> |
| <i class="fas fa-sign-out-alt"></i> |
| </button> |
| <button id="admin-btn" class="p-2 text-gray-400 hover:text-purple-500 rounded-full hover:bg-gray-700 transition hidden"> |
| <i class="fas fa-shield-alt"></i> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="messages-container" class="flex-1 overflow-y-auto p-4 bg-gray-800 scrollbar-hide"> |
| <div id="messages" class="space-y-3"></div> |
| </div> |
| |
| |
| <div class="bg-gray-800 p-4 border-t border-gray-700"> |
| <div class="flex space-x-2"> |
| <input id="message-input" type="text" maxlength="1000" placeholder="Type your message..." class="flex-1 px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| <button id="send-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg font-medium transition duration-200"> |
| Send |
| </button> |
| </div> |
| <div id="message-counter" class="text-xs text-gray-500 mt-1 text-right"></div> |
| <div id="message-error" class="text-xs text-red-500 mt-1 hidden"></div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="settings-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| <div class="bg-gray-800 rounded-xl p-6 w-full max-w-md border border-gray-700"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-bold">Settings</h3> |
| <button id="close-settings" class="text-gray-400 hover:text-white"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-sm font-medium mb-1">Change Name (max 20 chars)</label> |
| <div class="flex space-x-2"> |
| <input type="text" id="new-name" maxlength="20" class="flex-1 px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| <button id="change-name-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg font-medium transition duration-200"> |
| Update |
| </button> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium mb-1">Change Password (max 20 chars)</label> |
| <div class="flex space-x-2"> |
| <input type="password" id="new-password" maxlength="20" class="flex-1 px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| <button id="change-password-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg font-medium transition duration-200"> |
| Update |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| <div id="settings-message" class="mt-4 text-sm text-center hidden"></div> |
| </div> |
| </div> |
| |
| |
| <div id="admin-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| <div class="bg-gray-800 rounded-xl p-6 w-full max-w-4xl border border-gray-700 max-h-[90vh] flex flex-col"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-bold text-purple-500">Admin Panel</h3> |
| <button id="close-admin" class="text-gray-400 hover:text-white"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="flex-1 overflow-hidden flex flex-col"> |
| <div class="bg-gray-900 p-4 rounded-lg mb-4 overflow-y-auto scrollbar-hide flex-1"> |
| <div id="admin-console" class="font-mono text-sm space-y-2"> |
| <div class="text-green-400">Welcome to Admin Panel</div> |
| <div class="text-gray-500">Type /help to see available commands</div> |
| </div> |
| </div> |
| |
| <div class="flex space-x-2"> |
| <input id="admin-command" type="text" placeholder="Enter command..." class="flex-1 px-4 py-2 bg-gray-700 rounded-lg focus:ring-2 focus:ring-purple-500 focus:outline-none"> |
| <button id="execute-command" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg font-medium transition duration-200"> |
| Execute |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let users = [ |
| { id: 0, name: "Null", password: "QWERTYnull123!", isAdmin: true } |
| ]; |
| |
| let messages = []; |
| let mutedAccounts = []; |
| let mutedIPs = []; |
| let messageLimit = 10; |
| let messageCounts = {}; |
| let accountCreationLimits = {}; |
| |
| |
| let currentUser = null; |
| let currentIP = generateRandomIP(); |
| |
| |
| const authScreen = document.getElementById('auth-screen'); |
| const chatScreen = document.getElementById('chat-screen'); |
| const loginForm = document.getElementById('login-form'); |
| const registerForm = document.getElementById('register-form'); |
| const loginTab = document.getElementById('login-tab'); |
| const registerTab = document.getElementById('register-tab'); |
| const loginBtn = document.getElementById('login-btn'); |
| const registerBtn = document.getElementById('register-btn'); |
| const loginIdInput = document.getElementById('login-id'); |
| const loginPasswordInput = document.getElementById('login-password'); |
| const registerNameInput = document.getElementById('register-name'); |
| const registerPasswordInput = document.getElementById('register-password'); |
| const authMessage = document.getElementById('auth-message'); |
| const messagesContainer = document.getElementById('messages'); |
| const messageInput = document.getElementById('message-input'); |
| const sendBtn = document.getElementById('send-btn'); |
| const messageCounter = document.getElementById('message-counter'); |
| const messageError = document.getElementById('message-error'); |
| const userBadge = document.getElementById('user-badge'); |
| const settingsBtn = document.getElementById('settings-btn'); |
| const logoutBtn = document.getElementById('logout-btn'); |
| const adminBtn = document.getElementById('admin-btn'); |
| const settingsModal = document.getElementById('settings-modal'); |
| const closeSettings = document.getElementById('close-settings'); |
| const newNameInput = document.getElementById('new-name'); |
| const newPasswordInput = document.getElementById('new-password'); |
| const changeNameBtn = document.getElementById('change-name-btn'); |
| const changePasswordBtn = document.getElementById('change-password-btn'); |
| const settingsMessage = document.getElementById('settings-message'); |
| const adminModal = document.getElementById('admin-modal'); |
| const closeAdmin = document.getElementById('close-admin'); |
| const adminCommand = document.getElementById('admin-command'); |
| const executeCommand = document.getElementById('execute-command'); |
| const adminConsole = document.getElementById('admin-console'); |
| |
| |
| loadFromLocalStorage(); |
| renderMessageCounter(); |
| |
| |
| loginTab.addEventListener('click', () => { |
| loginTab.classList.add('text-purple-500', 'border-purple-500'); |
| loginTab.classList.remove('text-gray-400'); |
| registerTab.classList.add('text-gray-400'); |
| registerTab.classList.remove('text-purple-500', 'border-purple-500'); |
| loginForm.classList.remove('hidden'); |
| registerForm.classList.add('hidden'); |
| authMessage.classList.add('hidden'); |
| }); |
| |
| registerTab.addEventListener('click', () => { |
| registerTab.classList.add('text-purple-500', 'border-purple-500'); |
| registerTab.classList.remove('text-gray-400'); |
| loginTab.classList.add('text-gray-400'); |
| loginTab.classList.remove('text-purple-500', 'border-purple-500'); |
| registerForm.classList.remove('hidden'); |
| loginForm.classList.add('hidden'); |
| authMessage.classList.add('hidden'); |
| }); |
| |
| loginBtn.addEventListener('click', handleLogin); |
| registerBtn.addEventListener('click', handleRegister); |
| sendBtn.addEventListener('click', sendMessage); |
| messageInput.addEventListener('keypress', (e) => { |
| if (e.key === 'Enter') sendMessage(); |
| }); |
| settingsBtn.addEventListener('click', () => { |
| newNameInput.value = currentUser.name; |
| newPasswordInput.value = ''; |
| settingsMessage.classList.add('hidden'); |
| settingsModal.classList.remove('hidden'); |
| }); |
| logoutBtn.addEventListener('click', handleLogout); |
| adminBtn.addEventListener('click', () => { |
| adminModal.classList.remove('hidden'); |
| }); |
| closeSettings.addEventListener('click', () => { |
| settingsModal.classList.add('hidden'); |
| }); |
| closeAdmin.addEventListener('click', () => { |
| adminModal.classList.add('hidden'); |
| }); |
| changeNameBtn.addEventListener('click', changeName); |
| changePasswordBtn.addEventListener('click', changePassword); |
| executeCommand.addEventListener('click', executeAdminCommand); |
| adminCommand.addEventListener('keypress', (e) => { |
| if (e.key === 'Enter') executeAdminCommand(); |
| }); |
| |
| |
| function handleLogin() { |
| const id = parseInt(loginIdInput.value); |
| const password = loginPasswordInput.value; |
| |
| if (isNaN(id)) { |
| showAuthMessage('ID must be a number'); |
| return; |
| } |
| |
| const user = users.find(u => u.id === id); |
| |
| if (!user) { |
| showAuthMessage('User not found'); |
| return; |
| } |
| |
| |
| const muteRecord = mutedAccounts.find(m => m.id === id && m.expires > Date.now()); |
| if (muteRecord) { |
| const hoursLeft = Math.ceil((muteRecord.expires - Date.now()) / (1000 * 60 * 60)); |
| showAuthMessage(`Account is muted for ${hoursLeft} more hours`); |
| return; |
| } |
| |
| |
| const ipMuteRecord = mutedIPs.find(m => m.ip === currentIP && m.expires > Date.now()); |
| if (ipMuteRecord) { |
| const hoursLeft = Math.ceil((ipMuteRecord.expires - Date.now()) / (1000 * 60 * 60)); |
| showAuthMessage(`Your IP is muted for ${hoursLeft} more hours`); |
| return; |
| } |
| |
| if (user.password !== password) { |
| showAuthMessage('Incorrect password'); |
| return; |
| } |
| |
| currentUser = user; |
| authScreen.classList.add('hidden'); |
| chatScreen.classList.remove('hidden'); |
| |
| if (currentUser.isAdmin) { |
| adminBtn.classList.remove('hidden'); |
| } |
| |
| userBadge.textContent = `${currentUser.name} (ID: ${currentUser.id})`; |
| renderMessages(); |
| renderMessageCounter(); |
| |
| |
| loginIdInput.value = ''; |
| loginPasswordInput.value = ''; |
| authMessage.classList.add('hidden'); |
| } |
| |
| function handleRegister() { |
| const name = registerNameInput.value.trim(); |
| const password = registerPasswordInput.value; |
| |
| if (!name || !password) { |
| showAuthMessage('Name and password are required'); |
| return; |
| } |
| |
| if (name.length > 20) { |
| showAuthMessage('Name must be 20 characters or less'); |
| return; |
| } |
| |
| if (password.length > 20) { |
| showAuthMessage('Password must be 20 characters or less'); |
| return; |
| } |
| |
| |
| const now = Date.now(); |
| const lastMonth = now - 30 * 24 * 60 * 60 * 1000; |
| |
| if (!accountCreationLimits[currentIP]) { |
| accountCreationLimits[currentIP] = []; |
| } |
| |
| |
| accountCreationLimits[currentIP] = accountCreationLimits[currentIP].filter(t => t > lastMonth); |
| |
| if (accountCreationLimits[currentIP].length >= 2) { |
| showAuthMessage('You can only create 2 accounts per month from this device'); |
| return; |
| } |
| |
| |
| const newId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1; |
| const newUser = { |
| id: newId, |
| name, |
| password, |
| isAdmin: false |
| }; |
| |
| users.push(newUser); |
| accountCreationLimits[currentIP].push(now); |
| |
| |
| showAuthMessage(`Account created! Your ID is ${newId}`, 'green'); |
| |
| |
| registerNameInput.value = ''; |
| registerPasswordInput.value = ''; |
| |
| saveToLocalStorage(); |
| } |
| |
| function handleLogout() { |
| currentUser = null; |
| chatScreen.classList.add('hidden'); |
| authScreen.classList.remove('hidden'); |
| adminModal.classList.add('hidden'); |
| settingsModal.classList.add('hidden'); |
| } |
| |
| function sendMessage() { |
| const text = messageInput.value.trim(); |
| |
| if (!text) return; |
| |
| |
| const muteRecord = mutedAccounts.find(m => m.id === currentUser.id && m.expires > Date.now()); |
| if (muteRecord) { |
| const hoursLeft = Math.ceil((muteRecord.expires - Date.now()) / (1000 * 60 * 60)); |
| showMessageError(`You are muted for ${hoursLeft} more hours`); |
| return; |
| } |
| |
| |
| const ipMuteRecord = mutedIPs.find(m => m.ip === currentIP && m.expires > Date.now()); |
| if (ipMuteRecord) { |
| const hoursLeft = Math.ceil((ipMuteRecord.expires - Date.now()) / (1000 * 60 * 60)); |
| showMessageError(`Your IP is muted for ${hoursLeft} more hours`); |
| return; |
| } |
| |
| |
| const now = Date.now(); |
| const lastHour = now - 60 * 60 * 1000; |
| |
| if (!messageCounts[currentUser.id]) { |
| messageCounts[currentUser.id] = []; |
| } |
| |
| |
| messageCounts[currentUser.id] = messageCounts[currentUser.id].filter(t => t > lastHour); |
| |
| if (messageCounts[currentUser.id].length >= messageLimit) { |
| showMessageError(`You can only send ${messageLimit} messages per hour`); |
| return; |
| } |
| |
| |
| const messageId = messages.length > 0 ? Math.max(...messages.map(m => m.id)) + 1 : 1; |
| const newMessage = { |
| id: messageId, |
| userId: currentUser.id, |
| userName: currentUser.name, |
| text, |
| timestamp: now, |
| ip: currentIP |
| }; |
| |
| messages.push(newMessage); |
| messageCounts[currentUser.id].push(now); |
| |
| |
| messageInput.value = ''; |
| renderMessages(); |
| renderMessageCounter(); |
| messageError.classList.add('hidden'); |
| |
| |
| setTimeout(() => { |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; |
| }, 100); |
| |
| saveToLocalStorage(); |
| } |
| |
| function renderMessages() { |
| messagesContainer.innerHTML = ''; |
| |
| messages.forEach(msg => { |
| const messageElement = document.createElement('div'); |
| messageElement.className = 'bg-gray-700 p-3 rounded-lg message-animation'; |
| |
| const header = document.createElement('div'); |
| header.className = 'flex justify-between text-xs text-gray-400 mb-1'; |
| header.innerHTML = ` |
| <span>${msg.userName} / ID: ${msg.userId} / mID: ${msg.id}</span> |
| <span>${formatTime(msg.timestamp)}</span> |
| `; |
| |
| const content = document.createElement('div'); |
| content.className = 'text-gray-100'; |
| content.textContent = msg.text; |
| |
| messageElement.appendChild(header); |
| messageElement.appendChild(content); |
| messagesContainer.appendChild(messageElement); |
| }); |
| } |
| |
| function renderMessageCounter() { |
| if (!currentUser) return; |
| |
| const now = Date.now(); |
| const lastHour = now - 60 * 60 * 1000; |
| |
| if (!messageCounts[currentUser.id]) { |
| messageCounts[currentUser.id] = []; |
| } |
| |
| |
| messageCounts[currentUser.id] = messageCounts[currentUser.id].filter(t => t > lastHour); |
| |
| const count = messageCounts[currentUser.id].length; |
| const remaining = Math.max(0, messageLimit - count); |
| |
| messageCounter.textContent = `Messages this hour: ${count}/${messageLimit} (${remaining} remaining)`; |
| |
| if (count >= messageLimit) { |
| messageCounter.classList.add('text-red-400'); |
| messageCounter.classList.remove('text-gray-500'); |
| } else { |
| messageCounter.classList.add('text-gray-500'); |
| messageCounter.classList.remove('text-red-400'); |
| } |
| } |
| |
| function changeName() { |
| const newName = newNameInput.value.trim(); |
| |
| if (!newName) { |
| showSettingsMessage('Name cannot be empty', 'red'); |
| return; |
| } |
| |
| if (newName.length > 20) { |
| showSettingsMessage('Name must be 20 characters or less', 'red'); |
| return; |
| } |
| |
| currentUser.name = newName; |
| userBadge.textContent = `${currentUser.name} (ID: ${currentUser.id})`; |
| |
| |
| messages.forEach(msg => { |
| if (msg.userId === currentUser.id) { |
| msg.userName = newName; |
| } |
| }); |
| |
| showSettingsMessage('Name updated successfully', 'green'); |
| renderMessages(); |
| saveToLocalStorage(); |
| } |
| |
| function changePassword() { |
| const newPassword = newPasswordInput.value; |
| |
| if (!newPassword) { |
| showSettingsMessage('Password cannot be empty', 'red'); |
| return; |
| } |
| |
| if (newPassword.length > 20) { |
| showSettingsMessage('Password must be 20 characters or less', 'red'); |
| return; |
| } |
| |
| currentUser.password = newPassword; |
| showSettingsMessage('Password updated successfully', 'green'); |
| newPasswordInput.value = ''; |
| saveToLocalStorage(); |
| } |
| |
| function executeAdminCommand() { |
| if (!currentUser || !currentUser.isAdmin) return; |
| |
| const command = adminCommand.value.trim(); |
| adminCommand.value = ''; |
| |
| if (!command) return; |
| |
| |
| addToConsole(`> ${command}`, 'text-purple-400'); |
| |
| |
| const parts = command.split(' '); |
| const cmd = parts[0].toLowerCase(); |
| const args = parts.slice(1); |
| |
| |
| switch (cmd) { |
| case '/mute': |
| if (args.length !== 2) { |
| addToConsole('Usage: /mute id hours', 'text-red-400'); |
| break; |
| } |
| const muteId = parseInt(args[0]); |
| const muteHours = parseInt(args[1]); |
| if (isNaN(muteId) || isNaN(muteHours)) { |
| addToConsole('Invalid id or hours', 'text-red-400'); |
| break; |
| } |
| muteAccount(muteId, muteHours); |
| break; |
| |
| case '/unmute': |
| if (args.length !== 1) { |
| addToConsole('Usage: /unmute id', 'text-red-400'); |
| break; |
| } |
| const unmuteId = parseInt(args[0]); |
| if (isNaN(unmuteId)) { |
| addToConsole('Invalid id', 'text-red-400'); |
| break; |
| } |
| unmuteAccount(unmuteId); |
| break; |
| |
| case '/del_m': |
| if (args.length !== 1) { |
| addToConsole('Usage: /del_m mid', 'text-red-400'); |
| break; |
| } |
| const delMid = parseInt(args[0]); |
| if (isNaN(delMid)) { |
| addToConsole('Invalid message id', 'text-red-400'); |
| break; |
| } |
| deleteMessage(delMid); |
| break; |
| |
| case '/del_um': |
| if (args.length !== 1) { |
| addToConsole('Usage: /del_um id', 'text-red-400'); |
| break; |
| } |
| const delUserId = parseInt(args[0]); |
| if (isNaN(delUserId)) { |
| addToConsole('Invalid user id', 'text-red-400'); |
| break; |
| } |
| deleteUserMessages(delUserId); |
| break; |
| |
| case '/limit_m': |
| if (args.length !== 1) { |
| addToConsole('Usage: /limit_m x', 'text-red-400'); |
| break; |
| } |
| const newLimit = parseInt(args[0]); |
| if (isNaN(newLimit) || newLimit < 1) { |
| addToConsole('Invalid limit (must be positive number)', 'text-red-400'); |
| break; |
| } |
| setMessageLimit(newLimit); |
| break; |
| |
| case '/limit_reset': |
| resetMessageCounts(); |
| break; |
| |
| case '/vipe_chat': |
| clearChat(); |
| break; |
| |
| case '/vipe_all': |
| clearAll(); |
| break; |
| |
| case '/change_n': |
| if (args.length < 2) { |
| addToConsole('Usage: /change_n id name', 'text-red-400'); |
| break; |
| } |
| const changeNameId = parseInt(args[0]); |
| if (isNaN(changeNameId)) { |
| addToConsole('Invalid id', 'text-red-400'); |
| break; |
| } |
| const newUserName = args.slice(1).join(' '); |
| changeUserName(changeNameId, newUserName); |
| break; |
| |
| case '/change_p': |
| if (args.length !== 2) { |
| addToConsole('Usage: /change_p id password', 'text-red-400'); |
| break; |
| } |
| const changePassId = parseInt(args[0]); |
| if (isNaN(changePassId)) { |
| addToConsole('Invalid id', 'text-red-400'); |
| break; |
| } |
| const newUserPass = args[1]; |
| changeUserPassword(changeNameId, newUserPass); |
| break; |
| |
| case '/check_ip': |
| if (args.length !== 1) { |
| addToConsole('Usage: /check_ip mid', 'text-red-400'); |
| break; |
| } |
| const checkIpMid = parseInt(args[0]); |
| if (isNaN(checkIpMid)) { |
| addToConsole('Invalid message id', 'text-red-400'); |
| break; |
| } |
| checkMessageIP(checkIpMid); |
| break; |
| |
| case '/mute_ip': |
| if (args.length !== 2) { |
| addToConsole('Usage: /mute_ip ip hours', 'text-red-400'); |
| break; |
| } |
| const muteIp = args[0]; |
| const muteIpHours = parseInt(args[1]); |
| if (isNaN(muteIpHours)) { |
| addToConsole('Invalid hours', 'text-red-400'); |
| break; |
| } |
| muteIP(muteIp, muteIpHours); |
| break; |
| |
| case '/unmute_ip': |
| if (args.length !== 1) { |
| addToConsole('Usage: /unmute_ip ip', 'text-red-400'); |
| break; |
| } |
| const unmuteIp = args[0]; |
| unmuteIP(unmuteIp); |
| break; |
| |
| case '/mutes': |
| showMutes(); |
| break; |
| |
| case '/create': |
| if (args.length < 2) { |
| addToConsole('Usage: /create name password', 'text-red-400'); |
| break; |
| } |
| const createName = args[0]; |
| const createPass = args.slice(1).join(' '); |
| createUser(createName, createPass); |
| break; |
| |
| case '/info_n': |
| if (args.length !== 1) { |
| addToConsole('Usage: /info_n id', 'text-red-400'); |
| break; |
| } |
| const infoId = parseInt(args[0]); |
| if (isNaN(infoId)) { |
| addToConsole('Invalid id', 'text-red-400'); |
| break; |
| } |
| getUserName(infoId); |
| break; |
| |
| case '/list': |
| listUsers(); |
| break; |
| |
| case '/help': |
| showHelp(); |
| break; |
| |
| default: |
| addToConsole('Unknown command. Type /help for list of commands', 'text-red-400'); |
| } |
| |
| |
| setTimeout(() => { |
| adminConsole.scrollTop = adminConsole.scrollHeight; |
| }, 10); |
| } |
| |
| |
| function muteAccount(id, hours) { |
| const user = users.find(u => u.id === id); |
| if (!user) { |
| addToConsole(`User with ID ${id} not found`, 'text-red-400'); |
| return; |
| } |
| |
| if (id === 0) { |
| addToConsole('Cannot mute admin account', 'text-red-400'); |
| return; |
| } |
| |
| const expires = Date.now() + hours * 60 * 60 * 1000; |
| |
| |
| mutedAccounts = mutedAccounts.filter(m => m.id !== id); |
| |
| mutedAccounts.push({ id, expires }); |
| addToConsole(`User ${user.name} (ID: ${id}) muted for ${hours} hours`, 'text-green-400'); |
| saveToLocalStorage(); |
| } |
| |
| function unmuteAccount(id) { |
| const user = users.find(u => u.id === id); |
| if (!user) { |
| addToConsole(`User with ID ${id} not found`, 'text-red-400'); |
| return; |
| } |
| |
| const wasMuted = mutedAccounts.some(m => m.id === id); |
| mutedAccounts = mutedAccounts.filter(m => m.id !== id); |
| |
| if (wasMuted) { |
| addToConsole(`User ${user.name} (ID: ${id}) unmuted`, 'text-green-400'); |
| } else { |
| addToConsole(`User ${user.name} (ID: ${id}) was not muted`, 'text-yellow-400'); |
| } |
| saveToLocalStorage(); |
| } |
| |
| function deleteMessage(mid) { |
| const messageIndex = messages.findIndex(m => m.id === mid); |
| if (messageIndex === -1) { |
| addToConsole(`Message with ID ${mid} not found`, 'text-red-400'); |
| return; |
| } |
| |
| messages.splice(messageIndex, 1); |
| addToConsole(`Message ${mid} deleted`, 'text-green-400'); |
| renderMessages(); |
| saveToLocalStorage(); |
| } |
| |
| function deleteUserMessages(id) { |
| const user = users.find(u => u.id === id); |
| if (!user) { |
| addToConsole(`User with ID ${id} not found`, 'text-red-400'); |
| return; |
| } |
| |
| const count = messages.filter(m => m.userId === id).length; |
| messages = messages.filter(m => m.userId !== id); |
| addToConsole(`Deleted ${count} messages from user ${user.name} (ID: ${id})`, 'text-green-400'); |
| renderMessages(); |
| saveToLocalStorage(); |
| } |
| |
| function setMessageLimit(limit) { |
| messageLimit = limit; |
| addToConsole(`Message limit set to ${limit} per hour`, 'text-green-400'); |
| renderMessageCounter(); |
| saveToLocalStorage(); |
| } |
| |
| function resetMessageCounts() { |
| messageCounts = {}; |
| addToConsole('All message counts reset to 0', 'text-green-400'); |
| renderMessageCounter(); |
| saveToLocalStorage(); |
| } |
| |
| function clearChat() { |
| messages = []; |
| addToConsole('Chat cleared', 'text-green-400'); |
| renderMessages(); |
| saveToLocalStorage(); |
| } |
| |
| function clearAll() { |
| |
| users = users.filter(u => u.id === 0); |
| messages = []; |
| mutedAccounts = []; |
| mutedIPs = []; |
| messageCounts = {}; |
| accountCreationLimits = {}; |
| |
| addToConsole('All data cleared except admin account', 'text-green-400'); |
| |
| if (currentUser && currentUser.id !== 0) { |
| handleLogout(); |
| } else { |
| renderMessages(); |
| } |
| |
| saveToLocalStorage(); |
| } |
| |
| function changeUserName(id, newName) { |
| const user = users.find(u => u.id === id); |
| if (!user) { |
| addToConsole(`User with ID ${id} not found`, 'text-red-400'); |
| return; |
| } |
| |
| if (newName.length > 20) { |
| addToConsole('Name must be 20 characters or less', 'text-red-400'); |
| return; |
| } |
| |
| const oldName = user.name; |
| user.name = newName; |
| |
| |
| messages.forEach(msg => { |
| if (msg.userId === id) { |
| msg.userName = newName; |
| } |
| }); |
| |
| addToConsole(`Changed name for user ${id} from "${oldName}" to "${newName}"`, 'text-green-400'); |
| |
| if (currentUser && currentUser.id === id) { |
| userBadge.textContent = `${newName} (ID: ${id})`; |
| } |
| |
| renderMessages(); |
| saveToLocalStorage(); |
| } |
| |
| function changeUserPassword(id, newPassword) { |
| const user = users.find(u => u.id === id); |
| if (!user) { |
| addToConsole(`User with ID ${id} not found`, 'text-red-400'); |
| return; |
| } |
| |
| if (newPassword.length > 20) { |
| addToConsole('Password must be 20 characters or less', 'text-red-400'); |
| return; |
| } |
| |
| user.password = newPassword; |
| addToConsole(`Password changed for user ${user.name} (ID: ${id})`, 'text-green-400'); |
| saveToLocalStorage(); |
| } |
| |
| function checkMessageIP(mid) { |
| const message = messages.find(m => m.id === mid); |
| if (!message) { |
| addToConsole(`Message with ID ${mid} not found`, 'text-red-400'); |
| return; |
| } |
| |
| addToConsole(`Message ${mid} was sent from IP: ${message.ip}`, 'text-green-400'); |
| } |
| |
| function muteIP(ip, hours) { |
| const expires = Date.now() + hours * 60 * 60 * 1000; |
| |
| |
| mutedIPs = mutedIPs.filter(m => m.ip !== ip); |
| |
| mutedIPs.push({ ip, expires }); |
| addToConsole(`IP ${ip} muted for ${hours} hours`, 'text-green-400'); |
| saveToLocalStorage(); |
| } |
| |
| function unmuteIP(ip) { |
| const wasMuted = mutedIPs.some(m => m.ip === ip); |
| mutedIPs = mutedIPs.filter(m => m.ip !== ip); |
| |
| if (wasMuted) { |
| addToConsole(`IP ${ip} unmuted`, 'text-green-400'); |
| } else { |
| addToConsole(`IP ${ip} was not muted`, 'text-yellow-400'); |
| } |
| saveToLocalStorage(); |
| } |
| |
| function showMutes() { |
| if (mutedAccounts.length === 0 && mutedIPs.length === 0) { |
| addToConsole('No active mutes', 'text-yellow-400'); |
| return; |
| } |
| |
| addToConsole('Active account mutes:', 'text-blue-400'); |
| mutedAccounts.forEach(m => { |
| const user = users.find(u => u.id === m.id); |
| const name = user ? user.name : 'Unknown'; |
| const hoursLeft = Math.ceil((m.expires - Date.now()) / (1000 * 60 * 60)); |
| addToConsole(`- ${name} (ID: ${m.id}): ${hoursLeft} hours remaining`); |
| }); |
| |
| addToConsole('Active IP mutes:', 'text-blue-400'); |
| mutedIPs.forEach(m => { |
| const hoursLeft = Math.ceil((m.expires - Date.now()) / (1000 * 60 * 60)); |
| addToConsole(`- ${m.ip}: ${hoursLeft} hours remaining`); |
| }); |
| } |
| |
| function createUser(name, password) { |
| if (name.length > 20) { |
| addToConsole('Name must be 20 characters or less', 'text-red-400'); |
| return; |
| } |
| |
| if (password.length > 20) { |
| addToConsole('Password must be 20 characters or less', 'text-red-400'); |
| return; |
| } |
| |
| const newId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1; |
| const newUser = { |
| id: newId, |
| name, |
| password, |
| isAdmin: false |
| }; |
| |
| users.push(newUser); |
| addToConsole(`Created new user: ${name} (ID: ${newId})`, 'text-green-400'); |
| saveToLocalStorage(); |
| } |
| |
| function getUserName(id) { |
| const user = users.find(u => u.id === id); |
| if (!user) { |
| addToConsole(`User with ID ${id} not found`, 'text-red-400'); |
| return; |
| } |
| |
| addToConsole(`User ${id} name: ${user.name}`, 'text-green-400'); |
| } |
| |
| function listUsers() { |
| if (users.length === 0) { |
| addToConsole('No users found', 'text-yellow-400'); |
| return; |
| } |
| |
| addToConsole('User list (ID - Name):', 'text-blue-400'); |
| users.forEach(user => { |
| addToConsole(`- ${user.id}: ${user.name}${user.isAdmin ? ' (Admin)' : ''}`); |
| }); |
| } |
| |
| function showHelp() { |
| addToConsole('Available commands:', 'text-blue-400'); |
| addToConsole('/mute id x - Mute account by ID for x hours'); |
| addToConsole('/unmute id - Unmute account by ID'); |
| addToConsole('/del_m mid - Delete message by message ID'); |
| addToConsole('/del_um id - Delete all messages by user ID'); |
| addToConsole('/limit_m x - Set message limit per hour to x'); |
| addToConsole('/limit_reset - Reset all message counts to 0'); |
| addToConsole('/vipe_chat - Delete all messages'); |
| addToConsole('/vipe_all - Delete all messages and accounts (except admin)'); |
| addToConsole('/change_n id x - Change username of ID to x'); |
| addToConsole('/change_p id x - Change password of ID to x'); |
| addToConsole('/check_ip mid - Check IP of message by message ID'); |
| addToConsole('/mute_ip ip x - Mute IP for x hours'); |
| addToConsole('/unmute_ip ip - Unmute IP'); |
| addToConsole('/mutes - Show all active mutes'); |
| addToConsole('/create x y - Create account with name x and password y'); |
| addToConsole('/info_n id - Get username by ID'); |
| addToConsole('/list - List all accounts'); |
| addToConsole('/help - Show this help'); |
| } |
| |
| |
| function showAuthMessage(message, color = 'red') { |
| authMessage.textContent = message; |
| authMessage.classList.remove('hidden'); |
| authMessage.classList.remove('text-red-500', 'text-green-500'); |
| authMessage.classList.add(color === 'green' ? 'text-green-500' : 'text-red-500'); |
| } |
| |
| function showMessageError(message) { |
| messageError.textContent = message; |
| messageError.classList.remove('hidden'); |
| } |
| |
| function showSettingsMessage(message, color = 'green') { |
| settingsMessage.textContent = message; |
| settingsMessage.classList.remove('hidden'); |
| settingsMessage.classList.remove('text-red-500', 'text-green-500'); |
| settingsMessage.classList.add(color === 'green' ? 'text-green-500' : 'text-red-500'); |
| } |
| |
| function addToConsole(text, colorClass = 'text-gray-300') { |
| const div = document.createElement('div'); |
| div.className = colorClass; |
| div.textContent = text; |
| adminConsole.appendChild(div); |
| } |
| |
| function formatTime(timestamp) { |
| const date = new Date(timestamp); |
| return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); |
| } |
| |
| function generateRandomIP() { |
| |
| return `${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}`; |
| } |
| |
| |
| function saveToLocalStorage() { |
| const data = { |
| users, |
| messages, |
| mutedAccounts, |
| mutedIPs, |
| messageLimit, |
| messageCounts, |
| accountCreationLimits |
| }; |
| |
| localStorage.setItem('nullMessenger', JSON.stringify(data)); |
| } |
| |
| function loadFromLocalStorage() { |
| const data = localStorage.getItem('nullMessenger'); |
| if (!data) return; |
| |
| try { |
| const parsed = JSON.parse(data); |
| |
| if (parsed.users) users = parsed.users; |
| if (parsed.messages) messages = parsed.messages; |
| if (parsed.mutedAccounts) mutedAccounts = parsed.mutedAccounts; |
| if (parsed.mutedIPs) mutedIPs = parsed.mutedIPs; |
| if (parsed.messageLimit) messageLimit = parsed.messageLimit; |
| if (parsed.messageCounts) messageCounts = parsed.messageCounts; |
| if (parsed.accountCreationLimits) accountCreationLimits = parsed.accountCreationLimits; |
| |
| |
| if (!users.some(u => u.id === 0)) { |
| users.unshift({ id: 0, name: "Null", password: "QWERTYnull123!", isAdmin: true }); |
| } |
| } catch (e) { |
| console.error('Failed to load from localStorage', e); |
| } |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Dapshen/null-v-0-1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |