From dcfe206bdac23c5e7ce02d44e07dc993a92cd20e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 15 Jul 2020 17:47:57 +0200 Subject: [PATCH] Implement CHATHISTORY AFTER References: https://todo.sr.ht/~emersion/soju/12 --- downstream.go | 51 ++++++++++++++++++++++----- logger.go | 98 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 111 insertions(+), 38 deletions(-) diff --git a/downstream.go b/downstream.go index 60c8572..af6b55e 100644 --- a/downstream.go +++ b/downstream.go @@ -1598,9 +1598,10 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { }} } + batchRef := "history" + maxTries := 100 switch subcommand { case "BEFORE": - batchRef := "history" dc.SendMessage(&irc.Message{ Prefix: dc.srv.prefix(), Command: "BATCH", @@ -1611,7 +1612,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { remaining := limit tries := 0 - for remaining > 0 { + for remaining > 0 && tries < maxTries { buf, err := parseMessagesBefore(uc.network, entity, timestamp, remaining) if err != nil { dc.logger.Printf("failed parsing log messages for chathistory: %v", err) @@ -1619,9 +1620,6 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { } if len(buf) == 0 { tries++ - if tries >= 100 { - break - } } else { tries = 0 } @@ -1631,9 +1629,44 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { timestamp = time.Date(year, month, day, 0, 0, 0, 0, timestamp.Location()).Add(-1) } - for _, m := range history[remaining:] { - m.Tags["batch"] = irc.TagValue(batchRef) - dc.SendMessage(dc.marshalMessage(m, uc.network)) + for _, msg := range history[remaining:] { + msg.Tags["batch"] = irc.TagValue(batchRef) + dc.SendMessage(dc.marshalMessage(msg, uc.network)) + } + + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: "BATCH", + Params: []string{"-" + batchRef}, + }) + case "AFTER": + dc.SendMessage(&irc.Message{ + Prefix: dc.srv.prefix(), + Command: "BATCH", + Params: []string{"+" + batchRef, "chathistory", target}, + }) + + remaining := limit + tries := 0 + now := time.Now() + for remaining > 0 && tries < maxTries && timestamp.Before(now) { + buf, err := parseMessagesAfter(uc.network, entity, timestamp, remaining) + if err != nil { + dc.logger.Printf("failed parsing log messages for chathistory: %v", err) + return newChatHistoryError(subcommand, target) + } + if len(buf) == 0 { + tries++ + } else { + tries = 0 + } + for _, msg := range buf { + msg.Tags["batch"] = irc.TagValue(batchRef) + dc.SendMessage(dc.marshalMessage(msg, uc.network)) + } + remaining -= len(buf) + year, month, day := timestamp.Date() + timestamp = time.Date(year, month, day + 1, 0, 0, 0, 0, timestamp.Location()) } dc.SendMessage(&irc.Message{ @@ -1642,7 +1675,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { Params: []string{"-" + batchRef}, }) default: - // TODO: support AFTER, LATEST, BETWEEN + // TODO: support LATEST, BETWEEN return ircError{&irc.Message{ Command: "FAIL", Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"}, diff --git a/logger.go b/logger.go index 11527ae..517b09f 100644 --- a/logger.go +++ b/logger.go @@ -134,9 +134,41 @@ func formatMessage(msg *irc.Message) string { } } -func parseMessagesBefore(network *network, entity string, timestamp time.Time, limit int) ([]*irc.Message, error) { - year, month, day := timestamp.Date() - path := logPath(network, entity, timestamp) +func parseMessage(line, entity string, ref time.Time) (*irc.Message, time.Time, error) { + var hour, minute, second int + _, err := fmt.Sscanf(line, "[%02d:%02d:%02d] ", &hour, &minute, &second) + if err != nil { + return nil, time.Time{}, err + } + line = line[11:] + + // TODO: support NOTICE + if !strings.HasPrefix(line, "<") { + return nil, time.Time{}, nil + } + i := strings.Index(line, "> ") + if i < 0 { + return nil, time.Time{}, nil + } + + year, month, day := ref.Date() + t := time.Date(year, month, day, hour, minute, second, 0, time.Local) + + sender := line[1:i] + text := line[i+2:] + msg := &irc.Message{ + Tags: map[string]irc.TagValue{ + "time": irc.TagValue(t.UTC().Format(serverTimeLayout)), + }, + Prefix: &irc.Prefix{Name: sender}, + Command: "PRIVMSG", + Params: []string{entity, text}, + } + return msg, t, nil +} + +func parseMessagesBefore(network *network, entity string, ref time.Time, limit int) ([]*irc.Message, error) { + path := logPath(network, entity, ref) f, err := os.Open(path) if err != nil { if os.IsNotExist(err) { @@ -151,38 +183,16 @@ func parseMessagesBefore(network *network, entity string, timestamp time.Time, l sc := bufio.NewScanner(f) for sc.Scan() { - line := sc.Text() - var hour, minute, second int - _, err := fmt.Sscanf(line, "[%02d:%02d:%02d] ", &hour, &minute, &second) + msg, t, err := parseMessage(sc.Text(), entity, ref) if err != nil { return nil, err - } - message := line[11:] - // TODO: support NOTICE - if !strings.HasPrefix(message, "<") { + } else if msg == nil { continue - } - i := strings.Index(message, "> ") - if i == -1 { - continue - } - t := time.Date(year, month, day, hour, minute, second, 0, time.Local) - if !t.Before(timestamp) { + } else if !t.Before(ref) { break } - sender := message[1:i] - text := message[i+2:] - historyRing[cur%limit] = &irc.Message{ - Tags: map[string]irc.TagValue{ - "time": irc.TagValue(t.UTC().Format(serverTimeLayout)), - }, - Prefix: &irc.Prefix{ - Name: sender, - }, - Command: "PRIVMSG", - Params: []string{entity, text}, - } + historyRing[cur%limit] = msg cur++ } if sc.Err() != nil { @@ -204,3 +214,33 @@ func parseMessagesBefore(network *network, entity string, timestamp time.Time, l return history, nil } } + +func parseMessagesAfter(network *network, entity string, ref time.Time, limit int) ([]*irc.Message, error) { + path := logPath(network, entity, ref) + f, err := os.Open(path) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + defer f.Close() + + var history []*irc.Message + sc := bufio.NewScanner(f) + for sc.Scan() && len(history) < limit { + msg, t, err := parseMessage(sc.Text(), entity, ref) + if err != nil { + return nil, err + } else if msg == nil || !t.After(ref) { + continue + } + + history = append(history, msg) + } + if sc.Err() != nil { + return nil, sc.Err() + } + + return history, nil +}