Multiple meeting lobbies and beta-gated features (3.5.2)
- /meeting add-lobby, remove-lobby, lobbies: up to 10 lobbies per server, capped in the database - Deleted voice channels are dropped from the lobbies - Beta features: registered only in the beta server and usable only with the Beta Tester role (BETA_GUILD_ID, BETA_ROLE_ID); fails closed - /beta (bot owner only) shows how beta access is set up Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
136377d81a
commit
a364435bab
15 files changed
+789
-52
No files matched your search
@@ -2,11 +2,14 @@ package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"velox-bot/internal/commands/meeting/shared"
|
||||
"velox-bot/internal/db/services"
|
||||
meetingsvc "velox-bot/internal/db/services/meeting"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
@@ -17,18 +20,37 @@ var Meeting = &discordgo.ApplicationCommand{
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "set-lobby",
|
||||
Description: "Set the voice lobby channel used to create meetings",
|
||||
Name: "add-lobby",
|
||||
Description: "Add a voice lobby: joining it creates a meeting room",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionChannel,
|
||||
Name: "channel",
|
||||
Description: "Voice channel lobby",
|
||||
Description: "Voice channel to use as a lobby",
|
||||
Required: true,
|
||||
ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildVoice},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "remove-lobby",
|
||||
Description: "Stop using a voice channel as a lobby",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionChannel,
|
||||
Name: "channel",
|
||||
Description: "The lobby to remove",
|
||||
Required: true,
|
||||
ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildVoice},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "lobbies",
|
||||
Description: "List this server's meeting lobbies",
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "lock",
|
||||
@@ -85,8 +107,12 @@ func MeetingHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
}
|
||||
|
||||
switch data.Options[0].Name {
|
||||
case "set-lobby":
|
||||
handleSetLobby(s, i)
|
||||
case "add-lobby":
|
||||
handleAddLobby(s, i)
|
||||
case "remove-lobby":
|
||||
handleRemoveLobby(s, i)
|
||||
case "lobbies":
|
||||
handleListLobbies(s, i)
|
||||
case "lock":
|
||||
handleLock(s, i)
|
||||
case "lock-private":
|
||||
@@ -102,37 +128,97 @@ func MeetingHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleSetLobby(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
guildID, ok := shared.ParseGuildID(s, i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// lobbyChannelOption reads the "channel" option of add-lobby and
|
||||
// remove-lobby, answering the user itself if it's missing or not a voice
|
||||
// channel.
|
||||
func lobbyChannelOption(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.Channel, int64, bool) {
|
||||
optMap := shared.SubcommandOptionMap(i)
|
||||
channelOpt, ok := optMap["channel"]
|
||||
if !ok {
|
||||
shared.RespondEphemeral(s, i, "Missing channel option.")
|
||||
return
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
ch := channelOpt.ChannelValue(s)
|
||||
if ch == nil || ch.Type != discordgo.ChannelTypeGuildVoice {
|
||||
shared.RespondEphemeral(s, i, "Invalid voice channel.")
|
||||
return
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
channelID, err := strconv.ParseInt(ch.ID, 10, 64)
|
||||
if err != nil {
|
||||
shared.RespondEphemeral(s, i, "Invalid channel ID.")
|
||||
return nil, 0, false
|
||||
}
|
||||
return ch, channelID, true
|
||||
}
|
||||
|
||||
func handleAddLobby(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
guildID, ok := shared.ParseGuildID(s, i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch, channelID, ok := lobbyChannelOption(s, i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := services.Global.Meeting.SetLobbyChannel(context.Background(), guildID, channelID); err != nil {
|
||||
shared.RespondEphemeral(s, i, "Failed to set meeting lobby.")
|
||||
added, err := services.Global.Meeting.AddLobby(context.Background(), guildID, channelID)
|
||||
switch {
|
||||
case errors.Is(err, meetingsvc.ErrLobbyLimit):
|
||||
shared.RespondEphemeral(s, i, fmt.Sprintf("This server already has %d lobbies. Remove one with `/meeting remove-lobby` first.", meetingsvc.MaxLobbies))
|
||||
case err != nil:
|
||||
shared.RespondEphemeral(s, i, "Failed to add the meeting lobby.")
|
||||
case !added:
|
||||
shared.RespondEphemeral(s, i, ch.Mention()+" is already a meeting lobby.")
|
||||
default:
|
||||
shared.RespondEphemeral(s, i, ch.Mention()+" is now a meeting lobby.")
|
||||
}
|
||||
}
|
||||
|
||||
func handleRemoveLobby(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
guildID, ok := shared.ParseGuildID(s, i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch, channelID, ok := lobbyChannelOption(s, i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
shared.RespondEphemeral(s, i, "Meeting lobby set to "+ch.Mention()+".")
|
||||
removed, err := services.Global.Meeting.RemoveLobby(context.Background(), guildID, channelID)
|
||||
switch {
|
||||
case err != nil:
|
||||
shared.RespondEphemeral(s, i, "Failed to remove the meeting lobby.")
|
||||
case !removed:
|
||||
shared.RespondEphemeral(s, i, ch.Mention()+" isn't a meeting lobby.")
|
||||
default:
|
||||
shared.RespondEphemeral(s, i, ch.Mention()+" is no longer a meeting lobby.")
|
||||
}
|
||||
}
|
||||
|
||||
func handleListLobbies(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
guildID, ok := shared.ParseGuildID(s, i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ids, err := services.Global.Meeting.ListLobbies(context.Background(), guildID)
|
||||
if err != nil {
|
||||
shared.RespondEphemeral(s, i, "Failed to list the meeting lobbies.")
|
||||
return
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
shared.RespondEphemeral(s, i, "No meeting lobbies yet. Add one with `/meeting add-lobby`.")
|
||||
return
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "Meeting lobbies (%d of %d):\n", len(ids), meetingsvc.MaxLobbies)
|
||||
for _, id := range ids {
|
||||
fmt.Fprintf(&b, "<#%d>\n", id)
|
||||
}
|
||||
shared.RespondEphemeral(s, i, b.String())
|
||||
}
|
||||
|
||||
func handleLock(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
|
||||
Reference in new issue
Block a user