diff --git a/assets/chat/js/chat.js b/assets/chat/js/chat.js index 7f5c2078..0ad9435e 100644 --- a/assets/chat/js/chat.js +++ b/assets/chat/js/chat.js @@ -634,8 +634,8 @@ class Chat { const nicks = [...(this.settings.get('highlightnicks') || [])].filter( (a) => a !== '', ); - this.regexhighlightself = this.user.nick - ? new RegExp(`\\b(?:${this.user.nick})\\b`, 'i') + this.regexhighlightself = this.user.displayName + ? new RegExp(`\\b(?:${this.user.displayName})\\b`, 'i') : null; this.regexhighlightcustom = cust.length > 0 ? new RegExp(`\\b(?:${cust.join('|')})\\b`, 'i') : null; @@ -718,18 +718,15 @@ class Chat { message.slashme = message.message.substring(0, 4).toLowerCase() === '/me '; // check if this is the current users message - message.isown = - message.user.username.toLowerCase() === - this.user.username.toLowerCase(); + message.isown = message.user.username === this.user.username; // get mentions from message message.mentioned = Chat.extractNicks(message.message).filter((a) => this.users.has(a.toLowerCase()), ); // set tagged state - message.tag = this.taggednicks.get(message.user.nick.toLowerCase()); + message.tag = this.taggednicks.get(message.user.username); // set tagged note - message.title = - this.taggednotes.get(message.user.nick.toLowerCase()) || ''; + message.title = this.taggednotes.get(message.user.username) || ''; } // Populate highlight for this $message @@ -739,8 +736,7 @@ class Chat { win.lastmessage && !win.lastmessage.target && win.lastmessage.user && - win.lastmessage.user.username.toLowerCase() === - message.user.username.toLowerCase(); + win.lastmessage.user.username === message.user.username; // set highlighted state message.highlighted = this.shouldHighlightMessage(message); } @@ -754,7 +750,7 @@ class Chat { win.addMessage(this, message); // Hide the message if the user is ignored - if (message.user && this.ignored(message.user.nick, message.message)) { + if (message.user && this.ignored(message.user.username, message.message)) { message.ignore(); } @@ -767,7 +763,7 @@ class Chat { !message.ignored ) { Chat.showNotification( - `${message.user.username} said ...`, + `${message.user.displayName} said ...`, message.message, message.timestamp.valueOf(), this.settings.get('notificationtimeout'), @@ -780,7 +776,7 @@ class Chat { resolveMessage(nick, str) { for (const message of this.unresolved) { if ( - this.user.username.toLowerCase() === nick.toLowerCase() && + this.user.username === nick.toLowerCase() && message.message === str ) { this.unresolved.splice(this.unresolved.indexOf(message), 1); @@ -871,7 +867,7 @@ class Chat { censor(nick) { for (const message of this.mainwindow.messages) { - if (message.user?.username === nick) { + if (message.user?.username === nick.toLowerCase()) { message.censor(parseInt(this.settings.get('showremoved') || '1', 10)); } } @@ -879,8 +875,8 @@ class Chat { this.mainwindow.update(); } - ignored(nick, text = null) { - const ignore = this.ignoring.has(nick.toLowerCase()); + ignored(username, text = null) { + const ignore = this.ignoring.has(username); if (!ignore && text !== null) { return ( (this.settings.get('ignorementions') && @@ -929,7 +925,7 @@ class Chat { setDefaultPlaceholderText() { const placeholderText = this.authenticated - ? `Write something ${this.user.username} ...` + ? `Write something ${this.user.displayName} ...` : `Write something ...`; this.input.attr('placeholder', placeholderText); } @@ -975,7 +971,9 @@ class Chat { if (data.recipient) { users.push(this.addUser(data.recipient)); } - users.forEach((u) => this.autocomplete.add(u.nick, false, Date.now())); + users.forEach((u) => + this.autocomplete.add(u.username, false, Date.now()), + ); } } @@ -1013,7 +1011,7 @@ class Chat { onNAMES(data) { MessageBuilder.status( `Connected as ${ - this.authenticated ? this.user.username : 'Guest' + this.authenticated ? this.user.displayName : 'Guest' }. Serving ${data.connectioncount || 0} connections and ${ data.users.length } users.`, @@ -1080,14 +1078,14 @@ class Chat { onVOTECAST(data) { const usr = this.users.get(data.nick.toLowerCase()); this.chatpoll.castVote(data, usr); - if (data.nick.toLowerCase() === this.user.nick.toLowerCase()) { + if (data.nick.toLowerCase() === this.user.username) { this.chatpoll.markVote(data.vote); } } onMUTE(data) { // data.data is the nick which has been banned - if (this.user.username.toLowerCase() === data.data.toLowerCase()) { + if (this.user.username === data.data.toLowerCase()) { MessageBuilder.command( `You have been muted by ${data.nick}.`, data.timestamp, @@ -1109,7 +1107,7 @@ class Chat { } onUNMUTE(data) { - if (this.user.username.toLowerCase() === data.data.toLowerCase()) { + if (this.user.username === data.data.toLowerCase()) { MessageBuilder.command( `You have been unmuted by ${data.nick}.`, data.timestamp, @@ -1126,7 +1124,7 @@ class Chat { onBAN(data) { // data.data is the nick which has been banned, no info about duration - if (this.user.username.toLowerCase() === data.data.toLowerCase()) { + if (this.user.username === data.data.toLowerCase()) { MessageBuilder.command( `You have been banned by ${data.nick}. Check your profile for more information.`, data.timestamp, @@ -1142,7 +1140,7 @@ class Chat { } onUNBAN(data) { - if (this.user.username.toLowerCase() === data.data.toLowerCase()) { + if (this.user.username === data.data.toLowerCase()) { MessageBuilder.command( `You have been unbanned by ${data.nick}.`, data.timestamp, @@ -1302,7 +1300,7 @@ class Chat { } else { conv.unread += 1; } - this.replyusername = user.username; + this.replyusername = user.displayName; this.menus.get('whisper-users').redraw(); this.redrawWindowIndicators(); } @@ -1501,9 +1499,7 @@ class Chat { ).into(this); } } else if ( - parts.some( - (username) => username.toLowerCase() === this.user.nick.toLowerCase(), - ) + parts.some((username) => username.toLowerCase() === this.user.username) ) { MessageBuilder.info("You can't add yourself to your ignore list.").into( this, @@ -1726,7 +1722,7 @@ class Chat { cmdWHISPER(parts) { if (!parts[0] || !nickregex.test(parts[0])) { MessageBuilder.error('Invalid nick - /msg nick message').into(this); - } else if (parts[0].toLowerCase() === this.user.username.toLowerCase()) { + } else if (parts[0].toLowerCase() === this.user.username) { MessageBuilder.error('Cannot send a message to yourself').into(this); } else { const data = parts.slice(1, parts.length).join(' '); @@ -1767,7 +1763,7 @@ class Chat { return; } const n = parts[0].toLowerCase(); - if (n === this.user.username.toLowerCase()) { + if (n === this.user.username) { MessageBuilder.error('Cannot tag yourself').into(this); return; } @@ -1980,7 +1976,7 @@ class Chat { const win = this.getActiveWindow(); const lastuser = win.lastmessage && win.lastmessage.user - ? win.lastmessage.user.username + ? win.lastmessage.user.displayName : null; const username = this.replyusername !== null && this.replyusername !== '' @@ -2211,7 +2207,7 @@ class Chat { const win = new ChatWindow( normalized, 'chat-output-whisper', - user.nick, + user.displayName, ).into(this); let once = true; win.on('show', () => { @@ -2220,7 +2216,7 @@ class Chat { MessageBuilder.info(`Messages between you and ${nick}`).into(this, win); fetch( `${this.config.api.base}/api/messages/usr/${encodeURIComponent( - user.nick, + user.displayName, )}/inbox`, { credentials: 'include' }, ) diff --git a/assets/chat/js/menus/ChatUserInfoMenu.js b/assets/chat/js/menus/ChatUserInfoMenu.js index 5609de80..39fb8ec0 100644 --- a/assets/chat/js/menus/ChatUserInfoMenu.js +++ b/assets/chat/js/menus/ChatUserInfoMenu.js @@ -207,7 +207,7 @@ export default class ChatUserInfoMenu extends ChatMenuFloating { [ this.clickedNick, providedDuration, - `${this.clickedNick} banned by ${this.chat.user.nick}.`, + `${this.clickedNick} banned by ${this.chat.user.displayName}.`, ], 'IPBAN', ); @@ -233,7 +233,7 @@ export default class ChatUserInfoMenu extends ChatMenuFloating { const selectedUser = [...message[0].querySelectorAll('.user')].find( (user) => user.innerText.toLowerCase() === this.clickedNick.toLowerCase(), ); - const prettyNick = selectedUser.innerText; + const displayName = selectedUser.innerText; const tagNote = this.chat.taggednotes.get(this.clickedNick); const usernameFeatures = selectedUser.classList.value; @@ -265,7 +265,7 @@ export default class ChatUserInfoMenu extends ChatMenuFloating { this.flairSubheader.style.display = 'none'; } - const messageList = this.createMessages(); + const messageList = this.createMessages(displayName); if (messageList.length === 0) { this.messagesList.toggleClass('hidden', true); this.messagesSubheader.style.display = 'none'; @@ -282,7 +282,7 @@ export default class ChatUserInfoMenu extends ChatMenuFloating { this.messagesContainer.empty(); this.flairList.empty(); - this.header.text(prettyNick); + this.header.text(displayName); this.header.addClass(usernameFeatures); this.flairList.append(featuresList); messageList.forEach((element) => { @@ -320,7 +320,7 @@ export default class ChatUserInfoMenu extends ChatMenuFloating { return features !== '' ? `${features}` : ''; } - createMessages() { + createMessages(nick) { const displayedMessages = []; if (this.messageArray.length > 0) { let nextMsg = this.messageArray[0].next('.msg-continue'); @@ -330,7 +330,6 @@ export default class ChatUserInfoMenu extends ChatMenuFloating { } this.messageArray.forEach((element) => { const text = element.find('.text')[0].innerText; - const nick = element.data('username'); // Create a new `ChatUser` to remove username styles for a cleaner look. const msg = MessageBuilder.message(text, new ChatUser(nick)); diff --git a/assets/chat/js/menus/ChatUserMenu.js b/assets/chat/js/menus/ChatUserMenu.js index 68ede387..dd401f50 100644 --- a/assets/chat/js/menus/ChatUserMenu.js +++ b/assets/chat/js/menus/ChatUserMenu.js @@ -235,7 +235,9 @@ export default class ChatUserMenu extends ChatMenu { addElement(messageUser, sort = false) { const user = new ChatUser(messageUser); const label = - !user.username || user.username === '' ? 'Anonymous' : user.username; + !user.displayName || user.displayName === '' + ? 'Anonymous' + : user.displayName; const features = user.features.length === 0 ? 'nofeature' : user.features.join(' '); const usr = $( diff --git a/assets/chat/js/menus/ChatWhisperUsers.js b/assets/chat/js/menus/ChatWhisperUsers.js index 0d1b6f17..981e9092 100644 --- a/assets/chat/js/menus/ChatWhisperUsers.js +++ b/assets/chat/js/menus/ChatWhisperUsers.js @@ -67,11 +67,9 @@ export default class ChatWhisperUsers extends ChatMenu { const user = this.chat.users.get(nick.toLowerCase()) || new ChatUser(nick); this.usersEl.append(`
  • - ${ - user.nick - } + ${user.displayName} ${unread} - +
  • `); } diff --git a/assets/chat/js/messages/ChatDonationMessage.js b/assets/chat/js/messages/ChatDonationMessage.js index a6b5bf83..1234e01f 100644 --- a/assets/chat/js/messages/ChatDonationMessage.js +++ b/assets/chat/js/messages/ChatDonationMessage.js @@ -34,7 +34,7 @@ export default class ChatDonationMessage extends ChatEventMessage { user.title = this.title; user.classList.add(colorFlair?.name); - user.innerText = this.user.username; + user.innerText = this.user.displayName; eventTemplate.querySelector('.event-info').append( user, diff --git a/assets/chat/js/messages/ChatEventMessage.js b/assets/chat/js/messages/ChatEventMessage.js index 5c0c4f63..9936c719 100644 --- a/assets/chat/js/messages/ChatEventMessage.js +++ b/assets/chat/js/messages/ChatEventMessage.js @@ -17,7 +17,7 @@ export default class ChatEventMessage extends ChatMessage { ?.content.cloneNode(true).firstElementChild; if (this.user && this.user.username) - eventTemplate.dataset.username = this.user.username.toLowerCase(); + eventTemplate.dataset.username = this.user.username; if (this.mentioned && this.mentioned.length > 0) eventTemplate.dataset.mentioned = this.mentioned.join(' ').toLowerCase(); if (this.slashme) eventTemplate.classList.add('msg-me'); diff --git a/assets/chat/js/messages/ChatUserMessage.js b/assets/chat/js/messages/ChatUserMessage.js index 63835093..2cf48031 100644 --- a/assets/chat/js/messages/ChatUserMessage.js +++ b/assets/chat/js/messages/ChatUserMessage.js @@ -36,7 +36,7 @@ export default class ChatUserMessage extends ChatMessage { if (this.id) attr['data-id'] = this.id; if (this.user && this.user.username) { classes.push(...this.user.features); - attr['data-username'] = this.user.username.toLowerCase(); + attr['data-username'] = this.user.username; } if (this.mentioned && this.mentioned.length > 0) attr['data-mentioned'] = this.mentioned.join(' ').toLowerCase(); @@ -56,7 +56,7 @@ export default class ChatUserMessage extends ChatMessage { const colorFlair = usernameColorFlair(chat.flairs, this.user); const user = `${this.buildFeatures(this.user, chat)} ${this.user.username}`; + }" class="user ${colorFlair?.name}">${this.user.displayName}`; return this.wrap( `${this.buildTime()} ${user}${ctrl} ${this.buildMessageTxt( chat, diff --git a/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js index 208ddbf8..4ea3e7d0 100644 --- a/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js @@ -40,7 +40,7 @@ export default class ChatSubscriptionMessage extends ChatEventMessage { ?.content.cloneNode(true).firstElementChild; user.title = this.title; user.classList.add(colorFlair?.name); - user.innerText = this.user.username; + user.innerText = this.user.displayName; const tierLabel = this.tierLabel ?? `Tier ${this.tier}`; diff --git a/assets/chat/js/mutedtimer.js b/assets/chat/js/mutedtimer.js index bf3a8ec0..b0ab5caf 100644 --- a/assets/chat/js/mutedtimer.js +++ b/assets/chat/js/mutedtimer.js @@ -74,7 +74,7 @@ class MutedTimer { getPlaceholderText() { return `Sorry, ${ - this.chat.user.username + this.chat.user.displayName }, you are muted. You can chat again ${this.getReadableDuration()}.`; } diff --git a/assets/chat/js/user.js b/assets/chat/js/user.js index 27e8dff1..8a846dc7 100644 --- a/assets/chat/js/user.js +++ b/assets/chat/js/user.js @@ -1,19 +1,57 @@ import UserFeature from './features'; +/** + * @typedef {Object} User + * @property {Number} id + * @property {string} nick + * @property {string} createdDate + * @property {string[]} features + */ + class ChatUser { - constructor(args = {}) { - if (typeof args === 'string') { - this.id = null; - this.nick = args; - this.username = args; - this.createdDate = args; - this.features = []; + /** + * User's immutable ID. + * @type {?Number} + */ + id = null; + + /** + * User's name that is displayed in chat (i.e. with case preserved). + * @type {string} + */ + displayName = ''; + + /** + * User's normalized (lowercase) name. + * @type {string} + */ + username = ''; + + /** + * User's creation date as an RFC3339 date-time string (e.g. '2069-04-20T13:37:00Z'). + * @type {string} + */ + createdDate = ''; + + /** + * User's features (a.k.a. flairs). + * @type {[]string} + */ + features = []; + + /** + * @param {string|User} user + */ + constructor(user = '') { + if (typeof user === 'string') { + this.displayName = user; + this.username = this.displayName.toLowerCase(); } else { - this.id = args.id || null; - this.nick = args.nick || ''; - this.username = args.nick || ''; - this.createdDate = args.createdDate || ''; - this.features = args.features || []; + this.id = user.id; + this.displayName = user.nick; + this.username = this.displayName.toLowerCase(); + this.createdDate = user.createdDate; + this.features = user.features; } } diff --git a/assets/chat/js/window.js b/assets/chat/js/window.js index c47de9b0..be45b57a 100644 --- a/assets/chat/js/window.js +++ b/assets/chat/js/window.js @@ -128,9 +128,9 @@ class ChatWindow extends EventEmitter { } if (message.user) { - const username = message.user.username.toLowerCase(); + const { username } = message.user; - message.setOwnMessage(username === chat.user.username.toLowerCase()); + message.setOwnMessage(username === chat.user.username); message.ignore(chat.ignored(username, message.message)); message.highlight(chat.shouldHighlightMessage(message)); message.setTag(chat.taggednicks.get(username));