diff --git a/README.md b/README.md index aedc990..137adbc 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,19 @@ There is an [example configuration file](./example-config.yml) that can be used as a base to create your own configuration, and there is a [birthdays example CSV file](./birthdays.csv) to load the data. +## Template file + +The template can be configured using the `birthday_template` config +parameter to point to the template file. The template uses the Go +Template Format, and has the following properties available: + +- `.Name`: the name of the person whose birthday we're notifying. +- `.Email`: the email of the person. +- `.Phone`: the phone of the person, as a string. +- `.YearOfBirth`: the year that the person was born, as number. +- `.MonthOfBirth`: the month that the person was born, as number. +- `.DayOfBirth`: the day that the person was born, as number. + ## Run the bot To get help for the bot command, run: @@ -39,7 +52,7 @@ $ make run - [X] Configure logger through config (levels and such) - [X] Reduce logger verbosity (through levels) - [ ] Add pictures to birthday notifications -- [ ] Create a configurable template to fill with each notification +- [X] Create a configurable template to fill with each notification - [ ] Create different message systems to use with the bot - [X] Telegram - [ ] Email diff --git a/example-config.yml b/example-config.yml index b9eb1d7..ec2ed99 100644 --- a/example-config.yml +++ b/example-config.yml @@ -1,5 +1,6 @@ --- birthday_file: birthdays.csv +birthday_template: ./birthday_message.tmpl logger: level: debug diff --git a/model/birthdays.go b/model/birthdays.go index c1bd058..1de971a 100644 --- a/model/birthdays.go +++ b/model/birthdays.go @@ -15,6 +15,17 @@ type Birthday struct { DayOfBirth int } +func (b *Birthday) ToMap() map[string]any { + return map[string]any{ + "Name": b.Name, + "Email": b.Email, + "Phone": b.Phone, + "YearOfBirth": b.YearOfBirth, + "MonthOfBirth": b.MonthOfBirth, + "DayOfBirth": b.DayOfBirth, + } +} + func NewBirthdayFromRecord(record []string) (*Birthday, error) { if len(record) != 4 { return nil, fmt.Errorf("invalid length %d for record", len(record)) diff --git a/model/config.go b/model/config.go index d4b9d09..c90e06d 100644 --- a/model/config.go +++ b/model/config.go @@ -18,11 +18,11 @@ var ( type Config struct { BirthdayFile string `yaml:"birthday_file"` + BirthdayTemplate string `yaml:"birthday_template"` Logger *LoggerConfig `yaml:"logger"` TelegramNotifications *TelegramNotificationsConfig `yaml:"telegram_notifications"` } -// ToDo: to be implemented func (c *Config) IsValid() error { if c.BirthdayFile == "" { return ErrConfigBirthdayFileEmpty diff --git a/notification/mocks/service_mock.go b/notification/mocks/service_mock.go index 8700f33..719adba 100644 --- a/notification/mocks/service_mock.go +++ b/notification/mocks/service_mock.go @@ -6,6 +6,7 @@ package mocks import ( reflect "reflect" + template "text/template" model "git.ctrlz.es/mgdelacroix/birthdaybot/model" gomock "github.com/golang/mock/gomock" @@ -35,15 +36,15 @@ func (m *MockNotificationService) EXPECT() *MockNotificationServiceMockRecorder } // Notify mocks base method. -func (m *MockNotificationService) Notify(arg0 *model.Birthday) error { +func (m *MockNotificationService) Notify(arg0 *model.Birthday, arg1 *template.Template) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Notify", arg0) + ret := m.ctrl.Call(m, "Notify", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // Notify indicates an expected call of Notify. -func (mr *MockNotificationServiceMockRecorder) Notify(arg0 interface{}) *gomock.Call { +func (mr *MockNotificationServiceMockRecorder) Notify(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notify", reflect.TypeOf((*MockNotificationService)(nil).Notify), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notify", reflect.TypeOf((*MockNotificationService)(nil).Notify), arg0, arg1) } diff --git a/notification/service.go b/notification/service.go index debedef..9098b8b 100644 --- a/notification/service.go +++ b/notification/service.go @@ -2,9 +2,11 @@ package notification import ( + "text/template" + "git.ctrlz.es/mgdelacroix/birthdaybot/model" ) type NotificationService interface { - Notify(*model.Birthday) error + Notify(*model.Birthday, *template.Template) error } diff --git a/notification/service_telegram.go b/notification/service_telegram.go index 77d7a68..4c94465 100644 --- a/notification/service_telegram.go +++ b/notification/service_telegram.go @@ -1,8 +1,10 @@ package notification import ( + "bytes" "fmt" "strconv" + "text/template" "git.ctrlz.es/mgdelacroix/birthdaybot/model" "github.com/charmbracelet/log" @@ -35,9 +37,19 @@ func NewTelegramNotificationService(logger *log.Logger, config *model.TelegramNo }, nil } -func (tns *TelegramNotificationService) Notify(birthday *model.Birthday) error { - // ToDo: introduce templates here - msgText := fmt.Sprintf("It's %s's birthday! You can reach them out at %s or %s", birthday.Name, birthday.Email, birthday.Phone) +func (tns *TelegramNotificationService) Notify(birthday *model.Birthday, template *template.Template) error { + var msgText string + if template != nil { + var stringBuffer bytes.Buffer + if err := template.Execute(&stringBuffer, birthday.ToMap()); err != nil { + return fmt.Errorf("cannot execute template for birthday: %w", err) + } + + msgText = stringBuffer.String() + } else { + msgText = fmt.Sprintf("It's %s's birthday! You can reach them out at %s or %s", birthday.Name, birthday.Email, birthday.Phone) + } + chatID, err := strconv.Atoi(tns.config.ChannelID) if err != nil { return fmt.Errorf("cannot parse ChannelID: %w", err) diff --git a/sample.tmpl b/sample.tmpl new file mode 100644 index 0000000..54ebaef --- /dev/null +++ b/sample.tmpl @@ -0,0 +1 @@ +¡Mañana es el cumpleaños de {{.Name}}! Puedes felicitarle o bien escribiendo a {{.Email}} o bien llamando al número {{.Phone}} \ No newline at end of file diff --git a/server/server.go b/server/server.go index de70304..2811a7e 100644 --- a/server/server.go +++ b/server/server.go @@ -3,6 +3,7 @@ package server import ( "errors" "fmt" + "text/template" "git.ctrlz.es/mgdelacroix/birthdaybot/model" "git.ctrlz.es/mgdelacroix/birthdaybot/notification" @@ -22,6 +23,7 @@ type Server struct { workers []Worker birthdays []*model.Birthday notificationServices []notification.NotificationService + tmpl *template.Template } func createNotificationServices(logger *log.Logger, config *model.Config) ([]notification.NotificationService, error) { @@ -84,6 +86,16 @@ func New(options ...Option) (*Server, error) { srv.workers = []Worker{NewSimpleWorker(srv)} } + if srv.config.BirthdayTemplate != "" { + srv.Logger.Debug("parsing birthday template", "file", srv.config.BirthdayTemplate) + + var err error + srv.tmpl, err = template.ParseFiles(srv.config.BirthdayTemplate) + if err != nil { + return nil, fmt.Errorf("cannot parse template file %q: %w", srv.config.BirthdayTemplate, err) + } + } + return srv, nil } @@ -106,7 +118,7 @@ func (s *Server) Stop() { func (s *Server) Notify(birthday *model.Birthday) error { errs := []error{} for _, service := range s.notificationServices { - err := service.Notify(birthday) + err := service.Notify(birthday, s.tmpl) if err != nil { errs = append(errs, err) }