This commit is contained in:
Bruno Rybársky 2024-08-23 12:19:44 +02:00
parent 8726a03e66
commit e176c80db7
9 changed files with 804 additions and 44 deletions

7
.gitignore vendored

@ -1,3 +1,6 @@
out
out/ out/
out/* out/*
secrets/.myconnectionstring
secrets
secrets/*
dataSources.xml

6
.idea/sqldialects.xml Normal file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MariaDB" />
</component>
</project>

4
go.mod

@ -3,6 +3,8 @@ module mcpingquick
go 1.23.0 go 1.23.0
require ( require (
github.com/Tnze/go-mc v1.20.2 github.com/go-sql-driver/mysql v1.8.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
) )
require filippo.io/edwards25519 v1.1.0 // indirect

6
go.sum

@ -1,4 +1,6 @@
github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

22
main.go

@ -1,8 +1,11 @@
package main package main
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
_ "github.com/go-sql-driver/mysql"
"log"
"os" "os"
) )
@ -14,6 +17,25 @@ func main() {
fmt.Println("Ty debil") fmt.Println("Ty debil")
fmt.Println(err) fmt.Println(err)
} }
connectionBytes, err := os.ReadFile("secrets/.myconnectionstring")
if err != nil {
return
}
db, err := sql.Open("mysql", string(connectionBytes))
if err != nil {
log.Fatal(err)
}
defer func(db *sql.DB) {
err := db.Close()
if err != nil {
log.Fatal(err)
}
}(db)
err = saveResponse(db, resp)
if err != nil {
log.Fatal(err)
return
}
// Pretty print the response // Pretty print the response
respJson, err := json.MarshalIndent(resp, "", " ") respJson, err := json.MarshalIndent(resp, "", " ")
if err != nil { if err != nil {

@ -86,6 +86,7 @@ func readPacket(reader *bufio.Reader, threshold int32) (packetID int32, packetDa
} }
n, packetID = receiveVarint(packetData) n, packetID = receiveVarint(packetData)
packetLength = len(packetData)
packetLength -= n packetLength -= n
packetData = packetData[n:] packetData = packetData[n:]
@ -94,37 +95,38 @@ func readPacket(reader *bufio.Reader, threshold int32) (packetID int32, packetDa
func createPacket(packetID int32, packetData []byte, threshold int32, startedCompression bool) ([]byte, error) { func createPacket(packetID int32, packetData []byte, threshold int32, startedCompression bool) ([]byte, error) {
var dataBuffer []byte var dataBuffer []byte
addVarint(&dataBuffer, packetID) addVarint(&dataBuffer, packetID) // Add the Packet ID as a VarInt
dataBuffer = append(dataBuffer, packetData...) // Append the packet data
dataBuffer = append(dataBuffer, packetData...)
var outBuffer []byte var outBuffer []byte
length := int32(len(dataBuffer)) var outTempBuffer []byte
length := int32(len(dataBuffer)) // Get the length of the uncompressed packet
if startedCompression { if startedCompression {
if threshold > 0 && length >= threshold { if threshold > 0 && length >= threshold {
// Compress the packet // Compress the packet if the length is greater than or equal to the threshold
compressedData, err := compress(dataBuffer) compressedData, err := compress(dataBuffer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dataBuffer = compressedData dataBuffer = compressedData
// Add the uncompressed length // Add the uncompressed length to outTempBuffer
addVarint(&outBuffer, int32(len(packetData))) addVarint(&outTempBuffer, length)
} else { } else {
// Set Data Length to 0 if not compressed // If not compressing (length < threshold), set Data Length to 0
addVarint(&outBuffer, 0) addVarint(&outTempBuffer, 0)
} }
} }
addVarint(&outBuffer, int32(len(dataBuffer))) // Append the (compressed or uncompressed) data buffer to outTempBuffer
outTempBuffer = append(outTempBuffer, dataBuffer...)
// Append compressed or uncompressed data // Add the total packet length to outBuffer
outBuffer = append(outBuffer, dataBuffer...) addVarint(&outBuffer, int32(len(outTempBuffer)))
outBuffer = append(outBuffer, outTempBuffer...)
// Add the Packet Length
// Return the final packet data
return outBuffer, nil return outBuffer, nil
} }

@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/google/uuid"
"net" "net"
"reflect" "reflect"
"strconv" "strconv"
@ -44,6 +45,12 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
response.Encryption = false response.Encryption = false
response.CompressionThreshold = -2 response.CompressionThreshold = -2
response.PluginDataSent = map[string]string{} response.PluginDataSent = map[string]string{}
response.ScanProgress = 0
response.ServerInfo = ServerInfo{
Hostname: host,
Port: port,
IP: ip.String(),
}
handshakePacketPing, err := createHandshakePacket(-1, host, port, 1, response.CompressionThreshold) handshakePacketPing, err := createHandshakePacket(-1, host, port, 1, response.CompressionThreshold)
if err != nil { if err != nil {
@ -83,6 +90,12 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
_, stringJson := receiveString(packetData) _, stringJson := receiveString(packetData)
jsonDecoder := json.NewDecoder(strings.NewReader(stringJson)) jsonDecoder := json.NewDecoder(strings.NewReader(stringJson))
err = jsonDecoder.Decode(&response) err = jsonDecoder.Decode(&response)
if err != nil {
errOut = err
println(stringJson)
return
}
response.ScanProgress = 1
if !didRestartConnection { if !didRestartConnection {
handshakePacketJoin, err := createHandshakePacket(response.Version.Protocol, host, port, 2, response.CompressionThreshold) handshakePacketJoin, err := createHandshakePacket(response.Version.Protocol, host, port, 2, response.CompressionThreshold)
if err != nil { if err != nil {
@ -116,11 +129,6 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
response.Username = username response.Username = username
PlayerUUID = constructOfflinePlayerUUID(username) PlayerUUID = constructOfflinePlayerUUID(username)
response.RawMessage = stringJson response.RawMessage = stringJson
if err != nil {
errOut = err
println(stringJson)
return
}
state = 1 state = 1
loginPacket, err := createOfflineLoginPacket(username, response.CompressionThreshold) loginPacket, err := createOfflineLoginPacket(username, response.CompressionThreshold)
if err != nil { if err != nil {
@ -161,6 +169,7 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
response.PluginDataSent[channelName] = channelValue response.PluginDataSent[channelName] = channelValue
} else if state == 2 { } else if state == 2 {
response.Encryption = true response.Encryption = true
response.ScanProgress = 2
return return
} else if state == 0 { } else if state == 0 {
//ping response //ping response
@ -578,7 +587,12 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
for i := 0; i < int(numberOfPlayers); i++ { for i := 0; i < int(numberOfPlayers); i++ {
player := PlayerUpdate{} player := PlayerUpdate{}
player.UUID = packetData[currentOffset : currentOffset+16] newUUID, err := uuid.FromBytes(packetData[currentOffset : currentOffset+16])
if err != nil {
errOut = err
return
}
player.UUID = newUUID
currentOffset += 16 currentOffset += 16
if actions&0x01 != 0x00 { if actions&0x01 != 0x00 {
@ -662,8 +676,9 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
player.DisplayName = &displayName player.DisplayName = &displayName
} }
} }
if !reflect.DeepEqual(PlayerUUID, player.UUID) {
response.PlayersInfo = append(response.PlayersInfo, player) response.PlayersInfo = append(response.PlayersInfo, player)
}
} }
} }
break break
@ -690,14 +705,14 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
} }
response.WorldBorder = WorldBorderInfo{ response.WorldBorder = WorldBorderInfo{
X: x, X: x,
Z: z, Z: z,
OldDiameter: oldDiameter, OldDiameter: oldDiameter,
NewDiameter: newDiameter, NewDiameter: newDiameter,
Speed: speed, Speed: speed,
PortalTeleportBoundry: portalTeleportBoundary, PortalTeleportBoundary: portalTeleportBoundary,
WarningBlocks: warningBlocks, WarningBlocks: warningBlocks,
WarningTime: warningTime, WarningTime: warningTime,
} }
} }
@ -752,6 +767,7 @@ func PingIP(ip net.IP, port uint16, host string) (response Response, errOut erro
errOut = err errOut = err
return return
} }
response.ScanProgress = 3
return //we dont want chunks return //we dont want chunks
} }
} }

@ -5,11 +5,18 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/google/uuid"
"io" "io"
"regexp" "regexp"
"strings" "strings"
) )
type ServerInfo struct {
Hostname string `json:"hostname,omitempty"`
Port uint16 `json:"port,omitempty"`
IP string `json:"ip,omitempty"`
}
type PlayerPosition struct { type PlayerPosition struct {
X float64 `json:"x,omitempty"` X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"` Y float64 `json:"y,omitempty"`
@ -56,8 +63,13 @@ type PlayerSignatureData struct {
PublicKeySignature []byte `json:"publicKeySignature,omitempty"` PublicKeySignature []byte `json:"publicKeySignature,omitempty"`
} }
type AddedPlayer struct {
UUID uuid.UUID
PlayerId int
}
type PlayerUpdate struct { type PlayerUpdate struct {
UUID []byte `json:"uuid,omitempty"` UUID uuid.UUID `json:"uuid,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Properties []PlayerProperty `json:"properties,omitempty"` Properties []PlayerProperty `json:"properties,omitempty"`
SignatureData *PlayerSignatureData `json:"signatureData,omitempty"` SignatureData *PlayerSignatureData `json:"signatureData,omitempty"`
@ -141,14 +153,14 @@ type TextComponent struct {
} }
type WorldBorderInfo struct { type WorldBorderInfo struct {
X float64 `json:"x,omitempty"` X float64 `json:"x,omitempty"`
Z float64 `json:"z,omitempty"` Z float64 `json:"z,omitempty"`
OldDiameter float64 `json:"oldDiameter,omitempty"` OldDiameter float64 `json:"oldDiameter,omitempty"`
NewDiameter float64 `json:"newDiameter,omitempty"` NewDiameter float64 `json:"newDiameter,omitempty"`
Speed int64 `json:"speed,omitempty"` Speed int64 `json:"speed,omitempty"`
PortalTeleportBoundry int32 `json:"portalTeleportBoundry,omitempty"` PortalTeleportBoundary int32 `json:"portalTeleportBoundry,omitempty"`
WarningBlocks int32 `json:"warningBlocks,omitempty"` WarningBlocks int32 `json:"warningBlocks,omitempty"`
WarningTime int32 `json:"warningTime,omitempty"` WarningTime int32 `json:"warningTime,omitempty"`
} }
// Custom unmarshaler for the TextComponent type // Custom unmarshaler for the TextComponent type
@ -248,4 +260,6 @@ type Response struct {
WorldAge int64 `json:"worldAge,omitempty"` WorldAge int64 `json:"worldAge,omitempty"`
WorldBorder WorldBorderInfo `json:"worldBorder,omitempty"` WorldBorder WorldBorderInfo `json:"worldBorder,omitempty"`
PlayersInfo []PlayerUpdate `json:"playersinfo,omitempty"` PlayersInfo []PlayerUpdate `json:"playersinfo,omitempty"`
ServerInfo ServerInfo `json:"ServerInfo,omitempty"`
ScanProgress byte `json:"ScanProgress,omitempty"`
} }

693
saver.go Normal file

@ -0,0 +1,693 @@
package main
import (
"database/sql"
"errors"
"github.com/google/uuid"
"log"
)
func saveResponse(db *sql.DB, response Response) error {
if response.ScanProgress < 1 {
return nil
}
tx, err := db.Begin()
if err != nil {
return err
}
commited := false
defer func(tx *sql.Tx) {
if !commited {
err := tx.Rollback()
if err != nil {
log.Fatalf("Rollback failed: %v", err)
}
}
}(tx)
// Insert or update server details
serverID, err := insertOrUpdateServer(tx, response.ServerInfo)
if err != nil {
return err
}
// Insert or update favicon
faviconID, err := insertFavicon(tx, response.Favicon.PngData)
if err != nil {
return err
}
// Insert a scan entry
scanID, err := insertScan(tx, response, serverID, faviconID)
if err != nil {
return err
}
var addedPlayers []AddedPlayer
// Insert players and seen players
err = insertPlayersAndSeenPlayers(tx, response.Players.Sample, scanID, &addedPlayers)
if err != nil {
return err
}
if response.ScanProgress >= 3 {
err = insertDimensions(tx, response.PlayerLoginInfo.Dimensions, scanID)
if err != nil {
return err
}
// Insert players and seen players
err = insertPlayersAndSeenPlayersUpdate(tx, response.PlayersInfo, scanID, &addedPlayers)
if err != nil {
return err
}
// Insert plugin data sent
err = insertPluginData(tx, response.PluginDataSent, scanID)
if err != nil {
return err
}
// Insert feature flags and link to scan
err = insertFeatureFlags(tx, response.FeatureFlags, scanID)
if err != nil {
return err
}
// Insert datapacks and link to scan
err = insertDatapacks(tx, response.EnabledDatapacks, scanID)
if err != nil {
return err
}
// Insert registry entries
err = insertRegistryEntries(tx, response.RegistryDatas, scanID)
if err != nil {
return err
}
// Insert player updates and properties
err = insertPlayerUpdates(tx, response.PlayersInfo, scanID, &addedPlayers)
if err != nil {
return err
}
err = saveUpdateTags(tx, response.Tags, scanID)
if err != nil {
return err
}
}
commited = true
return tx.Commit()
}
// Insert or update server details
func insertOrUpdateServer(tx *sql.Tx, serverInfo ServerInfo) (int, error) {
// First, try to select the server with the provided hostname, IP, and port
var serverID int
err := tx.QueryRow(
"SELECT id FROM servers WHERE hostname = ? AND ip = ? AND port = ?",
serverInfo.Hostname, serverInfo.IP, serverInfo.Port,
).Scan(&serverID)
// If the server already exists, update the details
if err == nil {
_, err := tx.Exec(
"UPDATE servers SET hostname = ?, ip = ?, port = ? WHERE id = ?",
serverInfo.Hostname, serverInfo.IP, serverInfo.Port, serverID,
)
if err != nil {
return 0, err
}
return serverID, nil
}
// If the server does not exist, insert a new record
if errors.Is(err, sql.ErrNoRows) {
result, err := tx.Exec(
"INSERT INTO servers (hostname, ip, port) VALUES (?, ?, ?)",
serverInfo.Hostname, serverInfo.IP, serverInfo.Port,
)
if err != nil {
return 0, err
}
newServerID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(newServerID), nil
}
// Return error if something went wrong during the SELECT query
return 0, err
}
// Insert favicon if not exists
func insertFavicon(tx *sql.Tx, pngData []byte) (int, error) {
if pngData == nil {
pngData = []byte{}
}
result, err := tx.Exec("INSERT INTO favicons (favicon) VALUES (?) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)", pngData)
if err != nil {
return 0, err
}
faviconID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(faviconID), nil
}
// Insert scan data
func insertScan(tx *sql.Tx, response Response, serverID int, faviconID int) (int, error) {
result, err := tx.Exec(
`INSERT INTO scans
(server_id, progress, timestamp, favicon_id, raw_message, version, max_players, online_players, description, enforces_secure_chat,
prevents_chat_reports, compression_threshold, is_offline_mode, encryption, message, entity_id, hardcore, view_distance,
simulation_distance, reduced_debug_info, enable_respawn_screen, do_limited_crafting, dimension_type, dimension_name,
hashed_seed, game_mode, previous_game_mode, is_debug, is_flat, has_death_location, death_dimension_name, death_location_x,
death_location_y, death_location_z, portal_cooldown, server_difficulty,
server_difficulty_locked, player_slot, player_location_x, player_location_y,player_location_z,player_location_pitch,
player_location_yaw, invulnerable, flying, allow_flying, creative_mode, flying_speed, field_of_view_modifier,
default_position_spawn_x, default_position_spawn_y, default_position_spawn_z, default_position_spawn_angle,
time_of_day, world_age, world_border_x, world_border_z, world_border_old_diameter, world_border_new_diameter,
world_border_speed, world_border_portal_teleport_boundary, world_border_warning_blocks, world_border_warning_time)
VALUES (?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
serverID, response.ScanProgress, faviconID, response.RawMessage, response.Version.Name, response.Players.Max, response.Players.Online, response.Description.CleanText,
response.EnforcesSecureChat, response.PreventsChatReports, response.CompressionThreshold, response.IsOfflineMode, response.Encryption,
response.Message.CleanText, response.PlayerLoginInfo.EntityID, response.PlayerLoginInfo.Hardcore, response.PlayerLoginInfo.ViewDistance,
response.PlayerLoginInfo.SimulationDistance, response.PlayerLoginInfo.ReducedDebugInfo, response.PlayerLoginInfo.EnableRespawnScreen,
response.PlayerLoginInfo.DoLimitedCrafting, response.PlayerLoginInfo.DimensionType, response.PlayerLoginInfo.DimensionName,
response.PlayerLoginInfo.HashedSeed, response.PlayerLoginInfo.GameMode, response.PlayerLoginInfo.PreviousGameMode,
response.PlayerLoginInfo.IsDebug, response.PlayerLoginInfo.IsFlat, response.PlayerLoginInfo.HasDeathLocation,
response.PlayerLoginInfo.DeathDimensionName, response.PlayerLoginInfo.DeathLocation.X,
response.PlayerLoginInfo.DeathLocation.Y, response.PlayerLoginInfo.DeathLocation.Z, response.PlayerLoginInfo.PortalCooldown,
response.ServerDifficulty.Difficulty, response.ServerDifficulty.Locked, response.PlayerSlot,
response.PlayerLocation.X, response.PlayerLocation.Y, response.PlayerLocation.Z, response.PlayerLocation.Pitch, response.PlayerLocation.Yaw,
response.PlayerAbilities.Invulnerable, response.PlayerAbilities.Flying, response.PlayerAbilities.AllowFlying, response.PlayerAbilities.CreativeMode, response.PlayerAbilities.FlyingSpeed,
response.PlayerAbilities.FieldOfViewModifier, response.DefaultPositionSpawn.Location.X, response.DefaultPositionSpawn.Location.Y, response.DefaultPositionSpawn.Location.Z, response.DefaultPositionSpawn.Angle,
response.TimeOfDay, response.WorldAge, response.WorldBorder.X, response.WorldBorder.Z, response.WorldBorder.OldDiameter, response.WorldBorder.NewDiameter, response.WorldBorder.Speed, response.WorldBorder.PortalTeleportBoundary,
response.WorldBorder.WarningBlocks, response.WorldBorder.WarningTime)
if err != nil {
return 0, err
}
scanID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(scanID), nil
}
// Insert dimensions and link to scan
func insertDimensions(tx *sql.Tx, dimensions []string, scanID int) error {
for _, dimension := range dimensions {
var dimensionID int
err := tx.QueryRow(
"SELECT id FROM dimensions WHERE name = ?",
dimension,
).Scan(&dimensionID)
if errors.Is(err, sql.ErrNoRows) {
// If the dimension does not exist, insert it
result, err := tx.Exec(
"INSERT INTO dimensions (name) VALUES (?)",
dimension,
)
if err != nil {
return err
}
dimensionID64, err := result.LastInsertId()
if err != nil {
return err
}
dimensionID = int(dimensionID64)
} else if err != nil {
return err
}
// Link the dimension to the scan
_, err = tx.Exec(
"INSERT INTO dimensions_scans (scan_id, dimension_id) VALUES (?, ?)",
scanID, dimensionID,
)
if err != nil {
return err
}
}
return nil
}
// Insert players and seen players
func insertPlayersAndSeenPlayers(tx *sql.Tx, players []Player, scanID int, addedPlayers *[]AddedPlayer) (errOut error) {
if addedPlayers == nil {
return errors.New("added players cannot be nil")
}
for _, player := range players {
// Check if the player has already been added
canAdd := true
for _, addedPlayer := range *addedPlayers {
if addedPlayer.UUID == player.ID {
canAdd = false
break
}
}
if !canAdd {
continue
}
playerID, err := insertPlayer(tx, player)
if err != nil {
return err
}
*addedPlayers = append(*addedPlayers, AddedPlayer{UUID: player.ID, PlayerId: playerID})
}
return nil
}
// Insert players and seen players updates
func insertPlayersAndSeenPlayersUpdate(tx *sql.Tx, players []PlayerUpdate, scanID int, addedPlayers *[]AddedPlayer) (errOut error) {
if addedPlayers == nil {
return errors.New("added players cannot be nil")
}
for _, player := range players {
// Check if the player has already been added
canAdd := true
for _, addedPlayer := range *addedPlayers {
if addedPlayer.UUID == player.UUID {
canAdd = false
break
}
}
if !canAdd {
continue
}
playerID, err := insertPlayerUpdateIntoPlayers(tx, player)
if err != nil {
return err
}
*addedPlayers = append(*addedPlayers, AddedPlayer{UUID: player.UUID, PlayerId: playerID})
}
return nil
}
// Insert or update player (UUID and username)
func insertPlayer(tx *sql.Tx, player Player) (int, error) {
var playerID int
// Check if a player with the given UUID already exists
err := tx.QueryRow(
"SELECT id FROM players WHERE uuid = ?",
player.ID.String(),
).Scan(&playerID)
if err == nil {
// If the player exists, check if the username needs to be updated
_, err = tx.Exec(
"UPDATE players SET username = ? WHERE id = ? AND username != ?",
player.Name, playerID, player.Name,
)
if err != nil {
return 0, err
}
return playerID, nil
}
// If the player does not exist, insert a new record
if errors.Is(err, sql.ErrNoRows) {
result, err := tx.Exec(
"INSERT INTO players (uuid, username) VALUES (?, ?)",
player.ID.String(), player.Name,
)
if err != nil {
return 0, err
}
newPlayerID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(newPlayerID), nil
}
// Return error if something went wrong during the SELECT query
return 0, err
}
// Insert or update player with additional fields (UUID and username)
func insertPlayerUpdateIntoPlayers(tx *sql.Tx, player PlayerUpdate) (int, error) {
var playerID int
// Check if a player with the given UUID already exists
err := tx.QueryRow(
"SELECT id FROM players WHERE uuid = ?",
player.UUID.String(),
).Scan(&playerID)
if err == nil {
// If the player exists, update the relevant fields only if needed
_, err = tx.Exec(
"UPDATE players SET username = ?, game_mode = ?, listed = ?, ping = ?, display_name = ? WHERE id = ? AND (username != ? OR game_mode != ? OR listed != ? OR ping != ? OR display_name != ?)",
player.Name, player.GameMode, player.Listed, player.Ping, player.DisplayName,
playerID, player.Name, player.GameMode, player.Listed, player.Ping, player.DisplayName,
)
if err != nil {
return 0, err
}
return playerID, nil
}
// If the player does not exist, insert a new record
if errors.Is(err, sql.ErrNoRows) {
result, err := tx.Exec(
"INSERT INTO players (uuid, username, game_mode, listed, ping, display_name) VALUES (?, ?, ?, ?, ?, ?)",
player.UUID.String(), player.Name, player.GameMode, player.Listed, player.Ping, player.DisplayName,
)
if err != nil {
return 0, err
}
newPlayerID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(newPlayerID), nil
}
// Return error if something went wrong during the SELECT query
return 0, err
}
// Insert plugin data sent
func insertPluginData(tx *sql.Tx, pluginData map[string]string, scanID int) error {
for name, value := range pluginData {
_, err := tx.Exec("INSERT INTO plugin_data_sent (scan_id, name, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)", scanID, name, value)
if err != nil {
return err
}
}
return nil
}
func insertTag(tx *sql.Tx, tagName string) (int, error) {
var tagID int
err := tx.QueryRow("SELECT id FROM tags WHERE name = ?", tagName).Scan(&tagID)
if errors.Is(err, sql.ErrNoRows) {
result, err := tx.Exec("INSERT INTO tags (name) VALUES (?)", tagName)
if err != nil {
return 0, err
}
tagID64, err := result.LastInsertId()
if err != nil {
return 0, err
}
tagID = int(tagID64)
} else if err != nil {
return 0, err
}
return tagID, nil
}
func insertEntry(tx *sql.Tx, value int32) (int, error) {
var entryID int
err := tx.QueryRow("SELECT id FROM entries WHERE value = ?", value).Scan(&entryID)
if errors.Is(err, sql.ErrNoRows) {
result, err := tx.Exec("INSERT INTO entries (value) VALUES (?)", value)
if err != nil {
return 0, err
}
entryID64, err := result.LastInsertId()
if err != nil {
return 0, err
}
entryID = int(entryID64)
} else if err != nil {
return 0, err
}
return entryID, nil
}
func saveUpdateTags(tx *sql.Tx, updates []UpdateTag, scanID int) error {
for _, update := range updates {
// Insert the tag registry identifier
registryID, err := insertTagRegistry(tx, update.TagRegistryIdentifier)
if err != nil {
return err
}
for _, tagArray := range update.Tags {
// Insert the tag
tagID, err := insertTag(tx, tagArray.TagName)
if err != nil {
return err
}
// Link the tag with the registry
err = insertTagRegistryTag(tx, registryID, tagID)
if err != nil {
return err
}
// Insert the entries and link them with the tag
for _, entryValue := range tagArray.Entries {
entryID, err := insertEntry(tx, entryValue)
if err != nil {
return err
}
err = insertTagEntry(tx, tagID, entryID, scanID)
if err != nil {
return err
}
}
}
}
return nil
}
func insertTagRegistry(tx *sql.Tx, registryIdentifier string) (int, error) {
var registryID int
err := tx.QueryRow("SELECT id FROM tag_registries WHERE registry_identifier = ?", registryIdentifier).Scan(&registryID)
if errors.Is(err, sql.ErrNoRows) {
result, err := tx.Exec("INSERT INTO tag_registries (registry_identifier) VALUES (?)", registryIdentifier)
if err != nil {
return 0, err
}
registryID64, err := result.LastInsertId()
if err != nil {
return 0, err
}
registryID = int(registryID64)
} else if err != nil {
return 0, err
}
return registryID, nil
}
func insertTagRegistryTag(tx *sql.Tx, registryID int, tagID int) error {
_, err := tx.Exec("INSERT IGNORE INTO tag_registry_tags (registry_id, tag_id) VALUES (?, ?)", registryID, tagID)
return err
}
func insertTagEntry(tx *sql.Tx, tagID int, entryID int, scanID int) error {
_, err := tx.Exec("INSERT IGNORE INTO tag_entries (tag_id, entry_id, scan_id) VALUES (?, ?, ?)", tagID, entryID, scanID)
return err
}
// Insert feature flags and link to scan
func insertFeatureFlags(tx *sql.Tx, featureFlags []string, scanID int) error {
for _, flagText := range featureFlags {
var featureID int
err := tx.QueryRow(
"SELECT id FROM feature_flags WHERE flag_text = ?",
flagText,
).Scan(&featureID)
if errors.Is(err, sql.ErrNoRows) {
// If the feature flag does not exist, insert it
featureID, err = insertFeatureFlag(tx, flagText)
if err != nil {
return err
}
} else if err != nil {
return err
}
// Link the feature flag to the scan
_, err = tx.Exec(
"INSERT INTO feature_flag_scans (feature_flag_id, scan_id) VALUES (?, ?)",
featureID, scanID,
)
if err != nil {
return err
}
}
return nil
}
// Insert feature flag (ID, flagText)
func insertFeatureFlag(tx *sql.Tx, flagText string) (int, error) {
result, err := tx.Exec("INSERT INTO feature_flags (flag_text) VALUES (?) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)", flagText)
if err != nil {
return 0, err
}
featureID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(featureID), nil
}
// Insert datapacks and link to scan
func insertDatapacks(tx *sql.Tx, datapacks []DatapackInfo, scanID int) error {
for _, dp := range datapacks {
datapackID, err := insertDatapack(tx, dp)
if err != nil {
return err
}
_, err = tx.Exec("INSERT INTO datapack_scans (datapack_id, scan_id) VALUES (?, ?)", datapackID, scanID)
if err != nil {
return err
}
}
return nil
}
// Insert or update datapack (ID, namespace, datapack ID, version)
func insertDatapack(tx *sql.Tx, dp DatapackInfo) (int, error) {
// First, try to select the datapack with the provided namespace and datapack ID
var datapackID int
err := tx.QueryRow(
"SELECT id FROM datapacks WHERE namespace = ? AND datapack_id = ?",
dp.Namespace, dp.ID,
).Scan(&datapackID)
// If the datapack already exists, update the version
if err == nil {
_, err = tx.Exec(
"UPDATE datapacks SET version = ? WHERE id = ?",
dp.Version, datapackID,
)
if err != nil {
return 0, err
}
return datapackID, nil
}
// If the datapack does not exist, insert a new record
if errors.Is(err, sql.ErrNoRows) {
result, err := tx.Exec(
"INSERT INTO datapacks (namespace, datapack_id, version) VALUES (?, ?, ?)",
dp.Namespace, dp.ID, dp.Version,
)
if err != nil {
return 0, err
}
newDatapackID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(newDatapackID), nil
}
// Return error if something went wrong during the SELECT query
return 0, err
}
// Insert registry entries
func insertRegistryEntries(tx *sql.Tx, registryDatas []RegistryData, scanID int) error {
for _, entry1 := range registryDatas {
for _, entry := range entry1.Entries {
_, err := insertRegistryEntry(tx, entry, entry1.RegistryID, scanID)
if err != nil {
return err
}
// Assuming linking logic with scan ID is required
}
}
return nil
}
// Insert registry entry (ID, hasNBT, nbt_data as BLOB)
func insertRegistryEntry(tx *sql.Tx, entry RegistryEntry, registryID string, scanID int) (int, error) {
var nbtData []byte
if entry.HasNBT {
nbtData = entry.NBTData
} else {
nbtData = []byte{}
}
result, err := tx.Exec("INSERT IGNORE INTO registry_entries (scan_id, registry_id, entry_id, has_nbt, nbt_data) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE scan_id = LAST_INSERT_ID(scan_id)", scanID, registryID, entry.EntryID, entry.HasNBT, nbtData)
if err != nil {
return 0, err
}
entryID, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(entryID), nil
}
// Insert player updates and properties
func insertPlayerUpdates(tx *sql.Tx, playerUpdates []PlayerUpdate, scanID int, addedPlayers *[]AddedPlayer) error {
for _, update := range playerUpdates {
updateID, err := insertPlayerUpdate(tx, update, scanID, addedPlayers)
if err != nil {
return err
}
err = insertPlayerProperties(tx, update.Properties, updateID)
if err != nil {
return err
}
}
return nil
}
// Insert player update (linked to player and scan)
func insertPlayerUpdate(tx *sql.Tx, update PlayerUpdate, scanID int, addedPlayers *[]AddedPlayer) (int, error) {
playerID, err := getPlayerID(tx, update.UUID, addedPlayers)
if err != nil {
return 0, err
}
// If the update exists, update the existing record
res, err := tx.Exec(
"INSERT INTO player_sightnings (player_id, username, game_mode, listed, ping, display_name, scan_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
playerID, update.Name, update.GameMode, update.Listed, update.Ping, update.DisplayName, scanID,
)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
if err != nil {
return 0, err
}
return int(lastID), nil
}
// Insert player properties (linked to player update)
func insertPlayerProperties(tx *sql.Tx, properties []PlayerProperty, updateID int) error {
for _, prop := range properties {
_, err := tx.Exec("INSERT INTO player_properties (player_update_id, name, value, signature) VALUES (?, ?, ?, ?)", updateID, prop.Name, prop.Value, prop.Signature)
if err != nil {
return err
}
}
return nil
}
// Helper function to get player ID by UUID
func getPlayerID(tx *sql.Tx, uuid uuid.UUID, addedPlayers *[]AddedPlayer) (int, error) {
for _, addedPlayer := range *addedPlayers {
if addedPlayer.UUID == uuid {
return addedPlayer.PlayerId, nil
}
}
var playerID int
err := tx.QueryRow("SELECT id FROM players WHERE uuid = ?", uuid.String()).Scan(&playerID)
if err != nil {
return 0, err
}
return playerID, nil
}