This commit is contained in:
parent
21e4c434fd
commit
72c6dd6982
12 changed files with 695 additions and 48 deletions
178
internal/plugin/reminder/reminder.go
Normal file
178
internal/plugin/reminder/reminder.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package reminder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.nakama.town/fmartingr/butterrobot/internal/model"
|
||||
"git.nakama.town/fmartingr/butterrobot/internal/plugin"
|
||||
)
|
||||
|
||||
// Duration regex patterns to match reminders
|
||||
var (
|
||||
remindMePattern = regexp.MustCompile(`(?i)^!remindme\s(\d+)(y|mo|d|h|m|s)$`)
|
||||
)
|
||||
|
||||
// ReminderCreator is an interface for creating reminders
|
||||
type ReminderCreator interface {
|
||||
CreateReminder(platform, channelID, messageID, replyToID, userID, username, content string, triggerAt time.Time) (*model.Reminder, error)
|
||||
}
|
||||
|
||||
// Reminder is a plugin that sets reminders for messages
|
||||
type Reminder struct {
|
||||
plugin.BasePlugin
|
||||
creator ReminderCreator
|
||||
}
|
||||
|
||||
// New creates a new Reminder plugin
|
||||
func New(creator ReminderCreator) *Reminder {
|
||||
return &Reminder{
|
||||
BasePlugin: plugin.BasePlugin{
|
||||
ID: "reminder.remindme",
|
||||
Name: "Remind Me",
|
||||
Help: "Reply to a message with `!remindme <duration>` to set a reminder (e.g., `!remindme 2d` for 2 days, `!remindme 1y` for 1 year).",
|
||||
ConfigRequired: false,
|
||||
},
|
||||
creator: creator,
|
||||
}
|
||||
}
|
||||
|
||||
// OnMessage processes incoming messages
|
||||
func (r *Reminder) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
|
||||
// Only process replies to messages
|
||||
if msg.ReplyTo == "" {
|
||||
return []*model.Message{
|
||||
{
|
||||
Text: "Please reply to a message with `!remindme <duration>` to set a reminder.",
|
||||
Chat: msg.Chat,
|
||||
Channel: msg.Channel,
|
||||
ReplyTo: msg.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the message is a reminder command
|
||||
match := remindMePattern.FindStringSubmatch(msg.Text)
|
||||
if match == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the duration
|
||||
amount, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return []*model.Message{
|
||||
{
|
||||
Text: "Invalid duration format. Please use a number followed by y (years), mo (months), d (days), h (hours), m (minutes), or s (seconds).",
|
||||
Chat: msg.Chat,
|
||||
Channel: msg.Channel,
|
||||
Author: "bot",
|
||||
FromBot: true,
|
||||
Date: time.Now(),
|
||||
ReplyTo: msg.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the trigger time
|
||||
var duration time.Duration
|
||||
unit := match[2]
|
||||
switch strings.ToLower(unit) {
|
||||
case "y":
|
||||
duration = time.Duration(amount) * 365 * 24 * time.Hour
|
||||
case "mo":
|
||||
duration = time.Duration(amount) * 30 * 24 * time.Hour
|
||||
case "d":
|
||||
duration = time.Duration(amount) * 24 * time.Hour
|
||||
case "h":
|
||||
duration = time.Duration(amount) * time.Hour
|
||||
case "m":
|
||||
duration = time.Duration(amount) * time.Minute
|
||||
case "s":
|
||||
duration = time.Duration(amount) * time.Second
|
||||
default:
|
||||
return []*model.Message{
|
||||
{
|
||||
Text: "Invalid duration unit. Please use y (years), mo (months), d (days), h (hours), m (minutes), or s (seconds).",
|
||||
Chat: msg.Chat,
|
||||
Channel: msg.Channel,
|
||||
Author: "bot",
|
||||
FromBot: true,
|
||||
Date: time.Now(),
|
||||
ReplyTo: msg.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
triggerAt := time.Now().Add(duration)
|
||||
|
||||
// Determine the username for the reminder
|
||||
username := msg.Author
|
||||
if username == "" {
|
||||
// Try to extract username from message raw data
|
||||
if authorData, ok := msg.Raw["author"].(map[string]interface{}); ok {
|
||||
if name, ok := authorData["username"].(string); ok {
|
||||
username = name
|
||||
} else if name, ok := authorData["name"].(string); ok {
|
||||
username = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the reminder
|
||||
_, err = r.creator.CreateReminder(
|
||||
msg.Channel.Platform,
|
||||
msg.Chat,
|
||||
msg.ID,
|
||||
msg.ReplyTo,
|
||||
msg.Author,
|
||||
username,
|
||||
"", // No additional content for now
|
||||
triggerAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return []*model.Message{
|
||||
{
|
||||
Text: fmt.Sprintf("Failed to create reminder: %v", err),
|
||||
Chat: msg.Chat,
|
||||
Channel: msg.Channel,
|
||||
Author: "bot",
|
||||
FromBot: true,
|
||||
Date: time.Now(),
|
||||
ReplyTo: msg.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Format the acknowledgment message
|
||||
var confirmText string
|
||||
switch strings.ToLower(unit) {
|
||||
case "y":
|
||||
confirmText = fmt.Sprintf("I'll remind you about this message in %d year(s) on %s", amount, triggerAt.Format("Mon, Jan 2, 2006 at 15:04"))
|
||||
case "mo":
|
||||
confirmText = fmt.Sprintf("I'll remind you about this message in %d month(s) on %s", amount, triggerAt.Format("Mon, Jan 2 at 15:04"))
|
||||
case "d":
|
||||
confirmText = fmt.Sprintf("I'll remind you about this message in %d day(s) on %s", amount, triggerAt.Format("Mon, Jan 2 at 15:04"))
|
||||
case "h":
|
||||
confirmText = fmt.Sprintf("I'll remind you about this message in %d hour(s) at %s", amount, triggerAt.Format("15:04"))
|
||||
case "m":
|
||||
confirmText = fmt.Sprintf("I'll remind you about this message in %d minute(s) at %s", amount, triggerAt.Format("15:04"))
|
||||
case "s":
|
||||
confirmText = fmt.Sprintf("I'll remind you about this message in %d second(s)", amount)
|
||||
}
|
||||
|
||||
return []*model.Message{
|
||||
{
|
||||
Text: confirmText,
|
||||
Chat: msg.Chat,
|
||||
Channel: msg.Channel,
|
||||
Author: "bot",
|
||||
FromBot: true,
|
||||
Date: time.Now(),
|
||||
ReplyTo: msg.ID,
|
||||
},
|
||||
}
|
||||
}
|
164
internal/plugin/reminder/reminder_test.go
Normal file
164
internal/plugin/reminder/reminder_test.go
Normal file
|
@ -0,0 +1,164 @@
|
|||
package reminder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.nakama.town/fmartingr/butterrobot/internal/model"
|
||||
)
|
||||
|
||||
// MockCreator is a mock implementation of ReminderCreator for testing
|
||||
type MockCreator struct {
|
||||
reminders []*model.Reminder
|
||||
}
|
||||
|
||||
func (m *MockCreator) CreateReminder(platform, channelID, messageID, replyToID, userID, username, content string, triggerAt time.Time) (*model.Reminder, error) {
|
||||
reminder := &model.Reminder{
|
||||
ID: int64(len(m.reminders) + 1),
|
||||
Platform: platform,
|
||||
ChannelID: channelID,
|
||||
MessageID: messageID,
|
||||
ReplyToID: replyToID,
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
Content: content,
|
||||
TriggerAt: triggerAt,
|
||||
}
|
||||
m.reminders = append(m.reminders, reminder)
|
||||
return reminder, nil
|
||||
}
|
||||
|
||||
func TestReminderOnMessage(t *testing.T) {
|
||||
creator := &MockCreator{reminders: make([]*model.Reminder, 0)}
|
||||
plugin := New(creator)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message *model.Message
|
||||
expectResponse bool
|
||||
expectReminder bool
|
||||
}{
|
||||
{
|
||||
name: "Valid reminder command - years",
|
||||
message: &model.Message{
|
||||
Text: "!remindme 1y",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: true,
|
||||
expectReminder: true,
|
||||
},
|
||||
{
|
||||
name: "Valid reminder command - months",
|
||||
message: &model.Message{
|
||||
Text: "!remindme 3mo",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: true,
|
||||
expectReminder: true,
|
||||
},
|
||||
{
|
||||
name: "Valid reminder command - days",
|
||||
message: &model.Message{
|
||||
Text: "!remindme 2d",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: true,
|
||||
expectReminder: true,
|
||||
},
|
||||
{
|
||||
name: "Valid reminder command - hours",
|
||||
message: &model.Message{
|
||||
Text: "!remindme 5h",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: true,
|
||||
expectReminder: true,
|
||||
},
|
||||
{
|
||||
name: "Valid reminder command - minutes",
|
||||
message: &model.Message{
|
||||
Text: "!remindme 30m",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: true,
|
||||
expectReminder: true,
|
||||
},
|
||||
{
|
||||
name: "Valid reminder command - seconds",
|
||||
message: &model.Message{
|
||||
Text: "!remindme 60s",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: true,
|
||||
expectReminder: true,
|
||||
},
|
||||
{
|
||||
name: "Not a reply",
|
||||
message: &model.Message{
|
||||
Text: "!remindme 2d",
|
||||
ReplyTo: "",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: false,
|
||||
expectReminder: false,
|
||||
},
|
||||
{
|
||||
name: "Not a reminder command",
|
||||
message: &model.Message{
|
||||
Text: "hello world",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: false,
|
||||
expectReminder: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid duration format",
|
||||
message: &model.Message{
|
||||
Text: "!remindme abc",
|
||||
ReplyTo: "original-message-id",
|
||||
Author: "testuser",
|
||||
Channel: &model.Channel{Platform: "test"},
|
||||
},
|
||||
expectResponse: false,
|
||||
expectReminder: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
initialCount := len(creator.reminders)
|
||||
responses := plugin.OnMessage(tt.message, nil)
|
||||
|
||||
if tt.expectResponse && len(responses) == 0 {
|
||||
t.Errorf("Expected response, but got none")
|
||||
}
|
||||
|
||||
if !tt.expectResponse && len(responses) > 0 {
|
||||
t.Errorf("Expected no response, but got %d", len(responses))
|
||||
}
|
||||
|
||||
if tt.expectReminder && len(creator.reminders) != initialCount+1 {
|
||||
t.Errorf("Expected reminder to be created, but it wasn't")
|
||||
}
|
||||
|
||||
if !tt.expectReminder && len(creator.reminders) != initialCount {
|
||||
t.Errorf("Expected no reminder to be created, but got %d", len(creator.reminders)-initialCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue