Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Implement the new bot mode spec:
ircv3/ircv3-specifications#439
  • Loading branch information
slingamn committed Mar 17, 2021
1 parent 507d53c commit 1efde96
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 96 deletions.
2 changes: 2 additions & 0 deletions irc/caps/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ const (
MultilineConcatTag = "draft/multiline-concat"
// draft/relaymsg:
RelaymsgTagName = "draft/relaymsg"
// BOT mode: https://github.com/ircv3/ircv3-specifications/pull/439
BotTagName = "draft/bot"
)

func init() {
Expand Down
78 changes: 44 additions & 34 deletions irc/channel.go

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions irc/chanserv.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ func csAmodeHandler(service *ircService, server *Server, client *Client, command
if member.Account() == change.Arg {
applied, change := channel.applyModeToMember(client, change, rb)
if applied {
announceCmodeChanges(channel, modes.ModeChanges{change}, server.name, "*", "", rb)
announceCmodeChanges(channel, modes.ModeChanges{change}, server.name, "*", "", false, rb)
}
}
}
Expand Down Expand Up @@ -334,7 +334,7 @@ func csOpHandler(service *ircService, server *Server, client *Client, command st
},
rb)
if applied {
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, server.name, "*", "", rb)
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, server.name, "*", "", false, rb)
}

service.Notice(rb, client.t("Successfully granted operator privileges"))
Expand Down Expand Up @@ -386,7 +386,8 @@ func csDeopHandler(service *ircService, server *Server, client *Client, command
// the changes as coming from chanserv
applied := channel.ApplyChannelModeChanges(client, false, modeChanges, rb)
details := client.Details()
announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, rb)
isBot := client.HasMode(modes.Bot)
announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb)

if len(applied) == 0 {
return
Expand Down Expand Up @@ -437,7 +438,7 @@ func csRegisterHandler(service *ircService, server *Server, client *Client, comm
},
rb)
if applied {
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, service.prefix, "*", "", rb)
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, service.prefix, "*", "", false, rb)
}
}

Expand Down
45 changes: 22 additions & 23 deletions irc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1095,9 +1095,9 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
continue
}
if hasEventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "INVITE", nick, item.Message.Message)
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "INVITE", nick, item.Message.Message)
} else {
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", fmt.Sprintf(client.t("%[1]s invited you to channel %[2]s"), NUHToNick(item.Nick), item.Message.Message))
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", fmt.Sprintf(client.t("%[1]s invited you to channel %[2]s"), NUHToNick(item.Nick), item.Message.Message))
}
continue
case history.Privmsg:
Expand All @@ -1118,11 +1118,11 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
tags = item.Tags
}
if !isSelfMessage(&item) {
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, tags, command, nick, item.Message)
} else {
// this message was sent *from* the client to another nick; the target is item.Params[0]
// substitute client's current nickmask in case client changed nick
rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, tags, command, item.Params[0], item.Message)
rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, item.IsBot, tags, command, item.Params[0], item.Message)
}
}

Expand Down Expand Up @@ -1244,8 +1244,9 @@ func (client *Client) SetOper(oper *Oper) {
// this is annoying to do correctly
func (client *Client) sendChghost(oldNickMask string, vhost string) {
details := client.Details()
isBot := client.HasMode(modes.Bot)
for fClient := range client.Friends(caps.ChgHost) {
fClient.sendFromClientInternal(false, time.Time{}, "", oldNickMask, details.accountName, nil, "CHGHOST", details.username, vhost)
fClient.sendFromClientInternal(false, time.Time{}, "", oldNickMask, details.accountName, isBot, nil, "CHGHOST", details.username, vhost)
}
}

Expand Down Expand Up @@ -1594,14 +1595,16 @@ func (client *Client) destroy(session *Session) {
quitMessage = "Exited"
}
splitQuitMessage := utils.MakeMessage(quitMessage)
isBot := client.HasMode(modes.Bot)
quitItem = history.Item{
Type: history.Quit,
Nick: details.nickMask,
AccountName: details.accountName,
Message: splitQuitMessage,
IsBot: isBot,
}
var cache MessageCache
cache.Initialize(client.server, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
cache.Initialize(client.server, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, isBot, nil, "QUIT", quitMessage)
for friend := range friends {
for _, session := range friend.Sessions() {
cache.Send(session)
Expand All @@ -1615,12 +1618,12 @@ func (client *Client) destroy(session *Session) {

// SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
// Adds account-tag to the line as well.
func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) {
if message.Is512() {
session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, isBot, tags, command, target, message.Message)
} else {
if session.capabilities.Has(caps.Multiline) {
for _, msg := range composeMultilineBatch(session.generateBatchID(), nickmask, accountName, tags, command, target, message) {
for _, msg := range composeMultilineBatch(session.generateBatchID(), nickmask, accountName, isBot, tags, command, target, message) {
session.SendRawMessage(msg, blocking)
}
} else {
Expand All @@ -1634,24 +1637,13 @@ func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask,
msgidSent = true
msgid = message.Msgid
}
session.sendFromClientInternal(blocking, message.Time, msgid, nickmask, accountName, tags, command, target, messagePair.Message)
session.sendFromClientInternal(blocking, message.Time, msgid, nickmask, accountName, isBot, tags, command, target, messagePair.Message)
}
}
}
}

// Sends a line with `nickmask` as the prefix, adding `time` and `account` tags if supported
func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
for _, session := range client.Sessions() {
err_ := session.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, params...)
if err_ != nil {
err = err_
}
}
return
}

