mami/src/mami.js/sockchat_old.js

634 lines
18 KiB
JavaScript

#include common.js
#include mszauth.js
#include servers.js
#include watcher.js
#include websock.js
Umi.Protocol.SockChat.Protocol = function() {
const watchers = new MamiWatchers(false);
watchers.define([
'conn:init', 'conn:ready', 'conn:lost', 'conn:error',
'ping:send', 'ping:long', 'ping:recv',
'session:start', 'session:fail', 'session:term',
'user:add', 'user:remove', 'user:update', 'user:clear',
'chan:add', 'chan:remove', 'chan:update', 'chan:clear', 'chan:focus', 'chan:join', 'chan:leave',
'msg:add', 'msg:remove', 'msg:clear',
]);
const parseUserColour = str => {
// todo
return str;
};
let parseUserPermsSep;
const parseUserPerms = str => {
parseUserPermsSep ??= str.includes("\f") ? "\f" : ' ';
return str.split(parseUserPermsSep);
};
const parseMsgFlags = str => {
return {
nameBold: str[0] !== '0',
nameItalics: str[1] !== '0',
nameUnderline: str[2] !== '0',
showColon: str[3] !== '0',
isPM: str[4] !== '0',
isAction: str[1] !== '0' && str[3] === '0',
};
};
let wasConnected = false;
let noReconnect = false;
let connectAttempts = 0;
let wasKicked = false;
let isRestarting = false;
let dumpPackets = false;
let sock;
let selfUserId, selfChannelName, selfPseudoChannelName;
let lastPing, lastPong, pingTimer, pingWatcher;
const handlers = {};
const stopPingWatcher = () => {
if(pingWatcher !== undefined) {
clearTimeout(pingWatcher);
pingWatcher = undefined;
}
};
const startPingWatcher = () => {
if(pingWatcher === undefined)
pingWatcher = setTimeout(() => {
stopPingWatcher();
if(lastPong === undefined)
watchers.call('ping:long');
}, 2000);
};
const send = (...args) => {
if(args.length < 1)
throw 'you must specify at least one argument as an opcode';
const pack = args.join("\t");
if(dumpPackets)
console.log(pack);
sock?.send(pack);
};
const onSendPing = () => {
if(selfUserId === undefined)
return;
watchers.call('ping:send');
startPingWatcher();
lastPong = undefined;
lastPing = Date.now();
};
const sendAuth = args => {
if(selfUserId === undefined)
send('1', ...args);
};
const sendMessage = text => {
if(selfUserId === undefined)
return;
if(text.substring(0, 1) !== '/' && selfPseudoChannelName !== undefined)
text = `/msg ${selfPseudoChannelName} ${text}`;
send('2', selfUserId, text);
};
const startKeepAlive = () => sock?.sendInterval(`0\t${selfUserId}`, futami.get('ping') * 1000);
const stopKeepAlive = () => sock?.clearIntervals();
const onOpen = ev => {
if(dumpPackets)
console.log(ev);
isRestarting = false;
watchers.call('conn:ready', {
wasConnected: wasConnected,
});
// see if these are neccesary
watchers.call('user:clear');
watchers.call('chan:clear');
const authInfo = MamiMisuzuAuth.getInfo();
sendAuth([authInfo.method, authInfo.token]);
};
const onClose = ev => {
if(dumpPackets)
console.log(ev);
selfUserId = undefined;
selfChannelName = undefined;
selfPseudoChannelName = undefined;
stopPingWatcher();
stopKeepAlive();
if(wasKicked)
return;
let code = ev.code;
if(isRestarting && code === 1006) {
code = 1012;
} else if(code === 1012)
isRestarting = true;
watchers.call('conn:lost', {
wasConnected: wasConnected,
isRestarting: isRestarting,
code: code,
});
connectAttempts = 0;
setTimeout(() => beginConnecting(), 5000);
};
const onError = ex => {
watchers.call('conn:error', ex);
};
const unfuckText = text => {
const elem = document.createElement('div');
elem.innerHTML = text.replace(/ <br\/> /g, "\n");
text = elem.innerText;
return text;
};
const onMessage = ev => {
const args = ev.data.split("\t");
let handler = handlers;
if(dumpPackets)
console.log(args);
for(;;) {
handler = handler[args.shift()];
if(handler === undefined)
break;
if(typeof handler === 'function') {
handler(...args);
break;
}
}
};
// pong handler
handlers['0'] = () => {
lastPong = Date.now();
watchers.call('ping:recv', {
ping: lastPing,
pong: lastPong,
diff: lastPong - lastPing,
});
};
// join/auth
handlers['1'] = (successOrTimeStamp, userIdOrReason, userNameOrExpiry, userColour, userPerms, chanNameOrMsgId, maxLength) => {
if(successOrTimeStamp === 'y') {
selfUserId = userIdOrReason;
selfChannelName = chanNameOrMsgId;
watchers.call('session:start', {
wasConnected: wasConnected,
session: { success: true },
ctx: {
maxMsgLength: parseInt(maxLength),
},
user: {
id: selfUserId,
self: true,
name: userNameOrExpiry,
colour: parseUserColour(userColour),
perms: parseUserPerms(userPerms),
permsRaw: userPerms,
},
channel: {
name: selfChannelName,
},
});
startKeepAlive();
wasConnected = true;
return;
}
if(successOrTimeStamp === 'n') {
wasKicked = true;
const failInfo = {
session: {
success: false,
reason: userIdOrReason,
needsAuth: userIdOrReason === 'authfail',
},
};
if(userNameOrExpiry !== undefined)
failInfo.baka = {
type: 'join',
perma: userNameOrExpiry === '-1',
until: userNameOrExpiry === '-1' ? undefined : new Date(parseInt(userNameOrExpiry) * 1000),
};
watchers.call('session:fail', failInfo);
return;
}
watchers.call('user:add', {
msg: {
id: chanNameOrMsgId,
time: new Date(parseInt(successOrTimeStamp) * 1000),
channel: selfChannelName,
botInfo: {
type: 'join',
args: [userNameOrExpiry],
},
},
user: {
id: userIdOrReason,
self: userIdOrReason === selfUserId,
name: userNameOrExpiry,
colour: parseUserColour(userColour),
perms: parseUserPerms(userPerms),
permsRaw: userPerms,
},
});
};
// message add
handlers['2'] = (timeStamp, userId, msgText, msgId, msgFlags) => {
let mText = unfuckText(msgText);
let mChannelName = selfChannelName;
if(msgFlags[4] !== '0') {
if(userId === selfUserId) {
const mTextParts = mText.split(' ');
mChannelName = `@${mTextParts.shift()}`;
mText = mTextParts.join(' ');
} else {
mChannelName = `@~${userId}`;
}
}
const msgInfo = {
msg: {
id: msgId,
time: new Date(parseInt(timeStamp) * 1000),
channel: mChannelName,
sender: {
id: userId,
self: userId === selfUserId,
},
flags: parseMsgFlags(msgFlags),
flagsRaw: msgFlags,
isBot: userId === '-1',
text: mText,
},
};
if(msgInfo.msg.isBot) {
const botParts = msgText.split("\f");
msgInfo.msg.botInfo = {
isError: botParts[0] === '1',
type: botParts[1],
args: botParts.slice(2),
};
}
watchers.call('msg:add', msgInfo);
};
// user leave
handlers['3'] = (userId, userName, reason, timeStamp, msgId) => {
watchers.call('user:remove', {
leave: { type: reason },
msg: {
id: msgId,
time: new Date(parseInt(timeStamp) * 1000),
channel: selfChannelName,
botInfo: {
type: reason,
args: [userName],
},
},
user: {
id: userId,
self: userId === selfUserId,
name: userName,
},
});
};
// channel add/upd/del
handlers['4'] = {};
// channel add
handlers['4']['0'] = (name, hasPass, isTemp) => {
watchers.call('chan:add', {
channel: {
name: name,
hasPassword: hasPass !== '0',
isTemporary: isTemp !== '0',
},
});
};
// channel update
handlers['4']['1'] = (prevName, name, hasPass, isTemp) => {
watchers.call('chan:update', {
channel: {
previousName: prevName,
name: name,
hasPassword: hasPass !== '0',
isTemporary: isTemp !== '0',
},
});
};
// channel remove
handlers['4']['2'] = name => {
watchers.call('chan:remove', {
channel: { name: name },
});
};
// user channel move
handlers['5'] = {};
// user join channel
handlers['5']['0'] = (userId, userName, userColour, userPerms, msgId) => {
watchers.call('chan:join', {
user: {
id: userId,
self: userId === selfUserId,
name: userName,
colour: parseUserColour(userColour),
perms: parseUserPerms(userPerms),
permsRaw: userPerms,
},
msg: {
id: msgId,
channel: selfChannelName,
botInfo: {
type: 'jchan',
args: [userName],
},
},
});
};
// user leave channel
handlers['5']['1'] = (userId, msgId) => {
watchers.call('chan:leave', {
user: {
id: userId,
self: userId === selfUserId,
},
msg: {
id: msgId,
channel: selfChannelName,
botInfo: {
type: 'lchan',
args: [userId],
},
},
});
};
// user forced switch channel
handlers['5']['2'] = name => {
selfChannelName = name;
watchers.call('chan:focus', {
channel: { name: selfChannelName },
});
};
// message delete
handlers['6'] = msgId => {
watchers.call('msg:remove', {
msg: {
id: msgId,
channel: selfChannelName,
},
});
};
// context populate
handlers['7'] = {};
// existing users
handlers['7']['0'] = (count, ...args) => {
count = parseInt(count);
for(let i = 0; i < count; ++i) {
const offset = 5 * i;
watchers.call('user:add', {
user: {
id: args[offset],
self: args[offset] === selfUserId,
name: args[offset + 1],
colour: parseUserColour(args[offset + 2]),
perms: parseUserPerms(args[offset + 3]),
permsRaw: args[offset + 3],
hidden: args[offset + 4] !== '0',
},
});
}
};
// existing message
handlers['7']['1'] = (timeStamp, userId, userName, userColour, userPerms, msgText, msgId, msgNotify, msgFlags) => {
const info = {
msg: {
id: msgId,
time: new Date(parseInt(timeStamp) * 1000),
channel: selfChannelName,
sender: {
id: userId,
self: userId === selfUserId,
name: userName,
colour: parseUserColour(userColour),
perms: parseUserColour(userPerms),
permsRaw: userPerms,
},
isBot: userId === '-1',
silent: msgNotify === '0',
flags: parseMsgFlags(msgFlags),
flagsRaw: msgFlags,
text: unfuckText(msgText),
},
};
const msgIdFirst = info.msg.id.charCodeAt(0);
if(msgIdFirst < 48 || msgIdFirst > 57)
info.msg.id = (Math.round(Number.MIN_SAFE_INTEGER * Math.random())).toString();
if(info.msg.isBot) {
const botParts = msgText.split("\f");
info.msg.botInfo = {
isError: botParts[0] === '1',
type: botParts[1],
args: botParts.slice(2),
};
}
watchers.call('msg:add', info);
};
// existing channels
handlers['7']['2'] = (count, ...args) => {
count = parseInt(count);
for(let i = 0; i < count; ++i) {
const offset = 3 * i;
watchers.call('chan:add', {
channel: {
name: args[offset],
hasPassword: args[offset + 1] !== '0',
isTemporary: args[offset + 2] !== '0',
isCurrent: args[offset] === selfChannelName,
},
});
}
watchers.call('chan:focus', {
channel: { name: selfChannelName },
});
};
// context clear
handlers['8'] = mode => {
if(mode === '0' || mode === '3' || mode === '4')
watchers.call('msg:clear');
if(mode === '1' || mode === '3' || mode === '4')
watchers.call('user:clear');
if(mode === '2' || mode === '4')
watchers.call('chan:clear');
};
// baka (ban/kick)
handlers['9'] = (type, expiry) => {
noReconnect = true;
wasKicked = true;
const bakaInfo = {
session: { success: false },
baka: {
type: type === '0' ? 'kick' : 'ban',
},
};
if(bakaInfo.baka.type === 'ban') {
bakaInfo.baka.perma = expiry === '-1';
bakaInfo.baka.until = expiry === '-1' ? undefined : new Date(parseInt(expiry) * 1000);
}
watchers.call('session:term', bakaInfo);
};
// user update
handlers['10'] = (userId, userName, userColour, userPerms) => {
watchers.call('user:update', {
user: {
id: userId,
self: userId === selfUserId,
name: userName,
colour: parseUserColour(userColour),
perms: parseUserPerms(userPerms),
permsRaw: userPerms,
},
});
};
const beginConnecting = () => {
sock?.close();
if(noReconnect)
return;
UmiServers.getServer(server => {
watchers.call('conn:init', {
server: server,
wasConnected: wasConnected,
attempt: ++connectAttempts,
});
sock = new UmiWebSocket(server, ev => {
switch(ev.act) {
case 'ws:open':
onOpen(ev);
break;
case 'ws:close':
onClose(ev);
break;
case 'ws:message':
onMessage(ev);
break;
case 'ws:create_interval':
pingTimer = ev.id;
break;
case 'ws:call_interval':
if(ev.id === pingTimer)
onSendPing();
break;
case 'ws:clear_intervals':
pingTimer = undefined;
break;
default:
console.log(ev.data);
break;
}
});
});
};
return {
sendMessage: sendMessage,
open: () => {
noReconnect = false;
beginConnecting();
},
close: () => {
noReconnect = true;
sock?.close();
},
watch: (name, handler) => watchers.watch(name, handler),
unwatch: (name, handler) => watchers.unwatch(name, handler),
setDumpPackets: state => dumpPackets = !!state,
switchChannel: channelInfo => {
if(selfUserId === undefined)
return;
const name = channelInfo.getName();
if(channelInfo.isUserChannel()) {
selfPseudoChannelName = name.substring(1);
} else {
selfPseudoChannelName = undefined;
if(selfChannelName === name)
return;
selfChannelName = name;
sendMessage(`/join ${name}`);
}
},
};
};