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  }