package comm import ( "errors" "fmt" "log" "github.com/eternal-flame-AD/yoake/internal/comm/email" "github.com/eternal-flame-AD/yoake/internal/comm/gotify" "github.com/eternal-flame-AD/yoake/internal/comm/model" "github.com/eternal-flame-AD/yoake/internal/comm/telegram" "github.com/eternal-flame-AD/yoake/internal/db" "github.com/eternal-flame-AD/yoake/internal/util" ) type Communicator struct { commMethods map[string]model.CommMethod fallbackCommunicators []string } var ( errMethodNotSupported = errors.New("method not supported") ) func (c *Communicator) actualSendGenericMessage(tryMethod string, message *model.GenericMessage) error { if comm, ok := c.commMethods[tryMethod]; ok { if convertedMsg, err := ConvertGenericMessage(message, comm.SupportedMIME()); err == nil { err := comm.SendGenericMessage(convertedMsg) if err != nil { return err } message.ThreadID = convertedMsg.ThreadID return nil } else { return err } } return errMethodNotSupported } // GetMethod returns the method with the given name. func (c *Communicator) GetMethod(method string) model.CommMethod { return c.commMethods[method] } // GetMethodsByMIME returns a list of methods that support the given MIME type as the message type, MIME convertions were considered. func (c *Communicator) GetMethodsByMIME(mime string) []model.CommMethod { var result []model.CommMethod for _, comm := range c.commMethods { if util.Contain(ConvertOutMIMEToSupportedInMIME(comm.SupportedMIME()), mime) { result = append(result, comm) } } return result } type ErrorSentWithFallback struct { OriginalError error OrignalMethod string FallbackMethod string } func (e ErrorSentWithFallback) Error() string { return fmt.Sprintf("used fallback method %s because original method %s reeported error: %v", e.FallbackMethod, e.OrignalMethod, e.OriginalError) } // SendGenericMethods sends a message using the preferred method // if the preferred method failed to send the message, fallback methods will be tried, // and an ErrorSentWithFabback will be returned if any fallback method succeeded. // if fallback methods failed as well the original error will be returned. func (c *Communicator) SendGenericMessage(preferredMethod string, message *model.GenericMessage, force bool) error { if preferredMethod == "" { preferredMethod = c.fallbackCommunicators[0] } if origErr := c.actualSendGenericMessage(preferredMethod, message); origErr != nil { if force { log.Printf("Failed to send message using preferred method %s: %v", preferredMethod, origErr) return origErr } log.Printf("Failed to send message using preferred method %s: %v. trying fallback methods", preferredMethod, origErr) for _, fallback := range c.fallbackCommunicators { if fallback == preferredMethod { continue } if err := c.actualSendGenericMessage(fallback, message); err == nil { log.Printf("Sent message using fallback method %s", fallback) return ErrorSentWithFallback{ OriginalError: origErr, OrignalMethod: preferredMethod, FallbackMethod: fallback, } } else { log.Printf("Failed to send message using fallback method %s: %v", fallback, err) } } return origErr } return nil } func InitCommunicator(database db.DB) *Communicator { comm := &Communicator{ commMethods: make(map[string]model.CommMethod), } if emailHandler, err := email.NewHandler(); err == nil { comm.commMethods["email"] = emailHandler comm.fallbackCommunicators = append(comm.fallbackCommunicators, "email") } else { log.Printf("Failed to initialize email communicator: %v", err) } if gotifyHandler, err := gotify.NewClient(); err == nil { comm.commMethods["gotify"] = gotifyHandler comm.fallbackCommunicators = append(comm.fallbackCommunicators, "gotify") } else { log.Printf("Failed to initialize gotify communicator: %v", err) } if telegramHandler, err := telegram.NewClient(database); err == nil { comm.commMethods["telegram"] = telegramHandler comm.fallbackCommunicators = append(comm.fallbackCommunicators, "telegram") } else { log.Printf("Failed to initialize telegram communicator: %v", err) } return comm }