-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
264 lines (231 loc) · 9.1 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
"use strict";
var login = require("facebook-chat-api");
var lisp = require("./lisp");
var Firebase = require("firebase");
var RJSON = require("rjson");
var config = JSON.parse(require('fs').readFileSync('config.json', 'utf8'));
var db = new Firebase(config.firebase);
var globalScopeDB = db.child("globalScope");
var allStackFramesDB = db.child("allStackFrames");
var allRuleListsDB = db.child("allRuleLists");
function startBot(api, globalScope, allStackFrames, allRuleLists) {
var currentUserId;
var currentThreadId;
var currentMessageID;
var currentChat;
var currentOtherUsernames;
var currentOtherIds;
var currentStackFrame;
var currentRuleList;
// Loaded globally
lisp.evaluate(lisp.parse("(load parser)"));
lisp.addFunction("listen", function (utils){
return function(args, charPos){
utils.checkNumArgs(args, 2);
utils.throwError("Unimplemented.", charPos);
return utils.toLispData("deal with it.");
};
}, "Call with regex, then function. Listens to all messages and calls the function when the regex is matched.");
lisp.addFunction("send-message", function (utils){
return function(args, charPos){
utils.checkNumArgs(args, 2);
if (utils.isList(args[0]) || utils.isList(args[1]))
utils.throwError("Arguments can't be lists.", charPos);
api.sendMessage(args[1].value, args[0].value);
return utils.toLispData("Message sent");
};
}, "Call with threadid, then message");
lisp.addFunction("thread-id", function(utils){
return function(args, charPos) {
return utils.toLispData(currentThreadId);
};
}, "Current thread id.");
lisp.addFunction("id-list", function(utils){
return function(args, charPos) {
return utils.toLispData(currentOtherIds);
};
}, "List of ids in thread.");
lisp.addFunction("name-list", function(utils){
return function(args, charPos) {
return utils.toLispData(currentOtherUsernames);
};
}, "List of names in thread.");
lisp.addFunction("clear-namespace", function(utils) {
return function(args, charPos) {
delete allStackFrames[currentThreadId];
return utils.toLispData("Namespace cleared");
};
}, "Will delete all user-defined values.");
// lisp.addMacro("define-with-default", function(utils) {
// return function(args, charPos) {
// utils.checkNumArgs(charPos, args, 2);
// if(args[0].type !== 'identifier') utils.throwError("First argument to define-with-default should be an identifier.", args[0]);
//
// if(currentStackFrame[args[0].value]) {
// return globalScope[currentStackFrame[args[0].value]].node;
// }
//
// return lisp.evaluate(utils.makeArr(charPos,
// new utils.Node("define", "identifier", charPos),
// new utils.Node(args[0].value, "identifier", charPos),
// lisp.evaluate(args[1])
// ));
// };
// }, "Will define only if that identifier isn't already defined in the scope (aka loaded from the DB).");
// Main method
var stopListening = api.listen(function(err, event) {
if(err) return console.error(err);
if(event.type === 'message') {
console.log("Received ->", event);
api.markAsRead(event.threadID);
// Sigh. The api has changed and getThreadInfo doesn't work with Rose.
// We use getThreadList which returns the right info, but we assume that
// rose will not receive more than 5 messages in 5 different chats at the
// same time. getThreadList doesn't allow us to get thread information
// of one specific thread.
// Ben - June 12th 2017
api.getThreadList(0, 5, function(err, threads) {
var thread = threads.filter(function(t) {
return t.threadID === event.threadID;
})[0];
api.getUserInfo(thread.participantIDs, function(err, users) {
if (err) throw err;
var user = users[event.senderID];
var participantNames = [];
for (var id in users) {
participantNames.push(users[id].name);
}
read(event.body, event.threadID, event.senderID, participantNames, thread.participantIDs, event.messageID, function(msg) {
if(!msg) return;
if(msg.text && msg.text.length > 0) {
console.log("Sending ->", msg, msg.text.length, event.threadID);
// api.sendMessage("```scheme\n" + msg.text + "\n```", event.threadID);
api.sendMessage(msg.text, event.threadID);
}
});
});
});
}
});
// messages, chat id are Strings, otherUsernames is array of Strings
function read(message, threadID, userId, otherUsernames, otherIds, messageID, callback) {
// Default chat object or existing one
// And set the global object
//if (!currentChat.existingChat){
// currentChat.existingChat = true;
// api.sendMessage("Hey, type '/help' for some useful commands!", threadID);
//}
currentThreadId = threadID;
currentMessageID = messageID;
currentUserId = userId;
currentOtherUsernames = otherUsernames;
var newThread = !allStackFrames[currentThreadId] || Object.keys(allStackFrames[currentThreadId]).length == 0;
allStackFrames[currentThreadId] = allStackFrames[currentThreadId] || {};
currentStackFrame = allStackFrames[currentThreadId];
if (allRuleLists[currentThreadId] != null) {
var parsedJSON = JSON.parse(allRuleLists[currentThreadId]);
currentRuleList = Object.keys(parsedJSON) === 0 ? {} : RJSON.unpack(parsedJSON);
}
parseLisp(message, callback, newThread);
}
function parseLisp(msg, sendReply, newThread) {
var inTxt = "";
var shouldExposeExceptions = false;
if ((/^!\(/m).test(msg)) {
inTxt = msg.slice(1);
shouldExposeExceptions = true;
} else if ((/^\/[\S\s]+/m).test(msg)){
// inTxt = "(" + msg.slice(1) + ")";
return;
} else if ((/^\([\S\s]+\)/m).test(msg)){
inTxt = msg;
}
var outTxt = "";
if (inTxt.length > 0) {
try {
var availableNodes = {};
Object.keys(globalScope).forEach(function(uuid) {
availableNodes[uuid] = globalScope[uuid].node;
});
var output;
var context = {
stackFrame: currentStackFrame,
uuidToNodeMap: availableNodes,
ruleList: currentRuleList,
};
console.log(newThread);
if (newThread) {
var defaultVars = lisp.parseAndEvaluateWith("(load std-lib)", context);
defaultVars = lisp.parseAndEvaluateWith("(load bot-lib)", defaultVars);
output = lisp.parseAndEvaluateWith(inTxt, defaultVars);
} else {
output = lisp.parseAndEvaluateWith(inTxt, context);
}
outTxt = lisp.prettyPrint(output.res, output.uuidToNodeMap);
if (outTxt.length > 0) {
if (outTxt[0] == '"' && outTxt[outTxt.length-1] == '"') {
outTxt = outTxt.substring(1,outTxt.length-1);
}
sendReply({text: outTxt});
}
} catch (e) {
if (!shouldExposeExceptions) {
api.setMessageReaction(":thumbsdown:", currentMessageID, console.error);
} else {
outTxt = e.toString();
sendReply({text: outTxt});
}
}
// shortcut when output is null (this probably only happens when there's an error)
if (output == null) return;
try {
Object.keys(output.stackFrame).forEach(function(identifier) {
var uuid = output.stackFrame[identifier];
var node = output.uuidToNodeMap[uuid];
currentStackFrame[identifier] = uuid;
});
Object.keys(output.uuidToNodeMap).forEach(function(uuid) {
var node = output.uuidToNodeMap[uuid];
var writePermissions = [currentThreadId];
if(globalScope[uuid] && globalScope[uuid].writePermissions) {
writePermissions = globalScope[uuid].writePermissions;
}
globalScope[uuid] = {
node: typeof node !== 'string' ? JSON.stringify(RJSON.pack(node)) : node,
writePermissions: node.type === 'ref' ? writePermissions : null,
};
});
// Update allRuleLists with the new one
allRuleLists[currentThreadId] = JSON.stringify(RJSON.pack(output.ruleList));
globalScopeDB.set(globalScope);
allStackFramesDB.set(allStackFrames);
allRuleListsDB.set(allRuleLists);
} catch (e) {
console.error('Error during data persist: `'+e+'`');
}
}
}
}
function replaceUndefinedList(node){
switch(node.type){
case 'list':
node.value = node.value || [];
node.value.forEach(n => replaceUndefinedList(n));
break;
case 'function':
node.argNames = node.argNames || [];
node.scope = node.scope || {};
break;
}
}
// Main function
db.once('value', function(snapshot) {
var data = snapshot.val() || {};
login(config, {forceLogin: true}, function(err, api) {
if(err) return console.error(err);
if (data.globalScope) {
Object.keys(data.globalScope).forEach(k => replaceUndefinedList(data.globalScope[k]));
}
startBot(api, data.globalScope || {}, data.allStackFrames || {}, data.allRuleLists || {});
});
});