From e741c1476b69d906d435e3a1cd70a5d78241a4a0 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sat, 14 Jan 2017 19:52:47 +1000 Subject: [PATCH] Implement message-ids draft --- irc/capability.go | 2 ++ irc/channel.go | 45 +++++++++++++++++++++------------------------ irc/client.go | 16 ++++++++++++---- irc/monitor.go | 2 +- irc/server.go | 38 ++++++++++++++++++++++++++------------ oragono.go | 3 +++ 6 files changed, 65 insertions(+), 41 deletions(-) diff --git a/irc/capability.go b/irc/capability.go index 1849040b..49c16cfe 100644 --- a/irc/capability.go +++ b/irc/capability.go @@ -23,6 +23,7 @@ const ( ExtendedJoin Capability = "extended-join" InviteNotify Capability = "invite-notify" MaxLine Capability = "draft/maxline" + MessageIDs Capability = "draft/message-ids" MessageTags Capability = "draft/message-tags-0.2" MultiPrefix Capability = "multi-prefix" SASL Capability = "sasl" @@ -40,6 +41,7 @@ var ( EchoMessage: true, ExtendedJoin: true, InviteNotify: true, + MessageIDs: true, // MaxLine is set during server startup MessageTags: true, MultiPrefix: true, diff --git a/irc/channel.go b/irc/channel.go index ff299447..55c717c7 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -372,21 +372,21 @@ func (channel *Channel) CanSpeak(client *Client) bool { } // TagMsg sends a tag message to everyone in this channel who can accept them. -func (channel *Channel) TagMsg(minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) { - channel.sendMessage("TAGMSG", []Capability{MessageTags}, minPrefix, clientOnlyTags, client, nil) +func (channel *Channel) TagMsg(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) { + channel.sendMessage(msgid, "TAGMSG", []Capability{MessageTags}, minPrefix, clientOnlyTags, client, nil) } // PrivMsg sends a private message to everyone in this channel. -func (channel *Channel) PrivMsg(minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message string) { - channel.sendMessage("PRIVMSG", nil, minPrefix, clientOnlyTags, client, &message) +func (channel *Channel) PrivMsg(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message string) { + channel.sendMessage(msgid, "PRIVMSG", nil, minPrefix, clientOnlyTags, client, &message) } // Notice sends a private message to everyone in this channel. -func (channel *Channel) Notice(minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message string) { - channel.sendMessage("NOTICE", nil, minPrefix, clientOnlyTags, client, &message) +func (channel *Channel) Notice(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message string) { + channel.sendMessage(msgid, "NOTICE", nil, minPrefix, clientOnlyTags, client, &message) } -func (channel *Channel) sendMessage(cmd string, requiredCaps []Capability, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) { +func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) { if !channel.CanSpeak(client) { client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return @@ -419,33 +419,30 @@ func (channel *Channel) sendMessage(cmd string, requiredCaps []Capability, minPr continue } + var messageTagsToUse *map[string]ircmsg.TagValue if member.capabilities[MessageTags] { - if message == nil { - member.SendFromClient(client, clientOnlyTags, client.nickMaskString, cmd, channel.name) - } else { - member.SendFromClient(client, clientOnlyTags, client.nickMaskString, cmd, channel.name, *message) - } + messageTagsToUse = clientOnlyTags + } + + if message == nil { + member.SendFromClient(msgid, client, messageTagsToUse, client.nickMaskString, cmd, channel.name) } else { - if message == nil { - member.SendFromClient(client, nil, client.nickMaskString, cmd, channel.name) - } else { - member.SendFromClient(client, nil, client.nickMaskString, cmd, channel.name, *message) - } + member.SendFromClient(msgid, client, messageTagsToUse, client.nickMaskString, cmd, channel.name, *message) } } } // SplitPrivMsg sends a private message to everyone in this channel. -func (channel *Channel) SplitPrivMsg(minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { - channel.sendSplitMessage("PRIVMSG", minPrefix, clientOnlyTags, client, message) +func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { + channel.sendSplitMessage(msgid, "PRIVMSG", minPrefix, clientOnlyTags, client, message) } // SplitNotice sends a private message to everyone in this channel. -func (channel *Channel) SplitNotice(minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { - channel.sendSplitMessage("NOTICE", minPrefix, clientOnlyTags, client, message) +func (channel *Channel) SplitNotice(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { + channel.sendSplitMessage(msgid, "NOTICE", minPrefix, clientOnlyTags, client, message) } -func (channel *Channel) sendSplitMessage(cmd string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { +func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) { if !channel.CanSpeak(client) { client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel") return @@ -468,9 +465,9 @@ func (channel *Channel) sendSplitMessage(cmd string, minPrefix *ChannelMode, cli continue } if member.capabilities[MessageTags] { - member.SendSplitMsgFromClient(client, clientOnlyTags, cmd, channel.name, message) + member.SendSplitMsgFromClient(msgid, client, clientOnlyTags, cmd, channel.name, message) } else { - member.SendSplitMsgFromClient(client, nil, cmd, channel.name, message) + member.SendSplitMsgFromClient(msgid, client, nil, cmd, channel.name, message) } } } diff --git a/irc/client.go b/irc/client.go index 346c1fff..077f15ec 100644 --- a/irc/client.go +++ b/irc/client.go @@ -498,19 +498,19 @@ func (client *Client) destroy() { // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client. // Adds account-tag to the line as well. -func (client *Client) SendSplitMsgFromClient(from *Client, tags *map[string]ircmsg.TagValue, command, target string, message SplitMessage) { +func (client *Client) SendSplitMsgFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command, target string, message SplitMessage) { if client.capabilities[MaxLine] { - client.SendFromClient(from, tags, from.nickMaskString, command, target, message.ForMaxLine) + client.SendFromClient(msgid, from, tags, from.nickMaskString, command, target, message.ForMaxLine) } else { for _, str := range message.For512 { - client.SendFromClient(from, tags, from.nickMaskString, command, target, str) + client.SendFromClient(msgid, from, tags, from.nickMaskString, command, target, str) } } } // SendFromClient sends an IRC line coming from a specific client. // Adds account-tag to the line as well. -func (client *Client) SendFromClient(from *Client, tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error { +func (client *Client) SendFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, prefix string, command string, params ...string) error { // attach account-tag if client.capabilities[AccountTag] && from.account != &NoAccount { if tags == nil { @@ -519,6 +519,14 @@ func (client *Client) SendFromClient(from *Client, tags *map[string]ircmsg.TagVa (*tags)["account"] = ircmsg.MakeTagValue(from.account.Name) } } + // attach message-id + if len(msgid) > 0 && client.capabilities[MessageIDs] { + if tags == nil { + tags = ircmsg.MakeTags("draft/msgid", msgid) + } else { + (*tags)["draft/msgid"] = ircmsg.MakeTagValue(msgid) + } + } return client.Send(tags, prefix, command, params...) } diff --git a/irc/monitor.go b/irc/monitor.go index 8bd62c20..9af0f5b4 100644 --- a/irc/monitor.go +++ b/irc/monitor.go @@ -16,7 +16,7 @@ func (client *Client) alertMonitors() { for _, mClient := range client.server.monitoring[client.nickCasefolded] { // don't have to notify ourselves if &mClient != client { - mClient.SendFromClient(client, nil, client.server.name, RPL_MONONLINE, mClient.nick, client.nickMaskString) + mClient.SendFromClient("", client, nil, client.server.name, RPL_MONONLINE, mClient.nick, client.nickMaskString) } } } diff --git a/irc/server.go b/irc/server.go index da7860b4..baca3045 100644 --- a/irc/server.go +++ b/irc/server.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "log" + "math/rand" "net" "net/http" "os" @@ -606,6 +607,11 @@ func (server *Server) wslisten(addr string, tlsMap map[string]*TLSListenConfig) }() } +// generateMessageID returns a network-unique message ID. +func (server *Server) generateMessageID() string { + return fmt.Sprintf("%s-%s", strconv.FormatInt(time.Now().UTC().UnixNano(), 10), strconv.FormatInt(rand.Int63(), 10)) +} + // // server functionality // @@ -937,7 +943,8 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, "No such channel") continue } - channel.SplitPrivMsg(lowestPrefix, clientOnlyTags, client, splitMsg) + msgid := server.generateMessageID() + channel.SplitPrivMsg(msgid, lowestPrefix, clientOnlyTags, client, splitMsg) } else { target, err = CasefoldName(targetString) user := server.clients.Get(target) @@ -950,9 +957,10 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool if !user.capabilities[MessageTags] { clientOnlyTags = nil } - user.SendSplitMsgFromClient(client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg) + msgid := server.generateMessageID() + user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg) if client.capabilities[EchoMessage] { - client.SendFromClient(client, clientOnlyTags, client.nickMaskString, "PRIVMSG", user.nick, message) + client.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg) } if user.flags[Away] { //TODO(dan): possibly implement cooldown of away notifications to users @@ -993,7 +1001,9 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, "No such channel") continue } - channel.TagMsg(lowestPrefix, clientOnlyTags, client) + msgid := server.generateMessageID() + + channel.TagMsg(msgid, lowestPrefix, clientOnlyTags, client) } else { target, err = CasefoldName(targetString) user := server.clients.Get(target) @@ -1003,13 +1013,15 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } continue } + msgid := server.generateMessageID() + // end user can't receive tagmsgs if !user.capabilities[MessageTags] { continue } - user.SendFromClient(client, clientOnlyTags, "TAGMSG", user.nick) + user.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick) if client.capabilities[EchoMessage] { - client.SendFromClient(client, clientOnlyTags, "TAGMSG", user.nick) + client.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick) } if user.flags[Away] { //TODO(dan): possibly implement cooldown of away notifications to users @@ -1210,7 +1222,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { originalHost := client.nickMaskString client.vhost = server.operators[name].Vhost for fClient := range client.Friends(ChgHost) { - fClient.SendFromClient(client, nil, originalHost, "CHGHOST", client.username, client.vhost) + fClient.SendFromClient("", client, nil, originalHost, "CHGHOST", client.username, client.vhost) } client.updateNickMask() } @@ -1449,9 +1461,9 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // dispatch away-notify for friend := range client.Friends(AwayNotify) { if client.flags[Away] { - friend.SendFromClient(client, nil, client.nickMaskString, "AWAY", client.awayMessage) + friend.SendFromClient("", client, nil, client.nickMaskString, "AWAY", client.awayMessage) } else { - friend.SendFromClient(client, nil, client.nickMaskString, "AWAY") + friend.SendFromClient("", client, nil, client.nickMaskString, "AWAY") } } @@ -1515,7 +1527,8 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // errors silently ignored with NOTICE as per RFC continue } - channel.SplitNotice(lowestPrefix, clientOnlyTags, client, splitMsg) + msgid := server.generateMessageID() + channel.SplitNotice(msgid, lowestPrefix, clientOnlyTags, client, splitMsg) } else { target, err := CasefoldName(targetString) if err != nil { @@ -1530,9 +1543,10 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { if !user.capabilities[MessageTags] { clientOnlyTags = nil } - user.SendSplitMsgFromClient(client, clientOnlyTags, "NOTICE", user.nick, splitMsg) + msgid := server.generateMessageID() + user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg) if client.capabilities[EchoMessage] { - client.SendFromClient(client, clientOnlyTags, client.nickMaskString, "NOTICE", user.nick, message) + client.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg) } } } diff --git a/oragono.go b/oragono.go index 61977e48..cc2e1931 100644 --- a/oragono.go +++ b/oragono.go @@ -8,7 +8,9 @@ package main import ( "fmt" "log" + "math/rand" "syscall" + "time" "github.com/DanielOaks/oragono/irc" "github.com/DanielOaks/oragono/mkcerts" @@ -83,6 +85,7 @@ Options: } } else if arguments["run"].(bool) { irc.Log.SetLevel(config.Server.Log) + rand.Seed(time.Now().UTC().UnixNano()) server := irc.NewServer(configfile, config) if server == nil { log.Println("Could not load server")