Browse Source

Fix #1562

Implement the new bot mode spec:
https://github.com/ircv3/ircv3-specifications/pull/439
tags/v2.6.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
1efde964e1
10 changed files with 130 additions and 96 deletions
  1. 2
    0
      irc/caps/constants.go
  2. 44
    34
      irc/channel.go
  3. 5
    4
      irc/chanserv.go
  4. 22
    23
      irc/client.go
  5. 17
    12
      irc/handlers.go
  6. 1
    0
      irc/history/history.go
  7. 14
    8
      irc/message_cache.go
  8. 4
    2
      irc/nickname.go
  9. 16
    10
      irc/responsebuffer.go
  10. 5
    3
      irc/roleplay.go

+ 2
- 0
irc/caps/constants.go View File

@@ -60,6 +60,8 @@ const (
60 60
 	MultilineConcatTag = "draft/multiline-concat"
61 61
 	// draft/relaymsg:
62 62
 	RelaymsgTagName = "draft/relaymsg"
63
+	// BOT mode: https://github.com/ircv3/ircv3-specifications/pull/439
64
+	BotTagName = "draft/bot"
63 65
 )
64 66
 
65 67
 func init() {

+ 44
- 34
irc/channel.go View File

@@ -729,6 +729,7 @@ func (channel *Channel) AddHistoryItem(item history.Item, account string) (err e
729 729
 // Join joins the given client to this channel (if they can be joined).
730 730
 func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) (joinErr error, forward string) {
731 731
 	details := client.Details()
732
+	isBot := client.HasMode(modes.Bot)
732 733
 
733 734
 	channel.stateMutex.RLock()
734 735
 	chname := channel.name
@@ -824,6 +825,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
824 825
 			Nick:        details.nickMask,
825 826
 			AccountName: details.accountName,
826 827
 			Message:     message,
828
+			IsBot:       isBot,
827 829
 		}
828 830
 		histItem.Params[0] = details.realname
829 831
 		channel.AddHistoryItem(histItem, details.account)
@@ -840,7 +842,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
840 842
 
841 843
 	// cache the most common case (JOIN without extended-join)
842 844
 	var cache MessageCache
843
-	cache.Initialize(channel.server, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "JOIN", chname)
845
+	cache.Initialize(channel.server, message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "JOIN", chname)
844 846
 	isAway, awayMessage := client.Away()
845 847
 	for _, member := range channel.Members() {
846 848
 		if respectAuditorium {
@@ -859,7 +861,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
859 861
 				continue
860 862
 			}
861 863
 			if session.capabilities.Has(caps.ExtendedJoin) {
862
-				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "JOIN", chname, details.accountName, details.realname)
864
+				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "JOIN", chname, details.accountName, details.realname)
863 865
 			} else {
864 866
 				cache.Send(session)
865 867
 			}
@@ -867,15 +869,15 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
867 869
 				session.Send(nil, client.server.name, "MODE", chname, modestr, details.nick)
868 870
 			}
869 871
 			if isAway && session.capabilities.Has(caps.AwayNotify) {
870
-				session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, nil, "AWAY", awayMessage)
872
+				session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
871 873
 			}
872 874
 		}
873 875
 	}
874 876
 
875 877
 	if rb.session.capabilities.Has(caps.ExtendedJoin) {
876
-		rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, nil, "JOIN", chname, details.accountName, details.realname)
878
+		rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "JOIN", chname, details.accountName, details.realname)
877 879
 	} else {
878
-		rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, nil, "JOIN", chname)
880
+		rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "JOIN", chname)
879 881
 	}
880 882
 
881 883
 	if rb.session.client == client {
@@ -988,6 +990,7 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
988 990
 	splitMessage := utils.MakeMessage(message)
989 991
 
990 992
 	details := client.Details()
993
+	isBot := client.HasMode(modes.Bot)
991 994
 	params := make([]string, 1, 2)
992 995
 	params[0] = chname
993 996
 	if message != "" {
@@ -996,7 +999,7 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
996 999
 	respectAuditorium := channel.flags.HasMode(modes.Auditorium) &&
997 1000
 		clientData.modes.HighestChannelUserMode() == modes.Mode(0)
998 1001
 	var cache MessageCache
999
-	cache.Initialize(channel.server, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
1002
+	cache.Initialize(channel.server, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, isBot, nil, "PART", params...)
1000 1003
 	for _, member := range channel.Members() {
1001 1004
 		if respectAuditorium {
1002 1005
 			channel.stateMutex.RLock()
@@ -1010,10 +1013,10 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
1010 1013
 			cache.Send(session)
1011 1014
 		}
1012 1015
 	}
1013
-	rb.AddFromClient(splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
1016
+	rb.AddFromClient(splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, isBot, nil, "PART", params...)
1014 1017
 	for _, session := range client.Sessions() {
1015 1018
 		if session != rb.session {
1016
-			session.sendFromClientInternal(false, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, nil, "PART", params...)
1019
+			session.sendFromClientInternal(false, splitMessage.Time, splitMessage.Msgid, details.nickMask, details.accountName, isBot, nil, "PART", params...)
1017 1020
 		}
1018 1021
 	}
1019 1022
 
@@ -1023,6 +1026,7 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
1023 1026
 			Nick:        details.nickMask,
1024 1027
 			AccountName: details.accountName,
1025 1028
 			Message:     splitMessage,
1029
+			IsBot:       isBot,
1026 1030
 		}, details.account)
1027 1031
 	}
1028 1032
 
@@ -1133,19 +1137,19 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
1133 1137
 		nick := NUHToNick(item.Nick)
1134 1138
 		switch item.Type {
1135 1139
 		case history.Privmsg:
1136
-			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.Tags, "PRIVMSG", chname, item.Message)
1140
+			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, item.Tags, "PRIVMSG", chname, item.Message)
1137 1141
 		case history.Notice:
