From 321c437499cbdea153705528c73d6d6ac01686ed Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Wed, 29 May 2019 19:23:46 -0400 Subject: [PATCH] fix #536 --- irc/channel.go | 10 +++++++--- irc/client.go | 14 ++++++++++++++ irc/znc.go | 22 ++++++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/irc/channel.go b/irc/channel.go index e4b71055..498d6d45 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -624,12 +624,16 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp // TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex rb.Flush(true) + channel.autoReplayHistory(client, rb, message.Msgid) +} + +func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, skipMsgid string) { // autoreplay any messages as necessary config := channel.server.Config() var items []history.Item - if rb.session.zncPlaybackTimes != nil && (rb.session.zncPlaybackTimes.targets == nil || rb.session.zncPlaybackTimes.targets[chcfname]) { + if rb.session.zncPlaybackTimes != nil && (rb.session.zncPlaybackTimes.targets == nil || rb.session.zncPlaybackTimes.targets[channel.NameCasefolded()]) { items, _ = channel.history.Between(rb.session.zncPlaybackTimes.after, rb.session.zncPlaybackTimes.before, false, config.History.ChathistoryMax) - } else { + } else if !rb.session.HasHistoryCaps() { var replayLimit int customReplayLimit := client.AccountSettings().AutoreplayLines if customReplayLimit != nil { @@ -648,7 +652,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp // remove the client's own JOIN line from the replay numItems := len(items) for i := len(items) - 1; 0 <= i; i-- { - if items[i].Message.Msgid == message.Msgid { + if items[i].Message.Msgid == skipMsgid { // zero'ed items will not be replayed because their `Type` field is not recognized items[i] = history.Item{} numItems-- diff --git a/irc/client.go b/irc/client.go index add5b3d4..f1ccc8db 100644 --- a/irc/client.go +++ b/irc/client.go @@ -162,6 +162,13 @@ func (session *Session) SetDestroyed() { atomic.StoreUint32(&session.destroyed, 1) } +// returns whether the client supports a smart history replay cap, +// and therefore autoreplay-on-join and similar should be suppressed +func (session *Session) HasHistoryCaps() bool { + // TODO the chathistory cap will go here as well + return session.capabilities.Has(caps.ZNCPlayback) +} + // WhoWas is the subset of client details needed to answer a WHOWAS query type WhoWas struct { nick string @@ -473,6 +480,13 @@ func (client *Client) playReattachMessages(session *Session) { client.server.playRegistrationBurst(session) for _, channel := range session.client.Channels() { channel.playJoinForSession(session) + // clients should receive autoreplay-on-join lines, if applicable; + // if they negotiated znc.in/playback or chathistory, they will receive nothing, + // because those caps disable autoreplay-on-join and they haven't sent the relevant + // *playback PRIVMSG or CHATHISTORY command yet + rb := NewResponseBuffer(session) + channel.autoReplayHistory(client, rb, "") + rb.Send(true) } } diff --git a/irc/znc.go b/irc/znc.go index ab5b0b3d..ca765cc3 100644 --- a/irc/znc.go +++ b/irc/znc.go @@ -73,8 +73,21 @@ func zncPlaybackHandler(client *Client, command string, params []string, rb *Res var targets map[string]bool - // OK: the user's PMs get played back immediately on receiving this, - // then we save the timestamps in the session to handle replay on future channel joins + // three cases: + // 1. the user's PMs get played back immediately upon receiving this + // 2. if this is a new connection (from the server's POV), save the information + // and use it to process subsequent joins + // 3. if this is a reattach (from the server's POV), immediately play back + // history for channels that the client is already joined to. In this scenario, + // there are three total attempts to play the history: + // 3.1. During the initial reattach (no-op because the *playback privmsg + // hasn't been received yet, but they negotiated the znc.in/playback + // cap so we know we're going to receive it later) + // 3.2 Upon receiving the *playback privmsg, i.e., now: we should play + // the relevant history lines + // 3.3 When the client sends a subsequent redundant JOIN line for those + // channels; redundant JOIN is a complete no-op so we won't replay twice + config := client.server.Config() if params[1] == "*" { items, _ := client.history.Between(after, before, false, config.History.ChathistoryMax) @@ -95,4 +108,9 @@ func zncPlaybackHandler(client *Client, command string, params []string, rb *Res before: before, targets: targets, } + + for _, channel := range client.Channels() { + channel.autoReplayHistory(client, rb, "") + rb.Flush(true) + } }