func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, isBot bool, tags map[string]string, command string, params ...string) (err error) {
msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
// attach account-tag
if session.capabilities.Has(caps.AccountTag) && accountName != "*" {
Expand All @@ -1663,17 +1655,24 @@ func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Ti
}
// attach server-time
session.setTimeTag(&msg, serverTime)
// attach bot tag
if isBot && session.capabilities.Has(caps.MessageTags) {
msg.SetTag(caps.BotTagName, "")
}

return session.SendRawMessage(msg, blocking)
}

func composeMultilineBatch(batchID, fromNickMask, fromAccount string, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.Message) {
func composeMultilineBatch(batchID, fromNickMask, fromAccount string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.Message) {
batchStart := ircmsg.MakeMessage(tags, fromNickMask, "BATCH", "+"+batchID, caps.MultilineBatchType, target)
batchStart.SetTag("time", message.Time.Format(IRCv3TimestampFormat))
batchStart.SetTag("msgid", message.Msgid)
if fromAccount != "*" {
batchStart.SetTag("account", fromAccount)
}
if isBot {
batchStart.SetTag(caps.BotTagName, "")
}
result = append(result, batchStart)

for _, msg := range message.Split {
Expand Down
29 changes: 17 additions & 12 deletions irc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,12 @@ func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
// dispatch away-notify
details := client.Details()
isBot := client.HasMode(modes.Bot)
for session := range client.Friends(caps.AwayNotify) {
if isAway {
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, nil, "AWAY", awayMessage)
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
} else {
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, nil, "AWAY")
session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")
}
}
}
Expand Down Expand Up @@ -1689,12 +1690,13 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respon
// process mode changes, include list operations (an empty set of changes does a list)
applied := channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb)
details := client.Details()
announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, rb)
isBot := client.HasMode(modes.Bot)
announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb)

return false
}

func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName, account string, rb *ResponseBuffer) {
func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName, account string, isBot bool, rb *ResponseBuffer) {
// send out changes
if len(applied) > 0 {
message := utils.MakeMessage("")
Expand All @@ -1703,11 +1705,11 @@ func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, a
message.Split = append(message.Split, utils.MessagePair{Message: changeString})
}
args := append([]string{channel.name}, changeStrings...)
rb.AddFromClient(message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
rb.AddFromClient(message.Time, message.Msgid, source, accountName, isBot, nil, "MODE", args...)
for _, member := range channel.Members() {
for _, session := range member.Sessions() {
if session != rb.session {
session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, isBot, nil, "MODE", args...)
}
}
}
Expand All @@ -1716,6 +1718,7 @@ func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, a
Nick: source,
AccountName: accountName,
Message: message,
IsBot: isBot,
}, account)
}
}
Expand Down Expand Up @@ -2204,17 +2207,18 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
}
}

isBot := client.HasMode(modes.Bot)
for _, session := range deliverySessions {
hasTagsCap := session.capabilities.Has(caps.MessageTags)
// don't send TAGMSG at all if they don't have the tags cap
if histType == history.Tagmsg && hasTagsCap {
session.sendFromClientInternal(false, message.Time, message.Msgid, nickMaskString, accountName, tags, command, tnick)
session.sendFromClientInternal(false, message.Time, message.Msgid, nickMaskString, accountName, isBot, tags, command, tnick)
} else if histType != history.Tagmsg && !(session.isTor && message.IsRestrictedCTCPMessage()) {
tagsToSend := tags
if !hasTagsCap {
tagsToSend = nil
}
session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, tagsToSend, command, tnick, message)
session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, isBot, tagsToSend, command, tnick, message)
}
}

Expand Down Expand Up @@ -2674,9 +2678,9 @@ func relaymsgHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res
}

