package main import ( "database/sql" "errors" "fmt" "github.com/yuin/gluamapper" "github.com/yuin/gopher-lua" "golang.org/x/text/language" "golang.org/x/text/message" "io" "os" "sort" "strings" ) var db *sql.DB const ( dbName = "gambot.sqlite" ) func initDB() (err error) { var newDb bool _, err = os.Stat(dbName) if errors.Is(err, os.ErrNotExist) { newDb = true } db, err = sql.Open("sqlite3", dbName) if err != nil { return } if newDb { commands := []string{ `CREATE TABLE 'players' ( 'name' VARCHAR(18) NOT NULL PRIMARY KEY, 'val' integer SIGNED NOT NULL);`, } for _, command := range commands { _, err = db.Exec(command) } } return } type Player struct { Name string Val int } func (p *Player) FormatVal() string { pr := message.NewPrinter(language.English) return pr.Sprintf("%d", p.Val) } func getPlayers() ([]Player, error) { players := make([]Player, 0) tx, err := db.Begin() if err != nil { return nil, fmt.Errorf("failed to begin transaction: %s", err) } stmt, err := tx.Prepare("SELECT * FROM players") if err != nil { return nil, fmt.Errorf("failed to prepare statement: %s", err) } defer stmt.Close() rows, err := stmt.Query() if err != nil { return nil, fmt.Errorf("failed to query rows: %s", err) } defer rows.Close() for rows.Next() { var name string var val int err = rows.Scan(&name, &val) if err != nil { return nil, fmt.Errorf("failed to scan row: %s", err) } players = append(players, Player{name, val}) } sort.Slice(players, func(i, j int) bool { return players[i].Val > players[j].Val }) return players, nil } type WoWGoldGamblerDB struct { ProfileKeys map[string]string Global struct { Game struct { ChatChannel string Wager int RealmFilter bool } Stats struct { Records struct { Overall map[string]any Classic map[string]any } Player map[string]int Aliases map[string][]string } } } func readTable(path string) error { L := lua.NewState() defer L.Close() L.OpenLibs() file, err := os.Open(path) if err != nil { return fmt.Errorf("failed to open lua file: %w", err) } defer file.Close() fData, err := io.ReadAll(file) if err != nil { return fmt.Errorf("failed to read from file: %w", err) } fStr := cleanup(fData) if err = L.DoString(fStr); err != nil { return fmt.Errorf("failed to load %s: %s", path, err) } var gamboDB WoWGoldGamblerDB table := L.GetGlobal("WoWGoldGamblerDB").(*lua.LTable) err = gluamapper.Map(table, &gamboDB) if err != nil { return fmt.Errorf("failed to parse table: %s", err) } var playerMap = make(map[string]int) for name, val := range gamboDB.Global.Stats.Player { playerMap[name] = val } for mainChar, alts := range gamboDB.Global.Stats.Aliases { for _, alt := range alts { playerMap[mainChar] += playerMap[alt] playerMap[alt] = 0 } } tx, err := db.Begin() if err != nil { return fmt.Errorf("failed to begin transaction: %s", err) } stmt, err := tx.Prepare("INSERT OR REPLACE INTO players(name, val) VALUES(?, ?)") if err != nil { tx.Rollback() return fmt.Errorf("failed to prepare statement: %s", err) } defer stmt.Close() for k, v := range playerMap { if v == 0 { continue } _, err = stmt.Exec(k, v) if err != nil { tx.Rollback() return fmt.Errorf("failed to execute statement: %s", err) } } if err = tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %s", err) } return nil } func cleanup(data []byte) string { s := string(data) strings.ReplaceAll(s, "Games Played", "GamesPlayed") strings.Replace(s, "Gold Traded", "GoldTraded", 1) strings.Replace(s, "Biggest Win", "BiggestWin", 1) strings.Replace(s, "Biggest Wager", "BiggestWager", 1) strings.Replace(s, "Luckiest Roll", "LuckiestRoll", 1) strings.Replace(s, "Unluckiest Roll", "UnluckiestRoll", 1) return s }