Index: /trunk/doc/soju.1.scd
===================================================================
--- /trunk/doc/soju.1.scd	(revision 435)
+++ /trunk/doc/soju.1.scd	(revision 436)
@@ -171,4 +171,68 @@
 	Show a list of saved networks and their current status.
 
+*channel update* <name> [options...]
+	Update the options of an existing channel.
+
+	Options are:
+
+	*-relay-detached* <mode>
+		Set when to relay messages from detached channels to the user with a BouncerServ NOTICE.
+
+		Modes are:
+
+		*message*
+			Relay any message from this channel when detached.
+
+		*highlight*
+			Relay only messages mentioning you when detached.
+
+		*none*
+			Don't relay any messages from this channel when detached.
+
+		*default*
+			Currently same as *highlight*. This is the default behaviour.
+
+	*-reattach-on* <mode>
+		Set when to automatically reattach to detached channels.
+
+		Modes are:
+
+		*message*
+			Reattach to this channel when any message is received.
+
+		*highlight*
+			Reattach to this channel when any message mentioning you is received.
+
+		*none*
+			Never automatically reattach to this channel.
+
+		*default*
+			Currently same as *none*. This is the default behaviour.
+
+	*-detach-after* <duration>
+		Automatically detach this channel after the specified duration has elapsed without receving any message corresponding to *-detach-on*.
+
+		Example duration values: *1h30m*, *30s*, *2.5h*.
+
+		Setting this value to 0 will disable this behaviour, i.e. this channel will never be automatically detached. This is the default behaviour.
+
+	*-detach-on* <mode>
+		Set when to reset the auto-detach timer used by *-detach-after*, causing it to wait again for the auto-detach duration timer before detaching.
+		Joining, reattaching, sending a message, or changing any channel option will reset the timer, in addition to the messages specified by the mode.
+
+		Modes are:
+
+		*message*
+			Receiving any message from this channel will reset the auto-detach timer.
+
+		*highlight*
+			Receiving any message mentioning you from this channel will reset the auto-detach timer.
+
+		*none*
+			Receiving messages from this channel will not reset the auto-detach timer. Sending messages or joining the channel will still reset the timer.
+
+		*default*
+			Currently same as *message*. This is the default behaviour.
+
 *certfp generate* [options...] <network name>
 	Generate self-signed certificate and use it for authentication (via SASL
Index: /trunk/service.go
===================================================================
--- /trunk/service.go	(revision 435)
+++ /trunk/service.go	(revision 436)
@@ -219,4 +219,13 @@
 			handle: handlePasswordChange,
 		},
+		"channel": {
+			children: serviceCommandSet{
+				"update": {
+					usage:  "<name> [-relay-detached <default|none|highlight|message>] [-reattach-on <default|none|highlight|message>] [-detach-after <duration>] [-detach-on <default|none|highlight|message>]",
+					desc:   "update a channel",
+					handle: handleServiceChannelUpdate,
+				},
+			},
+		},
 	}
 }
@@ -428,5 +437,5 @@
 func handleServiceNetworkUpdate(dc *downstreamConn, params []string) error {
 	if len(params) < 1 {
-		return fmt.Errorf("expected exactly one argument")
+		return fmt.Errorf("expected at least one argument")
 	}
 
@@ -697,2 +706,83 @@
 	return nil
 }
+
+type channelFlagSet struct {
+	*flag.FlagSet
+	RelayDetached, ReattachOn, DetachAfter, DetachOn *string
+}
+
+func newChannelFlagSet() *channelFlagSet {
+	fs := &channelFlagSet{FlagSet: newFlagSet()}
+	fs.Var(stringPtrFlag{&fs.RelayDetached}, "relay-detached", "")
+	fs.Var(stringPtrFlag{&fs.ReattachOn}, "reattach-on", "")
+	fs.Var(stringPtrFlag{&fs.DetachAfter}, "detach-after", "")
+	fs.Var(stringPtrFlag{&fs.DetachOn}, "detach-on", "")
+	return fs
+}
+
+func (fs *channelFlagSet) update(channel *Channel) error {
+	if fs.RelayDetached != nil {
+		filter, err := parseFilter(*fs.RelayDetached)
+		if err != nil {
+			return err
+		}
+		channel.RelayDetached = filter
+	}
+	if fs.ReattachOn != nil {
+		filter, err := parseFilter(*fs.ReattachOn)
+		if err != nil {
+			return err
+		}
+		channel.ReattachOn = filter
+	}
+	if fs.DetachAfter != nil {
+		dur, err := time.ParseDuration(*fs.DetachAfter)
+		if err != nil || dur < 0 {
+			return fmt.Errorf("unknown duration for -detach-after %q (duration format: 0, 300s, 22h30m, ...)", *fs.DetachAfter)
+		}
+		channel.DetachAfter = dur
+	}
+	if fs.DetachOn != nil {
+		filter, err := parseFilter(*fs.DetachOn)
+		if err != nil {
+			return err
+		}
+		channel.DetachOn = filter
+	}
+	return nil
+}
+
+func handleServiceChannelUpdate(dc *downstreamConn, params []string) error {
+	if len(params) < 1 {
+		return fmt.Errorf("expected at least one argument")
+	}
+	name := params[0]
+
+	fs := newChannelFlagSet()
+	if err := fs.Parse(params[1:]); err != nil {
+		return err
+	}
+
+	uc, upstreamName, err := dc.unmarshalEntity(name)
+	if err != nil {
+		return fmt.Errorf("unknown channel %q", name)
+	}
+
+	ch, ok := uc.network.channels[upstreamName]
+	if !ok {
+		return fmt.Errorf("unknown channel %q", name)
+	}
+
+	if err := fs.update(ch); err != nil {
+		return err
+	}
+
+	uc.updateChannelAutoDetach(upstreamName)
+
+	if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
+		return fmt.Errorf("failed to update channel: %v", err)
+	}
+
+	sendServicePRIVMSG(dc, fmt.Sprintf("updated channel %q", name))
+	return nil
+}
