2016-04-15 12:18:59 +02:00
/ * M a g i c M i r r o r
* Node Helper : Calendar - CalendarFetcher
*
* By Michael Teeuw http : //michaelteeuw.nl
* MIT Licensed .
* /
2016-04-20 11:32:48 +02:00
var ical = require ( "./vendor/ical.js" ) ;
2016-04-15 12:18:59 +02:00
var moment = require ( "moment" ) ;
2017-07-27 17:59:23 +02:00
var CalendarFetcher = function ( url , reloadInterval , excludedEvents , maximumEntries , maximumNumberOfDays , auth ) {
2016-04-15 12:18:59 +02:00
var self = this ;
var reloadTimer = null ;
var events = [ ] ;
var fetchFailedCallback = function ( ) { } ;
var eventsReceivedCallback = function ( ) { } ;
/ * f e t c h C a l e n d a r ( )
* Initiates calendar fetch .
* /
var fetchCalendar = function ( ) {
clearTimeout ( reloadTimer ) ;
reloadTimer = null ;
2016-12-08 01:29:55 -03:00
nodeVersion = Number ( process . version . match ( /^v(\d+\.\d+)/ ) [ 1 ] ) ;
2016-06-04 20:32:55 -06:00
var opts = {
headers : {
2016-12-08 01:29:55 -03:00
"User-Agent" : "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global . version + " (https://github.com/MichMich/MagicMirror/)"
2018-09-08 23:05:19 +08:00
} ,
gzip : true
2016-09-08 17:36:30 +02:00
} ;
2017-03-07 00:12:43 +01:00
if ( auth ) {
2017-03-07 00:34:17 +01:00
if ( auth . method === "bearer" ) {
opts . auth = {
bearer : auth . pass
}
} else {
opts . auth = {
user : auth . user ,
pass : auth . pass
} ;
if ( auth . method === "digest" ) {
opts . auth . sendImmediately = false ;
} else {
opts . auth . sendImmediately = true ;
}
2016-09-08 17:36:30 +02:00
}
2016-06-04 20:32:55 -06:00
}
2016-09-08 17:36:30 +02:00
2016-06-04 20:32:55 -06:00
ical . fromURL ( url , opts , function ( err , data ) {
2016-04-15 12:18:59 +02:00
if ( err ) {
fetchFailedCallback ( self , err ) ;
scheduleTimer ( ) ;
return ;
}
2017-03-07 00:12:43 +01:00
// console.log(data);
2016-04-15 12:18:59 +02:00
newEvents = [ ] ;
var limitFunction = function ( date , i ) { return i < maximumEntries ; } ;
2017-02-07 23:51:13 +01:00
var eventDate = function ( event , time ) {
return ( event [ time ] . length === 8 ) ? moment ( event [ time ] , "YYYYMMDD" ) : moment ( new Date ( event [ time ] ) ) ;
} ;
2016-04-15 12:18:59 +02:00
for ( var e in data ) {
var event = data [ e ] ;
var now = new Date ( ) ;
var today = moment ( ) . startOf ( "day" ) . toDate ( ) ;
2016-04-19 16:45:57 +02:00
var future = moment ( ) . startOf ( "day" ) . add ( maximumNumberOfDays , "days" ) . subtract ( 1 , "seconds" ) . toDate ( ) ; // Subtract 1 second so that events that start on the middle of the night will not repeat.
2016-04-15 12:18:59 +02:00
// FIXME:
// Ugly fix to solve the facebook birthday issue.
// Otherwise, the recurring events only show the birthday for next year.
var isFacebookBirthday = false ;
if ( typeof event . uid !== "undefined" ) {
if ( event . uid . indexOf ( "@facebook.com" ) !== - 1 ) {
isFacebookBirthday = true ;
}
}
if ( event . type === "VEVENT" ) {
2017-02-07 23:51:13 +01:00
var startDate = eventDate ( event , "start" ) ;
2016-04-15 22:09:59 +02:00
var endDate ;
if ( typeof event . end !== "undefined" ) {
2017-02-07 23:51:13 +01:00
endDate = eventDate ( event , "end" ) ;
2018-08-28 17:29:42 +02:00
} else if ( typeof event . duration !== "undefined" ) {
dur = moment . duration ( event . duration ) ;
endDate = startDate . clone ( ) . add ( dur ) ;
2016-04-15 22:09:59 +02:00
} else {
2016-05-03 11:56:24 +02:00
if ( ! isFacebookBirthday ) {
endDate = startDate ;
} else {
2016-10-14 15:23:03 +02:00
endDate = moment ( startDate ) . add ( 1 , "days" ) ;
2016-05-03 11:56:24 +02:00
}
2016-04-15 22:09:59 +02:00
}
2016-04-20 15:19:36 +02:00
// calculate the duration f the event for use with recurring events.
var duration = parseInt ( endDate . format ( "x" ) ) - parseInt ( startDate . format ( "x" ) ) ;
2016-04-15 12:18:59 +02:00
if ( event . start . length === 8 ) {
startDate = startDate . startOf ( "day" ) ;
}
2016-05-10 12:24:49 +02:00
var title = "Event" ;
if ( event . summary ) {
title = ( typeof event . summary . val !== "undefined" ) ? event . summary . val : event . summary ;
} else if ( event . description ) {
title = event . description ;
}
2018-03-18 23:33:48 -04:00
var excluded = false ,
dateFilter = null ;
2017-07-27 17:59:23 +02:00
for ( var f in excludedEvents ) {
2018-03-18 23:33:48 -04:00
var filter = excludedEvents [ f ] ,
testTitle = title . toLowerCase ( ) ,
2018-06-03 17:34:16 -04:00
until = null ,
useRegex = false ,
regexFlags = "g" ;
2018-03-18 23:33:48 -04:00
if ( filter instanceof Object ) {
if ( typeof filter . until !== "undefined" ) {
until = filter . until ;
}
2018-06-03 17:34:16 -04:00
if ( typeof filter . regex !== "undefined" ) {
useRegex = filter . regex ;
}
2018-03-18 23:33:48 -04:00
// If additional advanced filtering is added in, this section
// must remain last as we overwrite the filter object with the
// filterBy string
if ( filter . caseSensitive ) {
filter = filter . filterBy ;
testTitle = title ;
2018-06-03 17:34:16 -04:00
} else if ( useRegex ) {
filter = filter . filterBy ;
testTitle = title ;
regexFlags += "i" ;
2018-03-18 23:33:48 -04:00
} else {
filter = filter . filterBy . toLowerCase ( ) ;
}
} else {
filter = filter . toLowerCase ( ) ;
}
2018-06-03 17:34:16 -04:00
if ( testTitleByFilter ( testTitle , filter , useRegex , regexFlags ) ) {
2018-03-18 23:33:48 -04:00
if ( until ) {
dateFilter = until ;
} else {
excluded = true ;
}
2017-07-27 17:59:23 +02:00
break ;
}
}
if ( excluded ) {
continue ;
}
2016-11-05 21:21:23 -03:00
var location = event . location || false ;
var geo = event . geo || false ;
var description = event . description || false ;
2018-11-07 18:44:32 +00:00
if ( typeof event . rrule != "undefined" && event . rrule != null && ! isFacebookBirthday ) {
2016-04-15 12:18:59 +02:00
var rule = event . rrule ;
2018-10-29 20:26:54 +01:00
2018-10-03 22:43:29 +02:00
// can cause problems with e.g. birthdays before 1900
2018-10-29 20:26:54 +01:00
if ( rule . origOptions && rule . origOptions . dtstart && rule . origOptions . dtstart . getFullYear ( ) < 1900 ||
2018-10-03 22:43:29 +02:00
rule . options && rule . options . dtstart && rule . options . dtstart . getFullYear ( ) < 1900 ) {
rule . origOptions . dtstart . setYear ( 1900 ) ;
rule . options . dtstart . setYear ( 1900 ) ;
2018-10-03 22:03:50 +02:00
}
2018-10-29 20:26:54 +01:00
2016-04-15 12:18:59 +02:00
var dates = rule . between ( today , future , true , limitFunction ) ;
2016-04-20 11:32:48 +02:00
2016-04-15 12:18:59 +02:00
for ( var d in dates ) {
startDate = moment ( new Date ( dates [ d ] ) ) ;
2016-10-14 15:23:03 +02:00
endDate = moment ( parseInt ( startDate . format ( "x" ) ) + duration , "x" ) ;
2018-03-18 23:33:48 -04:00
if ( timeFilterApplies ( now , endDate , dateFilter ) ) {
continue ;
}
2016-04-20 15:19:36 +02:00
if ( endDate . format ( "x" ) > now ) {
newEvents . push ( {
2016-05-10 12:24:49 +02:00
title : title ,
2016-04-20 15:19:36 +02:00
startDate : startDate . format ( "x" ) ,
endDate : endDate . format ( "x" ) ,
2016-04-26 22:34:12 +02:00
fullDayEvent : isFullDayEvent ( event ) ,
2016-11-30 21:09:57 +01:00
class : event . class ,
2016-10-20 09:05:12 +02:00
firstYear : event . start . getFullYear ( ) ,
2016-11-05 21:21:23 -03:00
location : location ,
geo : geo ,
description : description
2016-04-20 15:19:36 +02:00
} ) ;
}
2016-04-15 12:18:59 +02:00
}
} else {
// console.log("Single event ...");
// Single event.
2016-05-03 11:56:24 +02:00
var fullDayEvent = ( isFacebookBirthday ) ? true : isFullDayEvent ( event ) ;
2016-04-15 12:50:34 +02:00
2016-04-15 13:13:06 +02:00
if ( ! fullDayEvent && endDate < new Date ( ) ) {
2016-04-15 13:16:02 +02:00
//console.log("It's not a fullday event, and it is in the past. So skip: " + title);
2016-04-15 12:50:34 +02:00
continue ;
2016-04-15 12:18:59 +02:00
}
2016-04-15 12:50:34 +02:00
2016-04-23 17:40:44 +02:00
if ( fullDayEvent && endDate <= today ) {
2016-04-15 13:16:02 +02:00
//console.log("It's a fullday event, and it is before today. So skip: " + title);
2016-04-15 12:50:34 +02:00
continue ;
}
if ( startDate > future ) {
2016-04-15 13:16:02 +02:00
//console.log("It exceeds the maximumNumberOfDays limit. So skip: " + title);
2016-04-15 12:50:34 +02:00
continue ;
}
2018-03-18 23:33:48 -04:00
if ( timeFilterApplies ( now , endDate , dateFilter ) ) {
continue ;
}
2016-11-05 21:21:23 -03:00
// Every thing is good. Add it to the list.
2016-10-14 15:23:03 +02:00
2016-04-15 12:50:34 +02:00
newEvents . push ( {
title : title ,
startDate : startDate . format ( "x" ) ,
2016-04-15 13:13:06 +02:00
endDate : endDate . format ( "x" ) ,
2016-11-30 21:09:57 +01:00
fullDayEvent : fullDayEvent ,
2016-12-22 22:00:59 +01:00
class : event . class ,
2016-11-05 21:21:23 -03:00
location : location ,
geo : geo ,
description : description
2016-04-15 12:50:34 +02:00
} ) ;
2016-11-05 21:21:23 -03:00
2016-04-15 12:18:59 +02:00
}
}
}
newEvents . sort ( function ( a , b ) {
return a . startDate - b . startDate ;
} ) ;
2016-04-20 15:19:36 +02:00
//console.log(newEvents);
2016-04-15 12:18:59 +02:00
events = newEvents . slice ( 0 , maximumEntries ) ;
self . broadcastEvents ( ) ;
scheduleTimer ( ) ;
} ) ;
} ;
/ * s c h e d u l e T i m e r ( )
* Schedule the timer for the next update .
* /
var scheduleTimer = function ( ) {
//console.log('Schedule update timer.');
clearTimeout ( reloadTimer ) ;
reloadTimer = setTimeout ( function ( ) {
fetchCalendar ( ) ;
} , reloadInterval ) ;
} ;
2016-04-19 10:34:14 +02:00
/ * i s F u l l D a y E v e n t ( e v e n t )
* Checks if an event is a fullday event .
*
2019-03-08 11:19:15 +01:00
* argument event object - The event object to check .
2016-04-19 10:34:14 +02:00
*
* return bool - The event is a fullday event .
* /
var isFullDayEvent = function ( event ) {
if ( event . start . length === 8 ) {
return true ;
}
var start = event . start || 0 ;
var startDate = new Date ( start ) ;
var end = event . end || 0 ;
2018-08-28 17:29:42 +02:00
if ( ( ( end - start ) % ( 24 * 60 * 60 * 1000 ) ) === 0 && startDate . getHours ( ) === 0 && startDate . getMinutes ( ) === 0 ) {
2016-04-19 10:34:14 +02:00
// Is 24 hours, and starts on the middle of the night.
2016-11-05 21:21:23 -03:00
return true ;
2016-04-19 10:34:14 +02:00
}
return false ;
} ;
2018-03-18 23:33:48 -04:00
/ * t i m e F i l t e r A p p l i e s ( )
* Determines if the user defined time filter should apply
*
* argument now Date - Date object using previously created object for consistency
* argument endDate Moment - Moment object representing the event end date
* argument filter string - The time to subtract from the end date to determine if an event should be shown
*
* return bool - The event should be filtered out
* /
var timeFilterApplies = function ( now , endDate , filter ) {
if ( filter ) {
var until = filter . split ( " " ) ,
value = parseInt ( until [ 0 ] ) ,
increment = until [ 1 ] . slice ( "-1" ) === "s" ? until [ 1 ] : until [ 1 ] + "s" , // Massage the data for moment js
filterUntil = moment ( endDate . format ( ) ) . subtract ( value , increment ) ;
return now < filterUntil . format ( "x" ) ;
}
return false ;
} ;
2018-06-03 17:34:16 -04:00
var testTitleByFilter = function ( title , filter , useRegex , regexFlags ) {
if ( useRegex ) {
// Assume if leading slash, there is also trailing slash
if ( filter [ 0 ] === "/" ) {
// Strip leading and trailing slashes
filter = filter . substr ( 1 ) . slice ( 0 , - 1 ) ;
}
filter = new RegExp ( filter , regexFlags ) ;
return filter . test ( title ) ;
} else {
return title . includes ( filter ) ;
}
}
2016-04-15 12:18:59 +02:00
/* public methods */
/ * s t a r t F e t c h ( )
* Initiate fetchCalendar ( ) ;
* /
this . startFetch = function ( ) {
fetchCalendar ( ) ;
} ;
/ * b r o a d c a s t I t e m s ( )
2016-06-04 20:32:55 -06:00
* Broadcast the existing events .
2016-04-15 12:18:59 +02:00
* /
this . broadcastEvents = function ( ) {
//console.log('Broadcasting ' + events.length + ' events.');
eventsReceivedCallback ( self ) ;
} ;
/ * o n R e c e i v e ( c a l l b a c k )
* Sets the on success callback
*
* argument callback function - The on success callback .
* /
this . onReceive = function ( callback ) {
eventsReceivedCallback = callback ;
} ;
/ * o n E r r o r ( c a l l b a c k )
* Sets the on error callback
*
* argument callback function - The on error callback .
* /
this . onError = function ( callback ) {
fetchFailedCallback = callback ;
} ;
/ * u r l ( )
* Returns the url of this fetcher .
*
* return string - The url of this fetcher .
* /
this . url = function ( ) {
return url ;
} ;
/ * e v e n t s ( )
* Returns current available events for this fetcher .
*
* return array - The current available events for this fetcher .
* /
this . events = function ( ) {
return events ;
} ;
} ;
2016-09-08 17:36:30 +02:00
module . exports = CalendarFetcher ;