More work on sync and wire together

Signed-off-by: jolheiser <john.olheiser@gmail.com>
main
jolheiser 2022-01-06 22:22:12 -06:00
parent 43ccae6916
commit 4cc19a80ac
Signed by: jolheiser
GPG Key ID: B853ADA5DA7BBF7A
12 changed files with 291 additions and 32 deletions

25
commands.go 100644
View File

@ -0,0 +1,25 @@
package main
import "github.com/Karitham/corde"
var (
appCommand = corde.NewSlashCommand("app", "App commands",
corde.NewSubcommand("accept", "Accept an app",
corde.NewIntOption("app_id", "App ID", true),
),
corde.NewSubcommand("deny", "Deny an app",
corde.NewIntOption("app_id", "App ID", true),
),
corde.NewSubcommand("clear", "Clear an app",
corde.NewIntOption("app_id", "App ID", true),
),
)
infoCommand = corde.NewSlashCommand("info", "Info on an app",
corde.NewStringOption("username", "Username", false),
corde.NewIntOption("app_id", "App ID", false),
)
searchCommand = corde.NewSlashCommand("search", "Searhc for apps",
corde.NewStringOption("username", "Username", false),
)
syncCommand = corde.NewSlashCommand("sync", "Sync users with Discord")
)

View File