1138
-			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.Tags, "NOTICE", chname, item.Message)
1142
+			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, item.Tags, "NOTICE", chname, item.Message)
1139 1143
 		case history.Tagmsg:
1140 1144
 			if eventPlayback {
1141
-				rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.Tags, "TAGMSG", chname, item.Message)
1145
+				rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, item.Tags, "TAGMSG", chname, item.Message)
1142 1146
 			}
1143 1147
 		case history.Join:
1144 1148
 			if eventPlayback {
1145 1149
 				if extendedJoin {
1146
-					rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "JOIN", chname, item.AccountName, item.Params[0])
1150
+					rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "JOIN", chname, item.AccountName, item.Params[0])
1147 1151
 				} else {
1148
-					rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "JOIN", chname)
1152
+					rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "JOIN", chname)
1149 1153
 				}
1150 1154
 			} else {
1151 1155
 				if !playJoinsAsPrivmsg {
@@ -1157,48 +1161,48 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
1157 1161
 				} else {
1158 1162
 					message = fmt.Sprintf(client.t("%[1]s [account: %[2]s] joined the channel"), nick, item.AccountName)
1159 1163
 				}
1160
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message)
1164
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
1161 1165
 			}
1162 1166
 		case history.Part:
1163 1167
 			if eventPlayback {
1164
-				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "PART", chname, item.Message.Message)
1168
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "PART", chname, item.Message.Message)
1165 1169
 			} else {
1166 1170
 				if !playJoinsAsPrivmsg {
1167 1171
 					continue // #474
1168 1172
 				}
1169 1173
 				message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
1170
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message)
1174
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
1171 1175
 			}
1172 1176
 		case history.Kick:
1173 1177
 			if eventPlayback {
1174
-				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "KICK", chname, item.Params[0], item.Message.Message)
1178
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "KICK", chname, item.Params[0], item.Message.Message)
1175 1179
 			} else {
1176 1180
 				message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Params[0], item.Message.Message)
1177
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message)
1181
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
1178 1182
 			}
1179 1183
 		case history.Quit:
1180 1184
 			if eventPlayback {
1181
-				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "QUIT", item.Message.Message)
1185
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "QUIT", item.Message.Message)
1182 1186
 			} else {
1183 1187
 				if !playJoinsAsPrivmsg {
1184 1188
 					continue // #474
1185 1189
 				}
1186 1190
 				message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
1187
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message)
1191
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
1188 1192
 			}
1189 1193
 		case history.Nick:
1190 1194
 			if eventPlayback {
1191
-				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "NICK", item.Params[0])
1195
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "NICK", item.Params[0])
1192 1196
 			} else {
1193 1197
 				message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
1194
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message)
1198
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
1195 1199
 			}
1196 1200
 		case history.Topic:
1197 1201
 			if eventPlayback {
1198
-				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "TOPIC", chname, item.Message.Message)
1202
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "TOPIC", chname, item.Message.Message)
1199 1203
 			} else {
1200 1204
 				message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message)
1201
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message)
1205
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
1202 1206
 			}
1203 1207
 		case history.Mode:
1204 1208
 			params := make([]string, len(item.Message.Split)+1)
@@ -1207,10 +1211,10 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
1207 1211
 				params[i+1] = pair.Message
1208 1212
 			}
1209 1213
 			if eventPlayback {
1210
-				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "MODE", params...)
1214
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "MODE", params...)
1211 1215
 			} else {
1212 1216
 				message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " "))
1213
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", nil, "PRIVMSG", chname, message)
1217
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
1214 1218
 			}
1215 1219
 		}
1216 1220
 	}
@@ -1268,12 +1272,13 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
1268 1272
 	channel.stateMutex.Unlock()
1269 1273
 
1270 1274
 	details := client.Details()
1275
+	isBot := client.HasMode(modes.Bot)
1271 1276
 	message := utils.MakeMessage(topic)
1272
-	rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic)
1277
+	rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "TOPIC", chname, topic)
1273 1278
 	for _, member := range channel.Members() {
1274 1279
 		for _, session := range member.Sessions() {
1275 1280
 			if session != rb.session {
1276
-				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic)
1281
+				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "TOPIC", chname, topic)
1277 1282
 			}
1278 1283
 		}
1279 1284
 	}
@@ -1397,7 +1402,7 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
1397 1402
 	rb.addEchoMessage(clientOnlyTags, details.nickMask, details.accountName, command, chname, message)
1398 1403
 
1399 1404
 	var cache MessageCache
