diff --git a/database/application.go b/database/application.go index 2e557a5..900fe4d 100644 --- a/database/application.go +++ b/database/application.go @@ -19,6 +19,19 @@ type Application struct { Date string `db:"date"` } +func (a *Application) Status() string { + switch { + case !a.Accepted.Valid: + return "Unanswered" + case a.Accepted.Bool: + return "Accepted" + case !a.Accepted.Bool: + return "Denied" + default: + return "Unknown" + } +} + func (a *Application) String() string { return a.Username } @@ -31,12 +44,11 @@ func (d *Database) ApplicationByID(id int64) (*Application, error) { return &app, d.db.Get(&app, applicationByIDSQL, id) } -//go:embed queries/application_by_player_username.sql -var applicationByPlayerUsernameSQL string +//go:embed queries/applications_by_player_username.sql +var applicationsByPlayerUsernameSQL string -func (d *Database) ApplicationByPlayerUsername(username string) (*Application, error) { - var app Application - return &app, d.db.Get(&app, applicationByPlayerUsernameSQL, strings.ToLower(username)) +func (d *Database) ApplicationsByPlayerUsername(username string) (apps []*Application, err error) { + return apps, d.db.Select(&apps, applicationsByPlayerUsernameSQL, strings.ToLower(username)) } //go:embed queries/applications_by_username.sql @@ -46,10 +58,17 @@ func (d *Database) ApplicationsByUsername(username string) (apps []*Application, return apps, d.db.Select(&apps, applicationsByUsernameSQL, strings.ToLower(username)) } +//go:embed queries/applications_by_username_count.sql +var applicationsByUsernameCountSQL string + +func (d *Database) ApplicationsByUsernameCount(username string) (count int64, err error) { + return count, d.db.Get(&count, applicationsByUsernameCountSQL, strings.ToLower(username)) +} + //go:embed queries/application_update_accepted.sql var applicationUpdateAcceptedSQL string -func (d *Database) ApplicationUpdateAccepted(accepted *bool) (*Application, error) { - var app Application - return &app, d.db.Get(&app, applicationUpdateAcceptedSQL, accepted) +func (d *Database) ApplicationUpdateAccepted(accepted *bool, appID int64) error { + _, err := d.db.Exec(applicationUpdateAcceptedSQL, accepted, appID) + return err } diff --git a/database/player.go b/database/player.go index 20744c6..553710f 100644 --- a/database/player.go +++ b/database/player.go @@ -36,6 +36,13 @@ func (d *Database) PlayersByUsernameApplicationNotNull(username string) (pl []*P return pl, d.db.Get(&pl, playersByUsernameApplicationNotNullSQL, strings.ToLower(username)) } +//go:embed queries/players_by_username_application_notnull_count.sql +var playersByUsernameApplicationNotNullCountSQL string + +func (d *Database) PlayersByUsernameApplicationNotNullCount(username string) (count int64, err error) { + return count, d.db.Get(&count, playersByUsernameApplicationNotNullCountSQL, strings.ToLower(username)) +} + //go:embed queries/players_by_discord_isnull.sql var playersByDiscordIsNullSQL string diff --git a/database/queries/application_by_player_username.sql b/database/queries/application_by_player_username.sql deleted file mode 100644 index 66baf98..0000000 --- a/database/queries/application_by_player_username.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT * -FROM minecraft_manager_application -WHERE id = (SELECT application_id FROM minecraft_manager_player WHERE LOWER(username) = ?); \ No newline at end of file diff --git a/database/queries/applications_by_player_username.sql b/database/queries/applications_by_player_username.sql new file mode 100644 index 0000000..29aa651 --- /dev/null +++ b/database/queries/applications_by_player_username.sql @@ -0,0 +1,3 @@ +SELECT * +FROM minecraft_manager_application +WHERE id IN (SELECT application_id FROM minecraft_manager_player WHERE LOWER(username) LIKE ?); \ No newline at end of file diff --git a/database/queries/applications_by_username.sql b/database/queries/applications_by_username.sql index 570416b..c5a0d86 100644 --- a/database/queries/applications_by_username.sql +++ b/database/queries/applications_by_username.sql @@ -1,3 +1,3 @@ SELECT * FROM minecraft_manager_application -WHERE LOWER(username) LIKE ?; \ No newline at end of file +WHERE LOWER(username) LIKE ? LIMIT 10; \ No newline at end of file diff --git a/database/queries/applications_by_username_count.sql b/database/queries/applications_by_username_count.sql new file mode 100644 index 0000000..b3a4436 --- /dev/null +++ b/database/queries/applications_by_username_count.sql @@ -0,0 +1,3 @@ +SELECT COUNT(*) +FROM minecraft_manager_application +WHERE LOWER(username) LIKE ?; \ No newline at end of file diff --git a/database/queries/players_by_username.sql b/database/queries/players_by_username.sql index 76d5986..1f8f475 100644 --- a/database/queries/players_by_username.sql +++ b/database/queries/players_by_username.sql @@ -1,3 +1,3 @@ SELECT * FROM minecraft_manager_player -WHERE username LIKE ?; \ No newline at end of file +WHERE username LIKE ? LIMIT 10; \ No newline at end of file diff --git a/database/queries/players_by_username_application_notnull_count.sql b/database/queries/players_by_username_application_notnull_count.sql new file mode 100644 index 0000000..f4c6c74 --- /dev/null +++ b/database/queries/players_by_username_application_notnull_count.sql @@ -0,0 +1,4 @@ +SELECT COUNT(*) +FROM minecraft_manager_player +WHERE username LIKE ? + AND application_id IS NOT NULL; \ No newline at end of file diff --git a/discord/action.go b/discord/action.go new file mode 100644 index 0000000..1db7331 --- /dev/null +++ b/discord/action.go @@ -0,0 +1,117 @@ +package discord + +import ( + "database/sql" + "errors" + + "github.com/Karitham/corde" +) + +func (d Discord) accept(w corde.ResponseWriter, i *corde.Interaction) { + id := i.Data.Options.Int64("app_id") + + app, err := d.store.ApplicationByID(id) + if err != nil { + w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral().B()) + 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()) + return + } + + players, err := d.store.PlayersByUsername(app.Username) + if err != nil || len(players) == 0 { + if errors.Is(err, sql.ErrNoRows) { + return + } + w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B()) + 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()) + } + + 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()) + return + } + + w.Respond(corde.NewResp().Contentf("Application #%d was successfully accepted.", id).B()) + } +} + +func (d Discord) deny(w corde.ResponseWriter, i *corde.Interaction) { + id := i.Data.Options.Int64("app_id") + + app, err := d.store.ApplicationByID(id) + if err != nil { + w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral().B()) + 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()) + return + } + + players, err := d.store.PlayersByUsername(app.Username) + if err != nil || len(players) == 0 { + if errors.Is(err, sql.ErrNoRows) { + return + } + w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B()) + 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()) + } + + 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()) + return + } + + w.Respond(corde.NewResp().Contentf("Application #%d was successfully denied.", id).B()) + } +} + +func (d Discord) clear(w corde.ResponseWriter, i *corde.Interaction) { + id := i.Data.Options.Int64("app_id") + + app, err := d.store.ApplicationByID(id) + if err != nil { + w.Respond(corde.NewResp().Contentf("No application #%d was found.", id).Ephemeral().B()) + 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()) + return + } + + players, err := d.store.PlayersByUsername(app.Username) + if err != nil || len(players) == 0 { + if errors.Is(err, sql.ErrNoRows) { + return + } + w.Respond(corde.NewResp().Contentf("Could not link player to application", id).Ephemeral().B()) + 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("Application #%d was successfully cleared.", id).B()) + } +} diff --git a/discord/info.go b/discord/info.go new file mode 100644 index 0000000..56500f9 --- /dev/null +++ b/discord/info.go @@ -0,0 +1,95 @@ +package discord + +import ( + "fmt" + "strings" + + "git.jojodev.com/minecraft/mcm-discord/database" + "github.com/Karitham/corde" +) + +func appResponse(app *database.Application) *corde.InteractionRespData { + 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("Age", fmt.Sprint(app.Age)). + Field("Type of Player", app.PlayerType). + Field("Ever been Banned", fmt.Sprint(app.EverBanned)) + if app.EverBanned && app.EverBannedExplanation != "" { + embed.Field("Reason for being banned", app.EverBannedExplanation) + } + if app.Reference != "" { + embed.Field("Reference", app.Reference) + } + embed.Field("Read the Rules", app.ReadRules). + Field("Status", app.Status()) + return corde.NewResp().Embeds(embed.B()).B() +} + +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()) + return + } + + var app *database.Application + var err error + 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()) + return + } + } else { + apps, err := d.store.ApplicationsByUsername(username) + if err != nil { + w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B()) + return + } + if len(apps) == 0 { + apps, err = d.store.ApplicationsByPlayerUsername(username) + if err != nil { + w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B()) + return + } + if len(apps) == 1 { + app = apps[0] + } + } else if len(apps) == 1 { + app = apps[0] + } + } + + w.Respond(appResponse(app)) +} + +func (d Discord) search(w corde.ResponseWriter, i *corde.Interaction) { + username := i.Data.Options.String("username") + + apps, err := d.store.ApplicationsByUsername(username) + if err != nil { + w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B()) + return + } + count, err := d.store.ApplicationsByUsernameCount(username) + if err != nil { + w.Respond(corde.NewResp().Content("No applications matched.").Ephemeral().B()) + return + } + + var app *database.Application + if count > 0 { + if count == 1 { + app = apps[0] + } else { + } + } + + w.Respond(appResponse(app)) +} diff --git a/discord/mux.go b/discord/mux.go new file mode 100644 index 0000000..1587cbf --- /dev/null +++ b/discord/mux.go @@ -0,0 +1,52 @@ +package discord + +import ( + "git.jojodev.com/minecraft/mcm-discord/database" + "git.jojodev.com/minecraft/mcm-discord/socket" + "github.com/Karitham/corde" +) + +type Storer interface { + ApplicationByID(int64) (*database.Application, error) + ApplicationsByPlayerUsername(string) ([]*database.Application, error) + ApplicationsByUsername(string) ([]*database.Application, error) + ApplicationsByUsernameCount(string) (int64, error) + ApplicationUpdateAccepted(*bool, int64) error + PlayersByUsername(string) ([]*database.Player, error) + PlayersByUsernameApplicationNotNull(string) ([]*database.Player, error) + PlayersByUsernameApplicationNotNullCount(string) (int64, error) + PlayersByDiscordIsNull() ([]*database.Player, error) + PlayerUpdateApplication(int64, int64) error + PlayerUpdateDiscordID(string, int64) error +} + +type Discord struct { + store Storer + sock *socket.Socket + Mux *corde.Mux +} + +func New(store Storer, sock *socket.Socket, publicKey, appID, botToken string) *Discord { + m := corde.NewMux(publicKey, corde.SnowflakeFromString(appID), botToken) + + d := &Discord{ + store: store, + sock: sock, + Mux: m, + } + + m.Route("app", func(m *corde.Mux) { + // Info + m.Command("search", d.search) + m.Command("info", d.info) + + // Action + m.Command("accept", d.accept) + m.Command("deny", d.deny) + m.Command("clear", d.clear) + }) + // Sync + m.Command("sync", nil) + + return d +} diff --git a/discord/sync.go b/discord/sync.go new file mode 100644 index 0000000..d9f7d22 --- /dev/null +++ b/discord/sync.go @@ -0,0 +1 @@ +package discord diff --git a/go.mod b/go.mod index 1dc514c..d9ca654 100644 --- a/go.mod +++ b/go.mod @@ -6,3 +6,9 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/jmoiron/sqlx v1.3.4 ) + +require ( + github.com/Karitham/corde v0.5.0 // indirect + github.com/akrennmair/go-radix v1.0.1-0.20211215212324-49d05194b0a3 // indirect + github.com/rs/zerolog v1.26.1 // indirect +) diff --git a/go.sum b/go.sum index d76d949..baff890 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,45 @@ +github.com/Karitham/corde v0.5.0 h1:Y/H0uwRqTWlGfVt76Bto1BB338DZwZkwzhuqbvE8Vec= +github.com/Karitham/corde v0.5.0/go.mod h1:1HP9kpDAN1293JjCv+IrE9xKI0seOm5ZORWm9Z0A4k0= +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/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= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= 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/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= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +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= diff --git a/socket/socket.go b/socket/socket.go new file mode 100644 index 0000000..c6c6701 --- /dev/null +++ b/socket/socket.go @@ -0,0 +1,23 @@ +package socket + +import ( + "fmt" + "net" + "strings" +) + +type Socket struct { + Port int +} + +func (s *Socket) Command(args ...string) error { + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", s.Port)) + if err != nil { + return err + } + + if _, err := conn.Write([]byte(strings.Join(args, " "))); err != nil { + return err + } + return conn.Close() +}