package public import ( "context" "strconv" "strings" "velox-bot/internal/commands/projects/shared" "velox-bot/internal/db/services" "github.com/bwmarrin/discordgo" ) // HandleComponent routes the select menus shown after /projects add-helper // and /projects close. func HandleComponent(s *discordgo.Session, i *discordgo.InteractionCreate) { parts := strings.Split(i.MessageComponentData().CustomID, ":") if len(parts) < 2 { return } switch parts[1] { case "add_helper_project_select": handleAddHelperProjectSelect(s, i) case "add_helper_user_select": handleAddHelperUserSelect(s, i, parts) case "close_select": handleCloseSelect(s, i) } } // handleAddHelperProjectSelect is step 1: a project was picked, now show a // native Discord user-select to pick who helps - the actual AddHelper call // happens in handleAddHelperUserSelect once both are known. func handleAddHelperProjectSelect(s *discordgo.Session, i *discordgo.InteractionCreate) { if !shared.RequireManageGuild(s, i) { return } values := i.MessageComponentData().Values if len(values) == 0 { return } projectID := values[0] _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseUpdateMessage, Data: &discordgo.InteractionResponseData{ Content: "Who should help with this project?", Components: []discordgo.MessageComponent{ discordgo.ActionsRow{ Components: []discordgo.MessageComponent{ discordgo.SelectMenu{ MenuType: discordgo.UserSelectMenu, CustomID: "projects:add_helper_user_select:" + projectID, Placeholder: "Select a user", }, }, }, }, }, }) } // handleAddHelperUserSelect is step 2: a user was picked from the native // select menu, so both the project (carried in the CustomID) and the user // (the selected value) are now known - perform the actual AddHelper call. func handleAddHelperUserSelect(s *discordgo.Session, i *discordgo.InteractionCreate, parts []string) { if !shared.RequireManageGuild(s, i) { return } if len(parts) < 3 { return } projectID, err := strconv.ParseInt(parts[2], 10, 64) if err != nil { return } guildID, ok := shared.ParseGuildID(s, i) if !ok { return } values := i.MessageComponentData().Values if len(values) == 0 { return } userID := values[0] userID64, err := strconv.ParseInt(userID, 10, 64) if err != nil { return } ctx := context.Background() project, err := services.Global.Projects.GetProject(ctx, guildID, projectID) if err != nil { shared.RespondEphemeral(s, i, "Failed to load project.") return } if project == nil { shared.RespondEphemeral(s, i, "That project no longer exists in this server.") return } if err := services.Global.Projects.AddHelper(ctx, projectID, userID64); err != nil { shared.RespondEphemeral(s, i, "Failed to add helper to project.") return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseUpdateMessage, Data: &discordgo.InteractionResponseData{ Content: "Added <@" + userID + "> as helper to project **" + project.Name + "**.", Components: []discordgo.MessageComponent{}, }, }) } func handleCloseSelect(s *discordgo.Session, i *discordgo.InteractionCreate) { if !shared.RequireManageGuild(s, i) { return } guildID, ok := shared.ParseGuildID(s, i) if !ok { return } values := i.MessageComponentData().Values if len(values) == 0 { return } projectID, err := strconv.ParseInt(values[0], 10, 64) if err != nil { return } ctx := context.Background() project, err := services.Global.Projects.GetProject(ctx, guildID, projectID) if err != nil { shared.RespondEphemeral(s, i, "Failed to load project.") return } if project == nil { shared.RespondEphemeral(s, i, "That project no longer exists in this server.") return } if err := services.Global.Projects.DeactivateProject(ctx, guildID, projectID); err != nil { shared.RespondEphemeral(s, i, "Failed to close project.") return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseUpdateMessage, Data: &discordgo.InteractionResponseData{ Content: "Closed project **" + project.Name + "**.", Components: []discordgo.MessageComponent{}, }, }) } // HandleModalSubmit handles the /projects create modal. func HandleModalSubmit(s *discordgo.Session, i *discordgo.InteractionCreate) { parts := strings.Split(i.ModalSubmitData().CustomID, ":") if len(parts) < 2 || parts[1] != "create_modal" { return } if !shared.RequireManageGuild(s, i) { return } guildID, ok := shared.ParseGuildID(s, i) if !ok { return } if i.Member == nil || i.Member.User == nil { shared.RespondEphemeral(s, i, "Missing member info.") return } values := modalTextInputValues(i.ModalSubmitData().Components) name := strings.TrimSpace(values["name"]) description := strings.TrimSpace(values["description"]) if name == "" { shared.RespondEphemeral(s, i, "Project name cannot be empty.") return } creatorID, err := strconv.ParseInt(i.Member.User.ID, 10, 64) if err != nil { shared.RespondEphemeral(s, i, "Invalid user ID.") return } id, err := services.Global.Projects.CreateProject(context.Background(), guildID, creatorID, name, description) if err != nil { shared.RespondEphemeral(s, i, "Failed to create project.") return } shared.RespondEphemeral(s, i, "Created project **"+name+"** with ID `"+strconv.FormatInt(id, 10)+"`.") } func modalTextInputValues(components []discordgo.MessageComponent) map[string]string { values := make(map[string]string) for _, c := range components { row, ok := c.(*discordgo.ActionsRow) if !ok { continue } for _, rc := range row.Components { ti, ok := rc.(*discordgo.TextInput) if !ok { continue } values[ti.CustomID] = ti.Value } } return values }