Index: trunk/downstream.go
===================================================================
--- trunk/downstream.go	(revision 550)
+++ trunk/downstream.go	(revision 551)
@@ -188,4 +188,6 @@
 	caps            map[string]bool
 
+	lastBatchRef uint64
+
 	saslServer sasl.Server
 }
@@ -380,4 +382,8 @@
 		}
 	}
+	if !dc.caps["batch"] && msg.Tags["batch"] != "" {
+		msg = msg.Copy()
+		delete(msg.Tags, "batch")
+	}
 	if msg.Command == "JOIN" && !dc.caps["extended-join"] {
 		msg.Params = msg.Params[:1]
@@ -388,4 +394,28 @@
 
 	dc.conn.SendMessage(msg)
+}
+
+func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, f func(batchRef irc.TagValue)) {
+	dc.lastBatchRef++
+	ref := fmt.Sprintf("%v", dc.lastBatchRef)
+
+	if dc.caps["batch"] {
+		dc.SendMessage(&irc.Message{
+			Tags:    tags,
+			Prefix:  dc.srv.prefix(),
+			Command: "BATCH",
+			Params:  append([]string{"+" + ref, typ}, params...),
+		})
+	}
+
+	f(irc.TagValue(ref))
+
+	if dc.caps["batch"] {
+		dc.SendMessage(&irc.Message{
+			Prefix:  dc.srv.prefix(),
+			Command: "BATCH",
+			Params:  []string{"-" + ref},
+		})
+	}
 }
 
@@ -1084,23 +1114,15 @@
 
 	if dc.caps["soju.im/bouncer-networks-notify"] {
-		dc.SendMessage(&irc.Message{
-			Prefix:  dc.srv.prefix(),
-			Command: "BATCH",
-			Params:  []string{"+networks", "soju.im/bouncer-networks"},
-		})
-		dc.user.forEachNetwork(func(network *network) {
-			idStr := fmt.Sprintf("%v", network.ID)
-			attrs := getNetworkAttrs(network)
-			dc.SendMessage(&irc.Message{
-				Tags:    irc.Tags{"batch": irc.TagValue("networks")},
-				Prefix:  dc.srv.prefix(),
-				Command: "BOUNCER",
-				Params:  []string{"NETWORK", idStr, attrs.String()},
-			})
-		})
-		dc.SendMessage(&irc.Message{
-			Prefix:  dc.srv.prefix(),
-			Command: "BATCH",
-			Params:  []string{"-networks"},
+		dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
+			dc.user.forEachNetwork(func(network *network) {
+				idStr := fmt.Sprintf("%v", network.ID)
+				attrs := getNetworkAttrs(network)
+				dc.SendMessage(&irc.Message{
+					Tags:    irc.Tags{"batch": batchRef},
+					Prefix:  dc.srv.prefix(),
+					Command: "BOUNCER",
+					Params:  []string{"NETWORK", idStr, attrs.String()},
+				})
+			})
 		})
 	}
@@ -1193,37 +1215,22 @@
 	}
 
-	batchRef := "history"
-	if dc.caps["batch"] {
-		dc.SendMessage(&irc.Message{
-			Prefix:  dc.srv.prefix(),
-			Command: "BATCH",
-			Params:  []string{"+" + batchRef, "chathistory", dc.marshalEntity(net, target)},
-		})
-	}
-
-	for _, msg := range history {
-		if !dc.messageSupportsHistory(msg) {
-			continue
-		}
-
-		if ch != nil && ch.Detached {
-			if net.detachedMessageNeedsRelay(ch, msg) {
-				dc.relayDetachedMessage(net, msg)
-			}
-		} else {
-			if dc.caps["batch"] {
-				msg.Tags["batch"] = irc.TagValue(batchRef)
-			}
-			dc.SendMessage(dc.marshalMessage(msg, net))
-		}
-	}
-
-	if dc.caps["batch"] {
-		dc.SendMessage(&irc.Message{
-			Prefix:  dc.srv.prefix(),
-			Command: "BATCH",
-			Params:  []string{"-" + batchRef},
-		})
-	}
+	dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
+		for _, msg := range history {
+			if !dc.messageSupportsHistory(msg) {
+				continue
+			}
+
+			if ch != nil && ch.Detached {
+				if net.detachedMessageNeedsRelay(ch, msg) {
+					dc.relayDetachedMessage(net, msg)
+				}
+			} else {
+				if dc.caps["batch"] {
+					msg.Tags["batch"] = irc.TagValue(batchRef)
+				}
+				dc.SendMessage(dc.marshalMessage(msg, net))
+			}
+		}
+	})
 }
 
