commit 544f40fa6362f585ba7d28f32b653cd323decb66
Author: Evan Burkey <evan@burkey.co>
Date:   Mon Mar 10 08:21:59 2025 -0700

    init

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c7042f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+WoWChatLog.txt
+.idea
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..0605659
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module gamboupload
+
+go 1.24
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..35f3417
--- /dev/null
+++ b/main.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"log"
+	"os"
+	"strings"
+)
+
+func main() {
+	b, err := os.ReadFile("WoWChatLog.txt")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Strip carriage returns because Windows is retarded
+	b = bytes.ReplaceAll(b, []byte("\r"), []byte(""))
+
+	lines := strings.Split(string(b), "\n")
+
+	games, err := parseGames(lines)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	for _, game := range games {
+		j, err := json.Marshal(game)
+		if err != nil {
+			log.Fatal(err)
+		}
+		fmt.Println(string(j))
+	}
+}
diff --git a/parser.go b/parser.go
new file mode 100644
index 0000000..ceae200
--- /dev/null
+++ b/parser.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+	"fmt"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+)
+
+const (
+	gameTypeUnknown = iota
+	gameTypeClassic
+)
+
+type Game struct {
+	Timestamp time.Time `json:"timestamp"`
+	GameType  int       `json:"game_type"`
+	Wager     int       `json:"wager"`
+	Winner    string    `json:"winner"`
+	Loser     string    `json:"loser"`
+	HighRoll  int       `json:"high_roll"`
+	LowRoll   int       `json:"low_roll"`
+	Payout    int       `json:"payout"`
+}
+
+var (
+	reTimeStamp = regexp.MustCompile(`(\d/\d \d+:\d+:\d+.\d+)`)
+	reGameStart = regexp.MustCompile(`WoWGoldGambler: A new game has been started`)
+	reWager     = regexp.MustCompile(`Game Mode - ([A-Z]+) - Wager - ([\d,]+)g$`)
+	reSignup    = regexp.MustCompile(`([\p{L}']+)-[\p{L}'0-9]+: (1|-1)`)
+	reRoll      = regexp.MustCompile(`([\p{L}']+) rolls (\d+) \(1-(\d+)\)$`)
+	reEnd       = regexp.MustCompile(`([\p{L}']+) owes ([\p{L}']+) ([\d,]+) gold!`)
+)
+
+func parseGames(lines []string) ([]Game, error) {
+	games := make([]Game, 0)
+	var err error
+
+	i := 0
+	for i < len(lines) {
+		if reGameStart.MatchString(lines[i]) {
+			var game Game
+			game, i, err = parse(lines, i)
+			if err != nil {
+				return nil, err
+			}
+			games = append(games, game)
+		}
+		i++
+	}
+
+	return games, nil
+}
+
+func parse(lines []string, i int) (Game, int, error) {
+	var (
+		g   Game
+		err error
+	)
+
+	// Timestamp
+	ts := reTimeStamp.FindString(lines[i])
+	if ts == "" {
+		return g, i, fmt.Errorf("failed to extract timestamp from %s", lines[i])
+	}
+	g.Timestamp, err = time.Parse("1/2 15:04:05.000", ts)
+	if err != nil {
+		return g, i, err
+	}
+
+	// Wager
+	i++
+	matches := reWager.FindStringSubmatch(lines[i])
+	switch matches[1] {
+	case "CLASSIC":
+		g.GameType = gameTypeClassic
+	default:
+		g.GameType = gameTypeUnknown
+	}
+	g.Wager, err = strconv.Atoi(strings.ReplaceAll(matches[2], ",", ""))
+	if err != nil {
+		return g, i, err
+	}
+
+	// Registration
+	for {
+		if strings.Contains(lines[i], "Registration has ended") {
+			break
+		}
+		i++
+	}
+
+	// Capture Rolls
+	rolls := make(map[string]int)
+	for {
+		if reEnd.MatchString(lines[i]) {
+			break
+		}
+
+		rollMatches := reRoll.FindStringSubmatch(lines[i])
+		if rollMatches == nil {
+			i++
+			continue
+		}
+
+		v, err := strconv.Atoi(rollMatches[3])
+		if err != nil {
+			return g, i, err
+		}
+		if v != g.Wager {
+			i++
+			continue
+		}
+
+		// Ignore extra rolls
+		if _, ok := rolls[rollMatches[1]]; !ok {
+			val, err := strconv.Atoi(rollMatches[2])
+			if err != nil {
+				return g, i, err
+			}
+			rolls[rollMatches[1]] = val
+		}
+
+		i++
+	}
+
+	endMatches := reEnd.FindStringSubmatch(lines[i])
+	p := strings.ReplaceAll(endMatches[3], ",", "")
+	payout, err := strconv.Atoi(p)
+	if err != nil {
+		return g, i, err
+	}
+	g.Payout = payout
+
+	g.Winner = endMatches[2]
+	g.HighRoll = rolls[g.Winner]
+
+	g.Loser = endMatches[1]
+	g.LowRoll = rolls[g.Loser]
+
+	return g, i, nil
+}