diff --git a/client/discord/discord.go b/client/discord/discord.go index 66964b9..c4ff776 100644 --- a/client/discord/discord.go +++ b/client/discord/discord.go @@ -20,99 +20,102 @@ var ( projectRepo string = "project_repo" buildRepo string = "build_repo" nameOption string = "repo_name" -) - -var tags = []discordgo.ForumTag{ - { - Name: "В работе", - Moderated: true, - EmojiName: "👩‍🍳", - }, - { - Name: "Готово", - Moderated: true, - EmojiName: "✅", - }, -} - -var commands = []discordgo.ApplicationCommand{ - { - Name: "ping", - Description: "pongs in a reply", - }, - { - Name: "init_project", - Description: "Connect project with Coda ID", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: "key", - Description: "Project's key from Coda.io", - Required: true, - MinLength: &minLength, - }, + tagsPreset = [3]discordgo.ForumTag{ + { + Name: "В работе", + Moderated: true, + EmojiName: "👩‍🍳", }, - }, - { - Name: "project", - Description: "Create new development ticket", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: "project_name", - Description: "Temporary project name", - Required: true, - MinLength: &minLength, - }, + { + Name: "Готово", + Moderated: true, + EmojiName: "✅", }, - }, - { - Name: "info", - Description: "Get project's info", - }, - { - Name: "repo", - Description: "Creates repository of selected type. Name used for projects channels only", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: repoType, - Description: "The type of repo", - Required: true, - Choices: []*discordgo.ApplicationCommandOptionChoice{ - { - Name: "Unity project repo", - Value: projectRepo, - }, - { - Name: "XCode build repo", - Value: buildRepo, - }, + { + Name: "Не начат", + Moderated: true, + EmojiName: "🚧", + }, + } + commands = []discordgo.ApplicationCommand{ + { + Name: "ping", + Description: "pongs in a reply", + }, + { + Name: "init_project", + Description: "Connect project with Coda ID", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "key", + Description: "Project's key from Coda.io", + Required: true, + MinLength: &minLength, }, }, - { - Type: discordgo.ApplicationCommandOptionString, - Name: nameOption, - Description: "Type the repository's name", - Required: false, - MinLength: &minLength, + }, + { + Name: "project", + Description: "Create new development ticket", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "project_name", + Description: "Temporary project name", + Required: true, + MinLength: &minLength, + }, }, }, - }, - { - Name: "folder", - Description: "Command for cloud folder creation", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: nameOption, - Description: "Type the folder's name", - Required: false, - MinLength: &minLength, + { + Name: "info", + Description: "Get project's info", + }, + { + Name: "repo", + Description: "Creates repository of selected type. Name used for projects channels only", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: repoType, + Description: "The type of repo", + Required: true, + Choices: []*discordgo.ApplicationCommandOptionChoice{ + { + Name: "Unity project repo", + Value: projectRepo, + }, + { + Name: "XCode build repo", + Value: buildRepo, + }, + }, + }, + { + Type: discordgo.ApplicationCommandOptionString, + Name: nameOption, + Description: "Type the repository's name", + Required: false, + MinLength: &minLength, + }, }, }, - }, -} + { + Name: "folder", + Description: "Command for cloud folder creation", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: nameOption, + Description: "Type the folder's name", + Required: false, + MinLength: &minLength, + }, + }, + }, + } +) func initBotWith(token string) *discordgo.Session { discord, err := discordgo.New("Bot " + token) @@ -128,87 +131,122 @@ type DiscordOptions struct { Controller *controller.WorkflowController } -func Run(conf domain.Config, opts DiscordOptions) error { +// Моментальный ответ для избежания столкновения с протуханием токена +func initialResponse(s *discordgo.Session, i *discordgo.InteractionCreate) { - s := initBotWith(conf.Discord.Token) + initialResponse := discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Content: "👩‍🍳 Cooking your query..", + }, + } - h := discord_handler.New( - opts.Controller, - &conf.Discord, - services.NewDummyClient(conf.Telegram), - tags, - ) + s.InteractionRespond(i.Interaction, &initialResponse) +} - commandHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){} +// Определяем канал и реджектим запрос, если пишут в лс: +func isRejected(s *discordgo.Session, i *discordgo.InteractionCreate) bool { - for _, cmd := range commands { - var f func(s *discordgo.Session, i *discordgo.InteractionCreate) + dchan, err := s.Channel(i.ChannelID) + if err != nil { + return true + } - switch cmd.Name { + if dchan.Type == discordgo.ChannelTypeDM { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Yo, fella! I'm not working in private!", + }, + }) + return true + } + return false +} + +func route(s *discordgo.Session, i *discordgo.InteractionCreate, h discord_handler.Handler) { + initialResponse(s, i) + + if isRejected(s, i) { + return + } + + // Определяем тип взаимодействия и хэндлим правильной функцией: + switch i.Type { + case discordgo.InteractionApplicationCommand: + cmd := i.ApplicationCommandData().Name + + switch cmd { case "ping": - f = h.Ping + h.Ping(s, i) case "project": - f = h.CreateTicket + h.CreateProject(s, i) case "info": - f = h.ProjectInfo + h.ProjectInfo(s, i) case "repo": - f = h.CreateGit + h.CreateGit(s, i) case "folder": - f = h.CreateFolder + h.CreateFolder(s, i) case "init_project": - f = h.InitChannelAsProject + h.InitChannelAsProject(s, i) } - commandHandlers[cmd.Name] = f - } - - componentsHandlers := map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ - "task_start": h.HandleTaskButtons, - "task_close": h.HandleTaskButtons, - } - - s.AddHandler(h.ListenPosts) - - s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { - h.AllInteractions(s, i) - - switch i.Type { - case discordgo.InteractionApplicationCommand: - if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { - h(s, i) - } - case discordgo.InteractionMessageComponent: - if h, ok := componentsHandlers[i.MessageComponentData().CustomID]; ok { - h(s, i) - } + case discordgo.InteractionMessageComponent: + c := i.MessageComponentData().CustomID + switch c { + case "task_start": + h.HandleTaskButtons(s, i) + case "task_close": + h.HandleTaskButtons(s, i) } - }) - - if err := s.Open(); err != nil { - return fmt.Errorf("cannot open the session: %v", err) } +} - // UPDATE FORUM IF NEEDED: +func updateForum(conf *domain.Config, s *discordgo.Session) ([]discordgo.ForumTag, error) { + log.Println("Updating forum chan...") + + // Get tasks channel instance: forum, err := s.Channel(conf.Discord.IsTaskForum) if err != nil { - log.Print(err) + return nil, err + } + + // Map all pre-set tags + var tagsMap = map[string]discordgo.ForumTag{} + for _, t := range forum.AvailableTags { + tagsMap[t.Name] = t + } + + // Result tags array + tags := forum.AvailableTags + + // Check if preset tag exists into current channel + for i := 0; i < len(tagsPreset); i++ { + _, ok := tagsMap[tagsPreset[i].Name] + if !ok { + tags = append(tags, tagsPreset[i]) + } } dchan, err := s.ChannelEditComplex(forum.ID, &discordgo.ChannelEdit{ AvailableTags: &tags, }) if err != nil { - log.Fatal(err) + return nil, err } log.Printf("Channel %s with ID %s propagated by tags:", dchan.Name, dchan.ID) for _, t := range dchan.AvailableTags { - log.Printf("N: %s, ID: %s", t.Name, t.ID) + fmt.Printf("N: %s, ID: %s", t.Name, t.ID) } + return dchan.AvailableTags, nil +} + +func commandRegistration(s *discordgo.Session, commands []discordgo.ApplicationCommand) []*discordgo.ApplicationCommand { log.Println("Adding commands...") var cmds []*discordgo.ApplicationCommand - for _, cmd := range commands { cmd, err := s.ApplicationCommandCreate(s.State.User.ID, "", &cmd) if err != nil { @@ -217,7 +255,48 @@ func Run(conf domain.Config, opts DiscordOptions) error { cmds = append(cmds, cmd) log.Println(cmd.Name + " command added") } + return cmds +} +func Run(conf *domain.Config, opts DiscordOptions) error { + + // bot init + s := initBotWith(conf.Discord.Token) + + // Init new handler + h := discord_handler.New( + opts.Controller, + &conf.Discord, + services.NewDummyClient(conf.Telegram), + ) + + // Add posts listener + s.AddHandler(h.ListenPosts) + + // Add interactions handlers + s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + + route(s, i, *h) + }) + + // session opening + if err := s.Open(); err != nil { + return fmt.Errorf("cannot open the session: %v", err) + } + + // Forum update + tags, err := updateForum(conf, s) + if err != nil { + log.Println(err.Error()) + } + + //Update handler with tags: + h.SetAvailableTags(tags) + + // commands registration + cmds := commandRegistration(s, commands) + + // gracefull shutdown defer s.Close() stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt)