Files
jules/engine/actions.go
T

240 lines
6.3 KiB
Go

package engine
import (
"context"
"errors"
"fmt"
"slices"
"time"
"github.com/d1nch8g/jules/database"
"github.com/d1nch8g/jules/engine/actions"
"github.com/d1nch8g/jules/engine/jtime"
"github.com/d1nch8g/jules/engine/prompt"
"github.com/google/uuid"
)
func (e *Engine) validateActions(ctx context.Context, actionSlice []any, promptCtx prompt.Context) error {
var errs []error
for _, actionAny := range actionSlice {
switch action := actionAny.(type) {
case actions.AddContact:
_, err := e.Database.Users().Get(ctx, uuid.MustParse(action.UUID), database.UserLookupByContactCode)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
errs = append(errs, fmt.Errorf("user with requested uuid %s not present in database, uuid might be wrong", action.UUID))
} else {
errs = append(errs, errors.New("unexpected db occured"))
}
}
case actions.AddNotification:
if action.Target == "self" {
continue
}
found := slices.ContainsFunc(promptCtx.Contacts, func(c database.Contact) bool {
return c.Name == action.Target
})
if !found {
errs = append(errs, fmt.Errorf("contact target %s is invalid, provide exact name", action.Target))
}
case actions.BindChat:
_, err := e.Database.Users().Get(ctx, uuid.MustParse(action.UUID), database.UserLookupByBindCode)
if err != nil {
if errors.Is(err, database.ErrNotFound) {
errs = append(errs, fmt.Errorf("user with requested uuid %s not present in database, uuid might be wrong", action.UUID))
} else {
errs = append(errs, errors.New("unexpected db occured"))
}
}
case actions.Message:
found := slices.ContainsFunc(promptCtx.Chats, func(c database.Chat) bool {
return action.Platform == c.Platform
})
if !found {
errs = append(errs, fmt.Errorf("message is invalid, platform %s might not connected for user", action.Platform))
}
case actions.SetChat:
found := slices.ContainsFunc(promptCtx.Chats, func(c database.Chat) bool {
return action.Chat == c.Platform
})
if !found {
errs = append(errs, fmt.Errorf("chat %s might not connected for user, not found", action.Chat))
}
}
}
return errors.Join(errs...)
}
func (e *Engine) runActions(ctx context.Context, actionSlice []any, user *database.User, promptCtx *prompt.Context) error {
for _, actionAny := range actionSlice {
switch action := actionAny.(type) {
case actions.BindChat:
targetUser, err := e.Database.Users().Get(ctx, uuid.MustParse(action.UUID), database.UserLookupByBindCode)
if err != nil {
return err
}
err = e.Database.Users().Delete(ctx, user.ID)
if err != nil {
return err
}
var errs []error
for _, chat := range promptCtx.Chats {
errs = append(errs, e.Database.Chats().Attach(ctx, targetUser.ID, chat.Platform, chat.Identifier))
}
for _, contact := range promptCtx.Contacts {
errs = append(errs, e.Database.Contacts().Add(ctx, &database.Contact{
OwnerID: targetUser.ID,
TargetID: contact.TargetID,
Name: contact.Name,
}))
}
for _, fact := range promptCtx.Facts {
errs = append(errs, e.Database.Facts().Add(ctx, targetUser.ID, fact.Value))
}
for _, notif := range promptCtx.IncomingNotifications {
if notif.InitiatorID == user.ID {
notif.InitiatorID = targetUser.ID
}
errs = append(errs, e.Database.Notifications().Push(ctx, &database.Notification{
ID: notif.ID,
UserID: targetUser.ID,
InitiatorID: notif.InitiatorID,
ScheduledAt: notif.ScheduledAt,
Content: notif.Content,
}))
}
for _, notif := range promptCtx.OutgoingNotificaions {
errs = append(errs, e.Database.Notifications().Push(ctx, &database.Notification{
ID: notif.ID,
UserID: notif.UserID,
InitiatorID: targetUser.ID,
ScheduledAt: notif.ScheduledAt,
Content: notif.Content,
}))
}
if err = errors.Join(errs...); err != nil {
return err
}
user = targetUser
case actions.Message:
var platformID string
for _, chat := range promptCtx.Chats {
if action.Platform == chat.Platform {
platformID = chat.Identifier
}
}
if err := e.Chats[action.Platform].Send(ctx, platformID, action.Text); err != nil {
return err
}
case actions.Wait:
time.Sleep(time.Duration(action.Ms) * time.Millisecond)
case actions.UpdateLang:
user.Language = action.Lang
if err := e.Database.Users().Update(ctx, user); err != nil {
return err
}
case actions.UpdateTZ:
user.Timezone = action.TZ
if err := e.Database.Users().Update(ctx, user); err != nil {
return err
}
case actions.SetChat:
user.PreferredChat = action.Chat
if err := e.Database.Users().Update(ctx, user); err != nil {
return err
}
case actions.AddFact:
if err := e.Database.Facts().Add(ctx, user.ID, action.Value); err != nil {
return err
}
case actions.RemoveFact:
if err := e.Database.Facts().Delete(ctx, user.ID, action.Value); err != nil {
if errors.Is(err, database.ErrNotFound) {
continue
}
return err
}
case actions.AddContact:
contactUser, err := e.Database.Users().Get(ctx, uuid.MustParse(action.UUID), database.UserLookupByContactCode)
if err != nil {
return err
}
err = e.Database.Contacts().Add(ctx, &database.Contact{
OwnerID: user.ID,
TargetID: contactUser.ID,
Name: action.Name,
})
if err != nil {
return err
}
case actions.AddNotification:
t, _ := jtime.ToUTC(action.Time, user.Timezone)
initiatorID := user.ID
if action.Target != "self" {
for _, contact := range promptCtx.Contacts {
initiatorID = contact.TargetID
}
}
err := e.Database.Notifications().Push(ctx, &database.Notification{
ID: uuid.New(),
UserID: user.ID,
InitiatorID: initiatorID,
ScheduledAt: t,
Content: action.Content,
})
if err != nil {
return err
}
case actions.RemoveNotification:
err := e.Database.Notifications().Delete(ctx, uuid.MustParse(action.UUID))
if err != nil {
if errors.Is(err, database.ErrNotFound) {
continue
}
}
case actions.Search:
// TODO: currently not supported
}
err := e.Database.Actions().Log(ctx, &database.Action{
UserID: user.ID,
ExecutedAt: time.Now(),
Payload: actions.Raw(actionAny),
})
if err != nil {
return err
}
}
return nil
}