From 711af30aa89fa637341d3748d31d8402394a2fea Mon Sep 17 00:00:00 2001 From: CEF Server Date: Mon, 18 Nov 2024 19:38:49 +0000 Subject: [PATCH] channel history modifications --- irc/channel.go | 6 ++--- irc/client.go | 19 ++++++++++--- irc/handlers.go | 63 ++++++++++++++++++++++++++------------------ irc/mysql/history.go | 55 +++++++++++++++++++++++++++++++++----- irc/znc.go | 4 +-- 5 files changed, 106 insertions(+), 41 deletions(-) diff --git a/irc/channel.go b/irc/channel.go index 45deb1a7..64ed95ec 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -971,7 +971,7 @@ func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, sk } } if 0 < numItems { - channel.replayHistoryItems(rb, items, false) + channel.replayHistoryItems(rb, items, false, "", "", numItems) rb.Flush(true) } } @@ -1062,7 +1062,7 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer) client.server.logger.Debug("channels", fmt.Sprintf("%s left channel %s", details.nick, chname)) } -func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.Item, chathistoryCommand bool) { +func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.Item, chathistoryCommand bool, identifier string, preposition string, limit int) { // send an empty batch if necessary, as per the CHATHISTORY spec chname := channel.Name() client := rb.target @@ -1082,7 +1082,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I } } - batchID := rb.StartNestedBatch("chathistory", chname) + batchID := rb.StartNestedBatch("chathistory", chname, identifier, preposition, strconv.Itoa(limit)) defer rb.EndNestedBatch(batchID) for _, item := range items { diff --git a/irc/client.go b/irc/client.go index b51d524a..ad66d9b2 100644 --- a/irc/client.go +++ b/irc/client.go @@ -35,8 +35,8 @@ import ( ) const ( - // maximum IRC line length, not including tags - DefaultMaxLineLen = 512 + // Set to 4096 because CEF doesn't care about compatibility + DefaultMaxLineLen = 4096 // IdentTimeout is how long before our ident (username) check times out. IdentTimeout = time.Second + 500*time.Millisecond @@ -649,6 +649,17 @@ func (client *Client) run(session *Session) { firstLine := !isReattach + correspondents, _ := client.server.historyDB.GetPMs(client.NickCasefolded()) + // For safety, let's keep this within the 4096 character barrier + var lineBuilder utils.TokenLineBuilder + lineBuilder.Initialize(MaxLineLen, ",") + for username, timestamp := range correspondents { + lineBuilder.Add(fmt.Sprintf("%s %d", client.server.getCurrentNick(username), timestamp)) + } + for _, message := range lineBuilder.Lines() { + session.Send(nil, client.server.name, "PMS", message) + } + for { var invalidUtf8 bool line, err := session.socket.Read() @@ -862,14 +873,14 @@ func (session *Session) Ping() { session.Send(nil, "", "PING", session.client.Nick()) } -func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, target string, chathistoryCommand bool) { +func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, target string, chathistoryCommand bool, identifier string, preposition string, limit int) { var batchID string details := client.Details() nick := details.nick if target == "" { target = nick } - batchID = rb.StartNestedBatch("chathistory", target) + batchID = rb.StartNestedBatch("chathistory", target, identifier, preposition, strconv.Itoa(limit)) isSelfMessage := func(item *history.Item) bool { // XXX: Params[0] is the message target. if the source of this message is an in-memory diff --git a/irc/handlers.go b/irc/handlers.go index 5f9066a2..6318a783 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -712,6 +712,14 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * var err error var listTargets bool var targets []history.TargetListing + var _, batchIdentifier = msg.GetTag("identifier") + var assuredPreposition = "error" + var limit int + + if len(batchIdentifier) == 0 { + batchIdentifier = "UNIDENTIFIED" + } + defer func() { // errors are sent either without a batch, or in a draft/labeled-response batch as usual if err == utils.ErrInvalidParams { @@ -731,9 +739,9 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * target.Time.Format(IRCv3TimestampFormat)) } } else if channel != nil { - channel.replayHistoryItems(rb, items, true) + channel.replayHistoryItems(rb, items, true, batchIdentifier, assuredPreposition, limit) } else { - client.replayPrivmsgHistory(rb, items, target, true) + client.replayPrivmsgHistory(rb, items, target, true, batchIdentifier, assuredPreposition, limit) } } }() @@ -786,7 +794,6 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * paramPos := 2 var start, end history.Selector - var limit int switch preposition { case "targets": // use the same selector parsing as BETWEEN, @@ -841,6 +848,7 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * err = utils.ErrInvalidParams return } + assuredPreposition = preposition if listTargets { targets, err = client.listTargets(start, end, limit) @@ -1227,29 +1235,34 @@ Get an explanation of , or "index" for a list of help topics.`), rb) // HISTORY alice 15 // HISTORY #darwin 1h func historyHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { - config := server.Config() - if !config.History.Enabled { - rb.Notice(client.t("This command has been disabled by the server administrators")) - return false - } - - items, channel, err := easySelectHistory(server, client, msg.Params) - - if err == errNoSuchChannel { - rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(msg.Params[0]), client.t("No such channel")) - return false - } else if err != nil { - rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), msg.Command, client.t("Could not retrieve history")) - return false - } - - if len(items) != 0 { - if channel != nil { - channel.replayHistoryItems(rb, items, true) - } else { - client.replayPrivmsgHistory(rb, items, "", true) + rb.Notice(client.t("This command is currently disabled. Please use CHATHISTORY")) + /* + config := server.Config() + if !config.History.Enabled { + rb.Notice(client.t("This command has been disabled by the server administrators")) + return false } - } + + items, channel, err := easySelectHistory(server, client, msg.Params) + + if err == errNoSuchChannel { + rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(msg.Params[0]), client.t("No such channel")) + return false + } else if err != nil { + rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), msg.Command, client.t("Could not retrieve history")) + return false + } + + var _, batchIdentifier = msg.GetTag("identifier") + + if len(items) != 0 { + if channel != nil { + channel.replayHistoryItems(rb, items, true, batchIdentifier) + } else { + client.replayPrivmsgHistory(rb, items, "", true, batchIdentifier) + } + } + */ return false } diff --git a/irc/mysql/history.go b/irc/mysql/history.go index 4b967104..6cc1ad21 100644 --- a/irc/mysql/history.go +++ b/irc/mysql/history.go @@ -623,6 +623,9 @@ func (mysql *MySQL) AddChannelItem(target string, item history.Item, account str func (mysql *MySQL) insertSequenceEntry(ctx context.Context, target string, messageTime int64, id int64) (err error) { _, err = mysql.insertSequence.ExecContext(ctx, target, messageTime, id) + if err != nil { + println(target, messageTime, id, ctx) + } mysql.logError("could not insert sequence entry", err) return } @@ -640,17 +643,17 @@ func (mysql *MySQL) insertCorrespondentsEntry(ctx context.Context, target, corre } func (mysql *MySQL) insertBase(ctx context.Context, item history.Item) (id int64, err error) { - value, err := marshalItem(&item) + _, err := marshalItem(&item) if mysql.logError("could not marshal item", err) { return } - msgidBytes, err := decodeMsgid(item.Message.Msgid) - if mysql.logError("could not decode msgid", err) { + //msgidBytes, err := decodeMsgid(item.Message.Msgid) + /*if mysql.logError("could not decode msgid", err) { return - } + }*/ - result, err := mysql.insertHistory.ExecContext(ctx, value, msgidBytes) + result, err := mysql.insertHistory.ExecContext(ctx, value, item.Message.Msgid) if mysql.logError("could not insert item", err) { return } @@ -812,7 +815,6 @@ func (mysql *MySQL) Export(account string, writer io.Writer) { } func (mysql *MySQL) lookupMsgid(ctx context.Context, msgid string, includeData bool) (result time.Time, id uint64, data []byte, err error) { - decoded, err := decodeMsgid(msgid) if err != nil { return } @@ -820,11 +822,14 @@ func (mysql *MySQL) lookupMsgid(ctx context.Context, msgid string, includeData b if includeData { cols = `sequence.nanotime, conversations.nanotime, history.id, history.data` } + + // Since CEF uses snowflakes and vanilla ergo uses blobs, we cast as int to make it function. + // May have to adjust it some day row := mysql.db.QueryRowContext(ctx, fmt.Sprintf(` SELECT %s FROM history LEFT JOIN sequence ON history.id = sequence.history_id LEFT JOIN conversations ON history.id = conversations.history_id - WHERE history.msgid = ? LIMIT 1;`, cols), decoded) + WHERE history.msgid = CAST(? AS INT) LIMIT 1;`, cols), msgid) var nanoSeq, nanoConv sql.NullInt64 if !includeData { err = row.Scan(&nanoSeq, &nanoConv) @@ -1042,6 +1047,7 @@ func (s *mySQLHistorySequence) Between(start, end history.Selector, limit int) ( defer cancel() startTime := start.Time + if start.Msgid != "" { startTime, _, _, err = s.mysql.lookupMsgid(ctx, start.Msgid, false) if err != nil { @@ -1055,6 +1061,7 @@ func (s *mySQLHistorySequence) Between(start, end history.Selector, limit int) ( endTime := end.Time if end.Msgid != "" { endTime, _, _, err = s.mysql.lookupMsgid(ctx, end.Msgid, false) + if err != nil { if err == sql.ErrNoRows { return nil, nil @@ -1101,3 +1108,37 @@ func (mysql *MySQL) MakeSequence(target, correspondent string, cutoff time.Time) cutoff: cutoff, } } + +func (mysql *MySQL) GetPMs(casefoldedUser string) (results map[string]int64, err error) { + if mysql.db == nil { + return + } + results = make(map[string]int64) + + ctx, cancel := context.WithTimeout(context.Background(), mysql.getTimeout()) + defer cancel() + + var queryBuf strings.Builder + args := make([]interface{}, 0) + + queryBuf.WriteString(`SELECT max(nanotime), correspondent FROM conversations WHERE target = ? GROUP BY correspondent;`) + args = append(args, casefoldedUser) + + rows, err := mysql.db.QueryContext(ctx, queryBuf.String(), args...) + if mysql.logError("could not get pms", err) { + return + } + defer rows.Close() + + var last int64 + var correspondent string + for rows.Next() { + err = rows.Scan(&last, &correspondent) + if mysql.logError("could not get pms", err) { + return + } + // We really don't need nanosecond precision + results[correspondent] = last / 1000000 + } + return +} diff --git a/irc/znc.go b/irc/znc.go index a118e839..1f8d0892 100644 --- a/irc/znc.go +++ b/irc/znc.go @@ -203,7 +203,7 @@ func zncPlayPrivmsgsFrom(client *Client, rb *ResponseBuffer, target string, star zncMax := client.server.Config().History.ZNCMax items, err := sequence.Between(history.Selector{Time: start}, history.Selector{Time: end}, zncMax) if err == nil && len(items) != 0 { - client.replayPrivmsgHistory(rb, items, target, false) + client.replayPrivmsgHistory(rb, items, target, false, "", "", 0) } } @@ -211,7 +211,7 @@ func zncPlayPrivmsgsFromAll(client *Client, rb *ResponseBuffer, start, end time. zncMax := client.server.Config().History.ZNCMax items, err := client.privmsgsBetween(start, end, maxDMTargetsForAutoplay, zncMax) if err == nil && len(items) != 0 { - client.replayPrivmsgHistory(rb, items, "", false) + client.replayPrivmsgHistory(rb, items, "", false, "", "", 0) } }