@ -19,6 +19,10 @@ type Application struct {
Date string `db:"date"`
}
func (a *Application) EscapeUsername() string {
return strings.ReplaceAll(a.Username, "_", `\_`)
}
func (a *Application) Status() string {
switch {
case !a.Accepted.Valid:

View File

@ -18,6 +18,10 @@ type Player struct {
DiscordID sql.NullString `db:"discord_id"`
}
func (p *Player) EscapeUsername() string {
return strings.ReplaceAll(p.Username, "_", `\_`)
}
func (p *Player) String() string {
return p.Username
}
@ -50,6 +54,13 @@ func (d *Database) PlayersByDiscordIsNull() (pl []*Player, err error) {
return pl, d.db.Select(pl, playersByDiscordIsNullSQL)
}
//go:embed queries/players_by_discord_isnull_count.sql
var playersByDiscordIsNullCountSQL string
func (d *Database) PlayersByDiscordIsNullCount() (count int64, err error) {
return count, d.db.Get(&count, playersByDiscordIsNullCountSQL)
}
//go:embed queries/player_update_application.sql
var playerUpdateApplicationSQL string

View File

@ -0,0 +1,3 @@
SELECT COUNT(*)
FROM minecraft_manager_player
WHERE discord_id IS NULL;

View File

@ -12,14 +12,14 @@ func (d Discord) accept(w corde.ResponseWriter, i *corde.Interaction) {
app, err := d.store.ApplicationByID(id)
if err != nil {
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral())
return
}
if !app.Accepted.Bool {
t := true
if err := d.store.ApplicationUpdateAccepted(&t, app.ID); err != nil {
w.Respond(corde.NewResp().Contentf("Could not accept application #%d.", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not accept application #%d.", id).Ephemeral())
return
}
@ -28,20 +28,20 @@ func (d Discord) accept(w corde.ResponseWriter, i *corde.Interaction) {
if errors.Is(err, sql.ErrNoRows) {
return
}
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral())
return
}
if err := d.store.PlayerUpdateApplication(app.ID, players[0].ID); err != nil {
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral())
}
if err := d.sock.Command("accept", app.Username); err != nil {
w.Respond(corde.NewResp().Contentf("Could not accept player in-game, is the server running?", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not accept player in-game, is the server running?", id).Ephemeral())
return
}
w.Respond(corde.NewResp().Contentf("Application #%d was successfully accepted.", id).B())
w.Respond(corde.NewResp().Contentf("Application #%d was successfully accepted.", id))
}
}
@ -50,14 +50,14 @@ func (d Discord) deny(w corde.ResponseWriter, i *corde.Interaction) {
app, err := d.store.ApplicationByID(id)
if err != nil {
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral())
return
}
if !app.Accepted.Bool {
f := false
if err := d.store.ApplicationUpdateAccepted(&f, app.ID); err != nil {
w.Respond(corde.NewResp().Contentf("Could not deny application #%d.", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not deny application #%d.", id).Ephemeral())
return
}
@ -66,20 +66,20 @@ func (d Discord) deny(w corde.ResponseWriter, i *corde.Interaction) {
if errors.Is(err, sql.ErrNoRows) {
return
}
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral())
return
}
if err := d.store.PlayerUpdateApplication(app.ID, players[0].ID); err != nil {
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral())
}
if err := d.sock.Command("deny", app.Username); err != nil {
w.Respond(corde.NewResp().Contentf("Could not deny player in-game, is the server running?", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not deny player in-game, is the server running?", id).Ephemeral())
return
}
w.Respond(corde.NewResp().Contentf("Application #%d was successfully denied.", id).B())
w.Respond(corde.NewResp().Contentf("Application #%d was successfully denied.", id))
}
}
@ -88,14 +88,14 @@ func (d Discord) clear(w corde.ResponseWriter, i *corde.Interaction) {
app, err := d.store.ApplicationByID(id)
if err != nil {
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral())
return
}
if !app.Accepted.Bool {
f := false
if err := d.store.ApplicationUpdateAccepted(&f, app.ID); err != nil {
w.Respond(corde.NewResp().Contentf("Could not clear application #%d.", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not clear application #%d.", id).Ephemeral())
return
}
@ -104,14 +104,14 @@ func (d Discord) clear(w corde.ResponseWriter, i *corde.Interaction) {
if errors.Is(err, sql.ErrNoRows) {
return
}
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral())
return
}
if err := d.store.PlayerUpdateApplication(app.ID, players[0].ID); err != nil {
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral())
}
w.Respond(corde.NewResp().Contentf("Application #%d was successfully cleared.", id).B())
w.Respond(corde.NewResp().Contentf("Application #%d was successfully cleared.", id))
}
}

View File

@ -8,14 +8,14 @@ import (
"github.com/Karitham/corde"
)
func appResponse(app *database.Application) *corde.InteractionRespData {
func appResponse(app *database.Application) *corde.RespB {
embed := corde.NewEmbed().
Color(0x417505).
Title("Application").
Thumbnail(corde.Image{
URL: fmt.Sprintf("https://minotar.net/helm/%s/100.png", app.Username),
}).
Field("Username", strings.ReplaceAll(app.Username, "_", `\_`)).
Field("Username", app.EscapeUsername()).
Field("Age", fmt.Sprint(app.Age)).
Field("Type of Player", app.PlayerType).
Field("Ever been Banned", fmt.Sprint(app.EverBanned))
@ -27,14 +27,14 @@ func appResponse(app *database.Application) *corde.InteractionRespData {
}
embed.Field("Read the Rules", app.ReadRules).
Field("Status", app.Status())
return corde.NewResp().Embeds(embed.B()).B()
return corde.NewResp().Embeds(embed)
}
func (d Discord) info(w corde.ResponseWriter, i *corde.Interaction) {
id := i.Data.Options.Int64("app_id")
username := i.Data.Options.String("username")
if id == 0 && username == "" {
w.Respond(corde.NewResp().Content("Info requires an application ID or username.").Ephemeral().B())
w.Respond(corde.NewResp().Content("Info requires an application ID or username.").Ephemeral())
return
}
@ -43,19 +43,19 @@ func (d Discord) info(w corde.ResponseWriter, i *corde.Interaction) {
if id != 0 {
app, err = d.store.ApplicationByID(id)
if err != nil {
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral().B())
w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral())
return
}
} else {
apps, err := d.store.ApplicationsByUsername(username)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B())
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
if len(apps) == 0 {
apps, err = d.store.ApplicationsByPlayerUsername(username)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B())
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
if len(apps) == 1 {
@ -66,7 +66,7 @@ func (d Discord) info(w corde.ResponseWriter, i *corde.Interaction) {
}
}
w.Respond(appResponse(app))
w.Respond(appResponse(app).Ephemeral())
}
func (d Discord) search(w corde.ResponseWriter, i *corde.Interaction) {
@ -74,22 +74,69 @@ func (d Discord) search(w corde.ResponseWriter, i *corde.Interaction) {
apps, err := d.store.ApplicationsByUsername(username)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B())
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
count, err := d.store.ApplicationsByUsernameCount(username)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B())
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
var app *database.Application
if count > 0 {
if count == 1 {
app = apps[0]
w.Respond(appResponse(apps[0]).Ephemeral())
return
} else {
var sb strings.Builder
sb.WriteString("**Found the following applications**")
for _, app := range apps {
sb.WriteString(fmt.Sprintf("\n%d - %s (%s)", app.ID, app.EscapeUsername(), app.Status()))
}
if count > 10 {
sb.WriteString(fmt.Sprintf("\n**This is only 10 applications out of %d found. Please narrow your search if possible.**", count))
}
w.Respond(corde.NewResp().Content(sb.String()).Ephemeral())
}
}
w.Respond(appResponse(app))
players, err := d.store.PlayersByUsernameApplicationNotNull(username)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
count, err = d.store.PlayersByUsernameApplicationNotNullCount(username)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
if count > 0 {
if count == 1 {
app, err := d.store.ApplicationByID(players[0].ApplicationID.Int64)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
w.Respond(appResponse(app).Ephemeral())
return
} else {
var sb strings.Builder
sb.WriteString("**No applications matched, however there are player matches**")
for _, player := range players {
app, err := d.store.ApplicationByID(player.ApplicationID.Int64)
if err != nil {
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
return
}
sb.WriteString(fmt.Sprintf("\n%d - %s AKA %s (%s)", app.ID, app.EscapeUsername(), player.EscapeUsername(), app.Status()))
}
if count > 10 {
sb.WriteString(fmt.Sprintf("\n**This is only 10 players out of %d found. Please narrow your search if possible.**", count))
}
w.Respond(corde.NewResp().Content(sb.String()).Ephemeral())
}
}
w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral())
}

View File

@ -16,6 +16,7 @@ type Storer interface {
PlayersByUsernameApplicationNotNull(string) ([]*database.Player, error)
PlayersByUsernameApplicationNotNullCount(string) (int64, error)
PlayersByDiscordIsNull() ([]*database.Player, error)
PlayersByDiscordIsNullCount() (int64, error)
PlayerUpdateApplication(int64, int64) error
PlayerUpdateDiscordID(string, int64) error
}
@ -23,6 +24,7 @@ type Storer interface {
type Discord struct {
store Storer
sock *socket.Socket
token string
Mux *corde.Mux
}
@ -32,6 +34,7 @@ func New(store Storer, sock *socket.Socket, publicKey, appID, botToken string) *
d := &Discord{
store: store,
sock: sock,
token: botToken,
Mux: m,
}
@ -46,7 +49,7 @@ func New(store Storer, sock *socket.Socket, publicKey, appID, botToken string) *
m.Command("clear", d.clear)
})
// Sync
m.Command("sync", nil)
m.Command("sync", d.sync)
return d
}