@@ -2108,28 +2115,17 @@
 			}
 
-			batchRef := "history-targets"
-			dc.SendMessage(&irc.Message{
-				Prefix:  dc.srv.prefix(),
-				Command: "BATCH",
-				Params:  []string{"+" + batchRef, "draft/chathistory-targets"},
-			})
-
-			for _, target := range targets {
-				if ch := uc.network.channels.Value(target.Name); ch != nil && ch.Detached {
-					continue
+			dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
+				for _, target := range targets {
+					if ch := uc.network.channels.Value(target.Name); ch != nil && ch.Detached {
+						continue
+					}
+
+					dc.SendMessage(&irc.Message{
+						Tags:    irc.Tags{"batch": batchRef},
+						Prefix:  dc.srv.prefix(),
+						Command: "CHATHISTORY",
+						Params:  []string{"TARGETS", target.Name, target.LatestMessage.UTC().Format(serverTimeLayout)},
+					})
 				}
-
-				dc.SendMessage(&irc.Message{
-					Tags:    irc.Tags{"batch": irc.TagValue(batchRef)},
-					Prefix:  dc.srv.prefix(),
-					Command: "CHATHISTORY",
-					Params:  []string{"TARGETS", target.Name, target.LatestMessage.UTC().Format(serverTimeLayout)},
-				})
-			}
-
-			dc.SendMessage(&irc.Message{
-				Prefix:  dc.srv.prefix(),
-				Command: "BATCH",
-				Params:  []string{"-" + batchRef},
 			})
 
@@ -2141,20 +2137,9 @@
 		}
 
-		batchRef := "history"
-		dc.SendMessage(&irc.Message{
-			Prefix:  dc.srv.prefix(),
-			Command: "BATCH",
-			Params:  []string{"+" + batchRef, "chathistory", target},
-		})
-
-		for _, msg := range history {
-			msg.Tags["batch"] = irc.TagValue(batchRef)
-			dc.SendMessage(dc.marshalMessage(msg, uc.network))
-		}
-
-		dc.SendMessage(&irc.Message{
-			Prefix:  dc.srv.prefix(),
-			Command: "BATCH",
-			Params:  []string{"-" + batchRef},
+		dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
+			for _, msg := range history {
+				msg.Tags["batch"] = batchRef
+				dc.SendMessage(dc.marshalMessage(msg, uc.network))
+			}
 		})
 	case "BOUNCER":
@@ -2166,23 +2151,15 @@
 		switch strings.ToUpper(subcommand) {
 		case "LISTNETWORKS":
-			dc.SendMessage(&irc.Message{
-				Prefix:  dc.srv.prefix(),
-				Command: "BATCH",
-				Params:  []string{"+networks", "soju.im/bouncer-networks"},
-			})
-			dc.user.forEachNetwork(func(network *network) {
-				idStr := fmt.Sprintf("%v", network.ID)
-				attrs := getNetworkAttrs(network)
-				dc.SendMessage(&irc.Message{
-					Tags:    irc.Tags{"batch": irc.TagValue("networks")},
-					Prefix:  dc.srv.prefix(),
-					Command: "BOUNCER",
-					Params:  []string{"NETWORK", idStr, attrs.String()},
+			dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
+				dc.user.forEachNetwork(func(network *network) {
+					idStr := fmt.Sprintf("%v", network.ID)
+					attrs := getNetworkAttrs(network)
+					dc.SendMessage(&irc.Message{
+						Tags:    irc.Tags{"batch": batchRef},
+						Prefix:  dc.srv.prefix(),
+						Command: "BOUNCER",
+						Params:  []string{"NETWORK", idStr, attrs.String()},
+					})
 				})
-			})
-			dc.SendMessage(&irc.Message{
-				Prefix:  dc.srv.prefix(),
-				Command: "BATCH",
-				Params:  []string{"-networks"},
 			})
 		case "ADDNETWORK":
