Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func StartServer(reload bool) {
PinfoRoute,
QueryRoute,
RemoveHashRoute,
SetMKWRatingRoute,
SetHashRoute,
UnbanRoute,
}
Expand Down
64 changes: 64 additions & 0 deletions api/mkw_rr_ratings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package api

import (
"encoding/json"
"net/http"
"net/url"
"strconv"
"wwfc/database"
)

type MKWRatingResponse struct {
Found int32 `json:"found"`
VR int32 `json:"vr"`
BR int32 `json:"br"`
}

func HandleMKWRatings(w http.ResponseWriter, r *http.Request) {
u, err := url.Parse(r.URL.String())
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

query, err := url.ParseQuery(u.RawQuery)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

pids := query["pid"]
if len(pids) != 1 {
w.WriteHeader(http.StatusBadRequest)
return
}

pid64, err := strconv.ParseUint(pids[0], 10, 32)
if err != nil || pid64 == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}

vr, br, found := database.GetMKWVRBR(pool, ctx, uint32(pid64))
response := MKWRatingResponse{
Found: 0,
VR: 0,
BR: 0,
}
if found {
response.Found = 1
response.VR = vr
response.BR = br
}

jsonData, err := json.Marshal(response)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
w.Write(jsonData)
}
8 changes: 8 additions & 0 deletions api/pinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ func handlePinfoImpl(req PinfoRequest, validSecret bool) (*database.User, int, e
return &database.User{}, http.StatusInternalServerError, err
}

vr, br, ratingErr := database.GetMKWRawVRBR(pool, ctx, req.ProfileID)
if ratingErr == nil {
realUser.VR = vr
realUser.BR = br
}