1400
-	cache.InitializeSplitMessage(channel.server, details.nickMask, details.accountName, clientOnlyTags, command, chname, message)
1405
+	cache.InitializeSplitMessage(channel.server, details.nickMask, details.accountName, client.HasMode(modes.Bot), clientOnlyTags, command, chname, message)
1401 1406
 	for _, member := range channel.Members() {
1402 1407
 		if minPrefixMode != modes.Mode(0) && !channel.ClientIsAtLeast(member, minPrefixMode) {
1403 1408
 			// STATUSMSG or OpModerated
@@ -1519,23 +1524,25 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
1519 1524
 
1520 1525
 	message := utils.MakeMessage(comment)
1521 1526
 	details := client.Details()
1527
+	isBot := client.HasMode(modes.Bot)
1522 1528
 
1523 1529
 	targetNick := target.Nick()
1524 1530
 	chname := channel.Name()
1525 1531
 	for _, member := range channel.Members() {
1526 1532
 		for _, session := range member.Sessions() {
1527 1533
 			if session != rb.session {
1528
-				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "KICK", chname, targetNick, comment)
1534
+				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "KICK", chname, targetNick, comment)
1529 1535
 			}
1530 1536
 		}
1531 1537
 	}
1532
-	rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, nil, "KICK", chname, targetNick, comment)
1538
+	rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "KICK", chname, targetNick, comment)
1533 1539
 
1534 1540
 	histItem := history.Item{
1535 1541
 		Type:        history.Kick,
1536 1542
 		Nick:        details.nickMask,
1537 1543
 		AccountName: details.accountName,
1538 1544
 		Message:     message,
1545
+		IsBot:       isBot,
1539 1546
 	}
1540 1547
 	histItem.Params[0] = targetNick
1541 1548
 	channel.AddHistoryItem(histItem, details.account)
@@ -1563,7 +1570,7 @@ func (channel *Channel) Purge(source string) {
1563 1570
 		tnick := member.Nick()
1564 1571
 		msgid := utils.GenerateSecretToken()
1565 1572
 		for _, session := range member.Sessions() {
1566
-			session.sendFromClientInternal(false, now, msgid, source, "*", nil, "KICK", chname, tnick, member.t("This channel has been purged by the server administrators and cannot be used"))
1573
+			session.sendFromClientInternal(false, now, msgid, source, "*", false, nil, "KICK", chname, tnick, member.t("This channel has been purged by the server administrators and cannot be used"))
1567 1574
 		}
1568 1575
 		member.removeChannel(channel)
1569 1576
 	}
@@ -1600,6 +1607,7 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client, rb *ResponseBuf
1600 1607
 	}
1601 1608
 
1602 1609
 	details := inviter.Details()
1610
+	isBot := inviter.HasMode(modes.Bot)
1603 1611
 	tDetails := invitee.Details()
1604 1612
 	tnick := invitee.Nick()
1605 1613
 	message := utils.MakeMessage(chname)
@@ -1614,13 +1622,15 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client, rb *ResponseBuf
1614 1622
 		}
1615 1623
 		for _, session := range member.Sessions() {
1616 1624
 			if session.capabilities.Has(caps.InviteNotify) {
1617
-				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "INVITE", tnick, chname)
1625
+				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "INVITE", tnick, chname)
1618 1626
 			}
1619 1627
 		}
1620 1628
 	}
1621 1629
 
1622 1630
 	rb.Add(nil, inviter.server.name, RPL_INVITING, details.nick, tnick, chname)
1623
-	invitee.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "INVITE", tnick, chname)
1631
+	for _, iSession := range invitee.Sessions() {
1632
+		iSession.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, isBot, nil, "INVITE", tnick, chname)
1633
+	}
1624 1634
 	if away, awayMessage := invitee.Away(); away {
1625 1635
 		rb.Add(nil, inviter.server.name, RPL_AWAY, details.nick, tnick, awayMessage)
1626 1636
 	}

+ 5
- 4
irc/chanserv.go View File

@@ -271,7 +271,7 @@ func csAmodeHandler(service *ircService, server *Server, client *Client, command
271 271
 				if member.Account() == change.Arg {
272 272
 					applied, change := channel.applyModeToMember(client, change, rb)
273 273
 					if applied {
274
-						announceCmodeChanges(channel, modes.ModeChanges{change}, server.name, "*", "", rb)
274
+						announceCmodeChanges(channel, modes.ModeChanges{change}, server.name, "*", "", false, rb)
275 275
 					}
276 276
 				}
277 277
 			}
@@ -334,7 +334,7 @@ func csOpHandler(service *ircService, server *Server, client *Client, command st
334 334
 		},
335 335
 		rb)
336 336
 	if applied {
337
-		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, server.name, "*", "", rb)
337
+		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, server.name, "*", "", false, rb)
338 338
 	}
339 339
 
340 340
 	service.Notice(rb, client.t("Successfully granted operator privileges"))
@@ -386,7 +386,8 @@ func csDeopHandler(service *ircService, server *Server, client *Client, command
386 386
 	// the changes as coming from chanserv
387 387
 	applied := channel.ApplyChannelModeChanges(client, false, modeChanges, rb)
388 388
 	details := client.Details()
389
-	announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, rb)
389
+	isBot := client.HasMode(modes.Bot)
390
+	announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb)
390 391
 
