diff --git a/src/mami.js/compat.js b/src/mami.js/compat.js index dbdef15..bb1dce5 100644 --- a/src/mami.js/compat.js +++ b/src/mami.js/compat.js @@ -25,6 +25,7 @@ const MamiCompat = (current, path, handler) => { // Backwards compat for scripts // Keep in sync with 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})`)); diff --git a/src/mami.js/main.js b/src/mami.js/main.js index 4b57db3..b1af818 100644 --- a/src/mami.js/main.js +++ b/src/mami.js/main.js @@ -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 => { diff --git a/src/mami.js/message.js b/src/mami.js/message.js index 51ce1a4..08d9140 100644 --- a/src/mami.js/message.js +++ b/src/mami.js/message.js @@ -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, }; }; diff --git a/src/mami.js/messages.js b/src/mami.js/messages.js index 21273a2..6fb5fbb 100644 --- a/src/mami.js/messages.js +++ b/src/mami.js/messages.js @@ -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) diff --git a/src/mami.js/sockchat_old.js b/src/mami.js/sockchat_old.js index 8227ab1..0f73d44 100644 --- a/src/mami.js/sockchat_old.js +++ b/src/mami.js/sockchat_old.js @@ -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(/ /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'; diff --git a/src/mami.js/sound/context.js b/src/mami.js/sound/context.js index 50f8c89..1139ac3 100644 --- a/src/mami.js/sound/context.js +++ b/src/mami.js/sound/context.js @@ -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 }, diff --git a/src/mami.js/sound/sndlibrary.js b/src/mami.js/sound/sndlibrary.js index e90210e..1ca5cc6 100644 --- a/src/mami.js/sound/sndlibrary.js +++ b/src/mami.js/sound/sndlibrary.js @@ -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') diff --git a/src/mami.js/sound/sndpacks.js b/src/mami.js/sound/sndpacks.js index bf9f7a9..1e22a96 100644 --- a/src/mami.js/sound/sndpacks.js +++ b/src/mami.js/sound/sndpacks.js @@ -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; diff --git a/src/mami.js/sound/umisound.js b/src/mami.js/sound/umisound.js index 04e76a7..daf712d 100644 --- a/src/mami.js/sound/umisound.js +++ b/src/mami.js/sound/umisound.js @@ -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, }; })(); diff --git a/src/mami.js/ui/hooks.js b/src/mami.js/ui/hooks.js index af324f9..12e0330 100644 --- a/src/mami.js/ui/hooks.js +++ b/src/mami.js/ui/hooks.js @@ -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); }); diff --git a/src/mami.js/ui/messages.jsx b/src/mami.js/ui/messages.jsx index 2515c7a..9c57c78 100644 --- a/src/mami.js/ui/messages.jsx +++ b/src/mami.js/ui/messages.jsx @@ -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 ' }, + '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 =
+ {eAvatar =
} +
+ {eMeta =
+ {eUser =
{avatarUser.getName()}
} + {eText =
} +
{msgDateTime}
+
} +
+
; + } else { + eBase =
+ {eAvatar =
} +
+ {eMeta =
+ {eUser =
{avatarUser.getName()}
} +
{msgDateTime}
+
} + {eText =
} +
+
; + } + + 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 = {textPart}; + eText.innerHTML = eText.innerHTML.replace(textPart.replace(/&/g, '&'), 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 =
- {eAvatar =
} -
- {eMeta =
- {eUser =
{avatarUser.getName()}
} - {eText =
} -
{msgDateTime}
-
} -
-
; - } else { - eBase =
- {eAvatar =
} -
- {eMeta =
- {eUser =
{avatarUser.getName()}
} -
{msgDateTime}
-
} - {eText =
} -
-
; - } - - 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, '&'), 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; diff --git a/src/mami.js/ui/settings.jsx b/src/mami.js/ui/settings.jsx index 6fcd269..6fdfe2f 100644 --- a/src/mami.js/ui/settings.jsx +++ b/src/mami.js/ui/settings.jsx @@ -209,11 +209,6 @@ Umi.UI.Settings = (function() { }; }, }, - { - name: 'playSoundOnConnect', - title: 'Play join sound on connect', - type: 'checkbox', - }, { name: 'windowsLiveMessenger', title: 'Windows Live Messenger',