1/** 2 * Moderator System 3 * Handles moderator privileges and admin actions 4 */ 5 6window.ChatroomModerator = (function() { 7 'use strict'; 8 9 let moderatorsRef = null; 10 let currentUsername = null; 11 let isModerator = false; 12 let moderatorsList = new Set(); 13 14 /** 15 * Initialize moderator system 16 */ 17 function init() { 18 if (typeof firebase === 'undefined' || !firebase.database) { 19 console.error('Firebase not available for moderator system'); 20 return false; 21 } 22 23 moderatorsRef = firebase.database().ref('chatroom/moderators'); 24 25 // Wait for username 26 const checkUsername = setInterval(() => { 27 if (window.__sharedUsername) { 28 currentUsername = window.__sharedUsername.get(); 29 if (currentUsername) { 30 clearInterval(checkUsername); 31 checkModeratorStatus(); 32 setupModeratorListeners(); 33 } 34 } 35 }, 100); 36 37 setTimeout(() => { 38 clearInterval(checkUsername); 39 if (!currentUsername) { 40 currentUsername = 'guest_' + Date.now(); 41 checkModeratorStatus(); 42 setupModeratorListeners(); 43 } 44 }, 5000); 45 46 return true; 47 } 48 49 /** 50 * Check if current user is a moderator 51 */ 52 function checkModeratorStatus() { 53 if (!currentUsername || !moderatorsRef) return; 54 55 moderatorsRef.child(currentUsername).once('value', (snapshot) => { 56 const wasModerator = isModerator; 57 isModerator = snapshot.exists() && snapshot.val() === true; 58 59 if (isModerator !== wasModerator) { 60 updateModeratorUI(); 61 if (isModerator) { 62 console.log('â Moderator privileges activated for:', currentUsername); 63 } 64 } 65 }); 66 } 67 68 /** 69 * Setup listeners for moderator list changes 70 */ 71 function setupModeratorListeners() { 72 if (!moderatorsRef) return; 73 74 moderatorsRef.on('value', (snapshot) => { 75 moderatorsList.clear(); 76 const moderators = snapshot.val(); 77 78 if (moderators) { 79 Object.keys(moderators).forEach(username => { 80 if (moderators[username] === true) { 81 moderatorsList.add(username); 82 } 83 }); 84 } 85 86 // Re-check current user's status 87 if (currentUsername) { 88 checkModeratorStatus(); 89 } 90 91 // Update moderator badges in UI 92 updateModeratorBadges(); 93 }); 94 } 95 96 /** 97 * Update UI to show moderator controls 98 */ 99 function updateModeratorUI() { 100 // Add moderator badge to user info 101 const userInfo = document.getElementById('user-info'); 102 if (userInfo) { 103 const existingBadge = userInfo.querySelector('.moderator-badge'); 104 if (isModerator && !existingBadge) { 105 const badge = document.createElement('span'); 106 badge.className = 'moderator-badge'; 107 badge.textContent = 'ð'; 108 badge.title = 'Moderator'; 109 userInfo.appendChild(badge); 110 } else if (!isModerator && existingBadge) { 111 existingBadge.remove(); 112 } 113 } 114 115 // Add delete buttons to messages 116 if (isModerator) { 117 addModeratorControlsToMessages(); 118 } 119 } 120 121 /** 122 * Show add moderator modal 123 */ 124 function showAddModModal() { 125 const username = prompt('Enter username to add as moderator:'); 126 if (username && username.trim()) { 127 addModerator(username.trim()); 128 } 129 } 130 131 /** 132 * Show remove moderator modal 133 */ 134 function showRemoveModModal() { 135 const username = prompt('Enter username to remove as moderator:'); 136 if (username && username.trim()) { 137 removeModerator(username.trim()); 138 } 139 } 140 141 /** 142 * Add moderator controls to messages 143 */ 144 function addModeratorControlsToMessages() { 145 document.querySelectorAll('.message .moderator-actions').forEach(function (el) { 146 el.remove(); 147 }); 148 } 149 150 /** 151 * Update moderator badges on usernames 152 */ 153 function updateModeratorBadges() { 154 document.querySelectorAll('.message-username').forEach(usernameEl => { 155 const username = usernameEl.textContent.trim(); 156 const existingBadge = usernameEl.querySelector('.mod-badge'); 157 158 if (moderatorsList.has(username)) { 159 if (!existingBadge) { 160 const badge = document.createElement('span'); 161 badge.className = 'mod-badge'; 162 badge.textContent = 'ð'; 163 badge.title = 'Moderator'; 164 usernameEl.insertBefore(badge, usernameEl.firstChild); 165 } 166 } else if (existingBadge) { 167 existingBadge.remove(); 168 } 169 }); 170 } 171 172 /** 173 * Delete a message (own messages or moderator) 174 */ 175 function deleteMessage(messageId) { 176 if (!confirm('Are you sure you want to delete this message?')) { 177 return; 178 } 179 180 const currentDM = window.ChatroomDMs ? window.ChatroomDMs.getCurrentDM() : null; 181 182 // Function to find and delete message by id field 183 const findAndDeleteMessage = (messagesRef) => { 184 // Query all messages to find the one with matching id 185 messagesRef.once('value').then((snapshot) => { 186 if (!snapshot.exists()) { 187 alert('Message not found.'); 188 return; 189 } 190 191 const messages = snapshot.val(); 192 let messageKey = null; 193 let message = null; 194 195 // messageId can be the Firebase key (from data-message-id) or the stored id field 196 if (messages[messageId]) { 197 messageKey = messageId; 198 message = messages[messageId]; 199 } else { 200 // Fallback: find by id field (legacy) 201 for (const key in messages) { 202 if (messages[key].id === messageId) { 203 messageKey = key; 204 message = messages[key]; 205 break; 206 } 207 } 208 } 209 210 if (!message || !messageKey) { 211 alert('Message not found.'); 212 return; 213 } 214 215 const messageUsername = message.username || ''; 216 217 // Check if user is moderator OR if message belongs to current user 218 if (!isModerator && messageUsername !== currentUsername) { 219 alert('You can only delete your own messages.'); 220 return; 221 } 222 223 // Delete the message using the Firebase key 224 messagesRef.child(messageKey).remove().then(() => {
225 console.log('â Message deleted'); 226 if (window.ChatroomModerationLog) { 227 window.ChatroomModerationLog.logMessageDelete({ 228 messageAuthor: messageUsername, 229 actor: currentUsername, 230 messageText: message.text || '', 231 messageId: messageId, 232 channel: currentDM ? '' : (window.Chatroom ? window.Chatroom.getCurrentChannel() : 'general'), 233 isDm: !!currentDM, 234 source: isModerator && messageUsername !== currentUsername ? 'moderator' : 'self' 235 }); 236 } 237 // Remove from UI 238 const messageEl = document.querySelector(`[data-message-id="${messageId}"]`); 239 if (messageEl) { 240 messageEl.remove(); 241 } 242 243 const messagesList = document.getElementById('messages-list'); 244 if (window.ChatroomMessageGrouping && messagesList) { 245 if (typeof window.ChatroomMessageGrouping.afterMessageRemoved === 'function') { 246 window.ChatroomMessageGrouping.afterMessageRemoved(messagesList); 247 } else if (typeof window.ChatroomMessageGrouping.reapplyDom === 'function') { 248 window.ChatroomMessageGrouping.reapplyDom(messagesList); 249 } 250 } 251 252 if (window.Chatroom && typeof window.Chatroom.removeMessageFromCache === 'function') { 253 window.Chatroom.removeMessageFromCache(messageId); 254 } 255 if (window.ChatroomDMs && typeof window.ChatroomDMs.removeMessageFromCache === 'function') { 256 window.ChatroomDMs.removeMessageFromCache(messageId); 257 } 258 }).catch(error => { 259 console.error('Error deleting message:', error); 260 alert('Error deleting message. You may not have permission.'); 261 }); 262 }).catch(error => { 263 console.error('Error fetching messages:', error); 264 alert('Error fetching messages. Please try again.'); 265 }); 266 }; 267 268 if (currentDM) { 269 // Delete DM message 270 const conversationId = [currentUsername, currentDM].sort().join('_'); 271 const messagesRef = firebase.database().ref(`chatroom/dms/${conversationId}/messages`); 272 findAndDeleteMessage(messagesRef); 273 } else { 274 // Delete channel message 275 const currentChannel = window.Chatroom ? window.Chatroom.getCurrentChannel() : 'general'; 276 const messagesRef = firebase.database().ref(`chatroom/channels/${currentChannel}/messages`); 277 findAndDeleteMessage(messagesRef); 278 } 279 } 280 281 /** 282 * Delete a channel (moderator only) 283 */ 284 function deleteChannel(channelId) { 285 if (!isModerator) { 286 alert('You do not have permission to delete channels.'); 287 return; 288 } 289 290 if (channelId === 'general') { 291 alert('Cannot delete the general channel.'); 292 return; 293 } 294 295 if (!confirm(`Are you sure you want to delete the channel "${channelId}"? This cannot be undone.`)) { 296 return; 297 } 298 299 const channelRef = firebase.database().ref(`chatroom/channels/${channelId}`); 300 channelRef.remove().then(() => { 301 console.log('â Channel deleted:', channelId); 302 if (window.ChatroomModerationLog) { 303 window.ChatroomModerationLog.logChannelDelete({ 304 actor: currentUsername, 305 channel: channelId 306 }); 307 } 308 309 // Switch to general if we were in the deleted channel 310 if (window.Chatroom && window.Chatroom.getCurrentChannel() === channelId) { 311 window.Chatroom.loadChannel('general'); 312 } 313 }).catch(error => { 314 console.error('Error deleting channel:', error); 315 alert('Error deleting channel. You may not have permission.'); 316 }); 317 } 318 319 /** 320 * Add a moderator (only existing moderators can do this) 321 */ 322 function addModerator(username) { 323 if (!isModerator) { 324 alert('You do not have permission to add moderators.'); 325 return; 326 } 327 328 if (!username || username.trim().length === 0) { 329 alert('Please enter a valid username.'); 330 return; 331 } 332
333 const cleanUsername = username.trim(); 334 moderatorsRef.child(cleanUsername).set(true).then(() => { 335 console.log('â Moderator added:', cleanUsername); 336 alert(`Moderator "${cleanUsername}" has been added.`); 337 }).catch(error => { 338 console.error('Error adding moderator:', error); 339 alert('Error adding moderator. You may not have permission.'); 340 }); 341 } 342 343 /** 344 * Remove a moderator (only existing moderators can do this) 345 */ 346 function removeModerator(username) { 347 if (!isModerator) { 348 alert('You do not have permission to remove moderators.'); 349 return; 350 } 351 352 if (username === currentUsername) { 353 alert('You cannot remove yourself as a moderator.'); 354 return; 355 } 356 357 if (!confirm(`Are you sure you want to remove "${username}" as a moderator?`)) { 358 return; 359 } 360 361 moderatorsRef.child(username).remove().then(() => { 362 console.log('â Moderator removed:', username); 363 alert(`Moderator "${username}" has been removed.`); 364 }).catch(error => { 365 console.error('Error removing moderator:', error); 366 alert('Error removing moderator. You may not have permission.'); 367 }); 368 } 369 370 /** 371 * Check if user is moderator 372 */ 373 function checkIsModerator() { 374 return isModerator; 375 } 376 377 /** 378 * Check if a specific username is a moderator 379 */ 380 function isUserModerator(username) { 381 return moderatorsList.has(username); 382 } 383 384 // Initialize on load 385 if (document.readyState === 'loading') { 386 document.addEventListener('DOMContentLoaded', init); 387 } else { 388 init(); 389 } 390 391 // Expose functions globally for onclick handlers 392 window.ChatroomModerator = { 393 deleteMessage, 394 deleteChannel, 395 addModerator, 396 removeModerator, 397 checkIsModerator, 398 isUserModerator, 399 showAddModModal, 400 showRemoveModModal, 401 init 402 }; 403 404 return { 405 deleteMessage, 406 deleteChannel, 407 addModerator, 408 removeModerator, 409 checkIsModerator, 410 isUserModerator, 411 init 412 }; 413})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.