391 392
 	if len(applied) == 0 {
392 393
 		return
@@ -437,7 +438,7 @@ func csRegisterHandler(service *ircService, server *Server, client *Client, comm
437 438
 		},
438 439
 		rb)
439 440
 	if applied {
440
-		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, service.prefix, "*", "", rb)
441
+		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, service.prefix, "*", "", false, rb)
441 442
 	}
442 443
 }
443 444
 

+ 22
- 23
irc/client.go View File

@@ -1095,9 +1095,9 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
1095 1095
 				continue
1096 1096
 			}
1097 1097
 			if hasEventPlayback {
1098
-				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "INVITE", nick, item.Message.Message)
1098
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "INVITE", nick, item.Message.Message)
1099 1099
 			} else {
1100
-				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))
1100
+				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))
1101 1101
 			}
1102 1102
 			continue
1103 1103
 		case history.Privmsg:
@@ -1118,11 +1118,11 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
1118 1118
 			tags = item.Tags
1119 1119
 		}
1120 1120
 		if !isSelfMessage(&item) {
1121
-			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
1121
+			rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, tags, command, nick, item.Message)
1122 1122
 		} else {
1123 1123
 			// this message was sent *from* the client to another nick; the target is item.Params[0]
1124 1124
 			// substitute client's current nickmask in case client changed nick
1125
-			rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, tags, command, item.Params[0], item.Message)
1125
+			rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, item.IsBot, tags, command, item.Params[0], item.Message)
1126 1126
 		}
1127 1127
 	}
1128 1128
 
@@ -1244,8 +1244,9 @@ func (client *Client) SetOper(oper *Oper) {
1244 1244
 // this is annoying to do correctly
1245 1245
 func (client *Client) sendChghost(oldNickMask string, vhost string) {
1246 1246
 	details := client.Details()
1247
+	isBot := client.HasMode(modes.Bot)
1247 1248
 	for fClient := range client.Friends(caps.ChgHost) {
1248
-		fClient.sendFromClientInternal(false, time.Time{}, "", oldNickMask, details.accountName, nil, "CHGHOST", details.username, vhost)
1249
+		fClient.sendFromClientInternal(false, time.Time{}, "", oldNickMask, details.accountName, isBot, nil, "CHGHOST", details.username, vhost)
1249 1250
 	}
1250 1251
 }
1251 1252
 
@@ -1594,14 +1595,16 @@ func (client *Client) destroy(session *Session) {
1594 1595
 		quitMessage = "Exited"
1595 1596
 	}
1596 1597
 	splitQuitMessage := utils.MakeMessage(quitMessage)
1598
+	isBot := client.HasMode(modes.Bot)
1597 1599
 	quitItem = history.Item{
1598 1600
 		Type:        history.Quit,
1599 1601
 		Nick:        details.nickMask,
1600 1602
 		AccountName: details.accountName,
1601 1603
 		Message:     splitQuitMessage,
1604
+		IsBot:       isBot,
1602 1605
 	}
1603 1606
 	var cache MessageCache
1604
-	cache.Initialize(client.server, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
1607
+	cache.Initialize(client.server, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, isBot, nil, "QUIT", quitMessage)
1605 1608
 	for friend := range friends {
1606 1609
 		for _, session := range friend.Sessions() {
1607 1610
 			cache.Send(session)
@@ -1615,12 +1618,12 @@ func (client *Client) destroy(session *Session) {
1615 1618
 
1616 1619
 // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
1617 1620
 // Adds account-tag to the line as well.
1618
-func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
1621
+func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask, accountName string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) {
1619 1622
 	if message.Is512() {
1620
-		session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
1623
+		session.sendFromClientInternal(blocking, message.Time, message.Msgid, nickmask, accountName, isBot, tags, command, target, message.Message)
1621 1624
 	} else {
1622 1625
 		if session.capabilities.Has(caps.Multiline) {
1623
-			for _, msg := range composeMultilineBatch(session.generateBatchID(), nickmask, accountName, tags, command, target, message) {
1626
+			for _, msg := range composeMultilineBatch(session.generateBatchID(), nickmask, accountName, isBot, tags, command, target, message) {
1624 1627
 				session.SendRawMessage(msg, blocking)
1625 1628
 			}
1626 1629
 		} else {
@@ -1634,24 +1637,13 @@ func (session *Session) sendSplitMsgFromClientInternal(blocking bool, nickmask,
1634 1637
 					msgidSent = true
1635 1638
 					msgid = message.Msgid
1636 1639
 				}
1637
-				session.sendFromClientInternal(blocking, message.Time, msgid, nickmask, accountName, tags, command, target, messagePair.Message)
1640
+				session.sendFromClientInternal(blocking, message.Time, msgid, nickmask, accountName, isBot, tags, command, target, messagePair.Message)
1638 1641
 			}
1639 1642
 		}
1640 1643
 	}
1641 1644
 }
1642 1645
 
1643
-// Sends a line with `nickmask` as the prefix, adding `time` and `account` tags if supported
1644
-func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
1645
-	for _, session := range client.Sessions() {
1646
-		err_ := session.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, params...)
1647
-		if err_ != nil {
1648
-			err = err_
1649
-		}
1650
-	}
1651
-	return
1652
-}
1653
-
1654
-func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
1646
+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) {
1655 1647
 	msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
1656 1648
 	// attach account-tag
1657 1649
 	if session.capabilities.Has(caps.AccountTag) && accountName != "*" {
@@ -1663,17 +1655,24 @@ func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Ti
1663 1655
 	}
1664 1656
 	// attach server-time
1665 1657
 	session.setTimeTag(&msg, serverTime)
1658
+	// attach bot tag
1659
+	if isBot && session.capabilities.Has(caps.MessageTags) {
1660
+		msg.SetTag(caps.BotTagName, "")
1661
+	}
1666 1662
 
1667 1663
 	return session.SendRawMessage(msg, blocking)
1668 1664
 }
1669 1665
 
1670
-func composeMultilineBatch(batchID, fromNickMask, fromAccount string, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.Message) {
1666
+func composeMultilineBatch(batchID, fromNickMask, fromAccount string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.Message) {
1671 1667
 	batchStart := ircmsg.MakeMessage(tags, fromNickMask, "BATCH", "+"+batchID, caps.MultilineBatchType, target)
1672 1668
 	batchStart.SetTag("time", message.Time.Format(IRCv3TimestampFormat))
1673 1669
 	batchStart.SetTag("msgid", message.Msgid)
1674 1670
 	if fromAccount != "*" {
1675 1671
 		batchStart.SetTag("account", fromAccount)
1676 1672
 	}
1673
+	if isBot {
1674
+		batchStart.SetTag(caps.BotTagName, "")
1675
+	}
1677 1676
 	result = append(result, batchStart)
1678 1677
 
1679 1678
 	for _, msg := range message.Split {

+ 17
- 12
irc/handlers.go View File

@@ -367,11 +367,12 @@ func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
367 367
 func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
368 368
 	// dispatch away-notify
369 369
 	details := client.Details()
370
+	isBot := client.HasMode(modes.Bot)
370 371
 	for session := range client.Friends(caps.AwayNotify) {
371 372
 		if isAway {
372
-			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, nil, "AWAY", awayMessage)
373
+			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY", awayMessage)
373 374
 		} else {
374
-			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, nil, "AWAY")
375
+			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.accountName, isBot, nil, "AWAY")
375 376
 		}
376 377
 	}
377 378
 }
