-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmagbot3.js
executable file
·267 lines (257 loc) · 10 KB
/
magbot3.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
/*
* -= Magbot3 =-
* Sj3rd & 7kasper
* Licensed under MIT
* -= Magbot3 =-
*/
'use strict';
// Imports
const winston = require('winston');
require('winston-daily-rotate-file');
winston.loggers.add('main', {
// level: 'alert',
// level: 'info',
level: 'silly',
format: winston.format.simple(),
transports: [
new winston.transports.Console({
// level: 'silly'
}),
new winston.transports.DailyRotateFile({
dirname: 'logs',
filename: 'magbot-%DATE%.log',
zippedArchive: true
})
]
});
const http = require('http');
const MagisterAuth = require('./lib/magister/authcode.function');
const User = require('./lib/magbot/User');
const secret = require('./secret');
const oAuth = [
'404820325442-ivr8klgohd73pm2lme8bmpc241prn03c.apps.googleusercontent.com',
secret.clientsecret,
'https://magbot.nl'
];
const log = winston.loggers.get('main');
/**
* @class magbot3
* @classdesc
* This file is an executable and runs the magbot program.
* First the webserver is started, afterwards both
* sync and purge functions are scheduled for the first time.
* This program is started with pm2 to keep it alive forever.
*/
const next = Math.floor(Math.random() * 100000);
log.info(`Starting first sync in ${next} and first purge in ${next * 2} millis...`);
setTimeout(sync, next);
//setTimeout(purge, next * 20);
http.createServer((req, res) => {
// Set CORS headers
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Origin', "https://magbot.nl");
res.setHeader('Access-Control-Request-Method', 'OPTIONS, POST');
res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, POST');
res.setHeader('Access-Control-Allow-Headers', 'code, school, username, password, ' +
'fullcalendar, splitcalendars, simplesummary, simpleshowteacher, showoutages, ' +
'remindermin, specialemailreminder, specialdayreminder, showschoolhour, specialcolors');
// Handle normal request
if ('code' in req.headers) {
MagisterAuth()
// .then(mAuth => console.dir(req.headers))
.then(mAuth => User.registerUpdate(oAuth, mAuth, req.headers))
.then(user => !user.isNew())
.then(updated => res.end(updated ? 'success: user updated' : 'success: user created'))
.catch(err => { log.error(err.toString()); res.writeHead(500); res.end('error: ' + err.toString()); });
// If not requesting properly show 'nice' welcome :)
} else {
res.end('MAGBOT API');
}
}).listen(7070);
/**
* Main function of magbot.
* This function calls itself to stay alive.
*/
async function sync() {
log.debug(`Going for sync...`);
try {
// Get the current magister authcode before syncing users.
let mAuth = await MagisterAuth();
// Get all users from DB & run over them.
let users = shuffleArray(await User.spot({isdisabled: false}).fetchAll());
log.info(`Syncing ${users.length} users...`);
for (let user of users) {
try {
// Log the user in.
await user.login(oAuth, mAuth);
// Fixy calendars (non - force).
await user.setupCalendars();
// Sync the actual appointments.
await user.syncCalendars();
// Wait some time (~0-5 seconds).
await sleep(Math.floor(Math.random() * 5000));
} catch(err) {
try {
user.log.warn(`Error syncing user! `, {err: err.toString()});
} catch(errr) {
log.error(`LOGGING ERROR! `, errr, {err: err.toString()});
}
}
}
} catch (err) {
log.error(`MAJOR SYNC ERROR!`, err);
}
const next = scheduleTime();
log.info(`Going for next sync in ${next} millis...`);
setTimeout(sync, next);
}
/**
* Purge function. Soft-deletes all users that have
* no calendars left or can't log in anymore.
* Note that soft-deleted users are still alowed
* to update and reclaim their accounts.
*/
async function purge() {
let purged = 0;
try {
// Get the current magister authcode before purging users.
let mAuth = await MagisterAuth();
// First check with a user we know to work to see we don't
// purge users for google and magister being unreachable or so.
let checkUser = await User.spot({email: '[email protected]'}).fetch();
await checkUser.login(oAuth, mAuth); // check if checkuser can login.
// Get all users from DB & run over them.
let users = shuffleArray(await User.spot({isdisabled: false}).fetchAll());
log.info(`Purging ${users.length} users...`);
// Go through all users and purge if necessary.
for (let user of users) {
try {
// Try logging in.
await user.login(oAuth, mAuth);
// Sleep a bit (0-10 sec) after logging in, to ease magister.
await sleep(Math.floor(Math.random() * 5000));
// If all tokens etc are valid and we log in,
// then we actually get here. Check if there
// are calendars left and if so, skip the purge.
if (user.hasCalendars()) continue;
user.log.warn(`Purging user for not having calendars left!`, {email: user.get('email')});
} catch(err) {
user.log.warn(`Purging user for `, err);
}
try {
// If possible, warn user they are being placed on non-active.
await user.calendar.events.insert({
calendarId: 'primary',
sendUpdates: 'all',
resource: {
summary: 'MAGBOT: Actie vereist! Lees mij!',
description: '<i>Volgens mij klopt er iets niet meer :(</i><br/>' +
'<br/>'+
'Uw magister informatie is veranderd of u heeft ' +
'bewust alle magbot agendas verwijderd.<br/>' +
'<b>Bij deze wordt uw account op non-actief gezet '+
'en zal niet automatisch meer werken!</b><br/>' +
'<i>Heractiveer uw account op ' +
'<a href="https://magbot.nl/signup/ ">magbot.nl</a></i><br/>' +
'<br/>' +
'Als u uw account niet meer wil hebben, onderneem dan ' +
'geen actie.<br/>' +
'<br/>' +
'Stuur bij vragen gerust een mailtje naar ' +
'<a href="mailto:[email protected]">[email protected]</a><br/>',
// Set start and end two days from now at 0900-0930.
start: {
dateTime: new Date(new Date(Date.now() + 1728e5)
.setHours( 9,0,0,0))
},
end: {
dateTime: new Date(new Date(Date.now() + 1728e5)
.setHours(17,0,0,0))
},
location: 'internet',
colorId: 11, // Nice red color
reminders: {
useDefault: false,
overrides: [
{ method: 'email', minutes: 1440 },
{ method: 'popup', minutes: 1440 },
{ method: 'popup', minutes: 5 }
]
}
}
});
user.log.info(`Notified user of purge!`);
} catch (err) {
user.log.error(`Error warning to-be purged user! `, {err: err.toString()})
}
try {
user.log.info(`Disabling!`)
user.set('isdisabled', true);
await user.update();
log.info(`PURGED ${purged++} USERS...`);
} catch (err) {
log.error(`MAJOR PURGE ERROR `, err);
}
}
} catch (err) { log.error('Purging error! ', err); }
const next = scheduleTime() * 20;
log.info(`Going for next purge in ${next} millis...`);
setTimeout(purge, next);
}
/**
* Simple function to await some time.
* @param {number} millis
*/
function sleep(millis) {
return new Promise(resolve => setTimeout(resolve, millis));
}
/**
* Gets the the right amount of time to
* wait before scheduling a new sync operation.
* Remember a sync operation takes about
* count(users) * 5 seconds to complete.
* Meaning 500 users gives ~83 minutes of sync time.
* After this, the randomised offset is applied
* before next sync.
*
* Weekdays:
* 0000-0700: ~every 60 min
* 0700-1700: ~every 30 min
* 1700-2400: ~every 60 min
* Weekend:
* 0000-2400: ~every 120 min
* Then we add a bit of randomness to not
* load the servers at regular intervals.
*
* @returns (in ms) the amount of time to wait
*/
function scheduleTime() {
let time = new Date();
// Get random time between 0 and 10 minutes
let rand = Math.floor(Math.random() * 10 * 60 * 1000);
// Weekends
if (time.getDay() === 0 || time.getDay() === 7) {
return 115 * 60 * 1000 + rand;
}
// Non-working hours
else if (time.getHours() < 7 || time.getHours() > 17) {
return 55 * 60 * 1000 + rand;
}
// Working (school) hours
else {
return 25 * 60 * 1000 + rand;
}
}
/**
* Shuffles array.
* @param {array} arr
* @returns - the array in random order.
*/
function shuffleArray(arr) {
const newArr = arr.slice()
for (let i = newArr.length - 1; i > 0; i--) {
const rand = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
}
return newArr
};