mami/src/mami.js/sockchat_old.js

969 lines
32 KiB
JavaScript

#include channel.js
#include channels.js
#include common.js
#include message.js
#include messages.js
#include mszauth.js
#include user.js
#include users.js
#include parsing.js
#include servers.js
#include settings.js
#include txtrigs.js
#include websock.js
#include ui/emotes.js
#include ui/markup.js
#include ui/menus.js
#include ui/messages.jsx
#include ui/view.js
#include ui/loading-overlay.jsx
#include sound/umisound.js
if(!Umi) window.Umi = {};
if(!Umi.Protocol) Umi.Protocol = {};
if(!Umi.Protocol.SockChat) Umi.Protocol.SockChat = {};
if(!Umi.Protocol.SockChat.Protocol) Umi.Protocol.SockChat.Protocol = {};
Umi.Protocol.SockChat.Protocol = function(views) {
const pub = {};
Umi.Protocol.SockChat.Protocol.Instance = pub;
const chatBot = new Umi.User('-1', 'Server');
let noReconnect = false,
connectAttempts = 0,
wasKicked = false,
isRestarting = false;
let userId = null,
channelName = null,
pmUserName = null;
let sock = null;
const send = function(opcode, data) {
if(!sock) return;
let msg = opcode;
if(data) msg += "\t" + data.join("\t");
console.log(msg);
sock.send(msg);
};
const sendPing = function() {
if(userId === null)
return;
lastPing = Date.now();
send('0', [userId]);
};
const sendAuth = function(args) {
if(userId !== null)
return;
send('1', args);
};
const sendMessage = function(text) {
if(userId === null)
return;
if(text.substring(0, 1) !== '/' && pmUserName !== null)
text = '/msg ' + pmUserName + ' ' + text;
send('2', [userId, text]);
};
const switchChannel = function(name) {
if(channelName === name)
return;
channelName = name;
sendMessage('/join ' + name);
};
const startKeepAlive = function() {
if(!sock) return;
sock.sendInterval("0\t" + userId, futami.get('ping') * 1000);
};
const stopKeepAlive = function() {
if(!sock) return;
sock.clearIntervals();
};
const getLoadingOverlay = async (icon, header, message) => {
const currentView = views.current();
if('setIcon' in currentView) {
currentView.setIcon(icon);
currentView.setHeader(header);
currentView.setMessage(message);
return currentView;
}
const loading = new Umi.UI.LoadingOverlay(icon, header, message);
await views.push(loading);
return loading;
};
const playBannedSfx = function() {
if(!mami.hasSound())
return;
const urls = {
mp3: '//static.flash.moe/sounds/touhou-death.mp3',
ogg: '//static.flash.moe/sounds/touhou-death.ogg'
};
mami.getSound().load('banSFX', urls, function(success, buffer) {
if(!success) {
console.log('Failed to load kick/ban SFX: ' + buffer);
return;
}
buffer.createSource().play();
});
};
const playBannedBgm = function(preload) {
if(!mami.hasSound())
return;
const urls = {
opus: '//static.flash.moe/sounds/players-score.opus',
caf: '//static.flash.moe/sounds/players-score.caf'
};
mami.getSound().load('banBGM', urls, function(success, buffer) {
if(!success) {
console.log('Failed to load kick/ban SFX: ' + buffer);
return;
}
if(!preload) {
const source = buffer.createSource();
source.setLoopStart(10.512);
source.setLoopEnd(38.074);
source.setLoop();
source.play();
}
});
};
const onOpen = function(ev) {
if(Umi.Settings.get('dumpPackets'))
console.log(ev);
wasKicked = false;
isRestarting = false;
getLoadingOverlay('spinner', 'Loading...', 'Authenticating...');
const authInfo = MamiMisuzuAuth.getInfo();
sendAuth([authInfo.method, authInfo.token]);
};
const closeReasons = {
'_1000': 'The connection has been ended.',
'_1001': 'Something went wrong on the server side.',
'_1002': 'Your client sent broken data to the server.',
'_1003': 'Your client sent data to the server that it doesn\'t understand.',
'_1005': 'No additional information was provided.',
'_1006': 'You lost connection unexpectedly!',
'_1007': 'Your client sent broken data to the server.',
'_1008': 'Your client did something the server did not agree with.',
'_1009': 'Your client sent too much data to the server at once.',
'_1011': 'Something went wrong on the server side.',
'_1012': 'The server is restarting, reconnecting soon...',
'_1013': 'You cannot connect to the server right now, try again later.',
'_1015': 'Your client and the server could not establish a secure connection.',
};
const onClose = function(ev) {
if(Umi.Settings.get('dumpPackets'))
console.log(ev);
userId = null;
channelName = null;
pmUserName = null;
stopKeepAlive();
if(wasKicked)
return;
let code = ev.code;
if(isRestarting && code === 1006) {
code = 1012;
} else if(code === 1012)
isRestarting = true;
const msg = closeReasons['_' + code.toString()]
|| ('Something caused an unexpected connection loss, the error code was: ' + ev.code.toString() + '.');
getLoadingOverlay('unlink', 'Disconnected!', msg);
//Umi.Messages.Clear();
Umi.Users.Clear();
connectAttempts = 0;
setTimeout(function() {
beginConnecting();
}, 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");
text = elem.innerText;
return text;
};
const onMessage = function(ev) {
const data = ev.data.split("\t");
if(Umi.Settings.get('dumpPackets'))
console.log(data);
switch(data[0]) {
case '0': // ping
// nothing to do
break;
case '1': // join
if(userId === null) {
if(data[1] == 'y') {
userId = data[2];
channelName = data[6];
views.pop(ctx => MamiAnimate({
async: true,
duration: 120,
easing: 'easeInOutSine',
start: () => {
ctx.toElem.style.zIndex = '100';
ctx.fromElem.style.pointerEvents = 'none';
ctx.fromElem.style.zIndex = '200';
},
update: t => {
ctx.fromElem.style.transform = `scale(${1 + (.25 * t)})`;
ctx.fromElem.style.opacity = 1 - (1 * t).toString();
},
end: () => {
ctx.toElem.style.zIndex = null;
},
}));
startKeepAlive();
if(Umi.Settings.get('playSoundOnConnect'))
Umi.Sound.Play('join');
} else {
switch (data[2]) {
case 'joinfail':
wasKicked = true;
const jfuntil = new Date(parseInt(data[3]) * 1000);
let banmsg = 'You were banned until {0}!'.replace('{0}', jfuntil.toLocaleString());
if(data[3] === '-1')
banmsg = 'You have been banned till the end of time, please try again in a different dimension.';
getLoadingOverlay('hammer', 'Banned!', banmsg);
playBannedBgm();
break;
case 'authfail':
let message = 'Authentication failed!';
const afredir = futami.get('login');
if(afredir) {
message = 'Authentication failed, redirecting to login page...';
setTimeout(function() {
location.assign(afredir);
}, 2000);
}
getLoadingOverlay('cross', 'Failed!', message);
break;
case 'sockfail':
getLoadingOverlay('cross', 'Failed!', 'Too many active connections.');
break;
default:
getLoadingOverlay('cross', 'Failed!', 'Connection failed!');
break;
}
break;
}
}
const juser = new Umi.User(data[2], data[3], data[4], data[5]);
if(userId === juser.getId())
Umi.User.setCurrentUser(juser);
Umi.Users.Add(juser);
if(Umi.User.isCurrentUser(juser)) {
Umi.UI.Markup.Reset();
Umi.UI.Emoticons.Init();
Umi.Parsing.Init();
break;
}
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,
}
));
Umi.Sound.Play('join');
break;
case '2': // message
let text = data[3],
sound = 'incoming';
const muser = Umi.Users.Get(data[2]) || chatBot,
textParts = text.split("\f");
isPM = data[5][4] === '1',
isAction = data[5][1] === '1' && data[5][3] === '0',
isBot = data[2] === '-1',
pmChannel = '@' + muser.getName(),
botInfo = {};
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) {
if(muser.getId() === userId) {
const tmpMsg = text.split(' ');
pmChannel = '@' + tmpMsg.shift();
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(Umi.Settings.get('playJokeSounds'))
try {
const trigger = mami.getTextTriggers().getTrigger(text);
if(trigger.isSoundType()) {
sound = '';
mami.playLibrarySound(
trigger.getRandomSoundName(),
null,
trigger.getVolume(),
trigger.getRate()
);
}
} catch(ex) {}
Umi.Messages.Add(new Umi.Message(
data[4],
data[1],
muser,
text,
isPM ? pmChannel : (data[6] || channelName),
false,
botInfo,
isAction
));
if(!Umi.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,
}
));
Umi.Users.Remove(luser);
if(lsound !== null)
Umi.Sound.Play(lsound);
break;
case '4': // channel
switch(data[1]) {
case '0':
Umi.Channels.Add(new Umi.Channel(data[2], data[3] === '1', data[4] === '1'));
break;
case '1':
const uchannel = Umi.Channels.Get(data[2]);
uchannel.setName(data[3]);
uchannel.setHasPassword(data[4] === '1');
uchannel.setTemporary(data[5] === '1');
Umi.Channels.Update(data[2], uchannel);
break;
case '2':
Umi.Channels.Remove(Umi.Channels.Get(data[2]));
break;
}
break;
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.';
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,
}
));
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.';
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,
}
));
Umi.Sound.Play('leave');
Umi.Users.Remove(mouser);
break;
case '2':
Umi.Channels.Switch(Umi.Channels.Get(data[2]));
break;
}
break;
case '6': // message delete
Umi.Messages.Remove(Umi.Messages.Get(data[1]));
break;
case '7': // context populate
switch(data[1]) {
case '0': // users
const cpuamount = parseInt(data[2]);
let cpustart = 3;
for(let i = 0; i < cpuamount; i++) {
const user = new Umi.User(data[cpustart], data[cpustart + 1], data[cpustart + 2], data[cpustart + 3]);
Umi.Users.Add(user);
cpustart += 5;
}
break;
case '1': // message
let cmid = +data[8],
cmtext = data[7],
cmflags = data[10];
const cmuser = new Umi.User(data[3], data[4], data[5], data[6]),
cmtextParts = cmtext.split("\f"),
cmbotInfo = {};
cmtext = unfuckText(cmtext);
if(isNaN(cmid))
cmid = -Math.ceil(Math.random() * 10000000000);
if(cmtextParts[1]) {
cmbotInfo.isError = cmtextParts[0] !== '0';
cmbotInfo.type = cmtextParts[1];
cmbotInfo.args = cmtextParts.slice(2);
cmtext = parseBotMessage(cmtextParts);
}
Umi.Messages.Add(new Umi.Message(
cmid,
data[2],
cmuser,
cmtext,
channelName,
false,
cmbotInfo,
cmflags[1] === '1' && cmflags[3] === '0',
));
break;
case '2': // channels
const ecpamount = +data[2];
let ecpstart = 3;
for(let i = 0; i < ecpamount; i++) {
const channel = new Umi.Channel(
data[ecpstart],
data[ecpstart + 1] === '1',
data[ecpstart + 2] === '1'
);
Umi.Channels.Add(channel);
if(channel.getName() === channelName)
Umi.Channels.Switch(channel);
ecpstart = ecpstart + 3;
}
break;
}
break;
case '8': // context clear
const cckeep = Umi.Users.Get(userId);
switch(data[1]) {
case '0':
Umi.UI.Messages.RemoveAll();
break;
case '1':
Umi.Users.Clear();
Umi.Users.Add(cckeep);
break;
case '2':
Umi.Channels.Clear();
break;
case '3':
Umi.Messages.Clear();
Umi.Users.Clear();
Umi.Users.Add(cckeep);
break;
case '4':
Umi.UI.Messages.RemoveAll();
Umi.Users.Clear();
Umi.Users.Add(cckeep);
Umi.Channels.Clear();
break;
}
break;
case '9': // baka
noReconnect = true;
wasKicked = true;
const isBan = data[1] === '1';
let message = 'You were kicked, refresh to log back in!';
if(isBan) {
if(data[2] === '-1') {
message = 'You have been banned till the end of time, please try again in a different dimension.';
} else {
const until = new Date(parseInt(data[2]) * 1000);
message = 'You were banned until {0}!'.replace('{0}', until.toLocaleString());
}
}
let icon, header;
if(isBan) {
icon = 'hammer';
header = 'Banned!';
} else {
icon = 'bomb';
header = 'Kicked!';
}
const currentView = views.currentElement();
MamiAnimate({
duration: 550,
easing: 'easeOutExpo',
start: function() {
playBannedBgm(true);
playBannedSfx();
},
update: function(t) {
const scale = 'scale(' + (1 - .5 * t).toString() + ', ' + (1 - 1 * t).toString() + ')';
currentView.style.transform = scale;
},
end: function() {
getLoadingOverlay(icon, header, message).then(() => {
playBannedBgm();
// there's currently no way to reconnect after a kick/ban so just dispose of the ui entirely
if(views.count() > 1)
views.shift();
});
},
});
break;
case '10': // user update
const spuser = Umi.Users.Get(+data[1]);
spuser.setName(data[2]);
spuser.setColour(data[3]);
spuser.setPermissions(data[4]);
Umi.Users.Update(spuser.getId(), spuser);
break;
}
};
const beginConnecting = function() {
if(noReconnect)
return;
++connectAttempts;
let str = 'Connecting to server...';
if(connectAttempts > 1)
str += ' (Attempt ' + connectAttempts.toString() + ')';
getLoadingOverlay('spinner', 'Loading...', str);
UmiServers.getServer(function(server) {
if(Umi.Settings.get('dumpPackets'))
console.log('Connecting to ' + server);
sock = new UmiWebSocket(server, function(ev) {
switch(ev.act) {
case 'ws:open':
onOpen(ev);
break;
case 'ws:close':
onClose(ev);
break;
case 'ws:message':
onMessage(ev);
break;
default:
console.log(ev.data);
break;
}
});
});
};
Umi.Channels.OnSwitch.push(function(old, channel) {
if(channel.isUserChannel()) {
pmUserName = channel.getName().substring(1);
} else {
pmUserName = null;
switchChannel(channel.getName());
}
});
pub.sendMessage = sendMessage;
pub.open = function() {
noReconnect = false;
beginConnecting();
};
pub.close = function() {
noReconnect = true;
sock.close();
};
return pub;
};