@@ -1689,12 +1690,13 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respon
1689 1690
 	// process mode changes, include list operations (an empty set of changes does a list)
1690 1691
 	applied := channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb)
1691 1692
 	details := client.Details()
1692
-	announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, rb)
1693
+	isBot := client.HasMode(modes.Bot)
1694
+	announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb)
1693 1695
 
1694 1696
 	return false
1695 1697
 }
1696 1698
 
1697
-func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName, account string, rb *ResponseBuffer) {
1699
+func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName, account string, isBot bool, rb *ResponseBuffer) {
1698 1700
 	// send out changes
1699 1701
 	if len(applied) > 0 {
1700 1702
 		message := utils.MakeMessage("")
@@ -1703,11 +1705,11 @@ func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, a
1703 1705
 			message.Split = append(message.Split, utils.MessagePair{Message: changeString})
1704 1706
 		}
1705 1707
 		args := append([]string{channel.name}, changeStrings...)
1706
-		rb.AddFromClient(message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
1708
+		rb.AddFromClient(message.Time, message.Msgid, source, accountName, isBot, nil, "MODE", args...)
1707 1709
 		for _, member := range channel.Members() {
1708 1710
 			for _, session := range member.Sessions() {
1709 1711
 				if session != rb.session {
1710
-					session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
1712
+					session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, isBot, nil, "MODE", args...)
1711 1713
 				}
1712 1714
 			}
1713 1715
 		}
@@ -1716,6 +1718,7 @@ func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, a
1716 1718
 			Nick:        source,
1717 1719
 			AccountName: accountName,
1718 1720
 			Message:     message,
1721
+			IsBot:       isBot,
1719 1722
 		}, account)
1720 1723
 	}
1721 1724
 }
@@ -2204,17 +2207,18 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
2204 2207
 			}
2205 2208
 		}
2206 2209
 
2210
+		isBot := client.HasMode(modes.Bot)
2207 2211
 		for _, session := range deliverySessions {
2208 2212
 			hasTagsCap := session.capabilities.Has(caps.MessageTags)
2209 2213
 			// don't send TAGMSG at all if they don't have the tags cap
2210 2214
 			if histType == history.Tagmsg && hasTagsCap {
2211
-				session.sendFromClientInternal(false, message.Time, message.Msgid, nickMaskString, accountName, tags, command, tnick)
2215
+				session.sendFromClientInternal(false, message.Time, message.Msgid, nickMaskString, accountName, isBot, tags, command, tnick)
2212 2216
 			} else if histType != history.Tagmsg && !(session.isTor && message.IsRestrictedCTCPMessage()) {
2213 2217
 				tagsToSend := tags
2214 2218
 				if !hasTagsCap {
2215 2219
 					tagsToSend = nil
2216 2220
 				}
2217
-				session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, tagsToSend, command, tnick, message)
2221
+				session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, isBot, tagsToSend, command, tnick, message)
2218 2222
 			}
2219 2223
 		}
2220 2224
 
