github.com/sharovik/devbot@v1.0.1-0.20240308094637-4a0387c40516/internal/service/message/message_service.go (about) 1 package message 2 3 import ( 4 "fmt" 5 "time" 6 7 _time "github.com/sharovik/devbot/internal/service/time" 8 9 "github.com/sharovik/devbot/internal/database" 10 "github.com/sharovik/devbot/internal/service/message/conversation" 11 12 "github.com/sharovik/devbot/internal/service/history" 13 14 "github.com/sharovik/devbot/internal/container" 15 "github.com/sharovik/devbot/internal/dto" 16 "github.com/sharovik/devbot/internal/log" 17 ) 18 19 var messagesReceived = map[string]dto.BaseChatMessage{} 20 21 func answerToMessage(m dto.BaseChatMessage) error { 22 response, statusCode, err := container.C.MessageClient.SendMessage(m) 23 if err != nil { 24 log.Logger().AddError(err). 25 Interface("response", response). 26 Interface("status", statusCode). 27 Msg("Failed to sent answer message") 28 return err 29 } 30 31 log.Logger().Info().Interface("message", m).Msg("Message sent") 32 return nil 33 } 34 35 // SendAnswerForReceivedMessage method which sends the answer by specific message 36 func SendAnswerForReceivedMessage(message dto.BaseChatMessage) error { 37 if err := answerToMessage(message); err != nil { 38 log.Logger().AddError(err).Msg("Failed to sent answer message") 39 return err 40 } 41 42 messageExpired(message) 43 return nil 44 } 45 46 func messageExpired(message dto.BaseChatMessage) { 47 delete(messagesReceived, message.Channel) 48 } 49 50 func refreshPreparedMessages() { 51 log.Logger().Debug(). 52 Interface("answers_prepared", messagesReceived). 53 Msg("Trigger refresh messages") 54 55 for channelID, msg := range messagesReceived { 56 if time.Since(msg.Ts).Seconds() >= 1 { 57 log.Logger().Debug(). 58 Time("message_ts", msg.Ts). 59 Interface("message_object", msg). 60 Msg("Message timestamp expired") 61 delete(messagesReceived, channelID) 62 } 63 } 64 } 65 66 func prepareAnswer(message *dto.SlackResponseEventMessage, dm dto.DictionaryMessage) (dto.BaseChatMessage, error) { 67 log.Logger().StartMessage("Answer prepare") 68 69 //If we don't have any answer, we trigger the event for unknown question 70 if dm.Answer == "" { 71 if !container.C.Config.LearningEnabled { 72 return triggerUnknownAnswerScenario(message) 73 } 74 75 //@todo: trigger learn scenario 76 } 77 78 responseMessage := dto.BaseChatMessage{ 79 Channel: message.Channel, 80 Text: dm.Answer, 81 ThreadTS: message.ThreadTS, 82 AsUser: true, 83 Ts: _time.Service.Now(), 84 OriginalMessage: message.ToBaseOriginalMessage(), 85 } 86 87 log.Logger().FinishMessage("Answer prepare") 88 return responseMessage, nil 89 } 90 91 func triggerUnknownAnswerScenario(message *dto.SlackResponseEventMessage) (answer dto.BaseChatMessage, err error) { 92 message.Text = fmt.Sprintf("similar questions %s", message.Text) 93 return dto.BaseChatMessage{ 94 Channel: message.Channel, 95 Text: "Hmmm", 96 AsUser: true, 97 ThreadTS: message.ThreadTS, 98 Ts: _time.Service.Now(), 99 OriginalMessage: message.ToBaseOriginalMessage(), 100 DictionaryMessage: dto.DictionaryMessage{ 101 ReactionType: "unknownquestion", 102 }, 103 }, nil 104 } 105 106 // TriggerScenario triggers the scenario for selected channel 107 func TriggerScenario(channel string, scenario database.EventScenario, shouldRemember bool) error { 108 dmAnswer := dto.DictionaryMessage{ 109 ScenarioID: scenario.ID, 110 EventID: scenario.EventID, 111 ReactionType: scenario.EventName, 112 } 113 114 conversation.AddConversation(scenario, dto.BaseChatMessage{ 115 Channel: channel, 116 AsUser: true, 117 Text: scenario.GetUnAnsweredQuestion(), 118 Ts: _time.Service.Now(), 119 DictionaryMessage: dmAnswer, 120 OriginalMessage: dto.BaseOriginalMessage{ 121 Text: scenario.GetUnAnsweredQuestion(), 122 }, 123 }) 124 125 if err := TriggerAnswer(channel, conversation.GetConversation(channel).LastQuestion, shouldRemember); err != nil { 126 return err 127 } 128 129 return nil 130 } 131 132 // TriggerAnswer triggers an answer for received message 133 func TriggerAnswer(channel string, answerMessage dto.BaseChatMessage, shouldRemember bool) error { 134 if answerMessage.Text != "" { 135 if err := SendAnswerForReceivedMessage(answerMessage); err != nil { 136 log.Logger().AddError(err).Msg("Failed to send prepared answers") 137 conversation.FinaliseConversation(channel) 138 139 return err 140 } 141 } 142 143 if answerMessage.DictionaryMessage.IsHelpTriggered { 144 return nil 145 } 146 147 conversation.SetLastQuestion(answerMessage) 148 149 if answerMessage.DictionaryMessage.ReactionType == "" || container.C.DefinedEvents[answerMessage.DictionaryMessage.ReactionType] == nil { 150 log.Logger().Warn(). 151 Interface("answer", answerMessage). 152 Msg("Reaction type wasn't found") 153 return nil 154 } 155 156 activeConversation := conversation.GetConversation(channel) 157 if activeConversation.ScenarioID != int64(0) && !activeConversation.EventReadyToBeExecuted { 158 log.Logger().Info(). 159 Interface("conversation", activeConversation). 160 Msg("This conversation isn't finished yet, so event cannot be executed.") 161 return nil 162 } 163 164 go func() { 165 answer, err := container.C.DefinedEvents[answerMessage.DictionaryMessage.ReactionType].Execute(answerMessage) 166 if err != nil { 167 log.Logger().AddError(err).Msg("Failed to execute the event") 168 conversation.FinaliseConversation(channel) 169 } 170 171 if answer.Text != "" { 172 answerMessage.Text = answer.Text 173 if err = SendAnswerForReceivedMessage(answerMessage); err != nil { 174 log.Logger().AddError(err).Msg("Failed to send post-answer for selected event") 175 } 176 } 177 178 //We will trigger the event history save in case when we don't have open conversation 179 //or when we do have open conversation, but it is time to trigger the event execution 180 //so, we can store all variables 181 if shouldRemember && (conversation.GetConversation(answerMessage.Channel).ScenarioID == 0 || conversation.GetConversation(answerMessage.Channel).EventReadyToBeExecuted) { 182 history.RememberEventExecution(answerMessage) 183 } 184 185 if conversation.GetConversation(answerMessage.Channel).EventReadyToBeExecuted { 186 conversation.FinaliseConversation(channel) 187 } 188 }() 189 190 return nil 191 }