package public import ( "context" "fmt" "log" "strconv" "strings" "time" "velox-bot/internal/db/services" "github.com/bwmarrin/discordgo" ) // Everything else /config needs so that no setting depends on a dashboard: // seeing the settings, clearing one, switching Twitch alerts on and off, // listing streamers, and the server owner's beta features switch. var clearChoices = []string{"welcome-channel", "welcome-message", "welcome-dm", "welcome-gif", "default-role", "log-channel"} func extraOptions() []*discordgo.ApplicationCommandOption { clear := &discordgo.ApplicationCommandOption{ Type: discordgo.ApplicationCommandOptionString, Name: "setting", Description: "What to clear", Required: true, } for _, c := range clearChoices { clear.Choices = append(clear.Choices, &discordgo.ApplicationCommandOptionChoice{Name: c, Value: c}) } return []*discordgo.ApplicationCommandOption{ {Type: discordgo.ApplicationCommandOptionSubCommand, Name: "view", Description: "Show the welcome, role, logging and Twitch settings"}, {Type: discordgo.ApplicationCommandOptionSubCommand, Name: "clear", Description: "Clear a welcome, role or log setting", Options: []*discordgo.ApplicationCommandOption{clear}}, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "toggletwitch", Description: "Turn Twitch live notifications on or off", Options: []*discordgo.ApplicationCommandOption{{Type: discordgo.ApplicationCommandOptionBoolean, Name: "enabled", Description: "On or off", Required: true}}, }, {Type: discordgo.ApplicationCommandOptionSubCommand, Name: "streamers", Description: "List the Twitch streamers being watched"}, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "beta-features", Description: "Let testers use beta features here. Only the server owner can change it", Options: []*discordgo.ApplicationCommandOption{{Type: discordgo.ApplicationCommandOptionBoolean, Name: "enabled", Description: "On or off. Leave out to see the state"}}, }, } } func guildInt(i *discordgo.InteractionCreate) int64 { n, _ := strconv.ParseInt(i.GuildID, 10, 64) return n } func ownerOf(s *discordgo.Session, guildID string) (string, error) { if g, err := s.State.Guild(guildID); err == nil && g != nil && g.OwnerID != "" { return g.OwnerID, nil } g, err := s.Guild(guildID) if err != nil { return "", err } return g.OwnerID, nil } // handleExtra runs the subcommands above. It reports whether it handled name. func handleExtra(s *discordgo.Session, i *discordgo.InteractionCreate, sub *discordgo.ApplicationCommandInteractionDataOption) bool { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() g := guildInt(i) svc := services.Global args := map[string]*discordgo.ApplicationCommandInteractionDataOption{} for _, o := range sub.Options { args[o.Name] = o } fail := func(err error) { respondEphemeral(s, i, "Something went wrong. Try again in a moment.") log.Printf("config %s: %v", sub.Name, err) } switch sub.Name { case "view": var b strings.Builder if w, err := svc.Welcome.GetSettings(ctx, g); err == nil { fmt.Fprintf(&b, "**Welcome**\nChannel: %s\nMessage: %s\nDM: %s\nGIF: %s\n", mention("#", w.ChannelID), textOr(w.WelcomeMessage), textOr(w.WelcomeDM), textOr(w.WelcomeGIFURL)) } if r, err := svc.DefaultRole.GetRole(ctx, g); err == nil { fmt.Fprintf(&b, "**Default role:** %s\n", mention("@&", r)) } if l, err := svc.LogSettings.GetSettings(ctx, g); err == nil && l != nil { state := "off" if l.IsEnabled { state = "on" } fmt.Fprintf(&b, "**Logging:** %s, channel %s\n", state, mention("#", l.ChannelID)) } else { b.WriteString("**Logging:** off\n") } if on, err := svc.Twitch.IsEnabled(ctx, g); err == nil { ch, _, _ := svc.Twitch.GetNotificationChannel(ctx, g) users, _ := svc.Twitch.ListUsersForGuild(ctx, g) state := "off" if on { state = "on" } fmt.Fprintf(&b, "**Twitch alerts:** %s, channel %s, %d streamer(s)\n", state, mention("#", ch), len(users)) } respondEphemeral(s, i, clipConfig(b.String(), 1900)) case "clear": var err error setting := args["setting"].StringValue() switch setting { case "welcome-channel": err = svc.Welcome.SetChannel(ctx, g, 0) case "welcome-message": err = svc.Welcome.SetMessage(ctx, g, "") case "welcome-dm": err = svc.Welcome.SetDM(ctx, g, "") case "welcome-gif": err = svc.Welcome.SetGIF(ctx, g, "") case "default-role": err = svc.DefaultRole.SetRole(ctx, g, 0) case "log-channel": err = svc.LogSettings.SetChannel(ctx, g, 0) default: respondEphemeral(s, i, "Unknown setting.") return true } if err != nil { fail(err) return true } respondEphemeral(s, i, "Cleared **"+setting+"**.") case "toggletwitch": on := args["enabled"].BoolValue() if err := svc.Twitch.SetEnabled(ctx, g, on); err != nil { fail(err) return true } if on { if ch, ok, _ := svc.Twitch.GetNotificationChannel(ctx, g); !ok || ch == 0 { respondEphemeral(s, i, "Twitch alerts are **on**, but there is no channel yet. Set one with `/config settwitchnotificationchannel`.") return true } respondEphemeral(s, i, "Twitch alerts are **on**.") } else { respondEphemeral(s, i, "Twitch alerts are **off**.") } case "streamers": users, err := svc.Twitch.ListUsersForGuild(ctx, g) if err != nil { fail(err) return true } if len(users) == 0 { respondEphemeral(s, i, "No streamers yet. Add one with `/config addstreamer`.") return true } respondEphemeral(s, i, clipConfig("Watching: "+strings.Join(users, ", "), 1900)) case "beta-features": on, err := svc.BetaSettings.IsOptedIn(ctx, g) if err != nil { fail(err) return true } state := "off" if on { state = "on" } want, ok := args["enabled"] if !ok { respondEphemeral(s, i, "Beta features are **"+state+"** in this server. Testers can use them here when this is on. Only the server owner can change it.") return true } owner, err := ownerOf(s, i.GuildID) if err != nil { fail(err) return true } if i.Member == nil || i.Member.User == nil || i.Member.User.ID != owner { respondEphemeral(s, i, "Only the server owner can change this. Beta features can act on the whole server.") return true } by, _ := strconv.ParseInt(owner, 10, 64) if err := svc.BetaSettings.SetOptedIn(ctx, g, by, want.BoolValue()); err != nil { fail(err) return true } if want.BoolValue() { respondEphemeral(s, i, "Beta features are **on**. Beta commands appear here within a minute, for testers only.") } else { respondEphemeral(s, i, "Beta features are **off**. Beta commands disappear from this server within a minute.") } default: return false } return true } func mention(prefix string, id int64) string { if id == 0 { return "not set" } return fmt.Sprintf("<%s%d>", prefix, id) } func textOr(v string) string { if strings.TrimSpace(v) == "" { return "not set" } return "`" + clipConfig(v, 120) + "`" } func clipConfig(s string, n int) string { if r := []rune(s); len(r) > n { return string(r[:n-1]) + "…" } return s }