package parsers import ( "errors" "git.aiterp.net/rpdata/api/models" "strconv" "strings" "time" ) // ErrNotPost is returned by parsePost if the line is empty or not a post. var ErrNotPost = errors.New("not a post") // ErrEmptyLog is returned by ParseLog if there are no (valid) posts in the log. var ErrEmptyLog = errors.New("no valid posts found in log") // MircLog parses the log and returns the things that can be gleamed from them. func MircLog(data string, date time.Time, strict bool) (*ParsedLog, error) { lines := strings.Split(data, "\n") posts := make([]*models.Post, 0, len(lines)) prev := models.Post{} for _, line := range lines { line = strings.Trim(line, "\r\t  ") if len(line) < 1 { continue } post, err := MircPost(line, date, prev) if err != nil { if strict { return nil, err } continue } posts = append(posts, &post) prev = post } if len(posts) == 0 { return nil, ErrEmptyLog } log := models.Log{ Date: posts[0].Time, } return &ParsedLog{log, posts}, nil } // MircPost 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 MircPost(line string, date time.Time, prev models.Post) (models.Post, error) { // Do basic validation line = strings.Trim(line, "  \t\n\r") if len(line) == 0 || !strings.HasPrefix(line, "[") { return models.Post{}, ErrNotPost } // Parse timestamp tsEndIndex := strings.IndexByte(line, ']') if tsEndIndex == -1 || len(line) < tsEndIndex+5 { return models.Post{}, ErrNotPost } tsStr := line[1:tsEndIndex] tsSplit := strings.Split(tsStr, ":") tsUnits := make([]int, len(tsSplit)) if len(tsSplit) < 2 { return models.Post{}, ErrNotPost } for i := range tsSplit { n, err := strconv.Atoi(tsSplit[i]) if err != nil { return models.Post{}, ErrNotPost } tsUnits[i] = n } if len(tsUnits) == 2 { tsUnits = append(tsUnits, 0) } // Determine timestamp from parsed data and previous post. ts := time.Date(date.Year(), date.Month(), date.Day(), tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location()) if !prev.Time.IsZero() && prev.Time.Sub(ts) > 30*time.Minute { ts = time.Date(prev.Time.Year(), prev.Time.Month(), prev.Time.Day()+1, tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location()) } if line[tsEndIndex+2] == '*' { split := strings.SplitN(line[tsEndIndex+4:], " ", 2) if len(split) == 1 { return models.Post{}, ErrNotPost } post := models.Post{ ID: "UNASSIGNED", LogID: "UNASSIGNED", Time: ts, Kind: "action", Nick: strings.TrimLeft(split[0], "+@!~"), Text: split[1], Position: prev.Position + 1, } if post.Nick[0] == '=' { 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{}, ErrNotPost } post := models.Post{ ID: "UNASSIGNED", LogID: "UNASSIGNED", Time: ts, Kind: "text", Nick: strings.TrimLeft(split[0][1:len(split[0])-1], "+@!~"), Text: split[1], Position: prev.Position + 1, } if post.Nick[0] == '=' { post.Kind = "scene" } return post, nil } else { return models.Post{}, ErrNotPost } }