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:
FernandoJVideiraandClaude Sonnet 5.5 committed 2026-10-23 00:38:48 +01:00
1 parent 136377d81a
commit a364435bab
15 files changed
+789 -52

No files matched your search

+66 -24
View File
@@ -3,6 +3,9 @@ package settingsrepo
import (
"context"
"database/sql"
"errors"
"github.com/jackc/pgx/v5/pgconn"
)
type Repo struct {
@@ -25,36 +28,75 @@ func NewRepo(db *sql.DB) *Repo {
return &Repo{db: db}
}
func (r *Repo) SetMeetingLobbyChannel(ctx context.Context, guildID int64, lobbyChannelID int64) error {
// MaxMeetingLobbies is how many lobby channels one server can have. The
// database enforces it too (a trigger from the API's migration 000020), so
// the command and the dashboard can't disagree.
const MaxMeetingLobbies = 10
// ErrMeetingLobbyLimit means the server already has MaxMeetingLobbies.
var ErrMeetingLobbyLimit = errors.New("meeting lobby limit reached")
// pgLimitExceeded is the error code the database raises for one lobby too many.
const pgLimitExceeded = "54000"
// AddMeetingLobby makes channelID a lobby. added is false when it already
// was one; going over the limit is ErrMeetingLobbyLimit.
func (r *Repo) AddMeetingLobby(ctx context.Context, guildID, channelID int64) (added bool, err error) {
const q = `
INSERT INTO meeting_settings (guild_id, lobby_channel_id)
INSERT INTO meeting_lobbies (guild_id, channel_id)
VALUES ($1, $2)
ON CONFLICT (guild_id)
DO UPDATE SET lobby_channel_id = EXCLUDED.lobby_channel_id
ON CONFLICT DO NOTHING
`
_, err := r.db.ExecContext(ctx, q, guildID, lobbyChannelID)
return err
res, err := r.db.ExecContext(ctx, q, guildID, channelID)
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == pgLimitExceeded {
return false, ErrMeetingLobbyLimit
}
if err != nil {
return false, err
}
n, err := res.RowsAffected()
return n > 0, err
}
func (r *Repo) GetMeetingLobbyChannel(ctx context.Context, guildID int64) (channelID int64, ok bool, err error) {
const q = `
SELECT lobby_channel_id
FROM meeting_settings
WHERE guild_id = $1
`
row := r.db.QueryRowContext(ctx, q, guildID)
var ch sql.NullInt64
switch err := row.Scan(&ch); err {
case nil:
if !ch.Valid {
return 0, false, nil
}
return ch.Int64, true, nil
case sql.ErrNoRows:
return 0, false, nil
default:
return 0, false, err
// RemoveMeetingLobby takes a channel off the lobby list; removed is false
// when it wasn't one.
func (r *Repo) RemoveMeetingLobby(ctx context.Context, guildID, channelID int64) (removed bool, err error) {
const q = `DELETE FROM meeting_lobbies WHERE guild_id = $1 AND channel_id = $2`
res, err := r.db.ExecContext(ctx, q, guildID, channelID)
if err != nil {
return false, err
}
n, err := res.RowsAffected()
return n > 0, err
}
// ListMeetingLobbies returns a server's lobbies, oldest first.
func (r *Repo) ListMeetingLobbies(ctx context.Context, guildID int64) ([]int64, error) {
const q = `SELECT channel_id FROM meeting_lobbies WHERE guild_id = $1 ORDER BY created_at, channel_id`
rows, err := r.db.QueryContext(ctx, q, guildID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
return nil, err
}
out = append(out, id)
}
return out, rows.Err()
}
// IsMeetingLobby reports whether channelID is one of the server's lobbies.
// It runs whenever someone joins a voice channel, so it's one indexed lookup.
func (r *Repo) IsMeetingLobby(ctx context.Context, guildID, channelID int64) (bool, error) {
const q = `SELECT EXISTS (SELECT 1 FROM meeting_lobbies WHERE guild_id = $1 AND channel_id = $2)`
var ok bool
err := r.db.QueryRowContext(ctx, q, guildID, channelID).Scan(&ok)
return ok, err
}
func (r *Repo) AddMeetingTempChannel(ctx context.Context, guildID, channelID, createdBy int64) error {