<commit_before>/* SBHS-Timetable-Node: Countdown and timetable all at once (NodeJS app).
 * Copyright (C) 2014 <NAME>, <NAME>
 *
 * This file is part of SBHS-Timetable-Node.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/* global sessions */

var qs = require('querystring'),
	request = require('request'),
	auth = require('./auth.js'),
	etag = require('./etag.js'),
	schoolday = require('./schoolday.js'),
	util = require('util');

var bellsCache = {},
	normalBells = {
		'mon': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Lunch 1','time':'11:10'},{'bell':'Lunch 2','time':'11:30'},{'bell':'3','time':'11:50'},{'bell':'Transition','time':'12:50'},
			{'bell':'4','time':'12:55'},{'bell':'Recess','time':'13:55'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'tue': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Lunch 1','time':'11:10'},{'bell':'Lunch 2','time':'11:30'},{'bell':'3','time':'11:50'},{'bell':'Transition','time':'12:50'},
			{'bell':'4','time':'12:55'},{'bell':'Recess','time':'13:55'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'wed': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Recess','time':'11:10'},{'bell':'3','time':'11:30'},{'bell':'Lunch 1','time':'12:30'},{'bell':'Lunch 2','time':'12:50'},
			{'bell':'4','time':'13:10'},{'bell':'Transition','time':'14:10'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'thu': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Recess','time':'11:10'},{'bell':'3','time':'11:30'},{'bell':'Lunch 1','time':'12:30'},{'bell':'Lunch 2','time':'12:50'},
			{'bell':'4','time':'13:10'},{'bell':'Transition','time':'14:10'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'fri': [
			{'bell':'Roll Call','time':'09:25'},{'bell':'1','time':'09:30'},{'bell':'Transition','time':'10:25'},{'bell':'2','time':'10:30'},
			{'bell':'Recess','time':'11:25'},{'bell':'3','time':'11:45'},{'bell':'Lunch 1','time':'12:40'},{'bell':'Lunch 2','time':'13:00'},
			{'bell':'4','time':'13:20'},{'bell':'Transition','time':'14:15'},{'bell':'5','time':'14:20'},{'bell':'End of Day','time':'15:15'}
		]
	};

function getSHSAPI(path, token, params, post, cb) {
	'use strict';
	var req,
		b = function(err, z, d) {
			if (err || z === undefined || z === null) {
				cb({'error': err, 'status': 500, '_fetchTime': Math.floor(Date.now()/1000)});
				return;
			}
			if (z.statusCode != 200) {
				console.error('[api_error] error while getting ' + path + ': HTTP status ' + z.statusCode);
				cb({'error': 'not ok', 'status': z.statusCode});
				return;
			}
			var responded = false;
			var obj = {};
			try {
				obj = JSON.parse(d);
				obj._fetchTime = Math.floor(Date.now() / 1000);
				cb(obj);
				responded = true;
			} catch (e) {
				console.error('[api_error] incomplete json response - ' + d, 'node err was', err);
				console.error(e.stack);
				cb({'error': 'incomplete json', 'status': 500, '_fetchTime': Math.floor(Date.now()/1000)});
			}
		};
	if (typeof params !== 'object') {
		params = {};
	}
	params.access_token = token;
	if (!post) {
		req = request('https://student.sbhs.net.au' + path + '?' + qs.stringify(params), b);
	} else {
		req = request.post({
			'uri': 'https://student.sbhs.net.au' + path,
			'form': params
		}, b);
	}
}

var apis = {
	// raw API requests
	'daytimetable.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var params = {};
			if ('date' in opts && opts.date !== '') {
				params.date = opts.date;
			}
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/timetable/daytimetable.json', sess.accessToken, params, false, cb);
		});
	},
	'timetable.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var params = {};
			if ('date' in opts && opts.date !== '') {
				params.date = opts.data;
			}
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/timetable/timetable.json', sess.accessToken, params, false, cb);
		});
	},
	'dailynews.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var params = {};
			if ('date' in opts && opts.date !== '') {
				params.date = opts.date;
			}
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/dailynews/list.json', sess.accessToken, params, false, cb);
		});
	},
	'participation.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/details/participation.json', sess.accessToken, {}, false, cb);
		});
	},
	'userinfo.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/details/userinfo.json', sess.accessToken, {}, false, cb);
		});
	},
	'barcodenews.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/barcodenews/list.json', sess.accessToken, {}, false, cb);
		});
	},
	'diarycalendar.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/diarycalendar/events.json', sess.accessToken, {}, false, cb);
		});
	},
	'calendar/days.json': function(opts,sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/calendar/days.json', sess.accessToken, opts, false, cb);
		});
	},
	'calendar/terms.json': function(opts,sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/calendar/terms.json', sess.accessToken, opts, false, cb);
		});
	},
	// API requests that are actually used by the client (easier to display etc)
	'belltimes': function(opts, sessID, cb) {
		'use strict';
		var d = new Date();
		var dateStr = d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate();
		if ('date' in opts) {
			opts.date = opts.date.replace('-0', '-');
		} else {
			opts.date = d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate();
		}
		if (opts.date in  bellsCache && opts.date === dateStr && d.getHours() > 9) {
			cb(bellsCache[opts.date]);
			return;
		}
		getSHSAPI('/api/timetable/bells.json', '', opts, false, function(obj) {
			if (obj.error || obj.status === 'Error') {
				cb({ json: JSON.stringify(obj), etag: null });
				return;
			}
			var shortDow = obj.day.toLowerCase().substr(0,3);
			//if (obj.bellsAltered) {
				var normal = normalBells[shortDow];
				for (var i in obj.bells) {
					obj.bells[i].index = Number(i);
					if (normal[i].time != obj.bells[i].time) {
						obj.bells[i].different = true;
						obj.bells[i].normally = normal[i].time;
					}
				}
			//}
			bellsCache[opts.date] = obj;
			cb(bellsCache[opts.date]);
		});<commit_msg>api: bugfixes<commit_after>/* SBHS-Timetable-Node: Countdown and timetable all at once (NodeJS app).
 * Copyright (C) 2014 <NAME>, <NAME>
 *
 * This file is part of SBHS-Timetable-Node.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/* global sessions */

var qs = require('querystring'),
	request = require('request'),
	auth = require('./auth.js'),
	etag = require('./etag.js'),
	schoolday = require('./schoolday.js'),
	util = require('util');

var bellsCache = {},
	normalBells = {
		'mon': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Lunch 1','time':'11:10'},{'bell':'Lunch 2','time':'11:30'},{'bell':'3','time':'11:50'},{'bell':'Transition','time':'12:50'},
			{'bell':'4','time':'12:55'},{'bell':'Recess','time':'13:55'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'tue': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Lunch 1','time':'11:10'},{'bell':'Lunch 2','time':'11:30'},{'bell':'3','time':'11:50'},{'bell':'Transition','time':'12:50'},
			{'bell':'4','time':'12:55'},{'bell':'Recess','time':'13:55'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'wed': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Recess','time':'11:10'},{'bell':'3','time':'11:30'},{'bell':'Lunch 1','time':'12:30'},{'bell':'Lunch 2','time':'12:50'},
			{'bell':'4','time':'13:10'},{'bell':'Transition','time':'14:10'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'thu': [
			{'bell':'Roll Call','time':'09:00'},{'bell':'1','time':'09:05'},{'bell':'Transition','time':'10:05'},{'bell':'2','time':'10:10'},
			{'bell':'Recess','time':'11:10'},{'bell':'3','time':'11:30'},{'bell':'Lunch 1','time':'12:30'},{'bell':'Lunch 2','time':'12:50'},
			{'bell':'4','time':'13:10'},{'bell':'Transition','time':'14:10'},{'bell':'5','time':'14:15'},{'bell':'End of Day','time':'15:15'}
		],
		'fri': [
			{'bell':'Roll Call','time':'09:25'},{'bell':'1','time':'09:30'},{'bell':'Transition','time':'10:25'},{'bell':'2','time':'10:30'},
			{'bell':'Recess','time':'11:25'},{'bell':'3','time':'11:45'},{'bell':'Lunch 1','time':'12:40'},{'bell':'Lunch 2','time':'13:00'},
			{'bell':'4','time':'13:20'},{'bell':'Transition','time':'14:15'},{'bell':'5','time':'14:20'},{'bell':'End of Day','time':'15:15'}
		]
	};

function getSHSAPI(path, token, params, post, cb) {
	'use strict';
	var req,
		b = function(err, z, d) {
			if (err || z === undefined || z === null) {
				cb({'error': err, 'status': 500, '_fetchTime': Math.floor(Date.now()/1000)});
				return;
			}
			if (z.statusCode != 200) {
				console.error('[api_error] error while getting ' + path + ': HTTP status ' + z.statusCode);
				cb({'error': 'not ok', 'status': z.statusCode});
				return;
			}
			var responded = false;
			var obj = {};
			try {
				obj = JSON.parse(d);
				obj._fetchTime = Math.floor(Date.now() / 1000);
				cb(obj);
				responded = true;
			} catch (e) {
				console.error('[api_error] incomplete json response - ' + d, 'node err was', err);
				console.error(e.stack);
				cb({'error': 'incomplete json', 'status': 500, '_fetchTime': Math.floor(Date.now()/1000)});
			}
		};
	if (typeof params !== 'object') {
		params = {};
	}
	params.access_token = token;
	if (!post) {
		req = request('https://student.sbhs.net.au' + path + '?' + qs.stringify(params), b);
	} else {
		req = request.post({
			'uri': 'https://student.sbhs.net.au' + path,
			'form': params
		}, b);
	}
}

var apis = {
	// raw API requests
	'daytimetable.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var params = {};
			if ('date' in opts && opts.date !== '') {
				params.date = opts.date;
			}
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/timetable/daytimetable.json', sess.accessToken, params, false, cb);
		});
	},
	'timetable.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var params = {};
			if ('date' in opts && opts.date !== '') {
				params.date = opts.data;
			}
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/timetable/timetable.json', sess.accessToken, params, false, cb);
		});
	},
	'dailynews.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var params = {};
			if ('date' in opts && opts.date !== '') {
				params.date = opts.date;
			}
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/dailynews/list.json', sess.accessToken, params, false, cb);
		});
	},
	'participation.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/details/participation.json', sess.accessToken, {}, false, cb);
		});
	},
	'userinfo.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/details/userinfo.json', sess.accessToken, {}, false, cb);
		});
	},
	'barcodenews.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/barcodenews/list.json', sess.accessToken, {}, false, cb);
		});
	},
	'diarycalendar.json': function(opts, sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/diarycalendar/events.json', sess.accessToken, {}, false, cb);
		});
	},
	'calendar/days.json': function(opts,sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/calendar/days.json', sess.accessToken, opts, false, cb);
		});
	},
	'calendar/terms.json': function(opts,sessID, cb) {
		'use strict';
		auth.refreshTokenIfNeeded(sessID, function() {
			var sess = session.getSessionData(sessID);
			getSHSAPI('/api/calendar/terms.json', sess.accessToken, opts, false, cb);
		});
	},
	// API requests that are actually used by the client (easier to display etc)
	'belltimes': function(opts, sessID, cb) {
		'use strict';
		var d = new Date();
		var dateStr = d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate();
		if ('date' in opts) {
			opts.date = opts.date.replace('-0', '-');
		} else {
			opts.date = d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate();
		}
		if (opts.date in  bellsCache && opts.date === dateStr && d.getHours() > 9) {
			cb(bellsCache[opts.date]);
			return;
		}
		getSHSAPI('/api/timetable/bells.json', '', opts, false, function(obj) {
			if (obj.error || obj.status === 'Error') {
				cb(obj);
				return;
			}
			var shortDow = obj.day.toLowerCase().substr(0,3);
			//if (obj.bellsAltered) {
				var normal = normalBells[shortDow];
				for (var i in obj.bells) {
					obj.bells[i].index = Number(i);
					if (normal[i].time != obj.bells[i].time) {
						obj.bells[i].different = true;
						obj.bells[i].normally = normal[i].time;
					}
				}
			//}
			bellsCache[opts.date] = obj;
			cb(bellsCache[opts.date]);
		});
{'fraction_non_alphanumeric': 0.24560857107262987, 'fraction_numerical': 0.038245920019932725, 'mean_word_length': 4.64521800281294, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3332565', 'n_tokens_mistral': 6886, 'n_tokens_neox': 5799, 'n_words': 1085}