@@ -2674,9 +2678,9 @@ func relaymsgHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res
2674 2678
 			}
2675 2679
 
2676 2680
 			if session == rb.session {
2677
-				rb.AddSplitMessageFromClient(nick, "*", tagsToUse, "PRIVMSG", channelName, message)
2681
+				rb.AddSplitMessageFromClient(nick, "*", false, tagsToUse, "PRIVMSG", channelName, message)
2678 2682
 			} else {
2679
-				session.sendSplitMsgFromClientInternal(false, nick, "*", tagsToUse, "PRIVMSG", channelName, message)
2683
+				session.sendSplitMsgFromClientInternal(false, nick, "*", false, tagsToUse, "PRIVMSG", channelName, message)
2680 2684
 			}
2681 2685
 		}
2682 2686
 	}
@@ -2835,11 +2839,12 @@ func setnameHandler(server *Server, client *Client, msg ircmsg.Message, rb *Resp
2835 2839
 	now := time.Now().UTC()
2836 2840
 	friends := client.Friends(caps.SetName)
2837 2841
 	delete(friends, rb.session)
2842
+	isBot := client.HasMode(modes.Bot)
2838 2843
 	for session := range friends {
2839
-		session.sendFromClientInternal(false, now, "", details.nickMask, details.accountName, nil, "SETNAME", details.realname)
2844
+		session.sendFromClientInternal(false, now, "", details.nickMask, details.accountName, isBot, nil, "SETNAME", details.realname)
2840 2845
 	}
2841 2846
 	// respond to the user unconditionally, even if they don't have the cap
2842
-	rb.AddFromClient(now, "", details.nickMask, details.accountName, nil, "SETNAME", details.realname)
2847
+	rb.AddFromClient(now, "", details.nickMask, details.accountName, isBot, nil, "SETNAME", details.realname)
2843 2848
 	return false
2844 2849
 }
2845 2850
 

+ 1
- 0
irc/history/history.go View File

@@ -45,6 +45,7 @@ type Item struct {
45 45
 	// an incoming or outgoing message). this lets us emulate the "query buffer" functionality
46 46
 	// required by CHATHISTORY:
47 47
 	CfCorrespondent string
48
+	IsBot           bool `json:"IsBot,omitempty"`
48 49
 }
49 50
 
50 51
 // HasMsgid tests whether a message has the message id `msgid`.

+ 14
- 8
irc/message_cache.go View File