if session == rb.session {
rb.AddSplitMessageFromClient(nick, "*", tagsToUse, "PRIVMSG", channelName, message)
rb.AddSplitMessageFromClient(nick, "*", false, tagsToUse, "PRIVMSG", channelName, message)
} else {
session.sendSplitMsgFromClientInternal(false, nick, "*", tagsToUse, "PRIVMSG", channelName, message)
session.sendSplitMsgFromClientInternal(false, nick, "*", false, tagsToUse, "PRIVMSG", channelName, message)
}
}
}
Expand Down Expand Up @@ -2835,11 +2839,12 @@ func setnameHandler(server *Server, client *Client, msg ircmsg.Message, rb *Resp
now := time.Now().UTC()
friends := client.Friends(caps.SetName)
delete(friends, rb.session)
isBot := client.HasMode(modes.Bot)
for session := range friends {
session.sendFromClientInternal(false, now, "", details.nickMask, details.accountName, nil, "SETNAME", details.realname)
session.sendFromClientInternal(false, now, "", details.nickMask, details.accountName, isBot, nil, "SETNAME", details.realname)
}
// respond to the user unconditionally, even if they don't have the cap
rb.AddFromClient(now, "", details.nickMask, details.accountName, nil, "SETNAME", details.realname)
rb.AddFromClient(now, "", details.nickMask, details.accountName, isBot, nil, "SETNAME", details.realname)
return false
}

Expand Down
1 change: 1 addition & 0 deletions irc/history/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Item struct {
// an incoming or outgoing message). this lets us emulate the "query buffer" functionality
// required by CHATHISTORY:
CfCorrespondent string
IsBot bool `json:"IsBot,omitempty"`
}

// HasMsgid tests whether a message has the message id `msgid`.
Expand Down
22 changes: 14 additions & 8 deletions irc/message_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ type MessageCache struct {
tags map[string]string
source string
command string
isBot bool

params []string

target string
splitMessage utils.SplitMessage
}

func addAllTags(msg *ircmsg.Message, tags map[string]string, serverTime time.Time, msgid, accountName string) {
func addAllTags(msg *ircmsg.Message, tags map[string]string, serverTime time.Time, msgid, accountName string, isBot bool) {
msg.UpdateTags(tags)
msg.SetTag("time", serverTime.Format(IRCv3TimestampFormat))
if accountName != "*" {
Expand All @@ -51,6 +52,9 @@ func addAllTags(msg *ircmsg.Message, tags map[string]string, serverTime time.Tim
if msgid != "" {
msg.SetTag("msgid", msgid)
}
if isBot {
msg.SetTag(caps.BotTagName, "")
}
}

func (m *MessageCache) handleErr(server *Server, err error) bool {
Expand All @@ -64,11 +68,12 @@ func (m *MessageCache) handleErr(server *Server, err error) bool {
return false
}

func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid string, nickmask, accountName string, isBot bool, tags map[string]string, command string, params ...string) (err error) {
m.time = serverTime
m.msgid = msgid
m.source = nickmask
m.accountName = accountName
m.isBot = isBot
m.tags = tags
m.command = command
m.params = params
Expand All @@ -87,19 +92,20 @@ func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid st
return
}

addAllTags(&msg, tags, serverTime, msgid, accountName)
addAllTags(&msg, tags, serverTime, msgid, accountName, isBot)
m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
if m.handleErr(server, err) {
return
}
return
}

func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) (err error) {
func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountName string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) (err error) {
m.time = message.Time
m.msgid = message.Msgid
m.source = nickmask
m.accountName = accountName
m.isBot = isBot
m.tags = tags
m.command = command
m.target = target
Expand Down Expand Up @@ -130,7 +136,7 @@ func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountN
}
}

addAllTags(&msg, tags, message.Time, message.Msgid, accountName)
addAllTags(&msg, tags, message.Time, message.Msgid, accountName, isBot)
m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
if m.handleErr(server, err) {
return
Expand Down Expand Up @@ -158,7 +164,7 @@ func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountN
// so a collision isn't expected until there are on the order of 2**32
// concurrent batches being relayed:
batchID := utils.GenerateSecretToken()[:utils.SecretTokenLength/2]
batch := composeMultilineBatch(batchID, nickmask, accountName, tags, command, target, message)
batch := composeMultilineBatch(batchID, nickmask, accountName, isBot, tags, command, target, message)
m.fullTagsMultiline = make([][]byte, len(batch))
for i, msg := range batch {
if forceTrailing {
Expand All @@ -184,7 +190,7 @@ func (m *MessageCache) Send(session *Session) {
session.sendBytes(m.plain, false)
} else {
// slowpath
session.sendFromClientInternal(false, m.time, m.msgid, m.source, m.accountName, nil, m.command, m.params...)
session.sendFromClientInternal(false, m.time, m.msgid, m.source, m.accountName, m.isBot, nil, m.command, m.params...)
}
}
} else if m.fullTagsMultiline != nil {
Expand All @@ -199,7 +205,7 @@ func (m *MessageCache) Send(session *Session) {
}
} else {
// slowpath
session.sendSplitMsgFromClientInternal(false, m.source, m.accountName, m.tags, m.command, m.target, m.splitMessage)
session.sendSplitMsgFromClientInternal(false, m.source, m.accountName, m.isBot, m.tags, m.command, m.target, m.splitMessage)
}
}
}
Loading

0 comments on commit 1efde96

Please sign in to comment.