This repository has been archived on 2025-07-20. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
lgbtq-minecraft/whitelister/command.go
AKP ce8dbcf668
Check for a role before adding to whitelist
Signed-off-by: AKP <tom@tdpain.net>
2023-03-22 15:46:23 +00:00

223 lines
5.3 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/bwmarrin/discordgo"
"github.com/docker/docker/api/types"
docker "github.com/docker/docker/client"
"github.com/rs/zerolog/log"
)
var processorLock sync.Mutex
func handleApplicationCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
cmdData := i.ApplicationCommandData()
if cmdData.Name != "register" {
return
}
rolesOk := false
for _, roleID := range i.Member.Roles {
if roleID == Config.RequiredRole {
rolesOk = true
}
}
if !rolesOk {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{Description: "Not verified - please verify first.", Color: 0xff0000},
},
},
})
return
}
username, rolesOk := cmdData.Options[0].Value.(string)
if !rolesOk {
log.Error().Interface("val", cmdData.Options[0].Value).Msg("discord api string not a string??")
return
}
if validationErr := validateUsername(username); validationErr != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{Description: fmt.Sprintf("Invalid username: %s", validationErr.Error()), Color: 0xff0000},
},
},
})
return
}
var (
err error
existingUsername string
alreadyRegistered bool
response *discordgo.InteractionResponse
)
processorLock.Lock()
known, err := loadKnownUsers()
if err != nil {
processorLock.Unlock()
goto error
}
existingUsername, alreadyRegistered = known[i.Interaction.Member.User.ID]
if alreadyRegistered && strings.EqualFold(existingUsername, username) {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{Description: "You've already registered with this username!", Color: 0xff0000},
},
},
})
return
} else if alreadyRegistered {
err = removeFromWhitelist(existingUsername)
if err != nil {
processorLock.Unlock()
goto error
}
}
err = addToWhitelist(username)
if err != nil {
processorLock.Unlock()
goto error
}
known[i.Interaction.Member.User.ID] = username
err = saveKnownUsers(known)
processorLock.Unlock()
response = &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{Description: fmt.Sprintf("Successfully registered `%s`", username), Color: 0x00ff00},
},
},
}
if alreadyRegistered {
response.Data.Embeds[0].Description += fmt.Sprintf(" and removed `%s`", existingUsername)
}
err = s.InteractionRespond(i.Interaction, response)
if err != nil {
goto error
}
return
error:
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: fmt.Sprintf("An internal error was encoutered and the bot was unable to process your command.\nError: %s", err.Error()),
},
})
log.Error().Err(err).Msg("in handler")
}
type knownUsers map[string]string
func loadKnownUsers() (knownUsers, error) {
res := make(knownUsers)
fcont, err := os.ReadFile("knownusers.json")
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return res, nil
}
return nil, err
}
if err := json.Unmarshal(fcont, &res); err != nil {
return nil, err
}
return res, nil
}
func saveKnownUsers(res knownUsers) error {
jcont, err := json.Marshal(res)
if err != nil {
return err
}
return os.WriteFile("knownusers.json", jcont, 0644)
}
var usernameValidationRegexp = regexp.MustCompile(`^[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]{3,16}$`)
func validateUsername(username string) error {
if !usernameValidationRegexp.MatchString(username) {
return errors.New("illegal")
}
return nil
}
func addToWhitelist(username string) error {
return runCommandInContainer([]string{"mc-send-to-console", "whitelist", "add", username})
}
func removeFromWhitelist(username string) error {
return runCommandInContainer([]string{"mc-send-to-console", "whitelist", "remove", username})
}
func runCommandInContainer(cmd []string) error {
client, err := docker.NewClientWithOpts(docker.WithHost(Config.DockerSocket))
if err != nil {
return err
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
id, err := client.ContainerExecCreate(ctx, Config.ContainerName, types.ExecConfig{
Cmd: cmd,
})
if err != nil {
return err
}
if err := client.ContainerExecStart(ctx, id.ID, types.ExecStartCheck{Detach: true}); err != nil {
return err
}
time.Sleep(time.Millisecond * 200)
ctx, cancel = context.WithTimeout(context.Background(), time.Second)
defer cancel()
insp, err := client.ContainerExecInspect(ctx, id.ID)
if err != nil {
return err
}
if insp.Running || insp.ExitCode != 0 {
return fmt.Errorf("failed to run %s: insp.Running=%v, insp.ExitCode=%d", cmd, insp.Running, insp.ExitCode)
}
return nil
}