Index: trunk/downstream.go
===================================================================
--- trunk/downstream.go	(revision 534)
+++ trunk/downstream.go	(revision 535)
@@ -58,13 +58,53 @@
 }}
 
-func parseBouncerNetID(s string) (int64, error) {
+func parseBouncerNetID(subcommand, s string) (int64, error) {
 	id, err := strconv.ParseInt(s, 10, 64)
 	if err != nil {
 		return 0, ircError{&irc.Message{
 			Command: "FAIL",
-			Params:  []string{"BOUNCER", "INVALID_NETID", s, "Invalid network ID"},
+			Params:  []string{"BOUNCER", "INVALID_NETID", subcommand, s, "Invalid network ID"},
 		}}
 	}
 	return id, nil
+}
+
+func getNetworkAttrs(network *network) irc.Tags {
+	state := "disconnected"
+	if uc := network.conn; uc != nil {
+		state = "connected"
+	}
+
+	attrs := irc.Tags{
+		"name":     irc.TagValue(network.GetName()),
+		"state":    irc.TagValue(state),
+		"nickname": irc.TagValue(network.Nick),
+	}
+
+	if network.Username != "" {
+		attrs["username"] = irc.TagValue(network.Username)
+	}
+	if network.Realname != "" {
+		attrs["realname"] = irc.TagValue(network.Realname)
+	}
+
+	if u, err := network.URL(); err == nil {
+		hasHostPort := true
+		switch u.Scheme {
+		case "ircs":
+			attrs["tls"] = irc.TagValue("1")
+		case "irc+insecure":
+			attrs["tls"] = irc.TagValue("0")
+		default:
+			hasHostPort = false
+		}
+		if host, port, err := net.SplitHostPort(u.Host); err == nil && hasHostPort {
+			attrs["host"] = irc.TagValue(host)
+			attrs["port"] = irc.TagValue(port)
+		} else if hasHostPort {
+			attrs["host"] = irc.TagValue(u.Host)
+		}
+	}
+
+	return attrs
 }
 
@@ -76,12 +116,14 @@
 // capabilities.
 var permanentDownstreamCaps = map[string]string{
-	"batch":                    "",
-	"soju.im/bouncer-networks": "",
-	"cap-notify":               "",
-	"echo-message":             "",
-	"invite-notify":            "",
-	"message-tags":             "",
-	"sasl":                     "PLAIN",
-	"server-time":              "",
+	"batch":         "",
+	"cap-notify":    "",
+	"echo-message":  "",
+	"invite-notify": "",
+	"message-tags":  "",
+	"sasl":          "PLAIN",
+	"server-time":   "",
+
+	"soju.im/bouncer-networks":        "",
+	"soju.im/bouncer-networks-notify": "",
 }
 
@@ -599,5 +641,5 @@
 			}
 
-			id, err := parseBouncerNetID(idStr)
+			id, err := parseBouncerNetID(subcommand, idStr)
 			if err != nil {
 				return err
@@ -1022,4 +1064,27 @@
 	dc.updateNick()
 	dc.updateSupportedCaps()
+
+	if dc.caps["soju.im/bouncer-networks-notify"] {
+		dc.SendMessage(&irc.Message{
+			Prefix:  dc.srv.prefix(),
+			Command: "BATCH",
+			Params:  []string{"+networks", "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.forEachUpstream(func(uc *upstreamConn) {
@@ -1986,47 +2051,11 @@
 			})
 			dc.user.forEachNetwork(func(network *network) {
-				id := fmt.Sprintf("%v", network.ID)
-
-				state := "disconnected"
-				if uc := network.conn; uc != nil {
-					state = "connected"
-				}
-
-				attrs := irc.Tags{
-					"name":     irc.TagValue(network.GetName()),
-					"state":    irc.TagValue(state),
-					"nickname": irc.TagValue(network.Nick),
-				}
-
-				if network.Username != "" {
-					attrs["username"] = irc.TagValue(network.Username)
-				}
-				if network.Realname != "" {
-					attrs["realname"] = irc.TagValue(network.Realname)
-				}
-
-				if u, err := network.URL(); err == nil {
-					hasHostPort := true
-					switch u.Scheme {
-					case "ircs":
-						attrs["tls"] = irc.TagValue("1")
-					case "irc+insecure":
-						attrs["tls"] = irc.TagValue("0")
-					default:
-						hasHostPort = false
-					}
-					if host, port, err := net.SplitHostPort(u.Host); err == nil && hasHostPort {
-						attrs["host"] = irc.TagValue(host)
-						attrs["port"] = irc.TagValue(port)
-					} else if hasHostPort {
-						attrs["host"] = irc.TagValue(u.Host)
-					}
-				}
-
+				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", id, attrs.String()},
+					Params:  []string{"NETWORK", idStr, attrs.String()},
 				})
 			})
@@ -2096,5 +2125,5 @@
 				return err
 			}
-			id, err := parseBouncerNetID(idStr)
+			id, err := parseBouncerNetID(subcommand, idStr)
 			if err != nil {
 				return err
@@ -2106,5 +2135,5 @@
 				return ircError{&irc.Message{
 					Command: "FAIL",
-					Params:  []string{"BOUNCER", "INVALID_NETID", idStr, "Invalid network ID"},
+					Params:  []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
 				}}
 			}
@@ -2149,5 +2178,5 @@
 				return err
 			}
-			id, err := parseBouncerNetID(idStr)
+			id, err := parseBouncerNetID(subcommand, idStr)
 			if err != nil {
 				return err
@@ -2158,5 +2187,5 @@
 				return ircError{&irc.Message{
 					Command: "FAIL",
-					Params:  []string{"BOUNCER", "INVALID_NETID", idStr, "Invalid network ID"},
+					Params:  []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
 				}}
 			}
