MediaWiki:Gadget-eventslist.js

Revision as of 15:06, 9 June 2020 by Banri (talk | contribs) (Created page with "//<nowiki> * * Interface for events * * @version 1.1 * @author JaydenKieran * @author Elessar2 * @author ThePsionic *: /*global jQuery, mediaWiki, mw, gswiki, rs...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
//<nowiki>
/**
 * Interface for events
 *
 * @version 1.1
 * @author JaydenKieran
 * @author Elessar2
 * @author ThePsionic
 * 
 */

/*global jQuery, mediaWiki, mw, gswiki, rs, OO, moment, ga */
'use strict';

// TODO: Add castaways?, circus city, Jewels of the Elid,
// TODO: Cache Vos as well

;(function($, mw, rs){
	var portletLink,
		$popup,
		popup,
		$content,
		settingsForm,
		notifsForm,
		api,
		et_events,
		merchstock,
		notiftimer,
		notifpopup,
		$notifs,
		formConst = false,
		formMade = false;

	// localStorage key
	var localKey = 'gsw-events-prefs',
		localLast = 'gsw-events-last',
		vosurl = 'https://chisel.weirdgloop.org/api/runescape/vos/history',
		simplevosurl = 'https://chisel.weirdgloop.org/api/runescape/vos';

	// Dailies
	var simple_dailies = [
		{ name:'Big Chinchompa', id:'bigchin', link:'/w/Big_Chinchompa', length:20, repeat:60, offset:30, from:'hour', img:'/images/6/63/Nyriki%27s_portal.png' },
		{ name:'Guthixian Caches', id:'guthcache', link:'/w/Guthixian_Cache', length:10, repeat:60, offset:0, from:'hour', img:'/images/thumb/2/23/Player_automaton.png/600px-Player_automaton.png' },
		{ name:'Guthixian Caches (boost)', id:'guthcacheb', link:'/w/Guthixian_Cache', length:10, repeat:(60*3), offset:0, from:'day', img:'/images/8/8e/Confused_automaton.png' },
		{ name:'Sinkholes', id:'sinkhole', link:'/w/Sinkholes', length:25, repeat:60, offset:30, from:'hour', img:'/images/5/5c/Sinkhole_entrance.png' },
		{ name:'Demon Flash Mob', id:'demonmob', link:'/w/Demon_Flash_Mob', length:5, repeat:60, offset:0, from:'hour', img:'/images/1/1f/Agrith_Naar_chathead.png' },
		{ name:'Fish Flingers', id:'fishflin', link:'/w/Fish_Flingers', length:10, repeat:20, offset:0, from:'hour', img:'/images/thumb/c/cb/Fish_Flingers_logo.png/200px-Fish_Flingers_logo.png' },
		{ name:'Supply Run', id:'goebiesup', link:'/w/Supply_run', length:25, repeat:(60 * 12), offset:0, from:'day', img:'/images/9/9e/Goebie_chathead.png' },
		{ name:'Wilderness Warbands', id:'wildwar', link:'/w/Wilderness_Warbands', length:10, repeat:(60 * 7), offset:(60 * 2), from:'isoWeek', img:'/images/5/57/Wilderness_Warbands_emblem.png' },
	];
	
		var self = {
		// Settings
		settings: {
			daily_sort: 'by time',
			notifs: false,
			nobrownotifs: false,
			notiftype: {},
			raven: true,
			et_events: false,
			num_events: 3,
			spots: true,
			mg_spotlight: true,
			dd_spotlight: true,
			ed_spotlight: true,
			show_next: false,
			pvm: false,
			//tms: false,
			//pof: false,
			merch: true,
			farm: true,
			//etlookup: '1970-01-01T01:00:00+00:00',
			//events: []
			dcache: '1970-01-01T01:00:00+00:00',
			cache: {
				events: [],
				tms: {},
				tmsTom: {} 
			}
		},

		// Using service worker notifications?
		swnotif: false,

		// Last times (for constant updating)
		updatetimer: 0,
		nextvos: '1970-01-01T01:00:00+00:00',

		// Latest rotations (for notifications)
		lastRot: {
			time: '1970-01-01T01:00:00+00:00',
			wreset: '1970-01-01T01:00:00+00:00',
			mreset: '1970-01-01T01:00:00+00:00',
			vos: '',
			raven: '',
			tms: '',
			vorago: 99,
			araxxor: 99,
			rots: 99,
			mgspot: 99,
			edspot: 99,
			ddspot: 99
		},

		/**
		 * Starts the events gadget
		 * @return {undefined}
		 */
		init: function () {
			mw.log('Starting Events Interface');
			api = new mw.Api();
			
			// setup moment library
			moment.locale('en-events', {
			    relativeTime : {
			    	parentLocale: "en",
			        future: "in %s",
			        past:   "%s ago",
			        s:  "secs",
			        m:  "1m",
			        mm: "%dm",
			        h:  "1h",
			        hh: "%dh",
			        d:  "1d",
			        dd: "%dd",
			        M:  "1mth",
			        MM: "%dmths",
			        y:  "1yr",
			        yy: "%dyr"
			    }
			});

			// Event team events
			et_events = [];
			// Travelling merchant stock
			merchstock = { tod:[], tmr:[] };

			// Check browser supports notifications
			self.supportNotif = rs.canSendBrowserNotifs();
			// TODO: Check if using service worker notifications

			portletLink = mw.util.addPortletLink(
				'p-personal',
				'',
				'',
				'pt-events',
				'Events',
				null,
				$('#pt-userpage, #pt-anonuserpage')
			);

			$(portletLink).find('a').addClass('oo-ui-icon-calendar').click(function(e) {
				e.preventDefault();
				var openPopup = function() {
					mw.log('Open events popup');
					self.loadSettings().then(self.loadEvents).then( self.updateAll(), self.updateAll() );
				
					// Disable browser notifications settings if unavailable
					// Done here to avoid a race condition where we disable it and save to localStorage
					// before we actually load the settings
					if (!self.supportNotif) {
						notifsForm.browsnotif.setValue(true);
						notifsForm.browsnotif.setDisabled(true);
					}

					// Google analytics tracker
					if (typeof ga === 'function') {
						ga('gtag_UA_126479006_1.send', 'event', 'Gadget-events', 'Open', 'Normal');
					}
					
					$('.gsw-events-popup .events-settings').removeClass('slideout').addClass('oo-ui-element-hidden');
					popup.toggle();
				};
				if (!formMade && !formConst) {
					mw.log('Initialise events popup');
					formConst = true;
					mw.loader.using(['oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets']).then(self.initInt).then(openPopup);
				} else if (!formConst) {
					openPopup();
				} else {
					mw.log('Waiting for initialisation to finish');
				}
			});

			// Load settings and events from localStorage or via api on page load to handle notifs
			self.loadSettings().then(self.loadEvents).then( function () {
				if (self.settings.notifs && !self.swnotif) {
					self.notifs();
					notiftimer = setInterval(self.notifs, 300000);
				}
			}, function () {
				console.warn('Error loading events settings!');
			});
		},

		/**
		 * Initialises the interface (popup and forms)
		 * @return {Promise}
		 */
		initInt: function () {
			mw.log('Initialising...');
			return new Promise( function (resolve,reject) {
				self.initPopup();
				self.initSettingsForm();
				self.initNotifsForm();

				$popup = $('<div>').append(
					$content,
					settingsForm.$form
				);
				settingsForm.$form.find('.settings-container').append( notifsForm.$form );

				popup = new OO.ui.PopupWidget({
					$content: $popup,
					$floatableContainer: $('#pt-events'),
					autoClose: true,
					classes: ['gsw-events-popup oo-ui-labelElement-invisible'],
					hideWhenOutOfView: (rs.isUsingStickyHeader() ? true : false),
					invisibleLabel: true,
					label: 'Events',
					width: 700
				});
				$('body').append(popup.$element);
				popup.on('toggle', function (state) {
					// Start constant update
					clearInterval(self.updatetimer);
					if (state) {
						self.updatetimer = setInterval(self.update, 60000);
					}
				});

				// For on wiki notifications
				$('body').append($('<div>').attr('id', 'gsw-notifs-anchor'));
				var notifclose = new OO.ui.ButtonWidget({
					classes: ['oo-ui-labelElement-invisible'],
					icon: 'close',
					id:'gsw-eventsnotifs-close',
					invisibleLabel: true,
					label: 'Close',
					framed: false
				});
				notifclose.on('click', function () {
					notifpopup.toggle(false);
				});
				$notifs = $('<div>').addClass('event-notifs-container').append(
					notifclose.$element,
					$('<h3>').addClass('oo-ui-element-hidden').text('Event Notifications'),
					$('<ul>').addClass('event-notifications')
				);
				notifpopup = new OO.ui.PopupWidget({
					$content: $notifs,
					$floatableContainer: $('#gsw-notifs-anchor'),
					anchor: false,
					autoClose: true,
					classes: ['gsw-events-notifs oo-ui-labelElement-invisible'],
					hideWhenOutOfView: false,
					invisibleLabel: true,
					label: 'Events  Notifications',
					width: 450
				});
				$('body').append(notifpopup.$element);

				// Mark interface as initialised
				formMade = true;
				formConst = false;

				resolve();
			} );
		},

		/**
		 * Loads settings from browser localStorage
		 * @return {Promise}
		 */
		loadSettings: function () {
			mw.log('Loading settings');
			return new Promise( function (resolve, reject) {
				if (!rs.hasLocalStorage()) {
					console.warn('Browser does not support localStorage');
					reject();
				}

				var prefs = {};
				try {
					prefs = JSON.parse(localStorage.getItem(localKey));
				} catch (err) {
					prefs = {};
					console.warn('Error loading settings (events)');
				}
				for (var p in prefs) {
					self.settings[p] = prefs[p];
				}
				resolve();
			} );
		},

		/**
		 * Loads events team events, travelling merchant stock
		 * @return {Promise}
		 */
		loadEvents: function ( force ) {
			mw.log('Loading events, travelling merchant stock');
			if ( moment().isBefore( moment(self.settings.dcache).utc().add(1, 'days').startOf('day') ) && !force ) {
				mw.log('Less than a day since last lookup');
				et_events = self.settings.cache.events;
				merchstock = {
					tod: self.settings.cache.tms,
					tmr: self.settings.cache.tmsTom
				};
				return Promise.resolve();
			}

			// ET events
			var etapi = api.get({
				action: 'ask',
				query: '[[RuneScape:Events Team]]|?Events JSON',
				maxage: '7200',
				smaxage: '7200'
			}).then( function (ret) {
				mw.log('Events team api return:');
				mw.log(ret);
				if (ret.query.results && ret.query.results['RuneScape:Events Team'].printouts['Events JSON']) {
					ret.query.results['RuneScape:Events Team'].printouts['Events JSON'].forEach( function (ev) {
						ev = JSON.parse(ev);
						et_events.push( { name:mw.html.escape(ev.name), date:ev.time, length:Number(ev.length) } );
					});
				} else {
					mw.log('No events team events');
				}
				self.saveSetting('cache.events', et_events);
			}, function (err) {
				console.warn('Error getting Events team events');
				throw err;
			});

			// Resolve/reject all
			return Promise.all([ etapi, tmsapi, tmsTomapi ]).then( function () {
				// Success, resolve promise
				self.saveSetting('dcache', moment().format());
			}, function (err) {
				// Error, reject promise
				throw err;
			});
		},

		/**
		 * Saves a setting to localStorage
		 * @param  {string} name Name (key) of setting to save
		 * @return {boolean}  If save was successfull
		 */
		saveSetting: function (key, value) {
			mw.log('Saving setting: ' + key);
			if ( key.split('.')[0] == 'cache' ) {
				self.settings.cache[key.split('.')[1]] = value;
			} else {
				self.settings[key] = value;
			}

			if (!rs.hasLocalStorage()) {
				console.warn('Browser does not support localStorage');
				return false;
			}

			var string = JSON.stringify(self.settings);
			try {
				localStorage.setItem(localKey, string);
			} catch (err) {
				console.warn('Error saving presets to localStorage');
				return false;
			}
			return true;
		},

		/**
		 * Saves a notification setting to localStorage
		 * @param  {string} key   Name (key) of notification setting to save
		 * @param  {boolean} value Send notifications?
		 * @return {boolean}       If save was successfull
		 */
		saveNotif: function (key, value) {
			mw.log('Saving notification setting: ' + key);
			self.settings.notiftype[key] = value;

			if (!rs.hasLocalStorage()) {
				console.warn('Browser does not support localStorage');
				return false;
			}

			var string = JSON.stringify(self.settings);
			try {
				localStorage.setItem(localKey, string);
			} catch (err) {
				console.warn('Error saving presets to localStorage');
				return false;
			}
			return true;
		},

		/**
		 * Generates the events popup
		 * @return {undefined}
		 */
		initPopup: function () {
			var closeButton = new OO.ui.ButtonWidget({
				classes: ['oo-ui-labelElement-invisible'],
				icon: 'close',
				id:'gsw-events-close',
				invisibleLabel: true,
				label: 'Close',
				framed: false
			});
			closeButton.on('click', function () {
				popup.toggle(false);
			});

			var settingsButton = new OO.ui.ButtonWidget({
				classes: ['oo-ui-labelElement-invisible'],
				icon: 'advanced',
				id:'gsw-events-setbut',
				invisibleLabel: true,
				label: 'Settings',
				framed: false
			});
			settingsButton.on('click', function () {
				self.openSettings();
			});

			// Current UTC time
			var now = moment.utc();

			$content = $('<div>');
			$content
				.addClass('events-popup')
				.append(
					$('<div>')
						.addClass('header')
						.append(
							closeButton.$element,
							$('<time>')
								.addClass('cur-utc-time')
								.attr({ 'title':now.format('HH:mm D/M/Y'), 'datetime':now.format() })
								.text(now.format('HH:mm') + ' (UTC)'),
							settingsButton.$element
						),
					$('<div>')
						.addClass('top')
						.append(
							$('<div>')
								.addClass('col col-l')
								.append(
									$('<div>')
										.addClass('dailies section')
										.append(
											$('<h3>').text('Dailies'),
											$('<ul>').addClass('events-list daily-list')
										),
									$('<div>')
										.addClass('raven section')
								),
							$('<div>')
								.addClass('col col-r')
								.append(
									$('<div>')
										.addClass('resets section')
										.append(
											$('<h3>').text('Resets'),
											$('<div>')
												.addClass('event-boxes')
												.append(
													$('<div>')
														.addClass('day event-box')
														.append(
															$('<label>').addClass('by-line').text('Day'),
															$('<time>').addClass('event-name countdown-day').text('? hrs')
														),
													$('<div>')
														.addClass('week event-box')
														.append(
															$('<label>').addClass('by-line').text('Week'),
															$('<time>').addClass('event-name countdown-week').text('? days')
														),
													$('<div>')
														.addClass('month event-box')
														.append(
															$('<label>').addClass('by-line').text('Month'),
															$('<time>').addClass('event-name countdown-month').text('? days')
														)
												)
										),
									$('<div>')
										.addClass('voice section')
										.append(
											$('<h3>').html('<a href="/w/Voice_of_Seren" title="Voice of Seren">Voice of Seren</a>'),
											$('<div>')
												.addClass('VoS-time by-line')
												.text(
													'There was an error loading the Voice of Seren. Please try again.<br/>If this issue persists, please <a href="/w/RS:AR">contact an administrator</a>.'
												),
											$('<div>')
												.addClass('VoS-container event-boxes'),
											$('<div>')
												.addClass('by-line')
												.append(
													$('<span>').text('Last districts: '),
													$('<span>').addClass('VoS-last').text('not found')
												)
										)
								)
						),
					$('<div>')
						.addClass('middle events-team'),
					$('<div>')
						.addClass('middle tms-pof'),
					$('<div>')
						.addClass('middle pvm'),
					$('<div>')
						.addClass('spotlights')
						.append(
							$('<div>')
								.addClass('spotlight-spacer empty'),
							$('<div>')
								.addClass('spotlight mg-spotlight'),
							$('<div>')
								.addClass('spotlight-spacer empty'),
							$('<div>')
								.addClass('spotlight ed-spotlight'),
							$('<div>')
								.addClass('spotlight-spacer empty'),
							$('<div>')
								.addClass('spotlight dd-spotlight'),
							$('<div>')
								.addClass('spotlight-spacer')
						)
				);
		},

		/**
		 * Generates the settings popup
		 * @return {undefined}
		 */
		initSettingsForm: function () {
			settingsForm = {};

			// Back button
			settingsForm.back = new OO.ui.ButtonWidget({
				classes: ['oo-ui-labelElement-invisible'],
				icon: 'close',
				id: 'gsw-events-backbut',
				invisibleLabel: true,
				label: 'Back',
				framed: false
			});
			var closeset = function () {
				self.updateTimes();
				self.updateVos();
				self.updateSpots();
				$('.gsw-events-popup .events-settings').addClass('slideout');
				setTimeout( function () {
					$('.gsw-events-popup .events-settings').removeClass('slideout').addClass('oo-ui-element-hidden');
				}, 500);
			};
			settingsForm.back.on('click', closeset);

			// Title
			settingsForm.$title = $('<h3>').text('Options');

			// Dailies sort selector
			settingsForm.sort = new OO.ui.DropdownInputWidget({
				classes: 'sort-selector',
				options: [
					{ data:'by time', label:'by time' },
					{ data:'by name', label:'by name' }
				],
			});
			settingsForm.sort.setValue(self.settings.daily_sort);
			settingsForm.sortLay = new OO.ui.FieldLayout(settingsForm.sort, {
				label: 'Sorting'
			});
			settingsForm.sort.on('change', self.saveSetting, ['daily_sort']);

			// Show browser notifications
			settingsForm.notifs = new OO.ui.ToggleSwitchWidget({
				title: 'Recieve browser notifications',
				value: self.settings.notifs
			});
			settingsForm.notifs.on('change', function (val) {
				self.saveSetting('notifs', val);
				if (val && !self.swnotif) {
					window.clearInterval(notiftimer);
					self.notifs();
					notiftimer = setInterval(self.notifs, 300000);
				} else {
					window.clearInterval(notiftimer);
				}
			});
			settingsForm.notifSets = new OO.ui.ButtonWidget({
				classes: ['oo-ui-labelElement-invisible'],
				icon: 'advanced',
				invisibleLabel: true,
				label: 'Notification Settings',
				framed: false
			});
			settingsForm.notifSets.on('click', function () {
				self.openNotifs();
			});
			settingsForm.notifsLay = new OO.ui.ActionFieldLayout(settingsForm.notifs, settingsForm.notifSets, {
				label: 'Notifications',
				help: 'Enable notifications for configured events. Currently in development.'
			});

			// Show events team events
			settingsForm.events = new OO.ui.ToggleSwitchWidget({
				title: 'Show events team events',
				value: self.settings.et_events
			});
			settingsForm.eventsLay = new OO.ui.FieldLayout(settingsForm.events, { label: 'Events Team' });
			settingsForm.events.on('change', function (val) {
				self.saveSetting('et_events', val);
				settingsForm.eventsNum.setDisabled(!val);
				settingsForm.eventsNumLay.toggle(val);
			});

			// Number of events to display
			settingsForm.eventsNum = new OO.ui.NumberInputWidget({
				min: 0,
				required: true,
				step: 1,
				validate: 'integer',
				value: self.settings.num_events
			});
			settingsForm.eventsNumLay = new OO.ui.FieldLayout(settingsForm.eventsNum, {
				label: 'Number of Events',
				help: 'Number of Events Team events to display, use 0 for infinite'
			});
			settingsForm.eventsNum.on('change', function (val) {
				val = parseInt(val, 10);
				if (isNaN(val)) { val = 1; }
				self.saveSetting('num_events', val);
			});
			// Disable if events turned off
			if (!self.settings.et_events) {
				settingsForm.eventsNum.setDisabled(true);
				settingsForm.eventsNumLay.toggle(false);
			}
			// Show minigame spotlight
			settingsForm.mg = new OO.ui.ToggleSwitchWidget({
				title: 'Show minigame spotlight',
				value: self.settings.mg_spotlight
			});
			settingsForm.mgLay = new OO.ui.FieldLayout(settingsForm.mg, { label: 'Minigame Spotlight' });
			settingsForm.mg.on('change', self.saveSetting, ['mg_spotlight']);
			// Show elite dungeon spotlight
			settingsForm.ed = new OO.ui.ToggleSwitchWidget({
				title: 'Show Elite Dungeon spotlight',
				value: self.settings.ed_spotlight
			});
			settingsForm.edLay = new OO.ui.FieldLayout(settingsForm.ed, { label: 'Elite Dungeon Spotlight' });
			settingsForm.ed.on('change', self.saveSetting, ['ed_spotlight']);

			// Show next spotlight
			settingsForm.next = new OO.ui.ToggleSwitchWidget({
				title: 'Show next spotlight item',
				value: self.settings.show_next
			});
			settingsForm.nextLay = new OO.ui.FieldLayout(settingsForm.next, {
				label: 'Show next',
				help: 'Show next item in spotlight rotations'
			});
			settingsForm.next.on('change', self.saveSetting, ['show_next']);

			// Show PvM
			settingsForm.pvm = new OO.ui.ToggleSwitchWidget({
				title: 'Show PvM rotations',
				value: self.settings.pvm
			});
			settingsForm.pvmLay = new OO.ui.FieldLayout(settingsForm.pvm, { label: 'PvM Rotations' });
			settingsForm.pvm.on('change', self.saveSetting, ['pvm']);
			
						// Settings form footer
			settingsForm.$footer = $('<div>')
				.addClass('footer')
				.html('<a href="/w/Help_talk:Gadget-events" title="Events gadget discussion">Give us feedback on these timers!</a>');

			var $setoverlay = $('<div>').addClass('settings-overlay');
			$setoverlay.click( closeset );

			// Complete form
			settingsForm.$form = $('<div>')
				.addClass('events-settings oo-ui-element-hidden')
				.append(
					$setoverlay,
					$('<div>')
						.addClass('settings-container')
						.append(
							settingsForm.back.$element,
							settingsForm.$title,
							settingsForm.form.$element,
							settingsForm.$footer
						)
				);
		},

		/**
		 * Generates the notifications settings popup
		 * @return {undefined}
		 */
		initNotifsForm: function () {
			notifsForm = {};

			// Back button
			notifsForm.back = new OO.ui.ButtonWidget({
				classes: ['oo-ui-labelElement-invisible'],
				icon: 'close',
				id: 'gsw-notifs-backbut',
				invisibleLabel: true,
				label: 'Back',
				framed: false
			});
			notifsForm.back.on('click', function () {
				$('.gsw-events-popup .events-notifs').addClass('slideout');
				setTimeout( function () {
					$('.gsw-events-popup .events-notifs').removeClass('slideout').addClass('oo-ui-element-hidden');
				}, 500);
			});

			// Title
			notifsForm.$title = $('<h3>').text('Notifications');

			// Disable browser notifications
			notifsForm.browsnotif = new OO.ui.ToggleSwitchWidget({
				title: 'Disable browser notifications',
				value: self.settings.nobrownotifs
			});
			notifsForm.browsnotif.on('change', self.saveSetting, ['nobrownotifs']);
			notifsForm.browsnotifLay = new OO.ui.FieldLayout(notifsForm.browsnotif, {
				label: 'No Browser Notifs',
				help: 'Disable native browser notifications. While disabled, notifications will instead be displayed in a popup at the top right of the wiki page.'
			});

			// All Dailies
			notifsForm.dailies = new OO.ui.DropdownInputWidget({
				options: [
					{ data:'None', label:'None' },
					{ data:'All', label:'All' },
					{ data:'Some', label:'Selectively' }
				],
				title: 'Recieve notifications for dailies',
			});
			notifsForm.dailies.setValue(self.settings.notiftype.dailies);
			notifsForm.dailiesLay = new OO.ui.FieldLayout(notifsForm.dailies, {
				label: 'Dailies',
				help: 'Recieve notifications for dailies such as Guthixian Caches or Sinkholes'
			});
			var toggledailies = function (val) {
				self.saveNotif('dailies', val);

				if (val == 'Some') {
					notifsForm.dailiesselLay.toggle(true);
					self.saveNotif('alldailies', false);
				} else if (val == 'All') {
					notifsForm.dailiesselLay.toggle(false);
					self.saveNotif('alldailies', true);
				} else {
					notifsForm.dailiesselLay.toggle(false);
					self.saveNotif('alldailies', false);
				}
			};
			notifsForm.dailies.on('change', toggledailies);

			// Individual Dailies
			notifsForm.dailiessel = new OO.ui.CheckboxMultiselectWidget({ classes: ['notifs-group'] });
			simple_dailies.forEach( function (dd) {
				notifsForm[dd.id] = new OO.ui.CheckboxMultioptionWidget({
					label: dd.name,
					data: dd.name,
					selected: self.settings.notiftype[dd.id]
				});

				notifsForm[dd.id].on('change', self.saveNotif, [dd.id]);

				notifsForm.dailiessel.addItems([ notifsForm[dd.id] ]);
			});
			notifsForm.dailiesselLay = new OO.ui.FieldLayout(notifsForm.dailiessel, {
				align: 'top',
				label: 'Individual Dailies',
				help: 'Choose individual dalies to be notified about'
			});
			if (self.settings.notiftype.dailies != 'Some') {
				notifsForm.dailiesselLay.toggle(false);
			}

			// Resets
			notifsForm.dreset = new OO.ui.ToggleSwitchWidget({
				title: 'Daily reset notification',
				value: self.settings.notiftype.dreset
			});
			notifsForm.dreset.on('change', self.saveNotif, ['dreset']);
			notifsForm.dresetLay = new OO.ui.FieldLayout(notifsForm.dreset, {
				label: 'Daily Reset'
			});
			notifsForm.wreset = new OO.ui.ToggleSwitchWidget({
				title: 'Weekly reset notification',
				value: self.settings.notiftype.wreset
			});
			notifsForm.wreset.on('change', self.saveNotif, ['wreset']);
			notifsForm.wresetLay = new OO.ui.FieldLayout(notifsForm.wreset, {
				label: 'Weekly Reset'
			});
			notifsForm.mreset = new OO.ui.ToggleSwitchWidget({
				title: 'Monthly reset notification',
				value: self.settings.notiftype.mreset
			});
			notifsForm.mreset.on('change', self.saveNotif, ['mreset']);
			notifsForm.mresetLay = new OO.ui.FieldLayout(notifsForm.mreset, {
				label: 'Monthly Reset'
			});
			
						// Events team events
			notifsForm.etevents = new OO.ui.ToggleSwitchWidget({
				title: 'Events team notifications',
				value: self.settings.notiftype.etevents
			});
			notifsForm.etevents.on('change', self.saveNotif, ['etevents']);
			notifsForm.eteventsLay = new OO.ui.FieldLayout(notifsForm.etevents, {
				label: 'Events Team',
				help: 'Receive notifications for RuneScape Wiki events team events'
			});

			// PVM
			notifsForm.vorago = new OO.ui.ToggleSwitchWidget({
				title: 'Vorago attack notifications',
				value: self.settings.notiftype.vorago
			});
			notifsForm.vorago.on('change', self.saveNotif, ['vorago']);
			notifsForm.voragoLay = new OO.ui.FieldLayout(notifsForm.vorago, {
				label: 'Vorago',
				help: 'Receive notifications about current Vorago attack'
			});
			notifsForm.araxxor = new OO.ui.ToggleSwitchWidget({
				title: 'Araxxor path notifications',
				value: self.settings.notiftype.araxxor
			});
			notifsForm.araxxor.on('change', self.saveNotif, ['araxxor']);
			notifsForm.araxxorLay = new OO.ui.FieldLayout(notifsForm.araxxor, {
				label: 'Araxxor',
				help: 'Receive notifications about open Araxxor paths'
			});
			notifsForm.rots = new OO.ui.ToggleSwitchWidget({
				title: 'Rise of the Six notifications',
				value: self.settings.notiftype.rots
			});
			notifsForm.rots.on('change', self.saveNotif, ['rots']);
			notifsForm.rotsLay = new OO.ui.FieldLayout(notifsForm.rots, {
				label: 'RotS',
				title: 'Rise of the Six',
				help: 'Receive notifications for Rise of the Six rotations'
			});

			// Spotlights
			notifsForm.mg_spotlight = new OO.ui.ToggleSwitchWidget({
				title: 'Minigame spotlight notifications',
				value: self.settings.notiftype.mg_spotlight
			});
			notifsForm.mg_spotlight.on('change', self.saveNotif, ['mg_spotlight']);
			notifsForm.mg_spotlightLay = new OO.ui.FieldLayout(notifsForm.mg_spotlight, {
				label: 'Minigame Spotlight',
				help: 'Receive notifications for current Minigame Spotlight'
			});
			notifsForm.ed_spotlight = new OO.ui.ToggleSwitchWidget({
				title: 'Elite dungeon spotlight notifications',
				value: self.settings.notiftype.ed_spotlight
			});
			notifsForm.ed_spotlight.on('change', self.saveNotif, ['ed_spotlight']);
			notifsForm.ed_spotlightLay = new OO.ui.FieldLayout(notifsForm.ed_spotlight, {
				label: 'Elite Dungeon Spotlight',
				help: 'Receive notifications for current Elite dungeon spotlight'
			});
			notifsForm.dd_spotlight = new OO.ui.ToggleSwitchWidget({
				title: 'Distraction and diversion of the week notifications',
				value: self.settings.notiftype.dd_spotlight
			});
			notifsForm.dd_spotlight.on('change', self.saveNotif, ['dd_spotlight']);
			notifsForm.dd_spotlightLay = new OO.ui.FieldLayout(notifsForm.dd_spotlight, {
				label: 'Distraction & Diversions',
				help: 'Receive notifications for Distraction and Diversion of the week'
			});
			
			notifsForm.form = new OO.ui.FieldsetLayout({ classes:'notifs-form' });
			notifsForm.form.addItems([
				notifsForm.browsnotifLay,
				notifsForm.dailiesLay,
				notifsForm.dailiesselLay,
				notifsForm.ravenLay,
				notifsForm.dresetLay,
				notifsForm.wresetLay,
				notifsForm.mresetLay,
				notifsForm.vosLay,
				notifsForm.vosselLay,
				notifsForm.tmsLay,
				notifsForm.tmsselLay,
				notifsForm.pofLay,
				notifsForm.pofselLay,
				notifsForm.eteventsLay,
				notifsForm.voragoLay,
				notifsForm.araxxorLay,
				notifsForm.rotsLay,
				notifsForm.mg_spotlightLay,
				notifsForm.ed_spotlightLay,
				notifsForm.dd_spotlightLay
			]);

			var $notifoverlay = $('<div>').addClass('notifs-overlay');
			$notifoverlay.click( function () {
				$('.gsw-events-popup .events-notifs').addClass('slideout');
				setTimeout( function () {
					$('.gsw-events-popup .events-notifs').removeClass('slideout').addClass('oo-ui-element-hidden');
				}, 500);
			});

			// Complete form
			notifsForm.$form = $('<div>')
				.addClass('events-notifs oo-ui-element-hidden')
				.append(
					$notifoverlay,
					$('<div>')
						.addClass('notifs-container')
						.append(
							notifsForm.back.$element,
							notifsForm.$title,
							notifsForm.form.$element
						)
				);
		},

		/**
		 * Updates all events dropdown values (used on  opening)
		 * @return {undefined}
		 */
		updateAll: function () {
			var now = moment.utc();
			$content.find('.cur-utc-time')
				.attr({ 'title':now.format('HH:mm D/M/Y'), 'datetime':now.format() })
				.text(now.format('HH:mm') + ' (UTC)');
			self.updateTimes();
			self.updateSpots();
			if ( now.isAfter(moment(self.nextvos)) ) {
				self.updateVos();
			} else {
				self.updateVostime();
			}
		},

		/**
		 * Continuously updates the values in Events dropdown
		 * @return {undefined}
		 */
		update: function () {
			self.updateTimes();

			// Current UTC time
			var now = moment.utc();

			$content.find('.cur-utc-time')
				.attr({ 'title':now.format('HH:mm D/M/Y'), 'datetime':now.format() })
				.text(now.format('HH:mm') + ' (UTC)');

			// Check if it's after a new hour, update VoS
			if ( now.isAfter(moment(self.nextvos)) ) {
				self.updateVos();
			} else {
				self.updateVostime();
			}

			// Check if it's after a new day, update PVM etc
			if ( now.isBetween(moment(now).startOf('day'), moment(now).startOf('day').add(1, 'minutes'), null, '[]') ) {
				self.updateSpots();
			}
		},

		/**
		 * Update time based events in the Events popup
		 * Dailies, Resets, Events team events
		 * @return {[type]} [description]
		 */
		updateTimes: function () {
			// Current UTC time
			var now = moment.utc();

			// Dailies
			$content.find('.daily-list').empty();
			var dailies_array = [];
			simple_dailies.forEach( function (dd) {
				var next = moment(now).startOf(dd.from).add((dd.offset), 'minutes'),
					nextEnd = moment(next).add(dd.length, 'minutes');
				while (!now.isBefore(nextEnd)) {
					next.add(dd.repeat, 'minutes');
					nextEnd.add(dd.repeat, 'minutes');
				}
				var time_till = 'now!',
					diff_secs = 0,
					title = 'Going on now',
					future = moment(next).add(dd.repeat, 'minutes'),
					ntxt = future.fromNow(true),
					ntitle = future.format('HH:mm') + ' game time, on ' + future.format('MMM D');
				if (now.isBefore(next)) {
					time_till = next.fromNow(true);
					title = next.format('HH:mm') + ' game time, on ' + next.format('MMM D');
					diff_secs = next.diff(now, 'seconds');
				}	
				dailies_array.push( {name:dd.name, link:dd.link, txt:time_till, title:title, sort:diff_secs, time:next.format(), ntxt:ntxt, ntime:future.format(), ntitle:ntitle} );
			});
			// Sort Dailies
			if ( self.settings.daily_sort == 'by time') {
				dailies_array.sort( function (a,b) {
					return a.sort - b.sort;
				});
			} else {
				dailies_array.sort( function (a,b) {
					if (a.name < b.name) {
						return -1;
					} else if (a.name > b.name) {
						return 1;
					} else {
						return 0;
					}
				});
			}
			// Print Dailies
			dailies_array.forEach( function (dd) {
				var ddClass = 'daily';
				if (dd.sort === 0) { ddClass += ' now'; }
				$content.find('.daily-list').append(
					$('<li>')
						.addClass(ddClass)
						.append(
							$('<label>').addClass('daily-title').html('<a href="'+dd.link+'" title="'+dd.name+'">'+dd.name+'</a>'),
							$('<time>').addClass('daily-timer').attr({'datetime':dd.time, 'title':dd.title}).text(dd.txt),
							$('<time>').addClass('daily-next').attr({'datetime':dd.ntime, 'title':dd.ntitle}).text(dd.ntxt)
						)
				);
			});

			// Update resets
			moment.relativeTimeThreshold('d', 31);
			moment.relativeTimeThreshold('h', 24);
			var dailyReset = moment(now).add(1, 'days').startOf('day'),
				dailyTitle = dailyReset.format('MMM D') + ' at ' + dailyReset.format('HH:mm') + ' game time',
				monthlyReset = moment(now).add(1, 'months').startOf('month'),
				monthlyTitle = monthlyReset.format('MMM D') + ' at ' + monthlyReset.format('HH:mm') + ' game time',
				weeklyReset = moment(now).day(3).startOf('day'); // reset is a wednesday
			if (now.isAfter(weeklyReset)) {
				weeklyReset.add(1, 'week');
			}
			var weeklyTitle = weeklyReset.format('MMM D') + ' at ' + weeklyReset.format('HH:mm') + ' game time';
			$content.find('.countdown-day').attr({'datetime':dailyReset.format(), 'title':dailyTitle }).text( dailyReset.fromNow(true) );
			$content.find('.countdown-week').attr({'datetime':weeklyReset.format(), 'title':weeklyTitle }).text( weeklyReset.fromNow(true) );
			$content.find('.countdown-month').attr({'datetime':monthlyReset.format(), 'title':monthlyTitle }).text( monthlyReset.fromNow(true) );
			moment.relativeTimeThreshold('h', 22);
			moment.relativeTimeThreshold('d', 26);

			// Events team events
			$content.find('.events-team').empty();
			if (self.settings.et_events) {
				$content.find('.events-team').append(
					$('<h3>').html('<a href="/w/RuneScape:Events_Team" title="Events Team events">Events Team</a>'),
					$('<ul>')
						.addClass('events-list etevents-list')
						.append(
							$('<li>').text('No upcoming events')
						)
				);
				var events_array = [];
				et_events.forEach( function (e) {
					var emom = moment(e.date).utc(),
						time = emom.fromNow(true),
						sort = emom.diff(now, 'seconds'),
						dtime = emom.format(),
						title = emom.format('MMM D') + ' at ' + emom.format('HH:mm') + ' game time';
					if (sort >= 0) {
						if (sort < (e.length * 60)) {
							sort = 0;
							time = 'now!';
						}
						events_array.push( {name:e.name, txt:time, sort:sort, dtime:dtime, title:title} );
					} else if (sort > -(e.length * 60)) {
						sort = 0;
						time = 'now!';
						events_array.push( {name:e.name, txt:time, sort:sort, dtime:dtime, title:title} );
					}
				});
				// Sort and trim
				events_array.sort( function (a,b) {
					return a.sort - b.sort;
				});
				if (self.settings.num_events > 0) {
					events_array = events_array.slice(0,self.settings.num_events);
				}
				// Print Events
				if (events_array.length > 0) {
					$content.find('.etevents-list').empty();
					events_array.forEach( function (e) {
						var eClass = 'event';
						if (e.sort === 0) { eClass += ' now'; }
						$content.find('.etevents-list').append(
							$('<li>')
								.addClass(eClass)
								.append(
									$('<label>').addClass('event-title').text(e.name),
									$('<time>').addClass('event-timer').attr({ 'datetime':e.dtime, 'title':e.title }).text(e.txt)
								)
						);
					});
				}
			}
		},

		/**
		 * Update the Voice of Seren values in the Events popup
		 * @return {undefined}
		 */
		updateVos: function () {
			// Current UTC time
			var now = moment.utc();

				// Append to content
				$content.find('.pvm').append(
					$('<div>')
						.addClass('col col-l')
						.append(
							$('<h3>').html('<a href="/w/Vorago" title="Vorago">Vorago</a>'),
							$vorago
						),
					$('<div>')
						.addClass('col col-r')
						.append(
							$('<div>')
								.addClass('section araxxor')
								.append(
									$('<h3>').html('<a href="/w/Araxxor" title="Araxxor">Araxxor</a>'),
									$araxxor
								),
							$('<div>')
								.addClass('section rots')
								.append(
									$('<h3>').attr('title', 'Barrows: Rise of the Six').html('<a href="/w/Barrows:_Rise_of_the_Six" title="Barrows: Rise of the Six">Rise of the Six</a>'),
									$rots
								)
						)
				);
			}

			// Spotlights container empty?
			if( !self.settings.dd_spotlight && !self.settings.mg_spotlight && !self.settings.ed_spotlight ) {
				$content.addClass('spotlights-empty');
			} else {
				$content.removeClass('spotlights-empty');
			}
			$content.find('.spotlight-spacer').removeClass('first');

			// Minigame Spotlight
			$content.find('.mg-spotlight').empty().prev().addClass('empty');
			if (self.settings.mg_spotlight) {
				var mg_array = spotlights(mg_spotlights, 3, 49);
				$content.find('.mg-spotlight').prev().removeClass('empty');
				$content.find('.mg-spotlight').append(
					$('<label>').text('Minigame Spotlight'),
					$('<span>').addClass('spotlight-value').html('<a href="/w/' + mg_array[0] + '" title="' + mg_array[0] + '">' + mg_array[0] + '</a>')
				);
				if (self.settings.show_next) {
					$content.find('.mg-spotlight').append(
						$('<label>').addClass('sl-next').text('Next:'),
						$('<span>').addClass('sl-next mg-spotlight-next').text(mg_array[2] + ' in '),
						$('<time>').addClass('sl-next mg-spotlight-in').attr({ 'datetime':mg_array[3], 'title':mg_array[4] }).text(mg_array[1])
					);
				}
			} else {
				$content.find('.mg-spotlight').next().addClass('first');
			}

			// Elite dungeon spotlight
			$content.find('.ed-spotlight').empty().prev().addClass('empty');
			if (self.settings.ed_spotlight) {
				var ed_array = spotlights(ed_spotlights, 1, 0);
				$content.find('.ed-spotlight').prev().removeClass('empty');
				$content.find('.ed-spotlight').append(
					$('<label>').text('Elite Dungeon Spotlight'),
					$('<span>').addClass('spotlight-value').html('<a href="/w/' + ed_array[0] + '" title="' + ed_array[0] + '">' + ed_array[0] + '</a>')
				);
				if (self.settings.show_next) {
					$content.find('.ed-spotlight').append(
						$('<label>').addClass('sl-next').text('Next:'),
						$('<span>').addClass('sl-next ed-spotlight-next').text(ed_array[2] + ' in '),
						$('<time>').addClass('sl-next ed-spotlight-in').attr({ 'datetime':ed_array[3], 'title':ed_array[4] }).text(ed_array[1])
					);
				}
			} else if (!self.settings.mg_spotlight) {
				$content.find('.ed-spotlight').prev().removeClass('first');
				$content.find('.ed-spotlight').next().addClass('first');
			}
			
					/**
		 * Sends notifications to the browser based on user prefs
		 * @return {undefined}
		 */
		notifs: function () {
			mw.log('Running notifications');
			// TODO: Check that still not using service worker notifs
			if (self.swnotif) {
				mw.log('Exiting notifications, using service worker instead');
				window.clearInterval(notiftimer);
				return;
			}
			
			// Notifications to send
			var notifs = [];

			// Latest rotations and settings from localStorage
			if (rs.hasLocalStorage()) {
				var last = {};
				try {
					last = JSON.parse(localStorage.getItem(localLast));
				} catch (err) {
					last = {};
					console.warn('Error loading latest rotations from localStorage');
				}
				for (var l in last) {
					self.lastRot[l] = last[l];
				}

				var prefs = {};
				try {
					prefs = JSON.parse(localStorage.getItem(localKey));
				} catch (err) {
					prefs = {};
					console.warn('Error loading settings (events)');
				}
				for (var p in prefs) {
					self.settings[p] = prefs[p];
				}
			}

			// Exit if notifs turned off
			if (!self.settings.notifs) {
				mw.log('Exiting notifications, they\'ve been disabled');
				return;
			}
			
			// Current UTC time
			var now = moment.utc();

			// Prevent multiple tabs from updating in short period of time
			mw.log( now.diff(moment(self.lastRot.time), 'seconds') );
			if ( now.diff(moment(self.lastRot.time), 'seconds') <= 255 ) {
				return;
			} else {
				self.lastRot.time = now.format();
			}

			// VoS
			if (now.isBefore( moment(now).startOf('hour').add(10,'minutes') )) {
				var vosError = function (jqXHR, status, error) {
					console.warn('Error loading VoS:\n' + status + ': ' + error);
				};
				var vosSucc = function (vosjson, status, jqXHR) {
					if (!vosjson.districts) {
						vosError({}, 'Missing districts', 'returned json did not contain a districts array');
						return;
					}
					var vostext = vosjson.districts[0] + ',' + vosjson.districts[1];
					if (vostext !== self.lastRot.vos) {
						self.lastRot.vos = vostext;
						var vosnotifs = [];
						vosjson.districts.forEach( function (clan) {
							if (self.settings.notiftype['vos' + clan] && self.settings.notiftype.vos == 'Some' || self.settings.notiftype.allvos) {
								var img = rs.getFileURL(clan + ' Clan.png');
								vosnotifs.push( { title:'VoS ' + clan, opts:{
									badge: img,
									body: 'Voice of Seren is now active in the ' + clan + ' district.',
									tag: 'vos-' + clan,
									icon: img,
									image: img,
									vibrate: true,
									renotify: true,
									requireInteraction: false
								} } );
							}
						});

						self.sendNotifs( vosnotifs, true );

						// Save the last rotations to localstorage if possible
						if (rs.hasLocalStorage()) {
							var string = JSON.stringify(self.lastRot);
							try {
								localStorage.setItem(localLast, string);
							} catch (err) {
								console.warn('Error saving latest rotations to localStorage');
							}
						}
					}
				}

				$.ajax({
					dataType: 'json',
					url: simplevosurl,
					error: vosError,
					success: vosSucc
				});
			}

			// Dailies
			simple_dailies.forEach( function (dd) {
				if (self.settings.notiftype[dd.id] && self.settings.notiftype.dailies == 'Some' || self.settings.notiftype.alldailies) {
					var next = moment(now).startOf(dd.from).add((dd.offset), 'minutes'),
						nextEnd = moment(next).add(dd.length, 'minutes'),
						text = dd.name + ' is going on now!';
					while (!now.isBefore(nextEnd)) {
						next.add(dd.repeat, 'minutes');
						nextEnd.add(dd.repeat, 'minutes');
					}

					if (now.isBefore(next) && next.diff(now, 'seconds') <= 300) {
						// Will happen soon
						text = dd.name + ' is starting in ' + next.fromNow(true);

						notifs.push( { title:dd.name, opts:{
							badge: dd.img,
							body: text,
							tag: 'dd-' + dd.name,
							icon: dd.img,
							image: dd.img,
							vibrate: true,
							renotify: true,
							requireInteraction: false
						} } );
					} else if (now.isAfter(next)) {
						// Happening now
						notifs.push( { title:dd.name, opts:{
							badge: dd.img,
							body: text,
							tag: 'dd-on-' + dd.name,
							icon: dd.img,
							image: dd.img,
							vibrate: true,
							renotify: false,
							requireInteraction: false
						} } ); 
					}
				}
			});

			// Resets
			if (self.settings.notiftype.dreset) {
				// Daily
				var dailyReset = moment(now).add(1, 'days').startOf('day'),
					img = '/images/thumb/0/08/D%26D_token_%28daily%29_detail.png/200px-D%26D_token_%28daily%29_detail.png';
				if (dailyReset.diff(now, 'minutes') <= 10) {
					notifs.push( { title:'Daily Reset', opts:{
						badge: img,
						body: 'Daily reset in ' + dailyReset.fromNow(true),
						tag: 'daily-reset',
						icon: img,
						image: img,
						vibrate: true,
						renotify: true,
						requireInteraction: false
					} } );
				}
			}
			if (self.settings.notiftype.wreset) {
				// Weekly
				var weeklyReset = moment(now).day(3).startOf('day'),
					img = '/images/4/47/D%26D_token_%28weekly%29_detail.png';
				if (now.isAfter(weeklyReset)) {
					weeklyReset.add(1, 'week');
				}
				if (weeklyReset.diff(now, 'minutes') <= 10 ) {
					notifs.push( { title:'Weekly Reset', opts:{
						badge: img,
						body: 'Weekly reset in ' + weeklyReset.fromNow(true),
						tag: 'weekly-reset-min',
						icon: img,
						image: img,
						vibrate: true,
						renotify: true,
						requireInteraction: false
					} } );
				} else if (weeklyReset.diff(now, 'days') <= 1 && now.isAfter(moment(self.lastRot.wreset))) {
					self.lastRot.wreset = weeklyReset.format();
					notifs.push( { title:'Weekly Reset', opts:{
						badge: img,
						body: 'Weekly reset is tomorrow',
						tag: 'weekly-reset',
						icon: img,
						image: img,
						vibrate: true,
						renotify: false,
						requireInteraction: false
					} } );
				}
			}
			if (self.settings.notiftype.mreset) {
				// Monthly
				var monthlyReset = moment(now).add(1, 'months').startOf('month'),
					img = '/images/thumb/a/a7/D%26D_token_%28monthly%29_detail.png/200px-D%26D_token_%28monthly%29_detail.png';
				if (monthlyReset.diff(now, 'minutes') <= 10) {
					notifs.push( { title:'Monthly Reset', opts:{
						badge: img,
						body: 'Monthly reset in ' + monthlyReset.fromNow(true),
						tag: 'monthly-reset-min',
						icon: img,
						image: img,
						vibrate: true,
						renotify: true,
						requireInteraction: false
					} } );
				} else if (monthlyReset.diff(now, 'days') <= 1  && now.isAfter(moment(self.lastRot.mreset))) {
					self.lastRot.mreset = monthlyReset.format();
					notifs.push( { title:'Monthly Reset', opts:{
						badge: img,
						body: 'Monthly reset is tomorrow',
						tag: 'monthly-reset',
						icon: img,
						image: img,
						vibrate: true,
						renotify: false,
						requireInteraction: false
					} } );
				}
			}

			// Daily cached
			if (self.settings.notiftype.tms == 'All' || self.settings.notiftype.tms == 'Some' || self.settings.notiftype.etevents) {
				self.loadEvents().then( function () {
					var dnotifs = [];

					// Events team events
					if (self.settings.notiftype.etevents) {
						var img = '/images/4/43/Apple-touch-icon.png';
						et_events.forEach( function (e) {
							var start = moment(e.date),
								end = moment(start).add(e.length, 'minutes');

							if (now.isBefore(start) && start.diff(now, 'seconds') <= 300) {
								// Starting soon
								dnotifs.push( { title:e.name, opts:{
									badge: img,
									body: 'Join the RS Wiki Events Team for ' + e.name + ' in ' + start.fromNow(true),
									tag: 'et-' + e.name,
									icon: img,
									image: img,
									vibrate: true,
									renotify: false,
									requireInteraction: false
								} } );
							} else if (now.isAfter(start) && now.isBefore(end)) {
								// Going on
								dnotifs.push( { title:e.name, opts:{
									badge: img,
									body: 'Join the RS Wiki Events Team for ' + e.name + ' going on now!',
									tag: 'et-on-' + e.name,
									icon: img,
									image: img,
									vibrate: true,
									renotify: false,
									requireInteraction: false
								} } );
							}
						});
					}

			// For spotlights
			var curspotlight = function(items, duration, offset) {
				var days_passed = -( moment('0', 'X').utc().add(offset, 'days').diff(now, 'days') ),
					days_into = days_passed % (duration * items.length),
					rotation = Math.floor(days_into / duration),
					days_till = duration - (days_into % duration);

				return [rotation, items[rotation], days_till];
			};
			
						// Minigame Spotlight
			if (self.settings.notiftype.mg_spotlight) {
				var spot = curspotlight(mg_spotlights, 3, 49);
				if (spot[0] != self.lastRot.mgspot) {
					self.lastRot.mgspot = spot[0];
					var img = '/images/c/c8/Current_spotlight_icon.png';

					notifs.push( { title:'Minigame Spotlight', opts:{
						badge: img,
						body: 'The currently spotlighted minigame is ' + spot[1],
						tag: 'mgspot',
						icon: img,
						image: img,
						vibrate: true,
						renotify: true,
						requireInteraction: false
					} } );
				}
			}

			// Elite dungeon spotlight
			if (self.settings.notiftype.ed_spotlight) {
				var spot = curspotlight(ed_spotlights, 1, 0);
				if (spot[0] != self.lastRot.edspot) {
					self.lastRot.edspot = spot[0];
					var imgs = {
							'Temple of Aminishi':'/images/thumb/f/fd/Relic_of_aminishi_%28rare%29_detail.png/200px-Relic_of_aminishi_%28rare%29_detail.png',
							'Dragonkin Laboratory':'/images/thumb/d/d4/Laboratory_relic_%28uncommon%29_detail.png/180px-Laboratory_relic_%28uncommon%29_detail.png',
							'The Shadow Reef':'/images/thumb/e/e5/Umbral_urn_detail.png/160px-Umbral_urn_detail.png'
						},
						img = imgs[spot[1]];

					notifs.push( { title:'Elite Dungeon Spotlight', opts:{
						badge: img,
						body: 'The currently spotlighted elite dungeon is ' + spot[1],
						tag: 'edspot',
						icon: img,
						image: img,
						vibrate: true,
						renotify: true,
						requireInteraction: false
					} } );
				}
			}
			
						// Send popup notifs
			var popNotifs = function ( nl, noempty ) {
				mw.log('Sending popup notifications');
				// Google analytics tracker
				if (typeof ga === 'function') {
					ga('gtag_UA_126479006_1.send', 'event', 'Gadget-events', 'Send Notifactions', 'Popup', nl.length);
				}

				var createNotifs = function () {
					var notiflist = $notifs.find('ul.event-notifications');
					if ( !noempty ) {
						notiflist.empty();
					}
					nl.forEach( function (n) {
						mw.log('Sending ' + n.title);
						notiflist.append(
							$('<li>').addClass('event-notif').append(
								$('<div>').addClass('notif-image').append(
									$('<img>', {'src':n.opts.image, 'alt':n.title})
								),
								$('<label>').text(n.title),
								$('<span>').text(n.opts.body)
							)
						);
					});

					notifpopup.toggle(true);
					setTimeout(notifpopup.toggle, 180000, false);
				};
				if (!formMade) {
					mw.log('Initialise interface');
					mw.loader.using(['oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets']).then(self.initInt).then(createNotifs);
				} else {
					createNotifs();
				}
			};

			// Check permissions
			if (self.supportNotif && Notification.permission === 'granted' && !self.settings.nobrownotifs) {
				mw.log('Notification permission granted');
				// Send browser notifications
				sendNotifs( notifs );
			} else if (self.supportNotif && Notification.permission !== 'denied' && !self.settings.nobrownotifs) {
				mw.log('Requesting permission');
				// Request permissions
				Notification.requestPermission().then( function (permission) {
					if (permission === 'granted') {
						mw.log('Notification permission granted');
						// Send browser notifications
						sendNotifs( notifs );
					} else {
						mw.log('Notification permission denied or dismissed');
						// Send popup notifications
						popNotifs( notifs, add );
					}
				}, function () {
					console.warn('Error getting notification permissions');
					// Send popup notifications
					popNotifs( notifs, add );
				});
			} else {
				mw.log('Notificationpermission denied');
				// Send popup notifications
				popNotifs( notifs, add );
			}

		}
	};

	mw.loader.using(['ext.gadget.gsw-util', 'moment', 'mediawiki.api', 'mediawiki.api.messages'], function () {
		$(self.init);
	});

}(jQuery, mediaWiki, gswiki));
//</nowiki>