-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.js
303 lines (246 loc) · 8.89 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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
// index.js
/* eslint-disable max-len */
/**
Log Configuration
Logging is configured at INFO level by default
To increase the log level start the server with:
LOG_LEVEL=DEBUG npm start
or export a value
export LOG_LEVEL=DEBUG
npm start
Optional Environment Variables
You can add any of the following environment variables to startup or export TheRealFCMBot
LOG_LEVEL Can be set to any value in [TRACE, DEBUG, INFO, WARN, ERROR, FATAL] the application currently logs on the debug, info, error channels (warning api keys are logged at debug level)
FCM_KEY Setting this env variable allows you to set the FCM Server Key, overriding what is in keys.server
PORT Setting the port variable will start the server on a port other than 3000, useful for deployment to places like Heroku
A full launch with all options set might look like:
LOG_LEVEL=INFO FCK_KEY=12345 PORT=8080 npm start
**/
/* eslint-enable max-len */
// Package Requires
const path = require('path');
const express = require('express');
const exphbs = require('express-handlebars');
// const keys = require('./keys.json');
const MarkovChain = require('markovchain');
const fs = require('fs');
const FCM = require('fcm-node');
const bunyan = require('bunyan'); // a fast happy logging library for node
const bodyParser = require('body-parser');
// Global Variables
const app = express(); // the express application
// setup parsing for express
app.use( bodyParser.json() ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true,
}));
const log = bunyan.createLogger({
name: 'AdvancedAndroid_FCMServer',
serializers: {
err: bunyan.stdSerializers.err,
},
level: process.env.LOG_LEVEL || bunyan.INFO,
}); // a global bunyan logger
// const serverkey = process.env.FCM_KEY || keys.server;
const testMarkovFile = './corpus/test.txt';
const testMarkov = new MarkovChain(fs.readFileSync(testMarkovFile, 'utf8'));
// Inputs
const CLIENT_API_KEY = 'clientApiKey';
const SERVER_KEY = 'serverKey';
const ASSER_KEY = 'key_asser';
const CEZANNE_KEY = 'key_cezanne';
const JLIN_KEY = 'key_jlin';
const LYLA_KEY = 'key_lyla';
const NIKITA_KEY = 'key_nikita';
const RANDOM_KEY = 'key_random';
const TEST_KEY = 'key_test';
const courseDevelopersDict = {};
const courseDeveloperKeys = [ASSER_KEY, CEZANNE_KEY, JLIN_KEY, LYLA_KEY, NIKITA_KEY];
// Queue
const Q_SIZE = 1000;
const INITIAL_Q_SIZE = 20;
const inMemoryMessageQ = [];
inMemoryMessageQ.addMessage = function(message) {
// Add the message and then removes old messages until it's down to the correct size
if (this.unshift(message) > Q_SIZE) {
while (this.length > Q_SIZE) this.pop();
}
};
inMemoryMessageQ.generateMessage = function(courseDeveloper, time) {
const cdObject = courseDevelopersDict[courseDeveloper];
log.info(`course developer is ${cdObject}`);
const author = `TheReal${cdObject.name}`;
let message = cdObject.markovChain.start(useUpperCase).end().process();
// if the message length is greater than 140, reduce the length of the message to less than that
if (message.length > 140) {
message = message.slice(0, message.lastIndexOf(' ', 140));
}
const messageObject = {
author: author,
message: message,
date: time,
authorKey: cdObject.key,
};
// Add message to the queue
inMemoryMessageQ.addMessage(messageObject);
return messageObject;
};
inMemoryMessageQ.addInitialVals = function() {
for (let i = 0; i < INITIAL_Q_SIZE; i++) {
// Random time from now till two days before now
this.generateMessage(getRandomCD(), Date.now() - getRandomInt(0, 1000*60*60*48));
}
this.sort((a, b) => {
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return new Date(b.date) - new Date(a.date);
});
};
// Classes
function CourseDeveloper(key) {
this.key = key;
let nameLower = (key.replace('key_', ''));
let nameUpper = nameLower.charAt(0).toUpperCase() + nameLower.slice(1);
if (key === JLIN_KEY) {
nameUpper = 'JLin';
}
this.name = nameUpper;
const markovFile = `./corpus/${nameLower}.txt`;
this.markovChain = new MarkovChain(fs.readFileSync(markovFile, 'utf8')); // a markhov chain of all words in the corpus file
};
// Global Functions
/*
* Returns a random integer between min (inclusive) and max (inclusive)
* Using Math.round() will give you a non-uniform distribution!
*/
const getRandomInt = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
const getRandomCD = () => {
const index = Math.floor(Math.random()*courseDeveloperKeys.length);
return courseDeveloperKeys[index];
};
const useUpperCase = (wordList) => {
const tmpList = Object.keys(wordList).filter(function(word) {
return word[0] >= 'A' && word[0] <= 'Z';
});
return tmpList[~~(Math.random()*tmpList.length)];
};
const setupCDObjects = () => {
log.info(`course dev keys is ${courseDeveloperKeys}`);
log.info(`length is ${courseDeveloperKeys.length}`);
for (let i = 0; i < courseDeveloperKeys.length; i++) {
const key = courseDeveloperKeys[i];
log.info(`on key ${key}`);
courseDevelopersDict[key] = new CourseDeveloper(key);
}
// need to add a bunch of initial Q messages
inMemoryMessageQ.addInitialVals();
// log.info("Object setup run and finished with the following dict " + JSON.stringify(courseDevelopersDict));
};
const sendTestFCMMessage = (clientToken, serverKey) => {
const testMessage = testMarkov.start(useUpperCase).end().process();
const message = {
to: clientToken,
data: { // you can send only notification or only data(or include both)
author: 'TestAccount',
message: testMessage,
date: Date.now(),
authorKey: TEST_KEY,
},
};
const fcm = new FCM(serverKey); // the Firebase Cloud Messaging connection
fcm.send(message, (err, response) => {
log.debug('Tried to send message: %s', message);
if (err) {
log.error({err: err}, 'Operation went boom: %s', err);
} else {
log.info(
{response: response},
'Successfully sent with response: %s', response
);
}
});
return message;
};
const sendFCMMessage = (courseDeveloper, serverKey) => {
// check who the course developer is, if it's random, then send to the api key
if (courseDeveloper === RANDOM_KEY) {
// get a random course developer
courseDeveloper = getRandomCD();
}
const sendTo = '/topics/' + courseDeveloper;
const markovMessage = inMemoryMessageQ.generateMessage(courseDeveloper, Date.now());
log.info(`sendTo is ${sendTo}`);
log.info(`markovMessage is ${markovMessage}`);
const shortMessage = markovMessage.message;
if (shortMessage.length > 30) {
shortMessage.slice(0, 30) + '…';
}
const message = {
to: sendTo,
data: { // you can send only notification or only data(or include both)
author: markovMessage.author,
message: markovMessage.message,
date: markovMessage.date,
authorKey: markovMessage.authorKey,
},
};
// log.debug('creating an fcm connection using key %s', serverkey);
const fcm = new FCM(serverKey); // the Firebase Cloud Messaging connection
fcm.send(message, (err, response) => {
log.debug('Tried to send message: %s', message);
if (err) {
log.error({err: err}, 'Operation went boom: %s', err);
} else {
log.info({response: response}, 'Successfully sent with response: %s', response);
}
});
return message;
};
// CD object configuration
setupCDObjects();
// ExpressJS Configuration
app.engine('.hbs', exphbs({
defaultLayout: 'main',
extname: '.hbs',
layoutsDir: path.join(__dirname, 'views/layouts'),
}));
app.set('view engine', '.hbs');
app.set('views', path.join(__dirname, 'views'));
app.get('/health-check', (request, response) => {
response.json({status: 'OK'});
});
app.get('/', (request, response) => {
response.render('home', {
clientToken: CLIENT_API_KEY,
serverKey: SERVER_KEY,
asser_key: ASSER_KEY,
cezanne_key: CEZANNE_KEY,
jlin_key: JLIN_KEY,
lyla_key: LYLA_KEY,
nikita_key: NIKITA_KEY,
random_key: RANDOM_KEY,
});
});
app.get('/messages', (req, res) => {
res.json(inMemoryMessageQ);
});
app.post('/dm', (req, res) => {
log.debug({req: req}, 'Request body was %s', req.body.serverKey);
const message = sendTestFCMMessage(req.body.clientApiKey, req.body.serverKey); // How can I set this to CLIENT_API_KEY's value instead of hardcoding?
res.send(message);
});
app.post('/cd', (req, res) => {
log.debug({req: req}, 'Request body was %s', req.body);
const message = sendFCMMessage(req.body.groupCD, req.body.serverKey); // How can I set this to CLIENT_API_KEY's value instead of hardcoding?
res.send(message);
});
const port = process.env.PORT || 3000;
app.listen(port, (err) => {
console.log(`logging at level ${bunyan.nameFromLevel[log.level()]}`);
if (log.level() >= bunyan.DEBUG) {
log.warn('At logging levels debug or higher, API keys will be logged. You have been warned');
}
log.info(`Server started on ${port}`);
});