Files
htwkalender/services/ical/service/ical/icalFileGeneration.go
2024-10-19 15:46:49 +02:00

182 lines
5.6 KiB
Go

//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
//Copyright (C) 2024 HTWKalender support@htwkalender.de
//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 <https://www.gnu.org/licenses/>.
package ical
import (
ics "github.com/arran4/golang-ical"
"htwkalender/ical/model"
"htwkalender/ical/service/functions"
clock "htwkalender/ical/service/functions/time"
"htwkalender/ical/service/names"
"time"
_ "time/tzdata"
)
func GenerateIcalFeed(events model.Events, mapping map[string]model.FeedCollection, userAgent string) *ics.Calendar {
cal := ics.NewCalendarFor("HTWK Kalender")
setDefaultIcalParams(cal)
europeTime, _ := time.LoadLocation("Europe/Berlin")
internalClock := clock.RealClock{}
for _, event := range events {
mapEntry, mappingFound := mapping[event.UUID]
var eventHash = functions.HashString(time.Time(event.Start).String() + time.Time(event.End).String() + event.Course + event.Name + event.Rooms)
icalEvent := ics.NewEvent(eventHash + "@htwkalender.de")
icalEvent.SetDtStampTime(internalClock.Now().Local().In(europeTime))
icalEvent.SetStartAt(time.Time(event.Start).Local().In(europeTime))
icalEvent.SetEndAt(time.Time(event.End).Local().In(europeTime))
if mappingFound {
addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertySummary, replaceNameIfUserDefined(&event, mapEntry))
addAlarmIfSpecified(icalEvent, event, mapEntry, internalClock)
} else {
addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertySummary, event.Name)
}
addUserAgentSpecificDescription(icalEvent, event, userAgent)
addPropertyIfNotEmpty(icalEvent, ics.ComponentPropertyLocation, event.Rooms)
cal.AddVEvent(icalEvent)
}
return cal
}
func setDefaultIcalParams(cal *ics.Calendar) {
cal.SetMethod(ics.MethodPublish)
cal.SetProductId("-//HTWK Kalender//htwkalender.de//DE")
cal.SetTzid("Europe/Berlin")
cal.SetXWRCalName("HTWK Kalender")
cal.SetXWRTimezone("Europe/Berlin")
cal.SetVersion("2.0")
cal.SetCalscale("GREGORIAN")
vTimeZone := ics.NewTimezone("Europe/Berlin")
vTimeZone.Components = []ics.Component{
&ics.Daylight{
ComponentBase: ics.ComponentBase{
Properties: []ics.IANAProperty{
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyDtstart),
Value: "19700329T020000",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyRrule),
Value: "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyTzname),
Value: "CEST",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyTzoffsetfrom),
Value: "+0100",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyTzoffsetto),
Value: "+0200",
},
},
},
},
},
&ics.Standard{
ComponentBase: ics.ComponentBase{
Properties: []ics.IANAProperty{
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyDtstart),
Value: "19701025T030000",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyRrule),
Value: "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyTzname),
Value: "CET",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyTzoffsetfrom),
Value: "+0200",
},
},
{
BaseProperty: ics.BaseProperty{
IANAToken: string(ics.PropertyTzoffsetto),
Value: "+0100",
},
},
},
},
},
}
cal.AddVTimezone(vTimeZone)
}
// AddPropertyIfNotEmpty adds a property to the component if the value is not empty
// or contains only whitespaces
func addPropertyIfNotEmpty(component *ics.VEvent, key ics.ComponentProperty, value string) {
if !functions.OnlyWhitespace(value) {
component.AddProperty(key, value)
}
}
// if reminder is specified in the configuration for this event, an alarm will be added to the event
func addAlarmIfSpecified(icalEvent *ics.VEvent, event model.Event, mapping model.FeedCollection, clock clock.Clock) {
// if event.Start > now
// then add alarm
if time.Time(event.Start).Local().After(clock.Now().Local()) && mapping.Reminder {
alarm := icalEvent.AddAlarm()
alarm.AddProperty(ics.ComponentPropertyTrigger, "-P0DT0H15M0S")
alarm.AddProperty(ics.ComponentPropertyAction, "DISPLAY")
alarm.AddProperty(ics.ComponentPropertyDescription, "Next course: "+replaceNameIfUserDefined(&event, mapping)+" in "+event.Rooms)
}
}
// replaceNameIfUserDefined replaces the name of the event with the user defined name if it is not empty
// all contained template strings will be replaced with the corresponding values from the event
func replaceNameIfUserDefined(event *model.Event, mapping model.FeedCollection) string {
if !functions.OnlyWhitespace(mapping.UserDefinedName) {
return names.ReplaceTemplateSubStrings(mapping.UserDefinedName, *event)
}
return event.Name
}