package main import ( "bytes" "encoding/json" "flag" "net/http" "os" "path/filepath" "strings" "sync" _ "embed" logger "git.site.quack-lab.dev/dave/cylogger" "github.com/bmatcuk/doublestar/v4" lua "github.com/yuin/gopher-lua" ) const nsqEndpoint = "https://nsq.site.quack-lab.dev/pub?topic=wowspy" var debug *bool var messageQueue = make(chan string, 10000) var nsqWorkers = 32 func main() { root := flag.String("root", ".", "Root workdir") debug = flag.Bool("d", false, "Debug") flag.Parse() logger.InitFlag() logger.Info("Root: %q", *root) cleanedRoot := strings.Replace(*root, "~", os.Getenv("HOME"), 1) cleanedRoot, err := filepath.Abs(cleanedRoot) if err != nil { logger.Error("error getting absolute path: %v", err) return } cleanedRoot = filepath.Clean(cleanedRoot) cleanedRoot = strings.TrimSuffix(cleanedRoot, "/") logger.Info("Looking for Heimdall.lua in %q", cleanedRoot) matches, err := doublestar.Glob(os.DirFS(cleanedRoot), "**/Heimdall.lua") if err != nil { logger.Error("error matching Heimdall.lua: %v", err) return } logger.Info("Found %d Heimdall.lua", len(matches)) wg := sync.WaitGroup{} messages := make(chan NSQMessage, 1000) for i := 0; i < nsqWorkers; i++ { wg.Add(1) go NsqWorker(&wg, messages) } for _, match := range matches { logger.Info("Processing %q", match) wg.Add(1) go ParseHeimdallFile(filepath.Join(cleanedRoot, match), &wg, messages) } for msg := range messages { logger.Info("Message: %#v", msg) } wg.Wait() close(messages) return } func ParseHeimdallFile(path string, wg *sync.WaitGroup, messages chan NSQMessage) { defer wg.Done() L := lua.NewState() defer L.Close() if err := L.DoFile(path); err != nil { logger.Error("error executing Lua file %q: %v", path, err) return } heimdallAchievements := L.GetGlobal("Heimdall_Achievements") if heimdallAchievements.Type() == lua.LTNil { logger.Error("Heimdall_Achievements not found in %q", path) return } playersTableLua := L.GetField(heimdallAchievements, "players") playersTable, ok := playersTableLua.(*lua.LTable) if !ok || playersTableLua.Type() == lua.LTNil { logger.Error("'players' table not found or not a table in Heimdall_Achievements in %q", path) return } playersTable.ForEach(func(playerNameLua lua.LValue, playerAchievementsLua lua.LValue) { currentPlayerName := playerNameLua.String() logger.Info("Processing player: %s in %q", currentPlayerName, path) achievementsTable, ok := playerAchievementsLua.(*lua.LTable) if !ok { logger.Error("Achievements for player %s is not a table in %q", currentPlayerName, path) return } achievementsTable.ForEach(func(_ lua.LValue, achievementDataLua lua.LValue) { achievementTable, ok := achievementDataLua.(*lua.LTable) if !ok { logger.Error("Achievement data for player %s is not a table in %q", currentPlayerName, path) return } currentAchievement := NSQMessage{Name: currentPlayerName} idVal := achievementTable.RawGetString("id") if idVal.Type() == lua.LTNumber { currentAchievement.ID = lua.LVAsString(idVal) } else if idVal.Type() == lua.LTString { currentAchievement.ID = idVal.String() } else { logger.Warning("Missing or invalid 'id' (expected number or string) for achievement for player %s in %q", currentPlayerName, path) } dateVal := achievementTable.RawGetString("date") if dateVal.Type() == lua.LTString { currentAchievement.Date = dateVal.String() } else { logger.Warning("Missing or invalid 'date' (expected string) for achievement for player %s in %q", currentPlayerName, path) } completedVal := achievementTable.RawGetString("completed") if completedVal.Type() == lua.LTBool { currentAchievement.Completed = lua.LVAsBool(completedVal) } else { logger.Warning("Missing or invalid 'completed' (expected boolean) for achievement for player %s in %q", currentPlayerName, path) } if currentAchievement.ID != "" { // Ensure we have at least an ID before sending logger.Info("Publishing achievement for %s: ID %s, Date: %s, Completed: %t", currentPlayerName, currentAchievement.ID, currentAchievement.Date, currentAchievement.Completed) messages <- currentAchievement } }) }) } func NsqWorker(wg *sync.WaitGroup, messages chan NSQMessage) { defer wg.Done() for msg := range messages { err := Publish(msg) if err != nil { logger.Warning("error publishing message: %v", err) continue } } } func Publish(msg NSQMessage) error { data := bytes.Buffer{} err := json.NewEncoder(&data).Encode(msg) if err != nil { return err } _, err = http.Post(nsqEndpoint, "application/json", &data) if err != nil { return err } return nil }