Index: trunk/downstream.go
===================================================================
--- trunk/downstream.go	(revision 434)
+++ trunk/downstream.go	(revision 435)
@@ -1158,11 +1158,21 @@
 			})
 
-			ch := &Channel{Name: upstreamName, Key: key, Detached: false}
-			if current, ok := uc.network.channels[ch.Name]; ok && key == "" {
+			var ch *Channel
+			var ok bool
+			if ch, ok = uc.network.channels[upstreamName]; ok {
 				// Don't clear the channel key if there's one set
 				// TODO: add a way to unset the channel key
-				ch.Key = current.Key
-			}
-			if err := uc.network.createUpdateChannel(ch); err != nil {
+				if key != "" {
+					ch.Key = key
+				}
+				uc.network.attach(ch)
+			} else {
+				ch = &Channel{
+					Name: upstreamName,
+					Key:  key,
+				}
+				uc.network.channels[upstreamName] = ch
+			}
+			if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
 				dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
 			}
@@ -1186,7 +1196,17 @@
 
 			if strings.EqualFold(reason, "detach") {
-				ch := &Channel{Name: upstreamName, Detached: true}
-				if err := uc.network.createUpdateChannel(ch); err != nil {
-					dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err)
+				var ch *Channel
+				var ok bool
+				if ch, ok = uc.network.channels[upstreamName]; ok {
+					uc.network.detach(ch)
+				} else {
+					ch = &Channel{
+						Name:     name,
+						Detached: true,
+					}
+					uc.network.channels[upstreamName] = ch
+				}
+				if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
+					dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
 				}
 			} else {
@@ -1614,4 +1634,6 @@
 			}
 			uc.produce(upstreamName, echoMsg, dc)
+
+			uc.updateChannelAutoDetach(upstreamName)
 		}
 	case "NOTICE":
@@ -1637,4 +1659,6 @@
 				Params:  []string{upstreamName, unmarshaledText},
 			})
+
+			uc.updateChannelAutoDetach(upstreamName)
 		}
 	case "TAGMSG":
@@ -1659,4 +1683,6 @@
 				Params:  []string{upstreamName},
 			})
+
+			uc.updateChannelAutoDetach(upstreamName)
 		}
 	case "INVITE":
Index: trunk/upstream.go
===================================================================
--- trunk/upstream.go	(revision 434)
+++ trunk/upstream.go	(revision 435)
@@ -51,4 +51,23 @@
 	Members      map[string]*memberships
 	complete     bool
+	detachTimer  *time.Timer
+}
+
+func (uc *upstreamChannel) updateAutoDetach(dur time.Duration) {
+	if uc.detachTimer != nil {
+		uc.detachTimer.Stop()
+		uc.detachTimer = nil
+	}
+
+	if dur == 0 {
+		return
+	}
+
+	uc.detachTimer = time.AfterFunc(dur, func() {
+		uc.conn.network.user.events <- eventChannelDetach{
+			uc:   uc.conn,
+			name: uc.Name,
+		}
+	})
 }
 
@@ -404,12 +423,17 @@
 				target = msg.Prefix.Name
 			}
+
+			if ch, ok := uc.network.channels[target]; ok {
+				if ch.Detached {
+					uc.handleDetachedMessage(msg.Prefix.Name, text, ch)
+				}
+
+				highlight := msg.Prefix.Name != uc.nick && isHighlight(text, uc.nick)
+				if ch.DetachOn == FilterMessage || ch.DetachOn == FilterDefault || (ch.DetachOn == FilterHighlight && highlight) {
+					uc.updateChannelAutoDetach(target)
+				}
+			}
+
 			uc.produce(target, msg, nil)
-
-			highlight := msg.Prefix.Name != uc.nick && isHighlight(text, uc.nick)
-			if ch, ok := uc.network.channels[target]; ok && ch.Detached && highlight {
-				uc.forEachDownstream(func(dc *downstreamConn) {
-					sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), msg.Prefix.Name, text))
-				})
-			}
 		}
 	case "CAP":
@@ -737,4 +761,5 @@
 					Members: make(map[string]*memberships),
 				}
+				uc.updateChannelAutoDetach(ch)
 
 				uc.SendMessage(&irc.Message{
@@ -767,5 +792,8 @@
 			if msg.Prefix.Name == uc.nick {
 				uc.logger.Printf("parted channel %q", ch)
-				delete(uc.channels, ch)
+				if uch, ok := uc.channels[ch]; ok {
+					delete(uc.channels, ch)
+					uch.updateAutoDetach(0)
+				}
 			} else {
 				ch, err := uc.getChannel(ch)
@@ -1409,4 +1437,23 @@
 }
 
+func (uc *upstreamConn) handleDetachedMessage(sender string, text string, ch *Channel) {
+	highlight := sender != uc.nick && isHighlight(text, uc.nick)
+	if ch.RelayDetached == FilterMessage || ((ch.RelayDetached == FilterHighlight || ch.RelayDetached == FilterDefault) && highlight) {
+		uc.forEachDownstream(func(dc *downstreamConn) {
+			if highlight {
+				sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), sender, text))
+			} else {
+				sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), sender, text))
+			}
+		})
+	}
+	if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && highlight) {
+		uc.network.attach(ch)
+		if err := uc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
+			uc.logger.Printf("failed to update channel %q: %v", ch.Name, err)
+		}
+	}
+}
+
 func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
 	caps := strings.Fields(capsStr)