if !validSecret {
// Invalid secret, only report normal user info
ret = &database.User{
Expand All @@ -42,6 +48,8 @@ func handlePinfoImpl(req PinfoRequest, validSecret bool) (*database.User, int, e
BanIssued: realUser.BanIssued,
BanExpires: realUser.BanExpires,
DiscordID: realUser.DiscordID,
VR: realUser.VR,
BR: realUser.BR,
}
} else {
ret = &realUser
Expand Down
117 changes: 117 additions & 0 deletions api/set_mkw_rating.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package api

import (
"errors"
"net/http"
"strings"
"wwfc/database"
"wwfc/gpcm"
)

const (
defaultMKWManualRating = 5000
minMKWManualRating = 100
maxMKWManualRating = 1000000
)

var (
ErrRatingType = errors.New("rating type must be either 'vr' or 'br'")
ErrRatingValue = errors.New("rating value must be between 100 and 1000000")
)

type SetMKWRatingRequest struct {
Secret string `json:"secret"`
ProfileID uint32 `json:"pid"`
RatingType string `json:"rating_type"`
Reason string `json:"reason"`
Value int32 `json:"value"`
}

type SetMKWRatingResponse struct {
User database.User `json:"user"`
RatingType string `json:"rating_type"`
PreviousValue int32 `json:"previous_value"`
Value int32 `json:"value"`
VR int32 `json:"vr"`
BR int32 `json:"br"`
Success bool `json:"success"`
Error string `json:"error"`
}

var SetMKWRatingRoute = MakeRouteSpec[SetMKWRatingRequest, SetMKWRatingResponse](
true,
"/api/set_mkw_rating",
func(req any, _ bool, _ *http.Request) (any, int, error) {
return handleSetMKWRatingImpl(req.(SetMKWRatingRequest))
},
http.MethodPost,
)

func handleSetMKWRatingImpl(req SetMKWRatingRequest) (SetMKWRatingResponse, int, error) {
if req.ProfileID == 0 {
return SetMKWRatingResponse{}, http.StatusBadRequest, ErrPIDMissing
}

if req.Value < minMKWManualRating || req.Value > maxMKWManualRating {
return SetMKWRatingResponse{}, http.StatusBadRequest, ErrRatingValue
}

currentVR := int32(defaultMKWManualRating)
currentBR := int32(defaultMKWManualRating)

storedVR, storedBR, err := database.GetMKWRawVRBR(pool, ctx, req.ProfileID)
if err != nil {
return SetMKWRatingResponse{}, http.StatusNotFound, ErrUserQuery
}

if storedVR != nil {
currentVR = *storedVR
}

if storedBR != nil {
currentBR = *storedBR
}

ratingType := strings.ToLower(req.RatingType)
reason := strings.TrimSpace(req.Reason)
previousValue := int32(0)

switch ratingType {
case "vr":
previousValue = currentVR
currentVR = req.Value
case "br":
previousValue = currentBR
currentBR = req.Value
default:
return SetMKWRatingResponse{}, http.StatusBadRequest, ErrRatingType
}

if err := database.UpdateMKWVRBR(pool, ctx, req.ProfileID, currentVR, currentBR); err != nil {
return SetMKWRatingResponse{}, http.StatusInternalServerError, ErrTransaction
}

user, err := database.GetProfile(pool, ctx, req.ProfileID)
if err != nil {
return SetMKWRatingResponse{}, http.StatusInternalServerError, ErrUserQueryTransaction
}

kickReason := "Your VR/BR was updated."
if reason != "" {
kickReason = "Your VR/BR was updated. - " + reason
}

err = gpcm.KickPlayer(req.ProfileID, kickReason, gpcm.WWFCMsgKickedCustom)
if err != nil {
return SetMKWRatingResponse{}, http.StatusInternalServerError, err
}

return SetMKWRatingResponse{
User: user,
RatingType: ratingType,
PreviousValue: previousValue,
Value: req.Value,
VR: currentVR,
BR: currentBR,
}, http.StatusOK, nil
}
12 changes: 10 additions & 2 deletions database/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ func UpdateTables(pool *pgxpool.Pool, ctx context.Context) {
ADD IF NOT EXISTS ban_reason_hidden character varying,
ADD IF NOT EXISTS ban_moderator character varying,
ADD IF NOT EXISTS ban_tos boolean,
ADD IF NOT EXISTS open_host boolean DEFAULT false;
ADD IF NOT EXISTS discord_id character varying
ADD IF NOT EXISTS open_host boolean DEFAULT false,
ADD IF NOT EXISTS discord_id character varying;

`)

pool.Exec(ctx, `

ALTER TABLE ONLY public.users
ADD IF NOT EXISTS mariokartwii_vr integer,
ADD IF NOT EXISTS mariokartwii_br integer;

`)

Expand Down
35 changes: 35 additions & 0 deletions database/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const (
GetMKWFriendInfoQuery = `SELECT mariokartwii_friend_info FROM users WHERE profile_id = $1`
UpdateMKWFriendInfoQuery = `UPDATE users SET mariokartwii_friend_info = $2 WHERE profile_id = $1`
CountTotalUsersQuery = `SELECT COUNT(DISTINCT csnum) FROM users`
GetMKWVRBRQuery = `SELECT COALESCE(mariokartwii_vr, 0), COALESCE(mariokartwii_br, 0), (mariokartwii_vr IS NOT NULL AND mariokartwii_br IS NOT NULL) FROM users WHERE profile_id = $1`
GetMKWRawVRBRQuery = `SELECT mariokartwii_vr, mariokartwii_br FROM users WHERE profile_id = $1`
UpdateMKWVRBRQuery = `UPDATE users SET mariokartwii_vr = $2, mariokartwii_br = $3 WHERE profile_id = $1`
)

type LinkStage byte
Expand Down Expand Up @@ -69,6 +72,8 @@ type User struct {
BanReasonHidden string
BanIssued *time.Time
BanExpires *time.Time
VR *int32
BR *int32
}

var (
Expand Down Expand Up @@ -298,6 +303,36 @@ func UpdateMKWFriendInfo(pool *pgxpool.Pool, ctx context.Context, profileId uint
}
}

func GetMKWVRBR(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (int32, int32, bool) {
var vr int32
var br int32
var found bool

err := pool.QueryRow(ctx, GetMKWVRBRQuery, profileId).Scan(&vr, &br, &found)
if err != nil {
return 0, 0, false
}

return vr, br, found
}

func GetMKWRawVRBR(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (*int32, *int32, error) {
var vr *int32
var br *int32

err := pool.QueryRow(ctx, GetMKWRawVRBRQuery, profileId).Scan(&vr, &br)
if err != nil {
return nil, nil, err
}

return vr, br, nil
}

func UpdateMKWVRBR(pool *pgxpool.Pool, ctx context.Context, profileId uint32, vr int32, br int32) error {
_, err := pool.Exec(ctx, UpdateMKWVRBRQuery, profileId, vr, br)
return err
}

// ScanUsers takes a query returning pids and collect the matching users
func ScanUsers(pool *pgxpool.Pool, ctx context.Context, query string) ([]User, error) {
logging.Info("QUERY", "Executing query", aurora.Cyan(query))
Expand Down
52 changes: 52 additions & 0 deletions gpcm/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,48 @@ package gpcm

import (
"strconv"
"strings"
"wwfc/common"
"wwfc/database"
"wwfc/logging"
"wwfc/qr2"

"github.com/logrusorgru/aurora/v3"
)

func parseMKWVRBRRecord(value string) (int32, int32, bool) {
parts := strings.Split(value, "|")
var vr int64 = -1
var br int64 = -1

for _, part := range parts {
keyValue := strings.SplitN(part, "=", 2)
if len(keyValue) != 2 {
return 0, 0, false
}

parsed, err := strconv.ParseInt(keyValue[1], 10, 32)
if err != nil || parsed < 1 || parsed > 1000000 {
return 0, 0, false
}

switch keyValue[0] {
case "vr":
vr = parsed
case "br":
br = parsed
default:
return 0, 0, false
}
}

if vr < 0 || br < 0 {
return 0, 0, false
}

return int32(vr), int32(br), true
}

func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) {
for key, value := range command.OtherValues {
logging.Info(g.ModuleName, "WiiLink Report:", aurora.Yellow(key))
Expand Down Expand Up @@ -87,6 +122,23 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) {
}

qr2.ProcessMKWRaceResult(g.User.ProfileId, value)

case "wl:mkw_vrbr":
if g.GameName != "mariokartwii" {
logging.Warn(g.ModuleName, "Ignoring", keyColored, "from wrong game")
continue
}

vr, br, ok := parseMKWVRBRRecord(value)
if !ok {
logging.Error(g.ModuleName, "Invalid", keyColored, "record:", aurora.Cyan(value))
continue
}

err := database.UpdateMKWVRBR(pool, ctx, g.User.ProfileId, vr, br)
if err != nil {
logging.Error(g.ModuleName, "Failed to persist", keyColored, "for", aurora.Cyan(g.User.ProfileId), ":", err)
}
}
}
}
8 changes: 6 additions & 2 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ CREATE TABLE IF NOT EXISTS public.users (
unique_nick character varying NOT NULL,
firstname character varying,
lastname character varying DEFAULT ''::character varying,
mariokartwii_friend_info character varying
mariokartwii_friend_info character varying,
mariokartwii_vr integer,
mariokartwii_br integer
);


Expand All @@ -50,7 +52,9 @@ ALTER TABLE ONLY public.users
ADD IF NOT EXISTS ban_tos boolean,
ADD IF NOT EXISTS open_host boolean DEFAULT false,
ADD IF NOT EXISTS csnum character varying[],
ADD IF NOT EXISTS discord_id character varying;
ADD IF NOT EXISTS discord_id character varying,
ADD IF NOT EXISTS mariokartwii_vr integer,
ADD IF NOT EXISTS mariokartwii_br integer;

--
-- Change ng_device_id from bigint to bigint[]
Expand Down