remove ability to disable default CTCP; config->Config, move Disable* fields into functions

This commit is contained in:
Liam Stanley 2017-02-08 02:55:38 -05:00
parent 68a5be5049
commit 82f34d9777
4 changed files with 114 additions and 78 deletions

6
cap.go

@ -25,7 +25,7 @@ var possibleCap = map[string][]string{
} }
func (c *Client) listCAP() error { func (c *Client) listCAP() error {
if !c.config.DisableTracking && !c.config.DisableCapTracking { if !c.Config.disableTracking && !c.Config.disableCapTracking {
if err := c.write(&Event{Command: CAP, Params: []string{CAP_LS, "302"}}); err != nil { if err := c.write(&Event{Command: CAP, Params: []string{CAP_LS, "302"}}); err != nil {
return err return err
} }
@ -37,8 +37,8 @@ func (c *Client) listCAP() error {
func possibleCapList(c *Client) map[string][]string { func possibleCapList(c *Client) map[string][]string {
out := make(map[string][]string) out := make(map[string][]string)
for k := range c.config.SupportedCaps { for k := range c.Config.SupportedCaps {
out[k] = c.config.SupportedCaps[k] out[k] = c.Config.SupportedCaps[k]
} }
for k := range possibleCap { for k := range possibleCap {

146
client.go

@ -20,8 +20,8 @@ import (
// Client contains all of the information necessary to run a single IRC // Client contains all of the information necessary to run a single IRC
// client. // client.
type Client struct { type Client struct {
// config represents the configuration // Config represents the configuration
config Config Config Config
// Events is a buffer of events waiting to be processed. // Events is a buffer of events waiting to be processed.
Events chan *Event Events chan *Event
@ -70,6 +70,7 @@ type Config struct {
User string User string
// Name is the "realname" that's used during connect. // Name is the "realname" that's used during connect.
Name string Name string
// Conn is an optional network connection to use (overrides TLSConfig). // Conn is an optional network connection to use (overrides TLSConfig).
Conn *net.Conn Conn *net.Conn
// TLSConfig is an optional user-supplied tls configuration, used during // TLSConfig is an optional user-supplied tls configuration, used during
@ -79,7 +80,8 @@ type Config struct {
// to the server after the last disconnect. // to the server after the last disconnect.
Retries int Retries int
// RateLimit is the delay in seconds between events sent to the server, // RateLimit is the delay in seconds between events sent to the server,
// with a burst of 4 messages. Set to -1 to disable. // with a burst of 4 messages. Set to -1 to disable. Cannot be changed
// once the client has been created.
RateLimit int RateLimit int
// Debugger is an optional, user supplied location to log the raw lines // Debugger is an optional, user supplied location to log the raw lines
// sent from the server, or other useful debug logs. Defaults to // sent from the server, or other useful debug logs. Defaults to
@ -97,22 +99,20 @@ type Config struct {
// ReconnectDelay is the a duration of time to delay before attempting a // ReconnectDelay is the a duration of time to delay before attempting a
// reconnection. Defaults to 10s (minimum of 10s). // reconnection. Defaults to 10s (minimum of 10s).
ReconnectDelay time.Duration ReconnectDelay time.Duration
// DisableTracking disables all channel and user-level tracking. Useful
// disableTracking disables all channel and user-level tracking. Useful
// for highly embedded scripts with single purposes. // for highly embedded scripts with single purposes.
DisableTracking bool disableTracking bool
// DisableDefaultCTCP disables all default CTCP responses. Though, any // disableCapTracking disables all network/server capability tracking.
// set CTCP's will override any pre-set ones, by default.
DisableDefaultCTCP bool
// DisableCapTracking disables all network/server capability tracking.
// This includes determining what feature the IRC server supports, what // This includes determining what feature the IRC server supports, what
// the "NETWORK=" variables are, and other useful stuff. DisableTracking // the "NETWORK=" variables are, and other useful stuff. DisableTracking
// cannot be enabled if you want to also tracking capabilities. // cannot be enabled if you want to also tracking capabilities.
DisableCapTracking bool disableCapTracking bool
// DisableNickCollision disables the clients auto-response to nickname // disableNickCollision disables the clients auto-response to nickname
// collisions. For example, if "test" is already in use, or is blocked by // collisions. For example, if "test" is already in use, or is blocked by
// the network/a service, the client will try and use "test_", then it // the network/a service, the client will try and use "test_", then it
// will attempt "test__", "test___", and so on. // will attempt "test__", "test___", and so on.
DisableNickCollision bool disableNickCollision bool
} }
// ErrNotConnected is returned if a method is used when the client isn't // ErrNotConnected is returned if a method is used when the client isn't
@ -137,26 +137,26 @@ func (e *ErrInvalidTarget) Error() string { return "invalid target: " + e.Target
// New creates a new IRC client with the specified server, name and config. // New creates a new IRC client with the specified server, name and config.
func New(config Config) *Client { func New(config Config) *Client {
client := &Client{ client := &Client{
config: config, Config: config,
Events: make(chan *Event, 100), // buffer 100 events max. Events: make(chan *Event, 100), // buffer 100 events max.
CTCP: newCTCP(), CTCP: newCTCP(),
initTime: time.Now(), initTime: time.Now(),
} }
if client.config.Debugger == nil { if client.Config.Debugger == nil {
client.config.Debugger = ioutil.Discard client.Config.Debugger = ioutil.Discard
} }
client.debug = log.New(client.config.Debugger, "debug:", log.Ltime|log.Lshortfile) client.debug = log.New(client.Config.Debugger, "debug:", log.Ltime|log.Lshortfile)
client.debug.Print("initializing debugging") client.debug.Print("initializing debugging")
// Setup the caller. // Setup the caller.
client.Callbacks = newCaller(client.debug) client.Callbacks = newCaller(client.debug)
// Setup a rate limiter if they requested one. // Setup a rate limiter if they requested one.
if client.config.RateLimit == 0 { if client.Config.RateLimit == 0 {
client.limiter = NewEventLimiter(4, 1*time.Second, client.write) client.limiter = NewEventLimiter(4, 1*time.Second, client.write)
} else if client.config.RateLimit > 0 { } else if client.Config.RateLimit > 0 {
client.limiter = NewEventLimiter(4, time.Duration(client.config.RateLimit)*time.Second, client.write) client.limiter = NewEventLimiter(4, time.Duration(client.Config.RateLimit)*time.Second, client.write)
} }
// Give ourselves a new state. // Give ourselves a new state.
@ -166,12 +166,56 @@ func New(config Config) *Client {
client.registerHandlers() client.registerHandlers()
// Register default CTCP responses. // Register default CTCP responses.
client.CTCP.disableDefault = client.config.DisableDefaultCTCP
client.CTCP.addDefaultHandlers() client.CTCP.addDefaultHandlers()
return client return client
} }
// DisableTracking disables all channel and user-level tracking, and clears
// all internal callbacks. Useful for highly embedded scripts with single
// purposes. This cannot be un-done.
func (c *Client) DisableTracking() {
c.debug.Print("disabling tracking")
c.Config.disableTracking = true
c.Callbacks.clearInternal()
c.state.mu.Lock()
c.state.channels = nil
c.state.mu.Unlock()
c.registerHandlers()
}
// DisableCapTracking disables all network/server capability tracking, and
// clears all internal callbacks. This includes determining what feature the
// IRC server supports, what the "NETWORK=" variables are, and other useful
// stuff. DisableTracking() cannot be called if you want to also track
// capabilities.
func (c *Client) DisableCapTracking() {
// No need to mess with internal callbacks. That should already be
// handled by the clear in Client.DisableTracking().
if c.Config.disableCapTracking {
return
}
c.debug.Print("disabling CAP tracking")
c.Config.disableCapTracking = true
c.Callbacks.clearInternal()
c.registerHandlers()
}
// DisableNickCollision disables the clients auto-response to nickname
// collisions. For example, if "test" is already in use, or is blocked by the
// network/a service, the client will try and use "test_", then it will
// attempt "test__", "test___", and so on.
func (c *Client) DisableNickCollision() {
c.debug.Print("disabling nick collision prevention")
c.Config.disableNickCollision = true
c.Callbacks.clearInternal()
c.state.mu.Lock()
c.state.channels = nil
c.state.mu.Unlock()
c.registerHandlers()
}
func (c *Client) cleanup(all bool) { func (c *Client) cleanup(all bool) {
if c.closeRead != nil { if c.closeRead != nil {
c.closeRead() c.closeRead()
@ -248,15 +292,15 @@ func (c *Client) Connect() error {
var err error var err error
// Sanity check a few options. // Sanity check a few options.
if c.config.Server == "" { if c.Config.Server == "" {
return errors.New("invalid server specified") return errors.New("invalid server specified")
} }
if c.config.Port < 21 || c.config.Port > 65535 { if c.Config.Port < 21 || c.Config.Port > 65535 {
return errors.New("invalid port (21-65535)") return errors.New("invalid port (21-65535)")
} }
if !IsValidNick(c.config.Nick) || !IsValidUser(c.config.User) { if !IsValidNick(c.Config.Nick) || !IsValidUser(c.Config.User) {
return errors.New("invalid nickname or user") return errors.New("invalid nickname or user")
} }
@ -266,11 +310,11 @@ func (c *Client) Connect() error {
c.debug.Printf("connecting to %s...", c.Server()) c.debug.Printf("connecting to %s...", c.Server())
// Allow the user to specify their own net.Conn. // Allow the user to specify their own net.Conn.
if c.config.Conn == nil { if c.Config.Conn == nil {
if c.config.TLSConfig == nil { if c.Config.TLSConfig == nil {
conn, err = net.Dial("tcp", c.Server()) conn, err = net.Dial("tcp", c.Server())
} else { } else {
conn, err = tls.Dial("tcp", c.Server(), c.config.TLSConfig) conn, err = tls.Dial("tcp", c.Server(), c.Config.TLSConfig)
} }
if err != nil { if err != nil {
return err return err
@ -278,7 +322,7 @@ func (c *Client) Connect() error {
c.state.conn = conn c.state.conn = conn
} else { } else {
c.state.conn = *c.config.Conn c.state.conn = *c.Config.Conn
} }
c.state.reader = newDecoder(c.state.conn) c.state.reader = newDecoder(c.state.conn)
@ -322,19 +366,19 @@ func (c *Client) Connect() error {
// connect to the IRC server. // connect to the IRC server.
func (c *Client) connectMessages() (events []*Event) { func (c *Client) connectMessages() (events []*Event) {
// Passwords first. // Passwords first.
if c.config.Password != "" { if c.Config.Password != "" {
events = append(events, &Event{Command: PASS, Params: []string{c.config.Password}}) events = append(events, &Event{Command: PASS, Params: []string{c.Config.Password}})
} }
// Then nickname. // Then nickname.
events = append(events, &Event{Command: NICK, Params: []string{c.config.Nick}}) events = append(events, &Event{Command: NICK, Params: []string{c.Config.Nick}})
// Then username and realname. // Then username and realname.
if c.config.Name == "" { if c.Config.Name == "" {
c.config.Name = c.config.User c.Config.Name = c.Config.User
} }
events = append(events, &Event{Command: USER, Params: []string{c.config.User, "+iw", "*"}, Trailing: c.config.Name}) events = append(events, &Event{Command: USER, Params: []string{c.Config.User, "+iw", "*"}, Trailing: c.Config.Name})
return events return events
} }
@ -361,26 +405,26 @@ func (c *Client) reconnect(remoteInvoked bool) (err error) {
return nil return nil
} }
if c.config.ReconnectDelay < (10 * time.Second) { if c.Config.ReconnectDelay < (10 * time.Second) {
c.config.ReconnectDelay = 25 * time.Second c.Config.ReconnectDelay = 25 * time.Second
} }
// Make sure we're not connected. // Make sure we're not connected.
c.Quit() c.Quit()
if c.config.Retries < 1 && !remoteInvoked { if c.Config.Retries < 1 && !remoteInvoked {
return errors.New("unexpectedly disconnected") return errors.New("unexpectedly disconnected")
} }
// Delay so we're not slaughtering the server with a bunch of // Delay so we're not slaughtering the server with a bunch of
// connections. // connections.
c.debug.Printf("reconnecting to %s in %s", c.Server(), c.config.ReconnectDelay) c.debug.Printf("reconnecting to %s in %s", c.Server(), c.Config.ReconnectDelay)
time.Sleep(c.config.ReconnectDelay) time.Sleep(c.Config.ReconnectDelay)
for err = c.Connect(); err != nil && c.tries < c.config.Retries; c.tries++ { for err = c.Connect(); err != nil && c.tries < c.Config.Retries; c.tries++ {
c.state.reconnecting = true c.state.reconnecting = true
c.debug.Printf("reconnecting to %s in %s (%d tries)", c.Server(), c.config.ReconnectDelay, c.tries) c.debug.Printf("reconnecting to %s in %s (%d tries)", c.Server(), c.Config.ReconnectDelay, c.tries)
time.Sleep(c.config.ReconnectDelay) time.Sleep(c.Config.ReconnectDelay)
} }
if err != nil { if err != nil {
@ -447,7 +491,7 @@ func (c *Client) Loop() {
// Server returns the string representation of host+port pair for net.Conn. // Server returns the string representation of host+port pair for net.Conn.
func (c *Client) Server() string { func (c *Client) Server() string {
return fmt.Sprintf("%s:%d", c.config.Server, c.config.Port) return fmt.Sprintf("%s:%d", c.Config.Server, c.Config.Port)
} }
// Lifetime returns the amount of time that has passed since the client was // Lifetime returns the amount of time that has passed since the client was
@ -518,13 +562,13 @@ func (c *Client) IsConnected() (connected bool) {
// GetNick returns the current nickname of the active connection. Returns // GetNick returns the current nickname of the active connection. Returns
// empty string if tracking is disabled. // empty string if tracking is disabled.
func (c *Client) GetNick() (nick string) { func (c *Client) GetNick() (nick string) {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("GetNick() used when tracking is disabled") panic("GetNick() used when tracking is disabled")
} }
c.state.mu.RLock() c.state.mu.RLock()
if c.state.nick == "" { if c.state.nick == "" {
nick = c.config.Nick nick = c.Config.Nick
} else { } else {
nick = c.state.nick nick = c.state.nick
} }
@ -550,7 +594,7 @@ func (c *Client) Nick(name string) error {
// Channels returns the active list of channels that the client is in. // Channels returns the active list of channels that the client is in.
// Panics if tracking is disabled. // Panics if tracking is disabled.
func (c *Client) Channels() []string { func (c *Client) Channels() []string {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("Channels() used when tracking is disabled") panic("Channels() used when tracking is disabled")
} }
@ -570,7 +614,7 @@ func (c *Client) Channels() []string {
// IsInChannel returns true if the client is in channel. Panics if tracking // IsInChannel returns true if the client is in channel. Panics if tracking
// is disabled. // is disabled.
func (c *Client) IsInChannel(channel string) bool { func (c *Client) IsInChannel(channel string) bool {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("Channels() used when tracking is disabled") panic("Channels() used when tracking is disabled")
} }
@ -903,7 +947,7 @@ func (c *Client) Whowas(nick string, amount int) error {
// nickLen, success := GetServerOption("MAXNICKLEN") // nickLen, success := GetServerOption("MAXNICKLEN")
// //
func (c *Client) GetServerOption(key string) (result string, ok bool) { func (c *Client) GetServerOption(key string) (result string, ok bool) {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("GetServerOption() used when tracking is disabled") panic("GetServerOption() used when tracking is disabled")
} }
@ -918,7 +962,7 @@ func (c *Client) GetServerOption(key string) (result string, ok bool) {
// as. May be empty if the server does not support RPL_MYINFO. Will panic if // as. May be empty if the server does not support RPL_MYINFO. Will panic if
// used when tracking has been disabled. // used when tracking has been disabled.
func (c *Client) ServerName() (name string) { func (c *Client) ServerName() (name string) {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("ServerName() used when tracking is disabled") panic("ServerName() used when tracking is disabled")
} }
@ -931,7 +975,7 @@ func (c *Client) ServerName() (name string) {
// May be empty if the server does not support RPL_ISUPPORT (or RPL_PROTOCTL). // May be empty if the server does not support RPL_ISUPPORT (or RPL_PROTOCTL).
// Will panic if used when tracking has been disabled. // Will panic if used when tracking has been disabled.
func (c *Client) NetworkName() (name string) { func (c *Client) NetworkName() (name string) {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("NetworkName() used when tracking is disabled") panic("NetworkName() used when tracking is disabled")
} }
@ -945,7 +989,7 @@ func (c *Client) NetworkName() (name string) {
// does not support RPL_MYINFO. Will panic if used when tracking has been // does not support RPL_MYINFO. Will panic if used when tracking has been
// disabled. // disabled.
func (c *Client) ServerVersion() (version string) { func (c *Client) ServerVersion() (version string) {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("ServerVersion() used when tracking is disabled") panic("ServerVersion() used when tracking is disabled")
} }
@ -957,7 +1001,7 @@ func (c *Client) ServerVersion() (version string) {
// ServerMOTD returns the servers message of the day, if the server has sent // ServerMOTD returns the servers message of the day, if the server has sent
// it upon connect. Will panic if used when tracking has been disabled. // it upon connect. Will panic if used when tracking has been disabled.
func (c *Client) ServerMOTD() (motd string) { func (c *Client) ServerMOTD() (motd string) {
if c.config.DisableTracking { if c.Config.disableTracking {
panic("ServerMOTD() used when tracking is disabled") panic("ServerMOTD() used when tracking is disabled")
} }

18
ctcp.go

@ -111,7 +111,6 @@ func encodeCTCPRaw(cmd, text string) (out string) {
// CTCP handles the storage and execution of CTCP handlers against incoming // CTCP handles the storage and execution of CTCP handlers against incoming
// CTCP events. // CTCP events.
type CTCP struct { type CTCP struct {
disableDefault bool
// mu is the mutex that should be used when accessing callbacks. // mu is the mutex that should be used when accessing callbacks.
mu sync.RWMutex mu sync.RWMutex
// handlers is a map of CTCP message -> functions. // handlers is a map of CTCP message -> functions.
@ -190,8 +189,7 @@ func (c *CTCP) SetBg(cmd string, handler func(client *Client, ctcp CTCPEvent)) {
}) })
} }
// Clear removes currently setup handler for cmd, if one is set. This will // Clear removes currently setup handler for cmd, if one is set.
// also disable default handlers for a specific cmd.
func (c *CTCP) Clear(cmd string) { func (c *CTCP) Clear(cmd string) {
if cmd = c.parseCMD(cmd); cmd == "" { if cmd = c.parseCMD(cmd); cmd == "" {
return return
@ -202,8 +200,7 @@ func (c *CTCP) Clear(cmd string) {
c.mu.Unlock() c.mu.Unlock()
} }
// ClearAll removes all currently setup and re-sets the default handlers, // ClearAll removes all currently setup and re-sets the default handlers.
// unless configured not to. See Client.Config.DisableDefaultCTCP.
func (c *CTCP) ClearAll() { func (c *CTCP) ClearAll() {
c.mu.Lock() c.mu.Lock()
c.handlers = map[string]CTCPHandler{} c.handlers = map[string]CTCPHandler{}
@ -217,13 +214,8 @@ func (c *CTCP) ClearAll() {
// implement a CTCP handler. // implement a CTCP handler.
type CTCPHandler func(client *Client, ctcp CTCPEvent) type CTCPHandler func(client *Client, ctcp CTCPEvent)
// addDefaultHandlers adds some useful default CTCP response handlers, unless // addDefaultHandlers adds some useful default CTCP response handlers.
// requested by the client not to.
func (c *CTCP) addDefaultHandlers() { func (c *CTCP) addDefaultHandlers() {
if c.disableDefault {
return
}
c.SetBg(CTCP_PING, handleCTCPPing) c.SetBg(CTCP_PING, handleCTCPPing)
c.SetBg(CTCP_PONG, handleCTCPPong) c.SetBg(CTCP_PONG, handleCTCPPong)
c.SetBg(CTCP_VERSION, handleCTCPVersion) c.SetBg(CTCP_VERSION, handleCTCPVersion)
@ -251,8 +243,8 @@ func handleCTCPPong(client *Client, ctcp CTCPEvent) {
// as the os type (darwin, linux, windows, etc) and architecture type (x86, // as the os type (darwin, linux, windows, etc) and architecture type (x86,
// arm, etc). // arm, etc).
func handleCTCPVersion(client *Client, ctcp CTCPEvent) { func handleCTCPVersion(client *Client, ctcp CTCPEvent) {
if client.config.Version != "" { if client.Config.Version != "" {
client.SendCTCPReply(ctcp.Source.Name, CTCP_VERSION, client.config.Version) client.SendCTCPReply(ctcp.Source.Name, CTCP_VERSION, client.Config.Version)
return return
} }

@ -21,7 +21,7 @@ func (c *Client) registerHandlers() {
})) }))
c.Callbacks.register(true, PING, CallbackFunc(handlePING)) c.Callbacks.register(true, PING, CallbackFunc(handlePING))
if !c.config.DisableTracking { if !c.Config.disableTracking {
// Joins/parts/anything that may add/remove/rename users. // Joins/parts/anything that may add/remove/rename users.
c.Callbacks.register(true, JOIN, CallbackFunc(handleJOIN)) c.Callbacks.register(true, JOIN, CallbackFunc(handleJOIN))
c.Callbacks.register(true, PART, CallbackFunc(handlePART)) c.Callbacks.register(true, PART, CallbackFunc(handlePART))
@ -51,24 +51,24 @@ func (c *Client) registerHandlers() {
c.Callbacks.register(true, NOTICE, CallbackFunc(updateLastActive)) c.Callbacks.register(true, NOTICE, CallbackFunc(updateLastActive))
c.Callbacks.register(true, TOPIC, CallbackFunc(updateLastActive)) c.Callbacks.register(true, TOPIC, CallbackFunc(updateLastActive))
c.Callbacks.register(true, KICK, CallbackFunc(updateLastActive)) c.Callbacks.register(true, KICK, CallbackFunc(updateLastActive))
// CAP IRCv3-specific tracking and functionality.
if !c.Config.disableCapTracking {
c.Callbacks.register(true, CAP, CallbackFunc(handleCAP))
c.Callbacks.register(true, CAP_CHGHOST, CallbackFunc(handleCHGHOST))
c.Callbacks.register(true, CAP_AWAY, CallbackFunc(handleAWAY))
c.Callbacks.register(true, CAP_ACCOUNT, CallbackFunc(handleACCOUNT))
c.Callbacks.register(true, ALLEVENTS, CallbackFunc(handleTags))
}
} }
// Nickname collisions. // Nickname collisions.
if !c.config.DisableNickCollision { if !c.Config.disableNickCollision {
c.Callbacks.register(true, ERR_NICKNAMEINUSE, CallbackFunc(nickCollisionHandler)) c.Callbacks.register(true, ERR_NICKNAMEINUSE, CallbackFunc(nickCollisionHandler))
c.Callbacks.register(true, ERR_NICKCOLLISION, CallbackFunc(nickCollisionHandler)) c.Callbacks.register(true, ERR_NICKCOLLISION, CallbackFunc(nickCollisionHandler))
c.Callbacks.register(true, ERR_UNAVAILRESOURCE, CallbackFunc(nickCollisionHandler)) c.Callbacks.register(true, ERR_UNAVAILRESOURCE, CallbackFunc(nickCollisionHandler))
} }
// CAP IRCv3-specific tracking and functionality.
if !c.config.DisableTracking && !c.config.DisableCapTracking {
c.Callbacks.register(true, CAP, CallbackFunc(handleCAP))
c.Callbacks.register(true, CAP_CHGHOST, CallbackFunc(handleCHGHOST))
c.Callbacks.register(true, CAP_AWAY, CallbackFunc(handleAWAY))
c.Callbacks.register(true, CAP_ACCOUNT, CallbackFunc(handleACCOUNT))
c.Callbacks.register(true, ALLEVENTS, CallbackFunc(handleTags))
}
c.Callbacks.mu.Unlock() c.Callbacks.mu.Unlock()
} }