feat: Jahrestage (ANNIVERSARY) und Emoji-Präfixe für Kalendereinträge
This commit is contained in:
@@ -179,8 +179,12 @@ func generateBirthdayEvents(birthdays []BirthdayContact) []birthdayEvent {
|
||||
for _, v := range birthdays {
|
||||
for year := cyear; year <= 10+cyear; year++ {
|
||||
yearshift := year - v.Date.Year()
|
||||
prefix := "\U0001F382 " // 🎂
|
||||
if v.Type == ContactTypeAnniversary {
|
||||
prefix = "\U0001F48D " // 💍
|
||||
}
|
||||
ev := birthdayEvent{
|
||||
Summary: fmt.Sprintf("%s %s", v.GivenName, v.FamilyName),
|
||||
Summary: fmt.Sprintf("%s%s %s", prefix, v.GivenName, v.FamilyName),
|
||||
DateTimeStart: v.Date.AddDate(yearshift, 0, 0).Format("20060102"),
|
||||
DateTimeEnd: v.Date.AddDate(yearshift, 0, 1).Format("20060102"),
|
||||
}
|
||||
|
||||
@@ -10,11 +10,20 @@ import (
|
||||
"github.com/emersion/go-webdav/carddav"
|
||||
)
|
||||
|
||||
// ContactType unterscheidet zwischen Geburtstagen und Jahrestagen.
|
||||
type ContactType int
|
||||
|
||||
const (
|
||||
ContactTypeBirthday ContactType = iota
|
||||
ContactTypeAnniversary
|
||||
)
|
||||
|
||||
type BirthdayContact struct {
|
||||
FamilyName string
|
||||
GivenName string
|
||||
Date time.Time
|
||||
YearKnown bool
|
||||
Type ContactType
|
||||
}
|
||||
|
||||
func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient, user string) ([]BirthdayContact, error) {
|
||||
@@ -41,20 +50,39 @@ func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient,
|
||||
}
|
||||
for _, v := range oo {
|
||||
nn := v.Card.Names()
|
||||
bdayprop := v.Card.Value(vcard.FieldBirthday)
|
||||
if len(nn) == 0 || len(bdayprop) == 0 {
|
||||
if len(nn) == 0 {
|
||||
continue
|
||||
}
|
||||
yyyy, mm, dd, err := sanitizeBirthday(bdayprop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
bdayprop := v.Card.Value(vcard.FieldBirthday)
|
||||
if len(bdayprop) > 0 {
|
||||
yyyy, mm, dd, err := sanitizeDate(bdayprop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, BirthdayContact{
|
||||
GivenName: nn[0].GivenName,
|
||||
FamilyName: nn[0].FamilyName,
|
||||
Date: time.Date(int(yyyy), time.Month(int(mm)), int(dd), 0, 0, 0, 0, time.UTC),
|
||||
YearKnown: yyyy != 0,
|
||||
Type: ContactTypeBirthday,
|
||||
})
|
||||
}
|
||||
{
|
||||
annivprop := v.Card.Value("ANNIVERSARY")
|
||||
if len(annivprop) > 0 {
|
||||
yyyy, mm, dd, err := sanitizeDate(annivprop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, BirthdayContact{
|
||||
GivenName: nn[0].GivenName,
|
||||
FamilyName: nn[0].FamilyName,
|
||||
Date: time.Date(int(yyyy), time.Month(int(mm)), int(dd), 0, 0, 0, 0, time.UTC),
|
||||
YearKnown: yyyy != 0,
|
||||
Type: ContactTypeAnniversary,
|
||||
})
|
||||
}
|
||||
}
|
||||
contacts = append(contacts, BirthdayContact{
|
||||
GivenName: v.Card.Names()[0].GivenName,
|
||||
FamilyName: v.Card.Names()[0].FamilyName,
|
||||
Date: time.Date(int(yyyy), time.Month(int(mm)), int(dd), 0, 0, 0, 0, time.UTC),
|
||||
YearKnown: yyyy != 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
return contacts, nil
|
||||
|
||||
@@ -32,7 +32,7 @@ func randomPassword(length int) (string, error) {
|
||||
return string(pass), nil
|
||||
}
|
||||
|
||||
func sanitizeBirthday(input string) (uint16, uint16, uint16, error) {
|
||||
func sanitizeDate(input string) (uint16, uint16, uint16, error) {
|
||||
input = strings.ReplaceAll(input, "-", "")
|
||||
switch len(input) {
|
||||
case 4:
|
||||
@@ -63,7 +63,7 @@ func sanitizeBirthday(input string) (uint16, uint16, uint16, error) {
|
||||
}
|
||||
return uint16(yyyy), uint16(mm), uint16(dd), nil
|
||||
}
|
||||
return 0, 0, 0, fmt.Errorf("birthday prop format unknown: %s", input)
|
||||
return 0, 0, 0, fmt.Errorf("date prop format unknown: %s", input)
|
||||
}
|
||||
|
||||
// parseNotificationTrigger konvertiert eine Uhrzeit im Format "HH:MM" in eine
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_sanitizeBirthday(t *testing.T) {
|
||||
func Test_sanitizeDate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string // description of this test case
|
||||
// Named input parameters for target function.
|
||||
@@ -47,24 +47,24 @@ func Test_sanitizeBirthday(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got2, got3, gotErr := sanitizeBirthday(tt.input)
|
||||
got, got2, got3, gotErr := sanitizeDate(tt.input)
|
||||
if gotErr != nil {
|
||||
if !tt.wantErr {
|
||||
t.Errorf("sanitizeBirthday() failed: %v", gotErr)
|
||||
t.Errorf("sanitizeDate() failed: %v", gotErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
if tt.wantErr {
|
||||
t.Fatal("sanitizeBirthday() succeeded unexpectedly")
|
||||
t.Fatal("sanitizeDate() succeeded unexpectedly")
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("sanitizeBirthday() = %v, want %v", got, tt.want)
|
||||
t.Errorf("sanitizeDate() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got2 != tt.want2 {
|
||||
t.Errorf("sanitizeBirthday() = %v, want %v", got2, tt.want2)
|
||||
t.Errorf("sanitizeDate() = %v, want %v", got2, tt.want2)
|
||||
}
|
||||
if got3 != tt.want3 {
|
||||
t.Errorf("sanitizeBirthday() = %v, want %v", got3, tt.want3)
|
||||
t.Errorf("sanitizeDate() = %v, want %v", got3, tt.want3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ Der Mailcow Birthday Daemon synchronisiert automatisch Geburtstagskalender für
|
||||
## Kontakte und Geburtstage
|
||||
|
||||
- Alle Kontakte aus sämtlichen Adressbüchern werden abgerufen und die Geburtstagsinformationen je Benutzer extrahiert.
|
||||
- Zusätzlich zu Geburtstagen werden auch Jahrestage (`ANNIVERSARY`-Feld nach vCard 4.0 / RFC 6350) ausgelesen. Dieses Feld wird von allen gängigen Clients unterstützt (Android, iOS, Thunderbird).
|
||||
- Geburtstags-Events erhalten das Präfix 🎂, Jahrestags-Events das Präfix 💍 – so sind beide Typen im Kalender sofort unterscheidbar.
|
||||
- Die daraus resultierenden Kalendereinträge werden im Voraus berechnet.
|
||||
- Aktuell fest eingestellt: 1 Jahr in der Vergangenheit, 10 Jahre in der Zukunft.
|
||||
- Selbstverständlich pro Mailbox isoliert – ein Benutzer sieht nur die Geburtstage seiner eigenen Kontakte.
|
||||
|
||||
Reference in New Issue
Block a user