Attempts at making message rendering and notifications more consistent and moved a lot of UI stuff out of the protocol handler.

This commit is contained in:
flash 2024-02-16 00:41:13 +00:00
parent c0246909c0
commit cdabf80e1c
12 changed files with 491 additions and 765 deletions

View file

@ -25,6 +25,7 @@ const MamiCompat = (current, path, handler) => {
// Backwards compat for scripts
// Keep in sync with <https://fii.moe/fp/13176> for as long as possible
MamiCompat(Umi, 'Server.SendMessage', text => Umi.Server.sendMessage(text));
MamiCompat(Umi, 'Protocol.SockChat.Protocol.Instance.SendMessage', text => Umi.Server.sendMessage(text));
MamiCompat(Umi, 'Protocol.SockLegacy.Protocol.Instance.SendMessage', text => Umi.Server.sendMessage(text));
MamiCompat(Umi, 'Parser.SockChatBBcode.EmbedStub', () => {}); // intentionally a no-op
MamiCompat(Umi, 'UI.View.SetText', text => console.log(`Umi.UI.View.SetText(text: ${text})`));

View file

@ -98,7 +98,6 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
settings.define('soundEnablePrivate', 'boolean', true);
settings.define('soundEnableForceLeave', 'boolean', true);
settings.define('minecraft', ['no', 'yes', 'old'], 'no');
settings.define('playSoundOnConnect', 'boolean', false);
settings.define('windowsLiveMessenger', 'boolean', false);
settings.define('seinfeld', 'boolean', false);
settings.define('flashTitle', 'boolean', true);
@ -149,7 +148,7 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
settings.touch('soundVolume');
settings.touch('soundPack');
soundCtx.packPlayer.playEvent('server');
soundCtx.library.play(soundCtx.pack.getEventSound('server'));
}
soundCtx.muted = !v;
@ -162,9 +161,8 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
return;
}
const player = soundCtx.packPlayer;
player.loadPack(packs.get(v));
if(!i) player.playEvent('server');
soundCtx.pack = packs.get(v);
if(!i) soundCtx.library.play(soundCtx.pack.getEventSound('server'));
});
settings.watch('soundVolume', v => {
@ -252,12 +250,18 @@ window.Umi = { UI: {}, Protocol: { SockChat: { Protocol: {} } } };
settings.watch('tmpDisableOldThemeSys', (v, n, i) => { if(!i) Umi.UI.View.AccentReload(); });
settings.watch('minecraft', (v, n, i) => {
if(v !== 'no') {
if(i && v === 'no')
return;
soundCtx.library.play((() => {
if(i)
soundCtx.library.play('minecraft:nether:enter');
else
Umi.Sound.Play('join');
}
return 'minecraft:nether:enter';
if(v === 'yes')
return 'minecraft:door:open';
if(v === 'old')
return 'minecraft:door:open-old';
return mami.sound.pack.getEventSound('join');
})());
});
settings.watch('enableNotifications', v => {

View file

@ -1,4 +1,4 @@
Umi.Message = function(msgId, time, user, text, channel, highlight, botInfo, isAction) {
Umi.Message = function(msgId, time, user, text, channel, highlight, botInfo, isAction, isLog) {
msgId = (msgId || '').toString();
time = time === null ? new Date() : new Date(parseInt(time || 0) * 1000);
user = user || {};
@ -6,18 +6,23 @@ Umi.Message = function(msgId, time, user, text, channel, highlight, botInfo, isA
channel = (channel || '').toString();
highlight = !!highlight;
isAction = !!isAction;
isLog = !!isLog;
hasSeen = isLog;
const msgIdInt = parseInt(msgId);
return {
getId: function() { return msgId; },
getIdInt: function() { return msgIdInt; },
getTime: function() { return time; },
getUser: function() { return user; },
getText: function() { return text; },
getChannel: function() { return channel; },
shouldHighlight: function() { return highlight; },
getBotInfo: function() { return botInfo; },
isAction: function() { return isAction; },
getId: () => msgId,
getIdInt: () => msgIdInt,
getTime: () => time,
getUser: () => user,
getText: () => text,
getChannel: () => channel,
shouldHighlight: () => highlight,
getBotInfo: () => botInfo,
isAction: () => isAction,
isLog: () => isLog,
hasSeen: () => hasSeen,
markSeen: () => hasSeen = true,
};
};

View file

@ -1,24 +1,16 @@
#include channels.js
#include server.js
#include ui/messages.jsx
Umi.Messages = (function() {
const msgs = new Map;
const onAdd = [],
onRemove = [],
onClear = [];
return {
OnAdd: onAdd,
OnRemove: onRemove,
OnClear: onClear,
Add: function(msg) {
const msgId = msg.getId();
if(!msgs.has(msgId)) {
msgs.set(msgId, msg);
for(const i in onAdd)
onAdd[i](msg);
Umi.UI.Messages.Add(msg);
if(window.CustomEvent)
window.dispatchEvent(new CustomEvent('umi:message_add', {
@ -30,16 +22,12 @@ Umi.Messages = (function() {
const msgId = msg.getId();
if(msgs.has(msgId)) {
msgs.delete(msgId);
for(const i in onRemove)
onRemove[i](msg);
Umi.UI.Messages.Remove(msg);
}
},
Clear: function() {
msgs.clear();
for(const i in onClear)
onClear[i]();
Umi.UI.Messages.RemoveAll();
},
All: function(channel, excludeNull) {
if(!channel)

View file

@ -8,7 +8,6 @@
#include users.js
#include parsing.js
#include servers.js
#include txtrigs.js
#include websock.js
#include ui/emotes.js
#include ui/markup.js
@ -16,11 +15,9 @@
#include ui/messages.jsx
#include ui/view.js
#include ui/loading-overlay.jsx
#include sound/umisound.js
Umi.Protocol.SockChat.Protocol = function(views, settings) {
const pub = {};
Umi.Protocol.SockChat.Protocol.Instance = pub;
const chatBot = new Umi.User('-1', 'Server');
@ -165,7 +162,6 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
getLoadingOverlay('unlink', 'Disconnected!', msg);
//Umi.Messages.Clear();
Umi.Users.Clear();
connectAttempts = 0;
@ -175,225 +171,6 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
}, 5000);
};
const parseBotMessage = function(parts) {
let text = '';
switch(parts[1]) {
case 'silence':
text = 'You have been silenced!';
break;
case 'unsil':
text = 'You are no longer silenced!';
break;
case 'silok':
text = '{0} is now silenced.'.replace('{0}', parts[2]);
break;
case 'usilok':
text = '{0} is no longer silenced.'.replace('{0}', parts[2]);
break;
case 'flood':
text = '{0} got kicked for flood protection.'.replace('{0}', parts[2]);
break;
case 'flwarn':
text = 'You are about to hit the flood limit! If you continue you will be kicked.';
break;
case 'unban':
text = '{0} is no longer banned.'.replace('{0}', parts[2]);
break;
case 'banlist':
const blentries = parts[2].split(', ');
for(const i in blentries)
blentries[i] = blentries[i].slice(92, -4);
text = 'Banned: {0}'.replace('{0}', blentries.join(', '));
break;
case 'who':
const wentries = parts[2].split(', ');
for(const i in wentries) {
const isSelf = wentries[i].includes(' style="font-weight: bold;"');
wentries[i] = wentries[i].slice(isSelf ? 102 : 75, -4);
if(isSelf) wentries[i] += ' (You)';
}
text = 'Online: {0}'.replace('{0}', wentries.join(', '));
break;
case 'whochan':
const wcentries = (parts[3] || '').split(', ');
for(const i in wcentries) {
const isSelf = wcentries[i].includes(' style="font-weight: bold;"');
wcentries[i] = wcentries[i].slice(isSelf ? 102 : 75, -4);
if(isSelf) wcentries[i] += ' (You)';
}
text = 'Online in {0}: {1}'.replace('{0}', parts[2]).replace('{1}', wcentries.join(', '));
break;
case 'silerr':
text = 'This user has already been silenced!';
break;
case 'usilerr':
text = "This user isn't silenced!";
break;
case 'silperr':
text = "You aren't allowed to silence this user!";
break;
case 'usilperr':
text = "You aren't allowed to remove the silence on this user!";
break;
case 'silself':
text = 'Why would you even try to silence yourself?';
break;
case 'delerr':
text = "You aren't allowed to delete this message!";
break;
case 'notban':
text = "{0} isn't banned!".replace('{0}', parts[2]);
break;
case 'whoerr':
text = '{0} does not exist!'.replace('{0}', parts[2]);
break;
case 'join':
text = '{0} joined.'.replace('{0}', parts[2]);
break;
case 'jchan':
text = '{0} joined the channel.'.replace('{0}', parts[2]);
break;
case 'leave':
text = '{0} left.'.replace('{0}', parts[2]);
break;
case 'timeout':
text = '{0} exploded.'.replace('{0}', parts[2]);
break;
case 'lchan':
text = '{0} left the channel.'.replace('{0}', parts[2]);
break;
case 'kick':
text = '{0} got kicked.'.replace('{0}', parts[2]);
break;
case 'nick':
text = '{0} changed their name to {1}.'.replace('{0}', parts[2]).replace('{1}', parts[3]);
break;
case 'crchan':
text = '{0} has been created.'.replace('{0}', parts[2]);
break;
case 'delchan':
text = '{0} has been deleted.'.replace('{0}', parts[2]);
break;
case 'cpwdchan':
text = 'Changed the channel password!';
break;
case 'cprivchan':
text = 'Change access level of the channel!';
break;
case 'ipaddr':
text = 'IP of {0}: {1}'.replace('{0}', parts[2]).replace('{1}', parts[3]);
break;
case 'cmdna':
text = "You aren't allowed to use '{0}'.".replace('{0}', parts[2]);
break;
case 'nocmd':
text = "The command '{0}' does not exist.".replace('{0}', parts[2]);
break;
case 'cmderr':
text = "You didn't format the command correctly!";
break;
case 'usernf':
text = "{0} isn't logged in to the chat right now!".replace('{0}', parts[2]);
break;
case 'kickna':
text = "You aren't allowed to kick {0}!".replace('{0}', parts[2]);
break;
case 'samechan':
text = 'You are already in {0}!'.replace('{0}', parts[2]);
break;
case 'ipchan':
case 'nochan':
text = "{0} doesn't exist!".replace('{0}', parts[2]);
break;
case 'nopwchan':
text = "{0} has a password! Use '/join {0} [password]'.".replace('{0}', parts[2]);
break;
case 'ipwchan':
text = "Wrong password! Couldn't join {0}.".replace('{0}', parts[2]);
break;
case 'inchan':
text = "Channel names can't start with @ or *!";
break;
case 'nischan':
text = '{0} already exists!'.replace('{0}', parts[2]);
break;
case 'ndchan':
text = "You aren't allowed to delete {0}!".replace('{0}', parts[2]);
break;
case 'namchan':
text = "You aren't allowed to edit {0}!".replace('{0}', parts[2]);
break;
case 'nameinuse':
text = '{0} is currently taken!'.replace('{0}', parts[2]);
break;
case 'rankerr':
text = "You can't set the access level of a channel higher than your own!";
break;
case 'reconnect':
text = 'Connection lost! Attempting to reconnect...';
break;
case 'generr':
text = 'Something happened.';
break;
case 'say':
default:
text = parts[2];
}
return text;
};
const unfuckText = function(text) {
const elem = document.createElement('div');
elem.innerHTML = text.replace(/ <br\/> /g, "\n");
@ -437,9 +214,6 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
}));
startKeepAlive();
if(settings.get('playSoundOnConnect'))
Umi.Sound.Play('join');
} else {
switch (data[2]) {
case 'joinfail':
@ -496,25 +270,13 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
}
Umi.Messages.Add(new Umi.Message(
data[6],
data[1],
chatBot,
'{0} joined.'.replace('{0}', juser.getName()),
channelName,
false,
{
type: 'join',
isError: false,
args: [juser.getName()],
target: juser,
}
data[6], data[1], chatBot, '', channelName, false,
{ type: 'join', isError: false, args: [juser.getName()], target: juser }
));
Umi.Sound.Play('join');
break;
case '2': // message
let text = data[3],
sound = 'incoming';
let text = data[3];
const muser = Umi.Users.Get(data[2]) || chatBot,
textParts = text.split("\f");
isPM = data[5][4] !== '0',
@ -526,16 +288,9 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
text = unfuckText(text);
if(isBot) {
sound = 'server';
botInfo.isError = textParts[0] !== '0';
botInfo.type = textParts[1];
botInfo.args = textParts.slice(2);
text = parseBotMessage(textParts);
if(botInfo.isError)
sound = 'error';
else if(textParts[1] === 'unban')
sound = 'unban';
}
if(isPM) {
@ -545,92 +300,27 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
text = tmpMsg.join(' ');
}
if(muser.getName() !== pmUserName)
sound = 'private';
if(Umi.Channels.Get(pmChannel) === null)
Umi.Channels.Add(new Umi.Channel(pmChannel, false, true, true));
Umi.UI.Menus.Attention('channels');
}
if(muser.getId() === userId)
sound = 'outgoing';
if(settings.get('playJokeSounds'))
try {
const trigger = mami.textTriggers.getTrigger(text);
if(trigger.isSoundType()) {
sound = '';
mami.sound.library.play(
trigger.getRandomSoundName(),
trigger.getVolume(),
trigger.getRate()
);
}
} catch(ex) {}
Umi.Messages.Add(new Umi.Message(
data[4],
data[1],
muser,
text,
data[4], data[1], muser, text,
isPM ? pmChannel : (data[6] || channelName),
false,
botInfo,
isAction
false, botInfo, isAction
));
if(!settings.get('onlySoundOnMention') && sound !== '')
Umi.Sound.Play(sound);
break;
case '3': // leave
const luser = Umi.Users.Get(data[1]);
let ltext = '',
lsound = null;
switch(data[3]) {
case 'flood':
ltext = '{0} got kicked for flood protection.';
lsound = 'flood';
break;
case 'timeout':
ltext = '{0} exploded.';
lsound = 'timeout';
break;
case 'kick':
ltext = '{0} got bludgeoned to death.';
lsound = 'kick';
break;
case 'leave':
default:
ltext = '{0} left.';
lsound = 'leave';
break;
}
Umi.Messages.Add(new Umi.Message(
data[5],
data[4],
chatBot,
ltext.replace('{0}', luser.getName()),
channelName,
false,
{
type: data[3],
isError: false,
args: [luser.getName()],
target: luser,
}
data[5], data[4], chatBot, '', channelName, false,
{ type: data[3], isError: false, args: [luser.getName()], target: luser }
));
Umi.Users.Remove(luser);
if(lsound !== null)
Umi.Sound.Play(lsound);
break;
case '4': // channel
@ -656,53 +346,25 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
case '5': // user move
switch(data[1]) {
case '0':
const umuser = new Umi.User(data[2], data[3], data[4], data[5]),
text = '{0} joined the channel.';
const umuser = new Umi.User(data[2], data[3], data[4], data[5]);
Umi.Users.Add(umuser);
Umi.Messages.Add(new Umi.Message(
data[6],
null,
chatBot,
text.replace('{0}', umuser.getName()),
channelName,
false,
{
type: 'jchan',
isError: false,
args: [
umuser.getName()
],
target: umuser,
}
data[6], null, chatBot, '', channelName, false,
{ type: 'jchan', isError: false, args: [ umuser.getName() ], target: umuser }
));
Umi.Sound.Play('join');
break;
case '1':
if(data[2] === userId)
return;
const mouser = Umi.Users.Get(+data[2]),
motext = '{0} has left the channel.';
const mouser = Umi.Users.Get(+data[2]);
Umi.Messages.Add(new Umi.Message(
data[3],
null,
chatBot,
motext.replace('{0}', mouser.getName()),
channelName,
false,
{
type: 'lchan',
isError: false,
args: [
mouser.getName()
],
target: mouser,
}
data[3], null, chatBot, '', channelName, false,
{ type: 'lchan', isError: false, args: [ mouser.getName() ], target: mouser }
));
Umi.Sound.Play('leave');
Umi.Users.Remove(mouser);
break;
@ -737,8 +399,6 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
cmtextParts = cmtext.split("\f"),
cmbotInfo = {};
cmtext = unfuckText(cmtext);
if(isNaN(cmid))
cmid = -Math.ceil(Math.random() * 10000000000);
@ -746,18 +406,13 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
cmbotInfo.isError = cmtextParts[0] !== '0';
cmbotInfo.type = cmtextParts[1];
cmbotInfo.args = cmtextParts.slice(2);
cmtext = parseBotMessage(cmtextParts);
}
cmtext = '';
} else
cmtext = unfuckText(cmtext);
Umi.Messages.Add(new Umi.Message(
cmid,
data[2],
cmuser,
cmtext,
channelName,
false,
cmbotInfo,
cmflags[1] !== '0' && cmflags[3] === '0',
cmid, data[2], cmuser, cmtext, channelName, false, cmbotInfo,
cmflags[1] !== '0' && cmflags[3] === '0', true
));
break;
@ -830,7 +485,6 @@ Umi.Protocol.SockChat.Protocol = function(views, settings) {
}
}
let icon, header;
if(isBan) {
icon = 'hammer';

View file

@ -8,7 +8,8 @@ const MamiSoundContext = function() {
const manager = new MamiSoundManager(audioCtx);
const library = new MamiSoundLibrary(manager);
const packs = new MamiSoundPacks;
const packPlayer = new MamiSoundPackPlayer(library);
let pack = new MamiSoundPack;
const pub = {};
@ -17,7 +18,16 @@ const MamiSoundContext = function() {
manager: { value: manager, enumerable: true },
library: { value: library, enumerable: true },
packs: { value: packs, enumerable: true },
packPlayer: { value: packPlayer, enumerable: true },
pack: {
get: () => pack,
set: value => {
if(typeof value !== 'object' || typeof value.getEventSound !== 'function')
throw 'value is not a valid soundpack';
pack = value;
},
enumerable: true,
},
ready: { get: audioCtx.isReady, enumerable: true },
volume: { get: audioCtx.getVolume, set: audioCtx.setVolume, enumerable: true },

View file

@ -78,6 +78,9 @@ const MamiSoundLibrary = function(soundMgr) {
loadBuffer: async name => await soundMgr.loadBuffer(getSoundSources(name)),
loadSource: loadSoundSource,
play: async (name, volume, rate) => {
if(typeof name !== 'string')
return;
const source = await loadSoundSource(name);
if(typeof volume === 'number')

View file

@ -6,6 +6,8 @@ const MamiSoundPack = function(name, isReadOnly, title, events) {
title = (title || '').toString();
events = events || new Map;
const hasEventSound = eventName => events.has(eventName);
return {
getName: () => name,
isReadOnly: () => isReadOnly,
@ -16,11 +18,24 @@ const MamiSoundPack = function(name, isReadOnly, title, events) {
title = (newTitle || '').toString();
},
getEventNames: () => Array.from(events.keys()),
hasEventSound: eventName => events.has(eventName),
getEventSound: eventName => {
if(!events.has(eventName))
throw 'event not registered';
return events.get(eventName);
hasEventSound: hasEventSound,
getEventSound: eventNames => {
let event;
if(Array.isArray(eventNames)) {
for(const name of eventNames)
if(hasEventSound(name)) {
event = name;
break;
}
} else if(hasEventSound(eventNames)) {
event = eventNames;
}
if(event === undefined)
return;
return events.get(event);
},
setEventSound: (eventName, soundName) => {
events.set(
@ -40,47 +55,6 @@ const MamiSoundPack = function(name, isReadOnly, title, events) {
};
};
const MamiSoundPackPlayer = function(sndLibrary) {
let pack;
return {
loadPack: packInfo => {
if(typeof packInfo !== 'object' || typeof packInfo.getEventSound !== 'function')
throw 'pack is not a valid soundpack';
pack = packInfo;
},
unloadPack: () => {
pack = undefined;
buffers.clear();
},
hasPack: () => pack !== undefined,
hasEvent: name => pack !== undefined && pack.hasEventSound(event),
playEvent: async event => {
if(pack === undefined)
return;
if(Array.isArray(event)) {
const names = event;
event = undefined;
for(const name of names) {
if(pack.hasEventSound(name)) {
event = name;
break;
}
}
if(event === undefined)
return;
} else if(!pack.hasEventSound(event))
return;
await sndLibrary.play(pack.getEventSound(event));
},
};
};
const MamiSoundPacks = function() {
const packs = new Map;

View file

@ -1,124 +1,109 @@
#include sound/seinfeld.js
Umi.Sound = (() => {
return {
Play: sound => {
if(!sound || sound === 'none')
const getLibrarySound = event => {
if(!event || event === 'none')
return;
const minecraft = mami.settings.get('minecraft');
if(event === 'join') {
if(!mami.settings.get('soundEnableJoin'))
return;
switch(sound) {
case 'join':
if(!mami.settings.get('soundEnableJoin'))
return;
if(mami.settings.get('seinfeld'))
return Seinfeld.getRandom();
if(mami.settings.get('seinfeld')) {
mami.sound.library.play(Seinfeld.getRandom());
break;
}
if(minecraft === 'yes')
return 'minecraft:door:open';
switch(mami.settings.get('minecraft')) {
case 'yes':
mami.sound.library.play('minecraft:door:open');
break;
if(minecraft === 'old')
return 'minecraft:door:open-old';
case 'old':
mami.sound.library.play('minecraft:door:open-old');
break;
return mami.sound.pack.getEventSound('join');
}
default:
mami.sound.packPlayer.playEvent('join');
break;
}
break;
if(event === 'leave') {
if(!mami.settings.get('soundEnableLeave'))
return;
case 'leave':
if(!mami.settings.get('soundEnableLeave'))
return;
if(minecraft === 'yes')
return 'minecraft:door:close';
switch(mami.settings.get('minecraft')) {
case 'yes':
mami.sound.library.play('minecraft:door:close');
break;
if(minecraft === 'old')
return 'minecraft:door:close-old';
case 'old':
mami.sound.library.play('minecraft:door:close-old');
break;
return mami.sound.pack.getEventSound('leave');
}
default:
mami.sound.packPlayer.playEvent('leave');
break;
}
break;
if(event === 'error') {
if(!mami.settings.get('soundEnableError'))
return;
case 'error':
if(!mami.settings.get('soundEnableError'))
return;
return mami.sound.pack.getEventSound('error');
}
mami.sound.packPlayer.playEvent('error');
break;
if(event === 'server') {
if(!mami.settings.get('soundEnableServer'))
return;
case 'server':
if(!mami.settings.get('soundEnableServer'))
return;
return mami.sound.pack.getEventSound('server');
}
mami.sound.packPlayer.playEvent('server');
break;
if(event === 'unban') {
if(!mami.settings.get('soundEnableServer'))
return;
case 'unban':
if(!mami.settings.get('soundEnableServer'))
return;
return mami.sound.pack.getEventSound(['unban', 'server']);
}
mami.sound.packPlayer.playEvent(['unban', 'server']);
break;
if(event === 'incoming') {
if(!mami.settings.get('soundEnableIncoming'))
return;
case 'incoming':
if(!mami.settings.get('soundEnableIncoming'))
return;
if(mami.settings.get('windowsLiveMessenger')) {
mami.sound.library.play('msn:incoming');
} else {
mami.sound.packPlayer.playEvent('incoming');
}
break;
if(mami.settings.get('windowsLiveMessenger'))
return 'msn:incoming';
case 'outgoing':
if(!mami.settings.get('soundEnableOutgoing'))
return;
return mami.sound.pack.getEventSound('incoming');
}
mami.sound.packPlayer.playEvent('outgoing');
break;
if(event === 'outgoing') {
if(!mami.settings.get('soundEnableOutgoing'))
return;
case 'private':
case 'incoming-priv':
if(!mami.settings.get('soundEnablePrivate'))
return;
return mami.sound.pack.getEventSound('outgoing');
}
mami.sound.packPlayer.playEvent(['incoming-priv', 'incoming']);
break;
if(event === 'private' || event === 'incoming-priv') {
if(!mami.settings.get('soundEnablePrivate'))
return;
case 'flood':
if(!mami.settings.get('soundEnableForceLeave'))
return;
return mami.sound.pack.getEventSound(['incoming-priv', 'incoming']);
}
mami.sound.packPlayer.playEvent(['flood', 'kick', 'leave']);
break;
if(event === 'flood') {
if(!mami.settings.get('soundEnableForceLeave'))
return;
case 'timeout':
if(!mami.settings.get('soundEnableForceLeave'))
return;
return mami.sound.pack.getEventSound(['flood', 'kick', 'leave']);
}
mami.sound.packPlayer.playEvent(['timeout', 'leave']);
break;
if(event === 'timeout') {
if(!mami.settings.get('soundEnableForceLeave'))
return;
case 'kick':
case 'forceLeave':
if(!mami.settings.get('soundEnableForceLeave'))
return;
return mami.sound.pack.getEventSound(['timeout', 'leave']);
}
mami.sound.packPlayer.playEvent(['kick', 'leave']);
break;
}
},
if(event === 'kick' || event === 'forceLeave') {
if(!mami.settings.get('soundEnableForceLeave'))
return;
return mami.sound.pack.getEventSound(['kick', 'leave']);
}
};
return {
Convert: getLibrarySound,
};
})();

View file

@ -1,7 +1,6 @@
#include channels.js
#include common.js
#include server.js
#include title.js
#include user.js
#include utility.js
#include sound/umisound.js
@ -13,76 +12,6 @@
Umi.UI.Hooks = (function() {
return {
AddHooks: function() {
const title = new MamiWindowTitle({
getName: () => futami.get('title'),
});
window.addEventListener('focus', function() {
title.clear();
});
Umi.Messages.OnRemove.push(function(msg) {
Umi.UI.Messages.Remove(msg);
});
Umi.Messages.OnClear.push(function() {
Umi.UI.Messages.RemoveAll();
});
Umi.Messages.OnAdd.push(function(msg) {
Umi.UI.Channels.Unread(msg.getChannel());
Umi.UI.Messages.Add(msg);
if(!document.hidden)
return;
if(mami.settings.get('flashTitle')) {
let titleText = ' ' + msg.getUser().getName(),
channel = Umi.Channels.Current() || null;
if(msg.getUser().isBot() && mami.settings.get('showServerMsgInTitle'))
titleText = ' ' + msg.getText();
if(channel !== null && channel.getName() !== msg.getChannel())
titleText += ' @ ' + channel.getName();
title.strobe([
`[ @] ${titleText}`,
`[@ ] ${titleText}`,
]);
}
if(mami.settings.get('enableNotifications') && Umi.User.getCurrentUser() !== null) {
const triggers = (mami.settings.get('notificationTriggers') || '').toLowerCase().split(' '),
options = {};
triggers.push((Umi.User.getCurrentUser() || { getName: function() { return ''; } }).getName().toLowerCase());
options.body = 'Click here to see what they said.';
if(mami.settings.get('notificationShowMessage'))
options.body += "\n" + msg.getText();
const avatarUrl = futami.get('avatar');
if(avatarUrl.length > 0)
options.icon = avatarUrl.replace('{user:id}', msg.getUser().getId()).replace('{resolution}', '80').replace('{user:avatar_change}', msg.getUser().getAvatarTime().toString());
for(const trigger of triggers) {
const message = ' ' + msg.getText() + ' ';
if(trigger.trim() === '')
continue;
if(message.toLowerCase().indexOf(' ' + trigger + ' ') >= 0) {
new Notification('{0} mentioned you!'.replace('{0}', msg.getUser().getName()), options);
if(mami.settings.get('onlySoundOnMention'))
Umi.Sound.Play('incoming');
break;
}
}
}
});
Umi.Users.OnAdd.push(function(user) {
Umi.UI.Users.Add(user);
});

View file

@ -1,48 +1,155 @@
#include channels.js
#include common.js
#include parsing.js
#include title.js
#include txtrigs.js
#include url.js
#include users.js
#include utility.js
#include weeb.js
#include sound/umisound.js
#include ui/emotes.js
Umi.UI.Messages = (function() {
let lastMsgUser = null,
let forceUserInfo = false,
lastMsgUser = null,
lastMsgChannel = null,
lastWasTiny = null;
const title = new MamiWindowTitle({
getName: () => futami.get('title'),
});
window.addEventListener('focus', function() {
title.clear();
});
const botMsgs = {
'say': { text: '%0' },
'generr': { text: 'Something unexpected happened.' },
'flwarn': { text: 'You are about to hit the flood limit! If you continue you will be kicked.' },
'unban': { text: '%0 is no longer banned.', sound: 'unban' },
'delerr': { text: 'You are not allowed to delete this message.' },
'notban': { text: '%0 is not banned.' },
'whoerr': { text: '%0 does not exist.' },
'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.' },
'crchan': { text: 'Channel %0 has been created.' },
'delchan': { text: 'Channel %0 has been deleted.' },
'cpwdchan': { text: 'Channel password has been changed.' },
'cprivchan': { text: 'Channel access level has been changed.' },
'ipaddr': { text: 'IP address of %0 is %1.' },
'cmdna': { text: 'You are not allowed to use %0.' },
'nocmd': { text: 'Command %0 does not exist.' },
'cmderr': { text: 'You did not use that command correctly.' },
'usernf': { text: '%0 is not logged in right now!' },
'kickna': { text: 'You are not allowed to kick %0.' },
'samechan': { text: 'You are already in channel %0.' },
'ipchan': { text: 'You are not allowed to join channel %0.' },
'nochan': { text: 'Channel %0 does not exist.' },
'nopwchan': { text: 'Channel %0 requires a password. Use /join %0 <password>' },
'ipwchan': { text: 'Wrong password for channel %0.' },
'inchan': { text: 'Channel name contains invalid characters.' },
'nischan': { text: 'A channel with the name %0 already exists.' },
'ndchan': { text: 'You are not allowed to deleted channel %0.' },
'namchan': { text: 'You are not allowed to edit channel %0.' },
'nameinuse': { text: 'Someone else is already using the name %0.' },
'rankerr': { text: 'You cannot set the access level of a channel higher than that of your own.' },
'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) {
if(msg.getChannel() !== null
&& Umi.Channels.Current() !== null
&& msg.getChannel() !== Umi.Channels.Current().getName())
return;
const currentChannel = Umi.Channels.Current();
const channelName = msg.getChannel();
const sender = msg.getUser();
const isOutgoing = Umi.User.isCurrentUser(sender);
const hasSeen = msg.hasSeen();
const displayMessage = currentChannel === null || channelName === null || channelName === currentChannel.getName();
const notifyPM = !displayMessage && !isOutgoing && !hasSeen && channelName.startsWith('@');
let isTiny = false,
skipTextParsing = false,
msgText = msg.getText();
msgText = msg.getText(),
msgTextLong = msgText;
let eBase = null,
eAvatar = null,
eText = null,
eMeta = null,
eUser = null;
let eBase, eAvatar, eText, eMeta, eUser;
const sender = msg.getUser();
let avatarUser = sender,
avatarSize = '80';
const userClass = 'message--user-' + sender.getId();
let soundName = isOutgoing ? 'outgoing' : 'incoming',
soundVolume, soundRate, soundIsLegacy = true;
const userClass = `message--user-${sender.getId()}`;
const classes = ['message', userClass];
const styles = {};
const avatarClasses = ['message__avatar'];
const msgIsFirst = lastMsgUser !== sender.getId() || lastMsgChannel !== msg.getChannel();
if(msgIsFirst)
const msgIsFirst = forceUserInfo || lastMsgUser !== sender.getId() || lastMsgChannel !== msg.getChannel();
if(msgIsFirst) {
forceUserInfo = false;
classes.push('message--first');
}
if(msg.shouldHighlight())
classes.push('message--highlight');
@ -60,164 +167,231 @@ Umi.UI.Messages = (function() {
+ ':' + msgDateTimeObj.getMinutes().toString().padStart(2, '0')
+ ':' + msgDateTimeObj.getSeconds().toString().padStart(2, '0');
if(sender.isBot() && mami.settings.get('fancyInfo')) {
if(sender.isBot()) {
const botInfo = msg.getBotInfo();
soundName = botInfo.isError ? 'error' : 'server';
if(botInfo) {
if(botInfo.type === 'join' || botInfo.type === 'jchan'
|| botInfo.type === 'leave' || botInfo.type === 'lchan'
|| botInfo.type === 'kick' || botInfo.type === 'flood'
|| botInfo.type === 'timeout') {
const target = botInfo.target || Umi.Users.FindExact(botInfo.args[0]);
if(botMsgs.hasOwnProperty(botInfo.type)) {
const bmInfo = botMsgs[botInfo.type];
let bArgs = botInfo.args;
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' && mami.settings.get('fancyInfo')) {
const target = botInfo.target || Umi.Users.FindExact(bArgs[0]);
if(target) {
actionSuccess = true;
isTiny = true;
skipTextParsing = true;
avatarUser = target;
msgText = 'did something';
$ari(classes, userClass);
switch(botInfo.type) {
case 'join':
msgText = 'has joined';
break;
case 'leave':
msgText = 'has disconnected';
avatarClasses.push('avatar-filter-greyscale');
break;
case 'jchan':
msgText = 'has joined the channel';
break;
case 'lchan':
msgText = 'has left the channel';
avatarClasses.push('avatar-filter-greyscale');
break;
case 'kick':
msgText = 'got bludgeoned to death';
avatarClasses.push('avatar-filter-invert');
break;
case 'flood':
msgText = 'got kicked for flood protection';
avatarClasses.push('avatar-filter-invert');
break;
case 'timeout':
msgText = 'exploded';
avatarClasses.push('avatar-filter-greyscale');
break;
msgText = bmInfo.action;
if(typeof bmInfo.avatar === 'string')
avatarClasses.push(`avatar-filter-${bmInfo.avatar}`);
}
}
msgTextLong = formatTemplate(bmInfo.text, bArgs);
if(!actionSuccess)
msgText = msgTextLong;
} else
msgText = msgTextLong = `!!! Received unsupported message type: ${botInfo.type} !!!`;
} else {
if(mami.settings.get('playJokeSounds'))
try {
const trigger = mami.textTriggers.getTrigger(msgText);
if(trigger.isSoundType()) {
soundIsLegacy = false;
soundName = trigger.getRandomSoundName();
soundVolume = trigger.getVolume();
soundRate = trigger.getRate();
}
} catch(ex) {}
}
let avatarUrl = futami.get('avatar');
if(typeof avatarUrl !== 'string' || avatarUrl.length < 1)
avatarUrl = undefined;
else
avatarUrl = avatarUrl.replace('{user:id}', avatarUser.getId())
.replace('{resolution}', avatarSize)
.replace('{user:avatar_change}', avatarUser.getAvatarTime().toString());
if(displayMessage) {
if(isTiny) {
if(!msgIsFirst) // small messages must always be "first"
classes.push('message--first');
classes.push('message-tiny');
avatarSize = '40';
if(msgText.indexOf("'") !== 0 || (msgText.match(/\'/g).length % 2) === 0)
msgText = "\xA0" + msgText;
eBase = <div id={`message-${msg.getId()}`} class={classes} style={styles}>
{eAvatar = <div class={avatarClasses}/>}
<div class="message__container">
{eMeta = <div class="message__meta">
{eUser = <div class="message__user" style={{ color: avatarUser.getColour() }}>{avatarUser.getName()}</div>}
{eText = <div class="message-tiny-text"/>}
<div class="message__time">{msgDateTime}</div>
</div>}
</div>
</div>;
} else {
eBase = <div id={`message-${msg.getId()}`} class={classes} style={styles}>
{eAvatar = <div class={avatarClasses}/>}
<div class="message__container">
{eMeta = <div class="message__meta">
{eUser = <div class="message__user" style={{ color: avatarUser.getColour() }}>{avatarUser.getName()}</div>}
<div class="message__time">{msgDateTime}</div>
</div>}
{eText = <div class="message__text"/>}
</div>
</div>;
}
eText.innerText = msgText;
if(!skipTextParsing) {
eText = Umi.UI.Emoticons.Parse(eText, msg);
eText = Umi.Parsing.Parse(eText, msg);
const urls = [];
if(mami.settings.get('autoParseUrls')) {
const textSplit = eText.innerText.split(' ');
for(const textPart of textSplit) {
const uri = Umi.URI.Parse(textPart);
if(uri !== null && uri.Slashes !== null) {
urls.push(textPart);
const anchorElem = <a class="markup__link" href={textPart} target="_blank" rel="nofollow noreferrer noopener">{textPart}</a>;
eText.innerHTML = eText.innerHTML.replace(textPart.replace(/&/g, '&amp;'), anchorElem.outerHTML);
}
}
}
}
}
if(isTiny) {
if(!msgIsFirst) // small messages must always be "first"
classes.push('message--first');
classes.push('message-tiny');
if(mami.settings.get('weeaboo')) {
eText.appendChild($t(Weeaboo.getTextSuffix(sender)));
avatarSize = '40';
if(msgText.indexOf("'") !== 0 || (msgText.match(/\'/g).length % 2) === 0)
msgText = "\xA0" + msgText;
eBase = <div id={`message-${msg.getId()}`} class={classes} style={styles}>
{eAvatar = <div class={avatarClasses}/>}
<div class="message__container">
{eMeta = <div class="message__meta">
{eUser = <div class="message__user" style={{ color: avatarUser.getColour() }}>{avatarUser.getName()}</div>}
{eText = <div class="message-tiny-text"/>}
<div class="message__time">{msgDateTime}</div>
</div>}
</div>
</div>;
} else {
eBase = <div id={`message-${msg.getId()}`} class={classes} style={styles}>
{eAvatar = <div class={avatarClasses}/>}
<div class="message__container">
{eMeta = <div class="message__meta">
{eUser = <div class="message__user" style={{ color: avatarUser.getColour() }}>{avatarUser.getName()}</div>}
<div class="message__time">{msgDateTime}</div>
</div>}
{eText = <div class="message__text"/>}
</div>
</div>;
}
eText.innerText = msgText;
if(!skipTextParsing) {
eText = Umi.UI.Emoticons.Parse(eText, msg);
eText = Umi.Parsing.Parse(eText, msg);
const urls = [];
if(mami.settings.get('autoParseUrls')) {
const textSplit = eText.innerText.split(' ');
for(const textPart of textSplit) {
const uri = Umi.URI.Parse(textPart);
if(uri !== null && uri.Slashes !== null) {
urls.push(textPart);
const linkElement = $e({
tag: 'a',
attrs: {
className: 'markup__link',
href: textPart,
target: '_blank',
rel: 'nofollow noreferrer noopener',
},
child: textPart,
});
eText.innerHTML = eText.innerHTML.replace(textPart.replace(/&/g, '&amp;'), linkElement.outerHTML);
const kaomoji = Weeaboo.getRandomKaomoji(true, msg);
if(kaomoji) {
eText.appendChild($t(' '));
eText.appendChild($t(kaomoji));
}
}
if(mami.settings.get('weeaboo'))
eUser.appendChild($t(Weeaboo.getNameSuffix(sender)));
}
if(mami.settings.get('weeaboo')) {
eText.appendChild($t(Weeaboo.getTextSuffix(sender)));
if(isTiny !== lastWasTiny) {
if(!msgIsFirst)
eBase.classList.add('message--first');
eBase.classList.add(isTiny ? 'message-tiny-fix' : 'message-big-fix');
}
lastWasTiny = isTiny;
const kaomoji = Weeaboo.getRandomKaomoji(true, msg);
if(kaomoji) {
eText.appendChild($t(' '));
eText.appendChild($t(kaomoji));
if(avatarUrl === undefined)
eAvatar.classList.add('message__avatar--disabled');
else
eAvatar.style.backgroundImage = `url(${avatarUrl})`;
const msgsList = $i('umi-messages');
msgsList.appendChild(eBase);
lastMsgUser = sender.getId();
lastMsgChannel = msg.getChannel();
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'))
msgsList.scrollTop = msgsList.scrollHeight;
}
let isMentioned = false;
const mentionTriggers = (mami.settings.get('notificationTriggers') || '').toLowerCase().split(' ');
const currentUser = Umi.User.getCurrentUser();
if(typeof currentUser === 'object' && typeof currentUser.getName === 'function')
mentionTriggers.push(currentUser.getName().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 = sender.isBot() && mami.settings.get('showServerMsgInTitle')
? ` ${msgTextLong}`
: ` ${sender.getName()}`;
// oops this won't work lol, we're filtering at the top
if(currentChannel !== null && currentChannel.getName() !== channelName)
titleText += ` @ ${channelName}`;
title.strobe([
`[ @] ${titleText}`,
`[@ ] ${titleText}`,
]);
}
if(!hasSeen) {
Umi.UI.Channels.Unread(channelName);
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 !== undefined)
options.icon = avatarUrl;
const notif = new Notification(`${sender.getName()} mentioned you!`, options);
notif.addEventListener('click', () => {
window.focus();
});
document.addEventListener('visibilitychange', () => {
if(document.visibilityState === 'visible')
notif.close();
});
}
}
if(mami.settings.get('weeaboo'))
eUser.appendChild($t(Weeaboo.getNameSuffix(sender)));
}
if(isTiny !== lastWasTiny) {
if(!msgIsFirst)
eBase.classList.add('message--first');
eBase.classList.add(isTiny ? 'message-tiny-fix' : 'message-big-fix');
if(soundName !== undefined && !hasSeen) {
if(soundIsLegacy)
soundName = Umi.Sound.Convert(soundName);
mami.sound.library.play(soundName, soundVolume, soundRate);
}
lastWasTiny = isTiny;
const avatarUrl = futami.get('avatar');
if (avatarUrl !== null && avatarUrl.length > 1) {
eAvatar.style.backgroundImage = 'url({0})'.replace('{0}', avatarUrl.replace('{user:id}', avatarUser.getId())
.replace('{resolution}', avatarSize).replace('{user:avatar_change}', avatarUser.getAvatarTime().toString()));
} else eAvatar.classList.add('message__avatar--disabled');
const msgsList = $i('umi-messages');
msgsList.appendChild(eBase);
lastMsgUser = sender.getId();
lastMsgChannel = msg.getChannel();
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'))
msgsList.scrollTop = msgsList.scrollHeight;
if(window.CustomEvent)
window.dispatchEvent(new CustomEvent('umi:ui:message_add', {
@ -226,14 +400,18 @@ Umi.UI.Messages = (function() {
message: msg,
},
}));
msg.markSeen();
},
Remove: function(msg) {
forceUserInfo = true;
lastMsgUser = null;
lastMsgChannel = null;
lastWasTiny = null;
$ri('message-' + msg.getId());
$ri(`message-${msg.getId()}`);
},
RemoveAll: function() {
forceUserInfo = true;
lastMsgUser = null;
lastMsgChannel = null;
lastWasTiny = null;

View file

@ -209,11 +209,6 @@ Umi.UI.Settings = (function() {
};
},
},
{
name: 'playSoundOnConnect',
title: 'Play join sound on connect',
type: 'checkbox',
},
{
name: 'windowsLiveMessenger',
title: 'Windows Live Messenger',