@@ -35,6 +35,7 @@ type MessageCache struct {
35 35
 	tags        map[string]string
36 36
 	source      string
37 37
 	command     string
38
+	isBot       bool
38 39
 
39 40
 	params []string
40 41
 
@@ -42,7 +43,7 @@ type MessageCache struct {
42 43
 	splitMessage utils.SplitMessage
43 44
 }
44 45
 
45
-func addAllTags(msg *ircmsg.Message, tags map[string]string, serverTime time.Time, msgid, accountName string) {
46
+func addAllTags(msg *ircmsg.Message, tags map[string]string, serverTime time.Time, msgid, accountName string, isBot bool) {
46 47
 	msg.UpdateTags(tags)
47 48
 	msg.SetTag("time", serverTime.Format(IRCv3TimestampFormat))
48 49
 	if accountName != "*" {
@@ -51,6 +52,9 @@ func addAllTags(msg *ircmsg.Message, tags map[string]string, serverTime time.Tim
51 52
 	if msgid != "" {
52 53
 		msg.SetTag("msgid", msgid)
53 54
 	}
55
+	if isBot {
56
+		msg.SetTag(caps.BotTagName, "")
57
+	}
54 58
 }
55 59
 
56 60
 func (m *MessageCache) handleErr(server *Server, err error) bool {
@@ -64,11 +68,12 @@ func (m *MessageCache) handleErr(server *Server, err error) bool {
64 68
 	return false
65 69
 }
66 70
 
67
-func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
71
+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) {
68 72
 	m.time = serverTime
69 73
 	m.msgid = msgid
70 74
 	m.source = nickmask
71 75
 	m.accountName = accountName
76
+	m.isBot = isBot
72 77
 	m.tags = tags
73 78
 	m.command = command
74 79
 	m.params = params
@@ -87,7 +92,7 @@ func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid st
87 92
 		return
88 93
 	}
89 94
 
90
-	addAllTags(&msg, tags, serverTime, msgid, accountName)
95
+	addAllTags(&msg, tags, serverTime, msgid, accountName, isBot)
91 96
 	m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
92 97
 	if m.handleErr(server, err) {
93 98
 		return
@@ -95,11 +100,12 @@ func (m *MessageCache) Initialize(server *Server, serverTime time.Time, msgid st
95 100
 	return
96 101
 }
97 102
 
98
-func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) (err error) {
103
+func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountName string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) (err error) {
99 104
 	m.time = message.Time
100 105
 	m.msgid = message.Msgid
101 106
 	m.source = nickmask
102 107
 	m.accountName = accountName
108
+	m.isBot = isBot
103 109
 	m.tags = tags
104 110
 	m.command = command
105 111
 	m.target = target
@@ -130,7 +136,7 @@ func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountN
130 136
 			}
131 137
 		}
132 138
 
133
-		addAllTags(&msg, tags, message.Time, message.Msgid, accountName)
139
+		addAllTags(&msg, tags, message.Time, message.Msgid, accountName, isBot)
134 140
 		m.fullTags, err = msg.LineBytesStrict(false, MaxLineLen)
135 141
 		if m.handleErr(server, err) {
136 142
 			return
@@ -158,7 +164,7 @@ func (m *MessageCache) InitializeSplitMessage(server *Server, nickmask, accountN
158 164
 		// so a collision isn't expected until there are on the order of 2**32
159 165
 		// concurrent batches being relayed:
160 166
 		batchID := utils.GenerateSecretToken()[:utils.SecretTokenLength/2]
161
-		batch := composeMultilineBatch(batchID, nickmask, accountName, tags, command, target, message)
167
+		batch := composeMultilineBatch(batchID, nickmask, accountName, isBot, tags, command, target, message)
162 168
 		m.fullTagsMultiline = make([][]byte, len(batch))
163 169
 		for i, msg := range batch {
164 170
 			if forceTrailing {
@@ -184,7 +190,7 @@ func (m *MessageCache) Send(session *Session) {
184 190
 				session.sendBytes(m.plain, false)
185 191
 			} else {
186 192
 				// slowpath
187
-				session.sendFromClientInternal(false, m.time, m.msgid, m.source, m.accountName, nil, m.command, m.params...)
193
+				session.sendFromClientInternal(false, m.time, m.msgid, m.source, m.accountName, m.isBot, nil, m.command, m.params...)
188 194
 			}
189 195
 		}
190 196
 	} else if m.fullTagsMultiline != nil {
@@ -199,7 +205,7 @@ func (m *MessageCache) Send(session *Session) {
199 205
 			}
200 206
 		} else {
201 207
 			// slowpath
202
-			session.sendSplitMsgFromClientInternal(false, m.source, m.accountName, m.tags, m.command, m.target, m.splitMessage)
208
+			session.sendSplitMsgFromClientInternal(false, m.source, m.accountName, m.isBot, m.tags, m.command, m.target, m.splitMessage)
203 209
 		}
204 210
 	}
205 211
 }

+ 4
- 2
irc/nickname.go View File

@@ -11,6 +11,7 @@ import (
11 11
 
12 12
 	"github.com/goshuirc/irc-go/ircfmt"
13 13
 	"github.com/oragono/oragono/irc/history"
14
+	"github.com/oragono/oragono/irc/modes"
14 15
 	"github.com/oragono/oragono/irc/sno"
15 16
 	"github.com/oragono/oragono/irc/utils"
16 17
 )
@@ -101,10 +102,11 @@ func performNickChange(server *Server, client *Client, target *Client, session *
101 102
 			target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("Operator %s changed nickname of $%s$r to %s"), client.Nick(), details.nick, assignedNickname))
102 103
 		}
103 104
 		target.server.whoWas.Append(details.WhoWas)
104
-		rb.AddFromClient(message.Time, message.Msgid, origNickMask, details.accountName, nil, "NICK", assignedNickname)
105
+		isBot := !isSanick && client.HasMode(modes.Bot)
106
+		rb.AddFromClient(message.Time, message.Msgid, origNickMask, details.accountName, isBot, nil, "NICK", assignedNickname)
105 107
 		for session := range target.Friends() {
106 108
 			if session != rb.session {
107
-				session.sendFromClientInternal(false, message.Time, message.Msgid, origNickMask, details.accountName, nil, "NICK", assignedNickname)
109
+				session.sendFromClientInternal(false, message.Time, message.Msgid, origNickMask, details.accountName, isBot, nil, "NICK", assignedNickname)
108 110
 			}
109 111
 		}
110 112
 	}

+ 16
- 10
irc/responsebuffer.go View File

@@ -96,7 +96,7 @@ func (rb *ResponseBuffer) Broadcast(tags map[string]string, prefix string, comma
96 96
 }
97 97
 
98 98
 // AddFromClient adds a new message from a specific client to our queue.
