|
|
package parsers
import ( "errors" "fmt" "git.aiterp.net/rpdata/api/models" "strings" "time" )
var ErrSkip = errors.New("parsers: skip this post")
func IRCCloudLogs(data string, location *time.Location, threshold time.Duration) ([]ParsedLog, error) { lines := strings.Split(data, "\n") pos := 0 results := make([]ParsedLog, 0, 8) for pos < len(lines) { log, n, err := IRCCloudLog(lines[pos:], location, threshold) if err != nil { if err == ErrEmptyLog { pos += n continue }
return nil, err }
pos += n results = append(results, *log) }
return results, nil }
// IRCCloudLog parses the log and returns the things that can be gleamed from them.
func IRCCloudLog(lines []string, location *time.Location, threshold time.Duration) (*ParsedLog, int, error) { posts := make([]*models.Post, 0, len(lines)) prev := (*models.Post)(nil) amount := 0
for _, line := range lines { line = strings.Trim(line, "\r\t ") if len(line) < 1 { amount += 1 continue }
post, err := IRCCloudPost(line, location) if err == ErrSkip { amount += 1 continue } else if err != nil { return nil, -1, err }
if prev != nil { if post.Time.Sub(prev.Time) >= threshold { break }
post.Position = prev.Position + 1 } else { post.Position = 1 }
posts = append(posts, &post) prev = &post amount += 1 }
if len(posts) == 0 { return nil, amount, ErrEmptyLog }
log := models.Log{ Date: posts[0].Time, }
return &ParsedLog{log, posts}, amount, nil }
// IRCCloudPost parses a post from a mirc-like line. If the previous post is included (it can be empty), it will be used
// to determine whether midnight has passed.
func IRCCloudPost(line string, tz *time.Location) (models.Post, error) { // Do basic validation
line = strings.Trim(line, " \t\n\r") if len(line) == 0 || !strings.HasPrefix(line, "[") { return models.Post{}, &ParseError{ Line: line, Problem: "no timestamp", } }
// Parse timestamp
tsEndIndex := strings.IndexByte(line, ']') if tsEndIndex == -1 || len(line) < tsEndIndex+5 { return models.Post{}, &ParseError{ Line: line, Problem: "incomplete timestamp", } } tsStr := line[1:tsEndIndex]
ts, err := time.ParseInLocation("2006-01-02 15:04:05", tsStr, tz) if err != nil { return models.Post{}, &ParseError{ Line: line, Problem: fmt.Sprintf("Could not parse date: %s", err.Error()), } }
if strings.HasPrefix(line[tsEndIndex+2:], "—") { split := strings.SplitN(line[tsEndIndex+6:], " ", 2) if len(split) == 1 { return models.Post{}, &ParseError{ Line: line, Problem: "post is empty", } }
post := models.Post{ ID: "UNASSIGNED", LogID: "UNASSIGNED", Time: ts, Kind: "action", Nick: strings.Trim(strings.TrimLeft(split[0], "+@!~"), "\u001F"), Text: split[1], }
if strings.HasPrefix(post.Nick, "=") { post.Kind = "scene" }
return post, nil } else if line[tsEndIndex+2] == '<' { split := strings.SplitN(line[tsEndIndex+2:], " ", 2) if len(split) == 1 { return models.Post{}, &ParseError{ Line: line, Problem: "post is empty", } }
post := models.Post{ ID: "UNASSIGNED", LogID: "UNASSIGNED", Time: ts, Kind: "text", Nick: strings.Trim(strings.TrimLeft(split[0][1:len(split[0])-1], "+@!~"), "\u001F"), Text: split[1], }
if strings.HasPrefix(post.Nick, "=") { post.Kind = "scene" }
return post, nil } else { return models.Post{}, ErrSkip } }
|