From 0dedea464b0af425bfdeea7023c6f21ae1dd34f7 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Wed, 4 Sep 2024 19:15:38 +0200 Subject: [PATCH 1/3] add reaction action for rescheduling repeating events --- internal/cmd/run.go | 1 + .../actions/reaction/reschdule_repeating.go | 91 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 internal/connectors/matrix/actions/reaction/reschdule_repeating.go diff --git a/internal/cmd/run.go b/internal/cmd/run.go index f4cc3083..9845c010 100644 --- a/internal/cmd/run.go +++ b/internal/cmd/run.go @@ -202,6 +202,7 @@ func assembleMatrixConfig(config *Config, icalConnector ical.Service) *matrix.Co &reaction.DeleteEventAction{}, &reaction.AddTimeAction{}, &reaction.MarkDoneAction{}, + &reaction.RescheduleRepeatingAction{}, ) cfg.BridgeServices = &matrix.BridgeServices{ diff --git a/internal/connectors/matrix/actions/reaction/reschdule_repeating.go b/internal/connectors/matrix/actions/reaction/reschdule_repeating.go new file mode 100644 index 00000000..c51d0352 --- /dev/null +++ b/internal/connectors/matrix/actions/reaction/reschdule_repeating.go @@ -0,0 +1,91 @@ +package reaction + +import ( + "time" + + "github.com/CubicrootXYZ/gologger" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix" + matrixdb "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/database" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/mautrixcl" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/messenger" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/database" +) + +// RescheduleRepeatingAction takes care of rescheduling a repeating event. +type RescheduleRepeatingAction struct { + logger gologger.Logger + client mautrixcl.Client + messenger messenger.Messenger + matrixDB matrixdb.Service + db database.Service +} + +// Configure is called on startup and sets all dependencies. +func (action *RescheduleRepeatingAction) Configure(logger gologger.Logger, client mautrixcl.Client, messenger messenger.Messenger, matrixDB matrixdb.Service, db database.Service, _ *matrix.BridgeServices) { + action.logger = logger + action.client = client + action.matrixDB = matrixDB + action.db = db + action.messenger = messenger +} + +// Name of the action. +func (action *RescheduleRepeatingAction) Name() string { + return "Reschedule Repeating Event" +} + +// GetDocu returns the documentation for the action. +func (action *RescheduleRepeatingAction) GetDocu() (title, explaination string, examples []string) { + return "Reschedule Repeating Event", + "React with a 🔂 to get reminded again in 1 hour without changing the next repeat cycle.", + []string{"🔂"} +} + +// Selector defines on which reactions this action should be called. +func (action *RescheduleRepeatingAction) Selector() []string { + return []string{"🔂"} +} + +// HandleEvent is where the reaction event and the related message get's send to if it matches the Selector. +func (action *RescheduleRepeatingAction) HandleEvent(event *matrix.ReactionEvent, reactionToMessage *matrixdb.MatrixMessage) { + // TODO tests & add new reaction + l := action.logger.WithFields( + map[string]any{ + "reaction": event.Content.RelatesTo.Key, + "room": reactionToMessage.RoomID, + "related_message": reactionToMessage.ID, + "user": event.Event.Sender, + }, + ) + if reactionToMessage.EventID == nil || reactionToMessage.Event == nil { + l.Infof("skipping because message does not relate to any event") + return + } + + // Clone the event without being repetitive. + newEvt := &database.Event{ + Time: time.Now().Add(time.Hour), + Duration: reactionToMessage.Event.Duration, + Message: reactionToMessage.Event.Message, + Active: true, + ChannelID: reactionToMessage.Event.ChannelID, + InputID: reactionToMessage.Event.InputID, + } + _, err := action.db.NewEvent(newEvt) + if err != nil { + l.Err(err) + _ = action.messenger.SendMessageAsync(messenger.PlainTextMessage( + "Whoopsie, can not update the event as requested.", + event.Room.RoomID, + )) + return + } + + err = action.messenger.DeleteMessageAsync(&messenger.Delete{ + ExternalIdentifier: reactionToMessage.ID, + ChannelExternalIdentifier: reactionToMessage.Room.RoomID, + }) + if err != nil { + l.Err(err) + } +} From 8cf23e8860de2c6b56cfe12072243a8dadf36fe7 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Thu, 3 Oct 2024 16:03:55 +0200 Subject: [PATCH 2/3] add tests --- docker-compose.yml | 2 - .../actions/reaction/reschdule_repeating.go | 1 - .../reaction/reschedule_repeating_test.go | 121 ++++++++++++++++++ internal/connectors/matrix/send_reminders.go | 2 +- 4 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 internal/connectors/matrix/actions/reaction/reschedule_repeating_test.go diff --git a/docker-compose.yml b/docker-compose.yml index 1b3df3f0..c9e0874d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,6 @@ services: environment: - 'MYSQL_ROOT_PASSWORD=mypass' - 'MYSQL_DATABASE=remindme' - command: - - "--default-authentication-plugin=mysql_native_password" ports: - 3306:3306 diff --git a/internal/connectors/matrix/actions/reaction/reschdule_repeating.go b/internal/connectors/matrix/actions/reaction/reschdule_repeating.go index c51d0352..a81974e7 100644 --- a/internal/connectors/matrix/actions/reaction/reschdule_repeating.go +++ b/internal/connectors/matrix/actions/reaction/reschdule_repeating.go @@ -48,7 +48,6 @@ func (action *RescheduleRepeatingAction) Selector() []string { // HandleEvent is where the reaction event and the related message get's send to if it matches the Selector. func (action *RescheduleRepeatingAction) HandleEvent(event *matrix.ReactionEvent, reactionToMessage *matrixdb.MatrixMessage) { - // TODO tests & add new reaction l := action.logger.WithFields( map[string]any{ "reaction": event.Content.RelatesTo.Key, diff --git a/internal/connectors/matrix/actions/reaction/reschedule_repeating_test.go b/internal/connectors/matrix/actions/reaction/reschedule_repeating_test.go new file mode 100644 index 00000000..c8b4d429 --- /dev/null +++ b/internal/connectors/matrix/actions/reaction/reschedule_repeating_test.go @@ -0,0 +1,121 @@ +package reaction_test + +import ( + "errors" + "testing" + + "github.com/CubicrootXYZ/gologger" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/actions/reaction" + matrixdb "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/database" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/mautrixcl" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/messenger" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/connectors/matrix/tests" + "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/database" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestRescheduleRepeatAction(t *testing.T) { + action := &reaction.RescheduleRepeatingAction{} + + assert.NotEmpty(t, action.Name()) + + title, desc, examples := action.GetDocu() + assert.NotEmpty(t, title) + assert.NotEmpty(t, desc) + assert.NotEmpty(t, examples) + + assert.NotNil(t, action.Selector()) +} + +func TestRescheduleRepeatingAction_Selector(t *testing.T) { + action := &reaction.RescheduleRepeatingAction{} + + examples := []string{} + + _, _, examplesFromDocu := action.GetDocu() + examples = append(examples, examplesFromDocu...) + + reactions := action.Selector() + for _, example := range examples { + matches := false + for _, reaction := range reactions { + if example == reaction { + matches = true + break + } + } + assert.Truef(t, matches, "%s is not part of reactions", example) + } +} + +func TestRescheduleRepeatingAction_HandleEvent(t *testing.T) { + // Setup + ctrl := gomock.NewController(t) + defer ctrl.Finish() + db := database.NewMockService(ctrl) + matrixDB := matrixdb.NewMockService(ctrl) + client := mautrixcl.NewMockClient(ctrl) + msngr := messenger.NewMockMessenger(ctrl) + + action := &reaction.RescheduleRepeatingAction{} + action.Configure( + gologger.New(gologger.LogLevelDebug, 0), + client, + msngr, + matrixDB, + db, + nil, + ) + + t.Run("success case", func(_ *testing.T) { + msg := tests.TestMessage( + tests.WithTestEvent(), + ) + + // Expectations + db.EXPECT().NewEvent(gomock.Any()).Return(nil, nil) + + msngr.EXPECT().DeleteMessageAsync(gomock.Any()).Return(nil) + + // Execute + action.HandleEvent(tests.TestReactionEvent( + tests.ReactionWithKey("✅"), + ), msg) + }) + + t.Run("new event fails", func(_ *testing.T) { + msg := tests.TestMessage( + tests.WithTestEvent(), + ) + + // Expectations + db.EXPECT().NewEvent(gomock.Any()).Return(nil, errors.New("test")) + + msngr.EXPECT().SendMessageAsync(messenger.PlainTextMessage( + "Whoopsie, can not update the event as requested.", + "!room123", + )).Return(nil) + + // Execute + action.HandleEvent(tests.TestReactionEvent( + tests.ReactionWithKey("✅"), + ), msg) + }) + + t.Run("delete message fails", func(_ *testing.T) { + msg := tests.TestMessage( + tests.WithTestEvent(), + ) + + // Expectations + db.EXPECT().NewEvent(gomock.Any()).Return(nil, nil) + + msngr.EXPECT().DeleteMessageAsync(gomock.Any()).Return(errors.New("test")) + + // Execute + action.HandleEvent(tests.TestReactionEvent( + tests.ReactionWithKey("✅"), + ), msg) + }) +} diff --git a/internal/connectors/matrix/send_reminders.go b/internal/connectors/matrix/send_reminders.go index a4feaa91..69ef80cb 100644 --- a/internal/connectors/matrix/send_reminders.go +++ b/internal/connectors/matrix/send_reminders.go @@ -7,7 +7,7 @@ import ( "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/daemon" ) -var ReminderReactions = []string{"✅", "▶️", "⏩", "1️⃣", "4️⃣"} +var ReminderReactions = []string{"✅", "▶️", "⏩", "1️⃣", "4️⃣", "🔂"} func (service *service) SendReminder(event *daemon.Event, output *daemon.Output) error { room, err := service.matrixDatabase.GetRoomByID(output.OutputID) From 63af3450e4fe666f8dbf6d713cfa3f935278c518 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Thu, 3 Oct 2024 16:08:18 +0200 Subject: [PATCH 3/3] only add new reaction to recurring events --- internal/connectors/matrix/send_reminders.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/connectors/matrix/send_reminders.go b/internal/connectors/matrix/send_reminders.go index 69ef80cb..bbe642ee 100644 --- a/internal/connectors/matrix/send_reminders.go +++ b/internal/connectors/matrix/send_reminders.go @@ -7,7 +7,8 @@ import ( "github.com/CubicrootXYZ/matrix-reminder-and-calendar-bot/internal/daemon" ) -var ReminderReactions = []string{"✅", "▶️", "⏩", "1️⃣", "4️⃣", "🔂"} +var ReminderReactions = []string{"✅", "▶️", "⏩", "1️⃣", "4️⃣"} +var ReminderReactionsRecurring = []string{"🔂"} func (service *service) SendReminder(event *daemon.Event, output *daemon.Output) error { room, err := service.matrixDatabase.GetRoomByID(output.OutputID) @@ -53,7 +54,12 @@ func (service *service) SendReminder(event *daemon.Event, output *daemon.Output) service.logger.Errorf("failed to save message to database: %v", err) } - for _, reaction := range ReminderReactions { + reactions := ReminderReactions + if event.RepeatInterval != nil { + reactions = append(reactions, ReminderReactionsRecurring...) + } + + for _, reaction := range reactions { err := service.messenger.SendReactionAsync(&messenger.Reaction{ Reaction: reaction, ChannelExternalIdentifier: room.RoomID,