268 lines
6.5 KiB
Go
268 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
logger "git.site.quack-lab.dev/dave/cylogger"
|
|
"github.com/bmatcuk/doublestar/v4"
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
const meiliEndpoint = "https://meili.site.quack-lab.dev/"
|
|
const meiliIndex = "chatlog"
|
|
|
|
var debug *bool
|
|
|
|
func main() {
|
|
root := flag.String("root", ".", "Root workdir")
|
|
debug = flag.Bool("d", false, "Debug")
|
|
flag.Parse()
|
|
logger.InitFlag()
|
|
if *debug {
|
|
logger.SetLevel(logger.LevelDebug)
|
|
}
|
|
|
|
err := Init()
|
|
if err != nil {
|
|
logger.Error("Failed to initialize Meilisearch: %v", err)
|
|
return
|
|
}
|
|
|
|
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 files.", len(matches))
|
|
if len(matches) == 0 {
|
|
logger.Info("No Heimdall.lua files found. Exiting.")
|
|
return
|
|
}
|
|
for i, match := range matches {
|
|
matches[i] = filepath.Join(cleanedRoot, match)
|
|
}
|
|
|
|
luaStates := loadLuaStates(matches)
|
|
chatMessages := loadChatMessages(luaStates)
|
|
|
|
// Save messages to Meilisearch
|
|
if err := AddMessages(chatMessages); err != nil {
|
|
logger.Error("Failed to save messages: %v", err)
|
|
return
|
|
}
|
|
logger.Info("Successfully saved %d messages", len(chatMessages))
|
|
|
|
// Clear chat tables in source files
|
|
clearChatTables(matches)
|
|
}
|
|
|
|
func loadLuaStates(matches []string) *sync.Map {
|
|
wg := sync.WaitGroup{}
|
|
fileLuaStates := &sync.Map{}
|
|
for _, match := range matches {
|
|
wg.Add(1)
|
|
go func(path string) {
|
|
defer wg.Done()
|
|
log := logger.Default.WithPrefix(path)
|
|
L := lua.NewState()
|
|
|
|
filestat, err := os.Stat(path)
|
|
if err != nil {
|
|
log.Error("error getting file stats: %v", err)
|
|
return
|
|
}
|
|
log.Info("File size: %.2f MB", float64(filestat.Size())/1024/1024)
|
|
|
|
log.Info("Running Lua file")
|
|
if err := L.DoFile(path); err != nil {
|
|
log.Error("error executing Lua file %q: %v", path, err)
|
|
return
|
|
}
|
|
log.Info("Lua file loaded")
|
|
fileLuaStates.Store(path, L)
|
|
}(match)
|
|
}
|
|
wg.Wait()
|
|
return fileLuaStates
|
|
}
|
|
|
|
func loadChatMessages(luaStates *sync.Map) []ChatMessage {
|
|
var messages []ChatMessage
|
|
wg := sync.WaitGroup{}
|
|
messageChan := make(chan []ChatMessage, 100) // Buffer for concurrent processing
|
|
|
|
luaStates.Range(func(path, state any) bool {
|
|
wg.Add(1)
|
|
go func(path string, state *lua.LState) {
|
|
defer wg.Done()
|
|
log := logger.Default.WithPrefix(path)
|
|
fileMessages := loadStateChatMessages(state, log)
|
|
messageChan <- fileMessages
|
|
}(path.(string), state.(*lua.LState))
|
|
return true
|
|
})
|
|
|
|
// Close channel when all goroutines are done
|
|
go func() {
|
|
wg.Wait()
|
|
close(messageChan)
|
|
}()
|
|
|
|
// Collect all messages
|
|
for fileMessages := range messageChan {
|
|
messages = append(messages, fileMessages...)
|
|
}
|
|
|
|
return messages
|
|
}
|
|
|
|
func loadStateChatMessages(L *lua.LState, log *logger.Logger) []ChatMessage {
|
|
log.Info("Getting Heimdall_Chat")
|
|
heimdallChat := L.GetGlobal("Heimdall_Chat")
|
|
if heimdallChat.Type() == lua.LTNil {
|
|
log.Warning("Heimdall_Chat not found. Skipping file.")
|
|
return nil
|
|
}
|
|
|
|
chatTable, ok := heimdallChat.(*lua.LTable)
|
|
if !ok {
|
|
log.Warning("Heimdall_Chat is not a table. Skipping file.")
|
|
return nil
|
|
}
|
|
|
|
var messages []ChatMessage
|
|
chatTable.ForEach(func(_, value lua.LValue) {
|
|
chatStr := value.String()
|
|
// Remove quotes and trailing comma if present
|
|
chatStr = strings.Trim(chatStr, "\", ")
|
|
|
|
// Parse the chat message
|
|
message, err := parseChatMessage(chatStr)
|
|
if err != nil {
|
|
log.Warning("Invalid chat format: %s", chatStr)
|
|
return
|
|
}
|
|
messages = append(messages, message)
|
|
})
|
|
|
|
log.Info("Loaded %d chat messages", len(messages))
|
|
return messages
|
|
}
|
|
|
|
func parseChatMessage(chatStr string) (ChatMessage, error) {
|
|
// Debug: Print the raw string
|
|
logger.Debug("Raw chat string: %q", chatStr)
|
|
|
|
// Split by pipe - we expect 6 parts (5 pipes)
|
|
parts := strings.Split(chatStr, "|")
|
|
logger.Debug("Split into %d parts: %v", len(parts), parts)
|
|
|
|
if len(parts) != 6 {
|
|
return ChatMessage{}, fmt.Errorf("invalid message format: expected 6 parts, got %d: %s", len(parts), chatStr)
|
|
}
|
|
|
|
timestamp := parts[0]
|
|
event := parts[1]
|
|
sender := parts[2]
|
|
msg := parts[3]
|
|
language := parts[4]
|
|
channel := parts[5]
|
|
|
|
// Parse ISO timestamp to epoch
|
|
epochTime, err := parseISOTimestamp(timestamp)
|
|
if err != nil {
|
|
return ChatMessage{}, fmt.Errorf("invalid timestamp format: %v", err)
|
|
}
|
|
|
|
return ChatMessage{
|
|
MessageHash: GenerateMessageHash(timestamp, event, sender, msg, language, channel),
|
|
Timestamp: timestamp,
|
|
EpochTime: epochTime,
|
|
Event: event,
|
|
Sender: sender,
|
|
Msg: msg,
|
|
Language: language,
|
|
Channel: channel,
|
|
}, nil
|
|
}
|
|
|
|
func parseISOTimestamp(isoTime string) (int64, error) {
|
|
// Debug: Print the timestamp we're trying to parse
|
|
logger.Debug("Parsing timestamp: %q", isoTime)
|
|
|
|
// Parse the timestamp format used in chat messages (e.g., "2025-05-25 02:51:05")
|
|
t, err := time.Parse("2006-01-02 15:04:05", isoTime)
|
|
if err != nil {
|
|
logger.Debug("Failed to parse timestamp: %v", err)
|
|
return 0, err
|
|
}
|
|
return t.Unix(), nil
|
|
}
|
|
|
|
func clearChatTables(matches []string) {
|
|
for _, path := range matches {
|
|
log := logger.Default.WithPrefix(path)
|
|
log.Info("Reading source file")
|
|
fileContent, err := os.ReadFile(path)
|
|
if err != nil {
|
|
logger.Error("Failed to read file: %v", err)
|
|
continue
|
|
}
|
|
strContent := string(fileContent)
|
|
log.Info("Read %d bytes", len(strContent))
|
|
|
|
strContent = clearChatTable(strContent)
|
|
log.Info("Cleared chat table, now %d bytes", len(strContent))
|
|
|
|
log.Info("Writing file")
|
|
err = os.WriteFile(path, []byte(strContent), 0644)
|
|
if err != nil {
|
|
logger.Error("Failed to write file: %v", err)
|
|
continue
|
|
}
|
|
log.Info("Done")
|
|
}
|
|
}
|
|
|
|
func clearChatTable(sourceContent string) string {
|
|
lines := strings.Split(sourceContent, "\n")
|
|
writeIndex := 0
|
|
isInChat := false
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "Heimdall_Chat = {") {
|
|
isInChat = true
|
|
lines[writeIndex] = "Heimdall_Chat = {"
|
|
writeIndex++
|
|
continue
|
|
}
|
|
if isInChat && strings.HasPrefix(line, "}") {
|
|
isInChat = false
|
|
lines[writeIndex] = "}"
|
|
writeIndex++
|
|
continue
|
|
}
|
|
if !isInChat {
|
|
lines[writeIndex] = line
|
|
writeIndex++
|
|
}
|
|
}
|
|
return strings.Join(lines[:writeIndex], "\n")
|
|
}
|