99
-func (rb *ResponseBuffer) AddFromClient(time time.Time, msgid string, fromNickMask string, fromAccount string, tags map[string]string, command string, params ...string) {
99
+func (rb *ResponseBuffer) AddFromClient(time time.Time, msgid string, fromNickMask string, fromAccount string, isBot bool, tags map[string]string, command string, params ...string) {
100 100
 	msg := ircmsg.MakeMessage(nil, fromNickMask, command, params...)
101 101
 	if rb.session.capabilities.Has(caps.MessageTags) {
102 102
 		msg.UpdateTags(tags)
@@ -107,8 +107,13 @@ func (rb *ResponseBuffer) AddFromClient(time time.Time, msgid string, fromNickMa
107 107
 		msg.SetTag("account", fromAccount)
108 108
 	}
109 109
 	// attach message-id
110
-	if len(msgid) > 0 && rb.session.capabilities.Has(caps.MessageTags) {
111
-		msg.SetTag("msgid", msgid)
110
+	if rb.session.capabilities.Has(caps.MessageTags) {
111
+		if len(msgid) != 0 {
112
+			msg.SetTag("msgid", msgid)
113
+		}
114
+		if isBot {
115
+			msg.SetTag(caps.BotTagName, "")
116
+		}
112 117
 	}
113 118
 	// attach server-time
114 119
 	rb.session.setTimeTag(&msg, time)
@@ -117,17 +122,17 @@ func (rb *ResponseBuffer) AddFromClient(time time.Time, msgid string, fromNickMa
117 122
 }
118 123
 
119 124
 // AddSplitMessageFromClient adds a new split message from a specific client to our queue.
120
-func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, tags map[string]string, command string, target string, message utils.SplitMessage) {
125
+func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, isBot bool, tags map[string]string, command string, target string, message utils.SplitMessage) {
121 126
 	if message.Is512() {
122 127
 		if message.Message == "" {
123 128
 			// XXX this is a TAGMSG
124
-			rb.AddFromClient(message.Time, message.Msgid, fromNickMask, fromAccount, tags, command, target)
129
+			rb.AddFromClient(message.Time, message.Msgid, fromNickMask, fromAccount, isBot, tags, command, target)
125 130
 		} else {
126
-			rb.AddFromClient(message.Time, message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
131
+			rb.AddFromClient(message.Time, message.Msgid, fromNickMask, fromAccount, isBot, tags, command, target, message.Message)
127 132
 		}
128 133
 	} else {
129 134
 		if rb.session.capabilities.Has(caps.Multiline) {
130
-			batch := composeMultilineBatch(rb.session.generateBatchID(), fromNickMask, fromAccount, tags, command, target, message)
135
+			batch := composeMultilineBatch(rb.session.generateBatchID(), fromNickMask, fromAccount, isBot, tags, command, target, message)
131 136
 			rb.setNestedBatchTag(&batch[0])
132 137
 			rb.setNestedBatchTag(&batch[len(batch)-1])
133 138
 			rb.messages = append(rb.messages, batch...)
@@ -137,25 +142,26 @@ func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAcc
137 142
 				if i == 0 {
138 143
 					msgid = message.Msgid
139 144
 				}
140
-				rb.AddFromClient(message.Time, msgid, fromNickMask, fromAccount, tags, command, target, messagePair.Message)
145
+				rb.AddFromClient(message.Time, msgid, fromNickMask, fromAccount, isBot, tags, command, target, messagePair.Message)
141 146
 			}
142 147
 		}
143 148
 	}
144 149
 }
145 150
 
146 151
 func (rb *ResponseBuffer) addEchoMessage(tags map[string]string, nickMask, accountName, command, target string, message utils.SplitMessage) {
152
+	// TODO fix isBot here
147 153
 	if rb.session.capabilities.Has(caps.EchoMessage) {
148 154
 		hasTagsCap := rb.session.capabilities.Has(caps.MessageTags)
149 155
 		if command == "TAGMSG" {
150 156
 			if hasTagsCap {
151
-				rb.AddFromClient(message.Time, message.Msgid, nickMask, accountName, tags, command, target)
157
+				rb.AddFromClient(message.Time, message.Msgid, nickMask, accountName, false, tags, command, target)
152 158
 			}
153 159
 		} else {
154 160
 			tagsToSend := tags
155 161
 			if !hasTagsCap {
156 162
 				tagsToSend = nil
157 163
 			}
158
-			rb.AddSplitMessageFromClient(nickMask, accountName, tagsToSend, command, target, message)
164
+			rb.AddSplitMessageFromClient(nickMask, accountName, false, tagsToSend, command, target, message)
159 165
 		}
160 166
 	}
161 167
 }

+ 5
- 3
irc/roleplay.go View File

@@ -92,15 +92,16 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
92 92
 			return
93 93
 		}
94 94
 
95
+		isBot := client.HasMode(modes.Bot)
95 96
 		for _, member := range channel.Members() {
96 97
 			for _, session := range member.Sessions() {
97 98
 				// see discussion on #865: clients do not understand how to do local echo
98 99
 				// of roleplay commands, so send them a copy whether they have echo-message
99 100
 				// or not
100 101
 				if rb.session == session {
101
-					rb.AddSplitMessageFromClient(sourceMask, "", nil, "PRIVMSG", targetString, splitMessage)
102
+					rb.AddSplitMessageFromClient(sourceMask, "*", isBot, nil, "PRIVMSG", targetString, splitMessage)
102 103
 				} else {
103
-					session.sendSplitMsgFromClientInternal(false, sourceMask, "*", nil, "PRIVMSG", targetString, splitMessage)
104
+					session.sendSplitMsgFromClientInternal(false, sourceMask, "*", isBot, nil, "PRIVMSG", targetString, splitMessage)
104 105
 				}
105 106
 			}
106 107
 		}
@@ -125,8 +126,9 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
125 126
 
126 127
 		cnick := client.Nick()
127 128
 		tnick := user.Nick()
129
+		isBot := client.HasMode(modes.Bot)
128 130
 		for _, session := range user.Sessions() {
129
-			session.sendSplitMsgFromClientInternal(false, sourceMask, "*", nil, "PRIVMSG", tnick, splitMessage)
131
+			session.sendSplitMsgFromClientInternal(false, sourceMask, "*", isBot, nil, "PRIVMSG", tnick, splitMessage)
130 132
 		}
131 133
 		if away, awayMessage := user.Away(); away {
132 134
 			//TODO(dan): possibly implement cooldown of away notifications to users

Loading…
Cancel
Save