140 lines
3.1 KiB
Go
140 lines
3.1 KiB
Go
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.
|
||
if !prev.Time.IsZero() {
|
||
date = prev.Time
|
||
}
|
||
ts := time.Date(date.Year(), date.Month(), date.Day(), tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location())
|
||
if ts.Before(prev.Time) {
|
||
ts = ts.Add(time.Hour * 24)
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|