523 lines
14 KiB
JavaScript
523 lines
14 KiB
JavaScript
#include servers.js
|
|
#include watcher.js
|
|
|
|
var AmiSockChat = function(auth) {
|
|
var pub = {},
|
|
watchers = new AmiWatcherCollection;
|
|
|
|
var sock = undefined,
|
|
handlers = {},
|
|
dumpPackets = false;
|
|
|
|
var selfUserId = undefined,
|
|
selfChannelName = undefined;
|
|
|
|
var wasConnected = false,
|
|
wasKicked = false,
|
|
isClosing = false;
|
|
|
|
var lastPing = undefined,
|
|
lastPong = undefined,
|
|
pingTimer = undefined,
|
|
pingWatcher = undefined;
|
|
|
|
watchers.proxy(pub);
|
|
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',
|
|
]);
|
|
|
|
pub.setPacketDump = function(value) { dumpPackets = !!value; };
|
|
|
|
var parseUserColour = function(str) {
|
|
return str;
|
|
};
|
|
var parseUserPermsSep = undefined;
|
|
var parseUserPerms = function(str) {
|
|
if(parseUserPermsSep === undefined)
|
|
parseUserPermsSep = str.indexOf("\f") > -1 ? "\f" : ' ';
|
|
return str;
|
|
};
|
|
var parseMsgFlags = function(str) {
|
|
return {
|
|
isNameBold: str[0] !== '0',
|
|
isNameItalic: str[1] !== '0',
|
|
isNameUnderline: str[2] !== '0',
|
|
isNameSeparate: str[3] !== '0',
|
|
isPrivate: str[4] !== '0',
|
|
};
|
|
};
|
|
|
|
var stopPingWatcher = function() {
|
|
if(pingWatcher === undefined)
|
|
return;
|
|
clearTimeout(pingWatcher);
|
|
pingWatcher = undefined;
|
|
};
|
|
var startPingWatcher = function() {
|
|
if(pingWatcher !== undefined)
|
|
return;
|
|
pingWatcher = setTimeout(function() {
|
|
stopPingWatcher();
|
|
if(lastPong !== undefined)
|
|
return;
|
|
|
|
watchers.call('ping:long', pub);
|
|
}, 2000);
|
|
};
|
|
|
|
var send = function(opcode, data) {
|
|
if(!sock) return;
|
|
var msg = opcode;
|
|
if(data) msg += "\t" + data.join("\t");
|
|
if(dumpPackets) console.log(msg);
|
|
sock.send(msg);
|
|
};
|
|
var sendPing = function() {
|
|
if(!UserContext.self)
|
|
return;
|
|
|
|
watchers.call('ping:send', pub);
|
|
startPingWatcher();
|
|
|
|
lastPong = undefined;
|
|
lastPing = Date.now();
|
|
send('0', [UserContext.self.id]);
|
|
};
|
|
var sendAuth = function(args) {
|
|
send('1', args);
|
|
};
|
|
var sendMessage = function(text) {
|
|
if(!UserContext.self)
|
|
return;
|
|
send('2', [UserContext.self.id, text]);
|
|
};
|
|
|
|
var startKeepAlive = function() {
|
|
if(pingTimer !== undefined)
|
|
return;
|
|
pingTimer = setInterval(sendPing, futami.get('ping') * 1000);
|
|
};
|
|
var stopKeepAlive = function() {
|
|
if(pingTimer === undefined)
|
|
return;
|
|
clearInterval(pingTimer);
|
|
pingTimer = undefined;
|
|
};
|
|
|
|
var connect = function() {
|
|
watchers.call('conn:init', pub, [{
|
|
wasConnected: wasConnected,
|
|
}]);
|
|
|
|
ami.servers.getServer(function(server) {
|
|
sock = new WebSocket(server);
|
|
sock.addEventListener('open', onOpen);
|
|
sock.addEventListener('close', onClose);
|
|
sock.addEventListener('error', onError);
|
|
sock.addEventListener('message', onMessage);
|
|
}.bind(this));
|
|
};
|
|
|
|
var formatBotMsg = function(type, args, isError) {
|
|
var str = (isError ? '1' : '0') + "\f" + type;
|
|
if(args && args.length)
|
|
str += "\f" + args.join("\f");
|
|
return str;
|
|
};
|
|
|
|
// pong handler
|
|
handlers['0'] = function(args) {
|
|
lastPong = Date.now();
|
|
watchers.call('ping:recv', pub, [{
|
|
ping: lastPing,
|
|
pong: lastPong,
|
|
diff: lastPong - lastPing,
|
|
}]);
|
|
};
|
|
|
|
// join/auth
|
|
handlers['1'] = function(args) {
|
|
if(args[0] !== 'y' && args[0] !== 'n') {
|
|
watchers.call('user:add', pub, [{
|
|
msg: {
|
|
id: args[5],
|
|
time: new Date(parseInt(args[0]) * 1000),
|
|
text: formatBotMsg('join', [args[2]]),
|
|
},
|
|
user: {
|
|
id: args[1],
|
|
name: args[2],
|
|
colour: parseUserColour(args[3]),
|
|
perms: parseUserPerms(args[4]),
|
|
permsRaw: args[4],
|
|
},
|
|
}]);
|
|
} else {
|
|
if(args[0] === 'y') {
|
|
var userId = args[1],
|
|
channelName = args[5],
|
|
maxMsgLength = parseInt(args[6]);
|
|
|
|
selfUserId = userId;
|
|
selfChannelName = channelName;
|
|
|
|
watchers.call('session:start', pub, [{
|
|
wasConnected: wasConnected,
|
|
session: { success: true },
|
|
ctx: {
|
|
maxMsgLength: maxMsgLength,
|
|
},
|
|
user: {
|
|
id: userId,
|
|
name: args[2],
|
|
colour: parseUserColour(args[3]),
|
|
perms: parseUserPerms(args[4]),
|
|
permsRaw: args[4],
|
|
},
|
|
channel: {
|
|
name: channelName,
|
|
},
|
|
}]);
|
|
|
|
wasConnected = true;
|
|
} else {
|
|
wasKicked = true;
|
|
|
|
var info = {
|
|
session: {
|
|
success: false,
|
|
reason: args[1],
|
|
},
|
|
};
|
|
if(args[2] !== undefined)
|
|
info.baka = {
|
|
type: 'join',
|
|
perma: args[2] === '-1',
|
|
until: new Date(parseInt(args[2]) * 1000),
|
|
};
|
|
|
|
watchers.call('session:fail', pub, [info]);
|
|
}
|
|
}
|
|
};
|
|
|
|
// message add
|
|
handlers['2'] = function(args) {
|
|
var info = {
|
|
msg: {
|
|
id: args[3],
|
|
time: new Date(parseInt(args[0]) * 1000),
|
|
sender: { id: args[1] },
|
|
flags: parseMsgFlags(args[4]),
|
|
flagsRaw: args[4],
|
|
isBot: args[1] === '-1',
|
|
text: args[2],
|
|
},
|
|
};
|
|
|
|
if(info.msg.isBot) {
|
|
var botParts = args[2].split("\f");
|
|
|
|
info.msg.botInfo = {
|
|
isError: botParts[0] === '1',
|
|
type: botParts[1],
|
|
args: botParts.slice(2),
|
|
};
|
|
}
|
|
|
|
watchers.call('msg:add', pub, [info]);
|
|
};
|
|
|
|
// user leave
|
|
handlers['3'] = function(args) {
|
|
watchers.call('user:remove', pub, [{
|
|
leave: { type: args[2] },
|
|
msg: {
|
|
id: args[4],
|
|
time: new Date(parseInt(args[3]) * 1000),
|
|
text: formatBotMsg(args[2], [args[1]]),
|
|
},
|
|
user: {
|
|
id: args[0],
|
|
name: args[1],
|
|
},
|
|
}]);
|
|
};
|
|
|
|
// channel add/upd/del
|
|
handlers['4'] = {};
|
|
|
|
// channel add
|
|
handlers['4']['0'] = function(args) {
|
|
watchers.call('chan:add', pub, [{
|
|
channel: {
|
|
name: args[0],
|
|
hasPassword: args[1] !== '0',
|
|
isTemporary: args[2] !== '0',
|
|
},
|
|
}]);
|
|
};
|
|
|
|
// channel update
|
|
handlers['4']['1'] = function(args) {
|
|
watchers.call('chan:update', pub, [{
|
|
channel: {
|
|
previousName: args[0],
|
|
name: args[1],
|
|
hasPassword: args[2] !== '0',
|
|
isTemporary: args[3] !== '0',
|
|
},
|
|
}]);
|
|
};
|
|
|
|
// channel nuke
|
|
handlers['4']['2'] = function(args) {
|
|
watchers.call('chan:remove', pub, [{
|
|
channel: { name: args[0] },
|
|
}]);
|
|
};
|
|
|
|
// user channel move
|
|
handlers['5'] = {};
|
|
|
|
// user join channel
|
|
handlers['5']['0'] = function(args) {
|
|
watchers.call('chan:join', pub, [{
|
|
user: {
|
|
id: args[0],
|
|
name: args[1],
|
|
colour: parseUserColour(args[2]),
|
|
perms: parseUserPerms(args[3]),
|
|
permsRaw: args[3],
|
|
},
|
|
msg: {
|
|
id: args[4],
|
|
text: formatBotMsg('jchan', [args[1]]),
|
|
},
|
|
}]);
|
|
};
|
|
|
|
// user leave channel
|
|
handlers['5']['1'] = function(args) {
|
|
watchers.call('chan:leave', pub, [{
|
|
user: { id: args[0] },
|
|
msg: {
|
|
id: args[1],
|
|
text: formatBotMsg('lchan', [UserContext.users[args[0]].username]),
|
|
},
|
|
}]);
|
|
};
|
|
|
|
// user forced channel switch
|
|
handlers['5']['2'] = function(args) {
|
|
selfChannelName = args[0];
|
|
|
|
watchers.call('chan:focus', pub, [{
|
|
channel: { name: selfChannelName },
|
|
}]);
|
|
};
|
|
|
|
// message delete
|
|
handlers['6'] = function(args) {
|
|
watchers.call('msg:remove', pub, [{
|
|
msg: { id: args[0] },
|
|
}]);
|
|
};
|
|
|
|
// context populate
|
|
handlers['7'] = {};
|
|
|
|
// existing users
|
|
handlers['7']['0'] = function(args) {
|
|
var count = parseInt(args[0]);
|
|
|
|
for(var i = 0; i < count; i++) {
|
|
var offset = 1 + 5 * i,
|
|
userId = args[offset];
|
|
|
|
if(selfUserId === userId)
|
|
continue;
|
|
|
|
watchers.call('user:add', pub, [{
|
|
user: {
|
|
id: userId,
|
|
name: args[offset + 1],
|
|
colour: parseUserColour(args[offset + 2]),
|
|
perms: parseUserPerms(args[offset + 3]),
|
|
permsRaw: args[offset + 3],
|
|
hidden: args[offset + 4] !== '0',
|
|
},
|
|
}]);
|
|
}
|
|
};
|
|
|
|
// existing messages
|
|
handlers['7']['1'] = function(args) {
|
|
var info = {
|
|
msg: {
|
|
id: args[6],
|
|
time: new Date(parseInt(args[0]) * 1000),
|
|
sender: {
|
|
id: args[1],
|
|
name: args[2],
|
|
colour: parseUserColour(args[3]),
|
|
perms: parseUserPerms(args[4]),
|
|
permsRaw: args[4],
|
|
},
|
|
isBot: args[1] === '-1',
|
|
silent: args[7] === '0',
|
|
flags: parseMsgFlags(args[8]),
|
|
flagsRaw: args[8],
|
|
text: args[5],
|
|
},
|
|
};
|
|
|
|
if(info.msg.isBot) {
|
|
var botParts = args[5].split("\f");
|
|
|
|
info.msg.botInfo = {
|
|
isError: botParts[0] === '1',
|
|
type: botParts[1],
|
|
args: botParts.slice(2),
|
|
};
|
|
}
|
|
|
|
watchers.call('msg:add', pub, [info]);
|
|
};
|
|
|
|
// existing channels
|
|
handlers['7']['2'] = function(args) {
|
|
var count = parseInt(args[0]);
|
|
|
|
for(var i = 0; i < count; i++) {
|
|
var offset = 1 + 3 * i,
|
|
chanName = args[offset];
|
|
|
|
watchers.call('chan:add', pub, [{
|
|
channel: {
|
|
name: chanName,
|
|
hasPassword: args[offset + 1] !== '0',
|
|
isTemporary: args[offset + 2] !== '0',
|
|
isCurrent: chanName === selfChannelName,
|
|
},
|
|
}]);
|
|
}
|
|
|
|
watchers.call('chan:focus', pub, [{
|
|
channel: { name: selfChannelName },
|
|
}]);
|
|
};
|
|
|
|
// context clear
|
|
handlers['8'] = function(args) {
|
|
if(args[0] === '0' || args[0] === '3' || args[0] === '4')
|
|
watchers.call('msg:clear', pub);
|
|
|
|
if(args[0] === '1' || args[0] === '3' || args[0] === '4')
|
|
watchers.call('user:clear', pub);
|
|
|
|
if(args[0] === '2' || args[0] === '4')
|
|
watchers.call('chan:clear', pub);
|
|
};
|
|
|
|
// baka (ban/kick)
|
|
handlers['9'] = function(args) {
|
|
var bakaType = args[0],
|
|
bakaTime = parseInt(args[1]);
|
|
|
|
wasKicked = true;
|
|
|
|
var info = {
|
|
session: { success: false },
|
|
baka: {
|
|
type: bakaType === '0' ? 'kick' : 'ban',
|
|
},
|
|
};
|
|
|
|
if(info.baka.type === 'ban')
|
|
info.baka.until = new Date(bakaTime * 1000);
|
|
|
|
watchers.call('session:term', pub, [info]);
|
|
};
|
|
|
|
// user update
|
|
handlers['10'] = function(args) {
|
|
watchers.call('user:update', pub, [{
|
|
user: {
|
|
id: args[0],
|
|
name: args[1],
|
|
colour: parseUserColour(args[2]),
|
|
perms: parseUserPerms(args[3]),
|
|
permsRaw: args[3],
|
|
},
|
|
}]);
|
|
};
|
|
|
|
var onOpen = function() {
|
|
watchers.call('conn:ready', pub, [{
|
|
wasConnected: wasConnected,
|
|
}]);
|
|
|
|
startKeepAlive();
|
|
|
|
watchers.call('user:clear', pub);
|
|
watchers.call('msg:clear', pub);
|
|
watchers.call('chan:clear', pub);
|
|
|
|
var authInfo = auth.getInfo();
|
|
sendAuth([authInfo.method, authInfo.token]);
|
|
};
|
|
|
|
var onClose = function(ev) {
|
|
stopPingWatcher();
|
|
stopKeepAlive();
|
|
|
|
if(isClosing || wasKicked)
|
|
return;
|
|
|
|
watchers.call('conn:lost', pub, [{
|
|
wasConnected: wasConnected,
|
|
code: ev.code,
|
|
msg: formatBotMsg('reconnect', [], true),
|
|
}]);
|
|
};
|
|
|
|
var onError = function(ex) {
|
|
watchers.call('conn:error', pub, [ex]);
|
|
};
|
|
|
|
var onMessage = function(ev) {
|
|
var args = ev.data.split("\t"),
|
|
handler = handlers;
|
|
|
|
if(dumpPackets)
|
|
console.log(args);
|
|
|
|
for(;;) {
|
|
handler = handler[args.shift()];
|
|
if(!handler)
|
|
break;
|
|
|
|
if(typeof handler === 'function') {
|
|
handler.call(pub, args);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
pub.sendMessage = sendMessage;
|
|
pub.open = function() {
|
|
connect();
|
|
};
|
|
pub.close = function() {
|
|
isClosing = true;
|
|
};
|
|
|
|
return pub;
|
|
};
|