@@ -64,7 +64,11 @@ var CalendarService = (function () {
6464 }
6565
6666 /**
67- * Find events on the designated calendar whose title matches the availability pattern.
67+ * Find events on the designated calendar that represent available booking windows.
68+ * Two detection paths:
69+ * 1. Title matches the AVAILABILITY_PATTERN (e.g. "Jeremy office hours")
70+ * 2. Title matches a FREE_EVENT_PATTERNS entry AND the event is marked "free"
71+ * (transparency = "transparent" in Google Calendar)
6872 */
6973 function findAvailabilityWindows ( calendarId , pattern , startDate , endDate ) {
7074 var calendar = CalendarApp . getCalendarById ( calendarId ) ;
@@ -76,20 +80,74 @@ var CalendarService = (function () {
7680 var events = calendar . getEvents ( startDate , endDate ) ;
7781 var windows = [ ] ;
7882 var patternLower = pattern . toLowerCase ( ) ;
83+ var freePatterns = getFreeEventPatterns ( ) ;
7984
8085 for ( var i = 0 ; i < events . length ; i ++ ) {
8186 var title = events [ i ] . getTitle ( ) . toLowerCase ( ) ;
87+
88+ // Path 1: title matches the main availability pattern
8289 if ( title . indexOf ( patternLower ) !== - 1 ) {
8390 windows . push ( {
8491 start : events [ i ] . getStartTime ( ) . getTime ( ) ,
8592 end : events [ i ] . getEndTime ( ) . getTime ( ) ,
8693 } ) ;
94+ continue ;
95+ }
96+
97+ // Path 2: title matches a free-event pattern AND event is marked "free"
98+ if ( freePatterns . length > 0 && matchesFreePattern ( title , freePatterns ) ) {
99+ if ( isEventFree ( calendarId , events [ i ] ) ) {
100+ windows . push ( {
101+ start : events [ i ] . getStartTime ( ) . getTime ( ) ,
102+ end : events [ i ] . getEndTime ( ) . getTime ( ) ,
103+ } ) ;
104+ }
87105 }
88106 }
89107
90108 return windows ;
91109 }
92110
111+ /**
112+ * Parse FREE_EVENT_PATTERNS Script Property into an array of lowercase patterns.
113+ */
114+ function getFreeEventPatterns ( ) {
115+ var raw = Config . get ( 'FREE_EVENT_PATTERNS' ) ;
116+ if ( ! raw ) return [ ] ;
117+ try {
118+ var patterns = JSON . parse ( raw ) ;
119+ if ( ! Array . isArray ( patterns ) ) return [ ] ;
120+ return patterns . map ( function ( p ) { return p . toLowerCase ( ) ; } ) ;
121+ } catch ( e ) {
122+ return [ ] ;
123+ }
124+ }
125+
126+ /**
127+ * Check if an event title matches any of the free-event patterns.
128+ */
129+ function matchesFreePattern ( titleLower , freePatterns ) {
130+ for ( var i = 0 ; i < freePatterns . length ; i ++ ) {
131+ if ( titleLower . indexOf ( freePatterns [ i ] ) !== - 1 ) return true ;
132+ }
133+ return false ;
134+ }
135+
136+ /**
137+ * Check if an event is marked as "free" (transparent) using the Calendar Advanced Service.
138+ * CalendarApp doesn't expose transparency, so we use Calendar.Events.get().
139+ */
140+ function isEventFree ( calendarId , event ) {
141+ try {
142+ var eventId = event . getId ( ) . replace ( '@google.com' , '' ) ;
143+ var resource = Calendar . Events . get ( calendarId , eventId ) ;
144+ return resource . transparency === 'transparent' ;
145+ } catch ( e ) {
146+ Logger . log ( 'Could not check transparency for event: ' + e . message ) ;
147+ return false ;
148+ }
149+ }
150+
93151 /**
94152 * Get busy times from ALL calendars the user has access to,
95153 * using the Calendar Advanced Service (freeBusy query).
@@ -152,11 +210,14 @@ var CalendarService = (function () {
152210 var events = calendar . getEvents ( startDate , endDate ) ;
153211 var busyTimes = [ ] ;
154212 var patternLower = pattern . toLowerCase ( ) ;
213+ var freePatterns = getFreeEventPatterns ( ) ;
155214
156215 for ( var i = 0 ; i < events . length ; i ++ ) {
157216 var title = events [ i ] . getTitle ( ) . toLowerCase ( ) ;
158217 // Skip availability window events — they define free time, not busy time
159218 if ( title . indexOf ( patternLower ) !== - 1 ) continue ;
219+ // Skip free-pattern events that are marked as "free" — they also define available time
220+ if ( freePatterns . length > 0 && matchesFreePattern ( title , freePatterns ) && isEventFree ( calendarId , events [ i ] ) ) continue ;
160221 // Skip events the user has declined
161222 var myStatus = events [ i ] . getMyStatus ( ) ;
162223 if ( myStatus === CalendarApp . GuestStatus . NO ) continue ;
@@ -320,10 +381,13 @@ var CalendarService = (function () {
320381 var freeWindows = subtractBusyTimes ( windows , busyTimes ) ;
321382 var slots = generateSlots ( freeWindows , 15 ) ;
322383
384+ var freePatterns = getFreeEventPatterns ( ) ;
385+
323386 return {
324387 calendarId : calendarId ,
325388 calendarName : calName ,
326389 pattern : pattern ,
390+ freeEventPatterns : freePatterns ,
327391 dateRange : { start : startDate . toISOString ( ) , end : endDate . toISOString ( ) } ,
328392 totalEventsInRange : eventList . length ,
329393 events : eventList ,
0 commit comments