package handler import ( "context" "fmt" "log" "ticket-pimp/adapters" "ticket-pimp/internal/controller" "ticket-pimp/internal/domain" "github.com/bwmarrin/discordgo" ) type Handler struct { controller *controller.WorkflowController conf *domain.DiscordConfig tg adapters.IDummyTelegram } func NewHandler( controller *controller.WorkflowController, conf *domain.DiscordConfig, tg adapters.IDummyTelegram, ) *Handler { return &Handler{ controller: controller, conf: conf, tg: tg, } } func (h *Handler) Ping(s *discordgo.Session, i *discordgo.InteractionCreate) { err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Pong to: " + i.Member.User.Mention(), }, }) if err != nil { log.Println(err) } } // setFlag // sets tag with In progress and Done text to discords channel; func (h *Handler) setFlag(s *discordgo.Session, i *discordgo.InteractionCreate, tag *discordgo.ForumTag) error { th, err := s.Channel(i.ChannelID) if err != nil { return err } forum, err := s.Channel(th.ParentID) if err != nil { return err } // Проверка на существование тега в списке тегов: if len(forum.AvailableTags) != 0 { for _, some := range forum.AvailableTags { if some.Name == tag.Name { _, err := s.ChannelEditComplex(i.ChannelID, &discordgo.ChannelEdit{ AppliedTags: &[]string{some.ID}, }) if err != nil { return err } } } } return nil } // ListenPosts // ..listens to new posts in specific channel // to act them like a task func (h *Handler) ListenPosts(s *discordgo.Session, th *discordgo.ThreadCreate) { // Check if thread starter is not a bot, and thread started at the tasks channel; if th.ParentID != h.conf.IsTaskForum || th.OwnerID == s.State.User.ID { return } msgs, _ := s.ChannelMessages(th.ID, 1, "", "", "") msg, _ := s.ChannelMessage(th.ID, msgs[0].ID) if msg.Author.ID == s.State.User.ID { return } content := th.Name content += "\n" + msg.Content user, _ := s.GuildMember(th.GuildID, msg.Author.ID) t, err := h.controller.WriteTaskToDB(&domain.Task{ Description: content, Creator: user.User.Mention(), }) if err != nil { s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to write task to db, %v", err)) return } // [x] -- Отредактировать Thread name как для задачи _, err = s.ChannelEditComplex(th.ID, &discordgo.ChannelEdit{ Name: fmt.Sprintf("Task ID: %d, by %s", t.ID, t.Creator), }) if err != nil { log.Printf("th edition is not complete: %v", err) } // Fix the original task message: taskMessage, err := s.ChannelMessageSendComplex(th.ID, &discordgo.MessageSend{ Content: content, Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ discordgo.Button{ Label: "Start", Style: discordgo.SuccessButton, Disabled: false, CustomID: "task_start", }, discordgo.Button{ Label: "Close", Style: discordgo.DangerButton, Disabled: true, CustomID: "task_close", }, }, }, }, }) if err != nil { log.Printf("th start message edition is not complete: %v", err) } err = h.controller.UpdateTasksMessageID(context.TODO(), taskMessage.ID, t.ID) if err != nil { s.ChannelMessageSend(th.ID, fmt.Sprintf("unable to update task at the db, %v", err)) return } } // handleTaskBurrons // .. handler function to work with the Action Buttons over a task func (h *Handler) HandleTaskButtons(s *discordgo.Session, i *discordgo.InteractionCreate) { // Send an empty interaction response; ---------------------------------------------------------------- s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseUpdateMessage, Data: &discordgo.InteractionResponseData{}, }) // Get assignee value; --------------------------------------------------------------------------------- user := i.Member.User.Mention() var ( opt int = -1 doneButtonIsDisabled bool = false state domain.TaskState = domain.NewTaskState() message string ) // Check what flow was touched: ------------------------------------------------------------------------- switch i.Interaction.MessageComponentData().CustomID { case "task_start": opt = 0 doneButtonIsDisabled = false state = domain.InrpogressTaskState() message = "взята в работу" case "task_close": opt = 1 doneButtonIsDisabled = true state = domain.DoneTaskState() message = "выполнена" } // Send the task update to db -------------------------------------------------------------------------- convertable, err := h.controller.UpdateTask(i.Message.ID, opt, user) if err != nil { s.ChannelMessageSend(i.ChannelID, fmt.Sprintf("Unable to update task at the db w/ error: %v", err)) return } // Map DB's response to domain.Task: ------------------------------------------------------------------- task := convertable. ExtractDomain() newContent := task.DiscordMessage(state) // Send message to the creator in Telegram: ------------------------------------------------------------- if task.CreatorLink != "" { h.tg.DummyNotification( task.CreatorLink, fmt.Sprintf("Task ID: %d %s", task.ID, message)) } // Send a message to the thread about the task was started: --------------------------------------------- _, err = s.ChannelMessageSendComplex(i.ChannelID, &discordgo.MessageSend{ Content: newContent, }) if err != nil { log.Printf("error while sending start task message: %v", err) } // Fix the original task message: ---------------------------------------------------------------------- _, err = s.ChannelMessageEditComplex(&discordgo.MessageEdit{ Channel: i.ChannelID, ID: i.Message.ID, Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ discordgo.Button{ Label: "Close", Style: discordgo.DangerButton, Disabled: doneButtonIsDisabled, CustomID: "task_close", }, }, }, }, }) if err != nil { log.Printf("th start message edition is not complete: %v", err) } // [ ] Устанавливаем тэги статуса на тред --------------------------------------------------------------------- // err = h.setFlag(s, i, &h.Tags[opt]) // if err != nil { // log.Printf("error while `start` tag setting: %v", err) // } } func (h *Handler) CreateFolder(s *discordgo.Session, i *discordgo.InteractionCreate) { const ( nameOption string = "folder_name" ) // Моментальный ответ для избежания столкновения с протуханием токена initialResponse := discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsEphemeral, Content: "👩‍🍳 Cooking your query..", }, } s.InteractionRespond(i.Interaction, &initialResponse) // Определение переменной для ответа var result string = "unexpected result" // Определение выбранных вариантов ответа options := i.ApplicationCommandData().Options optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) for _, opt := range options { optionMap[opt.Name] = opt } // Creating request: var req controller.FolderRequest name, insertedValueNotNil := optionMap[nameOption] dchan, err := s.Channel(i.ChannelID) if err != nil { log.Printf("error while identifying channel: %v", err) } else { if dchan.ParentID == h.conf.IsProjectChannel { req.ChannelID = dchan.ID if insertedValueNotNil { req.InsertedName = name.StringValue() } } else { req.ChannelID = "" if insertedValueNotNil { req.InsertedName = name.StringValue() } } } // Making request: resp := h.controller.CreateFolder(context.TODO(), req) if resp.Project == nil { result = "Надо написать имя для папки, или создать папку из проекта!" } else { result = resp.Project.DiscordString() if resp.Message != nil { result += "Errors: " + resp.Message.Error() } } h.defaultFollowUp(result, s, i) } func (h *Handler) CreateGit(s *discordgo.Session, i *discordgo.InteractionCreate) { const ( repoType = "repo_type" projectRepo = "project_repo" buildRepo = "build_repo" nameOption = "repo_name" ) // Моментальный ответ для избежания столкновения с протуханием токена initialResponse := discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsEphemeral, Content: "👩‍🍳 Cooking your query..", }, } s.InteractionRespond(i.Interaction, &initialResponse) // Определение переменной для ответа var result string = "unexpected result" // Access options in the order provided by the user. options := i.ApplicationCommandData().Options // Or convert the slice into a map optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) for _, opt := range options { optionMap[opt.Name] = opt } // Creating request: var req controller.GitRequest name, insertedValueNotNil := optionMap[nameOption] isBuild := optionMap[repoType] switch isBuild.StringValue() { case buildRepo: req.IsBuildGit = true case projectRepo: req.IsBuildGit = false } dchan, err := s.Channel(i.ChannelID) if err != nil { log.Printf("error while identifying channel: %v", err) } else { if dchan.ParentID == h.conf.IsProjectChannel { req.ChannelID = dchan.ID if insertedValueNotNil { req.InsertedName = name.StringValue() } } else { req.ChannelID = "" if insertedValueNotNil { req.InsertedName = name.StringValue() } } } // Making request: resp := h.controller.CreateGit(context.TODO(), req) if resp.Project == nil { if resp.Message != nil { result = resp.Message.Error() } } else { result = resp.Project.DiscordString() if resp.Message != nil { result += "Errors: " + resp.Message.Error() } } h.defaultFollowUp(result, s, i) } // PROJECT func (h *Handler) ProjectInfo(s *discordgo.Session, i *discordgo.InteractionCreate) { // Моментальный ответ для избежания столкновения с протуханием токена initialResponse := discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsEphemeral, Content: "👩‍🍳 Cooking your query..", }, } s.InteractionRespond(i.Interaction, &initialResponse) var result string // Get channel from the request dchan, err := s.Channel(i.ChannelID) if err != nil { result = "unable to get channel from the message" } else { project, err := h.controller.GetProjectByChannelID(context.TODO(), dchan.ID) if err != nil { result = err.Error() } else { if project != nil { result = project.DiscordString() if err != nil { result += "Errors: " + err.Error() } } else { result = "Something wrong with retrieving project from db" } } } h.defaultFollowUp(result, s, i) } func (h *Handler) InitChannelAsProject(s *discordgo.Session, i *discordgo.InteractionCreate) { // Моментальный ответ для избежания столкновения с протуханием токена initialResponse := discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsEphemeral, Content: "👩‍🍳 Cooking your query..", }, } s.InteractionRespond(i.Interaction, &initialResponse) var result string // Get channel from the request dchan, err := s.Channel(i.ChannelID) if err != nil { result = "unable to get channel from the message" } else { if dchan.ParentID != h.conf.IsProjectChannel { // Sending result: _, err := s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ Content: "This channel is not at the project's group", }) if err != nil { s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ Content: fmt.Sprintf("Something went wrong: %v", err), }) return } return } // Access options in the order provided by the user. options := i.ApplicationCommandData().Options // Or convert the slice into a map optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) for _, opt := range options { optionMap[opt.Name] = opt } if option, ok := optionMap["key"]; ok { var errMsg error = nil project, err := h.controller.InitProjectInChannel(context.TODO(), i.ChannelID, option.StringValue()) if err != nil { result = fmt.Sprintf("unable to init project: %v", err) } else { result = project.DiscordString() if errMsg != nil { result += "Errors: " + errMsg.Error() } } } } h.defaultFollowUp(result, s, i) } func (h *Handler) CreateTicket(s *discordgo.Session, i *discordgo.InteractionCreate) { // Моментальный ответ для избежания столкновения с протуханием токена initialResponse := discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsEphemeral, Content: "👩‍🍳 Cooking your query..", }, } s.InteractionRespond(i.Interaction, &initialResponse) var result string // Access options in the order provided by the user. options := i.ApplicationCommandData().Options // Or convert the slice into a map optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) for _, opt := range options { optionMap[opt.Name] = opt } if option, ok := optionMap["project_name"]; ok { dchan, err := s.GuildChannelCreate(i.GuildID, option.StringValue(), discordgo.ChannelTypeGuildText) if err != nil { result = fmt.Sprintf("chan creation problem: %v\n", err) } else { p, err := h.controller.ProjectCreate(context.TODO(), domain.Project{ ChannelID: dchan.ID, }) if err != nil { result = fmt.Sprintf("unable to create project: %v\n", err) _, err := s.ChannelDelete(dchan.ID) if err != nil { result += fmt.Sprintf("\nunable to clean channel: %v\n", err) } } else { edit := discordgo.ChannelEdit{ Name: p.ShortName, ParentID: "1150719794853716028", } dchan, err = s.ChannelEdit(dchan.ID, &edit) if err != nil { result = fmt.Sprintf("channel %s created, but unable to edit follow up message: %v\n", p.ShortName, err) } else { _, err = s.ChannelMessageSend(dchan.ID, "Hello!") if err != nil { log.Printf("message send problem: %v\n", err) } result = "Project " + p.ShortName + "Was created" } } } } h.defaultFollowUp(result, s, i) }