@@ -1702,2 +1749,10 @@
 	uc.away = away
 }
+
+func (uc *upstreamConn) updateChannelAutoDetach(name string) {
+	if uch, ok := uc.channels[name]; ok {
+		if ch, ok := uc.network.channels[name]; ok && !ch.Detached {
+			uch.updateAutoDetach(ch.DetachAfter)
+		}
+	}
+}
Index: trunk/user.go
===================================================================
--- trunk/user.go	(revision 434)
+++ trunk/user.go	(revision 435)
@@ -47,4 +47,9 @@
 type eventDownstreamDisconnected struct {
 	dc *downstreamConn
+}
+
+type eventChannelDetach struct {
+	uc   *upstreamConn
+	name string
 }
 
@@ -177,54 +182,57 @@
 }
 
-func (net *network) createUpdateChannel(ch *Channel) error {
-	if current, ok := net.channels[ch.Name]; ok {
-		ch.ID = current.ID // update channel if it already exists
-	}
-	if err := net.user.srv.db.StoreChannel(net.ID, ch); err != nil {
-		return err
-	}
-	prev := net.channels[ch.Name]
-	net.channels[ch.Name] = ch
-
-	if prev != nil && prev.Detached != ch.Detached {
-		history := net.history[ch.Name]
-		if ch.Detached {
-			net.user.srv.Logger.Printf("network %q: detaching channel %q", net.GetName(), ch.Name)
-			net.forEachDownstream(func(dc *downstreamConn) {
-				net.offlineClients[dc.clientName] = struct{}{}
-
-				dc.SendMessage(&irc.Message{
-					Prefix:  dc.prefix(),
-					Command: "PART",
-					Params:  []string{dc.marshalEntity(net, ch.Name), "Detach"},
-				})
-			})
-		} else {
-			net.user.srv.Logger.Printf("network %q: attaching channel %q", net.GetName(), ch.Name)
-
-			var uch *upstreamChannel
-			if net.conn != nil {
-				uch = net.conn.channels[ch.Name]
-			}
-
-			net.forEachDownstream(func(dc *downstreamConn) {
-				dc.SendMessage(&irc.Message{
-					Prefix:  dc.prefix(),
-					Command: "JOIN",
-					Params:  []string{dc.marshalEntity(net, ch.Name)},
-				})
-
-				if uch != nil {
-					forwardChannel(dc, uch)
-				}
-
-				if history != nil {
-					dc.sendNetworkHistory(net)
-				}
-			})
-		}
-	}
-
-	return nil
+func (net *network) detach(ch *Channel) {
+	if ch.Detached {
+		return
+	}
+	ch.Detached = true
+	net.user.srv.Logger.Printf("network %q: detaching channel %q", net.GetName(), ch.Name)
+
+	if net.conn != nil {
+		if uch, ok := net.conn.channels[ch.Name]; ok {
+			uch.updateAutoDetach(0)
+		}
+	}
+
+	net.forEachDownstream(func(dc *downstreamConn) {
+		net.offlineClients[dc.clientName] = struct{}{}
+
+		dc.SendMessage(&irc.Message{
+			Prefix:  dc.prefix(),
+			Command: "PART",
+			Params:  []string{dc.marshalEntity(net, ch.Name), "Detach"},
+		})
+	})
+}
+
+func (net *network) attach(ch *Channel) {
+	if !ch.Detached {
+		return
+	}
+	ch.Detached = false
+	net.user.srv.Logger.Printf("network %q: attaching channel %q", net.GetName(), ch.Name)
+
+	var uch *upstreamChannel
+	if net.conn != nil {
+		uch = net.conn.channels[ch.Name]
+
+		net.conn.updateChannelAutoDetach(ch.Name)
+	}
+
+	net.forEachDownstream(func(dc *downstreamConn) {
+		dc.SendMessage(&irc.Message{
+			Prefix:  dc.prefix(),
+			Command: "JOIN",
+			Params:  []string{dc.marshalEntity(net, ch.Name)},
+		})
+
+		if uch != nil {
+			forwardChannel(dc, uch)
+		}
+
+		if net.history[ch.Name] != nil {
+			dc.sendNetworkHistory(net)
+		}
+	})
 }
 
@@ -234,4 +242,10 @@
 		return fmt.Errorf("unknown channel %q", name)
 	}
+	if net.conn != nil {
+		if uch, ok := net.conn.channels[ch.Name]; ok {
+			uch.updateAutoDetach(0)
+		}
+	}
+
 	if err := net.user.srv.db.DeleteChannel(ch.ID); err != nil {
 		return err
@@ -399,4 +413,14 @@
 				uc.logger.Printf("failed to handle message %q: %v", msg, err)
 			}
+		case eventChannelDetach:
+			uc, name := e.uc, e.name
+			c, ok := uc.network.channels[name]
+			if !ok || c.Detached {
+				continue
+			}
+			uc.network.detach(c)
+			if err := uc.srv.db.StoreChannel(uc.network.ID, c); err != nil {
+				u.srv.Logger.Printf("failed to store updated detached channel %q: %v", c.Name, err)
+			}
 		case eventDownstreamConnected:
 			dc := e.dc
@@ -476,4 +500,8 @@
 	uc.endPendingLISTs(true)
 
+	for _, uch := range uc.channels {
+		uch.updateAutoDetach(0)
+	}
+
 	uc.forEachDownstream(func(dc *downstreamConn) {
 		dc.updateSupportedCaps()