View File

@ -0,0 +1,58 @@
package rest
import (
"encoding/json"
"fmt"
"net/http"
)
const memberEndpoint = "https://discord.com/api/v9/guilds/%s/members?after=%s"
func Members(guildID, token string) ([]Member, error) {
var mems []Member
after := "0"
for {
m, err := members(guildID, token, after)
if err != nil {
return nil, err
}
mems = append(mems, m...)
if len(mems) < 1000 {
break
}
after = mems[len(mems)-1].User.ID
}
return mems, nil
}
func members(guildID, token, after string) ([]Member, error) {
var members []Member
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(memberEndpoint, guildID, after), nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bot "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&members); err != nil {
return nil, err
}
return members, nil
}
type Member struct {
Nick string `json:"nick"`
User struct {
ID string `json:"id"`
Username string `json:"username"`
} `json:"user"`
}

View File

@ -1 +1,61 @@
package discord
import (
"fmt"
"strings"
"git.jojodev.com/minecraft/mcm-discord/database"
"git.jojodev.com/minecraft/mcm-discord/discord/rest"
"github.com/Karitham/corde"
)
func (d Discord) sync(w corde.ResponseWriter, i *corde.Interaction) {
members, err := rest.Members(i.GuildID.String(), d.token)
if err != nil {
w.Respond(corde.NewResp().Content("Could not sync.").Ephemeral())
return
}
syncPlayer := func(pl *database.Player) (bool, error) {
for _, member := range members {
name := member.Nick
if name == "" {
name = member.User.Username
}
if strings.EqualFold(pl.Username, name) {
return true, d.store.PlayerUpdateDiscordID(member.User.ID, pl.ID)
}
}
return false, nil
}
players, err := d.store.PlayersByDiscordIsNull()
if err != nil {
w.Respond(corde.NewResp().Content("Could not sync.").Ephemeral())
return
}
needed, err := d.store.PlayersByDiscordIsNullCount()
if err != nil {
w.Respond(corde.NewResp().Content("Could not sync.").Ephemeral())
return
}
var synced []string
var notSynced []string
if needed > 0 {
for _, player := range players {
ok, err := syncPlayer(player)
if err != nil {
w.Respond(corde.NewResp().Content("Could not sync.").Ephemeral())
return
}
if ok {
synced = append(synced, player.Username)
continue
}
notSynced = append(notSynced, player.Username)
}
}
txt := fmt.Sprintf("Synced\n\t%s\n\nNot Synced\n\t%s", strings.Join(synced, "\n\t"), strings.Join(notSynced, "\n\t"))
w.Respond(corde.NewResp().Attachment(strings.NewReader(txt), "sync.txt").Contentf("Successfully synced %d/%d players.", len(synced), needed))
}

