Files
velox-bot/internal/commands/projects/public/projects.go
T

269 lines
6.3 KiB
Go

package public
import (
"context"
"strconv"
"strings"
"velox-bot/internal/commands/projects/shared"
"velox-bot/internal/db/services"
"github.com/bwmarrin/discordgo"
)
var Projects = &discordgo.ApplicationCommand{
Name: "projects",
Description: "Manage and list ongoing projects",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "list",
Description: "List active projects with creators and helpers",
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "create",
Description: "Create a new active project",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "name",
Description: "Project name",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "description",
Description: "Short description (optional)",
Required: false,
},
},
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "add-helper",
Description: "Add a helper to an existing project",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "project-id",
Description: "ID of the project",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "user",
Description: "User to add as helper",
Required: true,
},
},
},
},
}
func ProjectsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if !shared.RequireGuild(s, i) || !shared.RequireProjectsService(s, i) {
return
}
data := i.ApplicationCommandData()
if len(data.Options) == 0 {
handleList(s, i)
return
}
switch data.Options[0].Name {
case "list":
handleList(s, i)
case "create":
handleCreate(s, i)
case "add-helper":
handleAddHelper(s, i)
default:
shared.RespondEphemeral(s, i, "Unknown subcommand.")
}
}
func handleList(s *discordgo.Session, i *discordgo.InteractionCreate) {
guildID, ok := shared.ParseGuildID(s, i)
if !ok {
return
}
const limit = 25
items, err := services.Global.Projects.ListActiveWithMembers(context.Background(), guildID, limit)
if err != nil {
shared.RespondEphemeral(s, i, "Failed to load projects.")
return
}
if len(items) == 0 {
shared.RespondEphemeral(s, i, "There are no active projects at the moment.")
return
}
embed := &discordgo.MessageEmbed{
Title: "Active projects",
Description: "",
Color: 0x00bcd4,
}
for _, item := range items {
if item == nil || item.Project == nil {
continue
}
p := item.Project
name := p.Name
if name == "" {
name = "Unnamed project"
}
creatorMention := mentionUser(item.Creator)
var helpersMentions []string
for _, h := range item.Helpers {
helpersMentions = append(helpersMentions, mentionUser(h))
}
helpersText := "None"
if len(helpersMentions) > 0 {
helpersText = strings.Join(helpersMentions, ", ")
}
desc := "**Creator:** " + creatorMention + "\n" +
"**Helpers:** " + helpersText
if p.Description != "" {
desc += "\n" + p.Description
}
embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
Name: name,
Value: desc,
})
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{embed},
},
})
}
func mentionUser(id int64) string {
if id == 0 {
return "Unknown"
}
return "<@" + strconv.FormatInt(id, 10) + ">"
}
func handleCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
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
}
data := i.ApplicationCommandData()
if len(data.Options) == 0 {
shared.RespondEphemeral(s, i, "Missing options.")
return
}
opt := data.Options[0]
var name, description string
for _, o := range opt.Options {
switch o.Name {
case "name":
name = o.StringValue()
case "description":
description = o.StringValue()
}
}
if strings.TrimSpace(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 handleAddHelper(s *discordgo.Session, i *discordgo.InteractionCreate) {
if !shared.RequireManageGuild(s, i) {
return
}
guildID, ok := shared.ParseGuildID(s, i)
if !ok {
return
}
data := i.ApplicationCommandData()
if len(data.Options) == 0 {
shared.RespondEphemeral(s, i, "Missing options.")
return
}
opt := data.Options[0]
var projectID int64
var userID string
for _, o := range opt.Options {
switch o.Name {
case "project-id":
projectID = o.IntValue()
case "user":
if u := o.UserValue(s); u != nil {
userID = u.ID
}
}
}
if projectID <= 0 {
shared.RespondEphemeral(s, i, "Invalid project ID.")
return
}
if userID == "" {
shared.RespondEphemeral(s, i, "Invalid user.")
return
}
exists, err := services.Global.Projects.ProjectExistsInGuild(context.Background(), guildID, projectID)
if err != nil {
shared.RespondEphemeral(s, i, "Failed to load project.")
return
}
if !exists {
shared.RespondEphemeral(s, i, "Project `"+strconv.FormatInt(projectID, 10)+"` not found in this server.")
return
}
userID64, err := strconv.ParseInt(userID, 10, 64)
if err != nil {
shared.RespondEphemeral(s, i, "Invalid user ID.")
return
}
if err := services.Global.Projects.AddHelper(context.Background(), projectID, userID64); err != nil {
shared.RespondEphemeral(s, i, "Failed to add helper to project. ("+err.Error()+")")
return
}
shared.RespondEphemeral(s, i, "Added <@"+userID+"> as helper to project `"+strconv.FormatInt(projectID, 10)+"`.")
}