Implement CHATHISTORY AFTER

References: https://todo.sr.ht/~emersion/soju/12
This commit is contained in:
Simon Ser 2020-07-15 17:47:57 +02:00
parent 25d4312e0f
commit dcfe206bda
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
2 changed files with 111 additions and 38 deletions

View File

@ -1598,9 +1598,10 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
}} }}
} }
batchRef := "history"
maxTries := 100
switch subcommand { switch subcommand {
case "BEFORE": case "BEFORE":
batchRef := "history"
dc.SendMessage(&irc.Message{ dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(), Prefix: dc.srv.prefix(),
Command: "BATCH", Command: "BATCH",
@ -1611,7 +1612,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
remaining := limit remaining := limit
tries := 0 tries := 0
for remaining > 0 { for remaining > 0 && tries < maxTries {
buf, err := parseMessagesBefore(uc.network, entity, timestamp, remaining) buf, err := parseMessagesBefore(uc.network, entity, timestamp, remaining)
if err != nil { if err != nil {
dc.logger.Printf("failed parsing log messages for chathistory: %v", err) 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 { if len(buf) == 0 {
tries++ tries++
if tries >= 100 {
break
}
} else { } else {
tries = 0 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) timestamp = time.Date(year, month, day, 0, 0, 0, 0, timestamp.Location()).Add(-1)
} }
for _, m := range history[remaining:] { for _, msg := range history[remaining:] {
m.Tags["batch"] = irc.TagValue(batchRef) msg.Tags["batch"] = irc.TagValue(batchRef)
dc.SendMessage(dc.marshalMessage(m, uc.network)) 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{ dc.SendMessage(&irc.Message{
@ -1642,7 +1675,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
Params: []string{"-" + batchRef}, Params: []string{"-" + batchRef},
}) })
default: default:
// TODO: support AFTER, LATEST, BETWEEN // TODO: support LATEST, BETWEEN
return ircError{&irc.Message{ return ircError{&irc.Message{
Command: "FAIL", Command: "FAIL",
Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"}, Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},

View File

@ -134,9 +134,41 @@ func formatMessage(msg *irc.Message) string {
} }
} }
func parseMessagesBefore(network *network, entity string, timestamp time.Time, limit int) ([]*irc.Message, error) { func parseMessage(line, entity string, ref time.Time) (*irc.Message, time.Time, error) {
year, month, day := timestamp.Date() var hour, minute, second int
path := logPath(network, entity, timestamp) _, 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) f, err := os.Open(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -151,38 +183,16 @@ func parseMessagesBefore(network *network, entity string, timestamp time.Time, l
sc := bufio.NewScanner(f) sc := bufio.NewScanner(f)
for sc.Scan() { for sc.Scan() {
line := sc.Text() msg, t, err := parseMessage(sc.Text(), entity, ref)
var hour, minute, second int
_, err := fmt.Sscanf(line, "[%02d:%02d:%02d] ", &hour, &minute, &second)
if err != nil { if err != nil {
return nil, err return nil, err
} } else if msg == nil {
message := line[11:]
// TODO: support NOTICE
if !strings.HasPrefix(message, "<") {
continue continue
} } else if !t.Before(ref) {
i := strings.Index(message, "> ")
if i == -1 {
continue
}
t := time.Date(year, month, day, hour, minute, second, 0, time.Local)
if !t.Before(timestamp) {
break break
} }
sender := message[1:i] historyRing[cur%limit] = msg
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},
}
cur++ cur++
} }
if sc.Err() != nil { if sc.Err() != nil {
@ -204,3 +214,33 @@ func parseMessagesBefore(network *network, entity string, timestamp time.Time, l
return history, nil 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
}