3
go.mod
View File

@ -8,7 +8,8 @@ require (
)
require (
github.com/Karitham/corde v0.5.0 // indirect
github.com/Karitham/corde v0.5.1-0.20220106205813-fc0fcd4a872f // indirect
github.com/akrennmair/go-radix v1.0.1-0.20211215212324-49d05194b0a3 // indirect
github.com/peterbourgon/ff/v3 v3.1.2 // indirect
github.com/rs/zerolog v1.26.1 // indirect
)

9
go.sum
View File

@ -1,8 +1,12 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Karitham/corde v0.5.0 h1:Y/H0uwRqTWlGfVt76Bto1BB338DZwZkwzhuqbvE8Vec=
github.com/Karitham/corde v0.5.0/go.mod h1:1HP9kpDAN1293JjCv+IrE9xKI0seOm5ZORWm9Z0A4k0=
github.com/Karitham/corde v0.5.1-0.20220106205813-fc0fcd4a872f h1:wPUWbFE/Hhp8uCsyvfvVbINnBd0OYHd35YtXrT9bRd4=
github.com/Karitham/corde v0.5.1-0.20220106205813-fc0fcd4a872f/go.mod h1:37EsBl0rfe6S3+vsWzkUaYRxLw5iVguX2hMC5PbGCkc=
github.com/akrennmair/go-radix v1.0.1-0.20211215212324-49d05194b0a3 h1:cC+usowX2qUArGE+BauZSn2qCxHqBXhBmA3CkFMULBg=
github.com/akrennmair/go-radix v1.0.1-0.20211215212324-49d05194b0a3/go.mod h1:YPJ4C/zdh9bNuHZCccyEX3xnq7vtjMw2Lu8RgBohgRk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@ -13,6 +17,9 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM=
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
@ -43,3 +50,5 @@ golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

38
main.go
View File

@ -1,4 +1,42 @@
package main
import (
"flag"
"fmt"
"os"
"git.jojodev.com/minecraft/mcm-discord/database"
"git.jojodev.com/minecraft/mcm-discord/discord"
"git.jojodev.com/minecraft/mcm-discord/socket"
"github.com/peterbourgon/ff/v3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
fs := flag.NewFlagSet("mcm-discord", flag.ExitOnError)
dns := flag.String("dns", "", "MariaDB DNS")
port := flag.Int("port", 8080, "Port to listen on")
pluginPort := flag.Int("plugin-port", 0, "Plugin port for socket connection")
publicKey := fs.String("public-ket", "", "App public key")
appID := fs.String("app-id", "", "App ID")
botToken := fs.String("bot-token", "", "Bot token")
if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("MCM")); err != nil {
log.Fatal().Err(err).Msg("")
}
store, err := database.New(*dns)
if err != nil {
log.Fatal().Err(err).Msg("")
}
sock := &socket.Socket{Port: *pluginPort}
mux := discord.New(store, sock, *publicKey, *appID, *botToken)
if err := mux.Mux.ListenAndServe(fmt.Sprintf(":%d", *port)); err != nil {
log.Fatal().Err(err).Msg("")
}
}