451 lines
17 KiB
JavaScript
451 lines
17 KiB
JavaScript
#include common.js
|
|
#include parsing.js
|
|
#include title.js
|
|
#include txtrigs.js
|
|
#include url.js
|
|
#include users.js
|
|
#include weeb.js
|
|
#include sound/umisound.js
|
|
#include ui/emotes.js
|
|
|
|
Umi.UI.Messages = (function() {
|
|
let focusChannelName = '';
|
|
|
|
const title = new MamiWindowTitle({
|
|
getName: () => window.TITLE,
|
|
});
|
|
|
|
window.addEventListener('focus', () => title.clear());
|
|
|
|
const shouldDisplayAuthorInfo = (target, ref) => {
|
|
if(!(target instanceof Element) || !(ref instanceof Element))
|
|
return true;
|
|
|
|
return target.dataset.tiny !== undefined
|
|
|| target.dataset.author !== ref.dataset.author
|
|
|| target.dataset.channel !== ref.dataset.channel
|
|
|| target.dataset.tiny !== ref.dataset.tiny;
|
|
};
|
|
|
|
const botMsgs = {
|
|
'say': { text: '%0' },
|
|
'join': { text: '%0 has joined.', action: 'has joined', sound: 'join' },
|
|
'leave': { text: '%0 has disconnected.', action: 'has disconnected', avatar: 'greyscale', sound: 'leave' },
|
|
'jchan': { text: '%0 has joined the channel.', action: 'has joined the channel', sound: 'join' },
|
|
'lchan': { text: '%0 has left the channel.', action: 'has left the channel', avatar: 'greyscale', sound: 'leave' },
|
|
'kick': { text: '%0 got bludgeoned to death.', action: 'got bludgeoned to death', avatar: 'invert', sound: 'kick' },
|
|
'flood': { text: '%0 got kicked for flood protection.', action: 'got kicked for flood protection', avatar: 'invert', sound: 'flood' },
|
|
'timeout': { text: '%0 exploded.', action: 'exploded', avatar: 'greyscale', sound: 'timeout' },
|
|
'nick': { text: '%0 changed their name to %1.', action: 'changed their name to %1' },
|
|
'ipaddr': { text: 'IP address of %0 is %1.' },
|
|
'banlist': {
|
|
text: 'Banned: %0',
|
|
filter: args => {
|
|
const bans = args[0].split(', ');
|
|
for(const i in bans)
|
|
bans[i] = bans[i].slice(92, -4);
|
|
|
|
args[0] = bans.join(', ');
|
|
return args;
|
|
},
|
|
},
|
|
'who': {
|
|
text: 'Online: %0',
|
|
filter: args => {
|
|
const users = args[0].split(', ');
|
|
for(const i in users) {
|
|
const isSelf = users[i].includes(' style="font-weight: bold;"');
|
|
users[i] = users[i].slice(isSelf ? 102 : 75, -4);
|
|
if(isSelf) users[i] += ' (You)';
|
|
}
|
|
|
|
args[0] = users.join(', ');
|
|
return args;
|
|
},
|
|
},
|
|
'whochan': {
|
|
text: 'Online in %0: %1',
|
|
filter: args => {
|
|
const users = args[1].split(', ');
|
|
for(const i in users) {
|
|
const isSelf = users[i].includes(' style="font-weight: bold;"');
|
|
users[i] = users[i].slice(isSelf ? 102 : 75, -4);
|
|
if(isSelf) users[i] += ' (You)';
|
|
}
|
|
|
|
args[1] = users.join(', ');
|
|
return args;
|
|
},
|
|
},
|
|
};
|
|
|
|
const formatTemplate = (template, args) => {
|
|
if(typeof template !== 'string')
|
|
template = '';
|
|
|
|
if(Array.isArray(args))
|
|
for(let i = 0; i < args.length; ++i) {
|
|
const arg = args[i] === undefined || args[i] === null ? '' : args[i].toString();
|
|
template = template.replace(new RegExp(`%${i}`, 'g'), arg);
|
|
}
|
|
|
|
return template;
|
|
};
|
|
|
|
return {
|
|
Add: function(msg) {
|
|
const elementId = `message-${msg.id}`;
|
|
|
|
if(msg.id !== '' && $id(elementId))
|
|
return;
|
|
|
|
let isTiny = false;
|
|
let skipTextParsing = false;
|
|
let msgText = '';
|
|
let msgTextLong = '';
|
|
let msgAuthor = msg.author;
|
|
|
|
let soundIsLegacy = true;
|
|
let soundName;
|
|
let soundVolume;
|
|
let soundRate;
|
|
|
|
const eText = <div/>;
|
|
const eAvatar = <div class="message__avatar"/>;
|
|
const eUser = <div class="message__user"/>;
|
|
const eMeta = <div class="message__meta">{eUser}</div>;
|
|
const eTime = <div class="message__time">
|
|
{msg.created.getHours().toString().padStart(2, '0')}
|
|
:{msg.created.getMinutes().toString().padStart(2, '0')}
|
|
:{msg.created.getSeconds().toString().padStart(2, '0')}
|
|
</div>;
|
|
const eContainer = <div class="message__container">{eMeta}</div>;
|
|
const eBase = <div class="message">
|
|
{eAvatar}
|
|
{eContainer}
|
|
</div>;
|
|
|
|
if(msg.type.startsWith('message:')) {
|
|
msgText = msgTextLong = msg.detail.body;
|
|
soundName = msg.author?.self === true ? 'outgoing' : 'incoming';
|
|
|
|
if(msg.type === 'message:action')
|
|
isTiny = true;
|
|
|
|
if(mami.settings.get('playJokeSounds'))
|
|
try {
|
|
const trigger = mami.textTriggers.getTrigger(msgText);
|
|
if(trigger.isSoundType) {
|
|
soundIsLegacy = false;
|
|
soundName = trigger.getRandomSoundName();
|
|
soundVolume = trigger.volume;
|
|
soundRate = trigger.rate;
|
|
}
|
|
} catch(ex) {}
|
|
} else {
|
|
let bIsError = false;
|
|
let bType;
|
|
let bArgs;
|
|
|
|
if(msg.type === 'user:join') {
|
|
bType = 'join';
|
|
bArgs = [msgAuthor.name];
|
|
} else if(msg.type === 'user:leave') {
|
|
bType = msg.detail.reason;
|
|
bArgs = [msgAuthor.name];
|
|
} else if(msg.type === 'channel:join') {
|
|
bType = 'jchan';
|
|
bArgs = [msgAuthor.name];
|
|
} else if(msg.type === 'channel:leave') {
|
|
bType = 'lchan';
|
|
bArgs = [msgAuthor.name];
|
|
} else if(msg.type.startsWith('legacy:')) {
|
|
bType = msg.type.substring(7);
|
|
bIsError = msg.detail.error;
|
|
bArgs = msg.detail.args;
|
|
}
|
|
|
|
soundName = bIsError ? 'error' : 'server';
|
|
|
|
if(botMsgs.hasOwnProperty(bType)) {
|
|
const bmInfo = botMsgs[bType];
|
|
|
|
if(typeof bmInfo.filter === 'function')
|
|
bArgs = bmInfo.filter(bArgs);
|
|
|
|
if(typeof bmInfo.sound === 'string')
|
|
soundName = bmInfo.sound;
|
|
|
|
let actionSuccess = false;
|
|
if(typeof bmInfo.action === 'string')
|
|
if(msgAuthor) {
|
|
actionSuccess = true;
|
|
isTiny = true;
|
|
skipTextParsing = true;
|
|
|
|
msgText = formatTemplate(bmInfo.action, bArgs);
|
|
if(typeof bmInfo.avatar === 'string')
|
|
eAvatar.classList.add(`avatar-filter-${bmInfo.avatar}`);
|
|
}
|
|
|
|
msgTextLong = formatTemplate(bmInfo.text, bArgs);
|
|
|
|
if(!actionSuccess)
|
|
msgText = msgTextLong;
|
|
} else
|
|
msgText = msgTextLong = `!!! Received unsupported message type: ${msg.type} !!!`;
|
|
}
|
|
|
|
if(msgAuthor !== null) {
|
|
eUser.style.color = msgAuthor.colour;
|
|
eUser.textContent = msgAuthor.name;
|
|
}
|
|
|
|
if(isTiny) {
|
|
eText.classList.add('message-tiny-text');
|
|
eBase.classList.add('message-tiny');
|
|
|
|
if(msgText.indexOf("'") !== 0 || (msgText.match(/\'/g).length % 2) === 0)
|
|
msgText = "\xA0" + msgText;
|
|
|
|
eMeta.append(eText, eTime);
|
|
} else {
|
|
eText.classList.add('message__text');
|
|
eMeta.append(eTime);
|
|
eContainer.append(eText);
|
|
}
|
|
|
|
eBase.classList.add(`message--user-${msgAuthor?.id ?? '-1'}`);
|
|
|
|
if(focusChannelName !== '' && msg.channel !== '' && msg.channel !== focusChannelName)
|
|
eBase.classList.add('hidden');
|
|
|
|
if(msg.id !== '') {
|
|
eBase.id = elementId;
|
|
eBase.dataset.id = msg.id;
|
|
}
|
|
if(msgAuthor)
|
|
eBase.dataset.author = msgAuthor.id;
|
|
if(msg.channel !== '')
|
|
eBase.dataset.channel = msg.channel;
|
|
if(isTiny)
|
|
eBase.dataset.tiny = '1';
|
|
eBase.dataset.created = msg.created.toISOString();
|
|
|
|
eBase.dataset.body = msgText;
|
|
eText.innerText = msgText;
|
|
|
|
if(!skipTextParsing) {
|
|
Umi.UI.Emoticons.Parse(eText, msgAuthor);
|
|
Umi.Parsing.Parse(eText, msg);
|
|
|
|
const textSplit = eText.innerText.split(' ');
|
|
for(const textPart of textSplit) {
|
|
const uri = Umi.URI.Parse(textPart);
|
|
|
|
if(uri !== null && uri.Slashes !== null) {
|
|
const anchorElem = <a class="markup__link" href={textPart} target="_blank" rel="nofollow noreferrer noopener">{textPart}</a>;
|
|
eText.innerHTML = eText.innerHTML.replace(textPart.replace(/&/g, '&'), anchorElem.outerHTML);
|
|
}
|
|
}
|
|
|
|
if(mami.settings.get('weeaboo')) {
|
|
eUser.appendChild($text(Weeaboo.getNameSuffix(msgAuthor)));
|
|
eText.appendChild($text(Weeaboo.getTextSuffix(msgAuthor)));
|
|
|
|
const kaomoji = Weeaboo.getRandomKaomoji(true, msg);
|
|
if(kaomoji)
|
|
eText.append(` ${kaomoji}`);
|
|
}
|
|
}
|
|
|
|
const avatarUrl = msgAuthor?.avatar?.[isTiny ? 'x40' : 'x80'] ?? '';
|
|
if(avatarUrl.length > 0)
|
|
eAvatar.style.backgroundImage = `url('${avatarUrl}')`;
|
|
else
|
|
eAvatar.classList.add('message__avatar--disabled');
|
|
|
|
const msgsList = $id('umi-messages');
|
|
|
|
let insertAfter = msgsList.lastElementChild;
|
|
if(insertAfter instanceof Element) {
|
|
while(insertAfter.dataset.created > eBase.dataset.created) {
|
|
if(!insertAfter.previousElementSibling || !insertAfter.previousElementSibling.dataset.channel)
|
|
break;
|
|
|
|
insertAfter = insertAfter.previousElementSibling;
|
|
}
|
|
|
|
eBase.classList.toggle('message--first', shouldDisplayAuthorInfo(eBase, insertAfter));
|
|
|
|
if(eBase.dataset.tiny !== insertAfter.dataset.tiny)
|
|
eBase.classList.add(isTiny ? 'message-tiny-fix' : 'message-big-fix');
|
|
|
|
insertAfter.after(eBase);
|
|
|
|
if(eBase.nextElementSibling instanceof Element)
|
|
eBase.nextElementSibling.classList.toggle('message--first', shouldDisplayAuthorInfo(eBase.nextElementSibling, eBase));
|
|
} else {
|
|
eBase.classList.add('message--first');
|
|
if(isTiny) eBase.classList.add('message-tiny-fix');
|
|
msgsList.append(eBase);
|
|
}
|
|
|
|
if(!eBase.classList.contains('hidden')) {
|
|
if(mami.settings.get('autoEmbedV1')) {
|
|
const callEmbedOn = eBase.querySelectorAll('a[onclick^="Umi.Parser.SockChatBBcode.Embed"]');
|
|
for(const embedElem of callEmbedOn)
|
|
if(embedElem.dataset.embed !== '1')
|
|
embedElem.click();
|
|
}
|
|
|
|
if(mami.settings.get('autoScroll'))
|
|
eBase.scrollIntoView({ inline: 'end' });
|
|
}
|
|
|
|
let isMentioned = false;
|
|
const mentionTriggers = mami.settings.get('notificationTriggers').toLowerCase().split(' ');
|
|
const currentUser = Umi.User.getCurrentUser();
|
|
if(typeof currentUser === 'object' && typeof currentUser.name === 'string')
|
|
mentionTriggers.push(currentUser.name.toLowerCase());
|
|
|
|
const mentionText = ` ${msgTextLong} `.toLowerCase();
|
|
for(const trigger of mentionTriggers) {
|
|
if(trigger.trim() === '')
|
|
continue;
|
|
|
|
if(mentionText.includes(` ${trigger} `)) {
|
|
isMentioned = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!isMentioned && mami.settings.get('onlySoundOnMention'))
|
|
soundName = undefined;
|
|
|
|
if(document.hidden) {
|
|
if(mami.settings.get('flashTitle')) {
|
|
let titleText = msgAuthor?.name ?? msgTextLong;
|
|
if(focusChannelName !== '' && focusChannelName !== msg.channel)
|
|
titleText += ` @ ${msg.channel}`;
|
|
|
|
title.strobe([
|
|
`[ @] ${titleText}`,
|
|
`[@ ] ${titleText}`,
|
|
]);
|
|
}
|
|
|
|
if(!msg.silent) {
|
|
if(mami.settings.get('enableNotifications') && isMentioned) {
|
|
const options = {};
|
|
|
|
options.body = 'Click here to see what they said.';
|
|
if(mami.settings.get('notificationShowMessage'))
|
|
options.body += "\n" + msgTextLong;
|
|
|
|
if(avatarUrl.length > 0)
|
|
options.icon = avatarUrl;
|
|
|
|
const notif = new Notification(`${msgAuthor.name} mentioned you!`, options);
|
|
notif.addEventListener('click', () => {
|
|
window.focus();
|
|
});
|
|
document.addEventListener('visibilitychange', () => {
|
|
if(document.visibilityState === 'visible')
|
|
notif.close();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!msg.silent && soundName !== undefined) {
|
|
if(soundIsLegacy)
|
|
soundName = Umi.Sound.Convert(soundName);
|
|
|
|
mami.sound.library.play(soundName, soundVolume, soundRate);
|
|
}
|
|
|
|
mami.globalEvents.dispatch('umi:ui:message_add', { element: eBase });
|
|
},
|
|
IsScrolledToBottom: () => {
|
|
const msgsList = $id('umi-messages');
|
|
return msgsList.scrollTop === (msgsList.scrollHeight - msgsList.offsetHeight);
|
|
},
|
|
ScrollIfNeeded: (offsetOrForce = 0) => {
|
|
const msgsList = $id('umi-messages');
|
|
if(!(msgsList instanceof Element))
|
|
return;
|
|
|
|
if(typeof offsetOrForce === 'boolean' && offsetOrForce !== true)
|
|
return;
|
|
|
|
if(typeof offsetOrForce === 'number' && msgsList.scrollTop < (msgsList.scrollHeight - msgsList.offsetHeight - offsetOrForce))
|
|
return;
|
|
|
|
if(mami.settings.get('autoScroll'))
|
|
msgsList.lastElementChild?.scrollIntoView({ inline: 'end' });
|
|
},
|
|
SwitchChannel: channel => {
|
|
if(typeof channel === 'object' && channel !== null && 'name' in channel)
|
|
channel = channel.name;
|
|
if(typeof channel !== 'string')
|
|
return;
|
|
|
|
focusChannelName = channel;
|
|
|
|
const root = $id('umi-messages');
|
|
for(const elem of root.children)
|
|
elem.classList.toggle('hidden', elem.dataset.channel !== undefined && elem.dataset.channel !== focusChannelName);
|
|
|
|
Umi.UI.Messages.ScrollIfNeeded();
|
|
},
|
|
Clear: retain => {
|
|
if(typeof retain === 'string' && !isNaN(retain))
|
|
retain = parseInt(retain);
|
|
if(typeof retain !== 'number')
|
|
return;
|
|
|
|
const root = $id('umi-messages');
|
|
|
|
// remove messages
|
|
if(root.childElementCount > retain)
|
|
for(let i = root.childElementCount - 1; i >= 0; --i) {
|
|
const elem = root.children[i];
|
|
|
|
if(!elem.dataset.channel || elem.classList.contains('hidden') || --retain > 0)
|
|
continue;
|
|
|
|
elem.remove();
|
|
}
|
|
|
|
// fix author display
|
|
for(const elem of root.children) {
|
|
elem.classList.toggle('message--first', shouldDisplayAuthorInfo(elem, elem.previousElementSibling));
|
|
lastAuthor = elem.dataset.author;
|
|
}
|
|
},
|
|
Remove: function(msgId) {
|
|
if(typeof msgId === 'object' && msgId !== null) {
|
|
if('getId' in msgId)
|
|
msgId = msgId.getId();
|
|
else if('id' in msgId)
|
|
msgId = msgId.id;
|
|
}
|
|
if(typeof msgId !== 'string')
|
|
msgId = msgId.toString();
|
|
|
|
if(msgId === '')
|
|
return;
|
|
|
|
const elem = $id(`message-${msgId}`);
|
|
if(!(elem instanceof Element))
|
|
return;
|
|
|
|
// todo: take channel into account
|
|
if(elem.nextElementSibling && elem.nextElementSibling.dataset.author === elem.dataset.author)
|
|
elem.nextElementSibling.classList.add('message--first');
|
|
|
|
elem.remove();
|
|
},
|
|
};
|
|
})();
|